NEWS update
[paraslash.git] / audiod_command.c
1 /*
2 * Copyright (C) 2005-2007 Andre Noll <maan@systemlinux.org>
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 #include "audiod_command_list.h"
38
39 /** iterate over the array of all audiod commands */
40 #define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++)
41
42 static int client_write(int fd, const char *buf)
43 {
44 size_t len = strlen(buf);
45 return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
46 }
47
48 static char *get_time_string(struct timeval *newest_stime)
49 {
50 struct timeval diff, adj_stream_start, tmp;
51 int total = 0, use_server_time = 1,
52 length_seconds = stat_task->length_seconds;
53
54 if (!stat_task->playing) {
55 if (length_seconds)
56 return NULL;
57 return make_message("%s:\n", status_item_list[SI_PLAY_TIME]);
58 }
59 if (audiod_status == AUDIOD_OFF)
60 goto out;
61 if (stat_task->sa_time_diff_sign > 0)
62 tv_diff(&stat_task->server_stream_start, &stat_task->sa_time_diff,
63 &adj_stream_start);
64 else
65 tv_add(&stat_task->server_stream_start, &stat_task->sa_time_diff,
66 &adj_stream_start);
67 tmp = adj_stream_start;
68 if (newest_stime && audiod_status == AUDIOD_ON) {
69 tv_diff(newest_stime, &adj_stream_start, &diff);
70 if (tv2ms(&diff) < 5000) {
71 tmp = *newest_stime;
72 use_server_time = 0;
73 }
74 }
75 tv_diff(now, &tmp, &diff);
76 total = diff.tv_sec + stat_task->offset_seconds;
77 if (total > length_seconds)
78 total = length_seconds;
79 if (total < 0)
80 total = 0;
81 out:
82 return make_message(
83 "%s:%s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n",
84 status_item_list[SI_PLAY_TIME],
85 use_server_time? "~" : "",
86 total / 60,
87 total % 60,
88 (length_seconds - total) / 60,
89 (length_seconds - total) % 60,
90 length_seconds? (total * 100 + length_seconds / 2) /
91 length_seconds : 0,
92 length_seconds / 60,
93 length_seconds % 60
94 );
95 }
96
97 __malloc static char *audiod_status_string(void)
98 {
99 const char *status = (audiod_status == AUDIOD_ON)?
100 "on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
101 return make_message("%s:%s\n", status_item_list[SI_AUDIOD_STATUS], status);
102 }
103
104 static struct timeval *wstime(void)
105 {
106 int i;
107 struct timeval *max = NULL;
108 FOR_EACH_SLOT(i) {
109 struct slot_info *s = &slot[i];
110 if (!s->wng)
111 continue;
112 if (max && tv_diff(&s->wstime, max, NULL) <= 0)
113 continue;
114 max = &s->wstime;
115 }
116 return max;
117 }
118 __malloc static char *decoder_flags(void)
119 {
120 int i;
121 char flags[MAX_STREAM_SLOTS + 1];
122
123 FOR_EACH_SLOT(i) {
124 struct slot_info *s = &slot[i];
125 char flag = '0';
126 if (s->receiver_node)
127 flag += 1;
128 if (s->wng)
129 flag += 2;
130 flags[i] = flag;
131 }
132 flags[MAX_STREAM_SLOTS] = '\0';
133 return make_message("%s:%s\n", status_item_list[SI_DECODER_FLAGS],
134 flags);
135 }
136
137 static int dump_commands(int fd)
138 {
139 char *buf = para_strdup(""), *tmp = NULL;
140 int i;
141 ssize_t ret;
142
143 FOR_EACH_COMMAND(i) {
144 tmp = make_message("%s%s\t%s\n", buf, audiod_cmds[i].name,
145 audiod_cmds[i].description);
146 free(buf);
147 buf = tmp;
148 }
149 ret = client_write(fd, buf);
150 free(buf);
151 return ret;
152 }
153
154 /*
155 * command handlers don't close their fd on errors (ret < 0) so that
156 * its caller can send an error message. Otherwise (ret >= 0) it's up
157 * to each individual command to close the fd if necessary.
158 */
159
160 int com_help(int fd, int argc, char **argv)
161 {
162 int i, ret;
163 char *buf;
164 const char *dflt = "No such command. Available commands:\n";
165
166 if (argc < 2) {
167 ret = dump_commands(fd);
168 goto out;
169 }
170 FOR_EACH_COMMAND(i) {
171 if (strcmp(audiod_cmds[i].name, argv[1]))
172 continue;
173 buf = make_message(
174 "NAME\n\t%s -- %s\n"
175 "SYNOPSIS\n\tpara_audioc %s\n"
176 "DESCRIPTION\n%s\n",
177 argv[1],
178 audiod_cmds[i].description,
179 audiod_cmds[i].usage,
180 audiod_cmds[i].help
181 );
182 ret = client_write(fd, buf);
183 free(buf);
184 goto out;
185 }
186 ret = client_write(fd, dflt);
187 if (ret > 0)
188 ret = dump_commands(fd);
189 out:
190 if (ret >= 0)
191 close(fd);
192 return ret;
193 }
194
195 int com_tasks(int fd, __a_unused int argc, __a_unused char **argv)
196 {
197 char *tl = get_task_list();
198 int ret = 1;
199 if (tl)
200 ret = client_write(fd, tl);
201 free(tl);
202 if (ret > 0)
203 close(fd);
204 return ret;
205 }
206
207 int com_kill(int fd, int argc, char **argv)
208 {
209 int i, ret = 1;
210 if (argc < 2)
211 return -E_AUDIOD_SYNTAX;
212 for (i = 1; i < argc; i++) {
213 ret = kill_task(argv[i]);
214 if (ret < 0)
215 break;
216 }
217 if (ret > 0)
218 close(fd);
219 return ret;
220 }
221
222 int com_stat(int fd, __a_unused int argc, __a_unused char **argv)
223 {
224 int i, ret;
225 char *buf = NULL;
226 long unsigned mask = ~0LU;
227
228 if (argc > 1) {
229 mask = 0;
230 for (i = 1; i < argc; i++) {
231 ret = stat_item_valid(argv[i]);
232 if (ret < 0)
233 return ret;
234 mask |= (1 << ret);
235 }
236 }
237 PARA_INFO_LOG("mask: 0x%lx\n", mask);
238 if (mask & (1 << SI_PLAY_TIME)) {
239 struct timeval *t = wstime();
240 char *ts = get_time_string(t);
241 if (ts) {
242 ret = client_write(fd, ts);
243 if (ret < 0)
244 goto out;
245 free(ts);
246 }
247 }
248 if (mask & (1 << SI_AUDIOD_UPTIME)) {
249 char *tmp, *us = uptime_str();
250 tmp = make_message("%s:%s\n",
251 status_item_list[SI_AUDIOD_UPTIME], us);
252 free(us);
253 ret = client_write(fd, tmp);
254 if (ret < 0)
255 goto out;
256 free(tmp);
257 }
258 if (mask & (1 << SI_AUDIOD_STATUS)) {
259 char *s = audiod_status_string();
260 ret = client_write(fd, s);
261 if (ret < 0)
262 goto out;
263 free(s);
264 }
265 if (mask & (1 << SI_DECODER_FLAGS)) {
266 char *df =decoder_flags();
267 ret = client_write(fd, df);
268 if (ret < 0)
269 goto out;
270 free(df);
271 }
272
273 for (i = 0; i < NUM_STAT_ITEMS; i++) {
274 char *tmp, *v;
275 if (!((1 << i) & mask))
276 continue;
277 v = stat_task->stat_item_values[i];
278 tmp = make_message("%s%s%s", buf? buf: "",
279 v? v : "", v? "\n" : "");
280 free(buf);
281 buf = tmp;
282 }
283 ret = client_write(fd, buf);
284 out:
285 if (ret > 0)
286 ret = stat_client_add(fd, mask);
287 free(buf);
288 return ret;
289 }
290
291 static struct filter_node *find_filter_node(int slot_num, int format, int filternum)
292 {
293 struct filter_node *fn;
294 int i, j;
295
296 FOR_EACH_SLOT(i) {
297 struct slot_info *s = &slot[i];
298 if (s->format < 0 || !s->fc)
299 continue;
300 if (slot_num >= 0 && slot_num != i)
301 continue;
302 if (format >= 0 && s->format != format)
303 continue;
304 if (num_filters(i) < filternum)
305 continue;
306 /* success */
307 j = 1;
308 list_for_each_entry(fn, &s->fc->filters, node)
309 if (filternum <= 0 || j++ == filternum)
310 break;
311 return fn;
312 }
313 return NULL;
314 }
315
316 int com_grab(int fd, char *cmdline)
317 {
318 struct grab_client *gc;
319 struct filter_node *fn;
320 int i, err;
321 char *msg;
322
323 gc = grab_client_new(fd, cmdline, &err);
324 if (!gc)
325 goto err_out;
326 fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg);
327 if (fn)
328 activate_grab_client(gc, fn);
329 return 1;
330 err_out:
331 if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN)
332 return err;
333 if (err == -E_GC_HELP_GIVEN) {
334 msg = make_message("%s\n\n", grab_client_args_info_usage);
335 for (i = 0; grab_client_args_info_help[i]; i++) {
336 char *tmp = make_message("%s%s\n", msg,
337 grab_client_args_info_help[i]);
338 free(msg);
339 msg = tmp;
340 }
341 } else
342 msg = make_message("%s %s\n",
343 GRAB_CLIENT_CMDLINE_PARSER_PACKAGE,
344 GRAB_CLIENT_CMDLINE_PARSER_VERSION);
345 err = client_write(fd, msg);
346 free(msg);
347 if (err < 0)
348 return err;
349 close(fd);
350 return 1;
351 }
352
353 __noreturn int com_term(int fd, __a_unused int argc, __a_unused char **argv)
354 {
355 close(fd);
356 clean_exit(EXIT_SUCCESS, "terminating on user request");
357 }
358
359 int com_on(int fd, __a_unused int argc, __a_unused char **argv)
360 {
361 audiod_status = AUDIOD_ON;
362 close(fd);
363 return 1;
364 }
365
366 int com_off(int fd, __a_unused int argc, __a_unused char **argv)
367 {
368 audiod_status = AUDIOD_OFF;
369 close(fd);
370 return 1;
371 }
372
373 int com_sb(int fd, __a_unused int argc, __a_unused char **argv)
374 {
375 audiod_status = AUDIOD_STANDBY;
376 close(fd);
377 return 1;
378 }
379
380 int com_cycle(int fd, int argc, char **argv)
381 {
382 switch (audiod_status) {
383 case AUDIOD_ON:
384 return com_sb(fd, argc, argv);
385 break;
386 case AUDIOD_OFF:
387 return com_on(fd, argc, argv);
388 break;
389 case AUDIOD_STANDBY:
390 return com_off(fd, argc, argv);
391 break;
392 }
393 close(fd);
394 return 1;
395 }
396
397 static int check_perms(uid_t uid)
398 {
399 int i;
400
401 if (!conf.user_allow_given)
402 return 1;
403 for (i = 0; i < conf.user_allow_given; i++)
404 if (uid == conf.user_allow_arg[i])
405 return 1;
406 return -E_UCRED_PERM;
407 }
408
409 /**
410 * handle arriving connections on the local socket
411 *
412 * \param accept_fd the fd to call accept() on
413 *
414 * This is called whenever para_audiod's main task detects an incoming
415 * connection by the readability of \a accept_fd. This function reads the
416 * command sent by the peer, checks the connecting user's permissions by using
417 * unix socket credentials (if supported by the OS) and calls the corresponding
418 * command handler if permissions are OK.
419 *
420 * \return positive on success, negative on errors
421 *
422 * \sa para_accept(), recv_cred_buffer()
423 * */
424 int handle_connect(int accept_fd)
425 {
426 int i, argc, ret, clifd = -1;
427 char *cmd = NULL, *p, *buf = para_calloc(MAXLINE), **argv = NULL;
428 struct sockaddr_un unix_addr;
429
430 ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un));
431 if (ret < 0)
432 goto out;
433 clifd = ret;
434 ret = recv_cred_buffer(clifd, buf, MAXLINE - 1);
435 if (ret < 0)
436 goto out;
437 PARA_INFO_LOG("connection from user %i, buf: %s\n", ret, buf);
438 ret = check_perms(ret);
439 if (ret < 0)
440 goto out;
441 ret = -E_INVALID_AUDIOD_CMD;
442 cmd = para_strdup(buf);
443 p = strchr(cmd, '\n');
444 if (!p)
445 p = "";
446 else {
447 *p = '\0';
448 p++;
449 }
450 for (i = 0; audiod_cmds[i].name; i++) {
451 int j;
452 if (strcmp(audiod_cmds[i].name, cmd))
453 continue;
454 if (audiod_cmds[i].handler) {
455 argc = split_args(buf, &argv, "\n");
456 PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc);
457 ret = audiod_cmds[i].handler(clifd, argc, argv);
458 goto out;
459 }
460 for (j = 0; p[j]; j++)
461 if (p[j] == '\n')
462 p[j] = ' ';
463 PARA_INFO_LOG("cmd: %s, options: %s\n", cmd, p);
464 ret = audiod_cmds[i].line_handler(clifd, p);
465 goto out;
466 }
467 ret = -E_INVALID_AUDIOD_CMD;
468 out:
469 free(cmd);
470 free(buf);
471 free(argv);
472 if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) {
473 char *tmp = make_message("%s\n", PARA_STRERROR(-ret));
474 client_write(clifd, tmp);
475 free(tmp);
476 close(clifd);
477 }
478 return ret;
479 }
480 /**
481 * send the current audiod status to all connected stat clients
482 */
483 void audiod_status_dump(void)
484 {
485 struct timeval *t = wstime();
486 char *old, *new, *tmp;
487
488 old = stat_task->stat_item_values[SI_PLAY_TIME];
489 new = get_time_string(t);
490 if (new) {
491 if (!old || strcmp(old, new)) {
492 free(old);
493 stat_client_write(new, SI_PLAY_TIME);
494 stat_task->stat_item_values[SI_PLAY_TIME] = new;
495 } else
496 free(new);
497 }
498
499 new = uptime_str();
500 old = stat_task->stat_item_values[SI_AUDIOD_UPTIME];
501 if (!old || strcmp(old, new)) {
502 free(old);
503 tmp = make_message("%s:%s\n",
504 status_item_list[SI_AUDIOD_UPTIME], new);
505 stat_client_write(tmp, SI_AUDIOD_UPTIME);
506 free(tmp);
507 stat_task->stat_item_values[SI_AUDIOD_UPTIME] = new;
508 } else
509 free(new);
510
511 old = stat_task->stat_item_values[SI_AUDIOD_STATUS];
512 new = audiod_status_string();
513 if (!old || strcmp(old, new)) {
514 free(old);
515 stat_client_write(new, SI_AUDIOD_STATUS);
516 stat_task->stat_item_values[SI_AUDIOD_STATUS] = new;
517 } else
518 free(new);
519
520 old = stat_task->stat_item_values[SI_DECODER_FLAGS];
521 new = decoder_flags();
522 if (!old || strcmp(old, new)) {
523 free(old);
524 stat_client_write(new, SI_DECODER_FLAGS);
525 stat_task->stat_item_values[SI_DECODER_FLAGS] = new;
526 } else
527 free(new);
528 }
529
530 /**
531 * send empty status list
532 *
533 * Send to each connected client the full status item list
534 * with empty values.
535 */
536 void dump_empty_status(void)
537 {
538 int i;
539
540 FOR_EACH_STAT_ITEM(i) {
541 char *tmp = make_message("%s:\n", status_item_list[i]);
542 stat_client_write(tmp, i);
543 free(tmp);
544 free(stat_task->stat_item_values[i]);
545 stat_task->stat_item_values[i] = NULL;
546 }
547 }