1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
|
/* SPDX-License-Identifier: GPL-2.0 */
/** \file audiod_command.c Commands for para_audiod. */
#include "para.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <netdb.h>
#include <lopsub.h>
#include "audiod.lsg.h"
#include "lsu.h"
#include "audiod_cmd.lsg.h"
#include "list.h"
#include "sched.h"
#include "buffer_tree.h"
#include "filter.h"
#include "grab_client.h"
#include "error.h"
#include "sideband.h"
#include "audiod.h"
#include "net.h"
#include "daemon.h"
#include "string.h"
#include "write.h"
#include "fd.h"
/** \cond doxygen_exclude */
#define OPT_GIVEN(_opt, _cmd, _lpr) (lls_opt_given(lls_opt_result( \
LSG_AUDIOD_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr)))
extern struct sched *sched;
extern char *stat_item_values[NUM_STAT_ITEMS];
/* The maximal number of simultaneous connections. */
#define MAX_STAT_CLIENTS 50
/* Pointer to a command handler function. */
typedef int (*audiod_cmd_handler_t)(int, struct lls_parse_result *);
/* The lopsub user_data pointer. Only the command handler at the moment. */
struct audiod_command_info {
audiod_cmd_handler_t handler; /**< Implementation of the command. */
};
/* Define the user_data pointer as expected by lopsub. */
#define EXPORT_AUDIOD_CMD_HANDLER(_cmd) \
/** Implementation of _cmd. */ \
const struct audiod_command_info lsg_audiod_cmd_com_ ## _cmd ## _user_data = { \
.handler = com_ ## _cmd \
};
/* Flags used for the stat command of para_audiod. */
enum stat_client_flags {
/** Enable parser-friendly output. */
SCF_PARSER_FRIENDLY = 1,
};
static INITIALIZED_LIST_HEAD(client_list);
static int num_clients;
/* The list of all status items used by para_{server,audiod,gui}. */
const char *status_item_list[] = {STATUS_ITEMS};
/*
* Describes one status client of para_audiod.
*
* There is one instance of this structure per audiod client that has sent
* the 'stat' command. These instances are organized as a linked list.
*/
struct stat_client {
/* The stat client's file descriptor. */
int fd;
/* Bitmask of those status items the client is interested in. */
uint64_t item_mask;
/* See stat_client flags. */
unsigned flags;
/* Its entry in the list of stat clients. */
struct list_head node;
};
/** \endcond */
/*
* Add a status client to the client list and increment num_clients.
* The mask parameter specifies which status items are sent to the client.
*/
static int stat_client_add(int fd, uint64_t mask, unsigned flags)
{
struct stat_client *new_client;
int ret;
if (num_clients >= MAX_STAT_CLIENTS) {
PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
MAX_STAT_CLIENTS);
return -E_TOO_MANY_CLIENTS;
}
ret = dup(fd);
if (ret < 0)
return -ERRNO_TO_PARA_ERROR(errno);
new_client = zalloc(sizeof(*new_client));
new_client->fd = ret;
PARA_INFO_LOG("adding client on fd %d\n", new_client->fd);
new_client->item_mask = mask;
new_client->flags = flags;
para_list_add(&new_client->node, &client_list);
num_clients++;
return 1;
}
static void close_stat_client(struct stat_client *sc)
{
PARA_INFO_LOG("closing client fd %d\n", sc->fd);
close(sc->fd);
list_del(&sc->node);
free(sc);
num_clients--;
}
/**
* Empty the status clients list.
*
* This iterates over the list of connected status clients, closes each client
* file descriptor and frees the resources.
*/
void close_stat_clients(void)
{
struct stat_client *sc, *tmp;
list_for_each_entry_safe(sc, tmp, &client_list, node)
close_stat_client(sc);
assert(num_clients == 0);
}
/**
* Send a sideband packet to a client.
*
* Create a header and send header and buffer to the client using \ref
* xwritev().
*
* \param fd Identifies the client.
* \param buf May be NULL.
* \param len Number of bytes to send, not including header.
* \param band Stored as part of the header.
*
* \return Standard.
*/
int client_write_bin(int fd, char *buf, size_t len, enum sb_designator band)
{
struct sb_context *ctx;
struct sb_buffer sbb;
struct iovec iov[2];
int ret;
sbb.iov.iov_base = buf;
sbb.iov.iov_len = len;
sbb.band = band;
ctx = sb_new_send(&sbb, true, NULL, NULL);
do {
ret = sb_get_send_buffers(ctx, iov);
ret = xwritev(fd, iov, ret);
if (ret < 0) {
sb_free(ctx);
return ret;
}
} while (sb_sent(ctx, ret) == false);
return 1;
}
static int client_write(int fd, char *buf, enum sb_designator band)
{
return client_write_bin(fd, buf, buf? strlen(buf) : 0, band);
}
/**
* Write the contents of a status item to all connected status clients.
*
* \param item_num The number of the status item.
*
* Removes the status client from the client list on write errors.
*/
void stat_client_write_item(int item_num)
{
struct stat_client *sc, *tmp;
struct para_buffer pb = {.flags = 0};
struct para_buffer pfpb = {.flags = PBF_SIZE_PREFIX};
const uint64_t one = 1;
char *msg = stat_item_values[item_num];
struct para_buffer *b;
list_for_each_entry_safe(sc, tmp, &client_list, node) {
int ret;
if (!((one << item_num) & sc->item_mask))
continue;
b = (sc->flags & SCF_PARSER_FRIENDLY)? &pfpb : &pb;
if (!b->buf)
WRITE_STATUS_ITEM(b, item_num, "%s\n", msg? msg : "");
ret = client_write(sc->fd, b->buf, SBD_OUTPUT);
if (ret < 0) {
PARA_INFO_LOG("%s: closing stat client on fd %d\n",
para_strerror(-ret), sc->fd);
close_stat_client(sc);
}
}
free(pb.buf);
free(pfpb.buf);
}
/* Check if the given string is a known status item and return its index. */
static int stat_item_valid(const char *item)
{
int i;
if (!item || !*item) {
PARA_ERROR_LOG("%s\n", "no item");
return -E_UNKNOWN_STAT_ITEM;
}
FOR_EACH_STATUS_ITEM(i)
if (!strcmp(status_item_list[i], item))
return i;
PARA_ERROR_LOG("invalid stat item: %s\n", item);
return -E_UNKNOWN_STAT_ITEM;
}
__malloc static char *audiod_status_string(void)
{
const char *status = (audiod_status == AUDIOD_ON)?
"on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
return para_strdup(status);
}
static int com_help(int fd, struct lls_parse_result *lpr)
{
char *buf;
int ret, ret2;
bool long_help = OPT_GIVEN(LONG, HELP, lpr);
ret = lsu_com_help(long_help, lpr, audiod_cmd_suite, NULL, &buf, NULL);
ret2 = client_write(fd, buf, ret < 0? SBD_ERROR_LOG : SBD_OUTPUT);
free(buf);
return ret < 0? ret : ret2;
}
EXPORT_AUDIOD_CMD_HANDLER(help)
static int com_ll(int fd, struct lls_parse_result *lpr)
{
unsigned ll;
char *errctx;
const char *sev[] = {SEVERITIES};
const char *arg;
int ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
if (ret < 0) {
char *tmp = make_message("%s\n", errctx);
free(errctx);
client_write(fd, tmp, SBD_ERROR_LOG);
free(tmp);
return ret;
}
if (lls_num_inputs(lpr) == 0) {
char *msg;
ll = daemon_get_loglevel();
msg = make_message("%s\n", sev[ll]);
ret = client_write(fd, msg, SBD_OUTPUT);
free(msg);
return ret;
}
arg = lls_input(0, lpr);
for (ll = 0; ll < NUM_LOGLEVELS; ll++) {
if (!strcmp(arg, sev[ll]))
break;
}
if (ll >= NUM_LOGLEVELS)
return -ERRNO_TO_PARA_ERROR(EINVAL);
PARA_INFO_LOG("new log level: %s\n", sev[ll]);
daemon_set_loglevel(ll);
return 1;
}
EXPORT_AUDIOD_CMD_HANDLER(ll)
static int com_tasks(int fd, __a_unused struct lls_parse_result *lpr)
{
int ret;
char *tl = get_task_list(sched);
if (!tl) /* no tasks registered yet */
return 0;
ret = client_write(fd, tl, SBD_OUTPUT);
free(tl);
return ret;
}
EXPORT_AUDIOD_CMD_HANDLER(tasks)
static int com_stat(int fd, struct lls_parse_result *lpr)
{
int i, ret;
bool parser_friendly = OPT_GIVEN(PARSER_FRIENDLY, STAT, lpr);
bool one_shot = OPT_GIVEN(ONE_SHOT, STAT, lpr);
uint64_t mask = 0;
const uint64_t one = 1;
struct para_buffer b = {.flags = 0};
unsigned num_inputs, client_flags = 0;
ret = mark_fd_nonblocking(fd);
if (ret < 0)
return ret;
if (parser_friendly) {
b.flags = PBF_SIZE_PREFIX;
client_flags |= SCF_PARSER_FRIENDLY;
}
num_inputs = lls_num_inputs(lpr);
if (num_inputs == 0)
mask--; /* set all bits */
for (i = 0; i < num_inputs; i++) {
ret = stat_item_valid(lls_input(i, lpr));
if (ret < 0)
return ret;
mask |= (one << ret);
}
PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask);
FOR_EACH_STATUS_ITEM(i) {
char *item = stat_item_values[i];
if (!((one << i) & mask))
continue;
WRITE_STATUS_ITEM(&b, i, "%s\n", item? item : "");
}
ret = client_write(fd, b.buf, SBD_OUTPUT);
free(b.buf);
if (ret < 0)
return ret;
if (one_shot)
return 0;
ret = stat_client_add(fd, mask, client_flags);
return ret < 0? ret : -E_SUBCMD_RUNNING;
}
EXPORT_AUDIOD_CMD_HANDLER(stat)
static int com_grab(int fd, struct lls_parse_result *lpr)
{
char buf[] = "The grab subcommand is deprecated.\n";
int ret;
PARA_WARNING_LOG("running deprecated grab subcommand on fd %d\n", fd);
ret = client_write(fd, buf, SBD_WARNING_LOG);
if (ret < 0)
return ret;
ret = grab_client_new(fd, lpr, sched);
return ret < 0? ret : -E_SUBCMD_RUNNING;
}
EXPORT_AUDIOD_CMD_HANDLER(grab)
static int com_term(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
{
return -E_AUDIOD_TERM;
}
EXPORT_AUDIOD_CMD_HANDLER(term)
static int com_on(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
{
audiod_status = AUDIOD_ON;
return 1;
}
EXPORT_AUDIOD_CMD_HANDLER(on)
static int com_off(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
{
audiod_status = AUDIOD_OFF;
return 1;
}
EXPORT_AUDIOD_CMD_HANDLER(off)
static int com_sb(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
{
audiod_status = AUDIOD_STANDBY;
return 1;
}
EXPORT_AUDIOD_CMD_HANDLER(sb)
static int com_cycle(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
{
switch (audiod_status) {
case AUDIOD_ON: audiod_status = AUDIOD_STANDBY; break;
case AUDIOD_OFF: audiod_status = AUDIOD_ON; break;
case AUDIOD_STANDBY: audiod_status = AUDIOD_OFF; break;
}
return 1;
}
EXPORT_AUDIOD_CMD_HANDLER(cycle)
static int com_version(int fd, struct lls_parse_result *lpr)
{
int ret;
char *msg;
bool verbose = OPT_GIVEN(VERBOSE, VERSION, lpr);
if (verbose)
msg = make_message("%s", version_text("audiod"));
else
msg = make_message("%s\n", version_single_line("audiod"));
ret = client_write(fd, msg, SBD_OUTPUT);
free(msg);
return ret < 0? ret : 0;
}
EXPORT_AUDIOD_CMD_HANDLER(version)
#ifndef HAVE_UCRED
static int recv_cred_buffer(int fd, struct iovec *iov, uid_t *uid)
{
*uid = 1;
return recv_buffer(fd, iov->iov_base, iov->iov_len);
}
#else /* HAVE_UCRED */
static void dispose_fds(int *fds, unsigned num)
{
int i;
for (i = 0; i < num; i++)
close(fds[i]);
}
/*
* Receive a buffer and the Unix credentials of the sending process.
* Returns negative on errors, the number of bytes received on success. The
* user id of the sending process is returned via the uid pointer argument.
*/
static int recv_cred_buffer(int fd, struct iovec *iov, uid_t *uid)
{
char control[255] __a_aligned(8);
struct msghdr msg;
struct cmsghdr *cmsg;
int ret, yes = 1;
struct ucred cred;
bool have_uid = false;
*uid = -1;
setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &yes, sizeof(int));
memset(&msg, 0, sizeof(msg));
memset(iov->iov_base, 0, iov->iov_len);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
ret = recvmsg(fd, &msg, 0);
if (ret < 0)
return -ERRNO_TO_PARA_ERROR(errno);
cmsg = CMSG_FIRSTHDR(&msg);
while (cmsg) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type
== SCM_CREDENTIALS) {
memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred));
*uid = cred.uid;
have_uid = true;
} else
if (cmsg->cmsg_level == SOL_SOCKET
&& cmsg->cmsg_type == SCM_RIGHTS) {
dispose_fds((int *)CMSG_DATA(cmsg),
(cmsg->cmsg_len - CMSG_LEN(0))
/ sizeof(int));
}
cmsg = CMSG_NXTHDR(&msg, cmsg);
}
return have_uid? ret: -E_SCM_CREDENTIALS;
}
#endif /* HAVE_UCRED */
/**
* Handle arriving connections on the local socket.
*
* \param accept_fd The fd to accept connections on.
*
* This is called in each iteration of the main loop of the scheduler. If there
* is an incoming connection, the function reads the command sent by the peer,
* checks the connecting user's permissions by using unix socket credentials
* (if supported by the OS) and calls the corresponding command handler if
* permissions are OK.
*
* \return Positive on success, negative on errors, zero if there was no
* connection to accept.
*
* \sa \ref para_accept().
*/
int dispatch_local_connection(int accept_fd)
{
int argc, ret, clifd;
char buf[MAXLINE], **argv = NULL;
struct iovec iov = {.iov_base = buf, .iov_len = sizeof(buf) - 1};
struct sockaddr_un unix_addr;
uid_t uid;
const struct lls_command *cmd;
struct lls_parse_result *lpr;
char *errctx = NULL;
const struct audiod_command_info *aci;
size_t received;
char *p, *end;
ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un), &clifd);
if (ret <= 0)
return ret;
ret = recv_cred_buffer(clifd, &iov, &uid);
if (ret < 0)
goto out;
received = ret;
#ifdef HAVE_UCRED
ret = -E_UCRED_PERM;
if (!uid_is_whitelisted(uid))
goto close_clifd;
#endif
p = buf + 1;
end = buf + received;
argc = 0;
while (p < end) {
argc++;
p = memchr(p, '\0', end - p);
ret = -ERRNO_TO_PARA_ERROR(EINVAL);
if (!p)
goto close_clifd;
p++;
}
argv = alloc((argc + 1) * sizeof(char *));
p = buf + 1;
for (unsigned n = 0; n < argc; n++) {
argv[n] = para_strdup(p);
p += strlen(p) + 1;
}
argv[argc] = NULL;
ret = lls(lls_lookup_subcmd(argv[0], audiod_cmd_suite, &errctx));
if (ret < 0)
goto free_argv;
cmd = lls_cmd(ret, audiod_cmd_suite);
ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
if (ret < 0)
goto free_argv;
aci = lls_user_data(cmd);
ret = aci->handler(clifd, lpr);
lls_free_parse_result(lpr, cmd);
free_argv:
free_argv(argv);
close_clifd:
if (ret == -E_CLIENT_WRITE)
goto out;
if (ret == -E_SUBCMD_RUNNING) {
ret = 0;
goto out;
}
if (ret < 0) {
char *tmp;
if (errctx) {
tmp = make_message("errctx: %s\n", errctx);
free(errctx);
client_write(clifd, tmp, SBD_ERROR_LOG);
free(tmp);
}
tmp = make_message("%s\n", para_strerror(-ret));
client_write(clifd, tmp, SBD_ERROR_LOG);
free(tmp);
client_write(clifd, NULL, ret == -E_AUDIOD_TERM?
SBD_EXIT__SUCCESS : SBD_EXIT__FAILURE);
goto out;
}
client_write(clifd, NULL, SBD_EXIT__SUCCESS);
out:
close(clifd);
return ret;
}
/**
* Send the current audiod status to all connected stat clients.
*
* \param force Whether to write unchanged items.
*/
void audiod_status_dump(bool force)
{
char *old, *new;
old = stat_item_values[SI_play_time];
new = get_time_string();
if (new) {
if (force || !old || strcmp(old, new)) {
free(old);
stat_item_values[SI_play_time] = new;
stat_client_write_item(SI_play_time);
} else
free(new);
}
new = daemon_get_uptime_str(now);
old = stat_item_values[SI_audiod_uptime];
if (force || !old || strcmp(old, new)) {
free(old);
stat_item_values[SI_audiod_uptime] = new;
stat_client_write_item(SI_audiod_uptime);
} else
free(new);
old = stat_item_values[SI_audiod_status];
new = audiod_status_string();
if (force || !old || strcmp(old, new)) {
free(old);
stat_item_values[SI_audiod_status] = new;
stat_client_write_item(SI_audiod_status);
} else
free(new);
old = stat_item_values[SI_decoder_flags];
new = audiod_get_decoder_flags();
if (force || !old || strcmp(old, new)) {
free(old);
stat_item_values[SI_decoder_flags] = new;
stat_client_write_item(SI_decoder_flags);
} else
free(new);
}
|