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