audiod: Clean up fd closing logic in command handlers.
[paraslash.git] / audiod_command.c
1 /*
2 * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
3 *
4 * Licensed under the GPL v2. For licencing details see COPYING.
5 */
6
7 /** \file audiod_command.c Commands for para_audiod. */
8
9 #include <netinet/in.h>
10 #include <sys/socket.h>
11 #include <regex.h>
12 #include <sys/types.h>
13 #include <arpa/inet.h>
14 #include <sys/un.h>
15 #include <netdb.h>
16
17 #include "para.h"
18 #include "audiod.cmdline.h"
19 #include "audiod.command_list.h"
20 #include "list.h"
21 #include "sched.h"
22 #include "ggo.h"
23 #include "buffer_tree.h"
24 #include "filter.h"
25 #include "grab_client.h"
26 #include "error.h"
27 #include "audiod.h"
28 #include "net.h"
29 #include "daemon.h"
30 #include "string.h"
31 #include "write.h"
32 #include "fd.h"
33 #include "version.h"
34
35 extern struct sched sched;
36 extern char *stat_item_values[NUM_STAT_ITEMS];
37
38 /* Defines one command of para_audiod. */
39 struct audiod_command {
40 const char *name;
41 /* Pointer to the function that handles the command. */
42 int (*handler)(int, int, char **);
43 /* One-line description. */
44 const char *description;
45 /* Summary of the command line options. */
46 const char *usage;
47 /* The long help text. */
48 const char *help;
49 };
50
51 static struct audiod_command audiod_cmds[] = {DEFINE_AUDIOD_CMD_ARRAY};
52
53 /** Iterate over the array of all audiod commands. */
54 #define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++)
55
56 /** The maximal number of simultaneous connections. */
57 #define MAX_STAT_CLIENTS 50
58
59 /** Flags used for the stat command of para_audiod. */
60 enum stat_client_flags {
61 /** Enable parser-friendly output. */
62 SCF_PARSER_FRIENDLY = 1,
63 };
64
65 /**
66 * Describes a status client of para_audiod.
67 *
68 * There's one such structure per audiod client that sent the 'stat' command.
69 *
70 * A status client is identified by its file descriptor. para_audiod
71 * keeps a list of connected status clients.
72 */
73 struct stat_client {
74 /** The stat client's file descriptor. */
75 int fd;
76 /** Bitmask of those status items the client is interested in. */
77 uint64_t item_mask;
78 /** See \ref stat_client flags. s*/
79 unsigned flags;
80 /** Its entry in the list of stat clients. */
81 struct list_head node;
82 };
83
84 static INITIALIZED_LIST_HEAD(client_list);
85 static int num_clients;
86
87 /** The list of all status items used by para_{server,audiod,gui}. */
88 const char *status_item_list[] = {STATUS_ITEM_ARRAY};
89
90 static void dump_stat_client_list(void)
91 {
92 struct stat_client *sc;
93
94 list_for_each_entry(sc, &client_list, node)
95 PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
96 }
97 /**
98 * Add a status client to the list.
99 *
100 * \param fd The file descriptor of the client.
101 * \param mask Bitfield of status items for this client.
102 * \param parser_friendly Enable parser-friendly output mode.
103 *
104 * Only those status items having the bit set in \a mask will be
105 * sent to the client.
106 *
107 * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
108 * the number of connected clients exceeds #MAX_STAT_CLIENTS.
109 */
110 static int stat_client_add(int fd, uint64_t mask, int parser_friendly)
111 {
112 struct stat_client *new_client;
113 int ret;
114
115 if (num_clients >= MAX_STAT_CLIENTS) {
116 PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
117 MAX_STAT_CLIENTS);
118 return -E_TOO_MANY_CLIENTS;
119 }
120 ret = dup(fd);
121 if (ret < 0)
122 return -ERRNO_TO_PARA_ERROR(errno);
123 new_client = para_calloc(sizeof(*new_client));
124 new_client->fd = ret;
125 PARA_INFO_LOG("adding client on fd %d\n", new_client->fd);
126 new_client->item_mask = mask;
127 if (parser_friendly)
128 new_client->flags = SCF_PARSER_FRIENDLY;
129 para_list_add(&new_client->node, &client_list);
130 dump_stat_client_list();
131 num_clients++;
132 return 1;
133 }
134
135 static void close_stat_client(struct stat_client *sc)
136 {
137 PARA_INFO_LOG("closing client fd %d\n", sc->fd);
138 close(sc->fd);
139 list_del(&sc->node);
140 free(sc);
141 num_clients--;
142 }
143
144 /**
145 * Empty the status clients list.
146 *
147 * This iterates over the list of connected status clients, closes each client
148 * file descriptor and frees the resources.
149 */
150 void close_stat_clients(void)
151 {
152 struct stat_client *sc, *tmp;
153
154 list_for_each_entry_safe(sc, tmp, &client_list, node)
155 close_stat_client(sc);
156 assert(num_clients == 0);
157 }
158
159 /**
160 * Write a message to all connected status clients.
161 *
162 * \param item_num The number of the status item of \a msg.
163 *
164 * On write errors, remove the status client from the client list and close its
165 * file descriptor.
166 */
167 void stat_client_write_item(int item_num)
168 {
169 struct stat_client *sc, *tmp;
170 struct para_buffer pb = {.flags = 0};
171 struct para_buffer pfpb = {.flags = PBF_SIZE_PREFIX};
172 const uint64_t one = 1;
173 char *msg = stat_item_values[item_num];
174 struct para_buffer *b;
175
176 list_for_each_entry_safe(sc, tmp, &client_list, node) {
177 int ret;
178
179 if (!((one << item_num) & sc->item_mask))
180 continue;
181 b = (sc->flags & SCF_PARSER_FRIENDLY)? &pfpb : &pb;
182 if (!b->buf)
183 (void)WRITE_STATUS_ITEM(b, item_num, "%s\n",
184 msg? msg : "");
185 ret = write(sc->fd, b->buf, b->offset);
186 if (ret == b->offset)
187 continue;
188 /* write error or short write */
189 close_stat_client(sc);
190 dump_stat_client_list();
191 }
192 free(pb.buf);
193 free(pfpb.buf);
194 }
195
196 /**
197 * Check if string is a known status item.
198 *
199 * \param item Buffer containing the text to check.
200 *
201 * \return If \a item is a valid status item, the number of that status item is
202 * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM.
203 */
204 static int stat_item_valid(const char *item)
205 {
206 int i;
207 if (!item || !*item) {
208 PARA_ERROR_LOG("%s\n", "no item");
209 return -E_UNKNOWN_STAT_ITEM;
210 }
211 FOR_EACH_STATUS_ITEM(i)
212 if (!strcmp(status_item_list[i], item))
213 return i;
214 PARA_ERROR_LOG("invalid stat item: %s\n", item);
215 return -E_UNKNOWN_STAT_ITEM;
216 }
217
218 static int client_write(int fd, const char *buf)
219 {
220 size_t len;
221
222 if (!buf)
223 return 0;
224 len = strlen(buf);
225 return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
226 }
227
228 __malloc static char *audiod_status_string(void)
229 {
230 const char *status = (audiod_status == AUDIOD_ON)?
231 "on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
232 return para_strdup(status);
233 }
234
235 static int dump_commands(int fd)
236 {
237 char *buf = para_strdup(""), *tmp = NULL;
238 int i;
239 ssize_t ret;
240
241 FOR_EACH_COMMAND(i) {
242 tmp = make_message("%s%s\t%s\n", buf, audiod_cmds[i].name,
243 audiod_cmds[i].description);
244 free(buf);
245 buf = tmp;
246 }
247 ret = client_write(fd, buf);
248 free(buf);
249 return ret;
250 }
251
252 static int com_help(int fd, int argc, char **argv)
253 {
254 int i, ret;
255 char *buf;
256 const char *dflt = "No such command. Available commands:\n";
257
258 if (argc < 2)
259 return dump_commands(fd);
260 FOR_EACH_COMMAND(i) {
261 if (strcmp(audiod_cmds[i].name, argv[1]))
262 continue;
263 buf = make_message(
264 "NAME\n\t%s -- %s\n"
265 "SYNOPSIS\n\tpara_audioc %s\n"
266 "DESCRIPTION\n%s\n",
267 argv[1],
268 audiod_cmds[i].description,
269 audiod_cmds[i].usage,
270 audiod_cmds[i].help
271 );
272 ret = client_write(fd, buf);
273 free(buf);
274 return ret;
275 }
276 ret = client_write(fd, dflt);
277 if (ret > 0)
278 ret = dump_commands(fd);
279 return ret;
280 }
281
282 static int com_tasks(int fd, __a_unused int argc, __a_unused char **argv)
283 {
284 char *tl = get_task_list(&sched);
285 int ret = 1;
286
287 if (tl)
288 ret = client_write(fd, tl);
289 free(tl);
290 return ret;
291 }
292
293 static int com_stat(int fd, int argc, char **argv)
294 {
295 int i, ret, parser_friendly = 0;
296 uint64_t mask = 0;
297 const uint64_t one = 1;
298 struct para_buffer b = {.flags = 0};
299
300 ret = mark_fd_nonblocking(fd);
301 if (ret < 0)
302 return ret;
303 for (i = 1; i < argc; i++) {
304 const char *arg = argv[i];
305 if (arg[0] != '-')
306 break;
307 if (!strcmp(arg, "--")) {
308 i++;
309 break;
310 }
311 if (!strncmp(arg, "-p", 2)) {
312 parser_friendly = 1;
313 b.flags = PBF_SIZE_PREFIX;
314 }
315 }
316 if (i >= argc)
317 mask--; /* set all bits */
318 for (; i < argc; i++) {
319 ret = stat_item_valid(argv[i]);
320 if (ret < 0)
321 return ret;
322 mask |= (one << ret);
323 }
324 PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask);
325 FOR_EACH_STATUS_ITEM(i) {
326 char *item = stat_item_values[i];
327 if (!((one << i) & mask))
328 continue;
329 (void)WRITE_STATUS_ITEM(&b, i, "%s\n", item? item : "");
330 }
331 ret = client_write(fd, b.buf);
332 if (ret >= 0)
333 ret = stat_client_add(fd, mask, parser_friendly);
334 free(b.buf);
335 return ret;
336 }
337
338 static int com_grab(int fd, int argc, char **argv)
339 {
340 return grab_client_new(fd, argc, argv, &sched);
341 }
342
343 static int com_term(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
344 {
345 return -E_AUDIOD_TERM;
346 }
347
348 static int com_on(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
349 {
350 audiod_status = AUDIOD_ON;
351 return 1;
352 }
353
354 static int com_off(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
355 {
356 audiod_status = AUDIOD_OFF;
357 return 1;
358 }
359
360 static int com_sb(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
361 {
362 audiod_status = AUDIOD_STANDBY;
363 return 1;
364 }
365
366 static int com_cycle(__a_unused int fd, int argc, char **argv)
367 {
368 switch (audiod_status) {
369 case AUDIOD_ON:
370 return com_sb(fd, argc, argv);
371 break;
372 case AUDIOD_OFF:
373 return com_on(fd, argc, argv);
374 break;
375 case AUDIOD_STANDBY:
376 return com_off(fd, argc, argv);
377 break;
378 }
379 return 1;
380 }
381
382 static int com_version(int fd, int argc, char **argv)
383 {
384 int ret;
385 char *msg;
386
387 if (argc > 1 && strcmp(argv[1], "-v") == 0)
388 msg = make_message("%s", version_text("audiod"));
389 else
390 msg = make_message("%s\n", version_single_line("audiod"));
391 ret = client_write(fd, msg);
392 free(msg);
393 return ret;
394 }
395
396 static int check_perms(uid_t uid, uid_t *whitelist)
397 {
398 int i;
399
400 if (!conf.user_allow_given)
401 return 1;
402 for (i = 0; i < conf.user_allow_given; i++)
403 if (uid == whitelist[i])
404 return 1;
405 return -E_UCRED_PERM;
406 }
407
408 /**
409 * Handle arriving connections on the local socket.
410 *
411 * \param accept_fd The fd to accept connections on.
412 * \param rfds If \a accept_fd is not set in \a rfds, do nothing.
413 * \param uid_whitelist Array of UIDs which are allowed to connect.
414 *
415 * This is called in each iteration of the select loop. If there is an incoming
416 * connection on \a accept_fd, this function reads the command sent by the peer,
417 * checks the connecting user's permissions by using unix socket credentials
418 * (if supported by the OS) and calls the corresponding command handler if
419 * permissions are OK.
420 *
421 * \return Positive on success, negative on errors, zero if there was no
422 * connection to accept.
423 *
424 * \sa para_accept(), recv_cred_buffer()
425 * */
426 int handle_connect(int accept_fd, fd_set *rfds, uid_t *uid_whitelist)
427 {
428 int i, argc, ret, clifd;
429 char buf[MAXLINE], **argv = NULL;
430 struct sockaddr_un unix_addr;
431 uid_t uid;
432
433 ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd);
434 if (ret <= 0)
435 return ret;
436 ret = recv_cred_buffer(clifd, buf, sizeof(buf) - 1);
437 if (ret < 0)
438 goto out;
439 uid = ret;
440 PARA_INFO_LOG("connection from user %i, buf: %s\n", ret, buf);
441 ret = check_perms(uid, uid_whitelist);
442 if (ret < 0)
443 goto out;
444 ret = create_argv(buf, "\n", &argv);
445 if (ret <= 0)
446 goto out;
447 argc = ret;
448 //PARA_INFO_LOG("argv[0]: %s, argc = %d\n", argv[0], argc);
449 FOR_EACH_COMMAND(i) {
450 if (strcmp(audiod_cmds[i].name, argv[0]))
451 continue;
452 ret = audiod_cmds[i].handler(clifd, argc, argv);
453 goto out;
454 }
455 ret = -E_INVALID_AUDIOD_CMD;
456 out:
457 free_argv(argv);
458 if (ret < 0 && ret != -E_CLIENT_WRITE) {
459 char *tmp = make_message("%s\n", para_strerror(-ret));
460 client_write(clifd, tmp);
461 free(tmp);
462 }
463 close(clifd);
464 return ret;
465 }
466
467 /**
468 * Send the current audiod status to all connected stat clients.
469 *
470 * \param force Whether to write unchanged items.
471 */
472 void audiod_status_dump(bool force)
473 {
474 char *old, *new;
475
476 old = stat_item_values[SI_PLAY_TIME];
477 new = get_time_string();
478 if (new) {
479 if (force || !old || strcmp(old, new)) {
480 free(old);
481 stat_item_values[SI_PLAY_TIME] = new;
482 stat_client_write_item(SI_PLAY_TIME);
483 } else
484 free(new);
485 }
486
487 new = daemon_get_uptime_str(now);
488 old = stat_item_values[SI_AUDIOD_UPTIME];
489 if (force || !old || strcmp(old, new)) {
490 free(old);
491 stat_item_values[SI_AUDIOD_UPTIME] = new;
492 stat_client_write_item(SI_AUDIOD_UPTIME);
493 } else
494 free(new);
495
496 old = stat_item_values[SI_AUDIOD_STATUS];
497 new = audiod_status_string();
498 if (force || !old || strcmp(old, new)) {
499 free(old);
500 stat_item_values[SI_AUDIOD_STATUS] = new;
501 stat_client_write_item(SI_AUDIOD_STATUS);
502 } else
503 free(new);
504
505 old = stat_item_values[SI_DECODER_FLAGS];
506 new = audiod_get_decoder_flags();
507 if (force || !old || strcmp(old, new)) {
508 free(old);
509 stat_item_values[SI_DECODER_FLAGS] = new;
510 stat_client_write_item(SI_DECODER_FLAGS);
511 } else
512 free(new);
513 }
514
515 /**
516 * Flush and send all status items.
517 *
518 * Send to each connected client the full status item list
519 * with empty values.
520 */
521 void clear_and_dump_items(void)
522 {
523 int i;
524
525 FOR_EACH_STATUS_ITEM(i) {
526 free(stat_item_values[i]);
527 stat_item_values[i] = NULL;
528 stat_client_write_item(i);
529 }
530 }