/* SPDX-License-Identifier: GPL-2.0 */ /** \file audioc.c The client program used to connect to para_audiod(1). * * The code if this file is conceptually similar to \ref client.c because * para_audioc(1) also operates in one of three possible modes: completion, * interactive or non-interactive mode. Like the client code it calls \ref * i9e_open() of \ref interactive.c to create an interactive session. * See \ref audioc_common.c for the code to connect to para_audiod(1), which * is shared with para_gui. */ /* \cond doxygen_ignore */ #include "para.h" #include #include #include #include #include #include #include #include #include "audiod_cmd.lsg.h" #include "audioc.lsg.h" #include "audioc.h" #include "error.h" #include "lsu.h" #include "net.h" #include "string.h" #include "fd.h" #include "sideband.h" DEFINE_PARA_ERRLIST; static const char *socket_name; static struct lls_parse_result *lpr; #define CMD_PTR (lls_cmd(0, audioc_suite)) #define OPT_RESULT(_name) \ (lls_opt_result(LSG_AUDIOC_PARA_AUDIOC_OPT_ ## _name, lpr)) #define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) #define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) #define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) static int loglevel; INIT_STDERR_LOGGING(loglevel); #ifdef HAVE_READLINE #include "list.h" #include "sched.h" #include "buffer_tree.h" #include "interactive.h" static struct sched *sched; struct audioc_task { int fd; struct btr_node *btrn; struct task *task; struct sb_context *ctx; }; static const struct i9e_completer audiod_completers[]; I9E_DUMMY_COMPLETER(cycle); I9E_DUMMY_COMPLETER(off); I9E_DUMMY_COMPLETER(on); I9E_DUMMY_COMPLETER(sb); I9E_DUMMY_COMPLETER(tasks); I9E_DUMMY_COMPLETER(term); static void help_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { char * const opts[] = {LSG_AUDIOD_CMD_HELP_OPTS, NULL}; if (ci->word[0] == '-') { i9e_complete_option(opts, ci, cr); return; } if (i9e_get_nonopt_argnum(opts, ci) == 1) cr->matches = i9e_complete_commands(ci->word, audiod_completers); } static void ll_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { i9e_ll_completer(ci, cr); } static void version_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { char * const opts[] = {LSG_AUDIOD_CMD_VERSION_OPTS, NULL}; if (ci->word[0] == '-') i9e_complete_option(opts, ci, cr); } static void stat_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { char *sia[] = {STATUS_ITEMS NULL}; char * const opts[] = {LSG_AUDIOD_CMD_STAT_OPTS, NULL}; if (ci->word[0] == '-') return i9e_complete_option(opts, ci, cr); i9e_extract_completions(ci->word, sia, &cr->matches); } static void grab_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { char * const opts[] = {LSG_AUDIOD_CMD_GRAB_OPTS, NULL}; int num = i9e_cword_is_option_arg(opts, ci); if (num >= 0) { const char *opt = opts[num]; if (!strcmp(opt, "-m=") || !strcmp(opt, "--mode=")) { cr->matches = arr_alloc(4, sizeof(char *)); cr->matches[0] = make_message("%ss", opt); cr->matches[1] = make_message("%sp", opt); cr->matches[2] = make_message("%sa", opt); cr->matches[3] = NULL; return; } } i9e_complete_option(opts, ci, cr); } static const struct i9e_completer audiod_completers[] = { #define LSG_AUDIOD_CMD_CMD(_name) {.name = #_name, \ .completer = _name ## _completer} LSG_AUDIOD_CMD_SUBCOMMANDS #undef LSG_AUDIOD_CMD_CMD {.name = NULL} }; static void audioc_pre_monitor(struct sched *s, void *context) { struct audioc_task *at = context; int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT); if (ret < 0) sched_min_delay(s); sched_monitor_readfd(at->fd, s); } static void dispatch_sbb(struct sb_buffer *sbb, struct btr_node *btrn) { if (sideband_log("audiod", sbb)) { free(sbb->iov.iov_base); sbb->iov.iov_base = NULL; return; } btr_add_output(sbb->iov.iov_base, sbb->iov.iov_len, btrn); } static int audioc_post_monitor(struct sched *s, void *context) { size_t bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE)); struct audioc_task *at = context; struct iovec iov; struct sb_buffer sbb; int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT); if (ret < 0) goto close_fd; if (!sched_read_ok(at->fd, s)) return 0; if (!at->ctx) at->ctx = sb_new_recv(bufsize, NULL, NULL); sb_get_recv_buffer(at->ctx, &iov); ret = recv_bin_buffer(at->fd, iov.iov_base, iov.iov_len); if (ret < 0) goto free_ctx; if (ret == 0) { ret = -E_EOF; goto free_ctx; } ret = sb_received(at->ctx, ret, &sbb); if (ret < 0) goto free_ctx; if (ret == 0) return 0; at->ctx = NULL; dispatch_sbb(&sbb, at->btrn); return 1; free_ctx: assert(ret < 0); sb_free(at->ctx); at->ctx = NULL; close_fd: close(at->fd); btr_remove_node(&at->btrn); return ret; } static struct audioc_task audioc_task, *at = &audioc_task; static int audioc_i9e_line_handler(char *line) { int ret, argc; char **argv; PARA_DEBUG_LOG("line: %s\n", line); ret = create_argv(line, " ", &argv); if (ret <= 0) return ret; argc = ret; ret = connect_audiod(socket_name, argc, argv); free_argv(argv); if (ret < 0) return ret; at->fd = ret; ret = mark_fd_nonblocking(at->fd); if (ret < 0) goto close; at->btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = "audioc line handler")); at->task = task_register(&(struct task_info) { .name = "audioc", .pre_monitor = audioc_pre_monitor, .post_monitor = audioc_post_monitor, .context = at, }, sched); i9e_attach_to_stdout(at->btrn); return 1; close: close(at->fd); return ret; } __noreturn static void interactive_session(void) { int ret; char *history_file; struct sigaction act; struct i9e_client_info ici = { .fds = {0, 1, 2}, .prompt = "para_audioc> ", .line_handler = audioc_i9e_line_handler, .loglevel = loglevel, .completers = audiod_completers, }; PARA_NOTICE_LOG("\n%s\n", version_text("audioc")); if (OPT_GIVEN(HISTORY_FILE)) history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE)); else { char *dot_para = create_dot_paraslash(); history_file = make_message("%s/.audioc.history", dot_para); free(dot_para); } ici.history_file = history_file; act.sa_handler = i9e_signal_dispatch; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, NULL); sched = sched_new(i9e_poll); ret = i9e_open(&ici, sched); if (ret < 0) goto out; para_log = i9e_log; ret = schedule(sched); sched_shutdown(sched); i9e_close(); para_log = stderr_log; out: free(history_file); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); } __noreturn static void print_completions(void) { int ret = i9e_print_completions(audiod_completers); exit(ret <= 0? EXIT_FAILURE : EXIT_SUCCESS); } #else /* HAVE_READLINE */ __noreturn static void interactive_session(void) { PARA_EMERG_LOG("interactive sessions not available\n"); exit(EXIT_FAILURE); } __noreturn static void print_completions(void) { PARA_EMERG_LOG("command completion not available\n"); exit(EXIT_FAILURE); } #endif /* HAVE_READLINE */ static void handle_help_flag(void) { char *help; if (OPT_GIVEN(DETAILED_HELP)) help = lls_long_help(CMD_PTR); else if (OPT_GIVEN(HELP)) help = lls_short_help(CMD_PTR); else return; printf("%s\n", help); free(help); exit(EXIT_SUCCESS); } static int create_argv_and_connect(void) { unsigned n = lls_num_inputs(lpr); char **argv = arr_alloc(n + 1, sizeof(char *)); int ret; for (unsigned k = 0; k < n; k++) argv[k] = para_strdup(lls_input(k, lpr)); argv[n] = NULL; ret = connect_audiod(socket_name, n, argv); free_argv(argv); return ret; } /** \endcond */ /** * The client program to connect to para_audiod. * * \param argc Options are defined in the audioc lopsub suite. * \param argv The audioc suite defines no subcommands. * * It connects to the "well-known" local socket to communicate with * para_audiod. Authentication is performed by sending a ucred buffer * containing the user id to the local socket. * * Any data received from the socket is written to stdout, employing the * buffer tree subsystem. * * \return EXIT_SUCCESS or EXIT_FAILURE. * * \sa para_audioc(1), para_audiod(1). */ int main(int argc, char *argv[]) { int ret, fd; char *errctx = NULL; size_t bufsize; struct sb_context *ctx; struct iovec iov; struct sb_buffer sbb; ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx)); if (ret < 0) goto free_errctx; version_handle_flag("audioc", OPT_GIVEN(VERSION)); handle_help_flag(); ret = lsu_merge_config_file_options(NULL, "audioc.conf", &lpr, CMD_PTR, audioc_suite, 0 /* default flags */); if (ret < 0) goto free_lpr; loglevel = OPT_UINT32_VAL(LOGLEVEL); if (OPT_GIVEN(COMPLETE)) print_completions(); if (OPT_GIVEN(SOCKET)) socket_name = OPT_STRING_VAL(SOCKET); if (lls_num_inputs(lpr) == 0) interactive_session(); ret = create_argv_and_connect(); if (ret < 0) goto free_lpr; fd = ret; ret = mark_fd_blocking(STDOUT_FILENO); if (ret < 0) goto free_lpr; bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE)); for (;;) { ctx = sb_new_recv(bufsize, NULL, NULL); do { sb_get_recv_buffer(ctx, &iov); ret = recv_bin_buffer(fd, iov.iov_base, iov.iov_len); if (ret < 0) goto free_ctx; if (ret == 0) { ret = -E_EOF; goto free_ctx; } ret = sb_received(ctx, ret, &sbb); if (ret < 0) goto free_ctx; } while (ret == 0); ctx = NULL; if (sideband_log("audiod", &sbb)) free(sbb.iov.iov_base); else switch (sbb.band) { case SBD_OUTPUT: ret = write_all(STDOUT_FILENO, sbb.iov.iov_base, sbb.iov.iov_len); free(sbb.iov.iov_base); if (ret < 0) goto free_lpr; break; case SBD_EXIT__SUCCESS: ret = 1; goto free_lpr; case SBD_EXIT__FAILURE: ret = -E_AUDIOD_DISPATCH; goto free_lpr; default: ret = -E_BAD_BAND; PARA_ERROR_LOG("invalid sideband packet (band = %u)\n", sbb.band); goto free_lpr; } } free_ctx: sb_free(ctx); free_lpr: lls_free_parse_result(lpr, CMD_PTR); free_errctx: if (errctx) PARA_ERROR_LOG("%s\n", errctx); free(errctx); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; }