/* SPDX-License-Identifier: GPL-2.0 */ /** \file stdout.c Functions that deal with writing to stdout. */ #include "para.h" #include "list.h" #include "sched.h" #include "fd.h" #include "error.h" #include "string.h" #include "buffer_tree.h" #include "stdout.h" /* para_recv, para_filter and para_client create an stdout task. */ struct stdout_task { /* Stdout is always a leaf node of the buffer tree. */ struct btr_node *btrn; }; /* Monitor STDOUT_FILENO if there is input data available. */ static void stdout_pre_monitor(struct sched *s, void *context) { struct stdout_task *sot = context; int ret; ret = btr_node_status(sot->btrn, 0, BTR_NT_LEAF); if (ret > 0) sched_monitor_writefd(STDOUT_FILENO, s); else if (ret < 0) sched_min_delay(s); } /* * If input from the buffer tree is available and STDOUT_FILENO is ready, write * as much as possible. */ static int stdout_post_monitor(struct sched *s, void *context) { struct stdout_task *sot = context; struct btr_node *btrn = sot->btrn; int ret; char *buf; size_t sz; ret = btr_node_status(btrn, 0, BTR_NT_LEAF); if (ret < 0) goto out; if (ret == 0) return 0; if (!sched_write_ok(STDOUT_FILENO, s)) return 0; for (;;) { sz = btr_next_buffer(btrn, &buf); if (sz == 0) break; ret = xwrite(STDOUT_FILENO, buf, sz); if (ret <= 0) break; btr_consume(btrn, ret); } out: if (ret < 0) btr_remove_node(&sot->btrn); return ret; } /** * Register a stdout task structure. * * \param parent From where we receive the data for stdout. * * \return An opaque pointer which identifies the newly created task. * All errors are regarded as fatal, hence this function never returns NULL. */ struct stdout_task *stdout_new(struct btr_node *parent) { struct stdout_task *sot = zalloc(sizeof(*sot)); sot->btrn = btr_new_node(&(struct btr_node_description) {.name = "stdout", .parent = parent}); return sot; } /** * Register an instance of the stdout task to the scheduler. * * \param sot As returned by \ref stdout_new(). * \param s The task will be added to this scheduler's task list. * * \sa \ref stdin_register(), \ref sched_new(). */ void stdout_register(struct stdout_task *sot, struct sched *s) { int ret; task_register(&(struct task_info) { .name = "stdout", .pre_monitor = stdout_pre_monitor, .post_monitor = stdout_post_monitor, .context = sot, }, s); if (isatty(STDOUT_FILENO)) return; ret = mark_fd_nonblocking(STDOUT_FILENO); if (ret < 0) { PARA_EMERG_LOG("set stdout to non-blocking mode: %s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } } /** * Get the buffer tree node of the stdout task. * * \param sot As returned from \ref stdout_new(). * * \return A pointer to the node that was allocated in \ref stdout_new(). This * never returns NULL. */ struct btr_node *stdout_btrn(const struct stdout_task *sot) { return sot->btrn; } /** * Deallocate all resources allocated in \ref stdout_new(). * * \param sot As returned from \ref stdout_new(). * * This should be called after \ref schedule() has returned. */ void stdout_free(struct stdout_task *sot) { btr_remove_node(&sot->btrn); free(sot); }