5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
12 * mLib is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * License for more details.
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
28 /*----- Header files ------------------------------------------------------*/
37 #include <sys/types.h>
53 /*----- Preliminaries -----------------------------------------------------*/
55 /* The control macros I'm using below provoke `dangling-else' warnings from
56 * compilers. Suppress them. I generally don't care.
59 #if GCC_VERSION_P(7, 1)
60 # pragma GCC diagnostic ignored "-Wdangling-else"
61 #elif GCC_VERSION_P(4, 2)
62 # pragma GCC diagnostic ignored "-Wparentheses"
65 #if CLANG_VERSION_P(3, 1)
66 # pragma clang diagnostic ignored "-Wdangling-else"
69 /*----- Basic I/O ---------------------------------------------------------*/
71 /* --- @init_comms@ --- *
73 * Arguments: @struct tvec_remotecomms *rc@ = communication state
77 * Use: Initialize a communication state. This doesn't allocate any
78 * resurces: it just ensures that everything is set up so that
79 * subsequent operations -- in particular @release_comms@ --
83 static void init_comms(struct tvec_remotecomms *rc)
85 rc->bin = 0; rc->binsz = 0; DBCREATE(&rc->bout);
86 rc->infd = rc->outfd = -1; rc->f = 0;
89 /* --- @close_comms@ --- *
91 * Arguments: @struct tvec_remotecomms *rc@ = communication state
95 * Use: Close the input and output descriptors.
97 * If the descriptors are already closed -- or were never opened
98 * -- then nothing happens.
101 static void close_comms(struct tvec_remotecomms *rc)
104 if (rc->infd != rc->outfd) close(rc->infd);
108 { close(rc->outfd); rc->outfd = -1; }
109 rc->f |= TVRF_BROKEN;
112 /* --- @release_comms@ --- *
114 * Arguments: @struct tvec_remotecomms *rc@ = communication state
118 * Use: Releases the resources -- most notably the input and output
119 * buffers -- held by the communication state. Also calls
123 static void release_comms(struct tvec_remotecomms *rc)
124 { close_comms(rc); xfree(rc->bin); DBDESTROY(&rc->bout); }
126 /* --- @setup_comms@ --- *
128 * Arguments: @struct tvec_remotecomms *rc@ = communication state
129 * @int infd, outfd@ = input and output file descriptors
133 * Use: Use the given descriptors for communication.
135 * Clears the private flags.
138 static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd)
140 rc->infd = infd; rc->outfd = outfd;
141 rc->binoff = rc->binlen = 0;
147 * Arguments: @struct tvec_state *tv@ = test-vector state
148 * @struct tvec_remotecomms *rc@ = communication state
149 * @const char *msg, ...@ = format string and arguments
153 * Use: Reports the message as an error, closes communications and
154 * marks them as broken.
157 static PRINTF_LIKE(3, 4)
158 int ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
159 const char *msg, ...)
164 close_comms(rc); rc->f |= TVRF_BROKEN;
165 tvec_report_v(tv, TVLEV_ERR, msg, &ap);
170 /* --- @send_all@ --- *
172 * Arguments: @struct tvec_state *tv@ = test-vector state
173 * @struct tvec_remotecomms *rc@ = communication state
174 * @const unsigned char *p@, @size_t sz@ = output buffer
176 * Returns: Zero on success, %$-1$% on error.
178 * Use: Send the output buffer over the communication state's output
179 * descriptor, even if it has to be written in multiple pieces.
182 static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
183 const unsigned char *p, size_t sz)
189 n = write(rc->outfd, p, sz);
193 ret = ioerr(tv, rc, "failed to send: %s",
194 n ? strerror(errno) : "empty write");
203 /* --- @recv_all@ --- *
205 * Arguments: @struct tvec_state *tv@ = test-vector state
206 * @struct tvec_remotecomms *rc@ = communication state
207 * @unsigned f@ = flags (@RCVF_...@)
208 * @unsigned char *p@, @size_t sz@ = input buffer
209 * @size_t min@ = minimum acceptable size to read
210 * @size_t *n_out@ = size read
212 * Returns: A @RECV_...@ code.
214 * Use: Receive data on the communication state's input descriptor to
215 * read at least @min@ bytes into the input buffer, even if it
216 * has to be done in multiple pieces. If more data is readily
217 * available, then up to @sz@ bytes will be read in total.
219 * If the descriptor immediately reports end-of-file, and
220 * @RCVF_ALLOWEOF@ is set in @f@, then return @RECV_EOF@.
221 * Otherwise, EOF is treated as an I/O error, resulting in a
222 * call to @ioerr@ and a return code of @RECV_FAIL@. If the
223 * read succeeded, then set @*n_out@ to the number of bytes read
224 * and return @RECV_OK@.
227 #define RCVF_ALLOWEOF 1u
235 static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
236 unsigned f, unsigned char *p, size_t sz,
237 size_t min, size_t *n_out)
243 n = read(rc->infd, p, sz);
245 p += n; sz -= n; tot += n;
246 if (tot >= min) break;
247 } else if (!n && !tot && (f&RCVF_ALLOWEOF))
248 { rc->f |= TVRF_BROKEN; return (RECV_EOF); }
250 return (ioerr(tv, rc, "failed to receive: %s",
251 n ? strerror(errno) : "unexpected end-of-file"));
253 *n_out = tot; return (RECV_OK);
258 /* --- @buferr@ --- *
260 * Arguments: @struct tvec_state *tv@ = test-vector state
261 * @struct tvec_remotecomms *rc@ = communication state
265 * Use: Report a problem preparing the output buffer.
268 static int buferr(struct tvec_state *tv, struct tvec_remotecomms *rc)
269 { return (ioerr(tv, rc, "failed to build output packet")); }
271 /* --- @malformed@ --- *
273 * Arguments: @struct tvec_state *tv@ = test-vector state
274 * @struct tvec_remotecomms *rc@ = communication state
278 * Use: Report an I/O error that the incoming packet is malformed.
281 static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
282 { return (ioerr(tv, rc, "received malformed packet")); }
284 /* --- @remote_send@ --- *
286 * Arguments: @struct tvec_state *tv@ = test-vector state
287 * @struct tvec_remotecomms *rc@ = communication state
289 * Returns: Zero on success, %$-1$% on error.
291 * Use: Send the accuulated contents of the output buffer @rc->bout@.
293 * The function arranges to convert @SIGPIPE@ into an error.
295 * If the output buffer is broken, report this as an I/O error.
298 #define SENDBUFSZ 4096
300 static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc)
302 void (*opipe)(int) = SIG_ERR;
305 /* Various preflight checks. */
306 if (rc->f&TVRF_BROKEN) { ret = -1; goto end; }
307 if (DBBAD(&rc->bout)) { ret = buferr(tv, rc); goto end; }
309 /* Arrange to trap broken-pipe errors. */
310 opipe = signal(SIGPIPE, SIG_IGN);
311 if (opipe == SIG_ERR) {
312 ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
316 /* Transmit the packet. */
317 if (send_all(tv, rc, DBBASE(&rc->bout), DBLEN(&rc->bout)))
318 { ret = -1; goto end; }
320 /* Done. Put things back the way we found them. */
324 if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
328 /* --- @receive_buffered@ --- *
330 * Arguments: @struct tvec_state *tv@ = test-vector state
331 * @struct tvec_remotecomms *rc@ = communication state
332 * @unsigned f@ = flags (@RCVF_...@)
333 * @size_t want@ = data block size required
335 * Returns: A @RECV_...@ code.
337 * Use: Reads a block of data from the input descriptor into the
340 * This is the main machinery for manipulating the input buffer.
341 * The buffer has three regions:
343 * * from the buffer start to @rc->binoff@ is `consumed';
344 * * from @rc->binoff@ to @rc->binlen@ is `available'; and
345 * * from @rc->binlen@ to @rc->binsz@ is `free'.
347 * Data is read into the start of the `free' region, and the
348 * `available' region is extended to include it. Data in the
349 * `consumed' region is periodically discarded by moving the
350 * data from the `available' region to the start of the buffer
351 * and decreasing @rc->binoff@ and @rc->binlen@.
353 * This function ensures that the `available' region contains at
354 * least @want@ bytes, by (a) extending the buffer, if
355 * necessary, so that @rc->binsz >= rc->binoff + want@, and (b)
356 * reading fresh data from the input descriptor to extend the
357 * `available' region.
359 * If absolutely no data is available, and @RCVF_ALLOWEOF@ is
360 * set in @f@, then return @RECV_EOF@. On I/O errors, including
361 * a short read or end-of-file if @RCVF_ALLOWEOF@ is clear,
362 * return @RECV_FAIL@. On success, return @RECV_OK@. The
363 * amount of data read is indicated by updating the input buffer
364 * variables as described above.
367 #define RECVBUFSZ 4096u
369 static int receive_buffered(struct tvec_state *tv,
370 struct tvec_remotecomms *rc,
371 unsigned f, size_t want)
376 /* If we can supply the caller's requirement from the buffer then do
379 if (rc->binlen - rc->binoff >= want) return (RECV_OK);
381 /* If the buffer is too small then we must grow it. */
382 GROWBUF_EXTEND(&arena_stdlib, rc->bin, rc->binsz, want, RECVBUFSZ, 1);
384 /* Shunt the unused existing material to the start of the buffer. */
385 memmove(rc->bin, rc->bin + rc->binoff, rc->binlen - rc->binoff);
386 rc->binlen -= rc->binoff; rc->binoff = 0;
388 /* Satisfy the caller from the input stream, and try to fill up as much of
389 * the rest of the buffer as we can.
391 ret = recv_all(tv, rc, rc->binlen ? 0 : f,
392 rc->bin + rc->binlen, rc->binsz - rc->binlen,
393 want - rc->binlen, &sz);
394 if (ret) return (ret);
396 /* Note how much material we have and return. */
397 rc->binlen += sz; return (RECV_OK);
400 /* --- @remote_recv@ --- *
402 * Arguments: @struct tvec_state *tv@ = test-vector state
403 * @unsigned f@ = flags (@RCVF_...@)
404 * @buf *b_out@ = buffer to establish around the packet contents
406 * Returns: A @RECV_...@ code.
408 * Use: Receive a packet into the input buffer @rc->bin@ and
409 * establish @*b_out@ to read from it.
412 static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
413 unsigned f, buf *b_out)
419 ASSIGN64(szmax, (size_t)-1);
421 /* Preflight checks. */
422 if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
424 /* See if we can read the next packet length from what we already have. */
425 ret = receive_buffered(tv, rc, f, 8); if (ret) return (ret);
426 LOAD64_L_(k, rc->bin + rc->binoff); rc->binoff += 8;
427 if (CMP64(k, >, szmax))
428 return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range",
429 (unsigned long)HI64(k), (unsigned long)LO64(k)));
430 want = GET64(size_t, k);
432 /* Read the next packet payload. */
433 ret = receive_buffered(tv, rc, 0, want); if (ret) return (ret);
434 buf_init(b_out, rc->bin + rc->binoff, want); rc->binoff += want;
438 /* --- @QUEUEPK_TAG@, @QUEUEPK@ --- *
440 * Arguments: @tag@ = control structure tag
441 * @struct tvec_state *tv@ = test-vector state
442 * @struct tvec_remotecomms *rc@ = communication state
443 * @unsigned fl@ = flags (@QF_...@)
444 * @unsigned pk@ = packet type
446 * Use: This is syntactically a statement head: the syntax is
447 * @QUEUEPK(tv, rc, f) body [else alt]@. The @body@ should
448 * write material to the output buffer @rc->bout@. The macro
449 * applies appropriate framing. If enough material has been
450 * collected, or if @QF_FORCE@ is set in @fl@, then
451 * @remote_send@ is invoked to transmit the buffered packets.
452 * If there is an error of any kind, then the @alt@ statement,
453 * if any, is executed.
457 #define QUEUEPK_TAG(tag, tv, rc, fl, pk) \
458 if ((rc)->f&TVRF_BROKEN) MC_GOELSE(tag##__else); else \
459 MC_ALLOWELSE(tag##__else) \
460 MC_AFTER(tag##__send, { \
461 if ((DBBAD(&(rc)->bout) && (buferr((tv), (rc)), 1)) || \
462 ((((fl)&QF_FORCE) || DBLEN(&(rc)->bout) >= SENDBUFSZ) && \
463 remote_send(tv, rc))) \
464 MC_GOELSE(tag##__else); \
466 DBUF_ENCLOSEITAG(tag##__frame, &(rc)->bout, (rc)->t, 64_L) \
467 MC_BEFORE(tag##__pkty, { \
468 dbuf_putu16l(&(rc)->bout, (pk)); \
471 #define QUEUEPK(tv, rc, fl, pk) QUEUEPK_TAG(queue, (tv), (rc), (fl), (pk))
473 /*----- Packet types ------------------------------------------------------*/
475 #define TVPF_ACK 0x0001u
477 #define TVPK_VER 0x0000u /* --> min, max: u16 *
479 #define TVPK_BGROUP 0x0002u /* --> name: str16
481 #define TVPK_SETVAR 0x0004u /* --> name: str16, rv: value
483 #define TVPK_TEST 0x0006u /* --> in: regs
485 #define TVPK_EGROUP 0x0008u /* --> --- *
488 #define TVPK_REPORT 0x0100u /* <-- level: u16; msg: string */
489 #define TVPK_PROGRESS 0x0102u /* <-- st: str16 */
491 #define TVPK_SKIPGRP 0x0104u /* <-- excuse: str16 */
492 #define TVPK_SKIP 0x0106u /* <-- excuse: str16 */
493 #define TVPK_FAIL 0x0108u /* <-- flag: u8, detail: str16 */
494 #define TVPK_DUMPREG 0x010au /* <-- ri: u16; disp: u16;
495 * flag: u8, rv: value */
496 #define TVPK_BBENCH 0x010cu /* <-- ident: str32; unit: u16 */
497 #define TVPK_EBENCH 0x010eu /* <-- ident: str32; unit: u16;
498 * flags: u16; n, t, cy: f64 */
500 /*----- Server ------------------------------------------------------------*/
502 /* Forward declaration of output operations. */
503 static const struct tvec_outops remote_ops;
505 static struct tvec_state srvtv; /* server's test-vector state */
506 static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; /* comms */
507 static struct tvec_output srvout = { &remote_ops }; /* output state */
509 /* --- @tvec_setprogress@, @tvec_setprogress_v@ --- *
511 * Arguments: @const char *status@ = progress status token format
512 * @va_list ap@ = argument tail
516 * Use: Reports the progress of a test execution to the client.
518 * The framework makes use of tokens beginning with %|%|%:
520 * * %|%IDLE|%: during the top-level server code;
522 * * %|%SETUP|%: during the enclosing environment's @before@
525 * * %|%RUN|%: during the environment's @run@ function, or the
528 * * %|%DONE|%: during the enclosing environment's @after@
531 * The intent is that a test can use the progress token to check
532 * that a function which is expected to crash does so at the
533 * correct point, so it's expected that more complex test
534 * functions and/or environments will set their own progress
535 * tokens to reflect what's going on.
538 int tvec_setprogress(const char *status, ...)
543 va_start(ap, status); rc = tvec_setprogress_v(status, &ap); va_end(ap);
547 int tvec_setprogress_v(const char *status, va_list *ap)
549 /* Force immediate output in case we crash before the buffer is output
552 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_PROGRESS)
553 dbuf_vputstrf16l(&srvrc.bout, status, ap);
558 /* --- @tvec_remoteserver@ --- *
560 * Arguments: @int infd@, @int outfd@ = input and output file descriptors
561 * @const struct tvec_config *config@ = test configuration
563 * Returns: Suggested exit code.
565 * Use: Run a test server, reading packets from @infd@ and writing
566 * responses and notifications to @outfd@, and invoking tests as
567 * described by @config@.
569 * This function is not particularly general purpose. It
570 * expects to `take over' the process, and makes use of private
574 int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
580 const struct tvec_test *t;
582 const struct tvec_env *env = 0;
583 const struct tvec_vardef *vd = 0; void *varctx;
584 struct tvec_reg *r = 0, rbuf, *r_alloc = 0; size_t rsz = 0;
588 /* Initialize the communication machinery. */
589 setup_comms(&srvrc, infd, outfd);
591 /* Begin a test session using our custom output driver. */
592 tvec_begin(&srvtv, config, &srvout);
594 /* Version negotiation. Expect a @TVPK_VER@ packet. At the moment,
595 * there's only version zero, so we return that.
597 if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
598 if (buf_getu16l(&b, &pk)) goto bad;
599 if (pk != TVPK_VER) {
600 rc = ioerr(&srvtv, &srvrc,
601 "unexpected packet type 0x%04x instead of client version",
605 if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad;
606 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_VER | TVPF_ACK)
607 dbuf_putu16l(&srvrc.bout, 0);
608 else { rc = -1; goto end; }
610 /* Handle packets until the server closes the connection.
612 * The protocol looks much simpler from our point of view than from the
615 * * Receive @TVPK_VER@; respond with @TVPK_VER | TVPF_ACK@.
617 * * Receive zero or more @TVPK_BGROUP@. Open a test group, producing
618 * output packets, and eventually answer with @TVPK_BGROUP | TVPF_ACK@.
620 * -- Receive zero or more @TVPK_TEST@. Run a test, producing output
621 * packets, and eventually answer with @TVPK_TEST | TVPF_ACK@.
623 * -- Receive @TVPK_EGROUP@. Maybe produce output packets, and
624 * answer with @TVPK_EGROUP | TVPF_ACK@.
630 /* Read a packet. End-of-file is expected here (and pretty much nowhere
631 * else). Otherwise, we expect to see @TVPK_BGROUP@.
633 rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b);
634 if (rc == RECV_EOF) break;
635 else if (rc == RECV_FAIL) goto end;
636 if (buf_getu16l(&b, &pk)) goto bad;
643 /* Parse the packet payload. */
644 p = buf_getmem16l(&b, &sz); if (!p) goto bad;
645 if (BLEFT(&b)) goto bad;
647 /* Find the group given its name. */
648 for (t = srvtv.tests; t->name; t++)
649 if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz))
651 rc = ioerr(&srvtv, &srvrc, "unknown test group `%.*s'",
656 /* Set up the test environment. */
657 srvtv.test = t; env = t->env;
658 if (env && env->setup == tvec_remotesetup)
659 env = ((struct tvec_remoteenv *)env)->r.env;
660 if (!env || !env->ctxsz) ctx = 0;
661 else ctx = xmalloc(env->ctxsz);
662 if (env && env->setup) env->setup(&srvtv, env, 0, ctx);
664 /* Initialize the registers. */
665 tvec_initregs(&srvtv);
667 /* Report that the group has been opened and that we're ready to run
670 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_BGROUP | TVPF_ACK);
671 else { rc = -1; goto end; }
673 /* Handle packets until we're told to end the group. */
676 /* Read a packet. We expect @TVPK_EGROUP@ or @TVPK_TEST@. */
677 if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
678 if (buf_getu16l(&b, &pk)) goto bad;
685 /* Check the payload. */
686 if (BLEFT(&b)) goto bad;
688 /* Leave the group loop. */
692 /* Set a subenvironment variable. */
694 /* Get the variable name. */
695 p = buf_getmem16l(&b, &sz); if (!p) goto bad;
696 DRESET(&d); DPUTM(&d, p, sz); DPUTZ(&d);
698 /* Look up the variable definition. */
699 if (env && env->findvar) {
700 vd = env->findvar(&srvtv, d.buf, &varctx, ctx);
701 if (vd) goto found_var;
703 rc = tvec_unkreg(&srvtv, d.buf); goto setvar_end;
706 /* Set up the register. */
707 if (vd->regsz <= sizeof(rbuf))
710 GROWBUF_REPLACE(&arena_stdlib, r_alloc, rsz, vd->regsz,
711 8*sizeof(void *), 1);
715 /* Collect and set the value. */
716 vd->def.ty->init(&r->v, &vd->def);
717 if (vd->def.ty->frombuf(&b, &r->v, &vd->def)) goto bad;
718 if (BLEFT(&b)) goto bad;
719 rc = vd->setvar(&srvtv, d.buf, &r->v, varctx);
721 /* Send the reply. */
723 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_SETVAR | TVPF_ACK)
724 dbuf_putbyte(&srvrc.bout, rc ? 0xff : 0);
725 else { rc = -1; goto end; }
726 if (vd) { vd->def.ty->release(&r->v, &vd->def); vd = 0; }
732 /* Parse the packet payload. */
733 if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs,
734 srvtv.nreg, srvtv.regsz))
736 if (BLEFT(&b)) goto bad;
738 /* If we're not skipping the test group, then actually try to
741 if (!(srvtv.f&TVSF_SKIP)) {
743 /* Prepare the output registers and reset the test outcome.
744 * (The environment may force a skip.)
746 for (i = 0; i < srvtv.nrout; i++)
747 if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
748 TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
749 srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK;
751 /* Invoke the environment @before@ function. */
752 tvec_setprogress("%%SETUP");
753 if (env && env->before) env->before(&srvtv, ctx);
755 /* Run the actual test. */
756 if (!(srvtv.f&TVSF_ACTIVE))
757 /* setup forced a skip */;
759 tvec_setprogress("%%RUN");
761 env->run(&srvtv, t->fn, ctx);
763 t->fn(srvtv.in, srvtv.out, ctx);
764 tvec_check(&srvtv, 0);
768 /* Conclude the test. */
769 tvec_setprogress("%%DONE");
770 if (env && env->after) env->after(&srvtv, ctx);
771 tvec_endtest(&srvtv);
774 /* Reset the input registers and report completion. */
775 tvec_releaseregs(&srvtv); tvec_initregs(&srvtv);
776 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_TEST | TVPF_ACK);
777 else { rc = -1; goto end; }
781 /* Some other kind of packet. Complain. */
783 rc = ioerr(&srvtv, &srvrc,
784 "unexpected packet type 0x%04x during test group",
792 /* The test group completed. */
794 /* Tear down the environment and release other resources. */
795 if (env && env->teardown) env->teardown(&srvtv, ctx);
796 tvec_releaseregs(&srvtv);
797 xfree(ctx); srvtv.test = 0; env = 0; ctx = 0;
799 /* Report completion. */
800 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_EGROUP | TVPF_ACK);
801 else { rc = -1; goto end; }
805 rc = ioerr(&srvtv, &srvrc,
806 "unexpected packet type 0x%04x at top level", pk);
812 /* Clean up and return. */
813 if (env && env->teardown) env->teardown(&srvtv, ctx);
814 if (vd) vd->def.ty->release(&r->v, &vd->def);
815 xfree(ctx); xfree(r_alloc);
816 if (srvtv.test) tvec_releaseregs(&srvtv);
817 release_comms(&srvrc); tvec_end(&srvtv);
821 /* Miscellaneous malformed packet. */
822 rc = malformed(&srvtv, &srvrc); goto end;
825 /*----- Server output driver ----------------------------------------------*/
827 /* --- @remote_bsession@ --- *
829 * Arguments: @struct tvec_output *o@ = output sink (ignored)
830 * @struct tvec_state *tv@ = the test state producing output
834 * Use: Begin a test session.
836 * The remote driver does nothing at all.
839 static void remote_bsession(struct tvec_output *o, struct tvec_state *tv)
842 /* --- @remote_esession@ --- *
844 * Arguments: @struct tvec_output *o@ = output sink (ignored)
846 * Returns: Suggested exit code.
848 * Use: End a test session.
850 * The remote driver returns a suitable exit code without
854 static int remote_esession(struct tvec_output *o)
855 { return (srvtv.f&TVSF_ERROR ? 2 : 0); }
857 /* --- @remote_bgroup@ --- *
859 * Arguments: @struct tvec_output *o@ = output sink (ignored)
863 * Use: Begin a test group.
865 * This is a stub which should never be called.
868 static void remote_bgroup(struct tvec_output *o)
869 { assert(!"remote_bgroup"); }
871 /* --- @remote_skipgroup@ --- *
873 * Arguments: @struct tvec_output *o@ = output sink (ignored)
874 * @const char *excuse@, @va_list *ap@ = reason for skipping the
879 * Use: Report that a test group is being skipped.
881 * The remote driver sends a @TVPK_SKIP@ packet to its client.
884 static void remote_skipgroup(struct tvec_output *o,
885 const char *excuse, va_list *ap)
887 QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIPGRP)
888 dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
891 /* --- @remote_egroup@ --- *
893 * Arguments: @struct tvec_output *o@ = output sink (ignored)
897 * Use: Report that a test group has finished.
899 * This is a stub which should never be called.
902 static void remote_egroup(struct tvec_output *o)
903 { assert(!"remote_egroup"); }
905 /* --- @remote_btest@ --- *
907 * Arguments: @struct tvec_output *o@ = output sink (ignored)
911 * Use: Report that a test is starting.
913 * This is a stub which should never be called.
916 static void remote_btest(struct tvec_output *o)
917 { assert(!"remote_btest"); }
919 /* --- @remote_skip@, @remote_fail@ --- *
921 * Arguments: @struct tvec_output *o@ = output sink (ignored)
922 * @unsigned attr@ = attribute to apply to the outcome
923 * @const char *outcome@ = outcome string to report
924 * @const char *detail@, @va_list *ap@ = a detail message
925 * @const char *excuse@, @va_list *ap@ = reason for skipping the
930 * Use: Report that a test has been skipped or failed.
932 * The remote driver sends a @TVPK_SKIP@ or @TVPK_FAIL@ packet
933 * to its client as appropriate.
936 static void remote_skip(struct tvec_output *o,
937 const char *excuse, va_list *ap)
939 QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIP)
940 dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
943 static void remote_fail(struct tvec_output *o,
944 const char *detail, va_list *ap)
946 QUEUEPK(&srvtv, &srvrc, 0, TVPK_FAIL)
948 dbuf_putbyte(&srvrc.bout, 0);
950 dbuf_putbyte(&srvrc.bout, 1);
951 dbuf_vputstrf16l(&srvrc.bout, detail, ap);
955 /* --- @remote_dumpreg@ --- *
957 * Arguments: @struct tvec_output *o@ = output sink (ignored)
958 * @unsigned disp@ = register disposition
959 * @const union tvec_regval *rv@ = register value
960 * @const struct tvec_regdef *rd@ = register definition
964 * Use: Dump a register.
966 * The remote driver sends a @TVPK_DUMPREG@ packet to its
967 * client. This will only work if the register definition is
968 * one of those listed in the current test definition.
971 static void remote_dumpreg(struct tvec_output *o,
972 unsigned disp, const union tvec_regval *rv,
973 const struct tvec_regdef *rd)
975 const struct tvec_regdef *reg;
978 /* Find the register definition. */
979 for (reg = srvtv.test->regs, r = 0; reg->name; reg++, r++)
980 if (reg == rd) goto found;
981 assert(!"unexpected register definition");
984 QUEUEPK(&srvtv, &srvrc, 0, TVPK_DUMPREG) {
985 dbuf_putu16l(&srvrc.bout, r);
986 dbuf_putu16l(&srvrc.bout, disp);
988 dbuf_putbyte(&srvrc.bout, 0);
990 dbuf_putbyte(&srvrc.bout, 1);
991 rd->ty->tobuf(DBUF_BUF(&srvrc.bout), rv, rd);
996 /* --- @remote_etest@ --- *
998 * Arguments: @struct tvec_output *o@ = output sink (ignored)
999 * @unsigned outcome@ = the test outcome
1003 * Use: Report that a test has finished.
1005 * The remote driver does nothing at all.
1008 static void remote_etest(struct tvec_output *o, unsigned outcome)
1011 /* --- @remote_bbench@ --- *
1013 * Arguments: @struct tvec_output *o@ = output sink (ignored)
1014 * @const char *ident@ = identifying register values
1015 * @unsigned unit@ = measurement unit (@TVBU_...@)
1019 * Use: Report that a benchmark has started.
1021 * The remote driver sends a @TVPK_BBENCH@ packet to its client.
1024 static void remote_bbench(struct tvec_output *o,
1025 const char *ident, unsigned unit)
1027 QUEUEPK(&srvtv, &srvrc, 0, TVPK_BBENCH) {
1028 dbuf_putstr32l(&srvrc.bout, ident);
1029 dbuf_putu16l(&srvrc.bout, unit);
1033 /* --- @remote_ebench@ --- *
1035 * Arguments: @struct tvec_output *o@ = output sink (ignored)
1036 * @const char *ident@ = identifying register values
1037 * @unsigned unit@ = measurement unit (@TVBU_...@)
1038 * @const struct bench_timing *tm@ = measurement
1042 * Use: Report a benchmark's results
1044 * The remote driver sends a @TVPK_EBENCH@ packet to its client.
1047 static void remote_ebench(struct tvec_output *o,
1048 const char *ident, unsigned unit,
1049 const struct bench_timing *t)
1051 QUEUEPK(&srvtv, &srvrc, 0, TVPK_EBENCH) {
1052 dbuf_putstr32l(&srvrc.bout, ident);
1053 dbuf_putu16l(&srvrc.bout, unit);
1054 if (!t || !(t->f&BTF_ANY))
1055 dbuf_putu16l(&srvrc.bout, 0);
1057 dbuf_putu16l(&srvrc.bout, t->f);
1058 dbuf_putf64l(&srvrc.bout, t->n);
1059 if (t->f&BTF_TIMEOK) dbuf_putf64l(&srvrc.bout, t->t);
1060 if (t->f&BTF_CYOK) dbuf_putf64l(&srvrc.bout, t->cy);
1065 /* --- @remote_report@ --- *
1067 * Arguments: @struct tvec_output *o@ = output sink (ignored)
1068 * @unsigned level@ = message level (@TVLEV_...@)
1069 * @const char *msg@, @va_list *ap@ = format string and
1074 * Use: Report a message to the user.
1076 * The remote driver sends a @TVPK_REPORT@ packet to its
1077 * client. If its attempt to transmit the packet fails, then
1078 * the message is written to the standard error stream instead,
1079 * in the hope that this will help it be noticed.
1082 static void remote_report(struct tvec_output *o, unsigned level,
1083 const char *msg, va_list *ap)
1085 QUEUEPK(&srvtv, &srvrc, 0, TVPK_REPORT) {
1086 dbuf_putu16l(&srvrc.bout, level);
1087 dbuf_vputstrf16l(&srvrc.bout, msg, ap);
1089 fprintf(stderr, "%s %s: ", QUIS, tvec_strlevel(level));
1090 vfprintf(stderr, msg, *ap);
1091 fputc('\n', stderr);
1095 /* --- @remote_destroy@ --- *
1097 * Arguments: @struct tvec_output *o@ = output sink (ignored)
1101 * Use: Release the resources held by the output driver.
1103 * The remote driver does nothing at all.
1106 static void remote_destroy(struct tvec_output *o)
1109 static const struct tvec_outops remote_ops = {
1110 remote_bsession, remote_esession,
1111 remote_bgroup, remote_skipgroup, remote_egroup,
1112 remote_btest, remote_skip, remote_fail, remote_dumpreg, remote_etest,
1113 remote_bbench, remote_ebench,
1118 /*----- Pseudoregister definitions ----------------------------------------*/
1120 static tvec_setvarfn setvar_local, setvar_remote;
1122 static const struct tvec_flag exit_flags[] = {
1125 { "running", TVXF_CAUSEMASK, TVXST_RUN },
1126 { "exited", TVXF_CAUSEMASK, TVXST_EXIT },
1127 { "killed", TVXF_CAUSEMASK, TVXST_KILL },
1128 { "stopped", TVXF_CAUSEMASK, TVXST_STOP },
1129 { "continued", TVXF_CAUSEMASK, TVXST_CONT },
1130 { "disconnected", TVXF_CAUSEMASK, TVXST_DISCONN },
1131 { "unknown", TVXF_CAUSEMASK, TVXST_UNK },
1132 { "error", TVXF_CAUSEMASK, TVXST_ERR },
1135 ;;; The signal name table is very boring to type. To make life less
1136 ;;; awful, put the signal names in this list and evaluate the code to
1137 ;;; get Emacs to regenerate it.
1139 (let ((signals '(HUP INT QUIT ILL TRAP ABRT IOT EMT FPE KILL BUS SEGV SYS
1140 PIPE ALRM TERM URG STOP TSTP CONT CHLD CLD TTIN TTOU
1141 POLL IO TIN XCPU XFSZ VTALRM PROF WINCH USR1 USR2
1142 STKFLT INFO PWR THR LWP LIBRT LOST)))
1144 (goto-char (point-min))
1145 (search-forward (concat "***" "BEGIN siglist" "***"))
1146 (beginning-of-line 2)
1147 (delete-region (point)
1149 (search-forward "***END***")
1152 (dolist (sig signals)
1153 (insert (format "#ifdef SIG%s\n { \"SIG%s\", TVXF_VALMASK | TVXF_SIG, SIG%s | TVXF_SIG },\n#endif\n"
1157 /***BEGIN siglist***/
1159 { "SIGHUP", TVXF_VALMASK | TVXF_SIG, SIGHUP | TVXF_SIG },
1162 { "SIGINT", TVXF_VALMASK | TVXF_SIG, SIGINT | TVXF_SIG },
1165 { "SIGQUIT", TVXF_VALMASK | TVXF_SIG, SIGQUIT | TVXF_SIG },
1168 { "SIGILL", TVXF_VALMASK | TVXF_SIG, SIGILL | TVXF_SIG },
1171 { "SIGTRAP", TVXF_VALMASK | TVXF_SIG, SIGTRAP | TVXF_SIG },
1174 { "SIGABRT", TVXF_VALMASK | TVXF_SIG, SIGABRT | TVXF_SIG },
1177 { "SIGIOT", TVXF_VALMASK | TVXF_SIG, SIGIOT | TVXF_SIG },
1180 { "SIGEMT", TVXF_VALMASK | TVXF_SIG, SIGEMT | TVXF_SIG },
1183 { "SIGFPE", TVXF_VALMASK | TVXF_SIG, SIGFPE | TVXF_SIG },
1186 { "SIGKILL", TVXF_VALMASK | TVXF_SIG, SIGKILL | TVXF_SIG },
1189 { "SIGBUS", TVXF_VALMASK | TVXF_SIG, SIGBUS | TVXF_SIG },
1192 { "SIGSEGV", TVXF_VALMASK | TVXF_SIG, SIGSEGV | TVXF_SIG },
1195 { "SIGSYS", TVXF_VALMASK | TVXF_SIG, SIGSYS | TVXF_SIG },
1198 { "SIGPIPE", TVXF_VALMASK | TVXF_SIG, SIGPIPE | TVXF_SIG },
1201 { "SIGALRM", TVXF_VALMASK | TVXF_SIG, SIGALRM | TVXF_SIG },
1204 { "SIGTERM", TVXF_VALMASK | TVXF_SIG, SIGTERM | TVXF_SIG },
1207 { "SIGURG", TVXF_VALMASK | TVXF_SIG, SIGURG | TVXF_SIG },
1210 { "SIGSTOP", TVXF_VALMASK | TVXF_SIG, SIGSTOP | TVXF_SIG },
1213 { "SIGTSTP", TVXF_VALMASK | TVXF_SIG, SIGTSTP | TVXF_SIG },
1216 { "SIGCONT", TVXF_VALMASK | TVXF_SIG, SIGCONT | TVXF_SIG },
1219 { "SIGCHLD", TVXF_VALMASK | TVXF_SIG, SIGCHLD | TVXF_SIG },
1222 { "SIGCLD", TVXF_VALMASK | TVXF_SIG, SIGCLD | TVXF_SIG },
1225 { "SIGTTIN", TVXF_VALMASK | TVXF_SIG, SIGTTIN | TVXF_SIG },
1228 { "SIGTTOU", TVXF_VALMASK | TVXF_SIG, SIGTTOU | TVXF_SIG },
1231 { "SIGPOLL", TVXF_VALMASK | TVXF_SIG, SIGPOLL | TVXF_SIG },
1234 { "SIGIO", TVXF_VALMASK | TVXF_SIG, SIGIO | TVXF_SIG },
1237 { "SIGTIN", TVXF_VALMASK | TVXF_SIG, SIGTIN | TVXF_SIG },
1240 { "SIGXCPU", TVXF_VALMASK | TVXF_SIG, SIGXCPU | TVXF_SIG },
1243 { "SIGXFSZ", TVXF_VALMASK | TVXF_SIG, SIGXFSZ | TVXF_SIG },
1246 { "SIGVTALRM", TVXF_VALMASK | TVXF_SIG, SIGVTALRM | TVXF_SIG },
1249 { "SIGPROF", TVXF_VALMASK | TVXF_SIG, SIGPROF | TVXF_SIG },
1252 { "SIGWINCH", TVXF_VALMASK | TVXF_SIG, SIGWINCH | TVXF_SIG },
1255 { "SIGUSR1", TVXF_VALMASK | TVXF_SIG, SIGUSR1 | TVXF_SIG },
1258 { "SIGUSR2", TVXF_VALMASK | TVXF_SIG, SIGUSR2 | TVXF_SIG },
1261 { "SIGSTKFLT", TVXF_VALMASK | TVXF_SIG, SIGSTKFLT | TVXF_SIG },
1264 { "SIGINFO", TVXF_VALMASK | TVXF_SIG, SIGINFO | TVXF_SIG },
1267 { "SIGPWR", TVXF_VALMASK | TVXF_SIG, SIGPWR | TVXF_SIG },
1270 { "SIGTHR", TVXF_VALMASK | TVXF_SIG, SIGTHR | TVXF_SIG },
1273 { "SIGLWP", TVXF_VALMASK | TVXF_SIG, SIGLWP | TVXF_SIG },
1276 { "SIGLIBRT", TVXF_VALMASK | TVXF_SIG, SIGLIBRT | TVXF_SIG },
1279 { "SIGLOST", TVXF_VALMASK | TVXF_SIG, SIGLOST | TVXF_SIG },
1283 /* This should be folded into the signal entries above. */
1284 { "signal", TVXF_SIG, TVXF_SIG },
1288 static const struct tvec_flaginfo exit_flaginfo =
1289 { "exit-status", exit_flags, &tvrange_uint };
1290 static const struct tvec_vardef exit_var =
1291 { sizeof(struct tvec_reg), setvar_local,
1292 { "@exit", -1, &tvty_flags, 0, { &exit_flaginfo } } };
1296 static const struct tvec_vardef progress_var =
1297 { sizeof(struct tvec_reg), setvar_local,
1298 { "@progress", -1, &tvty_text, 0 } };
1302 static const struct tvec_uassoc reconn_assocs[] = {
1303 { "on-demand", TVRCN_DEMAND },
1304 { "force", TVRCN_FORCE },
1305 { "skip", TVRCN_SKIP },
1308 static const struct tvec_uenuminfo reconn_enuminfo =
1309 { "remote-reconnection", reconn_assocs, &tvrange_uint };
1310 static const struct tvec_vardef reconn_var =
1311 { sizeof(struct tvec_reg), setvar_local,
1312 { "@reconnect", -1, &tvty_uenum, 0, { &reconn_enuminfo } } };
1314 /*----- Client ------------------------------------------------------------*/
1316 /* Connection state. */
1318 CONN_BROKEN = -2, /* previously broken */
1319 CONN_FAILED = -1, /* attempt freshly failed */
1320 CONN_ESTABLISHED = 0, /* previously established */
1321 CONN_FRESH = 1 /* freshly connected */
1324 /* --- @handle_packets@ --- *
1326 * Arguments: @struct tvec_state *tv@ = test-vector state
1327 * @struct tvec_remotectx *r@ = remote client context
1328 * @unsigned f@ = receive flags (@RCVF_...@)
1329 * @uint16 end@ = expected end packet type
1330 * @buf *b_out@ = buffer in which to return end packet payload
1332 * Returns: A @RECV_...@ code.
1334 * Use: Handles notification packets from the server until a final
1335 * termination packet is received.
1337 * The client/server protocol consists of a number of flows,
1338 * beginning with a request from the client, followed by a
1339 * number of notifications from the server, and terminated by an
1340 * acknowledgement to the original request indicating that the
1341 * server has completed acting on the original request.
1343 * This function handles the notifications issued by the server,
1344 * returning when one of the following occurs: (a) a packet of
1345 * type @end@ is received, in which case the function returns
1346 * @RECV_OK@ and the remainder of the packet payload is left in
1347 * @b_out@; (b) the flag @RCVF_ALLOWEOF@ was set in @f@ on entry
1348 * and end-of-file is received at a packet boundary, in which
1349 * case the function returns @RECV_EOF@; or (c) an I/O error
1350 * occurs, in which case @ioerr@ is called and the function
1351 * returns @RECV_FAIL@.
1354 static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
1355 unsigned f, uint16 end, buf *b_out)
1357 struct tvec_output *o = tv->output;
1359 const char *p; size_t n;
1362 const struct tvec_regdef *rd;
1363 struct bench_timing bt;
1364 struct tvec_reg *reg = 0;
1370 /* Read the next packet. If we didn't receive one then end the loop.
1371 * Otherwise, retrieve the packet type and check it against @end@: quit
1372 * the loop if we get a match.
1374 rc = remote_recv(tv, &r->rc, f, b); if (rc) break;
1375 if (buf_getu16l(b, &pk)) goto bad;
1376 if (pk == end) { rc = 0; break; }
1378 /* Dispatch based on the packet type. */
1382 /* A progress report. Update the saved progress. */
1384 p = buf_getmem16l(b, &n); if (!p) goto bad;
1385 if (BLEFT(b)) goto bad;
1387 DRESET(&r->progress); DPUTM(&r->progress, p, n); DPUTZ(&r->progress);
1391 /* A report. Recover the message and pass it along. */
1393 if (buf_getu16l(b, &u)) goto bad;
1394 p = buf_getmem16l(b, &n); if (!p) goto bad;
1395 if (BLEFT(b)) goto bad;
1397 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1398 tvec_report(tv, u, "%s", d.buf);
1402 /* A request to skip the group. Recover the excuse message and pass
1406 p = buf_getmem16l(b, &n); if (!p) goto bad;
1407 if (BLEFT(b)) goto bad;
1409 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1410 tvec_skipgroup(tv, "%s", d.buf);
1414 /* A request to skip the test. Recover the excuse message and pass
1415 * it along, if it's not unreasonable.
1418 if (!(tv->f&TVSF_ACTIVE)) {
1419 rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name);
1423 p = buf_getmem16l(b, &n); if (!p) goto bad;
1424 if (BLEFT(b)) goto bad;
1426 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1427 tvec_skip(tv, "%s", d.buf);
1431 /* A report that the test failed. Recover the detail message, if
1432 * any, and pass it along, if it's not unreasonable.
1435 if (!(tv->f&TVSF_ACTIVE) &&
1436 ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) {
1437 rc = ioerr(tv, &r->rc, "test `%s' not active or failing",
1442 rc = buf_getbyte(b); if (rc < 0) goto bad;
1443 if (rc) { p = buf_getmem16l(b, &n); if (!p) goto bad; }
1445 if (BLEFT(b)) goto bad;
1450 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1451 tvec_fail(tv, "%s", d.buf);
1456 /* A request to dump a register. */
1458 /* Find the register definition. */
1459 if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
1460 for (rd = tv->test->regs, i = 0; rd->name; rd++, i++)
1461 if (i == u) goto found_reg;
1462 rc = ioerr(tv, &r->rc,
1463 "register definition %u out of range for test `%s'",
1467 if (v >= TVRD_LIMIT) {
1468 rc = ioerr(tv, &r->rc, "register disposition %u out of range", v);
1472 /* Read the flag. If there's no register value, then `dump' its
1473 * absence. Otherwise retrieve the register value and dump it.
1475 rc = buf_getbyte(b); if (rc < 0) goto bad;
1477 tvec_dumpreg(tv, v, 0, rd);
1479 if (!reg) reg = xmalloc(tv->regsz);
1480 rd->ty->init(®->v, rd);
1481 rc = rd->ty->frombuf(b, ®->v, rd);
1482 if (!rc) tvec_dumpreg(tv, v, ®->v, rd);
1483 rd->ty->release(®->v, rd);
1486 if (BLEFT(b)) goto bad;
1490 /* A report that we're starting a benchmark. Pass this along. */
1492 p = buf_getmem32l(b, &n); if (!p) goto bad;
1493 if (buf_getu16l(b, &u)) goto bad;
1494 if (BLEFT(b)) goto bad;
1495 if (u >= TVBU_LIMIT) {
1496 rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
1500 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1501 o->ops->bbench(o, d.buf, u);
1505 /* A report that a benchmark completed. Pass this along. */
1507 p = buf_getmem32l(b, &n); if (!p) goto bad;
1508 if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
1509 if (u >= TVBU_LIMIT) {
1510 rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
1513 if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad;
1514 if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad;
1515 if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad;
1516 if (BLEFT(b)) goto bad;
1518 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1519 o->ops->ebench(o, d.buf, u, v&BTF_ANY ? &bt : 0);
1523 /* Something else. This is unexpected. */
1525 rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
1535 rc = malformed(tv, &r->rc); goto end;
1538 /* --- @reap_kid@ --- *
1540 * Arguments: @struct tvec_state *tv@ = test-vector state
1541 * @struct tvec_remotectx *r@ = remote client context
1545 * Use: Determine the exit status of a broken connection, setting
1546 * @r->exit@ appropriately.
1548 * If @r->kid@ is negative, the exit status has already been
1549 * set, and nothing further happens; this is not an error.
1551 * If @r->kid@ is zero, then there is no real child process
1552 * (e.g., because the remote connection is a network connection
1553 * or similar), so @r->exit@ is set equal to @RVXST_DISCONN@.
1555 * If @r->kid@ is positive, then it holds a child process id;
1556 * the function waits for it to end and collects its exit status
1558 * It is an error to call this function if the connection is not
1562 static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
1567 assert(r->rc.f&TVRF_BROKEN);
1569 { r->exit = TVXST_DISCONN; r->kid = -1; }
1570 else if (r->kid > 0) {
1571 kid = waitpid(r->kid, &st, 0);
1573 tvec_notice(tv, "failed to wait for remote child: %s",
1575 r->exit = TVXST_ERR;
1577 tvec_notice(tv, "remote child vanished without a trace");
1578 r->exit = TVXST_ERR;
1579 } else if (WIFCONTINUED(st))
1580 r->exit = TVXST_CONT;
1581 else if (WIFSIGNALED(st))
1582 r->exit = TVXST_KILL | TVXF_SIG | WTERMSIG(st);
1583 else if (WIFSTOPPED(st))
1584 r->exit = TVXST_STOP | TVXF_SIG | WSTOPSIG(st);
1585 else if (WIFEXITED(st))
1586 r->exit = TVXST_EXIT | WEXITSTATUS(st);
1588 tvec_notice(tv, "remote child died with unknown status 0x%04x",
1590 r->exit = TVXST_UNK;
1596 /* --- @report_errline@ --- *
1598 * Arguments: @char *p@ = pointer to the line
1599 * @size_t n@ = length in characters
1600 * @void *ctx@ = context, secretly a @struct tvec_remotectx@
1604 * Use: Print a line of stderr output from the child. If
1605 * @TVRF_MUFFLE@ is set, then discard the line silently.
1607 * This is an @lbuf_func@, invoked via @drain_errfd@.
1610 static void report_errline(char *p, size_t n, void *ctx)
1612 struct tvec_remotectx *r = ctx;
1613 struct tvec_state *tv = r->tv;
1615 if (p && !(r->rc.f&TVRF_MUFFLE))
1616 tvec_notice(tv, "child process stderr: %s", p);
1619 /* --- @drain_errfd@ --- *
1621 * Arguments: @struct tvec_state *tv@ = test-vector state
1622 * @struct tvec_remotectx *r@ = remote client context
1623 * @unsigned f@ = receive flags (@ERF_...@)
1625 * Returns: Zero on success, %$-1$% on error.
1627 * Use: Collect material written by the child to its stderr stream
1630 * If @f@ has @ERF_SILENT@ set, then discard the stderr material
1631 * without reporting it. Otherwise it is reported as
1634 * if @f@ has @ERF_CLOSE@ set, then continue reading until
1635 * end-of-file is received; also, report any final partial line,
1636 * and close @r->errfd@.
1638 * If @r->errfd@ is already closed, or never established, then
1639 * do nothing and return successfully.
1642 #define ERF_SILENT 0x0001u
1643 #define ERF_CLOSE 0x0002u
1644 static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
1651 /* Preliminaries. Bail if there is no error stream to fetch. Arrange
1652 * (rather clumsily) to muffle the output if we're supposed to be client.
1653 * And set the nonblocking state on @errfd@ appropriately.
1655 if (r->errfd < 0) { rc = 0; goto end; }
1656 if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
1657 else r->rc.f &= ~TVRF_MUFFLE;
1658 if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) {
1659 rc = ioerr(tv, &r->rc, "failed to %s error non-blocking flag",
1660 f&ERF_CLOSE ? "clear" : "set");
1664 /* Read pieces of error output and feed them into the line buffer. */
1666 sz = lbuf_free(&r->errbuf, &p);
1667 n = read(r->errfd, p, sz);
1670 if (errno == EINTR) continue;
1671 if (!(f&ERF_CLOSE) && (errno == EWOULDBLOCK || errno == EAGAIN))
1673 rc = ioerr(tv, &r->rc, "failed to read child stderr: %s",
1677 lbuf_flush(&r->errbuf, p, n);
1684 lbuf_close(&r->errbuf);
1685 close(r->errfd); r->errfd = -1;
1690 /* --- @disconnect_remote@ --- *
1692 * Arguments: @struct tvec_state *tv@ = test-vector state
1693 * @struct tvec_remotectx *r@ = remote client context
1694 * @unsigned f@ = receive flags (@DCF_...@)
1698 * Use: Disconnect and shut down all of the remote client state.
1700 * If @f@ has @DCF_KILL@ set then send the child process (if
1701 * any) @SIGTERM@ to make sure it shuts down in a timely manner.
1703 * In detail: this function closes the @infd@ and @outfd@
1704 * descriptors, drains and closes @errfd@, and collects the exit
1708 #define DCF_KILL 0x0100u
1709 static void disconnect_remote(struct tvec_state *tv,
1710 struct tvec_remotectx *r, unsigned f)
1712 if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
1713 close_comms(&r->rc);
1714 drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
1717 /* --- @connect_remote@ --- *
1719 * Arguments: @struct tvec_state *tv@ = test-vector state
1720 * @struct tvec_remotectx *r@ = remote client context
1722 * Returns: Zero on success, %$-1$% on error.
1724 * Use: Connect to the test server.
1727 static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
1729 const struct tvec_remoteenv *re = r->re;
1733 int infd = -1, outfd = -1, errfd = -1, rc;
1735 /* If we're already connected, then there's nothing to do. */
1736 if (r->kid >= 0) { rc = 0; goto end; }
1738 /* Set the preliminary progress indication. */
1739 DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
1741 /* Call the connection function to establish descriptors. */
1742 if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re))
1743 { rc = -1; goto end; }
1745 /* Establish communications state. */
1746 setup_comms(&r->rc, infd, outfd); r->kid = kid; r->errfd = errfd;
1747 lbuf_init(&r->errbuf, report_errline, r);
1748 r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN;
1750 /* Do version negotiation. */
1751 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) {
1752 dbuf_putu16l(&r->rc.bout, 0);
1753 dbuf_putu16l(&r->rc.bout, 0);
1754 } else { rc = -1; goto end; }
1755 if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b))
1756 { rc = -1; goto end; }
1757 if (buf_getu16l(&b, &v)) goto bad;
1758 if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1760 rc = ioerr(tv, &r->rc, "protocol version %u not supported", v);
1765 /* Begin the test group at the server. */
1766 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP)
1767 dbuf_putstr16l(&r->rc.bout, tv->test->name);
1768 else { rc = -1; goto end; }
1769 if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b))
1770 { rc = -1; goto end; }
1771 if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1776 if (rc) disconnect_remote(tv, r, DCF_KILL);
1779 rc = malformed(tv, &r->rc); goto end;
1782 /* --- @check_comms@ --- *
1784 * Arguments: @struct tvec_state *tv@ = test-vector state
1785 * @struct tvec_remotectx *r@ = remote client context
1787 * Returns: A @CONN_...@ code reflecting the current communication
1790 * Use: Determine the current connection state. If the connection
1791 * has recently broken (i.e., @TVRF_BROKEN@ is set in @r->rc.f@)
1792 * since the last time we checked then disconnect.
1795 static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r)
1798 return (CONN_BROKEN);
1799 else if (r->rc.f&TVRF_BROKEN)
1800 { disconnect_remote(tv, r, DCF_KILL); return (CONN_FAILED); }
1802 return (CONN_ESTABLISHED);
1805 /* --- @try_reconnect@ --- *
1807 * Arguments: @struct tvec_state *tv@ = test-vector state
1808 * @struct tvec_remotectx *r@ = remote client context
1810 * Returns: A @CONN_...@ code reflecting the new communication state.
1812 * Use: Reconnects to the server according to the configured
1813 * @TVRCN_...@ policy.
1816 static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
1820 switch (r->rc.f&TVRF_RCNMASK) {
1822 rc = check_comms(tv, r);
1823 if (rc < CONN_ESTABLISHED) {
1824 close_comms(&r->rc);
1825 if (connect_remote(tv, r)) rc = CONN_FAILED;
1826 else rc = CONN_FRESH;
1830 disconnect_remote(tv, r, DCF_KILL);
1831 if (connect_remote(tv, r)) rc = CONN_FAILED;
1832 else rc = CONN_FRESH;
1835 rc = check_comms(tv, r);
1843 /*----- Remote environment ------------------------------------------------*/
1845 /* --- @reset_vars@ --- *
1847 * Arguments: @struct tvec_remotectx *r@ = remote client context
1851 * Use: Reset the pseudoregisters set through @tvec_remoteset@.
1854 static void reset_vars(struct tvec_remotectx *r)
1856 const struct tvec_remoteenv *re = r->re;
1858 r->exwant = TVXST_RUN;
1859 r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) |
1860 (re->r.dflt_reconn&TVRF_RCNMASK);
1861 DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
1864 /* --- @tvec_remotesetup@ --- *
1866 * Arguments: @struct tvec_state *tv@ = test vector state
1867 * @const struct tvec_env *env@ = environment description
1868 * @void *pctx@ = parent context (ignored)
1869 * @void *ctx@ = context pointer to initialize
1873 * Use: Initialize a timeout environment context.
1875 * The environment description should be a @struct
1876 * tvec_remoteenv@ subclass suitable for use by the @connect@
1880 void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
1881 void *pctx, void *ctx)
1883 struct tvec_remotectx *r = ctx;
1884 const struct tvec_remoteenv *re = (const struct tvec_remoteenv *)env;
1885 const struct tvec_env *subenv = re->r.env;
1889 r->re = re; r->kid = -1;
1890 DCREATE(&r->prgwant); DCREATE(&r->progress);
1891 if (connect_remote(tv, r))
1892 tvec_skipgroup(tv, "failed to connect to test backend");
1894 if (subenv && subenv->ctxsz) r->subctx = xmalloc(subenv->ctxsz);
1896 if (subenv && subenv->setup) subenv->setup(tv, subenv, r, r->subctx);
1899 /* --- @tvec_remotefindvar@, @setvar_local@, @setvar_remote@ --- *
1901 * Arguments: @struct tvec_state *tv@ = test vector state
1902 * @const char *var@ = variable name to set
1903 * @const union tvec_regval *rv@ = register value
1904 * @void **ctx_out@ = where to put the @setvar@ context
1905 * @void *ctx@ = context pointer
1907 * Returns: @tvec_remotefindvar@ returns a pointer to the variable
1908 * definition, or null; @remote_setvar@ returns zero on success
1909 * or %$-1$% on error.
1911 * Use: Set a special variable. The following special variables are
1914 * * %|@exit|% is the expected exit status; see @TVXF_...@ and
1917 * * %|progress|% is the expected progress token when the test
1918 * completes. On successful completion, this will be
1919 * %|%DONE|%; it's %|%RUN|% on entry to the test function,
1920 * but that can call @tvec_setprogress@ to change it.
1922 * * %|reconnect|% is a reconnection policy; see @TVRCN_...@.
1925 static int setvar_local(struct tvec_state *tv, const char *var,
1926 const union tvec_regval *rv, void *ctx)
1928 struct tvec_remotectx *r = ctx;
1930 if (STRCMP(var, ==, "@exit")) {
1931 if (r->rc.f&TVRF_SETEXIT) return (tvec_dupreg(tv, var));
1932 r->exwant = rv->u; r->rc.f |= TVRF_SETEXIT; return (0);
1933 } else if (STRCMP(var, ==, "@progress")) {
1934 if (r->rc.f&TVRF_SETPRG) return (tvec_dupreg(tv, var));
1935 DRESET(&r->prgwant); DPUTM(&r->prgwant, rv->text.p, rv->text.sz);
1937 r->rc.f |= TVRF_SETPRG; return (0);
1938 } else if (STRCMP(var, ==, "@reconnect")) {
1939 if (r->rc.f&TVRF_SETRCN) return (tvec_dupreg(tv, var));
1940 r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv->u&TVRF_RCNMASK) | TVRF_SETRCN;
1942 } else assert(!"unknown var");
1945 static int setvar_remote(struct tvec_state *tv, const char *var,
1946 const union tvec_regval *rv, void *ctx)
1948 struct tvec_remotectx *r = ctx;
1952 if (try_reconnect(tv, r) < 0) { rc = 0; goto end; }
1954 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_SETVAR) {
1955 dbuf_putstr16l(&r->rc.bout, var);
1956 r->vd.def.ty->tobuf(DBUF_BUF(&r->rc.bout), rv, &r->vd.def);
1957 } else { rc = -1; goto end; }
1959 rc = handle_packets(tv, r, 0, TVPK_SETVAR | TVPF_ACK, &b);
1961 ch = buf_getbyte(&b);
1962 if (ch < 0) { rc = malformed(tv, &r->rc); goto end; }
1963 if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1970 const struct tvec_vardef *tvec_remotefindvar
1971 (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
1973 struct tvec_remotectx *r = ctx;
1974 const struct tvec_remoteenv *re = r->re;
1975 const struct tvec_env *subenv = re->r.env;
1976 const struct tvec_vardef *vd; void *varctx;
1978 if (STRCMP(var, ==, "@exit"))
1979 { *ctx_out = r; return (&exit_var); }
1980 else if (STRCMP(var, ==, "@progress"))
1981 { *ctx_out = r; return (&progress_var); }
1982 else if (STRCMP(var, ==, "@reconnect"))
1983 { *ctx_out = r; return (&reconn_var); }
1984 else if (subenv && subenv->findvar) {
1985 vd = subenv->findvar(tv, var, &varctx, r->subctx);
1986 if (!vd) return (0);
1987 r->vd.regsz = vd->regsz; r->vd.setvar = setvar_remote;
1988 r->vd.def = vd->def;
1989 *ctx_out = r; return (&r->vd);
1994 /* --- @tvec_remotebefore@ --- *
1996 * Arguments: @struct tvec_state *tv@ = test vector state
1997 * @void *ctx@ = context pointer
2001 * Use: Invoke the subordinate environment's @before@ function.
2004 void tvec_remotebefore(struct tvec_state *tv, void *ctx)
2006 struct tvec_remotectx *r = ctx;
2007 const struct tvec_remoteenv *re = r->re;
2008 const struct tvec_env *subenv = re->r.env;
2010 if (subenv && subenv->before) subenv->before(tv, r->subctx);
2013 /* --- @tvec_remoterun@ --- *
2015 * Arguments: @struct tvec_state *tv@ = test vector state
2016 * @tvec_testfn *fn@ = test function to run
2017 * @void *ctx@ = context pointer for the test function
2021 * Use: Run a test on a remote server.
2024 void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
2026 struct tvec_remotectx *r = ctx;
2027 union tvec_regval rv;
2030 #define f_progress 2u
2035 /* Reconnect to the server according to policy. */
2036 switch (try_reconnect(tv, r)) {
2038 tvec_skip(tv, "failed to connect to test backend"); return;
2040 tvec_skip(tv, "no connection"); return;
2043 /* Set initial progress state. */
2044 DRESET(&r->progress); DPUTS(&r->progress, "%IDLE");
2046 /* Send the command to the server and handle output. */
2047 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST)
2048 tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout),
2049 tv->test->regs, tv->nreg, tv->regsz);
2051 rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
2053 /* Deal with the outcome. */
2057 /* Some kind of error. Abandon ship. */
2060 tvec_skip(tv, "remote test runner communications failed");
2061 disconnect_remote(tv, r, 0);
2065 /* End-of-file at a packet boundary. The server crashed trying to run
2066 * our test. Collect the exit status and continue.
2072 /* Successful completion (or EOF). */
2074 /* Notice if the exit status isn't right. */
2075 if (r->exit != r->exwant) f |= f_exit;
2077 /* Notice if the progress token isn't right. */
2078 if (r->progress.len != r->prgwant.len ||
2079 MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len))
2082 /* If we found something wrong but the test is passing so far, then
2083 * report the failure and dump the input registers.
2085 if (f && (tv->f&TVSF_ACTIVE))
2086 { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); }
2088 /* If the test failed, then report the exit and progress states
2089 * relative to their expectations.
2091 if (!(tv->f&TVSF_ACTIVE) &&
2092 (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) {
2094 /* Note here that the test failed. */
2097 /* Report exit status. */
2099 tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
2100 &rv, &exit_var.def);
2103 tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_var.def);
2106 /* Report progress token. */
2107 rv.text.p = r->progress.buf; rv.text.sz = r->progress.len;
2108 tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH,
2109 &rv, &progress_var.def);
2111 rv.text.p = r->prgwant.buf; rv.text.sz = r->prgwant.len;
2112 tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_var.def);
2116 /* If we received end-of-file, then close the connection. Suppress
2117 * error output if the test passed: it was presumably expected.
2120 disconnect_remote(tv, r, f ? 0 : ERF_SILENT);
2129 /* --- @tvec_remoteafter@ --- *
2131 * Arguments: @struct tvec_state *tv@ = test vector state
2132 * @void *ctx@ = context pointer
2136 * Use: Reset variables to their default values.
2139 void tvec_remoteafter(struct tvec_state *tv, void *ctx)
2141 struct tvec_remotectx *r = ctx;
2142 const struct tvec_remoteenv *re = r->re;
2143 const struct tvec_env *subenv = re->r.env;
2146 if (subenv && subenv->after) subenv->after(tv, r->subctx);
2149 /* --- @tvec_remoteteardown@ --- *
2151 * Arguments: @struct tvec_state *tv@ = test vector state
2152 * @void *ctx@ = context pointer
2156 * Use: Tear down the remote environment.
2159 void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
2161 struct tvec_remotectx *r = ctx;
2162 const struct tvec_remoteenv *re = r->re;
2163 const struct tvec_env *subenv = re->r.env;
2166 if (subenv && subenv->teardown) subenv->teardown(tv, r->subctx);
2168 if (r->rc.outfd >= 0) {
2169 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP);
2170 if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b))
2171 if (BLEFT(&b)) malformed(tv, &r->rc);
2173 disconnect_remote(tv, r, 0); release_comms(&r->rc);
2174 DDESTROY(&r->prgwant); DDESTROY(&r->progress);
2177 /*----- Connectors --------------------------------------------------------*/
2179 /* --- @fork_common@ --- *
2181 * Arguments: @pid_t *kid_out@ = where to put child process-id
2182 * @int *infd_out, *outfd_out, *errfd_out@ = where to put file
2184 * @struct tvec_state *tv@ = test vector state
2186 * Returns: Zero on success, %$-1$% on failure.
2188 * Use: Common @fork@ machinery for the connectors. Create a
2189 * subprocess. On successful return, in the subprocess,
2190 * @*kid_out@ is zero, and the error descriptor replaces the
2191 * standard-error descriptor; in the parent, @*kid_out@ is the
2192 * child process-id, and @*errfd_out@ is a descriptor on which
2193 * the child's standard-error output can be read; in both
2194 * @*infd_out@ and @*outfd_out@ are descriptors for input and
2195 * output respectively -- they're opposite ends of pipes, but
2196 * obviously they're crossed over so that the parent's output
2197 * matches the child's input and %%\emph{vice versa}%%.
2200 static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
2201 int *errfd_out, struct tvec_state *tv)
2203 int p0[2] = { -1, -1 }, p1[2] = { -1, -1 }, pe[2] = { -1, -1 };
2207 /* Try to create the pipes. */
2208 if (pipe(p0) || pipe(p1) || pipe(pe) ||
2209 fdflags(p0[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
2210 fdflags(p1[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
2211 fdflags(pe[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC)) {
2212 tvec_error(tv, "pipe failed: %s", strerror(errno));
2216 /* Flush all of the stream buffers so that we don't get duplicated
2221 /* Try to set up the child process. */
2224 tvec_error(tv, "fork failed: %s", strerror(errno));
2229 /* Child process. */
2232 *infd_out = p0[0]; p0[0] = -1;
2233 *outfd_out = p1[1]; p1[1] = -1;
2234 if (pe[1] != STDERR_FILENO && dup2(pe[1], STDERR_FILENO) < 0) {
2235 fprintf(stderr, "failed to establish child stderr: %s",
2240 /* Parent process. */
2242 *kid_out = kid; kid = -1;
2243 *infd_out = p1[0]; p1[0] = -1;
2244 *outfd_out = p0[1]; p0[1] = -1;
2245 *errfd_out = pe[0]; pe[0] = -1;
2252 /* Clean up. So much of this... */
2253 if (p0[0] >= 0) close(p0[0]);
2254 if (p0[1] >= 0) close(p0[1]);
2255 if (p1[0] >= 0) close(p1[0]);
2256 if (p1[1] >= 0) close(p1[1]);
2257 if (pe[0] >= 0) close(pe[0]);
2258 if (pe[1] >= 0) close(pe[1]);
2262 /* --- @tvec_fork@ --- *
2264 * Arguments: @pid_t *kid_out@ = where to put child process-id
2265 * @int *infd_out, *outfd_out, *errfd_out@ = where to put file
2267 * @struct tvec_state *tv@ = test vector state
2268 * @const struct tvec_remoteenv@ = the remote environment
2270 * Returns: Zero on success, %$-1$% on failure.
2272 * Use: Starts a remote server running in a fork of the main
2273 * process. This is useful for testing functions which might --
2274 * or are even intended to -- crash.
2277 int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
2278 struct tvec_state *tv, const struct tvec_remoteenv *env)
2280 struct tvec_config config;
2281 const struct tvec_remotefork *rf = (const struct tvec_remotefork *)env;
2283 int infd = -1, outfd = -1, errfd = -1;
2286 if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
2288 if (tv->fp) fclose(tv->fp);
2289 config.tests = rf->f.tests ? rf->f.tests : tv->tests;
2290 config.nrout = tv->nrout; config.nreg = tv->nreg;
2291 config.regsz = tv->regsz;
2292 _exit(tvec_remoteserver(infd, outfd, &config));
2295 *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
2301 /* --- @tvec_exec@ --- *
2303 * Arguments: @pid_t *kid_out@ = where to put child process-id
2304 * @int *infd_out, *outfd_out, *errfd_out@ = where to put file
2306 * @struct tvec_state *tv@ = test vector state
2307 * @const struct tvec_remoteenv@ = the remote environment
2309 * Returns: Zero on success, %$-1$% on failure.
2311 * Use: Starts a remote server by running some program. The command
2312 * given in the environment description will probably some hairy
2313 * shell rune allowing for configuration via files or
2314 * environment variables.
2317 int tvec_exec(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
2318 struct tvec_state *tv, const struct tvec_remoteenv *env)
2320 const struct tvec_remoteexec *rx = (const struct tvec_remoteexec *)env;
2322 int infd = -1, outfd = -1, errfd = -1;
2326 if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
2328 v[0].cur = infd; v[0].want = STDIN_FILENO;
2329 v[1].cur = outfd; v[1].want = STDOUT_FILENO;
2331 fprintf(stderr, "failed to establish standard file descriptors: %s",
2335 execvp(rx->x.args[0], (/*uncosnt*/ char *const *)rx->x.args);
2336 fprintf(stderr, "failed to invoke test runner: %s", strerror(errno));
2340 *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
2346 /*----- That's all, folks -------------------------------------------------*/