+static void remote_fail(struct tvec_output *o,
+ const char *detail, va_list *ap)
+{
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_FAIL)
+ if (!detail)
+ dbuf_putbyte(&srvrc.bout, 0);
+ else {
+ dbuf_putbyte(&srvrc.bout, 1);
+ dbuf_vputstrf16l(&srvrc.bout, detail, ap);
+ }
+}
+
+/* --- @remote_dumpreg@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink (ignored)
+ * @unsigned disp@ = register disposition
+ * @const union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns: ---
+ *
+ * Use: Dump a register.
+ *
+ * The remote driver sends a @TVPK_DUMPREG@ packet to its
+ * client. This will only work if the register definition is
+ * one of those listed in the current test definition.
+ */
+
+static void remote_dumpreg(struct tvec_output *o,
+ unsigned disp, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const struct tvec_regdef *reg;
+ unsigned r;
+
+ /* Find the register definition. */
+ for (reg = srvtv.test->regs, r = 0; reg->name; reg++, r++)
+ if (reg == rd) goto found;
+ assert(!"unexpected register definition");
+
+found:
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_DUMPREG) {
+ dbuf_putu16l(&srvrc.bout, r);
+ dbuf_putu16l(&srvrc.bout, disp);
+ if (!rv)
+ dbuf_putbyte(&srvrc.bout, 0);
+ else {
+ dbuf_putbyte(&srvrc.bout, 1);
+ rd->ty->tobuf(DBUF_BUF(&srvrc.bout), rv, rd);
+ }
+ }
+}
+
+/* --- @remote_etest@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink (ignored)
+ * @unsigned outcome@ = the test outcome
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test has finished.
+ *
+ * The remote driver does nothing at all.
+ */
+
+static void remote_etest(struct tvec_output *o, unsigned outcome)
+ { ; }
+
+/* --- @remote_bbench@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink (ignored)
+ * @const char *ident@ = identifying register values
+ * @unsigned unit@ = measurement unit (@TVBU_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Report that a benchmark has started.
+ *
+ * The remote driver sends a @TVPK_BBENCH@ packet to its client.
+ */
+
+static void remote_bbench(struct tvec_output *o,
+ const char *ident, unsigned unit)
+{
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_BBENCH) {
+ dbuf_putstr32l(&srvrc.bout, ident);
+ dbuf_putu16l(&srvrc.bout, unit);
+ }
+}
+
+/* --- @remote_ebench@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink (ignored)
+ * @const char *ident@ = identifying register values
+ * @unsigned unit@ = measurement unit (@TVBU_...@)
+ * @const struct bench_timing *tm@ = measurement
+ *
+ * Returns: ---
+ *
+ * Use: Report a benchmark's results
+ *
+ * The remote driver sends a @TVPK_EBENCH@ packet to its client.
+ */
+
+static void remote_ebench(struct tvec_output *o,
+ const char *ident, unsigned unit,
+ const struct bench_timing *t)
+{
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_EBENCH) {
+ dbuf_putstr32l(&srvrc.bout, ident);
+ dbuf_putu16l(&srvrc.bout, unit);
+ if (!t || !(t->f&BTF_ANY))
+ dbuf_putu16l(&srvrc.bout, 0);
+ else {
+ dbuf_putu16l(&srvrc.bout, t->f);
+ dbuf_putf64l(&srvrc.bout, t->n);
+ if (t->f&BTF_TIMEOK) dbuf_putf64l(&srvrc.bout, t->t);
+ if (t->f&BTF_CYOK) dbuf_putf64l(&srvrc.bout, t->cy);
+ }
+ }
+}
+
+/* --- @remote_report@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink (ignored)
+ * @unsigned level@ = message level (@TVLEV_...@)
+ * @const char *msg@, @va_list *ap@ = format string and
+ * arguments
+ *
+ * Returns: ---
+ *
+ * Use: Report a message to the user.
+ *
+ * The remote driver sends a @TVPK_REPORT@ packet to its
+ * client. If its attempt to transmit the packet fails, then
+ * the message is written to the standard error stream instead,
+ * in the hope that this will help it be noticed.
+ */
+
+static void remote_report(struct tvec_output *o, unsigned level,
+ const char *msg, va_list *ap)
+{
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_REPORT) {
+ dbuf_putu16l(&srvrc.bout, level);
+ dbuf_vputstrf16l(&srvrc.bout, msg, ap);
+ } else {
+ fprintf(stderr, "%s %s: ", QUIS, tvec_strlevel(level));
+ vfprintf(stderr, msg, *ap);
+ fputc('\n', stderr);
+ }
+}
+
+/* --- @remote_destroy@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink (ignored)
+ *
+ * Returns: ---
+ *
+ * Use: Release the resources held by the output driver.
+ *
+ * The remote driver does nothing at all.
+ */
+
+static void remote_destroy(struct tvec_output *o)
+ { ; }
+
+static const struct tvec_outops remote_ops = {
+ remote_bsession, remote_esession,
+ remote_bgroup, remote_skipgroup, remote_egroup,
+ remote_btest, remote_skip, remote_fail, remote_dumpreg, remote_etest,
+ remote_bbench, remote_ebench,
+ remote_report,
+ remote_destroy
+};
+
+/*----- Pseudoregister definitions ----------------------------------------*/
+
+static tvec_setvarfn setvar_local, setvar_remote;
+
+static const struct tvec_flag exit_flags[] = {
+
+ /* Cause codes. */
+ { "running", TVXF_CAUSEMASK, TVXST_RUN },
+ { "exited", TVXF_CAUSEMASK, TVXST_EXIT },
+ { "killed", TVXF_CAUSEMASK, TVXST_KILL },
+ { "stopped", TVXF_CAUSEMASK, TVXST_STOP },
+ { "continued", TVXF_CAUSEMASK, TVXST_CONT },
+ { "disconnected", TVXF_CAUSEMASK, TVXST_DISCONN },
+ { "unknown", TVXF_CAUSEMASK, TVXST_UNK },
+ { "error", TVXF_CAUSEMASK, TVXST_ERR },
+
+ /*
+ ;;; The signal name table is very boring to type. To make life less
+ ;;; awful, put the signal names in this list and evaluate the code to
+ ;;; get Emacs to regenerate it.
+
+ (let ((signals '(HUP INT QUIT ILL TRAP ABRT IOT EMT FPE KILL BUS SEGV SYS
+ PIPE ALRM TERM URG STOP TSTP CONT CHLD CLD TTIN TTOU
+ POLL IO TIN XCPU XFSZ VTALRM PROF WINCH USR1 USR2
+ STKFLT INFO PWR THR LWP LIBRT LOST)))
+ (save-excursion
+ (goto-char (point-min))
+ (search-forward (concat "***" "BEGIN siglist" "***"))
+ (beginning-of-line 2)
+ (delete-region (point)
+ (progn
+ (search-forward "***END***")
+ (beginning-of-line)
+ (point)))
+ (dolist (sig signals)
+ (insert (format "#ifdef SIG%s\n { \"SIG%s\", TVXF_VALMASK | TVXF_SIG, SIG%s | TVXF_SIG },\n#endif\n"
+ sig sig sig)))))
+ */
+
+ /***BEGIN siglist***/
+#ifdef SIGHUP
+ { "SIGHUP", TVXF_VALMASK | TVXF_SIG, SIGHUP | TVXF_SIG },
+#endif
+#ifdef SIGINT
+ { "SIGINT", TVXF_VALMASK | TVXF_SIG, SIGINT | TVXF_SIG },
+#endif
+#ifdef SIGQUIT
+ { "SIGQUIT", TVXF_VALMASK | TVXF_SIG, SIGQUIT | TVXF_SIG },
+#endif
+#ifdef SIGILL
+ { "SIGILL", TVXF_VALMASK | TVXF_SIG, SIGILL | TVXF_SIG },
+#endif
+#ifdef SIGTRAP
+ { "SIGTRAP", TVXF_VALMASK | TVXF_SIG, SIGTRAP | TVXF_SIG },
+#endif
+#ifdef SIGABRT
+ { "SIGABRT", TVXF_VALMASK | TVXF_SIG, SIGABRT | TVXF_SIG },
+#endif
+#ifdef SIGIOT
+ { "SIGIOT", TVXF_VALMASK | TVXF_SIG, SIGIOT | TVXF_SIG },
+#endif
+#ifdef SIGEMT
+ { "SIGEMT", TVXF_VALMASK | TVXF_SIG, SIGEMT | TVXF_SIG },
+#endif
+#ifdef SIGFPE
+ { "SIGFPE", TVXF_VALMASK | TVXF_SIG, SIGFPE | TVXF_SIG },
+#endif
+#ifdef SIGKILL
+ { "SIGKILL", TVXF_VALMASK | TVXF_SIG, SIGKILL | TVXF_SIG },
+#endif
+#ifdef SIGBUS
+ { "SIGBUS", TVXF_VALMASK | TVXF_SIG, SIGBUS | TVXF_SIG },
+#endif
+#ifdef SIGSEGV
+ { "SIGSEGV", TVXF_VALMASK | TVXF_SIG, SIGSEGV | TVXF_SIG },
+#endif
+#ifdef SIGSYS
+ { "SIGSYS", TVXF_VALMASK | TVXF_SIG, SIGSYS | TVXF_SIG },
+#endif
+#ifdef SIGPIPE
+ { "SIGPIPE", TVXF_VALMASK | TVXF_SIG, SIGPIPE | TVXF_SIG },
+#endif
+#ifdef SIGALRM
+ { "SIGALRM", TVXF_VALMASK | TVXF_SIG, SIGALRM | TVXF_SIG },
+#endif
+#ifdef SIGTERM
+ { "SIGTERM", TVXF_VALMASK | TVXF_SIG, SIGTERM | TVXF_SIG },
+#endif
+#ifdef SIGURG
+ { "SIGURG", TVXF_VALMASK | TVXF_SIG, SIGURG | TVXF_SIG },
+#endif
+#ifdef SIGSTOP
+ { "SIGSTOP", TVXF_VALMASK | TVXF_SIG, SIGSTOP | TVXF_SIG },
+#endif
+#ifdef SIGTSTP
+ { "SIGTSTP", TVXF_VALMASK | TVXF_SIG, SIGTSTP | TVXF_SIG },
+#endif
+#ifdef SIGCONT
+ { "SIGCONT", TVXF_VALMASK | TVXF_SIG, SIGCONT | TVXF_SIG },
+#endif
+#ifdef SIGCHLD
+ { "SIGCHLD", TVXF_VALMASK | TVXF_SIG, SIGCHLD | TVXF_SIG },
+#endif
+#ifdef SIGCLD
+ { "SIGCLD", TVXF_VALMASK | TVXF_SIG, SIGCLD | TVXF_SIG },
+#endif
+#ifdef SIGTTIN
+ { "SIGTTIN", TVXF_VALMASK | TVXF_SIG, SIGTTIN | TVXF_SIG },
+#endif
+#ifdef SIGTTOU
+ { "SIGTTOU", TVXF_VALMASK | TVXF_SIG, SIGTTOU | TVXF_SIG },
+#endif
+#ifdef SIGPOLL
+ { "SIGPOLL", TVXF_VALMASK | TVXF_SIG, SIGPOLL | TVXF_SIG },
+#endif
+#ifdef SIGIO
+ { "SIGIO", TVXF_VALMASK | TVXF_SIG, SIGIO | TVXF_SIG },
+#endif
+#ifdef SIGTIN
+ { "SIGTIN", TVXF_VALMASK | TVXF_SIG, SIGTIN | TVXF_SIG },
+#endif
+#ifdef SIGXCPU
+ { "SIGXCPU", TVXF_VALMASK | TVXF_SIG, SIGXCPU | TVXF_SIG },
+#endif
+#ifdef SIGXFSZ
+ { "SIGXFSZ", TVXF_VALMASK | TVXF_SIG, SIGXFSZ | TVXF_SIG },
+#endif
+#ifdef SIGVTALRM
+ { "SIGVTALRM", TVXF_VALMASK | TVXF_SIG, SIGVTALRM | TVXF_SIG },
+#endif
+#ifdef SIGPROF
+ { "SIGPROF", TVXF_VALMASK | TVXF_SIG, SIGPROF | TVXF_SIG },
+#endif
+#ifdef SIGWINCH
+ { "SIGWINCH", TVXF_VALMASK | TVXF_SIG, SIGWINCH | TVXF_SIG },
+#endif
+#ifdef SIGUSR1
+ { "SIGUSR1", TVXF_VALMASK | TVXF_SIG, SIGUSR1 | TVXF_SIG },
+#endif
+#ifdef SIGUSR2
+ { "SIGUSR2", TVXF_VALMASK | TVXF_SIG, SIGUSR2 | TVXF_SIG },
+#endif
+#ifdef SIGSTKFLT
+ { "SIGSTKFLT", TVXF_VALMASK | TVXF_SIG, SIGSTKFLT | TVXF_SIG },
+#endif
+#ifdef SIGINFO
+ { "SIGINFO", TVXF_VALMASK | TVXF_SIG, SIGINFO | TVXF_SIG },
+#endif
+#ifdef SIGPWR
+ { "SIGPWR", TVXF_VALMASK | TVXF_SIG, SIGPWR | TVXF_SIG },
+#endif
+#ifdef SIGTHR
+ { "SIGTHR", TVXF_VALMASK | TVXF_SIG, SIGTHR | TVXF_SIG },
+#endif
+#ifdef SIGLWP
+ { "SIGLWP", TVXF_VALMASK | TVXF_SIG, SIGLWP | TVXF_SIG },
+#endif
+#ifdef SIGLIBRT
+ { "SIGLIBRT", TVXF_VALMASK | TVXF_SIG, SIGLIBRT | TVXF_SIG },
+#endif
+#ifdef SIGLOST
+ { "SIGLOST", TVXF_VALMASK | TVXF_SIG, SIGLOST | TVXF_SIG },
+#endif
+ /***END***/
+
+ /* This should be folded into the signal entries above. */
+ { "signal", TVXF_SIG, TVXF_SIG },
+
+ TVEC_ENDFLAGS
+};
+static const struct tvec_flaginfo exit_flaginfo =
+ { "exit-status", exit_flags, &tvrange_uint };
+static const struct tvec_vardef exit_var =
+ { sizeof(struct tvec_reg), setvar_local,
+ { "@exit", -1, &tvty_flags, 0, { &exit_flaginfo } } };
+
+/* Progress. */
+
+static const struct tvec_vardef progress_var =
+ { sizeof(struct tvec_reg), setvar_local,
+ { "@progress", -1, &tvty_text, 0 } };
+
+/* Reconnection. */
+
+static const struct tvec_uassoc reconn_assocs[] = {
+ { "on-demand", TVRCN_DEMAND },
+ { "force", TVRCN_FORCE },
+ { "skip", TVRCN_SKIP },
+ TVEC_ENDENUM
+};
+static const struct tvec_uenuminfo reconn_enuminfo =
+ { "remote-reconnection", reconn_assocs, &tvrange_uint };
+static const struct tvec_vardef reconn_var =
+ { sizeof(struct tvec_reg), setvar_local,
+ { "@reconnect", -1, &tvty_uenum, 0, { &reconn_enuminfo } } };
+
+/*----- Client ------------------------------------------------------------*/
+
+/* Connection state. */
+enum {
+ CONN_BROKEN = -2, /* previously broken */
+ CONN_FAILED = -1, /* attempt freshly failed */
+ CONN_ESTABLISHED = 0, /* previously established */
+ CONN_FRESH = 1 /* freshly connected */
+};
+
+/* --- @handle_packets@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotectx *r@ = remote client context
+ * @unsigned f@ = receive flags (@RCVF_...@)
+ * @uint16 end@ = expected end packet type
+ * @buf *b_out@ = buffer in which to return end packet payload
+ *
+ * Returns: A @RECV_...@ code.
+ *
+ * Use: Handles notification packets from the server until a final
+ * termination packet is received.
+ *
+ * The client/server protocol consists of a number of flows,
+ * beginning with a request from the client, followed by a
+ * number of notifications from the server, and terminated by an
+ * acknowledgement to the original request indicating that the
+ * server has completed acting on the original request.
+ *
+ * This function handles the notifications issued by the server,
+ * returning when one of the following occurs: (a) a packet of
+ * type @end@ is received, in which case the function returns
+ * @RECV_OK@ and the remainder of the packet payload is left in
+ * @b_out@; (b) the flag @RCVF_ALLOWEOF@ was set in @f@ on entry
+ * and end-of-file is received at a packet boundary, in which
+ * case the function returns @RECV_EOF@; or (c) an I/O error
+ * occurs, in which case @ioerr@ is called and the function
+ * returns @RECV_FAIL@.
+ */
+
+static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
+ unsigned f, uint16 end, buf *b_out)
+{
+ struct tvec_output *o = tv->output;
+ uint16 pk, u, v;
+ const char *p; size_t n;
+ dstr d = DSTR_INIT;
+ buf *b = b_out;
+ const struct tvec_regdef *rd;
+ struct bench_timing bt;
+ struct tvec_reg *reg = 0;
+ unsigned i;
+ int rc;
+
+ for (;;) {
+
+ /* Read the next packet. If we didn't receive one then end the loop.
+ * Otherwise, retrieve the packet type and check it against @end@: quit
+ * the loop if we get a match.
+ */
+ rc = remote_recv(tv, &r->rc, f, b); if (rc) break;
+ if (buf_getu16l(b, &pk)) goto bad;
+ if (pk == end) { rc = 0; break; }
+
+ /* Dispatch based on the packet type. */
+ switch (pk) {
+
+ case TVPK_PROGRESS:
+ /* A progress report. Update the saved progress. */
+
+ p = buf_getmem16l(b, &n); if (!p) goto bad;
+ if (BLEFT(b)) goto bad;
+
+ DRESET(&r->progress); DPUTM(&r->progress, p, n); DPUTZ(&r->progress);
+ break;
+
+ case TVPK_REPORT:
+ /* A report. Recover the message and pass it along. */
+
+ if (buf_getu16l(b, &u)) goto bad;
+ p = buf_getmem16l(b, &n); if (!p) goto bad;
+ if (BLEFT(b)) goto bad;
+
+ DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+ tvec_report(tv, u, "%s", d.buf);
+ break;
+
+ case TVPK_SKIPGRP:
+ /* A request to skip the group. Recover the excuse message and pass
+ * it along.
+ */
+
+ p = buf_getmem16l(b, &n); if (!p) goto bad;
+ if (BLEFT(b)) goto bad;
+
+ DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+ tvec_skipgroup(tv, "%s", d.buf);
+ break;
+
+ case TVPK_SKIP:
+ /* A request to skip the test. Recover the excuse message and pass
+ * it along, if it's not unreasonable.
+ */
+
+ if (!(tv->f&TVSF_ACTIVE)) {
+ rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name);
+ goto end;
+ }
+
+ p = buf_getmem16l(b, &n); if (!p) goto bad;
+ if (BLEFT(b)) goto bad;
+
+ DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+ tvec_skip(tv, "%s", d.buf);
+ break;
+
+ case TVPK_FAIL:
+ /* A report that the test failed. Recover the detail message, if
+ * any, and pass it along, if it's not unreasonable.
+ */
+
+ if (!(tv->f&TVSF_ACTIVE) &&
+ ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) {
+ rc = ioerr(tv, &r->rc, "test `%s' not active or failing",
+ tv->test->name);
+ goto end;
+ }
+
+ rc = buf_getbyte(b); if (rc < 0) goto bad;
+ if (rc) { p = buf_getmem16l(b, &n); if (!p) goto bad; }
+ else p = 0;
+ if (BLEFT(b)) goto bad;
+
+ if (!p)
+ tvec_fail(tv, 0);
+ else {
+ DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+ tvec_fail(tv, "%s", d.buf);
+ }
+ break;
+
+ case TVPK_DUMPREG:
+ /* A request to dump a register. */
+
+ /* Find the register definition. */
+ if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
+ for (rd = tv->test->regs, i = 0; rd->name; rd++, i++)
+ if (i == u) goto found_reg;
+ rc = ioerr(tv, &r->rc,
+ "register definition %u out of range for test `%s'",
+ u, tv->test->name);
+ goto end;
+ found_reg:
+ if (v >= TVRD_LIMIT) {
+ rc = ioerr(tv, &r->rc, "register disposition %u out of range", v);
+ goto end;
+ }
+
+ /* Read the flag. If there's no register value, then `dump' its
+ * absence. Otherwise retrieve the register value and dump it.
+ */
+ rc = buf_getbyte(b); if (rc < 0) goto bad;
+ if (!rc)
+ tvec_dumpreg(tv, v, 0, rd);
+ else {
+ if (!reg) reg = xmalloc(tv->regsz);
+ rd->ty->init(®->v, rd);
+ rc = rd->ty->frombuf(b, ®->v, rd);
+ if (!rc) tvec_dumpreg(tv, v, ®->v, rd);
+ rd->ty->release(®->v, rd);
+ if (rc) goto bad;
+ }
+ if (BLEFT(b)) goto bad;
+ break;
+
+ case TVPK_BBENCH:
+ /* A report that we're starting a benchmark. Pass this along. */
+
+ p = buf_getmem32l(b, &n); if (!p) goto bad;
+ if (buf_getu16l(b, &u)) goto bad;
+ if (BLEFT(b)) goto bad;
+ if (u >= TVBU_LIMIT) {
+ rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
+ goto end;
+ }
+
+ DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+ o->ops->bbench(o, d.buf, u);
+ break;
+
+ case TVPK_EBENCH:
+ /* A report that a benchmark completed. Pass this along. */
+
+ p = buf_getmem32l(b, &n); if (!p) goto bad;
+ if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
+ if (u >= TVBU_LIMIT) {
+ rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
+ goto end;
+ }
+ if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad;
+ if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad;
+ if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad;
+ if (BLEFT(b)) goto bad;
+
+ DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+ o->ops->ebench(o, d.buf, u, v&BTF_ANY ? &bt : 0);
+ break;
+
+ default:
+ /* Something else. This is unexpected. */
+
+ rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
+ goto end;
+ }
+ }
+
+end:
+ DDESTROY(&d);
+ xfree(reg);
+ return (rc);
+bad:
+ rc = malformed(tv, &r->rc); goto end;
+}
+
+/* --- @reap_kid@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns: ---
+ *
+ * Use: Determine the exit status of a broken connection, setting
+ * @r->exit@ appropriately.
+ *
+ * If @r->kid@ is negative, the exit status has already been
+ * set, and nothing further happens; this is not an error.
+ *
+ * If @r->kid@ is zero, then there is no real child process
+ * (e.g., because the remote connection is a network connection
+ * or similar), so @r->exit@ is set equal to @RVXST_DISCONN@.
+ *
+ * If @r->kid@ is positive, then it holds a child process id;
+ * the function waits for it to end and collects its exit status
+ *
+ * It is an error to call this function if the connection is not
+ * broken.
+ */
+
+static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+ pid_t kid;
+ int st;
+
+ assert(r->rc.f&TVRF_BROKEN);
+ if (!r->kid)
+ { r->exit = TVXST_DISCONN; r->kid = -1; }
+ else if (r->kid > 0) {
+ kid = waitpid(r->kid, &st, 0);
+ if (kid < 0) {
+ tvec_notice(tv, "failed to wait for remote child: %s",
+ strerror(errno));
+ r->exit = TVXST_ERR;
+ } else if (!kid) {
+ tvec_notice(tv, "remote child vanished without a trace");
+ r->exit = TVXST_ERR;
+ } else if (WIFCONTINUED(st))
+ r->exit = TVXST_CONT;
+ else if (WIFSIGNALED(st))
+ r->exit = TVXST_KILL | TVXF_SIG | WTERMSIG(st);
+ else if (WIFSTOPPED(st))
+ r->exit = TVXST_STOP | TVXF_SIG | WSTOPSIG(st);
+ else if (WIFEXITED(st))
+ r->exit = TVXST_EXIT | WEXITSTATUS(st);
+ else {
+ tvec_notice(tv, "remote child died with unknown status 0x%04x",
+ (unsigned)st);
+ r->exit = TVXST_UNK;
+ }
+ r->kid = -1;
+ }
+}
+
+/* --- @report_errline@ --- *
+ *
+ * Arguments: @char *p@ = pointer to the line
+ * @size_t n@ = length in characters
+ * @void *ctx@ = context, secretly a @struct tvec_remotectx@
+ *
+ * Returns: ---
+ *
+ * Use: Print a line of stderr output from the child. If
+ * @TVRF_MUFFLE@ is set, then discard the line silently.
+ *
+ * This is an @lbuf_func@, invoked via @drain_errfd@.
+ */
+
+static void report_errline(char *p, size_t n, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ struct tvec_state *tv = r->tv;
+
+ if (p && !(r->rc.f&TVRF_MUFFLE))
+ tvec_notice(tv, "child process stderr: %s", p);
+}
+
+/* --- @drain_errfd@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotectx *r@ = remote client context
+ * @unsigned f@ = receive flags (@ERF_...@)
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Collect material written by the child to its stderr stream
+ * and report it.
+ *
+ * If @f@ has @ERF_SILENT@ set, then discard the stderr material
+ * without reporting it. Otherwise it is reported as
+ * @TVLEV_NOTE@.
+ *
+ * if @f@ has @ERF_CLOSE@ set, then continue reading until
+ * end-of-file is received; also, report any final partial line,
+ * and close @r->errfd@.
+ *
+ * If @r->errfd@ is already closed, or never established, then
+ * do nothing and return successfully.
+ */
+
+#define ERF_SILENT 0x0001u
+#define ERF_CLOSE 0x0002u
+static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
+ unsigned f)
+{
+ char *p; size_t sz;
+ ssize_t n;
+ int rc;
+
+ /* Preliminaries. Bail if there is no error stream to fetch. Arrange
+ * (rather clumsily) to muffle the output if we're supposed to be client.
+ * And set the nonblocking state on @errfd@ appropriately.
+ */
+ if (r->errfd < 0) { rc = 0; goto end; }
+ if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
+ else r->rc.f &= ~TVRF_MUFFLE;
+ if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) {
+ rc = ioerr(tv, &r->rc, "failed to %s error non-blocking flag",
+ f&ERF_CLOSE ? "clear" : "set");
+ goto end;
+ }
+
+ /* Read pieces of error output and feed them into the line buffer. */
+ for (;;) {
+ sz = lbuf_free(&r->errbuf, &p);
+ n = read(r->errfd, p, sz);
+ if (!n) break;
+ if (n < 0) {
+ if (errno == EINTR) continue;
+ if (!(f&ERF_CLOSE) && (errno == EWOULDBLOCK || errno == EAGAIN))
+ break;
+ rc = ioerr(tv, &r->rc, "failed to read child stderr: %s",
+ strerror(errno));
+ goto end;
+ }
+ lbuf_flush(&r->errbuf, p, n);
+ }
+
+ /* Done. */
+ rc = 0;
+end:
+ if (f&ERF_CLOSE) {
+ lbuf_close(&r->errbuf);
+ close(r->errfd); r->errfd = -1;
+ }
+ return (rc);
+}
+
+/* --- @disconnect_remote@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotectx *r@ = remote client context
+ * @unsigned f@ = receive flags (@DCF_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Disconnect and shut down all of the remote client state.
+ *
+ * If @f@ has @DCF_KILL@ set then send the child process (if
+ * any) @SIGTERM@ to make sure it shuts down in a timely manner.
+ *
+ * In detail: this function closes the @infd@ and @outfd@
+ * descriptors, drains and closes @errfd@, and collects the exit
+ * status (if any).
+ */
+
+#define DCF_KILL 0x0100u
+static void disconnect_remote(struct tvec_state *tv,
+ struct tvec_remotectx *r, unsigned f)
+{
+ if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
+ close_comms(&r->rc);
+ drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
+}
+
+/* --- @connect_remote@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Connect to the test server.
+ */
+
+static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+ const struct tvec_remoteenv *re = r->re;
+ pid_t kid = 0;
+ buf b;
+ uint16 v;
+ int infd = -1, outfd = -1, errfd = -1, rc;
+
+ /* If we're already connected, then there's nothing to do. */
+ if (r->kid >= 0) { rc = 0; goto end; }
+
+ /* Set the preliminary progress indication. */
+ DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
+
+ /* Call the connection function to establish descriptors. */
+ if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re))
+ { rc = -1; goto end; }
+
+ /* Establish communications state. */
+ setup_comms(&r->rc, infd, outfd); r->kid = kid; r->errfd = errfd;
+ lbuf_init(&r->errbuf, report_errline, r);
+ r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN;
+
+ /* Do version negotiation. */
+ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) {
+ dbuf_putu16l(&r->rc.bout, 0);
+ dbuf_putu16l(&r->rc.bout, 0);
+ } else { rc = -1; goto end; }
+ if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b))
+ { rc = -1; goto end; }
+ if (buf_getu16l(&b, &v)) goto bad;
+ if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
+ if (v) {
+ rc = ioerr(tv, &r->rc, "protocol version %u not supported", v);
+ goto end;
+ }
+ r->ver = v;
+
+ /* Begin the test group at the server. */
+ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP)
+ dbuf_putstr16l(&r->rc.bout, tv->test->name);
+ else { rc = -1; goto end; }
+ if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b))
+ { rc = -1; goto end; }
+ if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
+
+ /* Done. */
+ rc = 0;
+end:
+ if (rc) disconnect_remote(tv, r, DCF_KILL);
+ return (rc);
+bad:
+ rc = malformed(tv, &r->rc); goto end;
+}
+
+/* --- @check_comms@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns: A @CONN_...@ code reflecting the current communication
+ * state.
+ *
+ * Use: Determine the current connection state. If the connection
+ * has recently broken (i.e., @TVRF_BROKEN@ is set in @r->rc.f@)
+ * since the last time we checked then disconnect.
+ */
+
+static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+ if (r->kid < 0)
+ return (CONN_BROKEN);
+ else if (r->rc.f&TVRF_BROKEN)
+ { disconnect_remote(tv, r, DCF_KILL); return (CONN_FAILED); }
+ else
+ return (CONN_ESTABLISHED);
+}
+
+/* --- @try_reconnect@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns: A @CONN_...@ code reflecting the new communication state.
+ *
+ * Use: Reconnects to the server according to the configured
+ * @TVRCN_...@ policy.
+ */
+
+static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+ int rc;
+
+ switch (r->rc.f&TVRF_RCNMASK) {
+ case TVRCN_DEMAND:
+ rc = check_comms(tv, r);
+ if (rc < CONN_ESTABLISHED) {
+ close_comms(&r->rc);
+ if (connect_remote(tv, r)) rc = CONN_FAILED;
+ else rc = CONN_FRESH;
+ }
+ break;
+ case TVRCN_FORCE:
+ disconnect_remote(tv, r, DCF_KILL);
+ if (connect_remote(tv, r)) rc = CONN_FAILED;
+ else rc = CONN_FRESH;
+ break;
+ case TVRCN_SKIP:
+ rc = check_comms(tv, r);
+ break;
+ default:
+ abort();
+ }
+ return (rc);
+}
+
+/*----- Remote environment ------------------------------------------------*/
+
+/* --- @reset_vars@ --- *
+ *
+ * Arguments: @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns: ---
+ *
+ * Use: Reset the pseudoregisters set through @tvec_remoteset@.
+ */
+
+static void reset_vars(struct tvec_remotectx *r)
+{
+ const struct tvec_remoteenv *re = r->re;
+
+ r->exwant = TVXST_RUN;
+ r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) |
+ (re->r.dflt_reconn&TVRF_RCNMASK);
+ DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
+}
+
+/* --- @tvec_remotesetup@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const struct tvec_env *env@ = environment description
+ * @void *pctx@ = parent context (ignored)
+ * @void *ctx@ = context pointer to initialize
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a timeout environment context.
+ *
+ * The environment description should be a @struct
+ * tvec_remoteenv@ subclass suitable for use by the @connect@
+ * function.
+ */
+
+void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
+ void *pctx, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ const struct tvec_remoteenv *re = (const struct tvec_remoteenv *)env;
+ const struct tvec_env *subenv = re->r.env;
+
+ r->tv = tv;
+ init_comms(&r->rc);
+ r->re = re; r->kid = -1;
+ DCREATE(&r->prgwant); DCREATE(&r->progress);
+ if (connect_remote(tv, r))
+ tvec_skipgroup(tv, "failed to connect to test backend");
+ reset_vars(r);
+ if (subenv && subenv->ctxsz) r->subctx = xmalloc(subenv->ctxsz);
+ else r->subctx = 0;
+ if (subenv && subenv->setup) subenv->setup(tv, subenv, r, r->subctx);
+}
+
+/* --- @tvec_remotefindvar@, @setvar_local@, @setvar_remote@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *var@ = variable name to set
+ * @const union tvec_regval *rv@ = register value
+ * @void **ctx_out@ = where to put the @setvar@ context
+ * @void *ctx@ = context pointer
+ *
+ * Returns: @tvec_remotefindvar@ returns a pointer to the variable
+ * definition, or null; @remote_setvar@ returns zero on success
+ * or %$-1$% on error.
+ *
+ * Use: Set a special variable. The following special variables are
+ * supported.
+ *
+ * * %|@exit|% is the expected exit status; see @TVXF_...@ and
+ * @TVXST_...@.
+ *
+ * * %|progress|% is the expected progress token when the test
+ * completes. On successful completion, this will be
+ * %|%DONE|%; it's %|%RUN|% on entry to the test function,
+ * but that can call @tvec_setprogress@ to change it.
+ *
+ * * %|reconnect|% is a reconnection policy; see @TVRCN_...@.
+ */
+
+static int setvar_local(struct tvec_state *tv, const char *var,
+ const union tvec_regval *rv, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+
+ if (STRCMP(var, ==, "@exit")) {
+ if (r->rc.f&TVRF_SETEXIT) return (tvec_dupreg(tv, var));
+ r->exwant = rv->u; r->rc.f |= TVRF_SETEXIT; return (0);
+ } else if (STRCMP(var, ==, "@progress")) {
+ if (r->rc.f&TVRF_SETPRG) return (tvec_dupreg(tv, var));
+ DRESET(&r->prgwant); DPUTM(&r->prgwant, rv->text.p, rv->text.sz);
+ DPUTZ(&r->prgwant);
+ r->rc.f |= TVRF_SETPRG; return (0);
+ } else if (STRCMP(var, ==, "@reconnect")) {
+ if (r->rc.f&TVRF_SETRCN) return (tvec_dupreg(tv, var));
+ r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv->u&TVRF_RCNMASK) | TVRF_SETRCN;
+ return (0);
+ } else assert(!"unknown var");
+}
+
+static int setvar_remote(struct tvec_state *tv, const char *var,
+ const union tvec_regval *rv, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ buf b;
+ int ch, rc;
+
+ if (try_reconnect(tv, r) < 0) { rc = 0; goto end; }
+
+ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_SETVAR) {
+ dbuf_putstr16l(&r->rc.bout, var);
+ r->vd.def.ty->tobuf(DBUF_BUF(&r->rc.bout), rv, &r->vd.def);
+ } else { rc = -1; goto end; }
+
+ rc = handle_packets(tv, r, 0, TVPK_SETVAR | TVPF_ACK, &b);
+ if (rc) goto end;
+ ch = buf_getbyte(&b);
+ if (ch < 0) { rc = malformed(tv, &r->rc); goto end; }
+ if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
+
+ rc = ch ? -1 : 0;
+end:
+ return (rc);
+}
+
+const struct tvec_vardef *tvec_remotefindvar
+ (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ const struct tvec_remoteenv *re = r->re;
+ const struct tvec_env *subenv = re->r.env;
+ const struct tvec_vardef *vd; void *varctx;
+
+ if (STRCMP(var, ==, "@exit"))
+ { *ctx_out = r; return (&exit_var); }
+ else if (STRCMP(var, ==, "@progress"))
+ { *ctx_out = r; return (&progress_var); }
+ else if (STRCMP(var, ==, "@reconnect"))
+ { *ctx_out = r; return (&reconn_var); }
+ else if (subenv && subenv->findvar) {
+ vd = subenv->findvar(tv, var, &varctx, r->subctx);
+ if (!vd) return (0);
+ r->vd.regsz = vd->regsz; r->vd.setvar = setvar_remote;
+ r->vd.def = vd->def;
+ *ctx_out = r; return (&r->vd);
+ } else
+ return (0);
+}
+
+/* --- @tvec_remotebefore@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Invoke the subordinate environment's @before@ function.
+ */
+
+void tvec_remotebefore(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ const struct tvec_remoteenv *re = r->re;
+ const struct tvec_env *subenv = re->r.env;
+
+ if (subenv && subenv->before) subenv->before(tv, r->subctx);
+}
+
+/* --- @tvec_remoterun@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @tvec_testfn *fn@ = test function to run
+ * @void *ctx@ = context pointer for the test function
+ *
+ * Returns: ---
+ *
+ * Use: Run a test on a remote server.
+ */
+
+void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ union tvec_regval rv;
+ unsigned f = 0;
+#define f_exit 1u
+#define f_progress 2u
+#define f_fail 4u
+ buf b;
+ int rc;
+
+ /* Reconnect to the server according to policy. */
+ switch (try_reconnect(tv, r)) {
+ case CONN_FAILED:
+ tvec_skip(tv, "failed to connect to test backend"); return;
+ case CONN_BROKEN:
+ tvec_skip(tv, "no connection"); return;
+ }
+
+ /* Set initial progress state. */
+ DRESET(&r->progress); DPUTS(&r->progress, "%IDLE");
+
+ /* Send the command to the server and handle output. */
+ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST)
+ tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout),
+ tv->test->regs, tv->nreg, tv->regsz);
+ else { goto fail; }
+ rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
+
+ /* Deal with the outcome. */
+ switch (rc) {
+
+ case RECV_FAIL:
+ /* Some kind of error. Abandon ship. */
+
+ fail:
+ tvec_skip(tv, "remote test runner communications failed");
+ disconnect_remote(tv, r, 0);
+ break;
+
+ case RECV_EOF:
+ /* End-of-file at a packet boundary. The server crashed trying to run
+ * our test. Collect the exit status and continue.
+ */
+ reap_kid(tv, r);
+ /* fall through */
+
+ case RECV_OK:
+ /* Successful completion (or EOF). */
+
+ /* Notice if the exit status isn't right. */
+ if (r->exit != r->exwant) f |= f_exit;
+
+ /* Notice if the progress token isn't right. */
+ if (r->progress.len != r->prgwant.len ||
+ MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len))
+ f |= f_progress;
+
+ /* If we found something wrong but the test is passing so far, then
+ * report the failure and dump the input registers.
+ */
+ if (f && (tv->f&TVSF_ACTIVE))
+ { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); }
+
+ /* If the test failed, then report the exit and progress states
+ * relative to their expectations.
+ */
+ if (!(tv->f&TVSF_ACTIVE) &&
+ (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) {
+
+ /* Note here that the test failed. */
+ f |= f_fail;
+
+ /* Report exit status. */
+ rv.u = r->exit;
+ tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
+ &rv, &exit_var.def);
+ if (f&f_exit) {
+ rv.u = r->exwant;
+ tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_var.def);
+ }
+
+ /* Report progress token. */
+ rv.text.p = r->progress.buf; rv.text.sz = r->progress.len;
+ tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH,
+ &rv, &progress_var.def);
+ if (f&f_progress) {
+ rv.text.p = r->prgwant.buf; rv.text.sz = r->prgwant.len;
+ tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_var.def);
+ }
+ }
+
+ /* If we received end-of-file, then close the connection. Suppress
+ * error output if the test passed: it was presumably expected.
+ */
+ if (rc == RECV_EOF)
+ disconnect_remote(tv, r, f ? 0 : ERF_SILENT);
+ break;
+ }
+
+#undef f_exit
+#undef f_progress
+#undef f_fail
+}
+
+/* --- @tvec_remoteafter@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Reset variables to their default values.
+ */
+
+void tvec_remoteafter(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ const struct tvec_remoteenv *re = r->re;
+ const struct tvec_env *subenv = re->r.env;
+
+ reset_vars(r);
+ if (subenv && subenv->after) subenv->after(tv, r->subctx);
+}
+
+/* --- @tvec_remoteteardown@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Tear down the remote environment.
+ */
+
+void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ const struct tvec_remoteenv *re = r->re;
+ const struct tvec_env *subenv = re->r.env;
+ buf b;
+
+ if (subenv && subenv->teardown) subenv->teardown(tv, r->subctx);
+ xfree(r->subctx);
+ if (r->rc.outfd >= 0) {
+ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP);
+ if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b))
+ if (BLEFT(&b)) malformed(tv, &r->rc);
+ }
+ disconnect_remote(tv, r, 0); release_comms(&r->rc);
+ DDESTROY(&r->prgwant); DDESTROY(&r->progress);
+}
+
+/*----- Connectors --------------------------------------------------------*/
+
+/* --- @fork_common@ --- *
+ *
+ * Arguments: @pid_t *kid_out@ = where to put child process-id
+ * @int *infd_out, *outfd_out, *errfd_out@ = where to put file
+ * descriptors
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, %$-1$% on failure.
+ *
+ * Use: Common @fork@ machinery for the connectors. Create a
+ * subprocess. On successful return, in the subprocess,
+ * @*kid_out@ is zero, and the error descriptor replaces the
+ * standard-error descriptor; in the parent, @*kid_out@ is the
+ * child process-id, and @*errfd_out@ is a descriptor on which
+ * the child's standard-error output can be read; in both
+ * @*infd_out@ and @*outfd_out@ are descriptors for input and
+ * output respectively -- they're opposite ends of pipes, but
+ * obviously they're crossed over so that the parent's output
+ * matches the child's input and %%\emph{vice versa}%%.
+ */
+
+static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
+ int *errfd_out, struct tvec_state *tv)
+{
+ int p0[2] = { -1, -1 }, p1[2] = { -1, -1 }, pe[2] = { -1, -1 };
+ pid_t kid = -1;
+ int rc;
+
+ /* Try to create the pipes. */
+ if (pipe(p0) || pipe(p1) || pipe(pe) ||
+ fdflags(p0[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
+ fdflags(p1[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
+ fdflags(pe[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC)) {
+ tvec_error(tv, "pipe failed: %s", strerror(errno));
+ rc = -1; goto end;
+ }
+
+ /* Flush all of the stream buffers so that we don't get duplicated
+ * output.
+ */
+ fflush(0);
+
+ /* Try to set up the child process. */
+ kid = fork();
+ if (kid < 0) {
+ tvec_error(tv, "fork failed: %s", strerror(errno));
+ rc = -1; goto end;
+ }
+
+ if (!kid) {
+ /* Child process. */
+
+ *kid_out = 0;
+ *infd_out = p0[0]; p0[0] = -1;
+ *outfd_out = p1[1]; p1[1] = -1;
+ if (pe[1] != STDERR_FILENO && dup2(pe[1], STDERR_FILENO) < 0) {
+ fprintf(stderr, "failed to establish child stderr: %s",
+ strerror(errno));
+ _exit(127);
+ }
+ } else {
+ /* Parent process. */
+
+ *kid_out = kid; kid = -1;
+ *infd_out = p1[0]; p1[0] = -1;
+ *outfd_out = p0[1]; p0[1] = -1;
+ *errfd_out = pe[0]; pe[0] = -1;
+ }
+
+ /* All done. */
+ rc = 0;
+
+end:
+ /* Clean up. So much of this... */
+ if (p0[0] >= 0) close(p0[0]);
+ if (p0[1] >= 0) close(p0[1]);
+ if (p1[0] >= 0) close(p1[0]);
+ if (p1[1] >= 0) close(p1[1]);
+ if (pe[0] >= 0) close(pe[0]);
+ if (pe[1] >= 0) close(pe[1]);
+ return (rc);
+}
+
+/* --- @tvec_fork@ --- *
+ *
+ * Arguments: @pid_t *kid_out@ = where to put child process-id
+ * @int *infd_out, *outfd_out, *errfd_out@ = where to put file
+ * descriptors
+ * @struct tvec_state *tv@ = test vector state
+ * @const struct tvec_remoteenv@ = the remote environment
+ *
+ * Returns: Zero on success, %$-1$% on failure.
+ *
+ * Use: Starts a remote server running in a fork of the main
+ * process. This is useful for testing functions which might --
+ * or are even intended to -- crash.
+ */
+
+int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
+ struct tvec_state *tv, const struct tvec_remoteenv *env)
+{
+ struct tvec_config config;
+ const struct tvec_remotefork *rf = (const struct tvec_remotefork *)env;
+ pid_t kid = -1;
+ int infd = -1, outfd = -1, errfd = -1;
+ int rc;
+
+ if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
+ if (!kid) {
+ if (tv->fp) fclose(tv->fp);
+ config.tests = rf->f.tests ? rf->f.tests : tv->tests;
+ config.nrout = tv->nrout; config.nreg = tv->nreg;
+ config.regsz = tv->regsz;
+ _exit(tvec_remoteserver(infd, outfd, &config));
+ }
+
+ *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
+ rc = 0;
+end:
+ return (rc);
+}
+
+/* --- @tvec_exec@ --- *
+ *
+ * Arguments: @pid_t *kid_out@ = where to put child process-id
+ * @int *infd_out, *outfd_out, *errfd_out@ = where to put file
+ * descriptors
+ * @struct tvec_state *tv@ = test vector state
+ * @const struct tvec_remoteenv@ = the remote environment
+ *
+ * Returns: Zero on success, %$-1$% on failure.
+ *
+ * Use: Starts a remote server by running some program. The command
+ * given in the environment description will probably some hairy
+ * shell rune allowing for configuration via files or
+ * environment variables.
+ */
+
+int tvec_exec(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
+ struct tvec_state *tv, const struct tvec_remoteenv *env)
+{
+ const struct tvec_remoteexec *rx = (const struct tvec_remoteexec *)env;
+ pid_t kid = -1;
+ int infd = -1, outfd = -1, errfd = -1;
+ mdup_fd v[2];
+ int rc;
+
+ if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
+ if (!kid) {
+ v[0].cur = infd; v[0].want = STDIN_FILENO;
+ v[1].cur = outfd; v[1].want = STDOUT_FILENO;
+ if (mdup(v, 2)) {
+ fprintf(stderr, "failed to establish standard file descriptors: %s",
+ strerror(errno));
+ exit(127);
+ }
+ execvp(rx->x.args[0], (/*uncosnt*/ char *const *)rx->x.args);
+ fprintf(stderr, "failed to invoke test runner: %s", strerror(errno));
+ exit(127);
+ }
+
+ *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
+ rc = 0;
+end:
+ return (rc);
+}