+/*
+ * Copyright (C) 2005-2006 Andre Noll <noll@mathematik.tu-darmstadt.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ */
+
+/** \file audiod.c the paraslash's audio daemon */
+
+#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"
+
+/** defines one command of para_audiod */
+struct audiod_command {
+ /** the name of the command */
+ const char *name;
+ /** pointer to the function that handles the command */
+ int (*handler)(int, int, char**);
+ /**
+ * if the command prefers to handle the full line (rather than the usual
+ * argv[] array), it stores a pointer to the corresponding line handling
+ * function here. In this case, the above \a handler pointer must be NULL.
+ */
+ int (*line_handler)(int, char*);
+ /** one-line description of the command */
+ const char *description;
+ /** summary of the command line options */
+ const char *synopsis;
+ /** the long help text */
+ const char *help;
+};
+static int com_grab(int, char *);
+static int com_cycle(int, int, char **);
+static int com_help(int, int, char **);
+static int com_kill(int, int, char **);
+static int com_off(int, int, char **);
+static int com_on(int, int, char **);
+static int com_sb(int, int, char **);
+static int com_stat(int, int, char **);
+static int com_tasks(int, int, char **);
+static int com_term(int, int, char **);
+static struct audiod_command cmds[] = {
+{
+.name = "cycle",
+.handler = com_cycle,
+.description = "switch to next mode",
+.synopsis = "cycle",
+.help =
+
+"on -> standby -> off -> on\n"
+
+},
+{
+.name = "grab",
+.line_handler = com_grab,
+.description = "grab the audio stream",
+.synopsis = "-- grab [grab_options]",
+.help =
+
+"grab ('splice') the audio stream at any position in the filter \n"
+"chain and send that data back to the client. Try\n"
+"\t para_audioc -- grab -h\n"
+"for the list of available options.\n"
+},
+
+{
+.name = "help",
+.handler = com_help,
+.description = "display command list or help for given command",
+.synopsis = "help [command]",
+.help =
+
+"When I was younger, so much younger than today, I never needed\n"
+"anybody's help in any way. But now these days are gone, I'm not so\n"
+"self assured. Now I find I've changed my mind and opened up the doors.\n"
+"\n"
+" -- Beatles: Help\n"
+
+},
+{
+.name = "kill",
+.handler = com_kill,
+.description = "kill an active audiod task",
+.synopsis = "kill task_id [task_id ...]",
+.help =
+
+"call sched_unregister() and the event_handler of the given task(s)\n"
+
+},
+{
+.name = "off",
+.handler = com_off,
+.description = "deactivate para_audiod",
+.synopsis = "off",
+.help =
+
+"Close connection to para_server and stop all decoders.\n"
+
+},
+{
+.name = "on",
+.handler = com_on,
+.description = "activate para_audiod",
+.synopsis = "on",
+.help =
+
+"Establish connection to para_server, retrieve para_server's current\n"
+"status. If playing, start corresponding decoder. Otherwise stop\n"
+"all decoders.\n"
+
+},
+{
+.name = "sb",
+.handler = com_sb,
+.description = "enter standby mode",
+.synopsis = "sb",
+.help =
+
+"Stop all decoders but leave connection to para_server open.\n"
+
+},
+{
+.name = "stat",
+.handler = com_stat,
+.description = "print status information",
+.synopsis = "stat [item1 ...]",
+.help =
+
+"Dump given status items (all if none given) to stdout.\n"
+
+},
+{
+.name = "tasks",
+.handler = com_tasks,
+.description = "list current tasks",
+.synopsis = "tasks",
+.help =
+
+"print the list of task ids together with the status of each task\n"
+
+},
+{
+.name = "term",
+.handler = com_term,
+.description = "terminate audiod",
+.synopsis = "term",
+.help =
+
+"Stop all decoders, shut down connection to para_server and exit.\n"
+
+},
+{
+.name = NULL,
+}
+};
+
+/** iterate over the array of all audiod commands */
+#define FOR_EACH_COMMAND(c) for (c = 0; 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 decoder_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;
+ decoder_flags[i] = flag;
+ }
+ decoder_flags[MAX_STREAM_SLOTS] = '\0';
+ return make_message("%s:%s\n", status_item_list[SI_DECODER_FLAGS],
+ decoder_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, cmds[i].name,
+ 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.
+ */
+
+static 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(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],
+ cmds[i].description,
+ cmds[i].synopsis,
+ 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static int __noreturn com_term(int fd, __a_unused int argc, __a_unused char **argv)
+{
+ close(fd);
+ clean_exit(EXIT_SUCCESS, "terminating on user request");
+}
+
+static int com_on(int fd, __a_unused int argc, __a_unused char **argv)
+{
+ audiod_status = AUDIOD_ON;
+ close(fd);
+ return 1;
+}
+
+static int com_off(int fd, __a_unused int argc, __a_unused char **argv)
+{
+ audiod_status = AUDIOD_OFF;
+ close(fd);
+ return 1;
+}
+
+static int com_sb(int fd, __a_unused int argc, __a_unused char **argv)
+{
+ audiod_status = AUDIOD_STANDBY;
+ close(fd);
+ return 1;
+}
+
+static 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;
+}
+
+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;
+
+ 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;
+ PARA_INFO_LOG("connection from user %i, buf: %s\n", ret, buf);
+ ret = check_perms(ret);
+ 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; cmds[i].name; i++) {
+ int j;
+ if (strcmp(cmds[i].name, cmd))
+ continue;
+ if (cmds[i].handler) {
+ argc = split_args(buf, &argv, "\n");
+ PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc);
+ ret = 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 = 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;
+}
+
+void audiod_status_dump(void)
+{
+ static char *p_ts, *p_us, *p_as, *p_df;
+ struct timeval *t = wstime();
+ char *us, *tmp = get_time_string(t);
+
+ if (tmp && (!p_ts || strcmp(tmp, p_ts)))
+ stat_client_write(tmp, SI_PLAY_TIME);
+ free(p_ts);
+ p_ts = tmp;
+
+ us = uptime_str();
+ tmp = make_message("%s:%s\n", status_item_list[SI_AUDIOD_UPTIME], us);
+ free(us);
+ if (!p_us || strcmp(p_us, tmp))
+ stat_client_write(tmp, SI_AUDIOD_UPTIME);
+ free(p_us);
+ p_us = tmp;
+
+ tmp = audiod_status_string();
+ if (!p_as || strcmp(p_as, tmp))
+ stat_client_write(tmp, SI_AUDIOD_STATUS);
+ free(p_as);
+ p_as = tmp;
+
+ tmp = decoder_flags();
+ if (!p_df || strcmp(p_df, tmp))
+ stat_client_write(tmp, SI_DECODER_FLAGS);
+ free(p_df);
+ p_df = tmp;
+}
+