+ 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);
+}