audiod: Clean up status output.
[paraslash.git] / audiod_command.c
1 /*
2  * Copyright (C) 2005-2009 Andre Noll <maan@systemlinux.org>
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 <sys/types.h>
10 #include <dirent.h>
11
12 #include "para.h"
13 #include "audiod.cmdline.h"
14 #include "list.h"
15 #include "close_on_fork.h"
16 #include "sched.h"
17 #include "ggo.h"
18 #include "filter.h"
19 #include "grab_client.cmdline.h"
20 #include "grab_client.h"
21
22 #include "error.h"
23 #include "audiod.h"
24 #include "net.h"
25 #include "daemon.h"
26 #include "string.h"
27 #include "fd.h"
28 #include "audiod_command_list.h"
29
30 extern char *stat_item_values[NUM_STAT_ITEMS];
31
32
33 /** iterate over the array of all audiod commands */
34 #define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++)
35
36 static int client_write(int fd, const char *buf)
37 {
38         size_t len = strlen(buf);
39         return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
40 }
41
42 __malloc static char *audiod_status_string(void)
43 {
44         const char *status = (audiod_status == AUDIOD_ON)?
45                 "on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
46         return make_message("%s: %s\n", status_item_list[SI_AUDIOD_STATUS], status);
47 }
48
49 static int get_play_time_slot_num(void)
50 {
51         int i, oldest = -1;
52
53         FOR_EACH_SLOT(i) {
54                 struct slot_info *s = &slot[i];
55                 if (!s->wng)
56                         continue;
57                 if (oldest >= 0 && tv_diff(&s->wstime, &slot[oldest].wstime,
58                                 NULL) > 0)
59                         continue;
60                 oldest = i;
61         }
62         return oldest;
63 }
64
65 __malloc static char *decoder_flags(void)
66 {
67         int i;
68         char flags[MAX_STREAM_SLOTS + 1];
69
70         FOR_EACH_SLOT(i) {
71                 struct slot_info *s = &slot[i];
72                 char flag = '0';
73                 if (s->receiver_node)
74                         flag += 1;
75                 if (s->wng)
76                         flag += 2;
77                 flags[i] = flag;
78         }
79         flags[MAX_STREAM_SLOTS] = '\0';
80         return make_message("%s: %s\n", status_item_list[SI_DECODER_FLAGS],
81                 flags);
82 }
83
84 static int dump_commands(int fd)
85 {
86         char *buf = para_strdup(""), *tmp = NULL;
87         int i;
88         ssize_t ret;
89
90         FOR_EACH_COMMAND(i) {
91                 tmp = make_message("%s%s\t%s\n", buf, audiod_cmds[i].name,
92                         audiod_cmds[i].description);
93                 free(buf);
94                 buf = tmp;
95         }
96         ret = client_write(fd, buf);
97         free(buf);
98         return ret;
99 }
100
101 /*
102  * command handlers don't close their fd on errors (ret < 0) so that
103  * its caller can send an error message. Otherwise (ret >= 0) it's up
104  * to each individual command to close the fd if necessary.
105  */
106
107 int com_help(int fd, int argc, char **argv)
108 {
109         int i, ret;
110         char *buf;
111         const char *dflt = "No such command. Available commands:\n";
112
113         if (argc < 2) {
114                 ret = dump_commands(fd);
115                 goto out;
116         }
117         FOR_EACH_COMMAND(i) {
118                 if (strcmp(audiod_cmds[i].name, argv[1]))
119                         continue;
120                 buf = make_message(
121                         "NAME\n\t%s -- %s\n"
122                         "SYNOPSIS\n\tpara_audioc %s\n"
123                         "DESCRIPTION\n%s\n",
124                         argv[1],
125                         audiod_cmds[i].description,
126                         audiod_cmds[i].usage,
127                         audiod_cmds[i].help
128                 );
129                 ret = client_write(fd, buf);
130                 free(buf);
131                 goto out;
132         }
133         ret = client_write(fd, dflt);
134         if (ret > 0)
135                 ret = dump_commands(fd);
136 out:
137         if (ret >= 0)
138                 close(fd);
139         return ret;
140 }
141
142 int com_tasks(int fd, __a_unused int argc, __a_unused char **argv)
143 {
144         char *tl = get_task_list();
145         int ret = 1;
146         if (tl)
147                 ret = client_write(fd, tl);
148         free(tl);
149         if (ret > 0)
150                 close(fd);
151         return ret;
152 }
153
154 int com_kill(int fd, int argc, char **argv)
155 {
156         int i, ret = 1;
157         if (argc < 2)
158                 return -E_AUDIOD_SYNTAX;
159         for (i = 1; i < argc; i++) {
160                 ret = kill_task(argv[i]);
161                 if (ret < 0)
162                         break;
163         }
164         if (ret > 0)
165                 close(fd);
166         return ret;
167 }
168
169 int com_stat(int fd, int argc, char **argv)
170 {
171         int i, ret;
172         char *buf = NULL;
173         uint64_t mask = 0;
174         const uint64_t one = 1;
175
176         if (argc > 1) {
177                 for (i = 1; i < argc; i++) {
178                         ret = stat_item_valid(argv[i]);
179                         if (ret < 0)
180                                 return ret;
181                         mask |= (one << ret);
182                 }
183         } else
184                 mask--; /* set all bits */
185         PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask);
186         FOR_EACH_STATUS_ITEM(i) {
187                 char *tmp, *v;
188                 if (!((one << i) & mask))
189                         continue;
190                 v = stat_item_values[i];
191                 if (!v)
192                         continue;
193                 tmp = make_message("%s%s%s", buf? buf: "", v,
194                         strrchr(v, '\n')? "" : "\n");
195                 free(buf);
196                 buf = tmp;
197         }
198         ret = client_write(fd, buf);
199 out:
200         if (ret > 0)
201                 ret = stat_client_add(fd, mask);
202         free(buf);
203         return ret;
204 }
205
206 static struct filter_node *find_filter_node(int slot_num, int format, int filternum)
207 {
208         int i;
209
210         FOR_EACH_SLOT(i) {
211                 struct slot_info *s = &slot[i];
212                 if (s->format < 0 || !s->fc)
213                         continue;
214                 if (slot_num >= 0 && slot_num != i)
215                         continue;
216                 if (format >= 0 && s->format != format)
217                         continue;
218                 if (num_filters(i) <= filternum)
219                         continue;
220                 /* success */
221                 return  s->fc->filter_nodes + filternum;
222         }
223         return NULL;
224 }
225
226 int com_grab(int fd, char *cmdline)
227 {
228         struct grab_client *gc;
229         struct filter_node *fn;
230         int i, err;
231         char *msg;
232
233         gc = grab_client_new(fd, cmdline, &err);
234         if (!gc)
235                 goto err_out;
236         fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg);
237         if (fn)
238                 activate_grab_client(gc, fn);
239         return 1;
240 err_out:
241         if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN)
242                 return err;
243         if (err == -E_GC_HELP_GIVEN) {
244                 msg = make_message("%s\n\n", grab_client_args_info_usage);
245                 for (i = 0; grab_client_args_info_help[i]; i++) {
246                         char *tmp = make_message("%s%s\n", msg,
247                                 grab_client_args_info_help[i]);
248                         free(msg);
249                         msg = tmp;
250                 }
251         } else
252                 msg = make_message("%s %s\n",
253                         GRAB_CLIENT_CMDLINE_PARSER_PACKAGE,
254                         GRAB_CLIENT_CMDLINE_PARSER_VERSION);
255         err = client_write(fd, msg);
256         free(msg);
257         if (err < 0)
258                 return err;
259         close(fd);
260         return 1;
261 }
262
263 __noreturn int com_term(int fd, __a_unused int argc, __a_unused char **argv)
264 {
265         close(fd);
266         clean_exit(EXIT_SUCCESS, "terminating on user request");
267 }
268
269 int com_on(int fd, __a_unused int argc, __a_unused char **argv)
270 {
271         audiod_status = AUDIOD_ON;
272         close(fd);
273         return 1;
274 }
275
276 int com_off(int fd, __a_unused int argc, __a_unused char **argv)
277 {
278         audiod_status = AUDIOD_OFF;
279         close(fd);
280         return 1;
281 }
282
283 int com_sb(int fd, __a_unused int argc, __a_unused char **argv)
284 {
285         audiod_status = AUDIOD_STANDBY;
286         close(fd);
287         return 1;
288 }
289
290 int com_cycle(int fd, int argc, char **argv)
291 {
292         switch (audiod_status) {
293                 case  AUDIOD_ON:
294                         return com_sb(fd, argc, argv);
295                         break;
296                 case  AUDIOD_OFF:
297                         return com_on(fd, argc, argv);
298                         break;
299                 case  AUDIOD_STANDBY:
300                         return com_off(fd, argc, argv);
301                         break;
302         }
303         close(fd);
304         return 1;
305 }
306
307 static int check_perms(uid_t uid)
308 {
309         int i;
310
311         if (!conf.user_allow_given)
312                 return 1;
313         for (i = 0; i < conf.user_allow_given; i++)
314                 if (uid == conf.user_allow_arg[i])
315                         return 1;
316         return -E_UCRED_PERM;
317 }
318
319 /**
320  * handle arriving connections on the local socket
321  *
322  * \param accept_fd the fd to call accept() on
323  *
324  * This is called whenever para_audiod's main task detects an incoming
325  * connection by the readability of \a accept_fd. This function reads the
326  * command sent by the peer, checks the connecting user's permissions by using
327  * unix socket credentials (if supported by the OS) and calls the corresponding
328  * command handler if permissions are OK.
329  *
330  * \return positive on success, negative on errors
331  *
332  * \sa para_accept(), recv_cred_buffer()
333  * */
334 int handle_connect(int accept_fd)
335 {
336         int i, argc, ret, clifd = -1;
337         char *cmd = NULL, *p, *buf = para_calloc(MAXLINE), **argv = NULL;
338         struct sockaddr_un unix_addr;
339         uid_t uid;
340
341         ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un));
342         if (ret < 0)
343                 goto out;
344         clifd = ret;
345         ret = recv_cred_buffer(clifd, buf, MAXLINE - 1);
346         if (ret < 0)
347                 goto out;
348         uid = ret;
349         PARA_INFO_LOG("connection from user %i, buf: %s\n",  ret, buf);
350         ret = check_perms(uid);
351         if (ret < 0)
352                 goto out;
353         cmd = para_strdup(buf);
354         p = strchr(cmd, '\n');
355         if (!p)
356                 p = "";
357         else {
358                 *p = '\0';
359                 p++;
360         }
361         for (i = 0; audiod_cmds[i].name; i++) {
362                 int j;
363                 if (strcmp(audiod_cmds[i].name, cmd))
364                         continue;
365                 if (audiod_cmds[i].handler) {
366                         argc = split_args(buf, &argv, "\n");
367                         PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc);
368                         ret = audiod_cmds[i].handler(clifd, argc, argv);
369                         goto out;
370                 }
371                 for (j = 0; p[j]; j++)
372                         if (p[j] == '\n')
373                                 p[j] = ' ';
374                 PARA_INFO_LOG("cmd: %s, options: %s\n", cmd, p);
375                 ret = audiod_cmds[i].line_handler(clifd, p);
376                 goto out;
377         }
378         ret = -E_INVALID_AUDIOD_CMD;
379 out:
380         free(cmd);
381         free(buf);
382         free(argv);
383         if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) {
384                 char *tmp = make_message("%s\n", para_strerror(-ret));
385                 client_write(clifd, tmp);
386                 free(tmp);
387                 close(clifd);
388         }
389         return ret;
390 }
391 /**
392  * send the current audiod status to all connected stat clients
393  */
394 void audiod_status_dump(void)
395 {
396         int slot_num = get_play_time_slot_num();
397         char *old, *new, *tmp;
398
399         old = stat_item_values[SI_PLAY_TIME];
400         new = get_time_string(slot_num);
401         if (new) {
402                 if (!old || strcmp(old, new)) {
403                         free(old);
404                         stat_client_write(new, SI_PLAY_TIME);
405                         stat_item_values[SI_PLAY_TIME] = new;
406                 } else
407                         free(new);
408         }
409
410         tmp = uptime_str();
411         new = make_message("%s: %s\n", status_item_list[SI_AUDIOD_UPTIME],
412                 tmp);
413         free(tmp);
414         old = stat_item_values[SI_AUDIOD_UPTIME];
415         if (!old || strcmp(old, new)) {
416                 free(old);
417                 stat_client_write(new, SI_AUDIOD_UPTIME);
418                 stat_item_values[SI_AUDIOD_UPTIME] = new;
419         } else
420                 free(new);
421
422         old = stat_item_values[SI_AUDIOD_STATUS];
423         new = audiod_status_string();
424         if (!old || strcmp(old, new)) {
425                 free(old);
426                 stat_client_write(new, SI_AUDIOD_STATUS);
427                 stat_item_values[SI_AUDIOD_STATUS] = new;
428         } else
429                 free(new);
430
431         old = stat_item_values[SI_DECODER_FLAGS];
432         new = decoder_flags();
433         if (!old || strcmp(old, new)) {
434                 free(old);
435                 stat_client_write(new, SI_DECODER_FLAGS);
436                 stat_item_values[SI_DECODER_FLAGS] = new;
437         } else
438                 free(new);
439 }
440
441 /**
442  * send empty status list
443  *
444  * Send to  each connected client the full status item list
445  * with empty values.
446  */
447 void dump_empty_status(void)
448 {
449         int i;
450
451         FOR_EACH_STATUS_ITEM(i) {
452                 char *tmp = make_message("%s:\n", status_item_list[i]);
453                 stat_client_write(tmp, i);
454                 free(tmp);
455                 free(stat_item_values[i]);
456                 stat_item_values[i] = NULL;
457         }
458 }