/*----- Header files ------------------------------------------------------*/
#include <errno.h>
+#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
-#include <sys/uio.h>
+#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include "alloc.h"
+#include "bench.h"
#include "buf.h"
+#include "compiler.h"
+#include "fdflags.h"
+#include "lbuf.h"
+#include "mdup.h"
+#include "quis.h"
#include "tvec.h"
-/*----- Data structures ---------------------------------------------------*/
+#if GCC_VERSION_P(7, 1)
+# pragma GCC diagnostic ignored "-Wdangling-else"
+#elif GCC_VERSION_P(4, 2)
+# pragma GCC diagnostic ignored "-Wparentheses"
+#endif
-struct tvec_remote {
- int infd, outfd;
- dbuf bin, bout;
- unsigned f;
-#define TVRF_BROKEN 1u
-};
+#if CLANG_VERSION_P(3, 1)
+# pragma clang diagnostic ignored "-Wdangling-else"
+#endif
-struct tvec_remotectx {
- struct tvec_remote r;
- pid_t kid;
-};
+/*----- Basic I/O ---------------------------------------------------------*/
-struct remote_output {
- struct tvec_output _o;
- struct tvec_remote r;
-};
+static void init_comms(struct tvec_remotecomms *rc)
+{
+ dbuf_create(&rc->bin); dbuf_create(&rc->bout);
+ rc->infd = rc->outfd = -1; rc->f = 0;
+}
-/*----- Basic I/O ---------------------------------------------------------*/
+static void close_comms(struct tvec_remotecomms *rc)
+{
+ if (rc->infd >= 0) { close(rc->infd); rc->infd = -1; }
+ if (rc->outfd >= 0) { close(rc->outfd); rc->outfd = -1; }
+}
+
+static void release_comms(struct tvec_remotecomms *rc)
+ { close_comms(rc); dbuf_destroy(&rc->bin); dbuf_destroy(&rc->bout); }
+
+static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd)
+{
+ rc->infd = infd; rc->outfd = outfd; rc->f &= ~0xffu;
+ dbuf_reset(&rc->bin); dbuf_reset(&rc->bout);
+}
static int PRINTF_LIKE(3, 4)
- ioerr(struct tvec_state *tv, struct tvec_remote *r, const char *msg, ...)
+ ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
+ const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
- r->f |= TVRF_BROKEN;
- tvec_write(tv, msg, &ap);
+ close_comms(rc); rc->f |= TVRF_BROKEN;
+ tvec_report_v(tv, TVLEV_ERR, msg, &ap);
va_end(ap);
return (-1);
}
-static int send_all(struct tvec_state *tv, struct tvec_remote *r,
+static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
const unsigned char *p, size_t sz)
{
+ void (*opipe)(int) = SIG_ERR;
ssize_t n;
+ int ret;
+ opipe = signal(SIGPIPE, SIG_IGN);
+ if (opipe == SIG_ERR) {
+ ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
+ goto end;
+ }
while (sz) {
- n = write(r->outfd, p, sz);
+ n = write(rc->outfd, p, sz);
if (n > 0)
{ p += n; sz -= n; }
- else
- return (ioerr(tv, r, "failed to send: %s",
- n ? strerror(errno) : "empty write"));
+ else {
+ ret = ioerr(tv, rc, "failed to send: %s",
+ n ? strerror(errno) : "empty write");
+ goto end;
+ }
}
- return (0);
+ ret = 0;
+end:
+ if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
+ return (ret);
}
#define RCVF_ALLOWEOF 1u
-static int recv_all(struct tvec_state *tv, struct tvec_remote *r,
+enum {
+ RECV_FAIL = -1,
+ RECV_OK = 0,
+ RECV_EOF = 1
+};
+static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
unsigned char *p, size_t sz, unsigned f)
{
ssize_t n;
#define f_any 1u
while (sz) {
- n = read(r->infd, p, sz);
+ n = read(rc->infd, p, sz);
if (n > 0)
{ p += n; sz -= n; ff |= f_any; }
else if (!n && (f&RCVF_ALLOWEOF) && !(ff&f_any))
- return (1);
+ return (RECV_EOF);
else
- return (ioerr(tv, r, "failed to receive: %s",
+ return (ioerr(tv, rc, "failed to receive: %s",
n ? strerror(errno) : "unexpected end-of-file"));
}
- return (0);
+ return (RECV_OK);
#undef f_any
}
-int tvec_send(struct tvec_state *tv, struct tvec_remote *r)
+static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc)
{
kludge64 k; unsigned char lenbuf[8];
- const char *p; size_t sz;
+ const unsigned char *p; size_t sz;
- if (r->f&TVRF_BROKEN) return (-1);
- if (BBAD(&r->bout.b))
- return (ioerr(tv, r, "failed to build output packet buffer");
+ if (rc->f&TVRF_BROKEN) return (-1);
+ if (BBAD(&rc->bout._b))
+ return (ioerr(tv, rc, "failed to build output packet buffer"));
- p = BBASE(r->bout.b); sz = BLEN(&r->bout.b);
+ p = BBASE(&rc->bout._b); sz = BLEN(&rc->bout._b);
ASSIGN64(k, sz); STORE64_L_(lenbuf, k);
- if (send_all(tv, r, lenbuf, sizeof(lenbuf))) return (-1);
- if (send_all(tv, r, p, sz)) return (-1);
+ if (send_all(tv, rc, lenbuf, sizeof(lenbuf))) return (-1);
+ if (send_all(tv, rc, p, sz)) return (-1);
return (0);
}
-int tvec_recv(struct tvec_state *tv, struct tvec_remote *r, buf *b_out)
+static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
+ unsigned f, buf *b_out)
{
kludge64 k, szmax; unsigned char lenbuf[8];
unsigned char *p;
size_t sz;
- int rc;
+ int ret;
- if (r->f&TVRF_BROKEN) return (-1);
- ASSIGN64(k, (size_t)-1);
- rc = recv_all(tv, r, lenbuf, sizeof(lenbuf), RCVF_ALLOWEOF);
- if (rc) return (rc);
+ if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
+ ASSIGN64(szmax, (size_t)-1);
+ ret = recv_all(tv, rc, lenbuf, sizeof(lenbuf), f);
+ if (ret) return (ret);
LOAD64_L_(k, lenbuf);
if (CMP64(k, >, szmax))
- return (ioerr(tv, r, "packet size 0x%08lx%08lx out of range",
+ return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range",
(unsigned long)HI64(k), (unsigned long)LO64(k)));
- sz = GET64(size_t, k); buf_reset(&r->bin); p = buf_get(&r->bin.b, sz);
- if (!p) return (ioerr(tv, r, "failed to allocate receive buffer"));
- if (recv_all(tv, r, p, sz, 0)) return (-1);
- buf_init(b_out, p, sz); return (0);
+ sz = GET64(size_t, k); dbuf_reset(&rc->bin); p = buf_get(&rc->bin._b, sz);
+ if (!p) return (ioerr(tv, rc, "failed to allocate receive buffer"));
+ if (recv_all(tv, rc, p, sz, 0)) return (RECV_FAIL);
+ buf_init(b_out, p, sz); return (RECV_OK);
}
-/*----- Data formatting primitives ----------------------------------------*/
+#define SENDPK(tv, rc, pk) \
+ if ((rc)->f&TVRF_BROKEN) MC_GOELSE(body); else \
+ MC_BEFORE(setpk, \
+ { dbuf_reset(&(rc)->bout); \
+ buf_putu16l(&(rc)->bout._b, (pk)); }) \
+ MC_ALLOWELSE(body) \
+ MC_AFTER(send, \
+ { if (remote_send(tv, rc)) MC_GOELSE(body); }) \
+static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
+ { return (ioerr(tv, rc, "received malformed packet")); }
/*----- Packet types ------------------------------------------------------*/
-#define TVPK_ERROR 0x0001 /* msg: string */
-#define TVPK_NOTICE 0x0002 /* msg: string */
-#define TVPK_STATUS 0x0003 /* st: char */
+#define TVPF_ACK 0x0001u
-#define TVPK_BGROUP 0x0101 /* name: string */
-#define TVPK_TEST 0x0102 /* in: regs */
-#define TVPK_EGROUP 0x0103 /* --- */
+#define TVPK_VER 0x0000u /* --> min, max: u16 */
+ /* <-- ver: u16 */
-#define TVPK_SKIPGRP 0x0201 /* excuse: string */
-#define TVPK_SKIP 0x0202 /* excuse: string */
-#define TVPK_FAIL 0x0203 /* detail: string */
-#define TVPK_MISMATCH 0x0204 /* in, out: regs */
-#define TVPK_BBENCH 0x0205 /* in: regs */
-#define TVPK_EBENCH 0x0206 /* flags: u16; n: u64; t, cy: float */
+#define TVPK_REPORT 0x0100u /* <-- level: u16; msg: string */
+#define TVPK_PROGRESS 0x0102u /* <-- st: str16 */
-/*----- The output driver -------------------------------------------------*/
+#define TVPK_BGROUP 0x0200u /* --> name: str16
+ * <-- --- */
+#define TVPK_TEST 0x0202u /* --> in: regs
+ * <-- --- */
+#define TVPK_EGROUP 0x0204u /* --> --- */
-#define SENDPK(ro, pk) \
- if ((ro)->r.f&TVRF_BROKEN) /* do nothing */; else \
- MC_BEFORE(setpk, \
- { buf_reset(&(ro)->r.bout); \
- buf_putu16l(&(ro)->r.bout.b, (pk)); }) \
- MC_AFTER(send, \
- { tvec_send(&ro->_o.tv, &ro->r); })
+#define TVPK_SKIPGRP 0x0300u /* <-- excuse: str16 */
+#define TVPK_SKIP 0x0302u /* <-- excuse: str16 */
+#define TVPK_FAIL 0x0304u /* <-- flag: u8, detail: str16 */
+#define TVPK_DUMPREG 0x0306u /* <-- ri: u16; disp: u16;
+ * flag: u8, rv: value */
+#define TVPK_BBENCH 0x0308u /* <-- ident: str32; unit: u16 */
+#define TVPK_EBENCH 0x030au /* <-- ident: str32; unit: u16;
+ * flags: u16; n, t, cy: f64 */
-static int sendstr(struct tvec_output *o, unsigned pk,
- const char *p, va_list *ap)
-{
- struct remote_output *ro = (struct remote_output *)o;
+/*----- Server ------------------------------------------------------------*/
+
+static const struct tvec_outops remote_ops;
+
+static struct tvec_state srvtv;
+static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT;
+static struct tvec_output srvout = { &remote_ops };
- SENDPK(ro, pk) buf_vputstrf16l(&ro->r.bout.b, msg, ap);
- return (ro->r.f&TVRF_BROKEN ? -1 : 0);
+int tvec_setprogress(const char *status)
+{
+ SENDPK(&srvtv, &srvrc, TVPK_PROGRESS)
+ buf_putstr16l(&srvrc.bout._b, status);
+ else return (-1);
+ return (0);
}
-static void report(struct tvec_output *o, unsigned pk, const char *what,
- const char *msg, va_list *ap)
+int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
{
- if (sendstr(o, pk, msg, ap)) {
- fprintf(stderr, "%s %s: ", QUIS, what);
- vfprintf(stderr, msg, *ap);
- fputc('\n', stderr);
+ uint16 pk, u, v;
+ unsigned i;
+ buf b;
+ const struct tvec_test *t;
+ void *p; size_t sz;
+ const struct tvec_env *env = 0;
+ unsigned f = 0;
+#define f_regslive 1u
+ void *ctx = 0;
+ int rc;
+
+ setup_comms(&srvrc, infd, outfd);
+ tvec_begin(&srvtv, config, &srvout);
+
+ if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
+ if (buf_getu16l(&b, &pk)) goto bad;
+ if (pk != TVPK_VER) {
+ rc = ioerr(&srvtv, &srvrc,
+ "unexpected packet type 0x%04x instead of client version",
+ pk);
+ goto end;
}
-}
+ if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad;
+ SENDPK(&srvtv, &srvrc, TVPK_VER | TVPF_ACK) buf_putu16l(&srvrc.bout._b, 0);
+ else { rc = -1; goto end; }
-static void remote_error(struct tvec_output *o, const char *msg, va_list *ap)
- { report(o, TVPK_ERROR, "ERROR", msg, ap); }
+ tvec_setprogress("%IDLE");
-static void remote_notice(struct tvec_output *o,
- const char *msg, va_list *ap)
- { report(o, TVPK_NOTICE, "notice", msg, ap); }
+ for (;;) {
+ rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b);
+ if (rc == RECV_EOF) break;
+ else if (rc == RECV_FAIL) goto end;
+ if (buf_getu16l(&b, &pk)) goto bad;
-static void remote_setstatus(struct tvec_ouptut *o, int st)
-{
- struct remote_output *ro = (struct remote_output *)o;
- SENDPK(ro, TVPK_STATUS) buf_putbyte(&ro->r.bout.b, st);
+ switch (pk) {
+
+ case TVPK_BGROUP:
+ p = buf_getmem16l(&b, &sz); if (!p) goto bad;
+ if (BLEFT(&b)) goto bad;
+ for (t = srvtv.tests; t->name; t++)
+ if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz))
+ goto found_group;
+ rc = ioerr(&srvtv, &srvrc, "unknown test group `%.*s'",
+ (int)sz, (char *)p);
+ goto end;
+
+ found_group:
+ srvtv.test = t; env = t->env;
+ if (env && env->setup == tvec_remotesetup)
+ env = ((struct tvec_remoteenv *)env)->r.env;
+ if (!env || !env->ctxsz) ctx = 0;
+ else ctx = xmalloc(env->ctxsz);
+ if (env && env->setup) env->setup(&srvtv, env, 0, ctx);
+
+ SENDPK(&srvtv, &srvrc, TVPK_BGROUP | TVPF_ACK);
+ else { rc = -1; goto end; }
+
+ for (;;) {
+ if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
+ if (buf_getu16l(&b, &pk)) goto bad;
+ switch (pk) {
+
+ case TVPK_EGROUP:
+ if (BLEFT(&b)) goto bad;
+ goto endgroup;
+
+ case TVPK_TEST:
+ tvec_initregs(&srvtv); f |= f_regslive;
+ if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs,
+ srvtv.nreg, srvtv.regsz))
+ goto bad;
+ if (BLEFT(&b)) goto bad;
+
+ if (!(srvtv.f&TVSF_SKIP)) {
+ srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK;
+ tvec_setprogress("%SETUP");
+ if (env && env->before) env->before(&srvtv, ctx);
+ if (!(srvtv.f&TVSF_ACTIVE))
+ /* setup forced a skip */;
+ else {
+ for (i = 0; i < srvtv.nrout; i++)
+ if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
+ TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
+ tvec_setprogress("%RUN");
+ if (env && env->run)
+ env->run(&srvtv, t->fn, ctx);
+ else {
+ t->fn(srvtv.in, srvtv.out, ctx);
+ tvec_check(&srvtv, 0);
+ }
+ }
+ tvec_setprogress("%DONE");
+ if (env && env->after) env->after(&srvtv, ctx);
+ tvec_endtest(&srvtv);
+ }
+ tvec_releaseregs(&srvtv); f &= ~f_regslive;
+ SENDPK(&srvtv, &srvrc, TVPK_TEST | TVPF_ACK);
+ else { rc = -1; goto end; }
+ tvec_setprogress("%IDLE");
+ break;
+
+ default:
+ rc = ioerr(&srvtv, &srvrc,
+ "unexpected packet type 0x%04x", pk);
+ goto end;
+
+ }
+ }
+
+ endgroup:
+ if (env && env->teardown) env->teardown(&srvtv, ctx);
+ xfree(ctx); t = 0; env = 0; ctx = 0;
+ break;
+
+ default:
+ goto bad;
+ }
+ }
+ rc = 0;
+
+end:
+ if (env && env->teardown) env->teardown(&srvtv, ctx);
+ xfree(ctx);
+ if (f&f_regslive) tvec_releaseregs(&srvtv);
+ release_comms(&srvrc);
+ return (rc ? 2 : 0);
+
+bad:
+ rc = malformed(&srvtv, &srvrc); goto end;
+
+#undef f_regslive
}
+/*----- Server output driver ----------------------------------------------*/
+
static void remote_skipgroup(struct tvec_output *o,
const char *excuse, va_list *ap)
- { sendstr(o, TVPK_SKIPGRP, excuse, ap); }
+{
+ SENDPK(&srvtv, &srvrc, TVPK_SKIPGRP)
+ buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+}
static void remote_skip(struct tvec_output *o,
const char *excuse, va_list *ap)
- { sendstr(o, TVPK_SKIP, excuse, ap); }
+{
+ SENDPK(&srvtv, &srvrc, TVPK_SKIP)
+ buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+}
static void remote_fail(struct tvec_output *o,
const char *detail, va_list *ap)
- { sendstr(o, TVPK_FAIL, detail, ap); }
+{
+ SENDPK(&srvtv, &srvrc, TVPK_FAIL)
+ if (!detail)
+ buf_putbyte(&srvrc.bout._b, 0);
+ else {
+ buf_putbyte(&srvrc.bout._b, 1);
+ buf_vputstrf16l(&srvrc.bout._b, detail, ap);
+ }
+}
-static void remote_mismatch(struct tvec_output *o)
+static void remote_dumpreg(struct tvec_output *o,
+ unsigned disp, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
{
- struct remote_output *ro = (struct remote_output *)o;
- struct tvec_state *rv = ro->_o.tv;
+ const struct tvec_regdef *reg;
+ unsigned r;
- SENDPK(ro, TVPK_MISMATCH) {
- tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz);
- tvec_serialize(tv, &ro->r.bout.b, tv->out, tv->nrout, tv->regsz);
+ /* 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:
+ SENDPK(&srvtv, &srvrc, TVPK_DUMPREG) {
+ buf_putu16l(&srvrc.bout._b, r);
+ buf_putu16l(&srvrc.bout._b, disp);
+ if (!rv)
+ buf_putbyte(&srvrc.bout._b, 0);
+ else {
+ buf_putbyte(&srvrc.bout._b, 1);
+ rd->ty->tobuf(&srvrc.bout._b, rv, rd);
+ }
}
}
-static void remote_bbench(struct tvec_output *o)
+static void remote_bbench(struct tvec_output *o,
+ const char *ident, unsigned unit)
{
- struct remote_output *ro = (struct remote_output *)o;
- struct tvec_state *rv = ro->_o.tv;
-
- SENDPK(ro, TVPK_BBENCH)
- tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz);
+ SENDPK(&srvtv, &srvrc, TVPK_BBENCH) {
+ buf_putstr32l(&srvrc.bout._b, ident);
+ buf_putu16l(&srvrc.bout._b, unit);
+ }
}
static void remote_ebench(struct tvec_output *o,
+ const char *ident, unsigned unit,
const struct bench_timing *t)
{
- struct remote_output *ro = (struct remote_output *)o;
- kludge64 k;
+ SENDPK(&srvtv, &srvrc, TVPK_EBENCH) {
+ buf_putstr32l(&srvrc.bout._b, ident);
+ buf_putu16l(&srvrc.bout._b, unit);
+ if (!t || !(t->f&BTF_ANY))
+ buf_putu16l(&srvrc.bout._b, 0);
+ else {
+ buf_putu16l(&srvrc.bout._b, t->f);
+ buf_putf64l(&srvrc.bout._b, t->n);
+ if (t->f&BTF_TIMEOK) buf_putf64l(&srvrc.bout._b, t->t);
+ if (t->f&BTF_CYOK) buf_putf64l(&srvrc.bout._b, t->cy);
+ }
+ }
+}
+
+static void remote_report(struct tvec_output *o, unsigned level,
+ const char *msg, va_list *ap)
+{
+ const char *what;
- SENDPK(ro, TVPK_EBENCH) {
- buf_putu16l(&ro->r.bout.b, t->f);
- ASSIGN64(k, t->n); buf_putk64l(&ro->r.bout.b, k);
- if (t->f&BTF_TIMEOK) buf_putf64l(&ro->r.bout.b, t->t);
- if (t->f&BTF_CYOK) buf_putf64l(&ro->r.bout.b, t->cy);
+ SENDPK(&srvtv, &srvrc, TVPK_REPORT) {
+ buf_putu16l(&srvrc.bout._b, level);
+ buf_vputstrf16l(&srvrc.bout._b, msg, ap);
+ } else {
+ switch (level) {
+ case TVLEV_NOTE: what = "notice"; break;
+ case TVLEV_ERR: what = "ERROR"; break;
+ default: what = "(?level)"; break;
+ }
+ fprintf(stderr, "%s %s: ", QUIS, what);
+ vfprintf(stderr, msg, *ap);
+ fputc('\n', stderr);
}
}
-static void remote_write(struct tvec_output *o, const char *p, size_t sz)
- { assert(!"remote_write"); }
-static void remote_bsession(struct tvec_output *o)
- { assert(!"remote_bsession"); }
+static void remote_bsession(struct tvec_output *o, struct tvec_state *tv)
+ { ; }
static int remote_esession(struct tvec_output *o)
- { assert(!"remote_esession"); return (-1); }
+ { return (srvtv.f&TVSF_ERROR ? 2 : 0); }
+static void remote_destroy(struct tvec_output *o)
+ { ; }
+static void remote_etest(struct tvec_output *o, unsigned outcome)
+ { ; }
+
static void remote_bgroup(struct tvec_output *o)
{ assert(!"remote_bgroup"); }
+static void remote_egroup(struct tvec_output *o)
+ { assert(!"remote_egroup"); }
static void remote_btest(struct tvec_output *o)
{ assert(!"remote_btest"); }
-static void remote_egroup(struct tvec_output *o, unsigned outcome)
- { assert(!"remote_egroup"); }
-static void remote_etest(struct tvec_output *o, unsigned outcome)
- { assert(!"remote_etest"); }
-
-static void remote_destroy(struct tvec_output *o)
-{
-}
static const struct tvec_outops remote_ops = {
- remote_error, remote_notice, remote_setstatus, remote_write,
remote_bsession, remote_esession,
- remote_bgroup, remote_egroup, remote_skip,
- remote_btest, remote_skip, remote_fail, remote_mismatch, remote_etest,
+ remote_bgroup, remote_skipgroup, remote_egroup,
+ remote_btest, remote_skip, remote_fail, remote_dumpreg, remote_etest,
remote_bbench, remote_ebench,
+ remote_report,
remote_destroy
+};
+
+/*----- Client ------------------------------------------------------------*/
+
+#define TVXF_VALMASK 0x0fffu
+#define TVXF_SIG 0x1000u
+#define TVXF_CAUSEMASK 0xe000u
+#define TVXST_RUN 0x0000u
+#define TVXST_EXIT 0x2000u
+#define TVXST_KILL 0x4000u
+#define TVXST_CONT 0x6000u
+#define TVXST_STOP 0x8000u
+#define TVXST_DISCONN 0xa000u
+#define TVXST_UNK 0xc000u
+#define TVXST_ERR 0xe000u
+
+static const struct tvec_flag exit_flags[] = {
+ /*
+ ;;; 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***/
+
+ { "signal", TVXF_SIG, TVXF_SIG },
+
+ { "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 },
+
+ TVEC_ENDFLAGS
+};
+
+static const struct tvec_flaginfo exit_flaginfo =
+ { "exit-status", exit_flags, &tvrange_uint };
+static const struct tvec_regdef exit_regdef =
+ { "@exit", 0, &tvty_flags, 0, { &exit_flaginfo } };
+
+static const struct tvec_regdef progress_regdef =
+ { "@progress", 0, &tvty_string, 0 };
+
+static const struct tvec_uassoc reconn_assocs[] = {
+ { "on-demand", TVRCN_DEMAND },
+ { "force", TVRCN_FORCE },
+ { "skip", TVRCN_SKIP },
+ TVEC_ENDENUM
+};
+
+enum {
+ CONN_BROKEN = -2, /* previously broken */
+ CONN_FAILED = -1, /* attempt freshly failed */
+ CONN_ESTABLISHED = 0, /* previously established */
+ CONN_FRESH = 1 /* freshly connected */
+};
+
+static const struct tvec_uenuminfo reconn_enuminfo =
+ { "remote-reconnection", reconn_assocs, &tvrange_uint };
+static const struct tvec_regdef reconn_regdef =
+ { "@reconnect", 0, &tvty_uenum, 0, { &reconn_enuminfo } };
+
+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 (;;) {
+ rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end;
+ if (buf_getu16l(b, &pk)) goto bad;
+
+ switch (pk) {
+
+ case TVPK_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:
+ 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:
+ p = buf_getmem16l(b, &n); if (!p) goto bad;
+ DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+ if (BLEFT(b)) goto bad;
+
+ tvec_skipgroup(tv, "%s", d.buf);
+ break;
+
+ case TVPK_SKIP:
+ 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:
+ 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;
-/*----- Main code ---------------------------------------------------------*/
+ 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:
+ 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;
+ }
+ 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:
+ 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:
+ 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:
+ if (pk == end) { rc = 0; goto end; }
+ 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;
+}
+
+static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+ pid_t kid;
+ int st;
+
+ 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;
+ }
+}
+
+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);
+}
+
+#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;
+
+ 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;
+ }
+
+ 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);
+ }
+ rc = 0;
+end:
+ if (f&ERF_CLOSE) {
+ lbuf_close(&r->errbuf);
+ close(r->errfd);
+ }
+ return (rc);
+}
+
+#define DCF_KILL 0x0100u
+static void disconnect_remote(struct tvec_state *tv,
+ struct tvec_remotectx *r, unsigned f)
+{
+ if (r->kid < 0) return;
+ if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
+ close_comms(&r->rc);
+ if (r->kid > 0) kill(r->kid, SIGTERM);
+ drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
+}
+
+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;
+
+ DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
+ if (r->kid >= 0) { rc = 0; goto end; }
+ if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re))
+ { rc = -1; goto end; }
+ 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;
+
+ SENDPK(tv, &r->rc, TVPK_VER) {
+ buf_putu16l(&r->rc.bout._b, 0);
+ buf_putu16l(&r->rc.bout._b, 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;
+ }
+
+ SENDPK(tv, &r->rc, TVPK_BGROUP)
+ buf_putstr16l(&r->rc.bout._b, 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; }
+ r->ver = v; rc = 0;
+end:
+ if (rc) disconnect_remote(tv, r, DCF_KILL);
+ return (rc);
+bad:
+ rc = malformed(tv, &r->rc); goto end;
+}
+
+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);
+}
+
+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);
+}
+
+static void reset_vars(struct tvec_remotectx *r)
+{
+ r->exwant = TVXST_RUN; r->rc.f = (r->rc.f&~TVRF_RCNMASK) | TVRCN_DEMAND;
+ DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
+}
+
+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;
+
+ assert(!re->r.env || tv->test->env == &re->_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);
+}
+
+int tvec_remoteset(struct tvec_state *tv, const char *var,
+ const struct tvec_env *env, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+ union tvec_regval rv;
+ int rc;
+
+ if (STRCMP(var, ==, "@exit")) {
+ if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; }
+ if (r) r->exwant = rv.u;
+ rc = 1;
+ } else if (STRCMP(var, ==, "@progress")) {
+ tvty_string.init(&rv, &progress_regdef);
+ rc = tvty_string.parse(&rv, &progress_regdef, tv);
+ if (r && !rc)
+ { DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); }
+ tvty_string.release(&rv, &progress_regdef);
+ if (rc) { rc = -1; goto end; }
+ rc = 1;
+ } else if (STRCMP(var, ==, "@reconnect")) {
+ if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; }
+ if (r) r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK);
+ rc = 1;
+ } else
+ rc = 0;
+
+end:
+ return (rc);
+}
+
+void tvec_remoteafter(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+
+ reset_vars(r);
+}
+
+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;
+
+ 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;
+ }
+
+ SENDPK(tv, &r->rc, TVPK_TEST)
+ tvec_serialize(tv->in, &r->rc.bout._b,
+ tv->test->regs, tv->nreg, tv->regsz);
+ else { rc = -1; goto end; }
+ rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
+ switch (rc) {
+ case RECV_FAIL:
+ goto end;
+ case RECV_EOF:
+ reap_kid(tv, r);
+ /* fall through */
+ case RECV_OK:
+ if (r->exit != r->exwant) f |= f_exit;
+ if (r->progress.len != r->prgwant.len ||
+ MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len))
+ f |= f_progress;
+ if (f && (tv->f&TVSF_ACTIVE))
+ { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); }
+ if (!(tv->f&TVSF_ACTIVE) &&
+ (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) {
+ f |= f_fail;
+
+ rv.u = r->exit;
+ tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
+ &rv, &exit_regdef);
+ if (f&f_exit) {
+ rv.u = r->exwant;
+ tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_regdef);
+ }
+
+ rv.str.p = r->progress.buf; rv.str.sz = r->progress.len;
+ tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH,
+ &rv, &progress_regdef);
+ if (f&f_progress) {
+ rv.str.p = r->prgwant.buf; rv.str.sz = r->prgwant.len;
+ tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_regdef);
+ }
+ }
+
+ if (rc == RECV_EOF)
+ disconnect_remote(tv, r, f ? 0 : ERF_SILENT);
+ break;
+ }
+
+end:
+ if (rc) {
+ if ((tv->f&TVSF_ACTIVE) && f)
+ tvec_skip(tv, "remote test runner communications failed");
+ disconnect_remote(tv, r, 0);
+ }
+
+#undef f_exit
+#undef f_progress
+#undef f_fail
+}
+
+void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
+{
+ struct tvec_remotectx *r = ctx;
+
+ if (r) {
+ disconnect_remote(tv, r, 0); release_comms(&r->rc);
+ DDESTROY(&r->prgwant); DDESTROY(&r->progress);
+ }
+}
+
+/*----- Connectors --------------------------------------------------------*/
+
+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;
+
+ 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;
+ }
+
+ fflush(0);
+
+ kid = fork();
+ if (kid < 0) {
+ tvec_error(tv, "fork failed: %s", strerror(errno));
+ rc = -1; goto end;
+ }
+
+ if (!kid) {
+ *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 {
+ *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;
+ }
+
+ rc = 0;
+end:
+ 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);
+}
+
+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) {
+ 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);
+}
+
+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);
+}
/*----- That's all, folks -------------------------------------------------*/