Use para_sigaction() in command handlers.
[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, __a_unused int argc, __a_unused char **argv)
170 {
171         int i, ret;
172         char *buf = NULL;
173         long unsigned mask = ~0LU;
174
175         if (argc > 1) {
176                 mask = 0;
177                 for (i = 1; i < argc; i++) {
178                         ret = stat_item_valid(argv[i]);
179                         if (ret < 0)
180                                 return ret;
181                         mask |= (1 << ret);
182                 }
183         }
184         PARA_INFO_LOG("mask: 0x%lx\n", mask);
185         if (mask & (1 << SI_PLAY_TIME)) {
186                 int slot_num = get_play_time_slot_num();
187                 char *ts = get_time_string(slot_num);
188                 if (ts) {
189                         ret = client_write(fd, ts);
190                         if (ret < 0)
191                                 goto out;
192                         free(ts);
193                 }
194         }
195         if (mask & (1 << SI_AUDIOD_UPTIME)) {
196                 char *tmp, *us = uptime_str();
197                 tmp = make_message("%s: %s\n",
198                         status_item_list[SI_AUDIOD_UPTIME], us);
199                 free(us);
200                 ret = client_write(fd, tmp);
201                 if (ret < 0)
202                         goto out;
203                 free(tmp);
204         }
205         if (mask & (1 << SI_AUDIOD_STATUS)) {
206                 char *s = audiod_status_string();
207                 ret = client_write(fd, s);
208                 if (ret < 0)
209                         goto out;
210                 free(s);
211         }
212         if (mask & (1 << SI_DECODER_FLAGS)) {
213                 char *df = decoder_flags();
214                 ret = client_write(fd, df);
215                 if (ret < 0)
216                         goto out;
217                 free(df);
218         }
219         FOR_EACH_STATUS_ITEM(i) {
220                 char *tmp, *v;
221                 if (!((1 << i) & mask))
222                         continue;
223                 v = stat_item_values[i];
224                 tmp = make_message("%s%s%s", buf? buf: "",
225                         v? v : "", v? "\n" : "");
226                 free(buf);
227                 buf = tmp;
228         }
229         ret = client_write(fd, buf);
230 out:
231         if (ret > 0)
232                 ret = stat_client_add(fd, mask);
233         free(buf);
234         return ret;
235 }
236
237 static struct filter_node *find_filter_node(int slot_num, int format, int filternum)
238 {
239         int i;
240
241         FOR_EACH_SLOT(i) {
242                 struct slot_info *s = &slot[i];
243                 if (s->format < 0 || !s->fc)
244                         continue;
245                 if (slot_num >= 0 && slot_num != i)
246                         continue;
247                 if (format >= 0 && s->format != format)
248                         continue;
249                 if (num_filters(i) <= filternum)
250                         continue;
251                 /* success */
252                 return  s->fc->filter_nodes + filternum;
253         }
254         return NULL;
255 }
256
257 int com_grab(int fd, char *cmdline)
258 {
259         struct grab_client *gc;
260         struct filter_node *fn;
261         int i, err;
262         char *msg;
263
264         gc = grab_client_new(fd, cmdline, &err);
265         if (!gc)
266                 goto err_out;
267         fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg);
268         if (fn)
269                 activate_grab_client(gc, fn);
270         return 1;
271 err_out:
272         if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN)
273                 return err;
274         if (err == -E_GC_HELP_GIVEN) {
275                 msg = make_message("%s\n\n", grab_client_args_info_usage);
276                 for (i = 0; grab_client_args_info_help[i]; i++) {
277                         char *tmp = make_message("%s%s\n", msg,
278                                 grab_client_args_info_help[i]);
279                         free(msg);
280                         msg = tmp;
281                 }
282         } else
283                 msg = make_message("%s %s\n",
284                         GRAB_CLIENT_CMDLINE_PARSER_PACKAGE,
285                         GRAB_CLIENT_CMDLINE_PARSER_VERSION);
286         err = client_write(fd, msg);
287         free(msg);
288         if (err < 0)
289                 return err;
290         close(fd);
291         return 1;
292 }
293
294 __noreturn int com_term(int fd, __a_unused int argc, __a_unused char **argv)
295 {
296         close(fd);
297         clean_exit(EXIT_SUCCESS, "terminating on user request");
298 }
299
300 int com_on(int fd, __a_unused int argc, __a_unused char **argv)
301 {
302         audiod_status = AUDIOD_ON;
303         close(fd);
304         return 1;
305 }
306
307 int com_off(int fd, __a_unused int argc, __a_unused char **argv)
308 {
309         audiod_status = AUDIOD_OFF;
310         close(fd);
311         return 1;
312 }
313
314 int com_sb(int fd, __a_unused int argc, __a_unused char **argv)
315 {
316         audiod_status = AUDIOD_STANDBY;
317         close(fd);
318         return 1;
319 }
320
321 int com_cycle(int fd, int argc, char **argv)
322 {
323         switch (audiod_status) {
324                 case  AUDIOD_ON:
325                         return com_sb(fd, argc, argv);
326                         break;
327                 case  AUDIOD_OFF:
328                         return com_on(fd, argc, argv);
329                         break;
330                 case  AUDIOD_STANDBY:
331                         return com_off(fd, argc, argv);
332                         break;
333         }
334         close(fd);
335         return 1;
336 }
337
338 static int check_perms(uid_t uid)
339 {
340         int i;
341
342         if (!conf.user_allow_given)
343                 return 1;
344         for (i = 0; i < conf.user_allow_given; i++)
345                 if (uid == conf.user_allow_arg[i])
346                         return 1;
347         return -E_UCRED_PERM;
348 }
349
350 /**
351  * handle arriving connections on the local socket
352  *
353  * \param accept_fd the fd to call accept() on
354  *
355  * This is called whenever para_audiod's main task detects an incoming
356  * connection by the readability of \a accept_fd. This function reads the
357  * command sent by the peer, checks the connecting user's permissions by using
358  * unix socket credentials (if supported by the OS) and calls the corresponding
359  * command handler if permissions are OK.
360  *
361  * \return positive on success, negative on errors
362  *
363  * \sa para_accept(), recv_cred_buffer()
364  * */
365 int handle_connect(int accept_fd)
366 {
367         int i, argc, ret, clifd = -1;
368         char *cmd = NULL, *p, *buf = para_calloc(MAXLINE), **argv = NULL;
369         struct sockaddr_un unix_addr;
370         uid_t uid;
371
372         ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un));
373         if (ret < 0)
374                 goto out;
375         clifd = ret;
376         ret = recv_cred_buffer(clifd, buf, MAXLINE - 1);
377         if (ret < 0)
378                 goto out;
379         uid = ret;
380         PARA_INFO_LOG("connection from user %i, buf: %s\n",  ret, buf);
381         ret = check_perms(uid);
382         if (ret < 0)
383                 goto out;
384         cmd = para_strdup(buf);
385         p = strchr(cmd, '\n');
386         if (!p)
387                 p = "";
388         else {
389                 *p = '\0';
390                 p++;
391         }
392         for (i = 0; audiod_cmds[i].name; i++) {
393                 int j;
394                 if (strcmp(audiod_cmds[i].name, cmd))
395                         continue;
396                 if (audiod_cmds[i].handler) {
397                         argc = split_args(buf, &argv, "\n");
398                         PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc);
399                         ret = audiod_cmds[i].handler(clifd, argc, argv);
400                         goto out;
401                 }
402                 for (j = 0; p[j]; j++)
403                         if (p[j] == '\n')
404                                 p[j] = ' ';
405                 PARA_INFO_LOG("cmd: %s, options: %s\n", cmd, p);
406                 ret = audiod_cmds[i].line_handler(clifd, p);
407                 goto out;
408         }
409         ret = -E_INVALID_AUDIOD_CMD;
410 out:
411         free(cmd);
412         free(buf);
413         free(argv);
414         if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) {
415                 char *tmp = make_message("%s\n", para_strerror(-ret));
416                 client_write(clifd, tmp);
417                 free(tmp);
418                 close(clifd);
419         }
420         return ret;
421 }
422 /**
423  * send the current audiod status to all connected stat clients
424  */
425 void audiod_status_dump(void)
426 {
427         int slot_num = get_play_time_slot_num();
428         char *old, *new, *tmp;
429
430         old = stat_item_values[SI_PLAY_TIME];
431         new = get_time_string(slot_num);
432         if (new) {
433                 if (!old || strcmp(old, new)) {
434                         free(old);
435                         stat_client_write(new, SI_PLAY_TIME);
436                         stat_item_values[SI_PLAY_TIME] = new;
437                 } else
438                         free(new);
439         }
440
441         new = uptime_str();
442         old = stat_item_values[SI_AUDIOD_UPTIME];
443         if (!old || strcmp(old, new)) {
444                 free(old);
445                 tmp = make_message("%s: %s\n",
446                         status_item_list[SI_AUDIOD_UPTIME], new);
447                 stat_client_write(tmp, SI_AUDIOD_UPTIME);
448                 free(tmp);
449                 stat_item_values[SI_AUDIOD_UPTIME] = new;
450         } else
451                 free(new);
452
453         old = stat_item_values[SI_AUDIOD_STATUS];
454         new = audiod_status_string();
455         if (!old || strcmp(old, new)) {
456                 free(old);
457                 stat_client_write(new, SI_AUDIOD_STATUS);
458                 stat_item_values[SI_AUDIOD_STATUS] = new;
459         } else
460                 free(new);
461
462         old = stat_item_values[SI_DECODER_FLAGS];
463         new = decoder_flags();
464         if (!old || strcmp(old, new)) {
465                 free(old);
466                 stat_client_write(new, SI_DECODER_FLAGS);
467                 stat_item_values[SI_DECODER_FLAGS] = new;
468         } else
469                 free(new);
470 }
471
472 /**
473  * send empty status list
474  *
475  * Send to  each connected client the full status item list
476  * with empty values.
477  */
478 void dump_empty_status(void)
479 {
480         int i;
481
482         FOR_EACH_STATUS_ITEM(i) {
483                 char *tmp = make_message("%s:\n", status_item_list[i]);
484                 stat_client_write(tmp, i);
485                 free(tmp);
486                 free(stat_item_values[i]);
487                 stat_item_values[i] = NULL;
488         }
489 }