/* SPDX-License-Identifier: GPL-2.0 */ /** \file grab_client.c Functions for grabbing the audio stream. */ #include #include #include "audiod_cmd.lsg.h" #include "para.h" #include "list.h" #include "sched.h" #include "buffer_tree.h" #include "grab_client.h" #include "sideband.h" #include "audiod.h" #include "error.h" #include "string.h" #include "fd.h" /** * How to handle blocking writes for the grab client fds. */ enum grab_mode { /** Ignore the data and do not write. */ GM_SLOPPY, /** Write anyway (default). */ GM_AGGRESSIVE, /** Close fd if write would block. */ GM_PEDANTIC, }; /** Flags specified as arguments to the grab command. */ enum grab_flags { /** Stop grabbing if audio file changes. */ GF_ONE_SHOT = 1, }; /** Describes one active grab client. */ struct grab_client { /* The value of the -p option. */ char *parent; /* The value of the -n option. */ char *name; /** The file descriptor to send the grabbed stream to. */ int fd; /** See \ref grab_mode. */ enum grab_mode mode; /** Flags given at the command line. */ enum grab_flags flags; /** The point of the grab client's node in the buffer tree. */ struct btr_node *btrn; /* The task of this grab client. */ struct task *task; /** Belongs to either the active or the inactive list. */ struct list_head node; }; /* Grab clients that are attached to a btr node. */ static INITIALIZED_LIST_HEAD(active_grab_client_list); /* Grab clients that are not currently attached any btr node. */ static INITIALIZED_LIST_HEAD(inactive_grab_client_list); static int gc_write(struct grab_client *gc, char *buf, size_t len) { int ret = write_ok(gc->fd); if (ret < 0) goto err; if (ret == 0) { /* fd not ready */ if (gc->mode == GM_PEDANTIC) goto err; if (gc->mode == GM_SLOPPY) return len; } ret = client_write_bin(gc->fd, buf, len, SBD_OUTPUT); if (ret < 0) goto err; if (ret > 0) return len; if (ret == 0) { if (gc->mode == GM_PEDANTIC) goto err; if (gc->mode == GM_SLOPPY) return len; } return 0; err: return -E_GC_WRITE; } static void gc_pre_monitor(struct sched *s, void *context) { struct grab_client *gc = context; int ret = btr_node_status(gc->btrn, 0, BTR_NT_LEAF); if (ret == 0) return; if (ret < 0) sched_min_delay(s); sched_monitor_writefd(gc->fd, s); } /* * We need this forward declaration as gc_post_monitor() needs * activate_grab_client and vice versa. */ static int gc_post_monitor(struct sched *s, void *context); /** * Move a grab client to the active list and start it. * * \param gc The grab client to activate. */ static void gc_activate(struct grab_client *gc, struct sched *s) { struct btr_node *root = audiod_get_btr_root(), *parent; char *name = gc->name? gc->name : "grab"; if (!root) return; parent = btr_search_node(gc->parent, root); if (!parent) return; PARA_INFO_LOG("activating fd %d\n", gc->fd); list_move(&gc->node, &active_grab_client_list); gc->btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = name, .parent = parent)); gc->task = task_register(&(struct task_info) { .name = name, .pre_monitor = gc_pre_monitor, .post_monitor = gc_post_monitor, .context = gc, }, s); } /** * Activate inactive grab clients if possible. * * \param s Needed to schedule the grab client task. * * This is called from audiod.c when the current audio file changes. It loops * over all inactive grab clients and checks each grab client's configuration * to determine if the client in question wishes to grab the new stream. If * yes, this grab client is moved from the inactive to the active grab client list. * * This function also garbage collects all grab clients whose tasks have been * unscheduled. */ void activate_grab_clients(struct sched *s) { struct grab_client *gc, *tmp; list_for_each_entry_safe(gc, tmp, &inactive_grab_client_list, node) { if (gc->fd < 0) { list_del(&gc->node); free(gc); continue; } gc_activate(gc, s); } } static int gc_close(struct grab_client *gc, int err) { btr_remove_node(&gc->btrn); PARA_INFO_LOG("closing gc: %s\n", para_strerror(-err)); list_move(&gc->node, &inactive_grab_client_list); if (err == -E_GC_WRITE || (gc->flags & GF_ONE_SHOT)) { /* * We must not free the gc structure here as it contains ->task * which is still used because this function is called from * post_monitor(). */ close(gc->fd); gc->fd = -1; free(gc->parent); free(gc->name); return 1; } return 0; } static int gc_post_monitor(__a_unused struct sched *s, void *context) { struct grab_client *gc = context; struct btr_node *btrn = gc->btrn; int ret; size_t sz; char *buf; ret = btr_node_status(btrn, 0, BTR_NT_LEAF); if (ret == 0) return 0; if (ret < 0) goto err; sz = btr_next_buffer(btrn, &buf); assert(sz != 0); ret = gc_write(gc, buf, sz); if (ret < 0) goto err; if (ret > 0) btr_consume(btrn, ret); return 0; err: gc_close(gc, ret); return ret; } static int gc_check_args(struct lls_parse_result *lpr, struct grab_client *gc) { const struct lls_opt_result *r; r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_MODE, lpr); if (lls_opt_given(r) > 0) { const char *arg = lls_string_val(0, r); if (strcmp(arg, "s") == 0) gc->mode = GM_SLOPPY; else if (strcmp(arg, "a") == 0) gc->mode = GM_AGGRESSIVE; else if (strcmp(arg, "p") == 0) gc->mode = GM_PEDANTIC; else return -E_GC_SYNTAX; } r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_ONE_SHOT, lpr); if (lls_opt_given(r) > 0) gc->flags |= GF_ONE_SHOT; r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_PARENT, lpr); if (lls_opt_given(r) > 0) { const char *arg = lls_string_val(0, r); gc->parent = para_strdup(arg); } r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_NAME, lpr); if (lls_opt_given(r) > 0) { const char *arg = lls_string_val(0, r); gc->name = para_strdup(arg); } return 1; } /** * Create and activate a grab client. * * \param fd The file descriptor of the client. * \param lpr The parsed command line of the grab command. * \param s The scheduler to register the grab client task to. * * This function semantically parses the arguments given as options to the grab * command. On success it allocates a struct grab_client, associates it with * the given file descriptor and activates it. If the new grab client can not * be attached to an existing buffer tree node it is put into the inactive list * for later activation. * * \return Standard. */ int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s) { int ret; struct grab_client *gc = zalloc(sizeof(struct grab_client)); ret = gc_check_args(lpr, gc); if (ret < 0) goto err_out; ret = dup(fd); if (ret < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err_out; } gc->fd = ret; para_list_add(&gc->node, &inactive_grab_client_list); gc_activate(gc, s); return 1; err_out: free(gc); return ret; }