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