]> git.tuebingen.mpg.de Git - paraslash.git/blob - audiod_command.c
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 }