Makefile.in: switch from git-tar-tree to git-archive
[paraslash.git] / audiod_command.c
1 /*
2 * Copyright (C) 2005-2006 Andre Noll <noll@mathematik.tu-darmstadt.de>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
17 */
18
19 /** \file audiod_command.c commands for para_audiod */
20
21 #include "para.h"
22
23 #include "audiod.cmdline.h"
24 #include "list.h"
25 #include "close_on_fork.h"
26 #include "sched.h"
27 #include "filter.h"
28 #include "grab_client.cmdline.h"
29 #include "grab_client.h"
30
31 #include "error.h"
32 #include "audiod.h"
33 #include "net.h"
34 #include "daemon.h"
35 #include "string.h"
36 #include "fd.h"
37
38 /** defines one command of para_audiod */
39 struct audiod_command {
40 /** the name of the command */
41 const char *name;
42 /** pointer to the function that handles the command */
43 int (*handler)(int, int, char**);
44 /**
45 * if the command prefers to handle the full line (rather than the usual
46 * argv[] array), it stores a pointer to the corresponding line handling
47 * function here. In this case, the above \a handler pointer must be NULL.
48 */
49 int (*line_handler)(int, char*);
50 /** one-line description of the command */
51 const char *description;
52 /** summary of the command line options */
53 const char *synopsis;
54 /** the long help text */
55 const char *help;
56 };
57 static int com_grab(int, char *);
58 static int com_cycle(int, int, char **);
59 static int com_help(int, int, char **);
60 static int com_kill(int, int, char **);
61 static int com_off(int, int, char **);
62 static int com_on(int, int, char **);
63 static int com_sb(int, int, char **);
64 static int com_stat(int, int, char **);
65 static int com_tasks(int, int, char **);
66 static int com_term(int, int, char **);
67 static struct audiod_command cmds[] = {
68 {
69 .name = "cycle",
70 .handler = com_cycle,
71 .description = "switch to next mode",
72 .synopsis = "cycle",
73 .help =
74
75 "on -> standby -> off -> on\n"
76
77 },
78 {
79 .name = "grab",
80 .line_handler = com_grab,
81 .description = "grab the audio stream",
82 .synopsis = "-- grab [grab_options]",
83 .help =
84
85 "grab ('splice') the audio stream at any position in the filter \n"
86 "chain and send that data back to the client. Try\n"
87 "\t para_audioc -- grab -h\n"
88 "for the list of available options.\n"
89 },
90
91 {
92 .name = "help",
93 .handler = com_help,
94 .description = "display command list or help for given command",
95 .synopsis = "help [command]",
96 .help =
97
98 "When I was younger, so much younger than today, I never needed\n"
99 "anybody's help in any way. But now these days are gone, I'm not so\n"
100 "self assured. Now I find I've changed my mind and opened up the doors.\n"
101 "\n"
102 " -- Beatles: Help\n"
103
104 },
105 {
106 .name = "kill",
107 .handler = com_kill,
108 .description = "kill an active audiod task",
109 .synopsis = "kill task_id [task_id ...]",
110 .help =
111
112 "call sched_unregister() and the event_handler of the given task(s)\n"
113
114 },
115 {
116 .name = "off",
117 .handler = com_off,
118 .description = "deactivate para_audiod",
119 .synopsis = "off",
120 .help =
121
122 "Close connection to para_server and stop all decoders.\n"
123
124 },
125 {
126 .name = "on",
127 .handler = com_on,
128 .description = "activate para_audiod",
129 .synopsis = "on",
130 .help =
131
132 "Establish connection to para_server, retrieve para_server's current\n"
133 "status. If playing, start corresponding decoder. Otherwise stop\n"
134 "all decoders.\n"
135
136 },
137 {
138 .name = "sb",
139 .handler = com_sb,
140 .description = "enter standby mode",
141 .synopsis = "sb",
142 .help =
143
144 "Stop all decoders but leave connection to para_server open.\n"
145
146 },
147 {
148 .name = "stat",
149 .handler = com_stat,
150 .description = "print status information",
151 .synopsis = "stat [item1 ...]",
152 .help =
153
154 "Dump given status items (all if none given) to stdout.\n"
155
156 },
157 {
158 .name = "tasks",
159 .handler = com_tasks,
160 .description = "list current tasks",
161 .synopsis = "tasks",
162 .help =
163
164 "print the list of task ids together with the status of each task\n"
165
166 },
167 {
168 .name = "term",
169 .handler = com_term,
170 .description = "terminate audiod",
171 .synopsis = "term",
172 .help =
173
174 "Stop all decoders, shut down connection to para_server and exit.\n"
175
176 },
177 {
178 .name = NULL,
179 }
180 };
181
182 /** iterate over the array of all audiod commands */
183 #define FOR_EACH_COMMAND(c) for (c = 0; cmds[c].name; c++)
184
185 static int client_write(int fd, const char *buf)
186 {
187 size_t len = strlen(buf);
188 return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
189 }
190
191 static char *get_time_string(struct timeval *newest_stime)
192 {
193 struct timeval diff, adj_stream_start, tmp;
194 int total = 0, use_server_time = 1,
195 length_seconds = stat_task->length_seconds;
196
197 if (!stat_task->playing) {
198 if (length_seconds)
199 return NULL;
200 return make_message("%s:\n", status_item_list[SI_PLAY_TIME]);
201 }
202 if (audiod_status == AUDIOD_OFF)
203 goto out;
204 if (stat_task->sa_time_diff_sign > 0)
205 tv_diff(&stat_task->server_stream_start, &stat_task->sa_time_diff,
206 &adj_stream_start);
207 else
208 tv_add(&stat_task->server_stream_start, &stat_task->sa_time_diff,
209 &adj_stream_start);
210 tmp = adj_stream_start;
211 if (newest_stime && audiod_status == AUDIOD_ON) {
212 tv_diff(newest_stime, &adj_stream_start, &diff);
213 if (tv2ms(&diff) < 5000) {
214 tmp = *newest_stime;
215 use_server_time = 0;
216 }
217 }
218 tv_diff(now, &tmp, &diff);
219 total = diff.tv_sec + stat_task->offset_seconds;
220 if (total > length_seconds)
221 total = length_seconds;
222 if (total < 0)
223 total = 0;
224 out:
225 return make_message(
226 "%s:%s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n",
227 status_item_list[SI_PLAY_TIME],
228 use_server_time? "~" : "",
229 total / 60,
230 total % 60,
231 (length_seconds - total) / 60,
232 (length_seconds - total) % 60,
233 length_seconds? (total * 100 + length_seconds / 2) /
234 length_seconds : 0,
235 length_seconds / 60,
236 length_seconds % 60
237 );
238 }
239
240 __malloc static char *audiod_status_string(void)
241 {
242 const char *status = (audiod_status == AUDIOD_ON)?
243 "on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
244 return make_message("%s:%s\n", status_item_list[SI_AUDIOD_STATUS], status);
245 }
246
247 static struct timeval *wstime(void)
248 {
249 int i;
250 struct timeval *max = NULL;
251 FOR_EACH_SLOT(i) {
252 struct slot_info *s = &slot[i];
253 if (!s->wng)
254 continue;
255 if (max && tv_diff(&s->wstime, max, NULL) <= 0)
256 continue;
257 max = &s->wstime;
258 }
259 return max;
260 }
261 __malloc static char *decoder_flags(void)
262 {
263 int i;
264 char flags[MAX_STREAM_SLOTS + 1];
265
266 FOR_EACH_SLOT(i) {
267 struct slot_info *s = &slot[i];
268 char flag = '0';
269 if (s->receiver_node)
270 flag += 1;
271 if (s->wng)
272 flag += 2;
273 flags[i] = flag;
274 }
275 flags[MAX_STREAM_SLOTS] = '\0';
276 return make_message("%s:%s\n", status_item_list[SI_DECODER_FLAGS],
277 flags);
278 }
279
280 static int dump_commands(int fd)
281 {
282 char *buf = para_strdup(""), *tmp = NULL;
283 int i;
284 ssize_t ret;
285
286 FOR_EACH_COMMAND(i) {
287 tmp = make_message("%s%s\t%s\n", buf, cmds[i].name,
288 cmds[i].description);
289 free(buf);
290 buf = tmp;
291 }
292 ret = client_write(fd, buf);
293 free(buf);
294 return ret;
295 }
296
297 /*
298 * command handlers don't close their fd on errors (ret < 0) so that
299 * its caller can send an error message. Otherwise (ret >= 0) it's up
300 * to each individual command to close the fd if necessary.
301 */
302
303 static int com_help(int fd, int argc, char **argv)
304 {
305 int i, ret;
306 char *buf;
307 const char *dflt = "No such command. Available commands:\n";
308
309 if (argc < 2) {
310 ret = dump_commands(fd);
311 goto out;
312 }
313 FOR_EACH_COMMAND(i) {
314 if (strcmp(cmds[i].name, argv[1]))
315 continue;
316 buf = make_message(
317 "NAME\n\t%s -- %s\n"
318 "SYNOPSIS\n\tpara_audioc %s\n"
319 "DESCRIPTION\n%s\n",
320 argv[1],
321 cmds[i].description,
322 cmds[i].synopsis,
323 cmds[i].help
324 );
325 ret = client_write(fd, buf);
326 free(buf);
327 goto out;
328 }
329 ret = client_write(fd, dflt);
330 if (ret > 0)
331 ret = dump_commands(fd);
332 out:
333 if (ret >= 0)
334 close(fd);
335 return ret;
336 }
337
338 static int com_tasks(int fd, __a_unused int argc, __a_unused char **argv)
339 {
340 char *tl = get_task_list();
341 int ret = 1;
342 if (tl)
343 ret = client_write(fd, tl);
344 free(tl);
345 if (ret > 0)
346 close(fd);
347 return ret;
348 }
349
350 static int com_kill(int fd, int argc, char **argv)
351 {
352 int i, ret = 1;
353 if (argc < 2)
354 return -E_AUDIOD_SYNTAX;
355 for (i = 1; i < argc; i++) {
356 ret = kill_task(argv[i]);
357 if (ret < 0)
358 break;
359 }
360 if (ret > 0)
361 close(fd);
362 return ret;
363 }
364
365 static int com_stat(int fd, __a_unused int argc, __a_unused char **argv)
366 {
367 int i, ret;
368 char *buf = NULL;
369 long unsigned mask = ~0LU;
370
371 if (argc > 1) {
372 mask = 0;
373 for (i = 1; i < argc; i++) {
374 ret = stat_item_valid(argv[i]);
375 if (ret < 0)
376 return ret;
377 mask |= (1 << ret);
378 }
379 }
380 PARA_INFO_LOG("mask: 0x%lx\n", mask);
381 if (mask & (1 << SI_PLAY_TIME)) {
382 struct timeval *t = wstime();
383 char *ts = get_time_string(t);
384 if (ts) {
385 ret = client_write(fd, ts);
386 if (ret < 0)
387 goto out;
388 free(ts);
389 }
390 }
391 if (mask & (1 << SI_AUDIOD_UPTIME)) {
392 char *tmp, *us = uptime_str();
393 tmp = make_message("%s:%s\n",
394 status_item_list[SI_AUDIOD_UPTIME], us);
395 free(us);
396 ret = client_write(fd, tmp);
397 if (ret < 0)
398 goto out;
399 free(tmp);
400 }
401 if (mask & (1 << SI_AUDIOD_STATUS)) {
402 char *s = audiod_status_string();
403 ret = client_write(fd, s);
404 if (ret < 0)
405 goto out;
406 free(s);
407 }
408 if (mask & (1 << SI_DECODER_FLAGS)) {
409 char *df =decoder_flags();
410 ret = client_write(fd, df);
411 if (ret < 0)
412 goto out;
413 free(df);
414 }
415
416 for (i = 0; i < NUM_STAT_ITEMS; i++) {
417 char *tmp, *v;
418 if (!((1 << i) & mask))
419 continue;
420 v = stat_task->stat_item_values[i];
421 tmp = make_message("%s%s%s", buf? buf: "",
422 v? v : "", v? "\n" : "");
423 free(buf);
424 buf = tmp;
425 }
426 ret = client_write(fd, buf);
427 out:
428 if (ret > 0)
429 ret = stat_client_add(fd, mask);
430 free(buf);
431 return ret;
432 }
433
434 static struct filter_node *find_filter_node(int slot_num, int format, int filternum)
435 {
436 struct filter_node *fn;
437 int i, j;
438
439 FOR_EACH_SLOT(i) {
440 struct slot_info *s = &slot[i];
441 if (s->format < 0 || !s->fc)
442 continue;
443 if (slot_num >= 0 && slot_num != i)
444 continue;
445 if (format >= 0 && s->format != format)
446 continue;
447 if (num_filters(i) < filternum)
448 continue;
449 /* success */
450 j = 1;
451 list_for_each_entry(fn, &s->fc->filters, node)
452 if (filternum <= 0 || j++ == filternum)
453 break;
454 return fn;
455 }
456 return NULL;
457 }
458
459 static int com_grab(int fd, char *cmdline)
460 {
461 struct grab_client *gc;
462 struct filter_node *fn;
463 int i, err;
464 char *msg;
465
466 gc = grab_client_new(fd, cmdline, &err);
467 if (!gc)
468 goto err_out;
469 fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg);
470 if (fn)
471 activate_grab_client(gc, fn);
472 return 1;
473 err_out:
474 if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN)
475 return err;
476 if (err == -E_GC_HELP_GIVEN) {
477 msg = make_message("%s\n\n", grab_client_args_info_usage);
478 for (i = 0; grab_client_args_info_help[i]; i++) {
479 char *tmp = make_message("%s%s\n", msg,
480 grab_client_args_info_help[i]);
481 free(msg);
482 msg = tmp;
483 }
484 } else
485 msg = make_message("%s %s\n",
486 GRAB_CLIENT_CMDLINE_PARSER_PACKAGE,
487 GRAB_CLIENT_CMDLINE_PARSER_VERSION);
488 err = client_write(fd, msg);
489 free(msg);
490 if (err < 0)
491 return err;
492 close(fd);
493 return 1;
494 }
495
496 static int __noreturn com_term(int fd, __a_unused int argc, __a_unused char **argv)
497 {
498 close(fd);
499 clean_exit(EXIT_SUCCESS, "terminating on user request");
500 }
501
502 static int com_on(int fd, __a_unused int argc, __a_unused char **argv)
503 {
504 audiod_status = AUDIOD_ON;
505 close(fd);
506 return 1;
507 }
508
509 static int com_off(int fd, __a_unused int argc, __a_unused char **argv)
510 {
511 audiod_status = AUDIOD_OFF;
512 close(fd);
513 return 1;
514 }
515
516 static int com_sb(int fd, __a_unused int argc, __a_unused char **argv)
517 {
518 audiod_status = AUDIOD_STANDBY;
519 close(fd);
520 return 1;
521 }
522
523 static int com_cycle(int fd, int argc, char **argv)
524 {
525 switch (audiod_status) {
526 case AUDIOD_ON:
527 return com_sb(fd, argc, argv);
528 break;
529 case AUDIOD_OFF:
530 return com_on(fd, argc, argv);
531 break;
532 case AUDIOD_STANDBY:
533 return com_off(fd, argc, argv);
534 break;
535 }
536 close(fd);
537 return 1;
538 }
539
540 static int check_perms(uid_t uid)
541 {
542 int i;
543
544 if (!conf.user_allow_given)
545 return 1;
546 for (i = 0; i < conf.user_allow_given; i++)
547 if (uid == conf.user_allow_arg[i])
548 return 1;
549 return -E_UCRED_PERM;
550 }
551
552 int handle_connect(int accept_fd)
553 {
554 int i, argc, ret, clifd = -1;
555 char *cmd = NULL, *p, *buf = para_calloc(MAXLINE), **argv = NULL;
556 struct sockaddr_un unix_addr;
557
558 ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un));
559 if (ret < 0)
560 goto out;
561 clifd = ret;
562 ret = recv_cred_buffer(clifd, buf, MAXLINE - 1);
563 if (ret < 0)
564 goto out;
565 PARA_INFO_LOG("connection from user %i, buf: %s\n", ret, buf);
566 ret = check_perms(ret);
567 if (ret < 0)
568 goto out;
569 ret = -E_INVALID_AUDIOD_CMD;
570 cmd = para_strdup(buf);
571 p = strchr(cmd, '\n');
572 if (!p)
573 p = "";
574 else {
575 *p = '\0';
576 p++;
577 }
578 for (i = 0; cmds[i].name; i++) {
579 int j;
580 if (strcmp(cmds[i].name, cmd))
581 continue;
582 if (cmds[i].handler) {
583 argc = split_args(buf, &argv, "\n");
584 PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc);
585 ret = cmds[i].handler(clifd, argc, argv);
586 goto out;
587 }
588 for (j = 0; p[j]; j++)
589 if (p[j] == '\n')
590 p[j] = ' ';
591 PARA_INFO_LOG("cmd: %s, options: %s\n", cmd, p);
592 ret = cmds[i].line_handler(clifd, p);
593 goto out;
594 }
595 ret = -E_INVALID_AUDIOD_CMD;
596 out:
597 free(cmd);
598 free(buf);
599 free(argv);
600 if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) {
601 char *tmp = make_message("%s\n", PARA_STRERROR(-ret));
602 client_write(clifd, tmp);
603 free(tmp);
604 close(clifd);
605 }
606 return ret;
607 }
608
609 void audiod_status_dump(void)
610 {
611 struct timeval *t = wstime();
612 char *old, *new, *tmp;
613
614 old = stat_task->stat_item_values[SI_PLAY_TIME];
615 new = get_time_string(t);
616 if (new) {
617 if (!old || strcmp(old, new)) {
618 free(old);
619 stat_client_write(new, SI_PLAY_TIME);
620 stat_task->stat_item_values[SI_PLAY_TIME] = new;
621 } else
622 free(new);
623 }
624
625 new = uptime_str();
626 old = stat_task->stat_item_values[SI_AUDIOD_UPTIME];
627 if (!old || strcmp(old, new)) {
628 free(old);
629 tmp = make_message("%s:%s\n",
630 status_item_list[SI_AUDIOD_UPTIME], new);
631 stat_client_write(tmp, SI_AUDIOD_UPTIME);
632 free(tmp);
633 stat_task->stat_item_values[SI_AUDIOD_UPTIME] = new;
634 } else
635 free(new);
636
637 old = stat_task->stat_item_values[SI_AUDIOD_STATUS];
638 new = audiod_status_string();
639 if (!old || strcmp(old, new)) {
640 free(old);
641 stat_client_write(new, SI_AUDIOD_STATUS);
642 stat_task->stat_item_values[SI_AUDIOD_STATUS] = new;
643 } else
644 free(new);
645
646 old = stat_task->stat_item_values[SI_DECODER_FLAGS];
647 new = decoder_flags();
648 if (!old || strcmp(old, new)) {
649 stat_client_write(new, SI_DECODER_FLAGS);
650 stat_task->stat_item_values[SI_DECODER_FLAGS] = new;
651 } else
652 free(new);
653 }
654
655 /**
656 * send empty status list
657 *
658 * Send to each connected client the full status item list
659 * with empty values.
660 */
661 void dump_empty_status(void)
662 {
663 int i;
664
665 FOR_EACH_STAT_ITEM(i) {
666 char *tmp = make_message("%s:\n", status_item_list[i]);
667 stat_client_write(tmp, i);
668 free(tmp);
669 free(stat_task->stat_item_values[i]);
670 stat_task->stat_item_values[i] = NULL;
671 }
672 }