/* * Copyright (C) 2005-2007 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file audiod_command.c commands for para_audiod */ #include "para.h" #include "audiod.cmdline.h" #include "list.h" #include "close_on_fork.h" #include "sched.h" #include "filter.h" #include "grab_client.cmdline.h" #include "grab_client.h" #include "error.h" #include "audiod.h" #include "net.h" #include "daemon.h" #include "string.h" #include "fd.h" #include "audiod_command_list.h" /** iterate over the array of all audiod commands */ #define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++) static int client_write(int fd, const char *buf) { size_t len = strlen(buf); return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1; } static char *get_time_string(struct timeval *newest_stime) { struct timeval diff, adj_stream_start, tmp; int total = 0, use_server_time = 1, length_seconds = stat_task->length_seconds; if (!stat_task->playing) { if (length_seconds) return NULL; return make_message("%s:\n", status_item_list[SI_PLAY_TIME]); } if (audiod_status == AUDIOD_OFF) goto out; if (stat_task->sa_time_diff_sign > 0) tv_diff(&stat_task->server_stream_start, &stat_task->sa_time_diff, &adj_stream_start); else tv_add(&stat_task->server_stream_start, &stat_task->sa_time_diff, &adj_stream_start); tmp = adj_stream_start; if (newest_stime && audiod_status == AUDIOD_ON) { tv_diff(newest_stime, &adj_stream_start, &diff); if (tv2ms(&diff) < 5000) { tmp = *newest_stime; use_server_time = 0; } } tv_diff(now, &tmp, &diff); total = diff.tv_sec + stat_task->offset_seconds; if (total > length_seconds) total = length_seconds; if (total < 0) total = 0; out: return make_message( "%s:%s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n", status_item_list[SI_PLAY_TIME], use_server_time? "~" : "", total / 60, total % 60, (length_seconds - total) / 60, (length_seconds - total) % 60, length_seconds? (total * 100 + length_seconds / 2) / length_seconds : 0, length_seconds / 60, length_seconds % 60 ); } __malloc static char *audiod_status_string(void) { const char *status = (audiod_status == AUDIOD_ON)? "on" : (audiod_status == AUDIOD_OFF)? "off": "sb"; return make_message("%s:%s\n", status_item_list[SI_AUDIOD_STATUS], status); } static struct timeval *wstime(void) { int i; struct timeval *max = NULL; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; if (!s->wng) continue; if (max && tv_diff(&s->wstime, max, NULL) <= 0) continue; max = &s->wstime; } return max; } __malloc static char *decoder_flags(void) { int i; char flags[MAX_STREAM_SLOTS + 1]; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; char flag = '0'; if (s->receiver_node) flag += 1; if (s->wng) flag += 2; flags[i] = flag; } flags[MAX_STREAM_SLOTS] = '\0'; return make_message("%s:%s\n", status_item_list[SI_DECODER_FLAGS], flags); } static int dump_commands(int fd) { char *buf = para_strdup(""), *tmp = NULL; int i; ssize_t ret; FOR_EACH_COMMAND(i) { tmp = make_message("%s%s\t%s\n", buf, audiod_cmds[i].name, audiod_cmds[i].description); free(buf); buf = tmp; } ret = client_write(fd, buf); free(buf); return ret; } /* * command handlers don't close their fd on errors (ret < 0) so that * its caller can send an error message. Otherwise (ret >= 0) it's up * to each individual command to close the fd if necessary. */ int com_help(int fd, int argc, char **argv) { int i, ret; char *buf; const char *dflt = "No such command. Available commands:\n"; if (argc < 2) { ret = dump_commands(fd); goto out; } FOR_EACH_COMMAND(i) { if (strcmp(audiod_cmds[i].name, argv[1])) continue; buf = make_message( "NAME\n\t%s -- %s\n" "SYNOPSIS\n\tpara_audioc %s\n" "DESCRIPTION\n%s\n", argv[1], audiod_cmds[i].description, audiod_cmds[i].usage, audiod_cmds[i].help ); ret = client_write(fd, buf); free(buf); goto out; } ret = client_write(fd, dflt); if (ret > 0) ret = dump_commands(fd); out: if (ret >= 0) close(fd); return ret; } int com_tasks(int fd, __a_unused int argc, __a_unused char **argv) { char *tl = get_task_list(); int ret = 1; if (tl) ret = client_write(fd, tl); free(tl); if (ret > 0) close(fd); return ret; } int com_kill(int fd, int argc, char **argv) { int i, ret = 1; if (argc < 2) return -E_AUDIOD_SYNTAX; for (i = 1; i < argc; i++) { ret = kill_task(argv[i]); if (ret < 0) break; } if (ret > 0) close(fd); return ret; } int com_stat(int fd, __a_unused int argc, __a_unused char **argv) { int i, ret; char *buf = NULL; long unsigned mask = ~0LU; if (argc > 1) { mask = 0; for (i = 1; i < argc; i++) { ret = stat_item_valid(argv[i]); if (ret < 0) return ret; mask |= (1 << ret); } } PARA_INFO_LOG("mask: 0x%lx\n", mask); if (mask & (1 << SI_PLAY_TIME)) { struct timeval *t = wstime(); char *ts = get_time_string(t); if (ts) { ret = client_write(fd, ts); if (ret < 0) goto out; free(ts); } } if (mask & (1 << SI_AUDIOD_UPTIME)) { char *tmp, *us = uptime_str(); tmp = make_message("%s:%s\n", status_item_list[SI_AUDIOD_UPTIME], us); free(us); ret = client_write(fd, tmp); if (ret < 0) goto out; free(tmp); } if (mask & (1 << SI_AUDIOD_STATUS)) { char *s = audiod_status_string(); ret = client_write(fd, s); if (ret < 0) goto out; free(s); } if (mask & (1 << SI_DECODER_FLAGS)) { char *df =decoder_flags(); ret = client_write(fd, df); if (ret < 0) goto out; free(df); } for (i = 0; i < NUM_STAT_ITEMS; i++) { char *tmp, *v; if (!((1 << i) & mask)) continue; v = stat_task->stat_item_values[i]; tmp = make_message("%s%s%s", buf? buf: "", v? v : "", v? "\n" : ""); free(buf); buf = tmp; } ret = client_write(fd, buf); out: if (ret > 0) ret = stat_client_add(fd, mask); free(buf); return ret; } static struct filter_node *find_filter_node(int slot_num, int format, int filternum) { struct filter_node *fn; int i, j; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; if (s->format < 0 || !s->fc) continue; if (slot_num >= 0 && slot_num != i) continue; if (format >= 0 && s->format != format) continue; if (num_filters(i) < filternum) continue; /* success */ j = 1; list_for_each_entry(fn, &s->fc->filters, node) if (filternum <= 0 || j++ == filternum) break; return fn; } return NULL; } int com_grab(int fd, char *cmdline) { struct grab_client *gc; struct filter_node *fn; int i, err; char *msg; gc = grab_client_new(fd, cmdline, &err); if (!gc) goto err_out; fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg); if (fn) activate_grab_client(gc, fn); return 1; err_out: if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN) return err; if (err == -E_GC_HELP_GIVEN) { msg = make_message("%s\n\n", grab_client_args_info_usage); for (i = 0; grab_client_args_info_help[i]; i++) { char *tmp = make_message("%s%s\n", msg, grab_client_args_info_help[i]); free(msg); msg = tmp; } } else msg = make_message("%s %s\n", GRAB_CLIENT_CMDLINE_PARSER_PACKAGE, GRAB_CLIENT_CMDLINE_PARSER_VERSION); err = client_write(fd, msg); free(msg); if (err < 0) return err; close(fd); return 1; } __noreturn int com_term(int fd, __a_unused int argc, __a_unused char **argv) { close(fd); clean_exit(EXIT_SUCCESS, "terminating on user request"); } int com_on(int fd, __a_unused int argc, __a_unused char **argv) { audiod_status = AUDIOD_ON; close(fd); return 1; } int com_off(int fd, __a_unused int argc, __a_unused char **argv) { audiod_status = AUDIOD_OFF; close(fd); return 1; } int com_sb(int fd, __a_unused int argc, __a_unused char **argv) { audiod_status = AUDIOD_STANDBY; close(fd); return 1; } int com_cycle(int fd, int argc, char **argv) { switch (audiod_status) { case AUDIOD_ON: return com_sb(fd, argc, argv); break; case AUDIOD_OFF: return com_on(fd, argc, argv); break; case AUDIOD_STANDBY: return com_off(fd, argc, argv); break; } close(fd); return 1; } static int check_perms(uid_t uid) { int i; if (!conf.user_allow_given) return 1; for (i = 0; i < conf.user_allow_given; i++) if (uid == conf.user_allow_arg[i]) return 1; return -E_UCRED_PERM; } /** * handle arriving connections on the local socket * * \param accept_fd the fd to call accept() on * * This is called whenever para_audiod's main task detects an incoming * connection by the readability of \a accept_fd. This function reads the * command sent by the peer, checks the connecting user's permissions by using * unix socket credentials (if supported by the OS) and calls the corresponding * command handler if permissions are OK. * * \return positive on success, negative on errors * * \sa para_accept(), recv_cred_buffer() * */ int handle_connect(int accept_fd) { int i, argc, ret, clifd = -1; char *cmd = NULL, *p, *buf = para_calloc(MAXLINE), **argv = NULL; struct sockaddr_un unix_addr; uid_t uid; ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un)); if (ret < 0) goto out; clifd = ret; ret = recv_cred_buffer(clifd, buf, MAXLINE - 1); if (ret < 0) goto out; uid = ret; PARA_INFO_LOG("connection from user %i, buf: %s\n", ret, buf); ret = check_perms(uid); if (ret < 0) goto out; ret = -E_INVALID_AUDIOD_CMD; cmd = para_strdup(buf); p = strchr(cmd, '\n'); if (!p) p = ""; else { *p = '\0'; p++; } for (i = 0; audiod_cmds[i].name; i++) { int j; if (strcmp(audiod_cmds[i].name, cmd)) continue; if (audiod_cmds[i].handler) { argc = split_args(buf, &argv, "\n"); PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc); ret = audiod_cmds[i].handler(clifd, argc, argv); goto out; } for (j = 0; p[j]; j++) if (p[j] == '\n') p[j] = ' '; PARA_INFO_LOG("cmd: %s, options: %s\n", cmd, p); ret = audiod_cmds[i].line_handler(clifd, p); goto out; } ret = -E_INVALID_AUDIOD_CMD; out: free(cmd); free(buf); free(argv); if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) { char *tmp = make_message("%s\n", PARA_STRERROR(-ret)); client_write(clifd, tmp); free(tmp); close(clifd); } return ret; } /** * send the current audiod status to all connected stat clients */ void audiod_status_dump(void) { struct timeval *t = wstime(); char *old, *new, *tmp; old = stat_task->stat_item_values[SI_PLAY_TIME]; new = get_time_string(t); if (new) { if (!old || strcmp(old, new)) { free(old); stat_client_write(new, SI_PLAY_TIME); stat_task->stat_item_values[SI_PLAY_TIME] = new; } else free(new); } new = uptime_str(); old = stat_task->stat_item_values[SI_AUDIOD_UPTIME]; if (!old || strcmp(old, new)) { free(old); tmp = make_message("%s:%s\n", status_item_list[SI_AUDIOD_UPTIME], new); stat_client_write(tmp, SI_AUDIOD_UPTIME); free(tmp); stat_task->stat_item_values[SI_AUDIOD_UPTIME] = new; } else free(new); old = stat_task->stat_item_values[SI_AUDIOD_STATUS]; new = audiod_status_string(); if (!old || strcmp(old, new)) { free(old); stat_client_write(new, SI_AUDIOD_STATUS); stat_task->stat_item_values[SI_AUDIOD_STATUS] = new; } else free(new); old = stat_task->stat_item_values[SI_DECODER_FLAGS]; new = decoder_flags(); if (!old || strcmp(old, new)) { free(old); stat_client_write(new, SI_DECODER_FLAGS); stat_task->stat_item_values[SI_DECODER_FLAGS] = new; } else free(new); } /** * send empty status list * * Send to each connected client the full status item list * with empty values. */ void dump_empty_status(void) { int i; FOR_EACH_STAT_ITEM(i) { char *tmp = make_message("%s:\n", status_item_list[i]); stat_client_write(tmp, i); free(tmp); free(stat_task->stat_item_values[i]); stat_task->stat_item_values[i] = NULL; } }