X-Git-Url: https://git.distorted.org.uk/~mdw/fwd/blobdiff_plain/07d71f34ab3887b63c6ff2d635fce07368f90295..48bb1f76fd805b19aa32a374a8a9d8106ab7f420:/exec.c diff --git a/exec.c b/exec.c new file mode 100644 index 0000000..1900017 --- /dev/null +++ b/exec.c @@ -0,0 +1,1152 @@ +/* -*-c-*- + * + * $Id: exec.c,v 1.1 1999/07/26 23:33:32 mdw Exp $ + * + * Source and target for executable programs + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `fw' port forwarder. + * + * `fw' is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * `fw' is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with `fw'; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: exec.c,v $ + * Revision 1.1 1999/07/26 23:33:32 mdw + * New sources and targets. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_SETRLIMIT +# include +#endif + +#ifndef DECL_ENVIRON + extern char **environ; +#endif + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conf.h" +#include "endpt.h" +#include "exec.h" +#include "fattr.h" +#include "fw.h" +#include "reffd.h" +#include "scan.h" +#include "source.h" +#include "target.h" + +/*----- Data structures ---------------------------------------------------*/ + +/* --- Resource usage --- */ + +#ifdef HAVE_SETRLIMIT + +typedef struct xlimit { +#define R(r, n) struct rlimit n; +#include "rlimits.h" +} xlimit; + +#endif + +/* --- Environment variable modifications --- */ + +typedef struct xenv { + struct xenv *next; + unsigned act; + char *name; + char *value; +} xenv; + +#define XEA_SET 0u +#define XEA_CLEAR 1u + +/* --- Program options --- */ + +typedef struct xopts { + unsigned ref; + unsigned f; + const char *dir; + const char *root; + uid_t uid; + gid_t gid; + xenv *env; + xenv **etail; +#ifdef HAVE_SETRLIMIT + xlimit xl; +#endif +} xopts; + +#define XF_NOLOG 1u + +/* --- Executable program arguments --- */ + +typedef struct xargs { + unsigned ref; + char *file; + char *argv[1]; +} xargs; + +#define XARGS_SZ(n) (sizeof(xargs) + sizeof(char *) * (n)) + +/* --- Executable endpoints --- */ + +typedef struct xept { + endpt e; + struct xept *next, *prev; + pid_t kid; + const char *desc; + int st; + xargs *xa; + xopts *xo; + selbuf err; +} xept; + +#define XEF_CLOSE 16u +#define XEF_EXIT 32u + +/* --- Executable program data --- */ + +typedef struct xdata { + xargs *xa; + xopts *xo; +} xdata; + +/* --- Executable source block --- */ + +typedef struct xsource { + source s; + xdata x; +} xsource; + +/* --- Executable target block --- */ + +typedef struct xtarget { + target t; + xdata x; +} xtarget; + +/*----- Static variables --------------------------------------------------*/ + +static xopts exec_opts = { 1, 0, 0, 0, -1, -1, 0, &exec_opts.env }; +static xept *xept_list; +static sig xept_sig; +static sym_table env; + +/*----- Fiddling about with resource limits -------------------------------*/ + +#ifdef HAVE_SETRLIMIT + +/* --- Table mapping user-level names to OS interface bits --- */ + +typedef struct rlimit_ent { + const char *name; + const char *rname; + int r; + size_t off; +} rlimit_ent; + +static rlimit_ent rlimits[] = { +#define R(r, n) { #n, #r, r, offsetof(xlimit, n) }, +#include "rlimits.h" + { 0, 0, 0, 0 } +}; + +#define RLIMIT(xl, o) ((struct rlimit *)((char *)(xl) + (o))) + +/* --- @rlimit_get@ --- * + * + * Arguments: @xlimit *xl@ = pointer to limit structure + * + * Returns: --- + * + * Use: Initializes a limit structure from the current limits. + */ + +static void rlimit_get(xlimit *xl) +{ + rlimit_ent *r; + + for (r = rlimits; r->name; r++) { + if (getrlimit(r->r, RLIMIT(xl, r->off))) { + moan("couldn't read %s: %s", r->rname, strerror(errno)); + _exit(1); + } + } +} + +/* --- @rlimit_set@ --- * + * + * Arguments: @xlimit *xl@ = pointer to limit structure + * + * Returns: --- + * + * Use: Sets resource limits from the supplied limits structure. + */ + +static void rlimit_set(xlimit *xl) +{ + rlimit_ent *r; + + for (r = rlimits; r->name; r++) { + if (setrlimit(r->r, RLIMIT(xl, r->off))) { + moan("couldn't set %s: %s", r->rname, strerror(errno)); + _exit(1); + } + } +} + +/* --- @rlimit_option@ --- */ + +static int rlimit_option(xlimit *xl, scanner *sc) +{ + CONF_BEGIN(sc, "rlimit", "resource limit") + enum { w_soft, w_hard, w_both } which = w_both; + rlimit_ent *chosen; + struct rlimit *rl; + long v; + + /* --- Find out which resource is being fiddled --- */ + + { + rlimit_ent *r; + + chosen = 0; + for (r = rlimits; r->name; r++) { + if (strncmp(sc->d.buf, r->name, sc->d.len) == 0) { + if (r->name[sc->d.len] == 0) { + chosen = r; + break; + } else if (chosen) + error(sc, "ambiguous resource limit name `%s'", sc->d.buf); + else if (CONF_QUAL) + chosen = r; + } + } + if (!chosen) + CONF_REJECT; + token(sc); + rl = RLIMIT(xl, chosen->off); + } + + /* --- Look for hard or soft restrictions --- */ + + { + int i; + if (sc->t == '.') + token(sc); + if (sc->t == CTOK_WORD) { + if ((i = conf_enum(sc, "soft,hard", + ENUM_ABBREV | ENUM_NONE, "limit type")) != -1) + which = i; + } + } + + /* --- Now read the new value --- */ + + if (sc->t == '=') + token(sc); + if (sc->t != CTOK_WORD) + error(sc, "parse error, expected limit value"); + + if (conf_enum(sc, "unlimited,infinity", + ENUM_ABBREV | ENUM_NONE, "limit value") > -1) + v = RLIM_INFINITY; + else { + char *p; + + v = strtol(sc->d.buf, &p, 0); + if (p == sc->d.buf) + error(sc, "parse error, invalid limit value `%s'", sc->d.buf); + switch (tolower((unsigned char)*p)) { + case 0: break; + case 'b': v *= 512; break; + case 'g': v *= 1024; + case 'm': v *= 1024; + case 'k': v *= 1024; break; + default: error(sc, "parse error, invalid limit scale `%c'", *p); + } + token(sc); + } + + /* --- Store the limit value away --- */ + + switch (which) { + case w_both: + rl->rlim_cur = v; + rl->rlim_max = v; + break; + case w_soft: + if (v > rl->rlim_max) + error(sc, "soft limit %l exceeds hard limit %l for %s", + v, rl->rlim_max, chosen->rname); + rl->rlim_cur = v; + break; + case w_hard: + rl->rlim_max = v; + if (rl->rlim_cur > v) + rl->rlim_cur = v; + break; + } + + CONF_ACCEPT; + CONF_END; +} + +#endif + +/*----- Environment fiddling ----------------------------------------------*/ + +/* --- @xenv_option@ --- * + * + * Arguments: @xopts *xo@ = pointer to options block + * @scanner *sc@ = pointer to scanner + * + * Returns: Nonzero if claimed + * + * Use: Parses environment variable assignments. + */ + +static int xenv_option(xopts *xo, scanner *sc) +{ + CONF_BEGIN(sc, "env", "environment") + xenv *xe; + + /* --- Unset a variable --- */ + + if (strcmp(sc->d.buf, "unset") == 0) { + token(sc); + if (sc->t != CTOK_WORD) + error(sc, "parse error, expected environment variable name"); + xe = CREATE(xenv); + xe->name = xstrdup(sc->d.buf); + xe->value = 0; + xe->act = XEA_SET; + token(sc); + goto link; + } + + /* --- Clear the entire environment --- */ + + if (strcmp(sc->d.buf, "clear") == 0) { + token(sc); + xe = CREATE(xenv); + xe->act = XEA_CLEAR; + goto link; + } + + /* --- Allow `set' to be omitted if there's a prefix --- */ + + if (strcmp(sc->d.buf, "set") == 0) + token(sc); + else if (!CONF_QUAL) + CONF_REJECT; + + /* --- Set a variable --- */ + + if (sc->t != CTOK_WORD) + error(sc, "parse error, expected environment variable name"); + xe = CREATE(xenv); + xe->name = xstrdup(sc->d.buf); + token(sc); + if (sc->t == '=') + token(sc); + if (sc->t != CTOK_WORD) + error(sc, "parse error, expected environment variable value"); + xe->value = xstrdup(sc->d.buf); + xe->act = XEA_SET; + token(sc); + goto link; + +link: + xe->next = 0; + *xo->etail = xe; + xo->etail = &xe->next; + CONF_ACCEPT; + + /* --- Nothing else to try --- */ + + CONF_END; +} + +/* --- @xenv_apply@ --- * + * + * Arguments: @xenv *xe@ = pointer to a variable change list + * + * Returns: --- + * + * Use: Modifies the environment (in @env@) according to the list. + */ + +static void xenv_apply(xenv *xe) +{ + while (xe) { + switch (xe->act) { + case XEA_SET: + env_put(&env, xe->name, xe->value); + break; + case XEA_CLEAR: + env_destroy(&env); + sym_create(&env); + break; + } + xe = xe->next; + } +} + +/* --- @xenv_destroy@ --- * + * + * Arguments: @xenv *xe@ = pointer to a variable change list + * + * Returns: --- + * + * Use: Frees the memory used by an environment variable change list. + */ + +static void xenv_destroy(xenv *xe) +{ + while (xe) { + xenv *xxe = xe; + xe = xe->next; + free(xxe->name); + if (xxe->value) + free(xxe->value); + DESTROY(xxe); + } +} + +/*----- Miscellaneous good things -----------------------------------------*/ + +/* --- @x_tidy@ --- * + * + * Arguments: @xargs *xa@ = pointer to an arguments block + * @xopts *xo@ = pointer to an options block + * + * Returns: --- + * + * Use: Releases a reference to argument and options blocks. + */ + +static void x_tidy(xargs *xa, xopts *xo) +{ + xa->ref--; + if (!xa->ref) + free(xa); + + xo->ref--; + if (!xo->ref) { + xenv_destroy(xo->env); + DESTROY(xo); + } +} + +/*----- Executable endpoints ----------------------------------------------*/ + +/* --- @attach@ --- */ + +static void xept_error(char */*p*/, void */*v*/); + +static void xept_attach(endpt *e, reffd *in, reffd *out) +{ + xept *xe = (xept *)e; + pid_t kid; + int fd[2]; + + /* --- Make a pipe for standard error --- */ + + if (pipe(fd)) { + fw_log(-1, "[%s] couldn't create pipe: %s", xe->desc, strerror(errno)); + return; + } + fdflags(fd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + + /* --- Fork a child, and handle an error if there was one --- */ + + if ((kid = fork()) == -1) { + fw_log(-1, "[%s] couldn't fork: %s", xe->desc, strerror(errno)); + close(fd[0]); + close(fd[1]); + return; + } + + /* --- Do the child thing --- */ + + if (kid == 0) { + xopts *xo = xe->xo; + + /* --- Fiddle with the file descriptors --- * + * + * Attach the other endpoint's descriptors to standard input and output. + * Attach my pipe to standard error. Mark everything as blocking and + * not-to-be-closed-on-exec at this end. + */ + + close(fd[0]); + if (dup2(in->fd, STDIN_FILENO) < 0 || + dup2(out->fd, STDOUT_FILENO) < 0 || + dup2(fd[1], STDERR_FILENO) < 0) { + moan("couldn't manipulate file descriptors: %s", strerror(errno)); + _exit(1); + } + + if (in->fd > 2) + close(in->fd); + if (out->fd > 2) + close(out->fd); + + fdflags(STDIN_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0); + fdflags(STDOUT_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0); + fdflags(STDERR_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0); + + /* --- First of all set the @chroot@ prison --- */ + + if (xo->root && chroot(xo->root)) { + moan("couldn't set `%s' as filesystem root: %s", + xo->root, strerror(errno)); + _exit(1); + } + + /* --- Now set the current directory --- */ + + if (xo->dir ? chdir(xo->dir) : xo->root ? chdir("/") : 0) { + moan("couldn't set `%s' as current directory: %s", + xo->dir ? xo->dir : "/", strerror(errno)); + _exit(1); + } + + /* --- Set the resource limits --- */ + +#ifdef HAVE_SETRLIMIT + rlimit_set(&xo->xl); +#endif + + /* --- Set group id --- */ + + if (xo->gid != -1) { + if (setgid(xo->gid)) { + moan("couldn't set gid %i: %s", xo->gid, strerror(errno)); + _exit(1); + } +#ifdef HAVE_SETGROUPS + if (setgroups(1, &xo->gid)) + moan("warning: couldn't set group list to %i: %s", xo->gid, + strerror(errno)); +#endif + } + + /* --- Set uid --- */ + + if (xo->uid != -1) { + if (setuid(xo->uid)) { + moan("couldn't set uid %i: %s", xo->uid, strerror(errno)); + _exit(1); + } + } + + /* --- Play with signal dispositions --- */ + + signal(SIGPIPE, SIG_DFL); + + /* --- Fiddle with the environment --- */ + + xenv_apply(exec_opts.env); + xenv_apply(xe->xo->env); + environ = env_export(&env); + + /* --- Run the program --- */ + + execvp(xe->xa->file, xe->xa->argv); + moan("couldn't execute `%s': %s", xe->xa->file, strerror(errno)); + _exit(127); + } + + /* --- The child's done; see to the parent --- */ + + xe->kid = kid; + selbuf_init(&xe->err, sel, fd[0], xept_error, xe); + close(fd[1]); + xe->next = xept_list; + xe->prev = 0; + if (xept_list) + xept_list->prev = xe; + xept_list = xe; + if (!(xe->xo->f & XF_NOLOG)) + fw_log(-1, "[%s] started with pid %i", xe->desc, kid); + fw_inc(); + return; +} + +/* --- @xept_close@ --- */ + +static void xept_close(endpt *e) +{ + xept *xe = (xept *)e; + if (xe->kid == -1) { + x_tidy(xe->xa, xe->xo); + DESTROY(xe); + fw_dec(); + } +} + +/* --- @xept_destroy@ --- */ + +static void xept_destroy(xept *xe) +{ + /* --- First emit the news about the process --- */ + + if (xe->xo->f & XF_NOLOG) + /* Nothin' doin' */; + else if (WIFEXITED(xe->st)) { + if (WEXITSTATUS(xe->st) == 0) + fw_log(-1, "[%s] pid %i exited successfully", xe->desc, xe->kid); + else { + fw_log(-1, "[%s] pid %i failed: status %i", + xe->desc, xe->kid, WEXITSTATUS(xe->st)); + } + } else if (WIFSIGNALED(xe->st)) { + const char *s; +#ifdef HAVE_STRSIGNAL + s = strsignal(WTERMSIG(xe->st)); +#elif HAVE__SYS_SIGLIST + s = _sys_siglist[WTERMSIG(xe->st)]; +#else + char buf[32]; + sprintf(buf, "signal %i", WTERMSIG(xe->st)); + s = buf; +#endif + fw_log(-1, "[%s] pid %i failed: %s", xe->desc, xe->kid, s); + } else + fw_log(-1, "[%s] pid %i failed: unrecognized status", xe->desc, xe->kid); + + /* --- Free up the parent-side resources --- */ + + if (xe->next) + xe->next->prev = xe->prev; + if (xe->prev) + xe->prev->next = xe->next; + else + xept_list = xe->next; + + x_tidy(xe->xa, xe->xo); + fw_dec(); + DESTROY(xe); +} + +/* --- @xept_chld@ --- * + * + * Arguments: @int n@ = signal number + * @void *p@ = uninteresting pointer + * + * Returns: --- + * + * Use: Deals with child death situations. + */ + +static void xept_chld(int n, void *p) +{ + pid_t kid; + int st; + + while ((kid = waitpid(-1, &st, WNOHANG)) > 0) { + xept *xe = xept_list; + while (xe) { + xept *xxe = xe; + xe = xe->next; + if (kid == xxe->kid) { + xxe->st = st; + xxe->e.f |= XEF_EXIT; + if (xxe->e.f & XEF_CLOSE) + xept_destroy(xxe); + break; + } + } + } +} + +/* --- @xept_error@ --- * + * + * Arguments: @char *p@ = pointer to string read from stderr + * @void *v@ = pointer to by endpoint + * + * Returns: --- + * + * Use: Handles error reports from a child process. + */ + +static void xept_error(char *p, void *v) +{ + xept *xe = v; + if (p) + fw_log(-1, "[%s] pid %i: %s", xe->desc, xe->kid, p); + else { + selbuf_disable(&xe->err); + close(xe->err.reader.fd); + xe->e.f |= XEF_CLOSE; + if (xe->e.f & XEF_EXIT) + xept_destroy(xe); + } +} + +/* --- Endpoint operations --- */ + +static endpt_ops xept_ops = { xept_attach, 0, xept_close }; + +/*----- General operations on sources and targets -------------------------*/ + +/* --- @exec_init@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Initializes the executable problem source and target. + */ + +void exec_init(void) +{ + rlimit_get(&exec_opts.xl); + sig_add(&xept_sig, SIGCHLD, xept_chld, 0); + sym_create(&env); + env_import(&env, environ); +} + +/* --- @exec_option@ --- */ + +static int exec_option(xdata *x, scanner *sc) +{ + xopts *xo = x ? x->xo : &exec_opts; + + CONF_BEGIN(sc, "exec", "executable"); + + /* --- Logging settings --- */ + + if (strcmp(sc->d.buf, "logging") == 0 || + strcmp(sc->d.buf, "log") == 0) { + token(sc); + if (sc->t == '=') + token(sc); + if (conf_enum(sc, "no,yes", ENUM_ABBREV, "logging status")) + xo->f &= ~XF_NOLOG; + else + xo->f |= XF_NOLOG; + CONF_ACCEPT; + } + + /* --- Current directory setting --- * + * + * Lots of possibilities to guard against possible brainoes. + */ + + if (strcmp(sc->d.buf, "dir") == 0 || + strcmp(sc->d.buf, "cd") == 0 || + strcmp(sc->d.buf, "chdir") == 0 || + strcmp(sc->d.buf, "cwd") == 0) { + dstr d = DSTR_INIT; + token(sc); + if (sc->t == '=') + token(sc); + conf_name(sc, '/', &d); + xo->dir = xstrdup(d.buf); + dstr_destroy(&d); + CONF_ACCEPT; + } + + /* --- Set a chroot prison --- */ + + if (strcmp(sc->d.buf, "root") == 0 || + strcmp(sc->d.buf, "chroot") == 0) { + dstr d = DSTR_INIT; + token(sc); + if (sc->t == '=') + token(sc); + conf_name(sc, '/', &d); + xo->root = xstrdup(d.buf); + dstr_destroy(&d); + CONF_ACCEPT; + } + + /* --- Set the target user id --- */ + + if (strcmp(sc->d.buf, "uid") == 0 || + strcmp(sc->d.buf, "user") == 0) { + token(sc); + if (sc->t == '=') + token(sc); + if (sc->t != CTOK_WORD) + error(sc, "parse error, expected user name or uid"); + if (isdigit((unsigned char)*sc->d.buf)) + xo->uid = atoi(sc->d.buf); + else { + struct passwd *pw = getpwnam(sc->d.buf); + if (!pw) + error(sc, "unknown user name `%s'", sc->d.buf); + xo->uid = pw->pw_uid; + } + token(sc); + CONF_ACCEPT; + } + + /* --- Set the target group id --- */ + + if (strcmp(sc->d.buf, "gid") == 0 || + strcmp(sc->d.buf, "group") == 0) { + token(sc); + if (sc->t == '=') + token(sc); + if (sc->t != CTOK_WORD) + error(sc, "parse error, expected group name or gid"); + if (isdigit((unsigned char)*sc->d.buf)) + xo->gid = atoi(sc->d.buf); + else { + struct group *gr = getgrnam(sc->d.buf); + if (!gr) + error(sc, "unknown user name `%s'", sc->d.buf); + xo->gid = gr->gr_gid; + } + token(sc); + CONF_ACCEPT; + } + + /* --- Now try resource limit settings --- */ + + if (rlimit_option(&xo->xl, sc)) + CONF_ACCEPT; + + /* --- And then environment settings --- */ + + if (xenv_option(xo, sc)) + CONF_ACCEPT; + + /* --- Nothing found --- */ + + CONF_END; +} + +/* --- @exec_desc@ --- */ + +static void exec_desc(xdata *x, dstr *d) +{ + char **p; + char sep = '['; + dstr_puts(d, "exec "); + if (strcmp(x->xa->file, x->xa->argv[0]) != 0) { + dstr_puts(d, x->xa->file); + dstr_putc(d, ' '); + } + for (p = x->xa->argv; *p; p++) { + dstr_putc(d, sep); + dstr_puts(d, *p); + sep = ' '; + } + dstr_putc(d, ']'); + dstr_putz(d); +} + +/* --- @exec_read@ --- */ + +static void exec_read(xdata *x, scanner *sc) +{ + size_t base = 0; + dstr d = DSTR_INIT; + xargs *xa; + + /* --- Read the first word --- * + * + * This is either a shell command or the actual program to run. + */ + + if (sc->t == CTOK_WORD) { + dstr_putd(&d, &sc->d); d.len++; + base = d.len; + token(sc); + } + + /* --- See if there's a list of arguments --- * + * + * If not, then the thing I saw was a shell command, so build the proper + * arguments for that. + */ + + if (sc->t != '[') { + char *p; + if (!base) + error(sc, "parse error, expected shell command or argument list"); + xa = xmalloc(XARGS_SZ(3) + 8 + 3 + d.len); + p = (char *)(xa->argv + 4); + xa->ref = 1; + xa->file = p; + xa->argv[0] = p; memcpy(p, "/bin/sh", 8); p += 8; + xa->argv[1] = p; memcpy(p, "-c", 3); p += 3; + xa->argv[2] = p; memcpy(p, d.buf, d.len); p += d.len; + xa->argv[3] = 0; + } + + /* --- Snarf in a list of arguments --- */ + + else { + int argc = 0; + char *p, *q; + char **v; + + /* --- Strip off the leading `[' --- */ + + token(sc); + + /* --- Read a sequence of arguments --- */ + + while (sc->t == CTOK_WORD) { + dstr_putd(&d, &sc->d); d.len++; + token(sc); + argc++; + } + + /* --- Expect the closing `]' --- */ + + if (sc->t != ']') + error(sc, "parse error, missing `]'"); + token(sc); + + /* --- If there are no arguments, whinge --- */ + + if (!argc) + error(sc, "must specify at least one argument"); + + /* --- Allocate a lump of memory for the array --- */ + + xa = xmalloc(XARGS_SZ(argc) + d.len); + xa->ref = 1; + v = xa->argv; + p = (char *)(v + argc + 1); + memcpy(p, d.buf, d.len); + q = p + d.len; + xa->file = p; + p += base; + + /* --- Start dumping addresses into the @argv@ array --- */ + + for (;;) { + *v++ = p; + while (*p++ && p < q) + ; + if (p >= q) + break; + } + *v++ = 0; + } + + /* --- Do some other setting up --- */ + + dstr_destroy(&d); + x->xa = xa; + x->xo = CREATE(xopts); + *x->xo = exec_opts; + x->xo->ref = 1; + return; +} + +/* --- @exec_endpt@ --- */ + +static endpt *exec_endpt(xdata *x, const char *desc) +{ + xept *xe = CREATE(xept); + xe->e.ops = &xept_ops; + xe->e.other = 0; + xe->e.t = 0; + xe->e.f = 0; + xe->xa = x->xa; xe->xa->ref++; + xe->xo = x->xo; xe->xo->ref++; + xe->kid = -1; + xe->desc = desc; + return (&xe->e); +} + +/* --- @exec_destroy@ --- */ + +static void exec_destroy(xdata *x) +{ + x_tidy(x->xa, x->xo); +} + +/*----- Source definition -------------------------------------------------*/ + +/* --- @option@ --- */ + +static int xsource_option(source *s, scanner *sc) +{ + xsource *xs = (xsource *)s; + return (exec_option(xs ? &xs->x : 0, sc)); +} + +/* --- @read@ --- */ + +static source *xsource_read(scanner *sc) +{ + xsource *xs; + + if (!conf_prefix(sc, "exec")) + return (0); + xs = CREATE(xsource); + xs->s.ops = &xsource_ops; + xs->s.desc = 0; + exec_read(&xs->x, sc); + return (&xs->s); +} + +/* --- @attach@ --- */ + +static void xsource_destroy(source */*s*/); + +static void xsource_attach(source *s, scanner *sc, target *t) +{ + xsource *xs = (xsource *)s; + endpt *e, *ee; + + /* --- Set up the source description string --- */ + + { + dstr d = DSTR_INIT; + exec_desc(&xs->x, &d); + dstr_puts(&d, " -> "); + dstr_puts(&d, t->desc); + xs->s.desc = xstrdup(d.buf); + dstr_destroy(&d); + } + + /* --- Create the endpoints --- */ + + if ((ee = t->ops->create(t, xs->s.desc)) == 0) + goto tidy; + if ((e = exec_endpt(&xs->x, xs->s.desc)) == 0) { + ee->ops->close(ee); + goto tidy; + } + endpt_join(e, ee); + + /* --- Dispose of source and target --- */ + +tidy: + t->ops->destroy(t); + xsource_destroy(&xs->s); +} + +/* --- @destroy@ --- */ + +static void xsource_destroy(source *s) +{ + xsource *xs = (xsource *)s; + exec_destroy(&xs->x); + DESTROY(xs); +} + +/* --- Executable source operations --- */ + +source_ops xsource_ops = { + "exec", + xsource_option, xsource_read, xsource_attach, xsource_destroy +}; + +/*----- Exec target description -------------------------------------------*/ + +/* --- @option@ --- */ + +static int xtarget_option(target *t, scanner *sc) +{ + xtarget *xt = (xtarget *)t; + return (exec_option(xt ? &xt->x : 0, sc)); +} + +/* --- @read@ --- */ + +static target *xtarget_read(scanner *sc) +{ + xtarget *xt; + dstr d = DSTR_INIT; + + if (!conf_prefix(sc, "exec")) + return (0); + xt = CREATE(xtarget); + xt->t.ops = &xtarget_ops; + exec_read(&xt->x, sc); + exec_desc(&xt->x, &d); + xt->t.desc = xstrdup(d.buf); + dstr_destroy(&d); + return (&xt->t); +} + +/* --- @create@ --- */ + +static endpt *xtarget_create(target *t, const char *desc) +{ + xtarget *xt = (xtarget *)t; + endpt *e = exec_endpt(&xt->x, desc); + return (e); +} + +/* --- @destroy@ --- */ + +static void xtarget_destroy(target *t) +{ + xtarget *xt = (xtarget *)t; + exec_destroy(&xt->x); + DESTROY(xt); +} + +/* --- Exec target operations --- */ + +target_ops xtarget_ops = { + "exec", + xtarget_option, xtarget_read, xtarget_create, xtarget_destroy +}; + +/*----- That's all, folks -------------------------------------------------*/