/* SPDX-License-Identifier: GPL-2.0 */ /** \file buffer_tree.c Buffer tree and buffer pool implementations. */ #include "para.h" #include "list.h" #include "string.h" #include "buffer_tree.h" #include "error.h" #include "sched.h" /* whead = NULL means area full */ struct btr_pool { char *name; char *area_start; char *area_end; char *rhead; char *whead; }; struct btr_buffer { char *buf; size_t size; /** The number of references to this buffer. */ int refcount; /* NULL means no buffer pool but a heap buffer. */ struct btr_pool *pool; /* Only relevant if pool is NULL. */ bool dont_free; }; struct btr_buffer_reference { struct btr_buffer *btrb; size_t consumed; /* Each buffer reference belongs to the buffer queue list of some buffer tree node. */ struct list_head node; size_t wrap_count; }; struct btr_node { char *name; struct btr_node *parent; /* The position of this btr node in the buffer tree. */ struct list_head node; /* The children nodes of this btr node are linked together in a list. */ struct list_head children; /* Time of first data transfer. */ struct timeval start; /** * The input queue is a list of references to btr buffers. Each item on * the list represents an input buffer which has not been completely * used by this btr node. */ struct list_head input_queue; btr_command_handler execute; void *context; }; /** * Create a new buffer pool. * * \param name The name of the new buffer pool. * \param area_size The size in bytes of the pool area. * * \return An opaque pointer to the newly created buffer pool. It must be * passed to \ref btr_pool_free() after it is no longer used to deallocate * all resources. */ struct btr_pool *btr_pool_new(const char *name, size_t area_size) { struct btr_pool *btrp; PARA_INFO_LOG("%s, %zu bytes\n", name, area_size); btrp = alloc(sizeof(*btrp)); btrp->area_start = alloc(area_size); btrp->area_end = btrp->area_start + area_size; btrp->rhead = btrp->area_start; btrp->whead = btrp->area_start; btrp->name = para_strdup(name); return btrp; } /** * Deallocate resources used by a buffer pool. * * \param btrp A pointer obtained via \ref btr_pool_new(). */ void btr_pool_free(struct btr_pool *btrp) { if (!btrp) return; free(btrp->area_start); free(btrp->name); free(btrp); } /** * Return the size of the buffer pool area. * * \param btrp The buffer pool. * * \return The same value which was passed during creation time to * \ref btr_pool_new(). */ size_t btr_pool_size(const struct btr_pool *btrp) { return btrp->area_end - btrp->area_start; } static size_t btr_pool_filled(const struct btr_pool *btrp) { if (!btrp->whead) return btr_pool_size(btrp); if (btrp->rhead <= btrp->whead) return btrp->whead - btrp->rhead; return btr_pool_size(btrp) - (btrp->rhead - btrp->whead); } /** * Get the number of unused bytes in the buffer pool. * * \param btrp The pool. * * \return The number of bytes that can currently be allocated. * * Note that in general the returned number of bytes is not available as a * single contiguous buffer. */ size_t btr_pool_unused(const struct btr_pool *btrp) { return btr_pool_size(btrp) - btr_pool_filled(btrp); } /* * Return maximal size available for one read. This is smaller than the * value returned by btr_pool_unused(). */ static size_t btr_pool_available(const struct btr_pool *btrp) { if (!btrp->whead) return 0; if (btrp->rhead <= btrp->whead) return btrp->area_end - btrp->whead; return btrp->rhead - btrp->whead; } /** * Obtain the current write head. * * \param btrp The buffer pool. * \param result The write head is returned here. * * \return The maximal amount of bytes that may be written to the returned * buffer. */ size_t btr_pool_get_buffer(const struct btr_pool *btrp, char **result) { if (result) *result = btrp->whead; return btr_pool_available(btrp); } /** * Get references to buffers pointing to free space of the buffer pool area. * * \param btrp The buffer pool. * \param iov The I/O vector. * * \return Zero if the buffer pool is full, one if the free space of the * buffer pool area is available as a single contiguous buffer, two if the * free space consists of two buffers. If this function returns the value n, * then n elements of the I/O vector are initialized. */ int btr_pool_get_buffers(const struct btr_pool *btrp, struct iovec iov[2]) { size_t sz, unused; char *buf; sz = btr_pool_get_buffer(btrp, &buf); if (sz == 0) return 0; iov[0].iov_len = sz; iov[0].iov_base = buf; unused = btr_pool_unused(btrp); if (sz == unused) return 1; iov[1].iov_len = unused - sz; iov[1].iov_base = btrp->area_start; return 2; } /** * Mark a part of the buffer pool area as allocated. * * \param btrp The buffer pool. * \param size The amount of bytes to be allocated. * * This is usually called after the caller wrote to the buffer obtained by * \ref btr_pool_get_buffer(). */ static void btr_pool_allocate(struct btr_pool *btrp, size_t size) { char *end; if (size == 0) return; assert(size <= btr_pool_available(btrp)); end = btrp->whead + size; assert(end <= btrp->area_end); if (end == btrp->area_end) { PARA_DEBUG_LOG("%s: end of pool area reached\n", btrp->name); end = btrp->area_start; } if (end == btrp->rhead) { PARA_DEBUG_LOG("%s btrp buffer full\n", btrp->name); end = NULL; /* buffer full */ } btrp->whead = end; } static void btr_pool_deallocate(struct btr_pool *btrp, size_t size) { char *end = btrp->rhead + size; if (size == 0) return; assert(end <= btrp->area_end); assert(size <= btr_pool_filled(btrp)); if (end == btrp->area_end) end = btrp->area_start; if (!btrp->whead) btrp->whead = btrp->rhead; btrp->rhead = end; if (btrp->rhead == btrp->whead) btrp->rhead = btrp->whead = btrp->area_start; } #define FOR_EACH_CHILD(_tn, _btrn) list_for_each_entry((_tn), \ &((_btrn)->children), node) #define FOR_EACH_CHILD_SAFE(_tn, _tmp, _btrn) \ list_for_each_entry_safe((_tn), (_tmp), &((_btrn)->children), node) #define FOR_EACH_BUFFER_REF(_br, _btrn) \ list_for_each_entry((_br), &(_btrn)->input_queue, node) #define FOR_EACH_BUFFER_REF_SAFE(_br, _tmp, _btrn) \ list_for_each_entry_safe((_br), (_tmp), &(_btrn)->input_queue, node) /** * Create a new buffer tree node. * * \param bnd Specifies how to create the new node. * * \return A pointer to the newly allocated node. * * This function always succeeds (or calls exit()). The returned pointer * must be freed using \ref btr_remove_node(). */ struct btr_node *btr_new_node(struct btr_node_description *bnd) { struct btr_node *btrn = alloc(sizeof(*btrn)); btrn->name = para_strdup(bnd->name); btrn->parent = bnd->parent; btrn->execute = bnd->handler; btrn->context = bnd->context; btrn->start.tv_sec = 0; btrn->start.tv_usec = 0; init_list_head(&btrn->children); init_list_head(&btrn->input_queue); if (!bnd->child) { if (bnd->parent) { list_add_tail(&btrn->node, &bnd->parent->children); PARA_INFO_LOG("new leaf node: %s (child of %s)\n", bnd->name, bnd->parent->name); } else PARA_INFO_LOG("added %s as btr root\n", bnd->name); goto out; } if (!bnd->parent) { assert(!bnd->child->parent); PARA_INFO_LOG("new root: %s (was %s)\n", bnd->name, bnd->child->name); btrn->parent = NULL; list_add_tail(&bnd->child->node, &btrn->children); /* link it in */ bnd->child->parent = btrn; goto out; } list_add_tail(&btrn->node, &bnd->parent->children); list_move(&bnd->child->node, &btrn->children); bnd->child->parent = btrn; PARA_INFO_LOG("added %s as internal node\n", bnd->name); out: return btrn; } /* * Allocate a new btr buffer. * * The freshly allocated buffer will have a zero reference count and will * not be associated with a buffer tree pool. */ static struct btr_buffer *new_btrb(char *buf, size_t size) { struct btr_buffer *btrb = zalloc(sizeof(*btrb)); btrb->buf = buf; btrb->size = size; return btrb; } static void dealloc_buffer(struct btr_buffer *btrb) { if (btrb->pool) btr_pool_deallocate(btrb->pool, btrb->size); else if (!btrb->dont_free) free(btrb->buf); } static struct btr_buffer_reference *get_first_input_br(const struct btr_node *btrn) { if (list_empty(&btrn->input_queue)) return NULL; return list_first_entry(&btrn->input_queue, struct btr_buffer_reference, node); } static struct btr_buffer_reference *get_last_input_br(const struct btr_node *btrn) { if (list_empty(&btrn->input_queue)) return NULL; return list_last_entry(&btrn->input_queue, struct btr_buffer_reference, node); } /* * Deallocate the buffer reference and decrement the reference count of the * underlying buffer. If the refcount becomes zero, deallocate the buffer. */ static void btr_drop_buffer_reference(struct btr_buffer_reference *br) { struct btr_buffer *btrb = br->btrb; list_del(&br->node); free(br); btrb->refcount--; if (btrb->refcount == 0) { if (!btrb->dont_free) dealloc_buffer(btrb); free(btrb); } } static bool try_merge_btrb(const struct btr_buffer *btrb, const struct btr_node *btrn, size_t consumed) { struct btr_node *ch; if (consumed > 0) return false; FOR_EACH_CHILD(ch, btrn) { struct btr_buffer_reference *br = get_last_input_br(ch); if (!br) return false; if (br->consumed > 0) return false; if (br->btrb->buf + br->btrb->size != btrb->buf) return false; if (!br->btrb->pool) continue; if (br->btrb->size + btrb->size >= btr_pool_size(br->btrb->pool) / 3) return false; } /* merge it with last input buffer references */ FOR_EACH_CHILD(ch, btrn) { struct btr_buffer_reference *br = get_last_input_br(ch); br->btrb->size += btrb->size; } return true; } /* Returns whether buffers were merged. */ static bool add_btrb_to_children(struct btr_buffer *btrb, struct btr_node *btrn, size_t consumed) { struct btr_node *ch; if (btrn->start.tv_sec == 0) btrn->start = *now; if (try_merge_btrb(btrb, btrn, consumed)) return true; FOR_EACH_CHILD(ch, btrn) { struct btr_buffer_reference *br = zalloc(sizeof(*br)); br->btrb = btrb; br->consumed = consumed; list_add_tail(&br->node, &ch->input_queue); btrb->refcount++; if (ch->start.tv_sec == 0) ch->start = *now; } return false; } /** * Insert a heap buffer into the buffer tree. * * \param buf The buffer to insert. * \param size The size of buf in bytes. * \param btrn Position in the buffer tree to create the output. * * This creates buffer references and adds these references to each child of * the given buffer tree node. The buffer will be freed using standard free() * once no buffer tree node is referencing it any more. * * Note that this function must not be used if the buffer was obtained from * a buffer pool. Use \ref btr_add_output_pool() in this case. */ void btr_add_output(char *buf, size_t size, struct btr_node *btrn) { struct btr_buffer *btrb; if (size == 0) return; if (list_empty(&btrn->children)) { free(buf); return; } btrb = new_btrb(buf, size); add_btrb_to_children(btrb, btrn, 0); } /** * Insert a buffer into the buffer tree, non-freeing variant. * * \param buf See \ref btr_add_output(). * \param size See \ref btr_add_output(). * \param btrn See \ref btr_add_output(). * * This is similar to \ref btr_add_output() but additionally sets the dont_free * flag on the buffer. If the reference count of the buffer drops to zero, * the buffer will *not* be deallocated, since this flag is set. * * The dont_free bit also prevents the children of buffer tree node from * modifying the buffer contents in-place. Specifically, \ref btr_inplace_ok() * returns false if there is any buffer in the input queue with the dont_free * bit set. */ void btr_add_output_dont_free(const char *buf, size_t size, struct btr_node *btrn) { struct btr_buffer *btrb; if (size == 0) return; if (list_empty(&btrn->children)) return; btrb = new_btrb((char *)buf, size); btrb->dont_free = true; if (add_btrb_to_children(btrb, btrn, 0)) free(btrb); } /** * Feed data to child nodes of a buffer tree node. * * \param btrp The buffer pool. * \param size The number of bytes to be allocated and fed to each child. * \param btrn The node whose children are to be fed. * * This function allocates the amount of bytes from the buffer pool area, * starting at the current value of the write head, and creates buffer * references to the resulting part of the buffer pool area, one for each * child of the given buffer tree node. The references are then fed into * the input queue of each child. */ void btr_add_output_pool(struct btr_pool *btrp, size_t size, struct btr_node *btrn) { struct btr_buffer *btrb; char *buf; size_t avail; if (size == 0) return; if (list_empty(&btrn->children)) return; avail = btr_pool_get_buffer(btrp, &buf); assert(avail >= size); btr_pool_allocate(btrp, size); btrb = new_btrb(buf, size); btrb->pool = btrp; if (add_btrb_to_children(btrb, btrn, 0)) free(btrb); } /** * Copy data to write head of a buffer pool and feed it to all children nodes. * * \param src The source buffer. * \param n The size of the source buffer in bytes. * \param btrp The destination buffer pool. * \param btrn Add the data as output of this node. * * This is expensive. The caller must make sure the data fits into the buffer * pool area. */ void btr_copy(const void *src, size_t n, struct btr_pool *btrp, struct btr_node *btrn) { char *buf; size_t sz, copy; if (n == 0) return; assert(n <= btr_pool_unused(btrp)); sz = btr_pool_get_buffer(btrp, &buf); copy = PARA_MIN(sz, n); memcpy(buf, src, copy); btr_add_output_pool(btrp, copy, btrn); if (copy == n) return; sz = btr_pool_get_buffer(btrp, &buf); assert(sz >= n - copy); assert(buf); memcpy(buf, src + copy, n - copy); btr_add_output_pool(btrp, n - copy, btrn); } static void btr_pushdown_br(struct btr_buffer_reference *br, struct btr_node *btrn) { add_btrb_to_children(br->btrb, btrn, br->consumed); btr_drop_buffer_reference(br); } /** * Feed all buffer references of the input queue through the output channel. * * \param btrn The node whose buffer references should be pushed down. * * This function is useful for filters that do not change the contents of the * buffers at all, like the wav filter or the amp filter if no amplification * was specified. This function is rather cheap. * * \sa \ref btr_pushdown_one(). */ void btr_pushdown(struct btr_node *btrn) { struct btr_buffer_reference *br, *tmp; FOR_EACH_BUFFER_REF_SAFE(br, tmp, btrn) btr_pushdown_br(br, btrn); } /** * Feed the next buffer of the input queue through the output channel. * * \param btrn The node whose first input queue buffer should be pushed down. * * This works like \ref btr_pushdown() but pushes down only one buffer * reference. */ void btr_pushdown_one(struct btr_node *btrn) { struct btr_buffer_reference *br; if (list_empty(&btrn->input_queue)) return; br = list_first_entry(&btrn->input_queue, struct btr_buffer_reference, node); btr_pushdown_br(br, btrn); } /* * Find out whether a node is a leaf node. * * \param btrn The node to check. * * \return True if this node has no children. False otherwise. */ static bool btr_no_children(const struct btr_node *btrn) { return list_empty(&btrn->children); } /** * Find out whether a node is an orphan. * * \param btrn The buffer tree node. * * \return True if the buffer tree node has no parent. * * This function returns true for the root node and false for any other node. * * After a (non-leaf) node was removed removed from the tree, the function * returns true for all child nodes. */ bool btr_no_parent(const struct btr_node *btrn) { return !btrn->parent; } /** * Find out whether it is OK to change an input buffer. * * \param btrn The buffer tree node to check. * * This is used by filters that produce exactly the same amount of output * as there is input. The amp filter which multiplies each sample by some * number is an example of such a filter. If there are no other nodes in the * buffer tree that read the same input stream (i.e. if the buffer tree node * has no siblings), the node may modify its input buffer directly and push * down the modified buffer to its children, thereby avoiding to allocate * a possibly large additional buffer. * * Since the buffer tree may change at any time, this function should be called * during each post_monitor call. * * \return True if the buffer tree node has no siblings. */ bool btr_inplace_ok(const struct btr_node *btrn) { struct btr_buffer_reference *br; FOR_EACH_BUFFER_REF(br, btrn) { struct btr_buffer *btrb = br->btrb; if (btrb->refcount > 1) return false; if (btrb->dont_free == true) return false; } return true; } static inline size_t br_available_bytes(const struct btr_buffer_reference *br) { return br->btrb->size - br->consumed; } static size_t btr_get_buffer_by_reference(const struct btr_buffer_reference *br, char **buf) { if (buf) *buf = br->btrb->buf + br->consumed; return br_available_bytes(br); } /** * Obtain the next buffer of the input queue, omitting data. * * \param btrn The node whose input queue is to be queried. * \param omit Number of bytes to be omitted. * \param bufp Result pointer. It is OK to pass NULL here. * * If a buffer tree node needs more input data but can not consume the data * it already has (because it might be needed again later) this function can * be used instead of \ref btr_next_buffer() to get a reference to the buffer * obtained by skipping the given number of bytes. Skipped input bytes are * not consumed. * * If omit is zero, this function acts identical to \ref btr_next_buffer(). * * \return Number of bytes in the returned buffer. If there are less than or * equal to omit many bytes available in the input queue of the buffer tree * node, the function returns zero and the value of of the result buffer * is undefined. */ size_t btr_next_buffer_omit(const struct btr_node *btrn, size_t omit, char **bufp) { struct btr_buffer_reference *br; size_t wrap_count, sz, rv = 0; char *buf, *result = NULL; br = get_first_input_br(btrn); if (!br) return 0; wrap_count = br->wrap_count; if (wrap_count > 0) { /* we have a wrap buffer */ sz = btr_get_buffer_by_reference(br, &buf); if (sz > omit) { /* and it's big enough */ result = buf + omit; rv = sz - omit; /* * Wrap buffers are allocated with malloc(), so the next * buffer ref will not align nicely, so we return the * tail of the wrap buffer. */ goto out; } /* * The next wrap_count bytes exist twice, in the wrap buffer * and as a buffer reference in the buffer tree pool. */ omit += wrap_count; } /* * For buffer tree pools, the buffers in the list align, i.e. the next * buffer in the list starts directly at the end of its predecessor. In * this case we merge adjacent buffers and return one larger buffer * instead. */ FOR_EACH_BUFFER_REF(br, btrn) { sz = btr_get_buffer_by_reference(br, &buf); if (result) { if (result + rv != buf) goto out; rv += sz; } else if (sz > omit) { result = buf + omit; rv = sz - omit; } else omit -= sz; } if (!result) return 0; out: if (bufp) *bufp = result; return rv; } /** * Obtain the next buffer of the input queue of a buffer tree node. * * \param btrn The node whose input queue is to be queried. * \param bufp Result pointer. * * \return The number of bytes that can be read from buf. * * The call of this function is is equivalent to calling \ref * btr_next_buffer_omit() with an omit value of zero. */ size_t btr_next_buffer(const struct btr_node *btrn, char **bufp) { return btr_next_buffer_omit(btrn, 0, bufp); } /** * Deallocate the given number of bytes from the input queue. * * \param btrn The buffer tree node. * \param numbytes The number of bytes to be deallocated. * * This function must be used to get rid of existing buffer references in the * node's input queue. If no references to a buffer remain, the underlying * buffers are either freed (in the non-buffer pool case) or the read head of * the buffer pool is being advanced. * * If the number of bytes to consume is smaller than the buffer size, the * buffer is not deallocated and subsequent calls to \ref btr_next_buffer() * return the remaining part of the buffer. */ void btr_consume(struct btr_node *btrn, size_t numbytes) { struct btr_buffer_reference *br, *tmp; size_t sz; if (numbytes == 0) return; br = get_first_input_br(btrn); assert(br); if (br->wrap_count == 0) { /* * No wrap buffer. Drop buffer references whose buffer * has been fully used. */ FOR_EACH_BUFFER_REF_SAFE(br, tmp, btrn) { if (br->consumed + numbytes <= br->btrb->size) { br->consumed += numbytes; if (br->consumed == br->btrb->size) btr_drop_buffer_reference(br); return; } numbytes -= br->btrb->size - br->consumed; btr_drop_buffer_reference(br); } assert(false); } /* * We have a wrap buffer, consume from it. If in total, i.e. including * previous calls to brt_consume(), less than wrap_count has been * consumed, there's nothing more we can do. * * Otherwise we drop the wrap buffer and consume from subsequent * buffers of the input queue the correct amount of bytes. This is the * total number of bytes that have been consumed from the wrap buffer. */ PARA_DEBUG_LOG("consuming %zu/%zu bytes from wrap buffer\n", numbytes, br_available_bytes(br)); assert(numbytes <= br_available_bytes(br)); if (br->consumed + numbytes < br->wrap_count) { br->consumed += numbytes; return; } PARA_DEBUG_LOG("dropping wrap buffer (%zu bytes)\n", br->btrb->size); /* get rid of the wrap buffer */ sz = br->consumed + numbytes; btr_drop_buffer_reference(br); return btr_consume(btrn, sz); } /** * Clear the input queue of a buffer tree node. * * \param btrn The node whose input queue should be cleared. */ void btr_drain(struct btr_node *btrn) { struct btr_buffer_reference *br, *tmp; FOR_EACH_BUFFER_REF_SAFE(br, tmp, btrn) btr_drop_buffer_reference(br); } static void btr_free_node(struct btr_node *btrn) { free(btrn->name); free(btrn); } /** * Remove a node from a buffer tree. * * \param btrnp Determines the node to remove. * * This orphans all children of the given buffer tree node and removes this * node from the child list of its parent. Moreover, the input queue is * flushed and the given node pointer given is set to NULL. * * \sa \ref btr_splice_out_node(). */ void btr_remove_node(struct btr_node **btrnp) { struct btr_node *ch; struct btr_node *btrn; if (!btrnp) return; btrn = *btrnp; if (!btrn) goto out; PARA_INFO_LOG("removing btr node %s from buffer tree\n", btrn->name); FOR_EACH_CHILD(ch, btrn) ch->parent = NULL; btr_drain(btrn); if (btrn->parent) list_del(&btrn->node); btr_free_node(btrn); out: *btrnp = NULL; } /** * Return the amount of available input bytes of a buffer tree node. * * \param btrn The node whose input size should be computed. * * \return The total number of bytes available in the node's input * queue. * * This simply iterates over all buffer references in the input queue and * returns the sum of the sizes of all references. */ size_t btr_get_input_queue_size(const struct btr_node *btrn) { struct btr_buffer_reference *br; size_t size = 0, wrap_consumed = 0; FOR_EACH_BUFFER_REF(br, btrn) { if (br->wrap_count != 0) { wrap_consumed = br->consumed; continue; } size += br_available_bytes(br); } assert(wrap_consumed <= size); size -= wrap_consumed; return size; } static bool min_iqs_available(size_t min_iqs, const struct btr_node *btrn) { struct btr_buffer_reference *br; size_t have = 0, wrap_consumed = 0; FOR_EACH_BUFFER_REF(br, btrn) { if (br->wrap_count != 0) { wrap_consumed = br->consumed; continue; } have += br_available_bytes(br); if (have > wrap_consumed + min_iqs) return true; } return false; } /** * Remove a node from the buffer tree, reconnecting parent and children. * * \param btrnp The node to splice out. * * This function is used by buffer tree nodes that do not exist during the * whole lifetime of the buffer tree. Unlike \ref btr_remove_node(), calling * \ref btr_splice_out_node() does not split the tree into disconnected * components but reconnects the buffer tree by making all child nodes of * of the given node children of its parent. */ void btr_splice_out_node(struct btr_node **btrnp) { struct btr_node *btrn = *btrnp, *ch, *tmp; assert(btrn); PARA_NOTICE_LOG("splicing out %s\n", btrn->name); btr_pushdown(btrn); if (btrn->parent) list_del(&btrn->node); FOR_EACH_CHILD_SAFE(ch, tmp, btrn) { PARA_INFO_LOG("parent(%s): %s\n", ch->name, btrn->parent? btrn->parent->name : "NULL"); ch->parent = btrn->parent; if (btrn->parent) list_move(&ch->node, &btrn->parent->children); else list_del(&ch->node); } assert(list_empty(&btrn->children)); btr_free_node(btrn); *btrnp = NULL; } /** * Return number of queued output bytes of a buffer tree node. * * \param btrn The node whose output queue size should be computed. * * \return This function iterates over all children of the given node and * returns the size of the largest input queue. */ size_t btr_get_output_queue_size(const struct btr_node *btrn) { size_t max_size = 0; struct btr_node *ch; FOR_EACH_CHILD(ch, btrn) { size_t size = btr_get_input_queue_size(ch); max_size = PARA_MAX(max_size, size); } return max_size; } /** * Execute an inter-node command on the given node or on a parent node. * * \param btrn The node to start looking. * \param command The command to execute. * \param value_result Additional arguments and result value. * * This function traverses the buffer tree from the given buffer tree node * upwards and looks for the first node that understands the given command. On * this node the command is executed, and the result is returned via the * value/result pointer. * * \return -ENOTSUP if no parent node understands the command. Otherwise the * return value of the command handler is returned. * * \sa \ref receiver::execute(), \ref filter::execute() */ int btr_exec_up(const struct btr_node *btrn, const char *command, char **value_result) { int ret; for (; btrn; btrn = btrn->parent) { if (!btrn->execute) continue; PARA_INFO_LOG("executing %s on %s\n", command, btrn->name); ret = btrn->execute(btrn, command, value_result); if (ret == -ERRNO_TO_PARA_ERROR(ENOTSUP)) continue; if (ret < 0) return ret; if (value_result && *value_result) PARA_INFO_LOG("%s(%s): %s\n", command, btrn->name, *value_result); return 1; } return -ERRNO_TO_PARA_ERROR(ENOTSUP); } /** * Obtain the context of a buffer node tree. * * \param btrn The node whose output queue size should be computed. * * \return A pointer to the context as specified at node creation time. * * \sa \ref btr_new_node(), struct \ref btr_node_description. */ void *btr_context(const struct btr_node *btrn) { return btrn->context; } static bool need_buffer_pool_merge(const struct btr_node *btrn) { struct btr_buffer_reference *br = get_first_input_br(btrn); if (!br) return false; if (br->wrap_count != 0) return true; if (br->btrb->pool) return true; return false; } static void merge_input_pool(struct btr_node *btrn, size_t dest_size) { struct btr_buffer_reference *br, *wbr = NULL; int num_refs; /* including wrap buffer */ char *buf, *buf1 = NULL, *buf2 = NULL; size_t sz, sz1 = 0, sz2 = 0, wb_consumed = 0; br = get_first_input_br(btrn); if (!br || br_available_bytes(br) >= dest_size) return; num_refs = 0; FOR_EACH_BUFFER_REF(br, btrn) { num_refs++; sz = btr_get_buffer_by_reference(br, &buf); if (sz == 0) break; if (br->wrap_count != 0) { assert(!wbr); assert(num_refs == 1); wbr = br; if (sz >= dest_size) return; wb_consumed = br->consumed; continue; } if (!buf1) { buf1 = buf; sz1 = sz; goto next; } if (buf1 + sz1 == buf) { sz1 += sz; goto next; } if (!buf2) { buf2 = buf; sz2 = sz; goto next; } assert(buf2 + sz2 == buf); sz2 += sz; next: if (sz1 + sz2 >= dest_size + wb_consumed) break; } if (!buf2) /* nothing to do */ return; assert(buf1 && sz2 > 0); /* * If the second buffer is large, we only take the first part of it * to avoid having to copy huge buffers. */ sz2 = PARA_MIN(sz2, (size_t)(64 * 1024)); if (!wbr) { /* Make a new wrap buffer combining buf1 and buf2. */ sz = sz1 + sz2; buf = alloc(sz); PARA_DEBUG_LOG("merging input buffers: (%p:%zu, %p:%zu) -> %p:%zu\n", buf1, sz1, buf2, sz2, buf, sz); memcpy(buf, buf1, sz1); memcpy(buf + sz1, buf2, sz2); br = zalloc(sizeof(*br)); br->btrb = new_btrb(buf, sz); br->btrb->refcount = 1; br->consumed = 0; /* This is a wrap buffer */ br->wrap_count = sz1; para_list_add(&br->node, &btrn->input_queue); return; } /* * We already have a wrap buffer, but it is too small. It might be * partially used. */ if (wbr->wrap_count == sz1 && wbr->btrb->size >= sz1 + sz2) /* nothing we can do about it */ return; sz = sz1 + sz2 - wbr->btrb->size; /* amount of new data */ PARA_DEBUG_LOG("increasing wrap buffer %zu -> %zu\n", wbr->btrb->size, wbr->btrb->size + sz); wbr->btrb->size += sz; wbr->btrb->buf = para_realloc(wbr->btrb->buf, wbr->btrb->size); /* copy the new data to the end of the reallocated buffer */ assert(sz2 >= sz); memcpy(wbr->btrb->buf + wbr->btrb->size - sz, buf2 + sz2 - sz, sz); } /** * Merge the first two input buffers into one. * * This is a quite expensive operation. * * \return The number of buffers that have been available (zero, one or two). */ static int merge_input(struct btr_node *btrn) { struct btr_buffer_reference *brs[2], *br; char *bufs[2], *buf; size_t szs[2], sz; int i; if (list_empty(&btrn->input_queue)) return 0; if (list_is_singular(&btrn->input_queue)) return 1; i = 0; /* get references to the first two buffers */ FOR_EACH_BUFFER_REF(br, btrn) { brs[i] = br; szs[i] = btr_get_buffer_by_reference(brs[i], bufs + i); i++; if (i == 2) break; } assert(i == 2); /* make a new btrb that combines the two buffers and a br to it. */ sz = szs[0] + szs[1]; buf = alloc(sz); PARA_DEBUG_LOG("%s: memory merging input buffers: (%zu, %zu) -> %zu\n", btrn->name, szs[0], szs[1], sz); memcpy(buf, bufs[0], szs[0]); memcpy(buf + szs[0], bufs[1], szs[1]); br = zalloc(sizeof(*br)); br->btrb = new_btrb(buf, sz); br->btrb->refcount = 1; /* replace the first two refs by the new one */ btr_drop_buffer_reference(brs[0]); btr_drop_buffer_reference(brs[1]); para_list_add(&br->node, &btrn->input_queue); return 2; } /** * Combine input queue buffers. * * \param btrn The buffer tree node whose input should be merged. * \param dest_size Stop merging if a buffer of at least this size exists. * * Used to combine as many buffers as needed into a single buffer whose size * is at least the destination size. This function is rather cheap in case * the parent node uses buffer pools and rather expensive otherwise. * * Note that if less than the destination size bytes are available in total, * this function does nothing and subsequent calls to \ref btr_next_buffer() * will still return a buffer whose size is less than the destination size. */ void btr_merge(struct btr_node *btrn, size_t dest_size) { if (need_buffer_pool_merge(btrn)) return merge_input_pool(btrn, dest_size); for (;;) { char *buf; size_t len = btr_next_buffer(btrn, &buf); if (len >= dest_size) return; PARA_DEBUG_LOG("input size = %zu < %zu = dest\n", len, dest_size); if (merge_input(btrn) < 2) return; } } static bool btr_eof(const struct btr_node *btrn) { char *buf; size_t len = btr_next_buffer(btrn, &buf); return (len == 0 && btr_no_parent(btrn)); } static void log_tree_recursively(const struct btr_node *btrn, int loglevel, int depth) { struct btr_node *ch; const char spaces[] = " ", *space = spaces + 16 - depth; if (depth > 16) return; para_log(loglevel, "%s%s\n", space, btrn->name); FOR_EACH_CHILD(ch, btrn) log_tree_recursively(ch, loglevel, depth + 1); } /** * Write the current buffer (sub-)tree to the log. * * \param btrn Start logging at this node. * \param loglevel Set severity with which the tree should be logged. */ void btr_log_tree(const struct btr_node *btrn, int loglevel) { return log_tree_recursively(btrn, loglevel, 0); } /** * Find the node with the given name in the buffer tree. * * \param name The name of the node to search. * \param root Where to start the search. * * \return A pointer to the node with the given name on success. If the name * is NULL, the function returns the given root node. If there is no node * with the given name, NULL is returned. */ struct btr_node *btr_search_node(const char *name, struct btr_node *root) { struct btr_node *ch; if (!name) return root; if (!strcmp(root->name, name)) return root; FOR_EACH_CHILD(ch, root) { struct btr_node *result = btr_search_node(name, ch); if (result) return result; } return NULL; } /* 96K ought to be enough for everybody ;) */ #define BTRN_MAX_PENDING (96 * 1024) /** * Return the current state of a buffer tree node. * * \param btrn The node whose state should be queried. * \param min_iqs The minimal input queue size. * \param type The type of the buffer tree node. * * Most users of the buffer tree subsystem call this function from both * their ->pre_monitor() and ->post_monitor() methods. * * \return Negative if an error condition was detected, zero if there * is nothing to do and positive otherwise. * * Examples: * * - If a non-root node has no parent and an empty input queue, the function * returns -E_BTR_EOF. Similarly, if a non-leaf node has no children, * -E_BTR_NO_CHILD is returned. * * - If less bytes than the minimal input queue size are available in the * input queue and no EOF condition was detected, the function returns zero. * * - If there is plenty of data left in the input queue of the children of * the given buffer tree node, the function also returns zero in order to * bound the memory usage of the buffer tree. */ int btr_node_status(const struct btr_node *btrn, size_t min_iqs, enum btr_node_type type) { if (type != BTR_NT_LEAF && btr_no_children(btrn)) return -E_BTR_NO_CHILD; if (type != BTR_NT_ROOT && btr_eof(btrn)) return -E_EOF; if (btr_get_output_queue_size(btrn) > BTRN_MAX_PENDING) return 0; if (type == BTR_NT_ROOT) return 1; if (min_iqs_available(min_iqs, btrn)) return 1; return btr_no_parent(btrn); } /** * Get the time of the first I/O for a buffer tree node. * * \param btrn The node whose I/O time should be obtained. * \param tv Result pointer. * * Mainly useful for the time display of para_audiod. */ void btr_get_node_start(const struct btr_node *btrn, struct timeval *tv) { *tv = btrn->start; } /** * Get the parent node of a buffer tree node. * * \param btrn The node whose parent should be returned. Must not be NULL. * * \return The parent node, or NULL if the given node is the root of the * buffer tree. */ struct btr_node *btr_parent(const struct btr_node *btrn) { return btrn->parent; }