/* * Copyright (C) 2005-2009 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file audiod_command.c commands for para_audiod */ #include #include #include "para.h" #include "audiod.cmdline.h" #include "list.h" #include "close_on_fork.h" #include "sched.h" #include "ggo.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" extern char *stat_item_values[NUM_STAT_ITEMS]; /** 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; } __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 int get_play_time_slot_num(void) { int i, oldest = -1; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; if (!s->wng) continue; if (oldest >= 0 && tv_diff(&s->wstime, &slot[oldest].wstime, NULL) > 0) continue; oldest = i; } return oldest; } __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, int argc, char **argv) { int i, ret; char *buf = NULL; uint64_t mask = 0; const uint64_t one = 1; if (argc > 1) { for (i = 1; i < argc; i++) { ret = stat_item_valid(argv[i]); if (ret < 0) return ret; mask |= (one << ret); } } else mask--; /* set all bits */ PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask); FOR_EACH_STATUS_ITEM(i) { char *tmp, *v; if (!((one << i) & mask)) continue; v = stat_item_values[i]; if (!v) continue; tmp = make_message("%s%s%s", buf? buf: "", v, strrchr(v, '\n')? "" : "\n"); free(buf); buf = tmp; } ret = client_write(fd, buf); 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) { int i; 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 */ return s->fc->filter_nodes + filternum; } 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; 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) { int slot_num = get_play_time_slot_num(); char *old, *new, *tmp; old = stat_item_values[SI_PLAY_TIME]; new = get_time_string(slot_num); if (new) { if (!old || strcmp(old, new)) { free(old); stat_client_write(new, SI_PLAY_TIME); stat_item_values[SI_PLAY_TIME] = new; } else free(new); } tmp = uptime_str(); new = make_message("%s: %s\n", status_item_list[SI_AUDIOD_UPTIME], tmp); free(tmp); old = stat_item_values[SI_AUDIOD_UPTIME]; if (!old || strcmp(old, new)) { free(old); stat_client_write(new, SI_AUDIOD_UPTIME); stat_item_values[SI_AUDIOD_UPTIME] = new; } else free(new); old = 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_item_values[SI_AUDIOD_STATUS] = new; } else free(new); old = stat_item_values[SI_DECODER_FLAGS]; new = decoder_flags(); if (!old || strcmp(old, new)) { free(old); stat_client_write(new, SI_DECODER_FLAGS); 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_STATUS_ITEM(i) { char *tmp = make_message("%s:\n", status_item_list[i]); stat_client_write(tmp, i); free(tmp); free(stat_item_values[i]); stat_item_values[i] = NULL; } }