chiark / gitweb /
@@@ timeout wip
[mLib] / test / tvec-remote.c
CommitLineData
e63124bc
MW
1/* -*-c-*-
2 *
3 * Remote testing
4 *
5 * (c) 2023 Straylight/Edgeware
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the mLib utilities library.
11 *
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.
16 *
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.
21 *
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,
25 * USA.
26 */
27
28/*----- Header files ------------------------------------------------------*/
29
30#include <errno.h>
c91413e6 31#include <signal.h>
e63124bc
MW
32#include <stdarg.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36
37#include <sys/types.h>
c91413e6 38#include <sys/wait.h>
e63124bc
MW
39#include <fcntl.h>
40#include <unistd.h>
41
42#include "alloc.h"
c91413e6 43#include "bench.h"
e63124bc 44#include "buf.h"
c91413e6
MW
45#include "compiler.h"
46#include "fdflags.h"
47#include "lbuf.h"
48#include "mdup.h"
49#include "quis.h"
e63124bc
MW
50#include "tvec.h"
51
31d0247c
MW
52/*----- Preliminaries -----------------------------------------------------*/
53
54/* The control macros I'm using below provoke `dangling-else' warnings from
55 * compilers. Suppress them. I generally don't care.
56 */
57
c91413e6
MW
58#if GCC_VERSION_P(7, 1)
59# pragma GCC diagnostic ignored "-Wdangling-else"
60#elif GCC_VERSION_P(4, 2)
61# pragma GCC diagnostic ignored "-Wparentheses"
62#endif
e63124bc 63
c91413e6
MW
64#if CLANG_VERSION_P(3, 1)
65# pragma clang diagnostic ignored "-Wdangling-else"
66#endif
e63124bc 67
c91413e6 68/*----- Basic I/O ---------------------------------------------------------*/
e63124bc 69
31d0247c
MW
70/* --- @init_comms@ --- *
71 *
72 * Arguments: @struct tvec_remotecomms *rc@ = communication state
73 *
74 * Returns: ---
75 *
76 * Use: Initialize a communication state. This doesn't allocate any
77 * resurces: it just ensures that everything is set up so that
78 * subsequent operations -- in particular @release_comms@ --
79 * behave sensibly.
80 */
81
c91413e6
MW
82static void init_comms(struct tvec_remotecomms *rc)
83{
31d0247c 84 rc->bin = 0; rc->binsz = 0; dbuf_create(&rc->bout);
c91413e6
MW
85 rc->infd = rc->outfd = -1; rc->f = 0;
86}
e63124bc 87
31d0247c
MW
88/* --- @close_comms@ --- *
89 *
90 * Arguments: @struct tvec_remotecomms *rc@ = communication state
91 *
92 * Returns: ---
93 *
94 * Use: Close the input and output descriptors.
95 *
96 * If the descriptors are already closed -- or were never opened
97 * -- then nothing happens.
98 */
99
c91413e6
MW
100static void close_comms(struct tvec_remotecomms *rc)
101{
102 if (rc->infd >= 0) { close(rc->infd); rc->infd = -1; }
103 if (rc->outfd >= 0) { close(rc->outfd); rc->outfd = -1; }
104}
105
31d0247c
MW
106/* --- @release_comms@ --- *
107 *
108 * Arguments: @struct tvec_remotecomms *rc@ = communication state
109 *
110 * Returns: ---
111 *
112 * Use: Releases the resources -- most notably the input and output
113 * buffers -- held by the communication state. Also calls
114 * @close_comms@.
115 */
116
c91413e6 117static void release_comms(struct tvec_remotecomms *rc)
31d0247c
MW
118 { close_comms(rc); xfree(rc->bin); dbuf_destroy(&rc->bout); }
119
120/* --- @setup_comms@ --- *
121 *
122 * Arguments: @struct tvec_remotecomms *rc@ = communication state
123 * @int infd, outfd@ = input and output file descriptors
124 *
125 * Returns: ---
126 *
127 * Use: Use the given descriptors for communication.
128 *
129 * Clears the private flags.
130 */
c91413e6
MW
131
132static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd)
133{
31d0247c
MW
134 rc->infd = infd; rc->outfd = outfd;
135 rc->binoff = rc->binlen = 0;
136 rc->f &= ~0xffu;
c91413e6 137}
e63124bc 138
31d0247c
MW
139/* --- @ioerr@ --- *
140 *
141 * Arguments: @struct tvec_state *tv@ = test-vector state
142 * @struct tvec_remotecomms *rc@ = communication state
143 * @const char *msg, ...@ = format string and arguments
144 *
145 * Returns: %$-1$%.
146 *
147 * Use: Reports the message as an error, closes communications and
148 * marks them as broken.
149 */
150
151static PRINTF_LIKE(3, 4)
152 int ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
153 const char *msg, ...)
e63124bc
MW
154{
155 va_list ap;
156
157 va_start(ap, msg);
c91413e6
MW
158 close_comms(rc); rc->f |= TVRF_BROKEN;
159 tvec_report_v(tv, TVLEV_ERR, msg, &ap);
e63124bc
MW
160 va_end(ap);
161 return (-1);
162}
163
31d0247c
MW
164/* --- @send_all@ --- *
165 *
166 * Arguments: @struct tvec_state *tv@ = test-vector state
167 * @struct tvec_remotecomms *rc@ = communication state
168 * @const unsigned char *p@, @size_t sz@ = output buffer
169 *
170 * Returns: Zero on success, %$-1$% on error.
171 *
172 * Use: Send the output buffer over the communication state's output
173 * descriptor, even if it has to be written in multiple pieces.
174 */
175
c91413e6 176static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
e63124bc
MW
177 const unsigned char *p, size_t sz)
178{
179 ssize_t n;
c91413e6 180 int ret;
e63124bc
MW
181
182 while (sz) {
c91413e6 183 n = write(rc->outfd, p, sz);
e63124bc
MW
184 if (n > 0)
185 { p += n; sz -= n; }
c91413e6
MW
186 else {
187 ret = ioerr(tv, rc, "failed to send: %s",
188 n ? strerror(errno) : "empty write");
189 goto end;
190 }
e63124bc 191 }
c91413e6
MW
192 ret = 0;
193end:
c91413e6 194 return (ret);
e63124bc
MW
195}
196
31d0247c
MW
197/* --- @recv_all@ --- *
198 *
199 * Arguments: @struct tvec_state *tv@ = test-vector state
200 * @struct tvec_remotecomms *rc@ = communication state
201 * @unsigned f@ = flags (@RCVF_...@)
202 * @unsigned char *p@, @size_t sz@ = input buffer
203 * @size_t min@ = minimum acceptable size to read
204 * @size_t *n_out@ = size read
205 *
206 * Returns: An @RECV_...@ code.
207 *
208 * Use: Receive data on the communication state's input descriptor to
209 * read at least @min@ bytes into the input buffer, even if it
210 * has to be done in multiple pieces. If more data is readily
211 * available, then up to @sz@ bytes will be read in total.
212 *
213 * If the descriptor immediately reports end-of-file, and
214 * @RCVF_ALLOWEOF@ is set in @f@, then return @RECV_EOF@.
215 * Otherwise, EOF is treated as an I/O error, resulting in a
216 * call to @ioerr@ and a return code of @RECV_FAIL@. If the
217 * read succeeded, then set @*n_out@ to the number of bytes read
218 * and return @RECV_OK@.
219 */
220
e63124bc 221#define RCVF_ALLOWEOF 1u
31d0247c 222
c91413e6
MW
223enum {
224 RECV_FAIL = -1,
225 RECV_OK = 0,
226 RECV_EOF = 1
227};
31d0247c 228
c91413e6 229static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
31d0247c
MW
230 unsigned f, unsigned char *p, size_t sz,
231 size_t min, size_t *n_out)
e63124bc 232{
31d0247c 233 size_t tot = 0;
e63124bc 234 ssize_t n;
e63124bc
MW
235
236 while (sz) {
c91413e6 237 n = read(rc->infd, p, sz);
31d0247c
MW
238 if (n > 0) {
239 p += n; sz -= n; tot += n;
240 if (tot >= min) break;
241 } else if (!n && !tot && (f&RCVF_ALLOWEOF))
c91413e6 242 return (RECV_EOF);
e63124bc 243 else
c91413e6 244 return (ioerr(tv, rc, "failed to receive: %s",
e63124bc
MW
245 n ? strerror(errno) : "unexpected end-of-file"));
246 }
31d0247c 247 *n_out = tot; return (RECV_OK);
e63124bc
MW
248
249#undef f_any
250}
251
31d0247c
MW
252/* --- @buferr@ --- *
253 *
254 * Arguments: @struct tvec_state *tv@ = test-vector state
255 * @struct tvec_remotecomms *rc@ = communication state
256 *
257 * Returns: %$-$%.
258 *
259 * Use: Report a problem preparing the output buffer.
260 */
261
262static int buferr(struct tvec_state *tv, struct tvec_remotecomms *rc)
263 { return (ioerr(tv, rc, "failed to build output packet")); }
264
265/* --- @malformed@ --- *
266 *
267 * Arguments: @struct tvec_state *tv@ = test-vector state
268 * @struct tvec_remotecomms *rc@ = communication state
269 *
270 * Returns: %$-$%.
271 *
272 * Use: Report an I/O error that the incoming packet is malformed.
273 */
274
275static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
276 { return (ioerr(tv, rc, "received malformed packet")); }
277
278/* --- @remote_send@ --- *
279 *
280 * Arguments: @struct tvec_state *tv@ = test-vector state
281 * @struct tvec_remotecomms *rc@ = communication state
282 *
283 * Returns: Zero on success, %$-1$% on error.
284 *
285 * Use: Send the accuulated contents of the output buffer @rc->bout@.
286 *
287 * The function arranges to convert @SIGPIPE@ into an error.
288 *
289 * If the output buffer is broken, report this as an I/O error.
290 */
291
292#define SENDBUFSZ 4096
293
c91413e6 294static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc)
e63124bc 295{
31d0247c
MW
296 void (*opipe)(int) = SIG_ERR;
297 int ret;
298
299 /* Various preflight checks. */
300 if (rc->f&TVRF_BROKEN) { ret = -1; goto end; }
301 if (DBBAD(&rc->bout)) { ret = buferr(tv, rc); goto end; }
e63124bc 302
31d0247c
MW
303 /* Arrange to trap broken-pipe errors. */
304 opipe = signal(SIGPIPE, SIG_IGN);
305 if (opipe == SIG_ERR) {
306 ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
307 goto end;
308 }
e63124bc 309
31d0247c
MW
310 /* Transmit the packet. */
311 if (send_all(tv, rc, DBBASE(&rc->bout), DBLEN(&rc->bout)))
312 { ret = -1; goto end; }
e63124bc 313
31d0247c
MW
314 /* Done. Put things back the way we found them. */
315 ret = 0;
316end:
317 DBRESET(&rc->bout);
318 if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
319 return (ret);
e63124bc
MW
320}
321
31d0247c
MW
322/* --- @receive_buffered@ --- *
323 *
324 * Arguments: @struct tvec_state *tv@ = test-vector state
325 * @struct tvec_remotecomms *rc@ = communication state
326 * @unsigned f@ = flags (@RCVF_...@)
327 * @size_t want@ = data block size required
328 *
329 * Returns: An @RECV_...@ code.
330 *
331 * Use: Reads a block of data from the input descriptor into the
332 * input buffer.
333 *
334 * This is the main machinery for manipulating the input buffer.
335 * The buffer has three regions:
336 *
337 * * from the buffer start to @rc->binoff@ is `consumed';
338 * * from @rc->binoff@ to @rc->binlen@ is `available'; and
339 * * from @rc->binlen@ to @rc->binsz@ is `free'.
340 *
341 * Data is read into the start of the `free' region, and the
342 * `available' region is extended to include it. Data in the
343 * `consumed' region is periodically discarded by moving the
344 * data from the `available' region to the start of the buffer
345 * and decreasing @rc->binoff@ and @rc->binlen@.
346 *
347 * This function ensures that the `available' region contains at
348 * least @want@ bytes, by (a) extending the buffer, if
349 * necessary, so that @rc->binsz >= rc->binoff + want@, and (b)
350 * reading fresh data from the input descriptor to extend the
351 * `available' region.
352 *
353 * If absolutely no data is available, and @RCVF_ALLOWEOF@ is
354 * set in @f@, then return @RECV_EOF@. On I/O errors, including
355 * a short read or end-of-file if @RCVF_ALLOWEOF@ is clear,
356 * return @RECV_FAIL@. On success, return @RECV_OK@. The
357 * amount of data read is indicated by updating the input buffer
358 * variables as described above.
359 */
360
361#define RECVBUFSZ 4096u
362
363static int receive_buffered(struct tvec_state *tv,
364 struct tvec_remotecomms *rc,
365 unsigned f, size_t want)
366{
367 size_t sz;
368 int ret;
369
370 /* If we can supply the caller's requirement from the buffer then do
371 * that.
372 */
373 if (rc->binlen - rc->binoff >= want) return (RECV_OK);
374
375 /* If the buffer is too small then we must grow it. */
376 if (want > rc->binsz) {
377 sz = rc->binsz; if (!sz) sz = RECVBUFSZ;
378 while (sz < want) { assert(sz < (size_t)-1/2); sz *= 2; }
379 if (!rc->bin) rc->bin = xmalloc(sz);
380 else rc->bin = xrealloc(rc->bin, sz, rc->binsz);
381 rc->binsz = sz;
382 }
383
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;
387
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.
390 */
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);
395
396 /* Note how much material we have and return. */
397 rc->binlen += sz; return (RECV_OK);
398}
399
400/* --- @remote_recv@ --- *
401 *
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
405 *
406 * Returns: An @RECV_...@ code.
407 *
408 * Use: Receive a packet into the input buffer @rc->bin@ and
409 * establish @*b_out@ to read from it.
410 */
411
c91413e6
MW
412static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
413 unsigned f, buf *b_out)
e63124bc 414{
31d0247c
MW
415 kludge64 k, szmax;
416 size_t want;
c91413e6 417 int ret;
e63124bc 418
c91413e6 419 ASSIGN64(szmax, (size_t)-1);
31d0247c
MW
420
421 /* Preflight checks. */
422 if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
423
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;
e63124bc 427 if (CMP64(k, >, szmax))
c91413e6 428 return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range",
e63124bc 429 (unsigned long)HI64(k), (unsigned long)LO64(k)));
31d0247c 430 want = GET64(size_t, k);
e63124bc 431
31d0247c
MW
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;
435 return (RECV_OK);
e63124bc
MW
436}
437
31d0247c
MW
438/* --- @QUEUEPK_TAG@, @QUEUEPK@ --- *
439 *
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
445 *
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.
454 */
e63124bc 455
31d0247c
MW
456#define QF_FORCE 1u
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); \
465 }) \
466 DBUF_ENCLOSEITAG(tag##__frame, &(rc)->bout, (rc)->t, 64_L) \
467 MC_BEFORE(tag##__pkty, { \
468 dbuf_putu16l(&(rc)->bout, (pk)); \
469 })
470
471#define QUEUEPK(tv, rc, fl, pk) QUEUEPK_TAG(queue, (tv), (rc), (fl), (pk))
e63124bc
MW
472
473/*----- Packet types ------------------------------------------------------*/
474
c91413e6 475#define TVPF_ACK 0x0001u
e63124bc 476
31d0247c
MW
477#define TVPK_VER 0x0000u /* --> min, max: u16 *
478 * <-- ver: u16 */
479#define TVPK_BGROUP 0x0002u /* --> name: str16
480 * <-- --- */
481#define TVPK_TEST 0x0004u /* --> in: regs
482 * <-- --- */
483#define TVPK_EGROUP 0x0006u /* --> --- *
484 * <-- --- */
e63124bc 485
c91413e6
MW
486#define TVPK_REPORT 0x0100u /* <-- level: u16; msg: string */
487#define TVPK_PROGRESS 0x0102u /* <-- st: str16 */
e63124bc 488
31d0247c
MW
489#define TVPK_SKIPGRP 0x0104u /* <-- excuse: str16 */
490#define TVPK_SKIP 0x0106u /* <-- excuse: str16 */
491#define TVPK_FAIL 0x0108u /* <-- flag: u8, detail: str16 */
492#define TVPK_DUMPREG 0x010au /* <-- ri: u16; disp: u16;
c91413e6 493 * flag: u8, rv: value */
31d0247c
MW
494#define TVPK_BBENCH 0x010cu /* <-- ident: str32; unit: u16 */
495#define TVPK_EBENCH 0x010eu /* <-- ident: str32; unit: u16;
c91413e6 496 * flags: u16; n, t, cy: f64 */
e63124bc 497
c91413e6
MW
498/*----- Server ------------------------------------------------------------*/
499
31d0247c 500/* Forward declaration of output operations. */
c91413e6
MW
501static const struct tvec_outops remote_ops;
502
31d0247c
MW
503static struct tvec_state srvtv; /* server's test-vector state */
504static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; /* comms */
505static struct tvec_output srvout = { &remote_ops }; /* output state */
e63124bc 506
31d0247c
MW
507/* --- @tvec_setprogress@, @tvec_setprogress_v@ --- *
508 *
509 * Arguments: @const char *status@ = progress status token format
510 * @va_list ap@ = argument tail
511 *
512 * Returns: ---
513 *
514 * Use: Reports the progress of a test execution to the client.
515 *
516 * The framework makes use of tokens beginning with %|%|%:
517 *
518 * * %|%IDLE|%: during the top-level server code;
519 *
520 * * %|%SETUP|%: during the enclosing environment's @before@
521 * function;
522 *
523 * * %|%RUN|%: during the environment's @run@ function, or the
524 * test function; and
525 *
526 * * %|%DONE|%: during the enclosing environment's @after@
527 * function.
528 *
529 * The intent is that a test can use the progress token to check
530 * that a function which is expected to crash does so at the
531 * correct point, so it's expected that more complex test
532 * functions and/or environments will set their own progress
533 * tokens to reflect what's going on.
534 */
535
536int tvec_setprogress(const char *status, ...)
537{
538 va_list ap;
539 int rc;
540
541 va_start(ap, status); rc = tvec_setprogress_v(status, &ap); va_end(ap);
542 return (rc);
543}
544
545int tvec_setprogress_v(const char *status, va_list *ap)
c91413e6 546{
31d0247c
MW
547 /* Force immediate output in case we crash before the buffer is output
548 * organically.
549 */
550 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_PROGRESS)
551 dbuf_vputstrf16l(&srvrc.bout, status, ap);
c91413e6
MW
552 else return (-1);
553 return (0);
e63124bc
MW
554}
555
31d0247c
MW
556/* --- @tvec_remoteserver@ --- *
557 *
558 * Arguments: @int infd@, @int outfd@ = input and output file descriptors
559 * @const struct tvec_config *config@ = test configuration
560 *
561 * Returns: Suggested exit code.
562 *
563 * Use: Run a test server, reading packets from @infd@ and writing
564 * responses and notifications to @outfd@, and invoking tests as
565 * described by @config@.
566 *
567 * This function is not particularly general purpose. It
568 * expects to `take over' the process, and makes use of private
569 * global variables.
570 */
571
c91413e6 572int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
e63124bc 573{
c91413e6
MW
574 uint16 pk, u, v;
575 unsigned i;
576 buf b;
577 const struct tvec_test *t;
578 void *p; size_t sz;
579 const struct tvec_env *env = 0;
c91413e6
MW
580 void *ctx = 0;
581 int rc;
582
31d0247c 583 /* Initialize the communication machinery. */
c91413e6 584 setup_comms(&srvrc, infd, outfd);
31d0247c
MW
585
586 /* Begin a test session using our custom output driver. */
c91413e6
MW
587 tvec_begin(&srvtv, config, &srvout);
588
31d0247c
MW
589 /* Version negotiation. Expect a @TVPK_VER@ packet. At the moment,
590 * there's only version zero, so we return that.
591 */
c91413e6
MW
592 if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
593 if (buf_getu16l(&b, &pk)) goto bad;
594 if (pk != TVPK_VER) {
595 rc = ioerr(&srvtv, &srvrc,
596 "unexpected packet type 0x%04x instead of client version",
597 pk);
598 goto end;
e63124bc 599 }
c91413e6 600 if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad;
31d0247c
MW
601 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_VER | TVPF_ACK)
602 dbuf_putu16l(&srvrc.bout, 0);
c91413e6 603 else { rc = -1; goto end; }
e63124bc 604
31d0247c
MW
605 /* Handle packets until the server closes the connection.
606 *
607 * The protocol looks much simpler from our point of view than from the
608 * client.
609 *
610 * * Receive @TVPK_VER@; respond with @TVPK_VER | TVPF_ACK@.
611 *
612 * * Receive zero or more @TVPK_BGROUP@. Open a test group, producing
613 * output packets, and eventually answer with @TVPK_BGROUP | TVPF_ACK@.
614 *
615 * -- Receive zero or more @TVPK_TEST@. Run a test, producing output
616 * packets, and eventually answer with @TVPK_TEST | TVPF_ACK@.
617 *
618 * -- Receive @TVPK_EGROUP@. Maybe produce output packets, and
619 * answer with @TVPK_EGROUP | TVPF_ACK@.
620 *
621 * * Read EOF. Stop.
622 */
c91413e6 623 for (;;) {
31d0247c
MW
624
625 /* Read a packet. End-of-file is expected here (and pretty much nowhere
626 * else). Otherwise, we expect to see @TVPK_BGROUP@.
627 */
c91413e6
MW
628 rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b);
629 if (rc == RECV_EOF) break;
630 else if (rc == RECV_FAIL) goto end;
631 if (buf_getu16l(&b, &pk)) goto bad;
e63124bc 632
c91413e6
MW
633 switch (pk) {
634
635 case TVPK_BGROUP:
31d0247c
MW
636 /* Start a group. */
637
638 /* Parse the packet payload. */
c91413e6
MW
639 p = buf_getmem16l(&b, &sz); if (!p) goto bad;
640 if (BLEFT(&b)) goto bad;
31d0247c
MW
641
642 /* Find the group given its name. */
c91413e6
MW
643 for (t = srvtv.tests; t->name; t++)
644 if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz))
645 goto found_group;
646 rc = ioerr(&srvtv, &srvrc, "unknown test group `%.*s'",
647 (int)sz, (char *)p);
648 goto end;
649
650 found_group:
31d0247c 651 /* Set up the test environment. */
c91413e6
MW
652 srvtv.test = t; env = t->env;
653 if (env && env->setup == tvec_remotesetup)
654 env = ((struct tvec_remoteenv *)env)->r.env;
655 if (!env || !env->ctxsz) ctx = 0;
656 else ctx = xmalloc(env->ctxsz);
657 if (env && env->setup) env->setup(&srvtv, env, 0, ctx);
658
31d0247c
MW
659 /* Initialize the registers. */
660 tvec_initregs(&srvtv);
661
662 /* Report that the group has been opened and that we're ready to run
663 * tests.
664 */
665 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_BGROUP | TVPF_ACK);
c91413e6
MW
666 else { rc = -1; goto end; }
667
31d0247c 668 /* Handle packets until we're told to end the group. */
c91413e6 669 for (;;) {
31d0247c
MW
670
671 /* Read a packet. We expect @TVPK_EGROUP@ or @TVPK_TEST@. */
c91413e6
MW
672 if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
673 if (buf_getu16l(&b, &pk)) goto bad;
31d0247c 674
c91413e6
MW
675 switch (pk) {
676
677 case TVPK_EGROUP:
31d0247c
MW
678 /* End the group. */
679
680 /* Check the payload. */
c91413e6 681 if (BLEFT(&b)) goto bad;
31d0247c
MW
682
683 /* Leave the group loop. */
c91413e6
MW
684 goto endgroup;
685
686 case TVPK_TEST:
31d0247c
MW
687 /* Run a test. */
688
689 /* Parse the packet payload. */
c91413e6
MW
690 if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs,
691 srvtv.nreg, srvtv.regsz))
692 goto bad;
693 if (BLEFT(&b)) goto bad;
694
31d0247c
MW
695 /* If we're not skipping the test group, then actually try to
696 * run the test.
697 */
c91413e6 698 if (!(srvtv.f&TVSF_SKIP)) {
31d0247c
MW
699
700 /* Prepare the output registers and reset the test outcome.
701 * (The environment may force a skip.)
702 */
703 for (i = 0; i < srvtv.nrout; i++)
704 if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
705 TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
c91413e6 706 srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK;
31d0247c
MW
707
708 /* Invoke the environment @before@ function. */
709 tvec_setprogress("%%SETUP");
c91413e6 710 if (env && env->before) env->before(&srvtv, ctx);
31d0247c
MW
711
712 /* Run the actual test. */
c91413e6
MW
713 if (!(srvtv.f&TVSF_ACTIVE))
714 /* setup forced a skip */;
715 else {
31d0247c 716 tvec_setprogress("%%RUN");
c91413e6
MW
717 if (env && env->run)
718 env->run(&srvtv, t->fn, ctx);
719 else {
720 t->fn(srvtv.in, srvtv.out, ctx);
721 tvec_check(&srvtv, 0);
722 }
723 }
31d0247c
MW
724
725 /* Conclude the test. */
726 tvec_setprogress("%%DONE");
c91413e6
MW
727 if (env && env->after) env->after(&srvtv, ctx);
728 tvec_endtest(&srvtv);
729 }
31d0247c
MW
730
731 /* Reset the input registers and report completion. */
732 tvec_releaseregs(&srvtv); tvec_initregs(&srvtv);
733 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_TEST | TVPF_ACK);
c91413e6 734 else { rc = -1; goto end; }
c91413e6
MW
735 break;
736
737 default:
31d0247c
MW
738 /* Some other kind of packet. Complain. */
739
c91413e6 740 rc = ioerr(&srvtv, &srvrc,
31d0247c
MW
741 "unexpected packet type 0x%04x during test group",
742 pk);
c91413e6
MW
743 goto end;
744
745 }
746 }
747
748 endgroup:
31d0247c
MW
749 /* The test group completed. */
750
751 /* Tear down the environment and release other resources. */
c91413e6 752 if (env && env->teardown) env->teardown(&srvtv, ctx);
31d0247c
MW
753 tvec_releaseregs(&srvtv);
754 xfree(ctx); srvtv.test = 0; env = 0; ctx = 0;
755
756 /* Report completion. */
757 QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_EGROUP | TVPF_ACK);
758 else { rc = -1; goto end; }
c91413e6
MW
759 break;
760
761 default:
31d0247c
MW
762 rc = ioerr(&srvtv, &srvrc,
763 "unexpected packet type 0x%04x at top level", pk);
c91413e6
MW
764 }
765 }
766 rc = 0;
767
768end:
31d0247c 769 /* Clean up and return. */
c91413e6
MW
770 if (env && env->teardown) env->teardown(&srvtv, ctx);
771 xfree(ctx);
31d0247c
MW
772 if (srvtv.test) tvec_releaseregs(&srvtv);
773 release_comms(&srvrc); tvec_end(&srvtv);
c91413e6
MW
774 return (rc ? 2 : 0);
775
776bad:
31d0247c 777 /* Miscellaneous malformed packet. */
c91413e6 778 rc = malformed(&srvtv, &srvrc); goto end;
e63124bc
MW
779}
780
c91413e6
MW
781/*----- Server output driver ----------------------------------------------*/
782
e63124bc
MW
783static void remote_skipgroup(struct tvec_output *o,
784 const char *excuse, va_list *ap)
c91413e6 785{
31d0247c
MW
786 QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIPGRP)
787 dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
c91413e6 788}
e63124bc
MW
789
790static void remote_skip(struct tvec_output *o,
791 const char *excuse, va_list *ap)
c91413e6 792{
31d0247c
MW
793 QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIP)
794 dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
c91413e6 795}
e63124bc
MW
796
797static void remote_fail(struct tvec_output *o,
798 const char *detail, va_list *ap)
c91413e6 799{
31d0247c 800 QUEUEPK(&srvtv, &srvrc, 0, TVPK_FAIL)
c91413e6 801 if (!detail)
31d0247c 802 dbuf_putbyte(&srvrc.bout, 0);
c91413e6 803 else {
31d0247c
MW
804 dbuf_putbyte(&srvrc.bout, 1);
805 dbuf_vputstrf16l(&srvrc.bout, detail, ap);
c91413e6
MW
806 }
807}
e63124bc 808
c91413e6
MW
809static void remote_dumpreg(struct tvec_output *o,
810 unsigned disp, const union tvec_regval *rv,
811 const struct tvec_regdef *rd)
e63124bc 812{
c91413e6
MW
813 const struct tvec_regdef *reg;
814 unsigned r;
e63124bc 815
c91413e6
MW
816 /* Find the register definition. */
817 for (reg = srvtv.test->regs, r = 0; reg->name; reg++, r++)
818 if (reg == rd) goto found;
819 assert(!"unexpected register definition");
820
821found:
31d0247c
MW
822 QUEUEPK(&srvtv, &srvrc, 0, TVPK_DUMPREG) {
823 dbuf_putu16l(&srvrc.bout, r);
824 dbuf_putu16l(&srvrc.bout, disp);
c91413e6 825 if (!rv)
31d0247c 826 dbuf_putbyte(&srvrc.bout, 0);
c91413e6 827 else {
31d0247c
MW
828 dbuf_putbyte(&srvrc.bout, 1);
829 rd->ty->tobuf(DBUF_BUF(&srvrc.bout), rv, rd);
c91413e6 830 }
e63124bc
MW
831 }
832}
833
c91413e6
MW
834static void remote_bbench(struct tvec_output *o,
835 const char *ident, unsigned unit)
e63124bc 836{
31d0247c
MW
837 QUEUEPK(&srvtv, &srvrc, 0, TVPK_BBENCH) {
838 dbuf_putstr32l(&srvrc.bout, ident);
839 dbuf_putu16l(&srvrc.bout, unit);
c91413e6 840 }
e63124bc
MW
841}
842
843static void remote_ebench(struct tvec_output *o,
c91413e6 844 const char *ident, unsigned unit,
e63124bc
MW
845 const struct bench_timing *t)
846{
31d0247c
MW
847 QUEUEPK(&srvtv, &srvrc, 0, TVPK_EBENCH) {
848 dbuf_putstr32l(&srvrc.bout, ident);
849 dbuf_putu16l(&srvrc.bout, unit);
c91413e6 850 if (!t || !(t->f&BTF_ANY))
31d0247c 851 dbuf_putu16l(&srvrc.bout, 0);
c91413e6 852 else {
31d0247c
MW
853 dbuf_putu16l(&srvrc.bout, t->f);
854 dbuf_putf64l(&srvrc.bout, t->n);
855 if (t->f&BTF_TIMEOK) dbuf_putf64l(&srvrc.bout, t->t);
856 if (t->f&BTF_CYOK) dbuf_putf64l(&srvrc.bout, t->cy);
c91413e6
MW
857 }
858 }
859}
860
861static void remote_report(struct tvec_output *o, unsigned level,
862 const char *msg, va_list *ap)
863{
31d0247c
MW
864 QUEUEPK(&srvtv, &srvrc, 0, TVPK_REPORT) {
865 dbuf_putu16l(&srvrc.bout, level);
866 dbuf_vputstrf16l(&srvrc.bout, msg, ap);
c91413e6 867 } else {
31d0247c 868 fprintf(stderr, "%s %s: ", QUIS, tvec_strlevel(level));
c91413e6
MW
869 vfprintf(stderr, msg, *ap);
870 fputc('\n', stderr);
e63124bc
MW
871 }
872}
873
c91413e6
MW
874static void remote_bsession(struct tvec_output *o, struct tvec_state *tv)
875 { ; }
e63124bc 876static int remote_esession(struct tvec_output *o)
c91413e6
MW
877 { return (srvtv.f&TVSF_ERROR ? 2 : 0); }
878static void remote_destroy(struct tvec_output *o)
879 { ; }
880static void remote_etest(struct tvec_output *o, unsigned outcome)
881 { ; }
882
e63124bc
MW
883static void remote_bgroup(struct tvec_output *o)
884 { assert(!"remote_bgroup"); }
c91413e6
MW
885static void remote_egroup(struct tvec_output *o)
886 { assert(!"remote_egroup"); }
e63124bc
MW
887static void remote_btest(struct tvec_output *o)
888 { assert(!"remote_btest"); }
e63124bc
MW
889
890static const struct tvec_outops remote_ops = {
e63124bc 891 remote_bsession, remote_esession,
c91413e6
MW
892 remote_bgroup, remote_skipgroup, remote_egroup,
893 remote_btest, remote_skip, remote_fail, remote_dumpreg, remote_etest,
e63124bc 894 remote_bbench, remote_ebench,
c91413e6 895 remote_report,
e63124bc 896 remote_destroy
c91413e6
MW
897};
898
899/*----- Client ------------------------------------------------------------*/
900
901#define TVXF_VALMASK 0x0fffu
902#define TVXF_SIG 0x1000u
903#define TVXF_CAUSEMASK 0xe000u
904#define TVXST_RUN 0x0000u
905#define TVXST_EXIT 0x2000u
906#define TVXST_KILL 0x4000u
907#define TVXST_CONT 0x6000u
908#define TVXST_STOP 0x8000u
909#define TVXST_DISCONN 0xa000u
910#define TVXST_UNK 0xc000u
911#define TVXST_ERR 0xe000u
912
913static const struct tvec_flag exit_flags[] = {
914 /*
915 ;;; The signal name table is very boring to type. To make life less
916 ;;; awful, put the signal names in this list and evaluate the code to
917 ;;; get Emacs to regenerate it.
918
919 (let ((signals '(HUP INT QUIT ILL TRAP ABRT IOT EMT FPE KILL BUS SEGV SYS
920 PIPE ALRM TERM URG STOP TSTP CONT CHLD CLD TTIN TTOU
921 POLL IO TIN XCPU XFSZ VTALRM PROF WINCH USR1 USR2
922 STKFLT INFO PWR THR LWP LIBRT LOST)))
923 (save-excursion
924 (goto-char (point-min))
925 (search-forward (concat "***" "BEGIN siglist" "***"))
926 (beginning-of-line 2)
927 (delete-region (point)
928 (progn
929 (search-forward "***END***")
930 (beginning-of-line)
931 (point)))
932 (dolist (sig signals)
933 (insert (format "#ifdef SIG%s\n { \"SIG%s\", TVXF_VALMASK | TVXF_SIG, SIG%s | TVXF_SIG },\n#endif\n"
934 sig sig sig)))))
935 */
936
937 /***BEGIN siglist***/
938#ifdef SIGHUP
939 { "SIGHUP", TVXF_VALMASK | TVXF_SIG, SIGHUP | TVXF_SIG },
940#endif
941#ifdef SIGINT
942 { "SIGINT", TVXF_VALMASK | TVXF_SIG, SIGINT | TVXF_SIG },
943#endif
944#ifdef SIGQUIT
945 { "SIGQUIT", TVXF_VALMASK | TVXF_SIG, SIGQUIT | TVXF_SIG },
946#endif
947#ifdef SIGILL
948 { "SIGILL", TVXF_VALMASK | TVXF_SIG, SIGILL | TVXF_SIG },
949#endif
950#ifdef SIGTRAP
951 { "SIGTRAP", TVXF_VALMASK | TVXF_SIG, SIGTRAP | TVXF_SIG },
952#endif
953#ifdef SIGABRT
954 { "SIGABRT", TVXF_VALMASK | TVXF_SIG, SIGABRT | TVXF_SIG },
955#endif
956#ifdef SIGIOT
957 { "SIGIOT", TVXF_VALMASK | TVXF_SIG, SIGIOT | TVXF_SIG },
958#endif
959#ifdef SIGEMT
960 { "SIGEMT", TVXF_VALMASK | TVXF_SIG, SIGEMT | TVXF_SIG },
961#endif
962#ifdef SIGFPE
963 { "SIGFPE", TVXF_VALMASK | TVXF_SIG, SIGFPE | TVXF_SIG },
964#endif
965#ifdef SIGKILL
966 { "SIGKILL", TVXF_VALMASK | TVXF_SIG, SIGKILL | TVXF_SIG },
967#endif
968#ifdef SIGBUS
969 { "SIGBUS", TVXF_VALMASK | TVXF_SIG, SIGBUS | TVXF_SIG },
970#endif
971#ifdef SIGSEGV
972 { "SIGSEGV", TVXF_VALMASK | TVXF_SIG, SIGSEGV | TVXF_SIG },
973#endif
974#ifdef SIGSYS
975 { "SIGSYS", TVXF_VALMASK | TVXF_SIG, SIGSYS | TVXF_SIG },
976#endif
977#ifdef SIGPIPE
978 { "SIGPIPE", TVXF_VALMASK | TVXF_SIG, SIGPIPE | TVXF_SIG },
979#endif
980#ifdef SIGALRM
981 { "SIGALRM", TVXF_VALMASK | TVXF_SIG, SIGALRM | TVXF_SIG },
982#endif
983#ifdef SIGTERM
984 { "SIGTERM", TVXF_VALMASK | TVXF_SIG, SIGTERM | TVXF_SIG },
985#endif
986#ifdef SIGURG
987 { "SIGURG", TVXF_VALMASK | TVXF_SIG, SIGURG | TVXF_SIG },
988#endif
989#ifdef SIGSTOP
990 { "SIGSTOP", TVXF_VALMASK | TVXF_SIG, SIGSTOP | TVXF_SIG },
991#endif
992#ifdef SIGTSTP
993 { "SIGTSTP", TVXF_VALMASK | TVXF_SIG, SIGTSTP | TVXF_SIG },
994#endif
995#ifdef SIGCONT
996 { "SIGCONT", TVXF_VALMASK | TVXF_SIG, SIGCONT | TVXF_SIG },
997#endif
998#ifdef SIGCHLD
999 { "SIGCHLD", TVXF_VALMASK | TVXF_SIG, SIGCHLD | TVXF_SIG },
1000#endif
1001#ifdef SIGCLD
1002 { "SIGCLD", TVXF_VALMASK | TVXF_SIG, SIGCLD | TVXF_SIG },
1003#endif
1004#ifdef SIGTTIN
1005 { "SIGTTIN", TVXF_VALMASK | TVXF_SIG, SIGTTIN | TVXF_SIG },
1006#endif
1007#ifdef SIGTTOU
1008 { "SIGTTOU", TVXF_VALMASK | TVXF_SIG, SIGTTOU | TVXF_SIG },
1009#endif
1010#ifdef SIGPOLL
1011 { "SIGPOLL", TVXF_VALMASK | TVXF_SIG, SIGPOLL | TVXF_SIG },
1012#endif
1013#ifdef SIGIO
1014 { "SIGIO", TVXF_VALMASK | TVXF_SIG, SIGIO | TVXF_SIG },
1015#endif
1016#ifdef SIGTIN
1017 { "SIGTIN", TVXF_VALMASK | TVXF_SIG, SIGTIN | TVXF_SIG },
1018#endif
1019#ifdef SIGXCPU
1020 { "SIGXCPU", TVXF_VALMASK | TVXF_SIG, SIGXCPU | TVXF_SIG },
1021#endif
1022#ifdef SIGXFSZ
1023 { "SIGXFSZ", TVXF_VALMASK | TVXF_SIG, SIGXFSZ | TVXF_SIG },
1024#endif
1025#ifdef SIGVTALRM
1026 { "SIGVTALRM", TVXF_VALMASK | TVXF_SIG, SIGVTALRM | TVXF_SIG },
1027#endif
1028#ifdef SIGPROF
1029 { "SIGPROF", TVXF_VALMASK | TVXF_SIG, SIGPROF | TVXF_SIG },
1030#endif
1031#ifdef SIGWINCH
1032 { "SIGWINCH", TVXF_VALMASK | TVXF_SIG, SIGWINCH | TVXF_SIG },
1033#endif
1034#ifdef SIGUSR1
1035 { "SIGUSR1", TVXF_VALMASK | TVXF_SIG, SIGUSR1 | TVXF_SIG },
1036#endif
1037#ifdef SIGUSR2
1038 { "SIGUSR2", TVXF_VALMASK | TVXF_SIG, SIGUSR2 | TVXF_SIG },
1039#endif
1040#ifdef SIGSTKFLT
1041 { "SIGSTKFLT", TVXF_VALMASK | TVXF_SIG, SIGSTKFLT | TVXF_SIG },
1042#endif
1043#ifdef SIGINFO
1044 { "SIGINFO", TVXF_VALMASK | TVXF_SIG, SIGINFO | TVXF_SIG },
1045#endif
1046#ifdef SIGPWR
1047 { "SIGPWR", TVXF_VALMASK | TVXF_SIG, SIGPWR | TVXF_SIG },
1048#endif
1049#ifdef SIGTHR
1050 { "SIGTHR", TVXF_VALMASK | TVXF_SIG, SIGTHR | TVXF_SIG },
1051#endif
1052#ifdef SIGLWP
1053 { "SIGLWP", TVXF_VALMASK | TVXF_SIG, SIGLWP | TVXF_SIG },
1054#endif
1055#ifdef SIGLIBRT
1056 { "SIGLIBRT", TVXF_VALMASK | TVXF_SIG, SIGLIBRT | TVXF_SIG },
1057#endif
1058#ifdef SIGLOST
1059 { "SIGLOST", TVXF_VALMASK | TVXF_SIG, SIGLOST | TVXF_SIG },
1060#endif
1061 /***END***/
1062
1063 { "signal", TVXF_SIG, TVXF_SIG },
1064
1065 { "running", TVXF_CAUSEMASK, TVXST_RUN },
1066 { "exited", TVXF_CAUSEMASK, TVXST_EXIT },
1067 { "killed", TVXF_CAUSEMASK, TVXST_KILL },
1068 { "stopped", TVXF_CAUSEMASK, TVXST_STOP },
1069 { "continued", TVXF_CAUSEMASK, TVXST_CONT },
1070 { "disconnected", TVXF_CAUSEMASK, TVXST_DISCONN },
1071 { "unknown", TVXF_CAUSEMASK, TVXST_UNK },
1072 { "error", TVXF_CAUSEMASK, TVXST_ERR },
1073
1074 TVEC_ENDFLAGS
1075};
1076
1077static const struct tvec_flaginfo exit_flaginfo =
1078 { "exit-status", exit_flags, &tvrange_uint };
1079static const struct tvec_regdef exit_regdef =
1080 { "@exit", 0, &tvty_flags, 0, { &exit_flaginfo } };
1081
1082static const struct tvec_regdef progress_regdef =
1083 { "@progress", 0, &tvty_string, 0 };
1084
1085static const struct tvec_uassoc reconn_assocs[] = {
1086 { "on-demand", TVRCN_DEMAND },
1087 { "force", TVRCN_FORCE },
1088 { "skip", TVRCN_SKIP },
1089 TVEC_ENDENUM
1090};
1091
1092enum {
1093 CONN_BROKEN = -2, /* previously broken */
1094 CONN_FAILED = -1, /* attempt freshly failed */
1095 CONN_ESTABLISHED = 0, /* previously established */
1096 CONN_FRESH = 1 /* freshly connected */
1097};
1098
1099static const struct tvec_uenuminfo reconn_enuminfo =
1100 { "remote-reconnection", reconn_assocs, &tvrange_uint };
1101static const struct tvec_regdef reconn_regdef =
1102 { "@reconnect", 0, &tvty_uenum, 0, { &reconn_enuminfo } };
1103
1104static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
1105 unsigned f, uint16 end, buf *b_out)
1106{
1107 struct tvec_output *o = tv->output;
1108 uint16 pk, u, v;
1109 const char *p; size_t n;
1110 dstr d = DSTR_INIT;
1111 buf *b = b_out;
1112 const struct tvec_regdef *rd;
1113 struct bench_timing bt;
1114 struct tvec_reg *reg = 0;
1115 unsigned i;
1116 int rc;
1117
1118 for (;;) {
1119 rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end;
1120 if (buf_getu16l(b, &pk)) goto bad;
31d0247c 1121 if (pk == end) break;
c91413e6
MW
1122
1123 switch (pk) {
1124
1125 case TVPK_PROGRESS:
1126 p = buf_getmem16l(b, &n); if (!p) goto bad;
1127 if (BLEFT(b)) goto bad;
1128
1129 DRESET(&r->progress); DPUTM(&r->progress, p, n); DPUTZ(&r->progress);
1130 break;
1131
1132 case TVPK_REPORT:
1133 if (buf_getu16l(b, &u)) goto bad;
1134 p = buf_getmem16l(b, &n); if (!p) goto bad;
1135 if (BLEFT(b)) goto bad;
1136
1137 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1138 tvec_report(tv, u, "%s", d.buf);
1139 break;
1140
1141 case TVPK_SKIPGRP:
1142 p = buf_getmem16l(b, &n); if (!p) goto bad;
1143 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1144 if (BLEFT(b)) goto bad;
1145
1146 tvec_skipgroup(tv, "%s", d.buf);
1147 break;
1148
1149 case TVPK_SKIP:
1150 if (!(tv->f&TVSF_ACTIVE)) {
1151 rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name);
1152 goto end;
1153 }
1154
1155 p = buf_getmem16l(b, &n); if (!p) goto bad;
1156 if (BLEFT(b)) goto bad;
1157
1158 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1159 tvec_skip(tv, "%s", d.buf);
1160 break;
1161
1162 case TVPK_FAIL:
1163 if (!(tv->f&TVSF_ACTIVE) &&
1164 ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) {
1165 rc = ioerr(tv, &r->rc, "test `%s' not active or failing",
1166 tv->test->name);
1167 goto end;
1168 }
1169
1170 rc = buf_getbyte(b); if (rc < 0) goto bad;
1171 if (rc) { p = buf_getmem16l(b, &n); if (!p) goto bad; }
1172 else p = 0;
1173 if (BLEFT(b)) goto bad;
e63124bc 1174
c91413e6
MW
1175 if (!p)
1176 tvec_fail(tv, 0);
1177 else {
1178 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1179 tvec_fail(tv, "%s", d.buf);
1180 }
1181 break;
e63124bc 1182
c91413e6
MW
1183 case TVPK_DUMPREG:
1184 if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
1185 for (rd = tv->test->regs, i = 0; rd->name; rd++, i++)
1186 if (i == u) goto found_reg;
1187 rc = ioerr(tv, &r->rc,
1188 "register definition %u out of range for test `%s'",
1189 u, tv->test->name);
1190 goto end;
1191 found_reg:
1192 if (v >= TVRD_LIMIT) {
1193 rc = ioerr(tv, &r->rc, "register disposition %u out of range", v);
1194 goto end;
1195 }
e63124bc 1196
c91413e6
MW
1197 rc = buf_getbyte(b); if (rc < 0) goto bad;
1198 if (!rc)
1199 tvec_dumpreg(tv, v, 0, rd);
1200 else {
1201 if (!reg) reg = xmalloc(tv->regsz);
1202 rd->ty->init(&reg->v, rd);
1203 rc = rd->ty->frombuf(b, &reg->v, rd);
1204 if (!rc) tvec_dumpreg(tv, v, &reg->v, rd);
1205 rd->ty->release(&reg->v, rd);
1206 if (rc) goto bad;
1207 }
1208 if (BLEFT(b)) goto bad;
1209 break;
1210
1211 case TVPK_BBENCH:
1212 p = buf_getmem32l(b, &n); if (!p) goto bad;
1213 if (buf_getu16l(b, &u)) goto bad;
1214 if (BLEFT(b)) goto bad;
1215 if (u >= TVBU_LIMIT) {
1216 rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
1217 goto end;
1218 }
1219
1220 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1221 o->ops->bbench(o, d.buf, u);
1222 break;
1223
1224 case TVPK_EBENCH:
1225 p = buf_getmem32l(b, &n); if (!p) goto bad;
1226 if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
31d0247c
MW
1227 if (u >= TVBU_LIMIT) {
1228 rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
1229 goto end;
1230 }
c91413e6
MW
1231 if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad;
1232 if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad;
1233 if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad;
1234 if (BLEFT(b)) goto bad;
1235
1236 DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1237 o->ops->ebench(o, d.buf, u, v&BTF_ANY ? &bt : 0);
1238 break;
1239
1240 default:
c91413e6
MW
1241 rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
1242 goto end;
1243 }
1244 }
1245
31d0247c 1246 rc = RECV_OK;
c91413e6
MW
1247end:
1248 DDESTROY(&d);
1249 xfree(reg);
1250 return (rc);
1251bad:
1252 rc = malformed(tv, &r->rc); goto end;
1253}
1254
1255static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
1256{
1257 pid_t kid;
1258 int st;
1259
1260 if (!r->kid)
1261 { r->exit = TVXST_DISCONN; r->kid = -1; }
1262 else if (r->kid > 0) {
1263 kid = waitpid(r->kid, &st, 0);
1264 if (kid < 0) {
1265 tvec_notice(tv, "failed to wait for remote child: %s",
1266 strerror(errno));
1267 r->exit = TVXST_ERR;
1268 } else if (!kid) {
1269 tvec_notice(tv, "remote child vanished without a trace");
1270 r->exit = TVXST_ERR;
1271 } else if (WIFCONTINUED(st))
1272 r->exit = TVXST_CONT;
1273 else if (WIFSIGNALED(st))
1274 r->exit = TVXST_KILL | TVXF_SIG | WTERMSIG(st);
1275 else if (WIFSTOPPED(st))
1276 r->exit = TVXST_STOP | TVXF_SIG | WSTOPSIG(st);
1277 else if (WIFEXITED(st))
1278 r->exit = TVXST_EXIT | WEXITSTATUS(st);
1279 else {
1280 tvec_notice(tv, "remote child died with unknown status 0x%04x",
1281 (unsigned)st);
1282 r->exit = TVXST_UNK;
1283 }
1284 r->kid = -1;
1285 }
1286}
1287
1288static void report_errline(char *p, size_t n, void *ctx)
1289{
1290 struct tvec_remotectx *r = ctx;
1291 struct tvec_state *tv = r->tv;
1292
1293 if (p && !(r->rc.f&TVRF_MUFFLE))
1294 tvec_notice(tv, "child process stderr: %s", p);
1295}
1296
1297#define ERF_SILENT 0x0001u
1298#define ERF_CLOSE 0x0002u
1299static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
1300 unsigned f)
1301{
1302 char *p; size_t sz;
1303 ssize_t n;
1304 int rc;
1305
31d0247c 1306 if (r->errfd < 0) { rc = 0; goto end; }
c91413e6
MW
1307 if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
1308 else r->rc.f &= ~TVRF_MUFFLE;
1309 if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) {
1310 rc = ioerr(tv, &r->rc, "failed to %s error non-blocking flag",
1311 f&ERF_CLOSE ? "clear" : "set");
1312 goto end;
1313 }
1314
1315 for (;;) {
1316 sz = lbuf_free(&r->errbuf, &p);
1317 n = read(r->errfd, p, sz);
1318 if (!n) break;
1319 if (n < 0) {
1320 if (errno == EINTR) continue;
1321 if (!(f&ERF_CLOSE) && (errno == EWOULDBLOCK || errno == EAGAIN))
1322 break;
1323 rc = ioerr(tv, &r->rc, "failed to read child stderr: %s",
1324 strerror(errno));
1325 goto end;
1326 }
1327 lbuf_flush(&r->errbuf, p, n);
1328 }
1329 rc = 0;
1330end:
1331 if (f&ERF_CLOSE) {
1332 lbuf_close(&r->errbuf);
31d0247c 1333 close(r->errfd); r->errfd = -1;
c91413e6
MW
1334 }
1335 return (rc);
1336}
1337
1338#define DCF_KILL 0x0100u
1339static void disconnect_remote(struct tvec_state *tv,
1340 struct tvec_remotectx *r, unsigned f)
1341{
c91413e6
MW
1342 if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
1343 close_comms(&r->rc);
c91413e6
MW
1344 drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
1345}
1346
1347static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
1348{
1349 const struct tvec_remoteenv *re = r->re;
1350 pid_t kid = 0;
1351 buf b;
1352 uint16 v;
1353 int infd = -1, outfd = -1, errfd = -1, rc;
1354
1355 DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
1356 if (r->kid >= 0) { rc = 0; goto end; }
1357 if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re))
1358 { rc = -1; goto end; }
1359 setup_comms(&r->rc, infd, outfd); r->kid = kid; r->errfd = errfd;
1360 lbuf_init(&r->errbuf, report_errline, r);
1361 r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN;
1362
31d0247c
MW
1363 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) {
1364 dbuf_putu16l(&r->rc.bout, 0);
1365 dbuf_putu16l(&r->rc.bout, 0);
c91413e6
MW
1366 } else { rc = -1; goto end; }
1367
1368 if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b))
1369 { rc = -1; goto end; }
1370 if (buf_getu16l(&b, &v)) goto bad;
1371 if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1372 if (v) {
1373 rc = ioerr(tv, &r->rc, "protocol version %u not supported", v);
1374 goto end;
1375 }
1376
31d0247c
MW
1377 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP)
1378 dbuf_putstr16l(&r->rc.bout, tv->test->name);
c91413e6
MW
1379 else { rc = -1; goto end; }
1380 if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b))
1381 { rc = -1; goto end; }
1382 if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1383 r->ver = v; rc = 0;
1384end:
1385 if (rc) disconnect_remote(tv, r, DCF_KILL);
1386 return (rc);
1387bad:
1388 rc = malformed(tv, &r->rc); goto end;
1389}
1390
1391static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r)
1392{
1393 if (r->kid < 0)
1394 return (CONN_BROKEN);
1395 else if (r->rc.f&TVRF_BROKEN)
1396 { disconnect_remote(tv, r, DCF_KILL); return (CONN_FAILED); }
1397 else
1398 return (CONN_ESTABLISHED);
1399}
1400
1401static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
1402{
1403 int rc;
1404
1405 switch (r->rc.f&TVRF_RCNMASK) {
1406 case TVRCN_DEMAND:
1407 rc = check_comms(tv, r);
1408 if (rc < CONN_ESTABLISHED) {
1409 close_comms(&r->rc);
1410 if (connect_remote(tv, r)) rc = CONN_FAILED;
1411 else rc = CONN_FRESH;
1412 }
1413 break;
1414 case TVRCN_FORCE:
1415 disconnect_remote(tv, r, DCF_KILL);
1416 if (connect_remote(tv, r)) rc = CONN_FAILED;
1417 else rc = CONN_FRESH;
1418 break;
1419 case TVRCN_SKIP:
1420 rc = check_comms(tv, r);
1421 break;
1422 default:
1423 abort();
1424 }
1425 return (rc);
1426}
1427
1428static void reset_vars(struct tvec_remotectx *r)
1429{
31d0247c
MW
1430 r->exwant = TVXST_RUN;
1431 r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) | TVRCN_DEMAND;
c91413e6
MW
1432 DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
1433}
1434
1435void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
1436 void *pctx, void *ctx)
1437{
1438 struct tvec_remotectx *r = ctx;
1439 const struct tvec_remoteenv *re = (const struct tvec_remoteenv *)env;
1440
1441 assert(!re->r.env || tv->test->env == &re->_env);
1442
1443 r->tv = tv;
1444 init_comms(&r->rc);
1445 r->re = re; r->kid = -1;
1446 DCREATE(&r->prgwant); DCREATE(&r->progress);
1447 if (connect_remote(tv, r))
1448 tvec_skipgroup(tv, "failed to connect to test backend");
1449 reset_vars(r);
1450}
1451
31d0247c 1452int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx)
c91413e6
MW
1453{
1454 struct tvec_remotectx *r = ctx;
1455 union tvec_regval rv;
1456 int rc;
1457
1458 if (STRCMP(var, ==, "@exit")) {
31d0247c 1459 if (r->rc.f&TVRF_SETEXIT) { rc = tvec_dupreg(tv, var); goto end; }
c91413e6 1460 if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; }
31d0247c 1461 r->exwant = rv.u; r->rc.f |= TVRF_SETEXIT; rc = 1;
c91413e6 1462 } else if (STRCMP(var, ==, "@progress")) {
31d0247c 1463 if (r->rc.f&TVRF_SETPRG) { rc = tvec_dupreg(tv, var); goto end; }
c91413e6
MW
1464 tvty_string.init(&rv, &progress_regdef);
1465 rc = tvty_string.parse(&rv, &progress_regdef, tv);
31d0247c
MW
1466 if (!rc) {
1467 DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz);
1468 r->rc.f |= TVRF_SETPRG;
1469 }
c91413e6
MW
1470 tvty_string.release(&rv, &progress_regdef);
1471 if (rc) { rc = -1; goto end; }
1472 rc = 1;
1473 } else if (STRCMP(var, ==, "@reconnect")) {
31d0247c 1474 if (r->rc.f&TVRF_SETRCN) { rc = tvec_dupreg(tv, var); goto end; }
c91413e6 1475 if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; }
31d0247c 1476 r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK) | TVRF_SETRCN;
c91413e6
MW
1477 rc = 1;
1478 } else
1479 rc = 0;
1480
1481end:
1482 return (rc);
1483}
1484
1485void tvec_remoteafter(struct tvec_state *tv, void *ctx)
1486{
1487 struct tvec_remotectx *r = ctx;
1488
1489 reset_vars(r);
1490}
1491
1492void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
1493{
1494 struct tvec_remotectx *r = ctx;
1495 union tvec_regval rv;
1496 unsigned f = 0;
1497#define f_exit 1u
1498#define f_progress 2u
1499#define f_fail 4u
1500 buf b;
1501 int rc;
1502
1503 switch (try_reconnect(tv, r)) {
1504 case CONN_FAILED:
1505 tvec_skip(tv, "failed to connect to test backend"); return;
1506 case CONN_BROKEN:
1507 tvec_skip(tv, "no connection"); return;
1508 }
1509
31d0247c
MW
1510 DRESET(&r->progress); DPUTS(&r->progress, "%IDLE");
1511 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST)
1512 tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout),
c91413e6
MW
1513 tv->test->regs, tv->nreg, tv->regsz);
1514 else { rc = -1; goto end; }
1515 rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
1516 switch (rc) {
1517 case RECV_FAIL:
1518 goto end;
1519 case RECV_EOF:
1520 reap_kid(tv, r);
1521 /* fall through */
1522 case RECV_OK:
1523 if (r->exit != r->exwant) f |= f_exit;
1524 if (r->progress.len != r->prgwant.len ||
1525 MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len))
1526 f |= f_progress;
1527 if (f && (tv->f&TVSF_ACTIVE))
1528 { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); }
1529 if (!(tv->f&TVSF_ACTIVE) &&
1530 (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) {
1531 f |= f_fail;
1532
1533 rv.u = r->exit;
1534 tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
1535 &rv, &exit_regdef);
1536 if (f&f_exit) {
1537 rv.u = r->exwant;
1538 tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_regdef);
1539 }
1540
1541 rv.str.p = r->progress.buf; rv.str.sz = r->progress.len;
1542 tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH,
1543 &rv, &progress_regdef);
1544 if (f&f_progress) {
1545 rv.str.p = r->prgwant.buf; rv.str.sz = r->prgwant.len;
1546 tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_regdef);
1547 }
1548 }
1549
1550 if (rc == RECV_EOF)
1551 disconnect_remote(tv, r, f ? 0 : ERF_SILENT);
1552 break;
1553 }
1554
1555end:
1556 if (rc) {
1557 if ((tv->f&TVSF_ACTIVE) && f)
1558 tvec_skip(tv, "remote test runner communications failed");
1559 disconnect_remote(tv, r, 0);
1560 }
1561
1562#undef f_exit
1563#undef f_progress
1564#undef f_fail
1565}
1566
1567void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
1568{
1569 struct tvec_remotectx *r = ctx;
31d0247c 1570 buf b;
c91413e6 1571
31d0247c
MW
1572 if (r->rc.outfd >= 0) {
1573 QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP);
1574 if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b))
1575 if (BLEFT(&b)) malformed(tv, &r->rc);
c91413e6 1576 }
31d0247c
MW
1577 disconnect_remote(tv, r, 0); release_comms(&r->rc);
1578 DDESTROY(&r->prgwant); DDESTROY(&r->progress);
c91413e6
MW
1579}
1580
1581/*----- Connectors --------------------------------------------------------*/
1582
1583static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
1584 int *errfd_out, struct tvec_state *tv)
1585{
1586 int p0[2] = { -1, -1 }, p1[2] = { -1, -1 }, pe[2] = { -1, -1 };
1587 pid_t kid = -1;
1588 int rc;
1589
1590 if (pipe(p0) || pipe(p1) || pipe(pe) ||
1591 fdflags(p0[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
1592 fdflags(p1[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
1593 fdflags(pe[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC)) {
1594 tvec_error(tv, "pipe failed: %s", strerror(errno));
1595 rc = -1; goto end;
1596 }
1597
1598 fflush(0);
1599
1600 kid = fork();
1601 if (kid < 0) {
1602 tvec_error(tv, "fork failed: %s", strerror(errno));
1603 rc = -1; goto end;
1604 }
1605
1606 if (!kid) {
1607 *kid_out = 0;
1608 *infd_out = p0[0]; p0[0] = -1;
1609 *outfd_out = p1[1]; p1[1] = -1;
1610 if (pe[1] != STDERR_FILENO && dup2(pe[1], STDERR_FILENO) < 0) {
1611 fprintf(stderr, "failed to establish child stderr: %s",
1612 strerror(errno));
1613 exit(127);
1614 }
1615 } else {
1616 *kid_out = kid; kid = -1;
1617 *infd_out = p1[0]; p1[0] = -1;
1618 *outfd_out = p0[1]; p0[1] = -1;
1619 *errfd_out = pe[0]; pe[0] = -1;
1620 }
1621
1622 rc = 0;
1623end:
1624 if (p0[0] >= 0) close(p0[0]);
1625 if (p0[1] >= 0) close(p0[1]);
1626 if (p1[0] >= 0) close(p1[0]);
1627 if (p1[1] >= 0) close(p1[1]);
1628 if (pe[0] >= 0) close(pe[0]);
1629 if (pe[1] >= 0) close(pe[1]);
1630 return (rc);
1631}
1632
1633int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
1634 struct tvec_state *tv, const struct tvec_remoteenv *env)
1635{
1636 struct tvec_config config;
1637 const struct tvec_remotefork *rf = (const struct tvec_remotefork *)env;
1638 pid_t kid = -1;
1639 int infd = -1, outfd = -1, errfd = -1;
1640 int rc;
1641
1642 if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
1643 if (!kid) {
31d0247c 1644 if (tv->fp) fclose(tv->fp);
c91413e6
MW
1645 config.tests = rf->f.tests ? rf->f.tests : tv->tests;
1646 config.nrout = tv->nrout; config.nreg = tv->nreg;
1647 config.regsz = tv->regsz;
1648 _exit(tvec_remoteserver(infd, outfd, &config));
1649 }
1650
1651 *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
1652 rc = 0;
1653end:
1654 return (rc);
1655}
1656
1657int tvec_exec(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
1658 struct tvec_state *tv, const struct tvec_remoteenv *env)
1659{
1660 const struct tvec_remoteexec *rx = (const struct tvec_remoteexec *)env;
1661 pid_t kid = -1;
1662 int infd = -1, outfd = -1, errfd = -1;
1663 mdup_fd v[2];
1664 int rc;
1665
1666 if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
1667 if (!kid) {
1668 v[0].cur = infd; v[0].want = STDIN_FILENO;
1669 v[1].cur = outfd; v[1].want = STDOUT_FILENO;
1670 if (mdup(v, 2)) {
1671 fprintf(stderr, "failed to establish standard file descriptors: %s",
1672 strerror(errno));
1673 exit(127);
1674 }
1675 execvp(rx->x.args[0], (/*uncosnt*/ char *const *)rx->x.args);
1676 fprintf(stderr, "failed to invoke test runner: %s", strerror(errno));
1677 exit(127);
1678 }
1679
1680 *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
1681 rc = 0;
1682end:
1683 return (rc);
1684}
e63124bc
MW
1685
1686/*----- That's all, folks -------------------------------------------------*/