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