diff --git a/nimutils/c/md4nim.h b/nimutils/c/md4nim.h index 0fe0c62..a318a4b 100644 --- a/nimutils/c/md4nim.h +++ b/nimutils/c/md4nim.h @@ -1,5 +1,4 @@ -#define HATRACK_PER_INSTANCE_AUX -#include +#include "n00b.h" // John's stuff. typedef void (*CB_TYPE)(const char *, unsigned int, void*); diff --git a/nimutils/c/subproc.c b/nimutils/c/subproc.c deleted file mode 100644 index cc3fc1e..0000000 --- a/nimutils/c/subproc.c +++ /dev/null @@ -1,940 +0,0 @@ -/* - * Currently, we're using select() here, not epoll(), etc. - */ -#if defined(__linux__) -#include -#elif defined(__APPLE__) -#include -#else -#error "Platform not supported" -#endif -#include -#ifndef SWITCHBOARD_H__ -#include "switchboard.h" -#if defined(SB_DEBUG) || defined(SB_TEST) -#include "test.c" -#include "hex.h" -#endif -#endif - -extern int party_fd(party_t *party); -/* - * Initializes a `subprocess` context, setting the process to spawn. - * By default, it will *not* be run on a pty; call `subproc_use_pty()` - * before calling `subproc_run()` in order to turn that on. - * - * By default, the process will run QUIETLY, without any capture or - * passthrough of IO. See `subproc_set_passthrough()` for routing IO - * between the subprocess and the parent, and `subproc_set_capture()` - * for capturing output from the subprocess (or from your terminal). - * - * This does not take ownership of the strings passed in, and doesn't - * use them until you call subproc_run(). In general, don't free - * anything passed into this API until the process is done. - */ -void -subproc_init(subprocess_t *ctx, char *cmd, char *argv[], bool proxy_stdin_close) -{ - memset(ctx, 0, sizeof(subprocess_t)); - sb_init(&ctx->sb, DEFAULT_HEAP_SIZE); - ctx->cmd = cmd; - ctx->argv = argv; - ctx->capture = 0; - ctx->passthrough = 0; - ctx->proxy_stdin_close = proxy_stdin_close; - - sb_init_party_fd(&ctx->sb, &ctx->parent_stdin, 0, O_RDONLY, false, false, false); - sb_init_party_fd(&ctx->sb, &ctx->parent_stdout, 1, O_WRONLY, false, false, false); - sb_init_party_fd(&ctx->sb, &ctx->parent_stderr, 2, O_WRONLY, false, false, false); -} - -/* - * By default, we pass through the environment. Use this to set your own - * environment. - */ -bool -subproc_set_envp(subprocess_t *ctx, char *envp[]) -{ - if (ctx->run) { - return false; - } - - ctx->envp = envp; - - return true; -} - -/* - * This function passes the given string to the subprocess via - * stdin. You can set this once before calling `subproc_run()`; but - * after you've called `subproc_run()`, you can call this as many - * times as you like, as long as the subprocess is open and its stdin - * file descriptor hasn't been closed. - */ -bool -subproc_pass_to_stdin(subprocess_t *ctx, char *str, size_t len, bool close_fd) -{ - if (ctx->str_waiting || ctx->sb.done) { - return false; - } - - if (ctx->run && close_fd) { - return false; - } - - sb_init_party_input_buf(&ctx->sb, &ctx->str_stdin, str, len, true, true, - close_fd); - - if (ctx->run) { - return sb_route(&ctx->sb, &ctx->str_stdin, &ctx->subproc_stdin); - } else { - ctx->str_waiting = true; - - if (close_fd) { - ctx->pty_stdin_pipe = true; - } - - return true; - } -} - -/* - * This controls whether I/O gets proxied between the parent process - * and the subprocess. - * - * The `which` parameter should be some combination of the following - * flags: - * - * SP_IO_STDIN (what you type goes to subproc stdin) - * SP_IO_STDOUT (subproc's stdout gets written to your stdout) - * SP_IO_STDERR - * - * SP_IO_ALL proxies everything. It's fine to use this even if no pty is used. - * - * If `combine` is true, then all subproc output for any proxied streams - * will go to STDOUT. - */ -bool -subproc_set_passthrough(subprocess_t *ctx, unsigned char which, bool combine) -{ - if (ctx->run || which > SP_IO_ALL) { - return false; - } - - ctx->passthrough = which; - ctx->pt_all_to_stdout = combine; - - return true; -} -/* - * This controls whether input from a file descriptor is captured into - * a string that is available when the process ends. - * - * You can capture any stream, including what the user's typing on stdin. - * - * The `which` parameter should be some combination of the following - * flags: - * - * SP_IO_STDIN (what you type); reference for string is "stdin" - * SP_IO_STDOUT reference for string is "stdout" - * SP_IO_STDERR reference for string is "stderr" - * - * SP_IO_ALL captures everything. It's fine to use this even if no pty is used. - * - * If `combine` is true, then all subproc output for any streams will - * be combined into "stdout". Retrieve from the `sb_result_t` object - * returned from `subproc_run()`, using the sp_result_...() api. - */ -bool -subproc_set_capture(subprocess_t *ctx, unsigned char which, bool combine) -{ - if (ctx->run || which > SP_IO_ALL) { - return false; - } - - ctx->capture = which; - ctx->pt_all_to_stdout = combine; - - return true; -} - -bool -subproc_set_io_callback(subprocess_t *ctx, unsigned char which, - switchboard_cb_t cb) -{ - if (ctx->run || which > SP_IO_ALL) { - return false; - } - - deferred_cb_t *cbinfo = (deferred_cb_t *)malloc(sizeof(deferred_cb_t)); - - cbinfo->next = ctx->deferred_cbs; - cbinfo->which = which; - cbinfo->cb = cb; - - ctx->deferred_cbs = cbinfo; - - return true; -} - -/* - * This sets how long to wait in `select()` for file-descriptors to be - * ready with data to read. If you don't set this, there will be no - * timeout, and it's possible for the process to die and for the file - * descriptors associated with them to never return ready. - * - * If you have a timeout, a progress callback can be called. - * - * Also, when the process is not blocked on the select(), right before - * the next select we check the status of the subprocess. If it's - * returned and all its descriptors are marked as closed, and no - * descriptors that are open are waiting to write, then the subprocess - * switchboard will exit. - */ -void -subproc_set_timeout(subprocess_t *ctx, struct timeval *timeout) -{ - sb_set_io_timeout(&ctx->sb, timeout); -} - -/* - * Removes any set timeout. - */ -void -subproc_clear_timeout(subprocess_t *ctx) -{ - sb_clear_io_timeout(&ctx->sb); -} - -/* - * When called before subproc_run(), will spawn the child process on - * a pseudo-terminal. - */ -bool -subproc_use_pty(subprocess_t *ctx) -{ - if (ctx->run) { - return false; - } - ctx->use_pty = true; - return true; -} - -bool -subproc_set_startup_callback(subprocess_t *ctx, void (*cb)(void *)) -{ - ctx->startup_callback = cb; -} - -int -subproc_get_pty_fd(subprocess_t *ctx) -{ - return ctx->pty_fd; -} - -void -pause_passthrough(subprocess_t *ctx, unsigned char which) -{ - /* - * Since there's no real consequence to trying to pause a - * subscription that doesn't exist, we'll just try to pause every - * subscription implied by `which`. Note that if we see stderr, we - * try to unsubscribe it from both the parent's stdout and the - * parent's stderr; no strong need to care whether they were - * combined or not here.. - */ - - if (which & SP_IO_STDIN) { - if (ctx->pty_fd) { - sb_pause_route(&ctx->sb, &ctx->parent_stdin, &ctx->subproc_stdout); - } else { - sb_pause_route(&ctx->sb, &ctx->parent_stdin, &ctx->subproc_stdin); - } - } - if (which & SP_IO_STDOUT) { - sb_pause_route(&ctx->sb, &ctx->subproc_stdout, &ctx->parent_stdout); - } - if (!ctx->pty_fd && (which & SP_IO_STDERR)) { - sb_pause_route(&ctx->sb, &ctx->subproc_stderr, &ctx->parent_stdout); - sb_pause_route(&ctx->sb, &ctx->subproc_stderr, &ctx->parent_stderr); - } -} - -void -resume_passthrough(subprocess_t *ctx, unsigned char which) -{ - /* - * Since there's no real consequence to trying to pause a - * subscription that doesn't exist, we'll just try to pause every - * subscription implied by `which`. Note that if we see stderr, we - * try to unsubscribe it from both the parent's stdout and the - * parent's stderr; no strong need to care whether they were - * combined or not here.. - */ - - if (which & SP_IO_STDIN) { - if (ctx->pty_fd) { - sb_resume_route(&ctx->sb, &ctx->parent_stdin, &ctx->subproc_stdout); - } else { - sb_resume_route(&ctx->sb, &ctx->parent_stdin, &ctx->subproc_stdin); - } - } - if (which & SP_IO_STDOUT) { - sb_resume_route(&ctx->sb, &ctx->subproc_stdout, &ctx->parent_stdout); - } - if (!ctx->pty_fd && (which & SP_IO_STDERR)) { - sb_resume_route(&ctx->sb, &ctx->subproc_stderr, &ctx->parent_stdout); - sb_resume_route(&ctx->sb, &ctx->subproc_stderr, &ctx->parent_stderr); - } -} - -void -pause_capture(subprocess_t *ctx, unsigned char which) -{ - if (which & SP_IO_STDIN) { - sb_pause_route(&ctx->sb, &ctx->parent_stdin, &ctx->capture_stdin); - } - - if (which & SP_IO_STDOUT) { - sb_pause_route(&ctx->sb, &ctx->subproc_stdout, &ctx->capture_stdout); - } - - if ((which & SP_IO_STDERR) && !ctx->pty_fd) { - sb_pause_route(&ctx->sb, &ctx->subproc_stderr, &ctx->capture_stdout); - sb_pause_route(&ctx->sb, &ctx->subproc_stderr, &ctx->capture_stderr); - } -} - -void -resume_capture(subprocess_t *ctx, unsigned char which) -{ - if (which & SP_IO_STDIN) { - sb_resume_route(&ctx->sb, &ctx->parent_stdin, &ctx->capture_stdin); - } - - if (which & SP_IO_STDOUT) { - sb_resume_route(&ctx->sb, &ctx->subproc_stdout, &ctx->capture_stdout); - } - - if ((which & SP_IO_STDERR) && !ctx->pty_fd) { - sb_resume_route(&ctx->sb, &ctx->subproc_stderr, &ctx->capture_stdout); - sb_resume_route(&ctx->sb, &ctx->subproc_stderr, &ctx->capture_stderr); - } -} - -static void -setup_subscriptions(subprocess_t *ctx, bool pty) -{ - party_t *stderr_dst = &ctx->parent_stderr; - - if (ctx->pt_all_to_stdout) { - stderr_dst = &ctx->parent_stdout; - } - - if (ctx->passthrough) { - if (ctx->passthrough & SP_IO_STDIN) { - if (pty) { - // in pty, ctx->subproc_stdout is the same FD used for stdin - // as its the same r/w FD for both - sb_route(&ctx->sb, &ctx->parent_stdin, &ctx->subproc_stdout); - } - else { - sb_route(&ctx->sb, &ctx->parent_stdin, &ctx->subproc_stdin); - } - } - if (ctx->passthrough & SP_IO_STDOUT) { - sb_route(&ctx->sb, &ctx->subproc_stdout, &ctx->parent_stdout); - } - if (!pty && ctx->passthrough & SP_IO_STDERR) { - sb_route(&ctx->sb, &ctx->subproc_stderr, stderr_dst); - } - } - - if (ctx->capture) { - if (ctx->capture & SP_IO_STDIN) { - sb_init_party_output_buf(&ctx->sb, &ctx->capture_stdin, - "stdin", CAP_ALLOC); - } - if (ctx->capture & SP_IO_STDOUT) { - sb_init_party_output_buf(&ctx->sb, &ctx->capture_stdout, - "stdout", CAP_ALLOC); - } - - if (ctx->combine_captures) { - if (!(ctx->capture & SP_IO_STDOUT) && - ctx->capture & SP_IO_STDERR) { - if (ctx->capture & SP_IO_STDOUT) { - sb_init_party_output_buf(&ctx->sb, &ctx->capture_stdout, - "stdout", CAP_ALLOC); - } - } - - stderr_dst = &ctx->capture_stdout; - } - else { - if (!pty && ctx->capture & SP_IO_STDERR) { - sb_init_party_output_buf(&ctx->sb, &ctx->capture_stderr, - "stderr", CAP_ALLOC); - } - - stderr_dst = &ctx->capture_stderr; - } - - if (ctx->capture & SP_IO_STDIN) { - sb_route(&ctx->sb, &ctx->parent_stdin, &ctx->capture_stdin); - } - if (ctx->capture & SP_IO_STDOUT) { - sb_route(&ctx->sb, &ctx->subproc_stdout, &ctx->capture_stdout); - } - if (!pty && ctx->capture & SP_IO_STDERR) { - sb_route(&ctx->sb, &ctx->subproc_stderr, stderr_dst); - } - } - - if (ctx->str_waiting) { - sb_route(&ctx->sb, &ctx->str_stdin, &ctx->subproc_stdin); - ctx->str_waiting = false; - } - - // Make sure calls to the API know we've started! - ctx->run = true; -} - -static void -subproc_do_exec(subprocess_t *ctx) -{ - if (ctx->envp) { - execve(ctx->cmd, ctx->argv, ctx->envp); - } - else { - execv(ctx->cmd, ctx->argv); - } - // If we get past the exec, kill the subproc with non-zero exit code, - // which will tear down the switchboard and print to stderr the - // errono description. For example for nonexisting command will be: - // foo: No such file or directory - fprintf(stderr, "%s: %s\n",ctx->cmd, strerror(errno)); - // TODO switch back to abort() once better event handling is implemented - // in switchboard to correctly detect exit code as otherwise waitpid() - // detects program has exited however not all signal handlers have executed - // hence exit code is unknown yet - exit(1); -} - -party_t * -subproc_new_party_callback(switchboard_t *ctx, switchboard_cb_t cb) -{ - party_t *result = (party_t *)calloc(sizeof(party_t), 1); - sb_init_party_callback(ctx, result, cb); - - return result; -} - - -static void -subproc_install_callbacks(subprocess_t *ctx) -{ - deferred_cb_t *entry = ctx->deferred_cbs; - - while(entry) { - entry->to_free = subproc_new_party_callback(&ctx->sb, entry->cb); - if (entry->which & SP_IO_STDIN) { - sb_route(&ctx->sb, &ctx->parent_stdin, entry->to_free); - } - if (entry->which & SP_IO_STDOUT) { - sb_route(&ctx->sb, &ctx->subproc_stdout, entry->to_free); - } - if (entry->which & SP_IO_STDERR) { - sb_route(&ctx->sb, &ctx->subproc_stderr, entry->to_free); - } - entry = entry->next; - } -} - -static void -run_startup_callback(subprocess_t *ctx) -{ - if (ctx->startup_callback) { - (*ctx->startup_callback)(ctx); - } -} - -static void -subproc_spawn_fork(subprocess_t *ctx) -{ - pid_t pid; - int stdin_pipe[2]; - int stdout_pipe[2]; - int stderr_pipe[2]; - - pipe(stdin_pipe); - pipe(stdout_pipe); - pipe(stderr_pipe); - - pid = fork(); - - if (pid != 0) { - close(stdin_pipe[0]); - close(stdout_pipe[1]); - close(stderr_pipe[1]); - - sb_init_party_fd(&ctx->sb, &ctx->subproc_stdin, stdin_pipe[1], - O_WRONLY, false, true, ctx->proxy_stdin_close); - sb_init_party_fd(&ctx->sb, &ctx->subproc_stdout, stdout_pipe[0], - O_RDONLY, false, true, false); - sb_init_party_fd(&ctx->sb, &ctx->subproc_stderr, stderr_pipe[0], - O_RDONLY, false, true, false); - - sb_monitor_pid(&ctx->sb, pid, &ctx->subproc_stdin, &ctx->subproc_stdout, - &ctx->subproc_stderr, true); - subproc_install_callbacks(ctx); - setup_subscriptions(ctx, false); - run_startup_callback(ctx); - } else { - close(stdin_pipe[1]); - close(stdout_pipe[0]); - close(stderr_pipe[0]); - dup2(stdin_pipe[0], 0); - dup2(stdout_pipe[1], 1); - dup2(stderr_pipe[1], 2); - - subproc_do_exec(ctx); - } -} - -void -termcap_set_raw_mode(struct termios *termcap) { - termcap->c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - //termcap->c_oflag &= ~OPOST; - termcap->c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - termcap->c_cflag &= ~(CSIZE | PARENB); - termcap->c_cc[VMIN] = 0; - termcap->c_cc[VTIME] = 0; - tcsetattr(1, TCSAFLUSH, termcap); -} - -static void -subproc_spawn_forkpty(subprocess_t *ctx) -{ - struct winsize wininfo; - struct termios *term_ptr = ctx->child_termcap; - struct winsize *win_ptr = &wininfo; - pid_t pid; - int pty_fd; - int stdin_pipe[2]; - - - tcgetattr(0, &ctx->saved_termcap); - - if (ctx->pty_stdin_pipe) { - pipe(stdin_pipe); - } - - // We're going to use a pipe for stderr to get a separate - // stream. The tty FD will be stdin and stdout for the child - // process. - // - // Also, if we want to close the subproc's stdin after an initial - // write, we will dup2. - // - // Note that this means the child process will see isatty() return - // true for stdin and stdout, but not stderr. - if(!isatty(0)) { - win_ptr = NULL; - } else { - ioctl(0, TIOCGWINSZ, win_ptr); - } - - pid = forkpty(&pty_fd, NULL, term_ptr, win_ptr); - - if (pid != 0) { - if (ctx->pty_stdin_pipe) { - close(stdin_pipe[0]); - sb_init_party_fd(&ctx->sb, &ctx->subproc_stdin, stdin_pipe[1], - O_WRONLY, false, true, ctx->proxy_stdin_close); - } - - ctx->pty_fd = pty_fd; - - sb_init_party_fd(&ctx->sb, &ctx->subproc_stdout, - pty_fd, O_RDWR, true, true, false); - - sb_monitor_pid(&ctx->sb, pid, &ctx->subproc_stdout, - &ctx->subproc_stdout, NULL, true); - subproc_install_callbacks(ctx); - setup_subscriptions(ctx, true); - - if (!ctx->parent_termcap) { - termcap_set_raw_mode(&ctx->saved_termcap); - } - else { - tcsetattr(1, TCSAFLUSH, ctx->parent_termcap); - } - int flags = fcntl(pty_fd, F_GETFL, 0) | O_NONBLOCK; - fcntl(pty_fd, F_SETFL, flags); - run_startup_callback(ctx); - - } else { - - setvbuf(stdout, NULL, _IONBF, (size_t) 0); - setvbuf(stdin, NULL, _IONBF, (size_t) 0); - - if (ctx->pty_stdin_pipe) { - close(stdin_pipe[1]); - dup2(stdin_pipe[0], 0); - } - - signal(SIGHUP, SIG_DFL); - signal(SIGINT, SIG_DFL); - signal(SIGILL, SIG_DFL); - signal(SIGABRT, SIG_DFL); - signal(SIGFPE, SIG_DFL); - signal(SIGKILL, SIG_DFL); - signal(SIGSEGV, SIG_DFL); - signal(SIGPIPE, SIG_DFL); - signal(SIGALRM, SIG_DFL); - signal(SIGTERM, SIG_DFL); - signal(SIGCHLD, SIG_DFL); - signal(SIGCONT, SIG_DFL); - signal(SIGSTOP, SIG_DFL); - signal(SIGTSTP, SIG_DFL); - signal(SIGTTIN, SIG_DFL); - signal(SIGTTOU, SIG_DFL); - signal(SIGWINCH, SIG_DFL); - subproc_do_exec(ctx); - } -} - -void -termcap_get(struct termios *termcap) { - tcgetattr(0, termcap); -} - -void -termcap_set(struct termios *termcap) { - tcsetattr(0, TCSANOW, termcap); -} - -/* - * Start a subprocess if you want to be responsible for making - * sufficient calls to poll for IP, instead of having it run to - * completion. - * - * If you use this, call subproc_poll() until it returns false - */ -void -subproc_start(subprocess_t *ctx) -{ - if (ctx->use_pty) { - subproc_spawn_forkpty(ctx); - } - else { - subproc_spawn_fork(ctx); - } -} - -/* - * Handle IO on the subprocess a single time. This is meant to be - * called only when manually runnng the subprocess; if you call - * subproc_run, don't use this interface! - */ -bool -subproc_poll(subprocess_t *ctx) -{ - return sb_operate_switchboard(&ctx->sb, false); -} - -/* - * Spawns a process, and runs it until the process has ended. The - * process must first be set up with `subproc_init()` and you may - * configure it with other `subproc_*()` calls before running. - * - * The results can be queried via the `subproc_get_*()` API. - */ -void -subproc_run(subprocess_t *ctx) -{ - subproc_start(ctx); - sb_operate_switchboard(&ctx->sb, true); -} - - -void -subproc_reset_terminal(subprocess_t *ctx) { - // Post-run cleanup. - if (ctx->use_pty) { - tcsetattr(0, TCSANOW, &ctx->saved_termcap); - } -} -/* - * This destroys any allocated memory inside a `subproc` object. You - * should *not* call this until you're done with the `sb_result_t` - * object, as any dynamic memory (like string captures) that you - * haven't taken ownership of will get freed when you call this. - * - * This call *will* destroy to sb_result_t object. - * - * However, this does *not* free the `subprocess_t` object itself. - */ -void -subproc_close(subprocess_t *ctx) -{ - subproc_reset_terminal(ctx); - sb_destroy(&ctx->sb, false); - - deferred_cb_t *cbs = ctx->deferred_cbs; - deferred_cb_t *next; - - while (cbs) { - next = cbs->next; - free(cbs->to_free); - free(cbs); - cbs = next; - } -} - -/* - * Return the PID of the current subprocess. Returns -1 if the - * subprocess hasn't been launched. - */ -pid_t -subproc_get_pid(subprocess_t *ctx) -{ - monitor_t *subproc = ctx->sb.pid_watch_list; - - if (!subproc) { - return -1; - } - return subproc->pid; -} - -/* - * If you've got captures under the given tag name, then this will - * return whatever was captured. If nothing was captured, it will - * return a NULL pointer. - * - * But if a capture is returned, it will have been allocated via - * `malloc()` and you will be responsible for calling `free()`. - */ -char * -sp_result_capture(sp_result_t *ctx, char *tag, size_t *outlen) -{ - for (int i = 0; i < ctx->num_captures; i++) { - if (!strcmp(tag, ctx->captures[i].tag)) { - *outlen = ctx->captures[i].len; - return ctx->captures[i].contents; - } - } - - *outlen = 0; - return NULL; -} - -char * -subproc_get_capture(subprocess_t *ctx, char *tag, size_t *outlen) -{ - sb_get_results(&ctx->sb, &ctx->result); - return sp_result_capture(&ctx->result, tag, outlen); -} - -int -subproc_get_exit(subprocess_t *ctx, bool wait_for_exit) -{ - monitor_t *subproc = ctx->sb.pid_watch_list; - - if (!subproc) { - return -1; - } - - process_status_check(subproc, wait_for_exit); - return subproc->exit_status; -} - -int -subproc_get_errno(subprocess_t *ctx, bool wait_for_exit) -{ - monitor_t *subproc = ctx->sb.pid_watch_list; - - if (!subproc) { - return -1; - } - - process_status_check(subproc, wait_for_exit); - return subproc->found_errno; -} - -int -subproc_get_signal(subprocess_t *ctx, bool wait_for_exit) -{ - monitor_t *subproc = ctx->sb.pid_watch_list; - - if (!subproc) { - return -1; - } - - process_status_check(subproc, wait_for_exit); - return subproc->term_signal; -} - -void -subproc_set_parent_termcap(subprocess_t *ctx, struct termios *tc) -{ - ctx->parent_termcap = tc; -} - -void -subproc_set_child_termcap(subprocess_t *ctx, struct termios *tc) -{ - ctx->child_termcap = tc; -} - -void -subproc_set_extra(subprocess_t *ctx, void *extra) -{ - sb_set_extra(&ctx->sb, extra); -} - -void * -subproc_get_extra(subprocess_t *ctx) -{ - return sb_get_extra(&ctx->sb); -} - - -#ifdef SB_TEST -void -capture_tty_data(switchboard_t *sb, party_t *party, char *data, size_t len) -{ - printf("Callback got %d bytes from fd %d\n", len, party_fd(party)); -} - -int -test1() { - char *cmd = "/bin/cat"; - char *args[] = { "/bin/cat", "../aes.nim", 0 }; - subprocess_t ctx; - sb_result_t *result; - struct timeval timeout = {.tv_sec = 0, .tv_usec = 1000 }; - - subproc_init(&ctx, cmd, args, true); - subproc_use_pty(&ctx); - subproc_set_passthrough(&ctx, SP_IO_ALL, false); - subproc_set_capture(&ctx, SP_IO_ALL, false); - subproc_set_timeout(&ctx, &timeout); - subproc_set_io_callback(&ctx, SP_IO_STDOUT, capture_tty_data); - - result = subproc_run(&ctx); - - while(result) { - if (result->tag) { - print_hex(result->contents, result->content_len, result->tag); - } - else { - printf("PID: %d\n", result->pid); - printf("Exit status: %d\n", result->exit_status); - } - result = result->next; - } - return 0; -} - -int -test2() { - char *cmd = "/bin/cat"; - char *args[] = { "/bin/cat", "-", 0 }; - - subprocess_t ctx; - sb_result_t *result; - struct timeval timeout = {.tv_sec = 0, .tv_usec = 1000 }; - - subproc_init(&ctx, cmd, args, true); - subproc_set_passthrough(&ctx, SP_IO_ALL, false); - subproc_set_capture(&ctx, SP_IO_ALL, false); - subproc_pass_to_stdin(&ctx, test_txt, strlen(test_txt), true); - subproc_set_timeout(&ctx, &timeout); - subproc_set_io_callback(&ctx, SP_IO_STDOUT, capture_tty_data); - - result = subproc_run(&ctx); - - while(result) { - if (result->tag) { - print_hex(result->contents, result->content_len, result->tag); - } - else { - printf("PID: %d\n", result->pid); - printf("Exit status: %d\n", result->exit_status); - } - result = result->next; - } - return 0; -} - -int -test3() { - char *cmd = "/usr/bin/less"; - char *args[] = { "/usr/bin/less", "../aes.nim", 0 }; - subprocess_t ctx; - sb_result_t *result; - struct timeval timeout = {.tv_sec = 0, .tv_usec = 1000 }; - - subproc_init(&ctx, cmd, args, true); - subproc_use_pty(&ctx); - subproc_set_passthrough(&ctx, SP_IO_ALL, false); - subproc_set_capture(&ctx, SP_IO_ALL, false); - subproc_set_timeout(&ctx, &timeout); - subproc_set_io_callback(&ctx, SP_IO_STDOUT, capture_tty_data); - - result = subproc_run(&ctx); - - while(result) { - if (result->tag) { - print_hex(result->contents, result->content_len, result->tag); - } - else { - printf("PID: %d\n", result->pid); - printf("Exit status: %d\n", result->exit_status); - } - result = result->next; - } - return 0; -} - -int -test4() { - char *cmd = "/bin/cat"; - char *args[] = { "/bin/cat", "-", 0 }; - - subprocess_t ctx; - sb_result_t *result; - struct timeval timeout = {.tv_sec = 0, .tv_usec = 1000 }; - - subproc_init(&ctx, cmd, args, true); - subproc_use_pty(&ctx); - subproc_set_passthrough(&ctx, SP_IO_ALL, false); - subproc_set_capture(&ctx, SP_IO_ALL, false); - subproc_set_timeout(&ctx, &timeout); - subproc_set_io_callback(&ctx, SP_IO_STDOUT, capture_tty_data); - - result = subproc_run(&ctx); - - while(result) { - if (result->tag) { - print_hex(result->contents, result->content_len, result->tag); - } - else { - printf("PID: %d\n", result->pid); - printf("Exit status: %d\n", result->exit_status); - } - result = result->next; - } - return 0; -} - - -int main() { - test1(); - test2(); - test3(); - test4(); -} -#endif diff --git a/nimutils/c/switchboard.c b/nimutils/c/switchboard.c deleted file mode 100644 index 15d360e..0000000 --- a/nimutils/c/switchboard.c +++ /dev/null @@ -1,1619 +0,0 @@ -/* - * Currently, we're using select() here, not epoll(), etc. - */ -#ifndef SWITCHBOARD_H__ -#include "switchboard.h" -#if defined(SB_DEBUG) || defined(SB_TEST) -#include -#include -#include "hex.h" -#endif -#endif - -/* The way we use the below two IO functions assumes that, while they - * may be interrupted, they won't be blocked. - * - * Therefore, we need to be sure not to: - * - * 1) use select() to make sure an op can be ready. - * 2) We limit ourselves to PIPE_BUF per write. - * - * In the switchboard code, we will always select on any open fds that - * have open subscribers. For writers, we will only select on their - * fds if there are messages waiting to be written. - * - * We do this by having a message queue attached to readers. So in the - * first possible select cycle, (assuming there's no string being - * routed into a file descriptor), we will only select() for read - * fds. - * - * There's no write delay though, as when we re-enter the select loop, - * we'd expect the fds for write to all be ready for data, so that - * select() call will return immediately. - */ -ssize_t -read_one(int fd, char *buf, size_t nbytes) -{ - ssize_t n; - - while (true) { - n = read(fd, buf, nbytes); - - if (n == -1) { - if (errno == EINTR || errno == EAGAIN) { - continue; - } - } - - return n; - } -} - -bool -write_data(int fd, char *buf, size_t nbytes) -{ - size_t towrite, written = 0; - ssize_t result; - - do { - if (nbytes - written > SSIZE_MAX) { - towrite = SSIZE_MAX; - } else { - towrite = nbytes - written; - } - if ((result = write(fd, buf + written, towrite)) >= 0) { - written += result; - } else if (errno != EINTR && errno != EAGAIN) { - return false; - } - } while (written < nbytes); - - return true; -} - -static inline void -register_fd(switchboard_t *ctx, int fd) -{ - if (ctx->max_fd <= fd) { - ctx->max_fd = fd + 1; - } -} - -int -party_fd(party_t *party) -{ - if (party->party_type == PT_FD) { - return party->info.fdinfo.fd; - } - else { - return party->info.listenerinfo.fd; - } -} - -static inline str_src_party_t * -get_sstr_obj(party_t *party) -{ - return &party->info.rstrinfo; -} - -static inline str_dst_party_t * -get_dstr_obj(party_t *party) -{ - return &party->info.wstrinfo; -} - -static inline fd_party_t * -get_fd_obj(party_t *party) -{ - return &party->info.fdinfo; -} - -static inline listener_party_t * -get_listener_obj(party_t *party) -{ - return &party->info.listenerinfo; -} - -/* Here we link together readers so we can walk through them to build - * the read FD set, and to do memory management. - * - * Non-FD data structures are put on the 'loners' list. - */ -static inline void -register_read_fd(switchboard_t *ctx, party_t *read_from) -{ - register_fd(ctx, party_fd(read_from)); - - read_from->next_reader = ctx->parties_for_reading; - ctx->parties_for_reading = read_from; -} - -static inline void -register_writer_fd(switchboard_t *ctx, party_t *write_to) -{ - register_fd(ctx, party_fd(write_to)); - write_to->next_writer = ctx->parties_for_writing; - ctx->parties_for_writing = write_to; -} - -/* 'Loner' is a horrible name for this; it's taking the party metaphor - * too far. This is just a list of party_t objects that will not - * appear on either the reader linked list or the writer linked list; - * generally we'll want this for cleanup after the fact. - */ -static inline void -register_loner(switchboard_t *ctx, party_t *loner) -{ - loner->next_loner = ctx->party_loners; - ctx->party_loners = loner; -} - -/* - * Here, we're handed a party_t and told it's a listener. You must - * pass in a sockfd that is already open, and has already had listen() - * called on it. - * - * When there's something ready to accept(), we'll get triggered that - * a read is available. We then accept() for you, and pass that to a - * callback. - * - * You can then register the fd connection in the same switchboard if - * you like. It's not done for you at this level, though. - * - * If `close_on_destroy` is true, we will call close() on the fd for - * you whenever the switchboard is torn down. - */ -void -sb_init_party_listener(switchboard_t *ctx, party_t *party, int sockfd, - accept_cb_t callback, bool stop_when_closed, - bool close_on_destroy) -{ - /* Stop on close should really only be applied to stdin/out/err, - * and socket FDs. For subprocesses, add the flag when registering - * them. - */ - party->party_type = PT_LISTENER; - party->open_for_read = true; - party->close_on_destroy = close_on_destroy; - ctx->parties_for_reading = party; - party->can_read_from_it = true; - party->can_write_to_it = false; - - listener_party_t *lobj = get_listener_obj(party); - lobj->fd = sockfd; - lobj->accept_cb = (accept_cb_decl)callback; - lobj->saved_flags = fcntl(sockfd, F_GETFL, 0); - - register_read_fd(ctx, party); - - if (stop_when_closed) { - party->stop_on_close = true; - } -} - -// allocate and call sb_init_party_listener. -party_t * -sb_new_party_listener(switchboard_t *ctx, int sockfd, accept_cb_t callback, - bool stop_when_closed, bool close_on_destroy) -{ - party_t *result = (party_t *)calloc(sizeof(party_t), 1); - sb_init_party_listener(ctx, result, sockfd, callback, stop_when_closed, - close_on_destroy); - - return result; -} - -/* - * Set up a party object for a non-listener file descriptor. The file - * descriptor does NOT have to be non-blocking; it does not matter - * whether or not it is, actually. - * - * The `perms` field should be O_RDONLY, O_WRONLY or O_RDWR. - * - * The `stop_when_closed` field closes down the switchboard when I/O - * to this fd fails. - * - * If `close_on_destroy` is true, we will call close() on the fd for - * you whenever the switchboard is torn down. - */ -void -sb_init_party_fd(switchboard_t *ctx, party_t *party, int fd, int perms, - bool stop_when_closed, bool close_on_destroy, - bool proxy_close) -{ - memset(party, 0, sizeof(party_t)); - - party->party_type = PT_FD; - party->close_on_destroy = close_on_destroy; - party->can_read_from_it = false; - party->can_write_to_it = false; - - fd_party_t *fd_obj = get_fd_obj(party); - fd_obj->first_msg = NULL; - fd_obj->last_msg = NULL; - fd_obj->subscribers = NULL; - fd_obj->proxy_close = proxy_close; - fd_obj->fd = fd; - - if (perms != O_WRONLY) { - party->open_for_read = true; - party->can_read_from_it = true; - register_read_fd(ctx, party); - } - if (perms != O_RDONLY) { - party->open_for_write = true; - party->can_write_to_it = true; - register_writer_fd(ctx, party); - } - if (stop_when_closed) { - party->stop_on_close = true; - } -} - -party_t * -sb_new_party_fd(switchboard_t *ctx, int fd, int perms, - bool stop_when_closed, bool close_on_destroy, - bool proxy_close) -{ - party_t *result = (party_t *)calloc(sizeof(party_t), 1); - sb_init_party_fd(ctx, result, fd, perms, stop_when_closed, - close_on_destroy, proxy_close); - - return result; -} - -/* - * This is for sending strings to a fd. You can send the same input - * buffer to multiple processes, or reuse an object to rechange the - * string; the string gets processed at the time you call `route()` - * with one of these as the `read_from` parameter. - */ -void -sb_init_party_input_buf(switchboard_t *ctx, party_t *party, char *input, - size_t len, bool dup, bool free, bool close_fd_when_done) -{ - char *to_set = input; - - if (dup) { - free = true; - to_set = (char *)calloc(len + 1, 1); - memcpy(to_set, input, len); - } - party->open_for_read = true; - party->open_for_write = false; - party->can_read_from_it = true; - party->can_write_to_it = false; - party->party_type = PT_STRING; - str_src_party_t *sobj = get_sstr_obj(party); - sobj->strbuf = to_set; - sobj->len = len; - sobj->free_on_close = free; - sobj->close_fd_when_done = close_fd_when_done; - - register_loner(ctx, party); -} - -party_t * -sb_new_party_input_buf(switchboard_t *ctx, char *input, size_t len, - bool dup, bool free, bool close_fd_when_done) -{ - party_t *result = (party_t *)calloc(sizeof(party_t), 1); - sb_init_party_input_buf(ctx, result, input, len, dup, free, - close_fd_when_done); - - return result; -} - -void -sb_party_input_buf_new_string(party_t *party, char *input, size_t len, - bool dup, bool close_fd_when_done) -{ - if (party->party_type != PT_STRING || !party->can_read_from_it) { - return; - } - str_src_party_t *sobj = get_sstr_obj(party); - if (sobj->free_on_close && sobj->strbuf) { - free(sobj->strbuf); - } - sobj->len = len; - sobj->close_fd_when_done = close_fd_when_done; - - if (dup) { - sobj->strbuf = (char *)calloc(len + 1, 1); - memcpy(sobj->strbuf, input, len); - sobj->free_on_close = true; - } else { - sobj->strbuf = input; - } -} - -/* - * When you want to capture process output, but don't need it until - * the process is closes, this is your huckleberry. You can use one of - * these per fd you want to capture, or you can combine them. - * - * So if you want to combine stdin / stdout into one stream, you can - * do it. - * - * The `tag` field is used to distinguish multiple output buffers when - * the switchboard is closed down. - */ -void -sb_init_party_output_buf(switchboard_t *ctx, party_t *party, char *tag, - size_t buflen) -{ - if (buflen < PIPE_BUF) { - buflen = PIPE_BUF; - } - - size_t n = buflen / PIPE_BUF; - - if (buflen % PIPE_BUF) { - n++; - } - - party->can_read_from_it = false; - party->can_write_to_it = true; - party->open_for_read = false; - party->open_for_write = true; - party->party_type = PT_STRING; - - str_dst_party_t *dobj = get_dstr_obj(party); - dobj->strbuf = (char *)calloc(PIPE_BUF, n); - dobj->len = n * PIPE_BUF; - dobj->step = party->info.wstrinfo.len; - dobj->tag = tag; - dobj->ix = 0; - - register_loner(ctx, party); - } - -party_t * -sb_new_party_output_buf(switchboard_t *ctx, char *tag, size_t buflen) -{ - party_t *result = (party_t *)calloc(sizeof(party_t), 1); - sb_init_party_output_buf(ctx, result, tag, buflen); - - return result; -} - -/* - * This sets up a callback that can receive incremental data read from - * a file descriptor (NOT a listener though). - * - * Any state information you can carry via the `extra` state in - * either switchboard_t or party_t. - * - * see `sb_get_extra()`, `sb_set_extra()`, - * `sb_get_party_extra()` and `sb_set_party_extra()`. - */ -void -sb_init_party_callback(switchboard_t *ctx, party_t *party, switchboard_cb_t cb) -{ - party->open_for_read = false; - party->open_for_write = true; - party->can_read_from_it = false; - party->can_write_to_it = true; - party->party_type = PT_CALLBACK; - party->info.cbinfo.callback = (switchboard_cb_t)cb; - - register_loner(ctx, party); -} - -/* - * This is used to register a process and associate it with its read/write - * file descriptors (via party objects). - */ -void -sb_monitor_pid(switchboard_t *ctx, pid_t pid, party_t *stdin_fd_party, - party_t *stdout_fd_party, party_t *stderr_fd_party, - bool shutdown) -{ - monitor_t *monitor = (monitor_t *)calloc(sizeof(monitor_t), 1); - - monitor->pid = pid; - monitor->stdin_fd_party = stdin_fd_party; - monitor->stdout_fd_party = stdout_fd_party; - monitor->stderr_fd_party = stderr_fd_party; - monitor->next = ctx->pid_watch_list; - monitor->shutdown_when_closed = shutdown; - ctx->pid_watch_list = monitor; -} - -/* - * Retrieve any user-defined pointer for a switchboard object. - */ -void * -sb_get_extra(switchboard_t *ctx) -{ - return ctx->extra; -} - -/* - * Set the user-defined pointer for a switchboard object. - */ -void -sb_set_extra(switchboard_t *ctx, void *ptr) -{ - ctx->extra = ptr; -} - -/* - * Retrieve any user-defined pointer for a party object. - */ -void * -sb_get_party_extra(party_t *party) -{ - return party->extra; -} - -/* - * Set the user-defined pointer for a party object. - */ -void -sb_set_party_extra(party_t *party, void *ptr) -{ - party->extra = ptr; -} - - -static inline void -add_heap(switchboard_t *ctx) -{ - sb_heap_t *old = ctx->heap; - int elem_space = ctx->heap_elems * sizeof(sb_msg_t); - - ctx->heap = calloc(elem_space + sizeof(sb_heap_t), 1); - ctx->heap->next = old; - ctx->heap->cur_cell = 0; -} - -static inline sb_msg_t * -get_msg_slot(switchboard_t *ctx) -{ - sb_msg_t *result; - sb_heap_t *heap; - - if (!ctx->heap || (ctx->heap->cur_cell >= ctx->heap_elems)) { - add_heap(ctx); - } - - if (ctx->freelist != NULL) { - result = ctx->freelist; - ctx->freelist = result->next; - - #ifdef SB_DEBUG - printf("get_slot: freelist (%p). New freelist: %p\n", - result, ctx->freelist); - #endif - return result; - } - - heap = ctx->heap; - result = &(heap->cells[heap->cur_cell++]); - - memset(result, 0, sizeof(sb_msg_t)); - return result; -} - -/* Doesn't mean we call free(), just that we can hand it out again - * when get_msg_slot() is called. - * - * Being good citizens, we zero out the len and data fields. - */ -static inline void -free_msg_slot(switchboard_t *ctx, sb_msg_t *slot) -{ - - slot->next = ctx->freelist; - slot->len = 0; - ctx->freelist = slot; - - memset(slot->data, 0, SB_MSG_LEN); -} - -/* - * Internal function to enqueue any messages of size up to PIPE_BUF to - * writable file descriptors. - */ -static inline void -publish(switchboard_t *ctx, char *buf, ssize_t len, party_t *party) -{ - if (!party->open_for_write) { - return; - } - - fd_party_t *receiver = get_fd_obj(party); - sb_msg_t *msg = get_msg_slot(ctx); - - if (len) { - memcpy(msg->data, buf, len); - msg->len = len; - } else { - msg->len = 0; - } - - msg->next = NULL; - - if (receiver->first_msg == NULL) { - receiver->first_msg = msg; - } else { - receiver->last_msg->next = msg; - } - - receiver->last_msg = msg; - - #ifdef SB_DEBUG - printf(">>Enqueued message for fd %d", party_fd(party)); - print_hex(receiver->last_msg->data, receiver->last_msg->len, ":"); - #endif -} - -/* - * Route a party that we read from, to a party that we write to. - * If the mix is invalid, then this returns 'false'. - * - * Listeners cannot be routed; you supply a callback when you - * register them. - * - * Strings can be routed only to FD writers; the strings are totally - * enqueued at the time of the route() call, by calling publish() - * on chunks up to PIPE_BUF in size. - * - * FDs for read can be routed to FD writers, strings for output, - * or to callbacks. - * - * You can use the same FDs in multiple routings, no problem. That - * means you can send output from a single read fd to as many places - * as you like, and you may similarly send multiple input sources into - * a single output file descriptor. - */ -bool -sb_route(switchboard_t *ctx, party_t *read_from, party_t *write_to) -{ - if (read_from == NULL || write_to == NULL) { - return false; - } - - if (!read_from->open_for_read || !write_to->open_for_write) { - return false; - } - - if (read_from->party_type & (PT_LISTENER | PT_CALLBACK)) { - return false; - } - - if (write_to->party_type & PT_LISTENER) { - return false; - } - - if (read_from->party_type == PT_STRING) { - if (write_to->party_type != PT_FD) { - return false; - } - str_src_party_t *s = get_sstr_obj(read_from); - size_t remaining = s->len; - char *p = s->strbuf; - char *end = p + remaining; - int total = 0; - - while (p < (end - SB_MSG_LEN)) { - publish(ctx, p, SB_MSG_LEN, write_to); - p += SB_MSG_LEN; - total += SB_MSG_LEN; - } - if (p != end) { - publish(ctx, p, end - p, write_to); - total += end - p; - } - - if (s->close_fd_when_done) { - publish(ctx, NULL, 0, write_to); - } - - return true; - } - else { - // Will be PT_FD. - fd_party_t *r_fd_obj = get_fd_obj(read_from); - - subscription_t *subscription; - subscription = (subscription_t *)calloc(sizeof(subscription_t), 1); - - if (write_to->party_type == PT_FD) { - #if defined(SB_DEBUG) || defined(SB_TEST) - printf("sub(src_fd=%d, dst_fd=%d)\n", - party_fd(read_from), party_fd(write_to)); - #endif - } - else if (write_to->party_type == PT_CALLBACK) { - #if defined(SB_DEBUG) || defined(SB_TEST) - printf("sub(src_fd=%d, dst = callback)\n", - party_fd(read_from)); - #endif - } - else { - str_dst_party_t *dob = get_dstr_obj(write_to); - if (!dob->tag) { - return false; - } - #if defined(SB_DEBUG) || defined(SB_TEST) - printf("sub(src=%d, tag=%s)\n", party_fd(read_from), dob->tag); - #endif - } - subscription->subscriber = write_to; - subscription->next = r_fd_obj->subscribers; - r_fd_obj->subscribers = subscription; - } - - return true; -} - -/* - * Pause the specified routing (unsubscribe), if one is active. - * Returns `true` if the subscription was marked as paused. - * - * This does not consider whether the fds are closed. - * - * Currently, there's no explicit facility for removing subscriptions. - * Just pause it and never unpause it! - */ -bool -sb_pause_route(switchboard_t *ctx, party_t *read_from, party_t *write_to) -{ - if (read_from == NULL || write_to == NULL) { - return false; - } - - fd_party_t *reader = get_fd_obj(read_from); - subscription_t *cur = reader->subscribers; - - while (cur != NULL) { - if (cur->subscriber != write_to) { - cur = cur->next; - continue; - } - if (cur->paused) { - return false; - } else { - cur->paused = true; - return true; - } - } - return false; -} - -/* - * Resumes the specified subscription. Returns `true` if resumption - * was successful, and `false` if not, including cases where the - * subscription was already active.. - */ -bool -sb_resume_route(switchboard_t *ctx, party_t *read_from, party_t *write_to) -{ - if (read_from == NULL || write_to == NULL) { - return false; - } - - fd_party_t *reader = get_fd_obj(read_from); - subscription_t *cur = reader->subscribers; - - while (cur != NULL) { - if (cur->subscriber != write_to) { - cur = cur->next; - continue; - } - if (cur->paused) { - cur->paused = true; - return true; - } else { - return false; - } - } - return false; -} - -/* - * Returns true if the subscription is active, and in an unpaused state. - */ -bool -sb_route_is_active(switchboard_t *ctx, party_t *read_from, party_t *write_to) -{ - if (read_from == NULL || write_to == NULL) { - return false; - } - - if (!read_from->open_for_read || !write_to->open_for_write) { - return false; - } - - fd_party_t *reader = get_fd_obj(read_from); - subscription_t *cur = reader->subscribers; - - while (cur != NULL) { - if (cur->subscriber != write_to) { - cur = cur->next; - continue; - } - return !cur->paused; - } - return false; -} - -/* - * Returns true if the subscription is active, but paused. - */ -bool -sb_route_is_paused(switchboard_t *ctx, party_t *read_from, party_t *write_to) -{ - if (read_from == NULL || write_to == NULL) { - return false; - } - - if (!read_from->open_for_read || !write_to->open_for_write) { - return false; - } - - fd_party_t *reader = get_fd_obj(read_from); - subscription_t *cur = reader->subscribers; - - while (cur != NULL) { - if (cur->subscriber != write_to) { - cur = cur->next; - continue; - } - return cur->paused; - } - return false; -} - -/* - * Returns true if the subscription is active, whether or not it is - * paused. - */ -bool -sb_is_subscribed(switchboard_t *ctx, party_t *read_from, party_t *write_to) -{ - if (read_from == NULL || write_to == NULL) { - return false; - } - - if (!read_from->open_for_read || !write_to->open_for_write) { - return false; - } - - fd_party_t *reader = get_fd_obj(read_from); - subscription_t *cur = reader->subscribers; - - while (cur != NULL) { - if (cur->subscriber != write_to) { - cur = cur->next; - continue; - } - return true; - } - return false; -} - - -/* - * Initializes a switchboard object, primarily zeroing out the - * contents, and setting up message buffering. - */ -void -sb_init(switchboard_t *ctx, size_t heap_size) -{ - memset(ctx, 0, sizeof(switchboard_t)); - ctx->heap_elems = heap_size; - add_heap(ctx); -} - -/* - * This internal function walks a switchboard's parties_for_reading - * and parties_for_writing fields to figure out what to add to the - * fd_sets that we pass to select(). - * - * The read fds are added as long as there are subscribers that are - * attached that isn't listed as closed. - * - * Write fds are added if there's explicitly something in their - * message queue; first_msg will be non-NULL. - * - * We also track to make sure that *some* party is open for either - * reading that has a valid subscriber, OR at least one party open for - * writing that has enqueued messages. - * - * If neither of these conditions are met, we shut down. - * down. - */ -static inline void -set_fdinfo(switchboard_t *ctx) -{ - party_t *cur; - bool open = false; // If this stays false, we give up. - - FD_ZERO(&ctx->readset); - FD_ZERO(&ctx->writeset); - - cur = ctx->parties_for_reading; - - while (cur != NULL) { - fd_party_t *r_fd_obj = get_fd_obj(cur); - subscription_t *subscribers = r_fd_obj->subscribers; - - if (cur->open_for_read) { - while (subscribers != NULL) { - if (cur->party_type == PT_FD) { - party_t *onesub = subscribers->subscriber; - - if (onesub && onesub->open_for_write) { - FD_SET(party_fd(cur), &ctx->readset); - open = true; - break; - - } - } else { - open = true; - FD_SET(party_fd(cur), &ctx->readset); - break; - } - subscribers = subscribers->next; - } - } - cur = cur->next_reader; - } - - cur = ctx->parties_for_writing; - while (cur != NULL) { - if (cur->party_type == PT_FD && cur->open_for_write && - cur->info.fdinfo.first_msg != NULL) { - open = true; - FD_SET(party_fd(cur), &ctx->writeset); - } - cur = cur->next_writer; - } - - // If nothing w/ a file descriptor is left standing, then we will - // finish up. - if (!open) { - ctx->done = true; - } -} - -/* - * Setting this timeout sets how long we will wait before timing out - * on a single select() call. Without setting it, select() will wait - * indefinitely long. - * - * When there's a timeout, if there's a progress_callback, we call it. - * But if there's no progress callback, the switchboard will exit. - */ -void -sb_set_io_timeout(switchboard_t *ctx, struct timeval *timeout) -{ - if (timeout == NULL) { - ctx->io_timeout_ptr = NULL; - } - else { - ctx->io_timeout_ptr = &ctx->io_timeout; - memcpy(ctx->io_timeout_ptr, timeout, sizeof(struct timeval)); - } -} - -void -sb_clear_io_timeout(switchboard_t *ctx) -{ - ctx->io_timeout_ptr = NULL; -} - -// After select(), test an FD to see if it's ready for read. -static inline bool -reader_ready(switchboard_t *ctx, party_t *party) -{ - if (party->open_for_read && FD_ISSET(party_fd(party), &ctx->readset) != 0) { - FD_CLR(party_fd(party), &ctx->writeset); - return true; - } - return false; -} - -// After select(), test an FD to see if it's ready for write. -static inline bool -writer_ready(switchboard_t *ctx, party_t *party) -{ - if (party->open_for_write && - FD_ISSET(party_fd(party), &ctx->writeset) != 0) { - return true; - } - return false; -} - -/* - * If a fd is reading data, and one of the places we want to send it - * is to a string that is returned once at the end, we don't bother to - * enqueue, we directly add to the sink at the time the source - * produces the data, using this function. - */ -static inline void -add_data_to_string_out(str_dst_party_t *party, char *buf, ssize_t len) { - - #ifdef SB_DEBUG - printf("tag = %s, buf = %s, len = %d\n", party->tag, buf, len); - print_hex(buf, len, ">> add_data_to_string_out: "); - #endif - - if (party->ix + len >= party->len) { - int newlen = party->len + len + party->step; - int rem = newlen % party->step; - - newlen -= rem; - party->strbuf = realloc(party->strbuf, newlen); - - if (party->strbuf == 0) { - #ifdef SB_DEBUG - printf("REALLOC FAILED. Skipping capture.\n"); - #endif - return; - } - - party->len = newlen; - memset(party->strbuf + party->ix, 0, newlen - party->ix); - } - - memcpy(&party->strbuf[party->ix], buf, len); - party->ix += len; -} - -/* - * This handles reading from fd sources. String sources never call - * this, as they get processed in full when sinks subscribe to them. - * - * Listeners are handled by a different function (handle_one_accept - * below). - * - * When reading one chunk, we then loop through our subscribers, and - * take action based on the subscriber type. - * - * If the subscriber is a writable fd, we call publish(), which will - * enqueue for that fd, as long as it's open (it's ignored otherwise). - * - * If the subscriber is a string or callback, these things can never - * be closed, and we process the data right away. - * - * Note that this is only called in one of two cases: - * - * 1) select() told us there's something to read, in which case, - * read_one() should never return a zero-length string (errors - * would result in a -1) - * - * 2) We know a subprocess is done and we're draining the read side of - * that file descriptor. In that case, we know when we're - * returned a 0-length value, it's time to mark the read side - * as done too. - */ -static inline void -handle_one_read(switchboard_t *ctx, party_t *party) -{ - char buf[SB_MSG_LEN + 1] = {0, }; - ssize_t read_result = read_one(party_fd(party), buf, SB_MSG_LEN); - - if (read_result <= 0) { - if (read_result < 0) { - party->found_errno = errno; - } - party->open_for_read = false; - #ifdef SB_DEBUG - printf("Shut down reading on fd %d in h1r top\n", party_fd(party)); - #endif - if (party->stop_on_close) { - ctx->done = true; - } - - /* - * When there is no input read, we need to propagate close - * to all the subscribers. - * This is driven by proxy_close subscriber attribute. - * As this happens via event-loop we can do that by passing - * empty write message to the subscriber - * (see comment in handle_one_write) - */ - if (party->party_type == PT_FD) { - fd_party_t *obj = get_fd_obj(party); - subscription_t *sublist = obj->subscribers; - while (sublist != NULL) { - party_t *sub = sublist->subscriber; - if (sub->party_type == PT_FD) { - fd_party_t *sub_fd = get_fd_obj(sub); - if (sub_fd->proxy_close) { - publish(ctx, NULL, 0, sub); - } - } - sublist = sublist->next; - } - } - } else { - #ifdef SB_DEBUG - printf(">>One read from fd %d", party_fd(party)); - print_hex(buf, read_result, ": "); - #endif - - fd_party_t *obj = get_fd_obj(party); - subscription_t *sublist = obj->subscribers; - - while (sublist != NULL) { - party_t *sub = sublist->subscriber; - - if (!sublist->paused) { - switch(sub->party_type) { - case PT_FD: - publish(ctx, buf, read_result, sub); - break; - case PT_STRING: - add_data_to_string_out(get_dstr_obj(sub), buf, read_result); - break; - case PT_CALLBACK: - (*sub->info.cbinfo.callback)(ctx->extra, sub->extra, buf, - (size_t)read_result); - break; - default: - break; - } - } - sublist = sublist->next; - } - } -} - -/* - * This just accepts a socket and calls the provided callback to - * decide what to do with it. - */ -static inline void -handle_one_accept(switchboard_t *ctx, party_t *party) -{ - int listener_fd = party_fd(party); - listener_party_t *listener_obj = get_listener_obj(party); - struct sockaddr address; - socklen_t address_len; - - while (true) { - int sockfd = accept(listener_fd, &address, &address_len); - - if (sockfd >= 0) { - (*listener_obj->accept_cb)(ctx, sockfd, &address, &address_len); - break; - } - if (errno == EINTR || errno == EAGAIN) { - continue; - } - if (errno == ECONNABORTED) { - break; - } - party->found_errno = errno; - party->open_for_read = false; - break; - } -} - -/* - * This function handles writing to a writable file descriptor, where - * select() has told us it's ready to receive a write. We simply - * attempt to write one queued message to it, then remove that message - * from the queue. - * - * The only exception is if the message is a null length, which is an - * instruction to actually call close() on the fd. - */ -static inline void -handle_one_write(switchboard_t *ctx, party_t *party) -{ - fd_party_t *fdobj = get_fd_obj(party); - sb_msg_t *msg = fdobj->first_msg; - - if (!msg) { - return; - } - - if (msg && (msg->next == NULL || msg == fdobj->last_msg)) { - fdobj->first_msg = NULL; - fdobj->last_msg = NULL; - } else { - fdobj->first_msg = msg->next; - } - - if (!msg->len) { - /* Real messages should always have lengths. We get passed a - * zero-length message only if a string was fed in for input, - * with instructions for us to close after it's consumed. - * - * The close instruction is communicated by sending a null - * message. So when we see it, we mark ourselves as closed. - */ - #ifdef SB_DEBUG - printf("0-length write; shutting down write-side of fd.\n"); - #endif - party->open_for_write = false; - if (!party->open_for_read) { - close(party_fd(party)); - } - free_msg_slot(ctx, msg); - return; - } - - #ifdef SB_DEBUG - printf("Writing from queue to fd %d", party_fd(party)); - print_hex(msg->data, msg->len, ": "); - #endif - - if (!write_data(party_fd(party), msg->data, msg->len)) { - party->found_errno = errno; - - party->open_for_write = false; - if (!party->open_for_read) { - close(party_fd(party)); - } - - if (party->stop_on_close) { - ctx->done = true; - } - /* If the write failed, we'll never try to write again. - * The message slot we tried to write gets freed below - * no matter what, but let's free any other queued slots - * here. - */ - sb_msg_t *to_free = fdobj->first_msg; - - while (to_free) { - sb_msg_t *next = to_free->next; - - free_msg_slot(ctx, to_free); - to_free = next; - } - fdobj->first_msg = NULL; - fdobj->last_msg = NULL; - return; - } - - free_msg_slot(ctx, msg); -} - -// Not much here, just identify fds ready for read and dispatch. -static inline void -handle_ready_reads(switchboard_t *ctx) -{ - party_t *reader = ctx->parties_for_reading; - - while(reader != NULL) { - if (reader_ready(ctx, reader)) { - FD_CLR(party_fd(reader), &ctx->readset); - - if (reader->party_type == PT_FD) { - handle_one_read(ctx, reader); - } else { - handle_one_accept(ctx, reader); - } - } - reader = reader->next_reader; - } -} - -// Dispatch for any fds ready for writing. -static inline void -handle_ready_writes(switchboard_t *ctx) -{ - party_t *writer = ctx->parties_for_writing; - - while (writer != NULL) { - if (writer_ready(ctx, writer)) { - FD_CLR(party_fd(writer), &ctx->readset); - - handle_one_write(ctx, writer); - } - writer = writer->next_writer; - } -} - -// If a subprocess shut down, clean up. -static inline void -subproc_mark_closed(monitor_t *proc, bool error) -{ - proc->closed = true; - - if (error) { - proc->found_errno = errno; - } - - // We can mark the write side of this as closed right away. But we - // can't do the same w/ the read side; we need to drain them - // first. - - if (proc->stdin_fd_party) { - proc->stdin_fd_party->open_for_write = false; - } -} - -static bool -sb_default_check_exit_conditions(switchboard_t *ctx) -{ - monitor_t *subproc = ctx->pid_watch_list; - bool close_if_drained = false; - - while (subproc) { - if (subproc->closed && subproc->shutdown_when_closed) { - close_if_drained = true; - break; - } - subproc = subproc->next; - } - - if (!close_if_drained) { - return false; - } - - if (subproc->stdout_fd_party && subproc->stdout_fd_party->open_for_read) { - return false; - } else { - #if defined(SB_DEBUG) || defined(SB_TEST) - printf("Subproc stdout is closed for business.\n"); - #endif - } - - if (subproc->stderr_fd_party && subproc->stderr_fd_party->open_for_read) { - #if defined(SB_DEBUG) || defined(SB_TEST) - printf("Read side of stderr is still open.\n"); - #endif - return false; - } - - party_t *writers = ctx->parties_for_writing; - - while (writers) { - - if (writers->party_type == PT_FD) { - fd_party_t *fd_writer = get_fd_obj(writers); - - #if defined(SB_DEBUG) || defined(SB_TEST) - printf("checking for pending writes to fd %d\n", fd_writer->fd); - #endif - if (writers->open_for_write && fd_writer->first_msg) { - #if defined(SB_DEBUG) || defined(SB_TEST) - printf("Don't exit yet.\n"); - #endif - return false; - } - #if defined(SB_DEBUG) || defined(SB_TEST) - if (writers->open_for_write) { - printf("No queue.\n"); - } - else { - printf("Already closed.\n"); - } - #endif - } - writers = writers->next_writer; - } - - return true; -} - -void -process_status_check(monitor_t *subproc, bool wait_on_exit) -{ - int stat_info; - int flag; - - - if (wait_on_exit) { - flag = 0; - } else { - flag = WNOHANG; - } - - if (subproc->closed) { - return; - } - - while (true) { - switch (waitpid(subproc->pid, &stat_info, flag)) { - case 0: - return; // Process is sill running. - case -1: - if (errno == EINTR) { - continue; - } - subproc->closed = true; - subproc->found_errno = errno; - return; - default: - subproc->closed = true; - subproc->exit_status = WEXITSTATUS(stat_info); - - if (WIFSIGNALED(stat_info)) { - subproc->term_signal = WTERMSIG(stat_info); - } - return; - } - } -} - -/* - * Every time we finish a select, check to see if we need to invoke - * the progress callback, and if we should exit. - * - * This involves testing exit conditions-- first, if the progress - * callback returns `true`, that indicates we should exit. Second, if - * no callback was set for progress monitoring, and the select() - * timeout fired, then we are done. - * - * Third, we look at each subprocess to see if it's fully exited (all - * its read fds are closed). If it has, and if we're supposed to shut - * down when it does, subproc_mark_closed() above will set the 'done' - * flag for us. - */ -static inline void -handle_loop_end(switchboard_t *ctx) -{ - monitor_t *subproc = ctx->pid_watch_list; - - while (subproc != NULL) { - process_status_check(subproc, false); - subproc = subproc->next; - } - - if (!ctx->progress_callback) { - ctx->progress_callback = - (progress_cb_decl)sb_default_check_exit_conditions; - } - - if (!ctx->progress_on_timeout_only) { - if ((*ctx->progress_callback)(ctx)) { - ctx->done = true; - } - } -} - -/* - * Used only to make sure we don't free registered readers when - * they're also registered writers; we wait until we process the - * registered writer list. - */ -static bool -is_registered_writer(switchboard_t *ctx, party_t *target) -{ - party_t *cur = ctx->parties_for_writing; - - while (cur) { - if (target == cur) { - return true; - } - cur = cur->next_writer; - } - - return false; -} - -/* - * Dealloc any memory we're responsible for. - * - * Note that this does NOT free the switchboard object, - * just any internal data structures. - */ -void -sb_destroy(switchboard_t *ctx, bool free_parties) -{ - while (ctx->heap) { - sb_heap_t *to_free = ctx->heap; - ctx->heap = ctx->heap->next; - free(to_free); - } - - while (ctx->pid_watch_list) { - monitor_t *to_free = ctx->pid_watch_list; - ctx->pid_watch_list = ctx->pid_watch_list->next; - free(to_free); - } - - party_t *cur, *next; - - cur = ctx->parties_for_reading; - - while (cur) { - if (cur->close_on_destroy) { - if (cur->party_type & (PT_FD | PT_LISTENER) ) { - close(party_fd(cur)); - } - } - - fd_party_t *fdobj = get_fd_obj(cur); - subscription_t *sub = fdobj->subscribers; - - while (sub) { - subscription_t *next_sub = sub->next; - free(sub); - sub = next_sub; - } - - if (cur->party_type == PT_STRING) { - str_src_party_t *sstr = get_sstr_obj(cur); - if (sstr->strbuf != NULL) { - free(sstr->strbuf); - } - } - next = cur->next_reader; - - if (free_parties) { - if (!cur->can_write_to_it || !is_registered_writer(ctx, cur)) { - free(cur); - } - } - cur = next; - } - - cur = ctx->parties_for_writing; - while (cur) { - if (cur->close_on_destroy && cur->party_type == PT_FD) { - close(party_fd(cur)); - } - next = cur->next_writer; - if (free_parties) { - free(cur); - } - cur = next; - } - - if (free_parties) { - cur = ctx->party_loners; - - while (cur) { - next = cur->next_loner; - free(cur); - cur = next; - } - } -} - -/* - * Extract results from the switchbaord. - */ -void -sb_get_results(switchboard_t *ctx, sb_result_t *result) -{ - str_dst_party_t *strobj; - party_t *party = ctx->party_loners; // Look for str outputs. - int capcount = 0; - int ix = 0; - - if (result->inited) { - return; - } - - result->inited = true; - - while (party) { - if (party->party_type == PT_STRING && party->can_write_to_it) { - capcount++; - } - party = party->next_loner; - } - - result->num_captures = capcount; - result->captures = calloc(sizeof(capture_result_t), capcount+1); - - party = ctx->party_loners; - - while (party) { - if (party->party_type == PT_STRING && party->can_write_to_it) { - capture_result_t *r = result->captures + ix; - - strobj = get_dstr_obj(party); - r->tag = strobj->tag; - r->len = strobj->ix; - - if (strobj->ix) { - r->contents = strobj->strbuf; - - strobj->strbuf = 0; - strobj->ix = 0; - } else { - r->contents = NULL; - } - ix += 1; - } - party = party->next_loner; - } -} - -char * -sb_result_get_capture(sb_result_t *ctx, char *tag, bool caller_borrow) -{ - char *result; - - for (int i = 0; i < ctx->num_captures; i++) { - if (!strcmp(ctx->captures[i].tag, tag)) { - result = ctx->captures[i].contents; - - if (!caller_borrow) { - ctx->captures[i].contents = NULL; - } - return result; - } - } - return NULL; -} - -/* - * The tags are borrowed, so we don't free. If you call this, then - * you're asking to free the capture string copies and the array of - * captures, but the actual sb_result_t object wasn't allocated by - * this API, so we don't own it and this does not try to free it. - */ -void -sb_result_destroy(sb_result_t *ctx) { - for (int i = 0; i < ctx->num_captures; i++) { - if(ctx->captures[i].contents) { - free(ctx->captures[i].contents); - } - } - free(ctx->captures); -} - -/* - * Returns true if there are any open writers that have enqueued items. - */ -static bool -waiting_writes(switchboard_t *ctx) -{ - party_t *writer = ctx->parties_for_writing; - - while(writer) { - if (writer->open_for_write) { - fd_party_t *fd_obj = get_fd_obj(writer); - - if (fd_obj->first_msg != NULL) { - return true; - } - } - writer = writer->next_writer; - } - - return false; -} - -/* - * Runs a switchboard; to completion if you pass `true` to the loop - * parameter, but doesn't post-process in any way. So it doesn't - * create a result object, nor does it clean up any memory. - */ -bool -sb_operate_switchboard(switchboard_t *ctx, bool loop) -{ - if (ctx->done && !waiting_writes(ctx)) { - return true; - } - do { - set_fdinfo(ctx); - if (sb_default_check_exit_conditions(ctx)) { - return true; - } - if (ctx->done && !waiting_writes(ctx)) { - return true; - } - ctx->fds_ready = select(ctx->max_fd, &ctx->readset, &ctx->writeset, - NULL, ctx->io_timeout_ptr); - if (ctx->fds_ready > 0) { - handle_ready_reads(ctx); - handle_ready_writes(ctx); - } - if (ctx->fds_ready >= 0) { - handle_loop_end(ctx); - } else { - // select returned error - if (errno == EINTR) { - continue; - } - ctx->done = true; - return false; - } - } while(loop); - return false; -} diff --git a/nimutils/c/switchboard.h b/nimutils/c/switchboard.h deleted file mode 100644 index cb11e95..0000000 --- a/nimutils/c/switchboard.h +++ /dev/null @@ -1,384 +0,0 @@ -#ifndef SWITCHBOARD_H__ -#define SWITCHBOARD_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEFAULT_HEAP_SIZE (256) -#define SB_ALLOC_LEN (PIPE_BUF + sizeof(struct sb_msg_t)) -#define SB_MSG_LEN PIPE_BUF - -typedef enum -{ PT_STRING = 1, PT_FD = 2, PT_LISTENER = 4, PT_CALLBACK = 8} party_e; - - -typedef void (*switchboard_cb_t)(void *, void *, char *, size_t); -typedef void (*accept_cb_decl)(void *, int fd, struct sockaddr *, socklen_t *); -typedef bool (*progress_cb_decl)(void *); - -/* We queue these messages up for parties registered for writing, but - * only if the sink is a file descriptor; callbacks and strings will - * have the write processed immediately when the reader generates it. - * - * Note that we alloc these messages in some bulk; the switchboard_t - * context below handles the memory management. - * - * In most systems when no reader is particularly slow relative to - * others, there may never need to be more than one malloc call. - */ -typedef struct sb_msg_t { - struct sb_msg_t *next; - size_t len; - char data[SB_MSG_LEN + 1]; -} sb_msg_t; - -/* - * This is the heap data type; the switchboard mallocs one heap at a - * time, and hands out sb_msg_t's from it. The switchboard also keeps - * a list of returned cells, and prefers returned cells over giving - * out unused cells from the heap. - * - * If there's nothing left to give out in the heap or in the free - * list, then we create a new heap (keeping the old one linked). - * - * When we get rid of our switchboard, we free any heaps, and can - * ignore individual sb_msg_t objects. - */ -typedef struct sb_heap_t { - struct sb_heap_t *next; - size_t cur_cell; - sb_msg_t cells[]; -} sb_heap_t; - -/* - * For file descriptors that we might read from, where we might proxy - * the data to some other file descriptor, we keep a linked list of - * subscriber information. - * - * Only parties implemented as FDs are allowed to have - * subscribers. Strings are the other source for input, but those are - * 'published' immediately when the string is connected to the output. - */ -typedef struct subscription_t { - struct subscription_t *next; - struct party_t *subscriber; - bool paused; -} subscription_t; - -/* - * This abstraction is used for any party that's a file descriptor. - * If the file descriptor is read-only, the first_msg and last_msg - * fields will be unused. - * - * If the FD is write-only, then subscribers will not be used. - */ -typedef struct { - int fd; - sb_msg_t *first_msg; - sb_msg_t *last_msg; - subscription_t *subscribers; - bool proxy_close; // close fd when proxy input is closed -} fd_party_t; - -/* - * This is used for listening sockets. - */ -typedef struct { - int fd; - accept_cb_decl accept_cb; - int saved_flags; -} listener_party_t; - -/* - * For strings being piped into a process, pipe or whatever. - */ -typedef struct { - char *strbuf; - bool free_on_close; // Did we take ownership of the str? - size_t len; // Total length of strbuf. - bool close_fd_when_done; // Close the fd after writing? -} str_src_party_t; - -/* - * For buffer output into a string that's fully returned at the end. - * If you want incremental output, use a callback. - */ -typedef struct { - char *strbuf; - size_t len; // Length allocated for strbuf - size_t ix; // Current length; next write at strbuf + ix - char *tag; // Used when returning. - size_t step; // Step for alloc length -} str_dst_party_t; - -/* - * For incremental output! If you need to save state, you can do it by - * assigning to either the swichboard_t 'extra' field or the party_t - * 'extra' field; these are there for you to be able to keep state. - */ -typedef struct { - switchboard_cb_t callback; -} callback_party_t; - -/* - * The union for the five party types above. - */ -typedef union { - str_src_party_t rstrinfo; // Strings used as an input source only - str_dst_party_t wstrinfo; // Strings used as an output sink only - fd_party_t fdinfo; // Can be source, sink or both. - listener_party_t listenerinfo; // We only read from it to kick off accept cb - callback_party_t cbinfo; // Sink only. -} party_info_t; - -/* - * The common abstraction for parties. - * - `erno` will hold the value of any captured (fatal) os error we - * ran accross. This is only used for PT_FD and PT_LISTENER. - * - `open` tracks whether we should deal with this party at all anymore; - * it can mean the fd is closed, or that nothing is routed to it anymore. - * - `can_read_from_it` and `can_write_to_it` indicates whether a party is - * a source (the former) or a sink (the later). Can be both, too. - * - `close_on_destroy` indicates that we should call close() on any file - * descriptors when tearing down the switchboard. - * When this is set, we do not report errors in close(), and we - * assume the same fd won't have been reused if it was otherwise - * closed during the switchboard operation. - * This only gets used for objs of type `PT_FD` and `PT_LISTENER` - * - `stop_on_close` indicates that, when we notice a failure to - * read/write from a file descriptor, we should stop the switchboard; - * we do go ahead and finish available reads/writes, but we do nothing - * else. - * This should generally be the behavior you want when stdin, stdout - * or stderr go away (the controlling terminal is probably gone). - * However, it's not the right behavior for when a sub-process dies; - * There, you want to drain the read-size of the file descriptor. - * For that, you register the sub-process with `sb_monitor_pid()`. - * - `next_reader`, `next_writer` and `next_loner` are three linked lists; - * a party might appear on up to two at once. `next_reader` and - * `next_writer` are only used for fd types. The first list can have - * both `PT_FD`s and `PT_LISTENER`s; the second only `PT_FD`s. - * The switchboard runs down these to figure out what to select() on. - * And, then when exiting, these lists are walked to free `party_t` - * objects. - * `next_loner` is for all other types, and is only used at the end to - * free stuff. - * - `extra` is user-defined, ideal for state keeping in callbacks. - */ -typedef struct party_t { - party_e party_type; - party_info_t info; - int found_errno; - bool open_for_write; - bool open_for_read; - bool can_read_from_it; - bool can_write_to_it; - bool close_on_destroy; - bool stop_on_close; - struct party_t *next_reader; - struct party_t *next_writer; - struct party_t *next_loner; - void *extra; -} party_t; - -/* - * When some of the i/o consists of other processes, we check on the - * status of each process after every select call. This both keeps - * state we need to monitor those processes, and anything we might - * return about the process when returning switchboard results. - */ -typedef struct monitor_t { - struct monitor_t *next; - int exit_status; - pid_t pid; - party_t *stdin_fd_party; - party_t *stdout_fd_party; - party_t *stderr_fd_party; - bool shutdown_when_closed; - bool closed; - int found_errno; - int term_signal; -} monitor_t; - -typedef struct { - char *tag; - char *contents; - int len; -} capture_result_t; - -typedef struct { - bool inited; - int num_captures; - capture_result_t *captures; -} sb_result_t; - -/* - * The main switchboard object. Generally, the fields here can be - * transparent to the user; everything should be dealt with via API. - */ -typedef struct switchboard_t { - struct timeval *io_timeout_ptr; - struct timeval io_timeout; - progress_cb_decl progress_callback; - bool progress_on_timeout_only; - bool done; - fd_set readset; - fd_set writeset; - int max_fd; - int fds_ready; - party_t *parties_for_reading; - party_t *parties_for_writing; - party_t *party_loners; - monitor_t *pid_watch_list; - sb_msg_t *freelist; - sb_heap_t *heap; - size_t heap_elems; - void *extra; - bool ignore_running_procs_on_shutdown; -} switchboard_t; - - -typedef sb_result_t sp_result_t; - -typedef struct { - switchboard_t sb; - bool run; - int signal_fd; - int pty_fd; - bool pty_stdin_pipe; - bool proxy_stdin_close; - bool use_pty; - bool str_waiting; - char *cmd; - char **argv; - char **envp; - char *path; - char passthrough; - bool pt_all_to_stdout; - char capture; - bool combine_captures; // Combine stdout / err and termout - party_t str_stdin; - party_t parent_stdin; - party_t parent_stdout; - party_t parent_stderr; - party_t subproc_stdin; - party_t subproc_stdout; - party_t subproc_stderr; - party_t capture_stdin; - party_t capture_stdout; - party_t capture_stderr; - void (*startup_callback)(void *); - sb_result_t result; - struct termios saved_termcap; - struct termios *parent_termcap; - struct termios *child_termcap; - struct dcb_t *deferred_cbs; -} subprocess_t; - -#define SP_IO_STDIN 1 -#define SP_IO_STDOUT 2 -#define SP_IO_STDERR 4 -#define SP_IO_ALL 7 -#define CAP_ALLOC 16 // In # of PIPE_BUF sized chunks - -// These are the real signatures. -typedef void (*accept_cb_t)(struct switchboard_t *, int fd, - struct sockaddr *, socklen_t *); -typedef bool (*progress_cb_t)(struct switchboard_t *); - -typedef struct dcb_t { - struct dcb_t *next; - unsigned char which; - switchboard_cb_t cb; - party_t *to_free; -} deferred_cb_t; - -extern ssize_t read_one(int, char *, size_t); -extern bool write_data(int, char *, size_t); -extern void sb_init_party_listener(switchboard_t *, party_t *, int, - accept_cb_t, bool, bool); -extern party_t * sb_new_party_listener(switchboard_t *, int, accept_cb_t, bool, - bool); -extern void sb_init_party_fd(switchboard_t *, party_t *, int , int , bool, bool, bool); -extern party_t *sb_new_party_fd(switchboard_t *, int, int, bool, bool, bool); -extern void sb_init_party_input_buf(switchboard_t *, party_t *, char *, - size_t, bool, bool, bool); -extern party_t *sb_new_party_input_buf(switchboard_t *, char *, size_t, - bool, bool, bool); -extern void sb_party_input_buf_new_string(party_t *, char *, size_t, bool, bool); -extern void sb_init_party_output_buf(switchboard_t *, party_t *, char *, - size_t); -extern party_t *sb_new_party_output_buf(switchboard_t *, char *, size_t); -extern void sb_init_party_callback(switchboard_t *, party_t *, - switchboard_cb_t); -extern party_t *sb_new_party_callback(switchboard_t *, switchboard_cb_t); -extern void sb_monitor_pid(switchboard_t *, pid_t, party_t *, party_t *, - party_t *, bool); -extern void *sb_get_extra(switchboard_t *); -extern void sb_set_extra(switchboard_t *, void *); -extern void *sb_get_party_extra(party_t *); -extern void sb_set_party_extra(party_t *, void *); -extern bool sb_route(switchboard_t *, party_t *, party_t *); -extern bool sb_pause_route(switchboard_t *, party_t *, party_t *); -extern bool sb_resume_route(switchboard_t *, party_t *, party_t *); -extern bool sb_route_is_active(switchboard_t *, party_t *, party_t *); -extern bool sb_route_is_paused(switchboard_t *, party_t *, party_t *); -extern bool sb_route_is_subscribed(switchboard_t *, party_t *, party_t *); -extern void sb_init(switchboard_t *, size_t); -extern void sb_set_io_timeout(switchboard_t *, struct timeval *); -extern void sb_clear_io_timeout(switchboard_t *); -extern void sb_destroy(switchboard_t *, bool); -extern bool sb_operate_switchboard(switchboard_t *, bool); -extern void sb_get_results(switchboard_t *, sb_result_t *); -extern char *sb_result_get_capture(sb_result_t *, char *, bool); -extern void sb_result_destroy(sb_result_t *); -extern void subproc_init(subprocess_t *, char *, char *[], bool); -extern bool subproc_set_envp(subprocess_t *, char *[]); -extern bool subproc_pass_to_stdin(subprocess_t *, char *, size_t, bool); -extern bool subproc_set_passthrough(subprocess_t *, unsigned char, bool); -extern bool subproc_set_capture(subprocess_t *, unsigned char, bool); -extern bool subproc_set_io_callback(subprocess_t *, unsigned char, - switchboard_cb_t); -extern void subproc_set_timeout(subprocess_t *, struct timeval *); -extern void subproc_clear_timeout(subprocess_t *); -extern bool subproc_use_pty(subprocess_t *); -extern bool subproc_set_startup_callback(subprocess_t *, void (*)(void *)); -extern int subproc_get_pty_fd(subprocess_t *); -extern void subproc_start(subprocess_t *); -extern bool subproc_poll(subprocess_t *); -extern void subproc_run(subprocess_t *); -extern void subproc_close(subprocess_t *); -extern pid_t subproc_get_pid(subprocess_t *); -extern char *sp_result_capture(sp_result_t *, char *, size_t *); -extern char *subproc_get_capture(subprocess_t *, char *, size_t *); -extern int subproc_get_exit(subprocess_t *, bool); -extern int subproc_get_errno(subprocess_t *, bool); -extern int subproc_get_signal(subprocess_t *, bool); -extern void subproc_set_parent_termcap(subprocess_t *, struct termios *); -extern void subproc_set_child_termcap(subprocess_t *, struct termios *); -extern void subproc_set_extra(subprocess_t *, void *); -extern void *subproc_get_extra(subprocess_t *); -extern int subproc_get_pty_fd(subprocess_t *); -extern void pause_passthrough(subprocess_t *, unsigned char); -extern void resume_passthrough(subprocess_t *, unsigned char); -extern void pause_capture(subprocess_t *, unsigned char); -extern void resume_capture(subprocess_t *, unsigned char); -extern void termcap_get(struct termios *); -extern void termcap_set(struct termios *); -extern void termcap_set_raw_mode(struct termios *); -extern void process_status_check(monitor_t *, bool); -// pty params. -// ASCII Cinema. -#endif diff --git a/nimutils/nimscript.nim b/nimutils/nimscript.nim index 85f4119..a667703 100644 --- a/nimutils/nimscript.nim +++ b/nimutils/nimscript.nim @@ -75,6 +75,9 @@ template applyCommonLinkOptions*(staticLink = true, quiet = true) = switch("passC", "-Wno-error=implicit-function-declaration") switch("passC", "-Wno-error=incompatible-pointer-types") + # n00b + switch("passC", "-DHATRACK_PER_INSTANCE_AUX") + setupTargetArch(quiet) when defined(macosx): diff --git a/nimutils/subproc.nim b/nimutils/subproc.nim index 470942f..a1ea297 100644 --- a/nimutils/subproc.nim +++ b/nimutils/subproc.nim @@ -1,8 +1,6 @@ -import std/[posix, os] -import "."/[switchboard, random, file] +import std/[posix] +import "."/[random, file] -{.warning[UnusedImport]: off.} -{.compile: joinPath(splitPath(currentSourcePath()).head, "c/subproc.c").} {.pragma: sproc, cdecl, importc, nodecl.} type @@ -54,32 +52,26 @@ type SPIoNone = 0, SpIoStdin = 1, SpIoStdout = 2, SpIoInOut = 3, SpIoStderr = 4, SpIoInErr = 5, SpIoOutErr = 6, SpIoAll = 7 - SPResultObj* {. importc: "sb_result_t", header: "switchboard.h" .} = object - SPResult* = ptr SPResultObj - SubProcess* {.importc: "subprocess_t", header: "switchboard.h" .} = object - -proc tcgetattr*(fd: cint, info: var Termcap): cint {. cdecl, importc, - header: "", discardable.} -proc tcsetattr*(fd: cint, opt: TcsaConst, info: var Termcap): - cint {. cdecl, importc, header: "", discardable.} -proc termcap_get*(termcap: var Termcap) {.sproc.} -proc termcap_set*(termcap: var Termcap) {.sproc.} -proc subproc_init(ctx: var SubProcess, cmd: cstring, args: cStringArray, - proxyStdinClose: bool) - {.sproc.} -proc subproc_set_envp(ctx: var SubProcess, args: cStringArray) - {.sproc.} -proc subproc_pass_to_stdin(ctx: var SubProcess, s: cstring, l: csize_t, - close_fd: bool): bool {.sproc.} -proc subproc_get_capture(ctx: var SubProcess, tag: cstring, ln: ptr csize_t): - cstring {.sproc.} -proc subproc_get_exit(ctx: var SubProcess, wait: bool): cint {.sproc.} -proc subproc_get_errno(ctx: var SubProcess, wait: bool): cint {.sproc.} -proc subproc_get_signal(ctx: var SubProcess, wait: bool): cint {.sproc.} + SubProcess* {.importc: "n00b_subproc_t", header: "n00b.h" } = object + +proc tcgetattr*(fd: cint, info: var Termcap): cint + {. cdecl, importc, header: "", discardable.} +proc tcsetattr*(fd: cint, opt: TcsaConst, info: var Termcap): cint + {. cdecl, importc, header: "", discardable.} + +proc n00b_termcap_get*(termcap: var Termcap) {.sproc.} +proc n00b_termcap_set*(termcap: var Termcap) {.sproc.} +proc n00b_subproc_init(ctx: var SubProcess, cmd: cstring, args: cStringArray, proxyStdinClose: bool) {.sproc.} +proc n00b_subproc_set_envp(ctx: var SubProcess, args: cStringArray) {.sproc.} +proc n00b_subproc_pass_to_stdin(ctx: var SubProcess, s: cstring, l: csize_t, close_fd: bool): bool {.sproc.} +proc n00b_subproc_get_capture(ctx: var SubProcess, tag: cstring, ln: ptr csize_t): cstring {.sproc.} +proc n00b_subproc_get_exit(ctx: var SubProcess, wait: bool): cint {.sproc.} +proc n00b_subproc_get_errno(ctx: var SubProcess, wait: bool): cint {.sproc.} +proc n00b_subproc_get_signal(ctx: var SubProcess, wait: bool): cint {.sproc.} # Functions we can call directly w/o a nim proxy. -proc setParentTermcap*(ctx: var SubProcess, tc: var Termcap) {.cdecl, - importc: "subproc_set_parent_termcap", nodecl.} +proc setParentTermcap*(ctx: var SubProcess, tc: var Termcap) + {.cdecl, importc: "n00b_subproc_set_parent_termcap", nodecl.} ## Set the parent's termcap at the time of a fork, for when subprocesses ## are using a pseudo-terminal (pty). Generally the default should be ## good. @@ -90,8 +82,8 @@ proc setParentTermcap*(ctx: var SubProcess, tc: var Termcap) {.cdecl, ## ## This must be called before spawning a process. -proc setChildTermcap*(ctx: var SubProcess, tc: var Termcap) {.cdecl, - importc: "subproc_set_parent_termcap", nodecl.} +proc setChildTermcap*(ctx: var SubProcess, tc: var Termcap) + {.cdecl, importc: "n00b_subproc_set_parent_termcap", nodecl.} ## Set the child's termcap at the time of a fork, for when subprocesses ## are using a pseudo-terminal (pty). ## @@ -100,7 +92,7 @@ proc setChildTermcap*(ctx: var SubProcess, tc: var Termcap) {.cdecl, ## a process. proc setPassthroughRaw*(ctx: var SubProcess, which: SPIoKind, combine: bool) - {.cdecl, importc: "subproc_set_passthrough", nodecl.} + {.cdecl, importc: "n00b_subproc_set_passthrough", nodecl.} ## Low-level wrapper used by `setPassthrough()` template setPassthrough*(ctx: var SubProcess, which = SPIoAll, merge = false) = @@ -112,7 +104,7 @@ template setPassthrough*(ctx: var SubProcess, which = SPIoAll, merge = false) = ctx.setPassthroughRaw(which, merge) proc setCaptureRaw*(ctx: var SubProcess, which: SPIoKind, combine: bool) - {.cdecl, importc: "subproc_set_capture", nodecl.} + {.cdecl, importc: "n00b_subproc_set_capture", nodecl.} ## The low-level interface used by `setCapture` template setCapture*(ctx: var SubProcess, which = SPIoOutErr, merge = false) = @@ -133,15 +125,14 @@ template setCapture*(ctx: var SubProcess, which = SPIoOutErr, merge = false) = ## Currently, this must be called before spawning a process. ctx.setCaptureRaw(which, merge) -proc rawMode*(termcap: var Termcap) {.cdecl, importc: "termcap_set_raw_mode", - nodecl.} +proc rawMode*(termcap: var Termcap) {.cdecl, importc: "n00b_termcap_set_raw_mode", nodecl.} ## This configures a `Termcap` data structure for `raw` mode, which ## loosely is non-echoing and unbuffered. There's not quite a firm ## standard for what raw mode is; but it's a collection of settings, ## most of which no longer matter in modern terminals. proc setTimeout*(ctx: var SubProcess, value: var Timeval) - {.cdecl, importc: "subproc_set_timeout", nodecl.} + {.cdecl, importc: "n00b_subproc_set_timeout", nodecl.} ## This sets how long the process should wait when doing a single ## poll of file-descriptors to be ready with data to read. If you ## don't set this, there will be no timeout, and it's possible for @@ -160,10 +151,10 @@ proc setTimeout*(ctx: var SubProcess, value: var Timeval) ## subprocess switchboard will exit. proc clearTimeout*(ctx: var SubProcess) - {.cdecl, importc: "subproc_clear_timeout", nodecl.} + {.cdecl, importc: "n00b_subproc_clear_timeout", nodecl.} ## Remove any set timeout. -proc usePty*(ctx: var SubProcess) {.cdecl, importc: "subproc_use_pty", nodecl.} +proc usePty*(ctx: var SubProcess) {.cdecl, importc: "n00b_subproc_use_pty", nodecl.} ## When this is set on a SubProcess object before the process is ## spawned, it will cause the process to start using a ## pseudo-terminal (pty), which, from the point of view of the @@ -173,24 +164,24 @@ proc usePty*(ctx: var SubProcess) {.cdecl, importc: "subproc_use_pty", nodecl.} ## connected to a terminal, such as `more()` or `less()`. proc getPtyFd*(ctx: var SubProcess): cint - {.cdecl, importc: "subproc_get_pty_fd", nodecl.} + {.cdecl, importc: "n00b_subproc_get_pty_fd", nodecl.} ## When using a PTY, this call returns the file descriptor associated ## with the child process's terminal. -proc start*(ctx: var SubProcess) {.cdecl, importc: "subproc_start", nodecl.} +proc start*(ctx: var SubProcess) {.cdecl, importc: "n00b_subproc_start", nodecl.} ## This starts the sub-process, forking it off. It does NOT poll for ## input-output. For many apps, you don't need this function; ## instead, use `run()`. ## ## Use this only when you're going to do your own IO polling loop. -proc poll*(ctx: var SubProcess): bool {.cdecl, importc: "subproc_poll", nodecl.} +proc poll*(ctx: var SubProcess): bool {.cdecl, importc: "n00b_subproc_poll", nodecl.} ## If you're running your own IO polling loop, this runs the loop ## one time. You must have previously called `start()`. ## ## This returns `true` when called after the process has exited. -proc run*(ctx: var SubProcess) {.cdecl, importc: "subproc_run", nodecl.} +proc run*(ctx: var SubProcess) {.cdecl, importc: "n00b_subproc_run", nodecl.} ## This launches a subprocess, and polls it for IO until the process ## ends, and has no waiting data left to read. ## @@ -201,7 +192,7 @@ proc run*(ctx: var SubProcess) {.cdecl, importc: "subproc_run", nodecl.} ## `setIoCallback()`) or manually poll by instead using `start()` and ## then calling `poll()` in your own loop. -proc close*(ctx: var SubProcess) {.cdecl, importc: "subproc_close", nodecl.} +proc close*(ctx: var SubProcess) {.cdecl, importc: "n00b_subproc_close", nodecl.} ## Closes down a subprocess; do not call any querying function after ## this, as the memory will be freed. @@ -209,26 +200,26 @@ proc `=destroy`*(ctx: var SubProcess) = ctx.close() proc getPid*(ctx: var SubProcess): Pid - {.cdecl, importc: "subproc_get_pid", nodecl.} + {.cdecl, importc: "n00b_subproc_get_pid", nodecl.} ## Returns the process ID associated with the subprocess. This may ## be called at any point after the process spawns. proc setExtra*(ctx: var SubProcess, p: pointer) - {.cdecl, importc: "subproc_set_extra", nodecl.} + {.cdecl, importc: "n00b_subproc_set_extra", nodecl.} ## This can be used to make arbitrary information available to your ## I/O callbacks that is specific to the SubProcess instance. proc getExtra*(ctx: var SubProcess): pointer - {.cdecl, importc: "subproc_get_extra", nodecl.} + {.cdecl, importc: "n00b_subproc_get_extra", nodecl.} ## This can be used to retrieve any information set via `setExtra()`. proc pausePassthrough*(ctx: var SubProcess, which: SpIoKind) - {.cdecl, importc: "subproc_pause_passthrough", nodecl.} + {.cdecl, importc: "n00b_subproc_pause_passthrough", nodecl.} ## Stops passthrough data from being passed (though pending writes ## may still succeed). proc resumePassthrough*(ctx: var SubProcess, which: SpIoKind) - {.cdecl, importc: "subproc_resume_passthrough", nodecl.} + {.cdecl, importc: "n00b_subproc_resume_passthrough", nodecl.} ## Resumes passthrough after being paused. For data that didn't get ## passed during the pause, it will not be seen after the pause ## either. @@ -237,17 +228,17 @@ proc resumePassthrough*(ctx: var SubProcess, which: SpIoKind) ## subprocess, for instance. proc pauseCapture*(ctx: var SubProcess, which: SpIoKind) - {.cdecl, importc: "subproc_pause_capture", nodecl.} + {.cdecl, importc: "n00b_subproc_pause_capture", nodecl.} ## Stops capture of a stream. If it's resumed, data published ## during the pause will NOT be added to the capture. proc resumeCapture*(ctx: var SubProcess, which: SpIoKind) - {.cdecl, importc: "subproc_resume_capture", nodecl.} + {.cdecl, importc: "n00b_subproc_resume_capture", nodecl.} ## Resumes capturing a stream that's been paused. proc setIoCallback*(ctx: var SubProcess, which: SpIoKind, callback: SubProcCallback): bool - {.cdecl, importc: "subproc_set_io_callback", nodecl, discardable.} + {.cdecl, importc: "n00b_subproc_set_io_callback", nodecl, discardable.} ## Sets up a callback for receiving IO as it is read or written from ## the terminal. The `which` parameter indicates which streams you ## wish to subscribe to. You may call this multiple times, for @@ -255,13 +246,13 @@ proc setIoCallback*(ctx: var SubProcess, which: SpIoKind, ## function, or would like two different functions to receive data. proc setStartupCallback*(ctx: var SubProcess, callback: SpStartupCallback) {. - cdecl, importc: "subproc_set_startup_callback", nodecl .} + cdecl, importc: "n00b_subproc_set_startup_callback", nodecl .} ## This allows you to set a callback in the parent process that will ## run once, after the underlying fork occurs, but before any IO is ## processed. proc rawFdWrite*(fd: cint, buf: pointer, l: csize_t) - {.cdecl, importc: "write_data", nodecl.} + {.cdecl, importc: "n00b_sb_write_data", nodecl.} ## An operation that writes from memory to a raw file descriptor. template binaryCstringToString*(s: cstring, l: int): string = @@ -278,14 +269,14 @@ proc initSubProcess*(ctx: var SubProcess, cmd: string, ## run the sub-process. Instead, you can first configure it, and ## then call `run()` when ready. var cargs = allocCstringArray(args) - subproc_init(ctx, cstring(cmd), cargs, proxyStdinClose) + n00b_subproc_init(ctx, cstring(cmd), cargs, proxyStdinClose) proc setEnv*(ctx: var SubProcess, env: openarray[string]) = ## Explicitly set the environment the subprocess should inherit. If ## not called before the process is launched, the parent's ## environment will be inherited. var envp = allocCstringArray(env) - ctx.subproc_set_envp(envp) + ctx.n00b_subproc_set_envp(envp) proc pipeToStdin*(ctx: var SubProcess, s: string, close_fd: bool): bool = ## This allows you to pass input to the subprocess through its @@ -297,7 +288,7 @@ proc pipeToStdin*(ctx: var SubProcess, s: string, close_fd: bool): bool = ## remains open. However, if you pass `true` to the parameter `close_fd`, ## then the child's stdin will get closed automatically once the ## write completes. - return ctx.subproc_pass_to_stdin(cstring(s), csize_t(s.len()), close_fd) + return ctx.n00b_subproc_pass_to_stdin(cstring(s), csize_t(s.len()), close_fd) proc getTaggedValue*(ctx: var SubProcess, tag: cstring): string = ## Lower-level interface to retrieving captured streams. Use @@ -306,7 +297,7 @@ proc getTaggedValue*(ctx: var SubProcess, tag: cstring): string = outlen: csize_t s: cstring - s = subproc_get_capture(ctx, tag, addr outlen) + s = n00b_subproc_get_capture(ctx, tag, addr outlen) if outlen == 0: result = "" else: @@ -332,17 +323,17 @@ proc getStderr*(ctx: var SubProcess): string = proc getExitCode*(ctx: var SubProcess, waitForExit = true): int = ## Returns the exit code of the process. - return int(subproc_get_exit(ctx, waitForExit)) + return int(n00b_subproc_get_exit(ctx, waitForExit)) proc getErrno*(ctx: var SubProcess, waitForExit = true): int = ## If the child died and we received an error, this will contain ## the value of `errno`. - return int(subproc_get_errno(ctx, waitForExit)) + return int(n00b_subproc_get_errno(ctx, waitForExit)) proc getSignal*(ctx: var SubProcess, waitForExit = true): int = ## If the process died as the result of being passed a signal, ## this will contain the signal number. - return int(subproc_get_signal(ctx, waitForExit)) + return int(n00b_subproc_get_signal(ctx, waitForExit)) type ExecOutput* = ref object stdin*: string @@ -397,6 +388,7 @@ proc runCommand*(exe: string, ## file descriptors are closed, and doesn't wait for the ## subprocess to finish. In this case, process exit status ## will not be reliable. + echo("RUNNING ", exe, " ", $args) var subproc: SubProcess timeout: Timeval diff --git a/nimutils/switchboard.nim b/nimutils/switchboard.nim index f7dd64e..5367964 100644 --- a/nimutils/switchboard.nim +++ b/nimutils/switchboard.nim @@ -41,45 +41,42 @@ ## `subprocess` interface, though we will, in the not-too-distant ## future add a more polished interface to this module that would be ## appropriate for server setups, etc. -import std/[os, posix] +import std/[posix] -{.pragma: sb, cdecl, importc, nodecl.} - -static: - {.compile: joinPath(splitPath(currentSourcePath()).head, "c/switchboard.c").} +{.pragma: sproc, cdecl, importc, nodecl.} type - SwitchBoard* {.importc: "switchboard_t", header: "switchboard.h" .} = object - Party* {.importc: "party_t", header: "switchboard.h" .} = object + SwitchBoard* {.importc: "n00b_switchboard_t", header: "n00b.h" } = object + Party* {.importc: "n00b_party_t", header: "n00b.h" } = object SBCallback* = proc (i0: var RootRef, i1: var RootRef, i2: cstring, i3: int) {. cdecl, gcsafe .} AcceptCallback* = proc (i0: var SwitchBoard, fd: cint, addressp: pointer, addrlenp: pointer) {. cdecl, gcsafe .} - SBCaptures* {. importc: "sb_result_t", header: "switchboard.h" .} = object + SBCaptures* {. importc: "n00b_capture_result_t", header: "n00b.h" } = object SbFdPerms* = enum sbRead = 0, sbWrite = 1, sbAll = 2 -proc sb_init*(ctx: var SwitchBoard, heap_elems: csize_t) {.sb.} +proc n00b_sb_init*(ctx: var SwitchBoard, heap_elems: csize_t) {.sproc.} ## Low-level interface. Use initSwitchboard(). -proc sb_init_party_fd(ctx: var Switchboard, party: var Party, fd: cint, +proc n00b_sb_init_party_fd(ctx: var Switchboard, party: var Party, fd: cint, perms: SbFdPerms, stopWhenClosed: bool, - closeOnDestroy: bool, closeWhenDone: bool) {.sb.} + closeOnDestroy: bool, closeWhenDone: bool) {.sproc.} proc initPartyCallback*(ctx: var Switchboard, party: var Party, callback: SBCallback) {.cdecl, - importc: "sb_init_party_callback", nodecl .} + importc: "n00b_sb_init_party_callback", nodecl .} ## This sets up a callback to receive incremental data that ## has been read from any file descriptor, except listening sockets. ## ## Any state information can be passed to this callback via the - ## as-yet-unwrapped `sb_set_extra()` and retrieved by the similarly + ## as-yet-unwrapped `n00b_sb_set_extra()` and retrieved by the similarly ## unwrapped `sb_get_party_extra()`. -proc sb_init_party_listener(ctx: var Switchboard, party: var Party, +proc n00b_sb_init_party_listener(ctx: var Switchboard, party: var Party, sockfd: int, callback: AcceptCallback, - stopWhenClosed: bool, closeOnDestroy: bool) {.sb.} + stopWhenClosed: bool, closeOnDestroy: bool) {.sproc.} proc initPartyListener*(ctx: var Switchboard, party: var Party, sockfd: int, callback: AcceptCallback, @@ -87,15 +84,15 @@ proc initPartyListener*(ctx: var Switchboard, party: var Party, ## This sets up monitoring of a socket that is listening for connections. ## The provided callback will be called whenever there is a listening ## socket waiting to be read. - ctx.sb_init_party_listener(party, sockfd, callback, stopWhenClosed, + ctx.n00b_sb_init_party_listener(party, sockfd, callback, stopWhenClosed, closeOnDestroy) -proc sb_init_party_input_buf(ctx: var Switchboard, party: var Party, +proc n00b_sb_init_party_input_buf(ctx: var Switchboard, party: var Party, input: cstring, l: csize_t, dup: bool, - free: bool, close_fd_when_done: bool) {.sb.} + free: bool, close_fd_when_done: bool) {.sproc.} -proc sb_init_party_output_buf(ctx: var Switchboard, party: var Party, - tag: cstring, l: csize_t) {.sb.} +proc n00b_sb_init_party_output_buf(ctx: var Switchboard, party: var Party, + tag: cstring, l: csize_t) {.sproc.} proc initPartyCapture*(ctx: var Switchboard, party: var Party, prealloc = 4096, tag: static[string]) = @@ -105,7 +102,7 @@ proc initPartyCapture*(ctx: var Switchboard, party: var Party, ## The underlying api assumes that it never has to free the passed ## tag and that it will always exit, so in this variant, the tag ## must point to static memory. - ctx.sb_init_party_output_buf(party, tag, csize_t(prealloc)) + ctx.n00b_sb_init_party_output_buf(party, tag, csize_t(prealloc)) proc unsafeInitPartyCapture*(ctx: var Switchboard, party: var Party, prealloc = 4096, tag: string) = @@ -118,10 +115,10 @@ proc unsafeInitPartyCapture*(ctx: var Switchboard, party: var Party, ## ## We may refactor the underlying implementation to address this, ## but don't count on it! - ctx.sb_init_party_output_buf(party, tag, csize_t(prealloc)) + ctx.n00b_sb_init_party_output_buf(party, tag, csize_t(prealloc)) -proc sb_monitor_pid(ctx: var Switchboard, pid: Pid, stdin: ptr Party, - stdout: ptr Party, stderr: ptr Party, shutdown: bool) {.sb.} +proc n00b_sb_monitor_pid(ctx: var Switchboard, pid: Pid, stdin: ptr Party, + stdout: ptr Party, stderr: ptr Party, shutdown: bool) {.sproc.} proc monitorProcess*(ctx: var Switchboard, pid: Pid, stdin: ref Party = nil, stdout: ref Party = nil, stderr: ref Party = nil, @@ -142,7 +139,7 @@ proc monitorProcess*(ctx: var Switchboard, pid: Pid, stdin: ref Party = nil, ## writes are completed, then the switchboard will exit (possibly ## with active file descriptors). A few reads from other file ## descriptors could get services while waiting for the shutdown. - ctx.sb_monitor_pid(pid, cast[ptr Party](stdin), cast[ptr Party](stdout), + ctx.n00b_sb_monitor_pid(pid, cast[ptr Party](stdin), cast[ptr Party](stdout), cast[ptr Party](stderr), shutdown) proc initPartyStrInput*(ctx: var Switchboard, party: var Party, @@ -163,12 +160,12 @@ proc initPartyStrInput*(ctx: var Switchboard, party: var Party, ## and any file descriptors this is scheduled to write to will be ## closed automatically once the write is completed. This allows you ## to close the stdin of a subprocess after the string gets written. - ctx.sb_init_party_input_buf(party, cstring(input), csize_t(input.len()), + ctx.n00b_sb_init_party_input_buf(party, cstring(input), csize_t(input.len()), true, true, close_fd_when_done) -proc sb_party_input_buf_new_string(party: var Party, input: cstring, +proc n00b_sb_party_input_buf_new_string(party: var Party, input: cstring, l: csize_t, dup: bool, - free: bool, closeFd: bool) {.sb.} + free: bool, closeFd: bool) {.sproc.} proc setString*(party: var Party, input: string, closeAfter: bool = false) = ## If a party is a string input buffer, this will update the string @@ -178,7 +175,7 @@ proc setString*(party: var Party, input: string, closeAfter: bool = false) = ## ## If `closeAfter` is true, then once this string is written, any ## subscriber will be closed. - party.sb_party_input_buf_new_string(cstring(input), csize_t(input.len()), + party.n00b_sb_party_input_buf_new_string(cstring(input), csize_t(input.len()), true, true, closeAfter) proc initPartyFd*(ctx: var SwitchBoard, party: var Party, fd: int, perms: SbFdPerms, stopWhenClosed = false, @@ -195,51 +192,51 @@ proc initPartyFd*(ctx: var SwitchBoard, party: var Party, fd: int, ## ## If `closeOnDestroy` is true, we will call close() on the fd for ## you whenever the switchboard is torn down. - sb_init_party_fd(ctx, party, cint(fd), perms, stopWhenClosed, + n00b_sb_init_party_fd(ctx, party, cint(fd), perms, stopWhenClosed, closeOnDestroy, closeWhenDone) -proc sb_destroy(ctx: var Switchboard, free: bool) {.sb.} +proc n00b_sb_destroy(ctx: var Switchboard, free: bool) {.sproc.} template initSwitchboard*(ctx: var SwitchBoard, heap_elems: int = 16) = - sb_init(ctx, csize_t(heap_elems)) + n00b_sb_init(ctx, csize_t(heap_elems)) ## Initialize a switchboard object. proc route*(ctx: var Switchboard, src: var Party, dst: var Party): bool - {.cdecl, importc: "sb_route", nodecl, discardable.} + {.cdecl, importc: "n00b_sb_route", nodecl, discardable.} ## Route messages from the `src` object to the `dst` object. ## Basically, the `dst` party subscribes to messages from the `src`. ## These subscriptions shouldn't be removed, but can be paused and ## resumed (pausing and never resuming is tantamount to removing). proc pauseRoute*(ctx: var Switchboard, src: var Party, dst: var Party): bool - {.cdecl, importc: "sb_pause_route", nodecl, discardable.} + {.cdecl, importc: "n00b_sb_pause_route", nodecl, discardable.} ## Akin to removing a route subscription, except that you can easily ## re-subscribe if you wish by calling `resumeRoute` proc resumeRoute*(ctx: var Switchboard, src: var Party, dst: var Party): bool - {.cdecl, importc: "sb_resume_route", nodecl, discardable.} + {.cdecl, importc: "n00b_sb_resume_route", nodecl, discardable.} ## Restarts a previous route / subscription that has been paused. proc routeIsActive*(ctx: var Switchboard, src: var Party, dst: var Party): bool - {.cdecl, importc: "sb_route_is_active", nodecl, discardable.} + {.cdecl, importc: "n00b_sb_route_is_active", nodecl, discardable.} ## Returns true if the subscription is active, meaning it exists, ## neither side is closed, and the subscription is not paused. proc routeIsPaused*(ctx: var Switchboard, src: var Party, dst: var Party): bool - {.cdecl, importc: "sb_route_is_paused", nodecl, discardable.} + {.cdecl, importc: "n00b_sb_route_is_paused", nodecl, discardable.} ## Returns true if the subscription is active but paused, meaning a ## subscribed happened, but it was paused. If either side is closed, ## this will return `false`, even if it had previously been paused. proc routeIsSubscribed*(ctx: var Switchboard, src: var Party, dst: var Party): bool - {.cdecl, importc: "sb_route_is_subscribed", nodecl, discardable.} + {.cdecl, importc: "n00b_sb_route_is_subscribed", nodecl, discardable.} ## Returns true if the subscription is active, meaning a subscribed ## happened, and neither side is closed. However, it may be either ## paused or unpaused. proc setTimeout*(ctx: var Switchboard, value: var Timeval) - {.cdecl, importc: "sb_set_io_timeout", nodecl.} + {.cdecl, importc: "n00b_sb_set_io_timeout", nodecl.} ## Sets the amount of time that one polling loop blocks waiting for ## I/O. If you're definitely going to wait forever until the ## switchboard ends, then this can be unlimited (see @@ -250,23 +247,23 @@ proc setTimeout*(ctx: var Switchboard, value: var Timeval) ## unnecessarily drive up CPU. proc clearTimeout*(ctx: var Switchboard) - {.cdecl, importc: "sb_clear_io_timeout", nodecl.} + {.cdecl, importc: "n00b_sb_clear_io_timeout", nodecl.} ## Removes any polling timeout; if there's no IO on the switchboard, ## polling will hang until there is. proc operateSwitchboard*(ctx: var Switchboard, toCompletion: bool): bool - {.cdecl, importc: "sb_operate_switchboard", nodecl, discardable.} + {.cdecl, importc: "n00b_sb_operate_switchboard", nodecl, discardable.} ## Low-level interface; use run() instead. -proc sb_set_extra(ctx: var Switchboard, extra: RootRef) {.sb.} -proc sb_set_party_extra(ctx: var Party, extra: RootRef) {.sb.} +proc n00b_sb_set_extra(ctx: var Switchboard, extra: RootRef) {.sproc.} +proc n00b_sb_set_party_extra(ctx: var Party, extra: RootRef) {.sproc.} proc getExtraData*(ctx: var Switchboard): RootRef - {.cdecl, importc: "sb_get_extra", nodecl.} + {.cdecl, importc: "n00b_sb_get_extra", nodecl.} ## Retrieves any extra data stored, specific to a switchboard. proc getExtraData*(ctx: var Party): RootRef - {.cdecl, importc: "sb_get_party_extra", nodecl.} + {.cdecl, importc: "n00b_sb_get_party_extra", nodecl.} ## Retrieves any extra data stored for the party. proc clearExtraData*(ctx: var Switchboard) = @@ -274,7 +271,7 @@ proc clearExtraData*(ctx: var Switchboard) = let x = ctx.getExtraData() if x != nil: - ctx.sb_set_extra(RootRef(nil)) + ctx.n00b_sb_set_extra(RootRef(nil)) GC_unref(x) proc clearExtraData*(ctx: var Party) = @@ -282,7 +279,7 @@ proc clearExtraData*(ctx: var Party) = let x = ctx.getExtraData() if x != nil: - ctx.sb_set_party_extra(RootRef(nil)) + ctx.n00b_sb_set_party_extra(RootRef(nil)) GC_unref(x) proc setExtraData*(ctx: var Switchboard, extra: RootRef) = @@ -300,7 +297,7 @@ proc setExtraData*(ctx: var Switchboard, extra: RootRef) = if x != nil: GC_unref(x) - ctx.sb_set_extra(extra) + ctx.n00b_sb_set_extra(extra) if extra != RootRef(nil): GC_ref(extra) @@ -321,7 +318,7 @@ proc setExtraData*(ctx: var Party, extra: RootRef) = if x != nil: GC_unref(x) - ctx.sb_set_party_extra(extra) + ctx.n00b_sb_set_party_extra(extra) if extra != RootRef(nil): GC_ref(extra) @@ -329,23 +326,23 @@ proc setExtraData*(ctx: var Party, extra: RootRef) = proc `=destroy`*(ctx: var Switchboard) = var copy = ctx copy.clearExtraData() - copy.sb_destroy(false) + copy.n00b_sb_destroy(false) proc `=destroy`*(ctx: var Party) = var copy = ctx copy.clearExtraData() -proc sb_result_destroy(res: ptr SBCaptures) {.sb.} +proc n00b_sb_result_destroy(res: ptr SBCaptures) {.sproc.} proc `=destroy`*(res: var SBCaptures) = - sb_result_destroy(addr res) + n00b_sb_result_destroy(addr res) -proc sb_result_get_capture(res: var SBCaptures, tag: cstring, - borrow: bool): cstring {.sb.} +proc n00b_sb_result_get_capture(res: var SBCaptures, tag: cstring, + borrow: bool): cstring {.sproc.} proc getCapture*(res: var SBCaptures, tag: string): string = ## Returns a specific process capture by tag. - return $(res.sb_result_get_capture(cstring(tag), true)) + return $(res.n00b_sb_result_get_capture(cstring(tag), true)) proc run*(ctx: var Switchboard, toCompletion = true): bool {.discardable.} = ## Runs the switchboard IO polling cycle. By default, this will keep @@ -366,7 +363,3 @@ proc run*(ctx: var Switchboard, toCompletion = true): bool {.discardable.} = return true else: return false - -# Not yet wrapped: -## extern void sb_monitor_pid(switchboard_t *, pid_t, party_t *, party_t *, -## party_t *, bool);