chiark / gitweb /
@@@ tvec setvar
[mLib] / test / tvec-remote.c
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>
31 #include <signal.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <fcntl.h>
40 #include <unistd.h>
41
42 #include "alloc.h"
43 #include "bench.h"
44 #include "buf.h"
45 #include "compiler.h"
46 #include "fdflags.h"
47 #include "lbuf.h"
48 #include "mdup.h"
49 #include "quis.h"
50 #include "tvec.h"
51
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
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
63
64 #if CLANG_VERSION_P(3, 1)
65 #  pragma clang diagnostic ignored "-Wdangling-else"
66 #endif
67
68 /*----- Basic I/O ---------------------------------------------------------*/
69
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
82 static void init_comms(struct tvec_remotecomms *rc)
83 {
84   rc->bin = 0; rc->binsz = 0; dbuf_create(&rc->bout);
85   rc->infd = rc->outfd = -1; rc->f = 0;
86 }
87
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
100 static void close_comms(struct tvec_remotecomms *rc)
101 {
102   if (rc->infd >= 0) {
103     if (rc->infd != rc->outfd) close(rc->infd);
104     rc->infd = -1;
105   }
106   if (rc->outfd >= 0)
107     { close(rc->outfd); rc->outfd = -1; }
108   rc->f |= TVRF_BROKEN;
109 }
110
111 /* --- @release_comms@ --- *
112  *
113  * Arguments:   @struct tvec_remotecomms *rc@ = communication state
114  *
115  * Returns:     ---
116  *
117  * Use:         Releases the resources -- most notably the input and output
118  *              buffers -- held by the communication state.  Also calls
119  *              @close_comms@.
120  */
121
122 static void release_comms(struct tvec_remotecomms *rc)
123   { close_comms(rc); xfree(rc->bin); dbuf_destroy(&rc->bout); }
124
125 /* --- @setup_comms@ --- *
126  *
127  * Arguments:   @struct tvec_remotecomms *rc@ = communication state
128  *              @int infd, outfd@ = input and output file descriptors
129  *
130  * Returns:     ---
131  *
132  * Use:         Use the given descriptors for communication.
133  *
134  *              Clears the private flags.
135  */
136
137 static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd)
138 {
139   rc->infd = infd; rc->outfd = outfd;
140   rc->binoff = rc->binlen = 0;
141   rc->f &= ~0xffu;
142 }
143
144 /* --- @ioerr@ --- *
145  *
146  * Arguments:   @struct tvec_state *tv@ = test-vector state
147  *              @struct tvec_remotecomms *rc@ = communication state
148  *              @const char *msg, ...@ = format string and arguments
149  *
150  * Returns:     %$-1$%.
151  *
152  * Use:         Reports the message as an error, closes communications and
153  *              marks them as broken.
154  */
155
156 static PRINTF_LIKE(3, 4)
157   int ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
158             const char *msg, ...)
159 {
160   va_list ap;
161
162   va_start(ap, msg);
163   close_comms(rc); rc->f |= TVRF_BROKEN;
164   tvec_report_v(tv, TVLEV_ERR, msg, &ap);
165   va_end(ap);
166   return (-1);
167 }
168
169 /* --- @send_all@ --- *
170  *
171  * Arguments:   @struct tvec_state *tv@ = test-vector state
172  *              @struct tvec_remotecomms *rc@ = communication state
173  *              @const unsigned char *p@, @size_t sz@ = output buffer
174  *
175  * Returns:     Zero on success, %$-1$% on error.
176  *
177  * Use:         Send the output buffer over the communication state's output
178  *              descriptor, even if it has to be written in multiple pieces.
179  */
180
181 static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
182                     const unsigned char *p, size_t sz)
183 {
184   ssize_t n;
185   int ret;
186
187   while (sz) {
188     n = write(rc->outfd, p, sz);
189     if (n > 0)
190       { p += n; sz -= n; }
191     else {
192       ret = ioerr(tv, rc, "failed to send: %s",
193                  n ? strerror(errno) : "empty write");
194       goto end;
195     }
196   }
197   ret = 0;
198 end:
199   return (ret);
200 }
201
202 /* --- @recv_all@ --- *
203  *
204  * Arguments:   @struct tvec_state *tv@ = test-vector state
205  *              @struct tvec_remotecomms *rc@ = communication state
206  *              @unsigned f@ = flags (@RCVF_...@)
207  *              @unsigned char *p@, @size_t sz@ = input buffer
208  *              @size_t min@ = minimum acceptable size to read
209  *              @size_t *n_out@ = size read
210  *
211  * Returns:     A @RECV_...@ code.
212  *
213  * Use:         Receive data on the communication state's input descriptor to
214  *              read at least @min@ bytes into the input buffer, even if it
215  *              has to be done in multiple pieces.  If more data is readily
216  *              available, then up to @sz@ bytes will be read in total.
217  *
218  *              If the descriptor immediately reports end-of-file, and
219  *              @RCVF_ALLOWEOF@ is set in @f@, then return @RECV_EOF@.
220  *              Otherwise, EOF is treated as an I/O error, resulting in a
221  *              call to @ioerr@ and a return code of @RECV_FAIL@.  If the
222  *              read succeeded, then set @*n_out@ to the number of bytes read
223  *              and return @RECV_OK@.
224  */
225
226 #define RCVF_ALLOWEOF 1u
227
228 enum {
229   RECV_FAIL = -1,
230   RECV_OK = 0,
231   RECV_EOF = 1
232 };
233
234 static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
235                     unsigned f, unsigned char *p, size_t sz,
236                     size_t min, size_t *n_out)
237 {
238   size_t tot = 0;
239   ssize_t n;
240
241   while (sz) {
242     n = read(rc->infd, p, sz);
243     if (n > 0) {
244       p += n; sz -= n; tot += n;
245       if (tot >= min) break;
246     } else if (!n && !tot && (f&RCVF_ALLOWEOF))
247       { rc->f |= TVRF_BROKEN; return (RECV_EOF); }
248     else
249       return (ioerr(tv, rc, "failed to receive: %s",
250                     n ? strerror(errno) : "unexpected end-of-file"));
251   }
252   *n_out = tot; return (RECV_OK);
253
254 #undef f_any
255 }
256
257 /* --- @buferr@ --- *
258  *
259  * Arguments:   @struct tvec_state *tv@ = test-vector state
260  *              @struct tvec_remotecomms *rc@ = communication state
261  *
262  * Returns:     %$-$%.
263  *
264  * Use:         Report a problem preparing the output buffer.
265  */
266
267 static int buferr(struct tvec_state *tv, struct tvec_remotecomms *rc)
268   { return (ioerr(tv, rc, "failed to build output packet")); }
269
270 /* --- @malformed@ --- *
271  *
272  * Arguments:   @struct tvec_state *tv@ = test-vector state
273  *              @struct tvec_remotecomms *rc@ = communication state
274  *
275  * Returns:     %$-$%.
276  *
277  * Use:         Report an I/O error that the incoming packet is malformed.
278  */
279
280 static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
281   { return (ioerr(tv, rc, "received malformed packet")); }
282
283 /* --- @remote_send@ --- *
284  *
285  * Arguments:   @struct tvec_state *tv@ = test-vector state
286  *              @struct tvec_remotecomms *rc@ = communication state
287  *
288  * Returns:     Zero on success, %$-1$% on error.
289  *
290  * Use:         Send the accuulated contents of the output buffer @rc->bout@.
291  *
292  *              The function arranges to convert @SIGPIPE@ into an error.
293  *
294  *              If the output buffer is broken, report this as an I/O error.
295  */
296
297 #define SENDBUFSZ 4096
298
299 static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc)
300 {
301   void (*opipe)(int) = SIG_ERR;
302   int ret;
303
304   /* Various preflight checks. */
305   if (rc->f&TVRF_BROKEN) { ret = -1; goto end; }
306   if (DBBAD(&rc->bout)) { ret = buferr(tv, rc); goto end; }
307
308   /* Arrange to trap broken-pipe errors. */
309   opipe = signal(SIGPIPE, SIG_IGN);
310     if (opipe == SIG_ERR) {
311       ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
312       goto end;
313     }
314
315   /* Transmit the packet. */
316   if (send_all(tv, rc, DBBASE(&rc->bout), DBLEN(&rc->bout)))
317     { ret = -1; goto end; }
318
319   /* Done.  Put things back the way we found them. */
320   ret = 0;
321 end:
322   DBRESET(&rc->bout);
323   if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
324   return (ret);
325 }
326
327 /* --- @receive_buffered@ --- *
328  *
329  * Arguments:   @struct tvec_state *tv@ = test-vector state
330  *              @struct tvec_remotecomms *rc@ = communication state
331  *              @unsigned f@ = flags (@RCVF_...@)
332  *              @size_t want@ = data block size required
333  *
334  * Returns:     A @RECV_...@ code.
335  *
336  * Use:         Reads a block of data from the input descriptor into the
337  *              input buffer.
338  *
339  *              This is the main machinery for manipulating the input buffer.
340  *              The buffer has three regions:
341  *
342  *                * from the buffer start to @rc->binoff@ is `consumed';
343  *                * from @rc->binoff@ to @rc->binlen@ is `available'; and
344  *                * from @rc->binlen@ to @rc->binsz@ is `free'.
345  *
346  *              Data is read into the start of the `free' region, and the
347  *              `available' region is extended to include it.  Data in the
348  *              `consumed' region is periodically discarded by moving the
349  *              data from the `available' region to the start of the buffer
350  *              and decreasing @rc->binoff@ and @rc->binlen@.
351  *
352  *              This function ensures that the `available' region contains at
353  *              least @want@ bytes, by (a) extending the buffer, if
354  *              necessary, so that @rc->binsz >= rc->binoff + want@, and (b)
355  *              reading fresh data from the input descriptor to extend the
356  *              `available' region.
357  *
358  *              If absolutely no data is available, and @RCVF_ALLOWEOF@ is
359  *              set in @f@, then return @RECV_EOF@.  On I/O errors, including
360  *              a short read or end-of-file if @RCVF_ALLOWEOF@ is clear,
361  *              return @RECV_FAIL@.  On success, return @RECV_OK@.  The
362  *              amount of data read is indicated by updating the input buffer
363  *              variables as described above.
364  */
365
366 #define RECVBUFSZ 4096u
367
368 static int receive_buffered(struct tvec_state *tv,
369                             struct tvec_remotecomms *rc,
370                             unsigned f, size_t want)
371 {
372   size_t sz;
373   int ret;
374
375   /* If we can supply the caller's requirement from the buffer then do
376    * that.
377    */
378   if (rc->binlen - rc->binoff >= want) return (RECV_OK);
379
380   /* If the buffer is too small then we must grow it. */
381   if (want > rc->binsz) {
382     sz = rc->binsz; if (!sz) sz = RECVBUFSZ;
383     while (sz < want) { assert(sz < (size_t)-1/2); sz *= 2; }
384     if (!rc->bin) rc->bin = xmalloc(sz);
385     else rc->bin = xrealloc(rc->bin, sz, rc->binsz);
386     rc->binsz = sz;
387   }
388
389   /* Shunt the unused existing material to the start of the buffer. */
390   memmove(rc->bin, rc->bin + rc->binoff, rc->binlen - rc->binoff);
391   rc->binlen -= rc->binoff; rc->binoff = 0;
392
393   /* Satisfy the caller from the input stream, and try to fill up as much of
394    * the rest of the buffer as we can.
395    */
396   ret = recv_all(tv, rc, rc->binlen ? 0 : f,
397                  rc->bin + rc->binlen, rc->binsz - rc->binlen,
398                  want - rc->binlen, &sz);
399     if (ret) return (ret);
400
401   /* Note how much material we have and return. */
402   rc->binlen += sz; return (RECV_OK);
403 }
404
405 /* --- @remote_recv@ --- *
406  *
407  * Arguments:   @struct tvec_state *tv@ = test-vector state
408  *              @unsigned f@ = flags (@RCVF_...@)
409  *              @buf *b_out@ = buffer to establish around the packet contents
410  *
411  * Returns:     A @RECV_...@ code.
412  *
413  * Use:         Receive a packet into the input buffer @rc->bin@ and
414  *              establish @*b_out@ to read from it.
415  */
416
417 static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
418                        unsigned f, buf *b_out)
419 {
420   kludge64 k, szmax;
421   size_t want;
422   int ret;
423
424   ASSIGN64(szmax, (size_t)-1);
425
426   /* Preflight checks. */
427   if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
428
429   /* See if we can read the next packet length from what we already have. */
430   ret = receive_buffered(tv, rc, f, 8); if (ret) return (ret);
431   LOAD64_L_(k, rc->bin + rc->binoff); rc->binoff += 8;
432   if (CMP64(k, >, szmax))
433     return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range",
434                   (unsigned long)HI64(k), (unsigned long)LO64(k)));
435   want = GET64(size_t, k);
436
437   /* Read the next packet payload. */
438   ret = receive_buffered(tv, rc, 0, want); if (ret) return (ret);
439   buf_init(b_out, rc->bin + rc->binoff, want); rc->binoff += want;
440   return (RECV_OK);
441 }
442
443 /* --- @QUEUEPK_TAG@, @QUEUEPK@ --- *
444  *
445  * Arguments:   @tag@ = control structure tag
446  *              @struct tvec_state *tv@ = test-vector state
447  *              @struct tvec_remotecomms *rc@ = communication state
448  *              @unsigned fl@ = flags (@QF_...@)
449  *              @unsigned pk@ = packet type
450  *
451  * Use:         This is syntactically a statement head: the syntax is
452  *              @QUEUEPK(tv, rc, f) body [else alt]@.  The @body@ should
453  *              write material to the output buffer @rc->bout@.  The macro
454  *              applies appropriate framing.  If enough material has been
455  *              collected, or if @QF_FORCE@ is set in @fl@, then
456  *              @remote_send@ is invoked to transmit the buffered packets.
457  *              If there is an error of any kind, then the @alt@ statement,
458  *              if any, is executed.
459  */
460
461 #define QF_FORCE 1u
462 #define QUEUEPK_TAG(tag, tv, rc, fl, pk)                                \
463         if ((rc)->f&TVRF_BROKEN) MC_GOELSE(tag##__else); else           \
464         MC_ALLOWELSE(tag##__else)                                       \
465         MC_AFTER(tag##__send, {                                         \
466           if ((DBBAD(&(rc)->bout) && (buferr((tv), (rc)), 1)) ||        \
467               ((((fl)&QF_FORCE) || DBLEN(&(rc)->bout) >= SENDBUFSZ) &&  \
468                remote_send(tv, rc)))                                    \
469             MC_GOELSE(tag##__else);                                     \
470         })                                                              \
471         DBUF_ENCLOSEITAG(tag##__frame, &(rc)->bout, (rc)->t, 64_L)      \
472         MC_BEFORE(tag##__pkty, {                                        \
473           dbuf_putu16l(&(rc)->bout, (pk));                              \
474         })
475
476 #define QUEUEPK(tv, rc, fl, pk) QUEUEPK_TAG(queue, (tv), (rc), (fl), (pk))
477
478 /*----- Packet types ------------------------------------------------------*/
479
480 #define TVPF_ACK        0x0001u
481
482 #define TVPK_VER        0x0000u         /* --> min, max: u16 *
483                                          * <-- ver: u16 */
484 #define TVPK_BGROUP     0x0002u         /* --> name: str16
485                                          * <-- --- */
486 #define TVPK_SETVAR     0x0004u         /* --> name: str16, rv: value
487                                          * <-- rc: u8 */
488 #define TVPK_TEST       0x0006u         /* --> in: regs
489                                          * <-- --- */
490 #define TVPK_EGROUP     0x0008u         /* --> --- *
491                                          * <-- --- */
492
493 #define TVPK_REPORT     0x0100u         /* <-- level: u16; msg: string */
494 #define TVPK_PROGRESS   0x0102u         /* <-- st: str16 */
495
496 #define TVPK_SKIPGRP    0x0104u         /* <-- excuse: str16 */
497 #define TVPK_SKIP       0x0106u         /* <-- excuse: str16 */
498 #define TVPK_FAIL       0x0108u         /* <-- flag: u8, detail: str16 */
499 #define TVPK_DUMPREG    0x010au         /* <-- ri: u16; disp: u16;
500                                          *     flag: u8, rv: value */
501 #define TVPK_BBENCH     0x010cu         /* <-- ident: str32; unit: u16 */
502 #define TVPK_EBENCH     0x010eu         /* <-- ident: str32; unit: u16;
503                                          *     flags: u16; n, t, cy: f64 */
504
505 /*----- Server ------------------------------------------------------------*/
506
507 /* Forward declaration of output operations. */
508 static const struct tvec_outops remote_ops;
509
510 static struct tvec_state srvtv;         /* server's test-vector state */
511 static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; /* comms */
512 static struct tvec_output srvout = { &remote_ops }; /* output state */
513
514 /* --- @tvec_setprogress@, @tvec_setprogress_v@ --- *
515  *
516  * Arguments:   @const char *status@ = progress status token format
517  *              @va_list ap@ = argument tail
518  *
519  * Returns:     ---
520  *
521  * Use:         Reports the progress of a test execution to the client.
522  *
523  *              The framework makes use of tokens beginning with %|%|%:
524  *
525  *                * %|%IDLE|%: during the top-level server code;
526  *
527  *                * %|%SETUP|%: during the enclosing environment's @before@
528  *                  function;
529  *
530  *                * %|%RUN|%: during the environment's @run@ function, or the
531  *                  test function; and
532  *
533  *                * %|%DONE|%: during the enclosing environment's @after@
534  *                  function.
535  *
536  *              The intent is that a test can use the progress token to check
537  *              that a function which is expected to crash does so at the
538  *              correct point, so it's expected that more complex test
539  *              functions and/or environments will set their own progress
540  *              tokens to reflect what's going on.
541  */
542
543 int tvec_setprogress(const char *status, ...)
544 {
545   va_list ap;
546   int rc;
547
548   va_start(ap, status); rc = tvec_setprogress_v(status, &ap); va_end(ap);
549   return (rc);
550 }
551
552 int tvec_setprogress_v(const char *status, va_list *ap)
553 {
554   /* Force immediate output in case we crash before the buffer is output
555    * organically.
556    */
557   QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_PROGRESS)
558     dbuf_vputstrf16l(&srvrc.bout, status, ap);
559   else return (-1);
560   return (0);
561 }
562
563 /* --- @tvec_remoteserver@ --- *
564  *
565  * Arguments:   @int infd@, @int outfd@ = input and output file descriptors
566  *              @const struct tvec_config *config@ = test configuration
567  *
568  * Returns:     Suggested exit code.
569  *
570  * Use:         Run a test server, reading packets from @infd@ and writing
571  *              responses and notifications to @outfd@, and invoking tests as
572  *              described by @config@.
573  *
574  *              This function is not particularly general purpose.  It
575  *              expects to `take over' the process, and makes use of private
576  *              global variables.
577  */
578
579 int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
580 {
581   uint16 pk, u, v;
582   unsigned i;
583   buf b;
584   dstr d = DSTR_INIT;
585   const struct tvec_test *t;
586   void *p; size_t sz;
587   const struct tvec_env *env = 0;
588   const struct tvec_vardef *vd = 0; void *varctx;
589   struct tvec_reg *r = 0, rbuf, *r_alloc = 0; size_t rsz = 0;
590   void *ctx = 0;
591   int rc;
592
593   /* Initialize the communication machinery. */
594   setup_comms(&srvrc, infd, outfd);
595
596   /* Begin a test session using our custom output driver. */
597   tvec_begin(&srvtv, config, &srvout);
598
599   /* Version negotiation.  Expect a @TVPK_VER@ packet.  At the moment,
600    * there's only version zero, so we return that.
601    */
602   if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
603   if (buf_getu16l(&b, &pk)) goto bad;
604   if (pk != TVPK_VER) {
605     rc = ioerr(&srvtv, &srvrc,
606                "unexpected packet type 0x%04x instead of client version",
607                pk);
608     goto end;
609   }
610   if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad;
611   QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_VER | TVPF_ACK)
612     dbuf_putu16l(&srvrc.bout, 0);
613   else { rc = -1; goto end; }
614
615   /* Handle packets until the server closes the connection.
616    *
617    * The protocol looks much simpler from our point of view than from the
618    * client.
619    *
620    *   * Receive @TVPK_VER@; respond with @TVPK_VER | TVPF_ACK@.
621    *
622    *   * Receive zero or more @TVPK_BGROUP@.  Open a test group, producing
623    *     output packets, and eventually answer with @TVPK_BGROUP | TVPF_ACK@.
624    *
625    *       -- Receive zero or more @TVPK_TEST@.  Run a test, producing output
626    *          packets, and eventually answer with @TVPK_TEST | TVPF_ACK@.
627    *
628    *       -- Receive @TVPK_EGROUP@.  Maybe produce output packets, and
629    *          answer with @TVPK_EGROUP | TVPF_ACK@.
630    *
631    *  * Read EOF.  Stop.
632    */
633   for (;;) {
634
635     /* Read a packet.  End-of-file is expected here (and pretty much nowhere
636      * else).   Otherwise, we expect to see @TVPK_BGROUP@.
637      */
638     rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b);
639       if (rc == RECV_EOF) break;
640       else if (rc == RECV_FAIL) goto end;
641     if (buf_getu16l(&b, &pk)) goto bad;
642
643     switch (pk) {
644
645       case TVPK_BGROUP:
646         /* Start a group. */
647
648         /* Parse the packet payload. */
649         p = buf_getmem16l(&b, &sz); if (!p) goto bad;
650         if (BLEFT(&b)) goto bad;
651
652         /* Find the group given its name. */
653         for (t = srvtv.tests; t->name; t++)
654           if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz))
655             goto found_group;
656         rc = ioerr(&srvtv, &srvrc, "unknown test group `%.*s'",
657                    (int)sz, (char *)p);
658         goto end;
659
660       found_group:
661         /* Set up the test environment. */
662         srvtv.test = t; env = t->env;
663         if (env && env->setup == tvec_remotesetup)
664           env = ((struct tvec_remoteenv *)env)->r.env;
665         if (!env || !env->ctxsz) ctx = 0;
666         else ctx = xmalloc(env->ctxsz);
667         if (env && env->setup) env->setup(&srvtv, env, 0, ctx);
668
669         /* Initialize the registers. */
670         tvec_initregs(&srvtv);
671
672         /* Report that the group has been opened and that we're ready to run
673          * tests.
674          */
675         QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_BGROUP | TVPF_ACK);
676         else { rc = -1; goto end; }
677
678         /* Handle packets until we're told to end the group. */
679         for (;;) {
680
681           /* Read a packet.  We expect @TVPK_EGROUP@ or @TVPK_TEST@. */
682           if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
683           if (buf_getu16l(&b, &pk)) goto bad;
684
685           switch (pk) {
686
687             case TVPK_EGROUP:
688               /* End the group. */
689
690               /* Check the payload. */
691               if (BLEFT(&b)) goto bad;
692
693               /* Leave the group loop. */
694               goto endgroup;
695
696             case TVPK_SETVAR:
697               /* Set a subenvironment variable. */
698
699               /* Get the variable name. */
700               p = buf_getmem16l(&b, &sz); if (!p) goto bad;
701               DRESET(&d); DPUTM(&d, p, sz); DPUTZ(&d);
702
703               /* Look up the variable definition. */
704               if (env && env->findvar) {
705                 vd = env->findvar(&srvtv, d.buf, &varctx, ctx);
706                   if (vd) goto found_var;
707               }
708               rc = tvec_unkreg(&srvtv, d.buf); goto setvar_end;
709             found_var:
710
711               /* Set up the register. */
712               if (vd->regsz <= sizeof(rbuf))
713                 r = &rbuf;
714               else {
715                 if (rsz < vd->regsz) {
716                   xfree(r_alloc);
717                   if (!rsz) rsz = 8*sizeof(void *);
718                   while (rsz < vd->regsz) rsz *= 2;
719                   r_alloc = xmalloc(rsz);
720                 }
721                 r = r_alloc;
722               }
723
724               /* Collect and set the value. */
725               vd->def.ty->init(&r->v, &vd->def);
726               if (vd->def.ty->frombuf(&b, &r->v, &vd->def)) goto bad;
727               if (BLEFT(&b)) goto bad;
728               rc = vd->setvar(&srvtv, d.buf, &r->v, varctx);
729
730               /* Send the reply. */
731             setvar_end:
732               QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_SETVAR | TVPF_ACK)
733                 dbuf_putbyte(&srvrc.bout, rc ? 0xff : 0);
734               else { rc = -1; goto end; }
735               if (vd) { vd->def.ty->release(&r->v, &vd->def); vd = 0; }
736               break;
737
738             case TVPK_TEST:
739               /* Run a test. */
740
741               /* Parse the packet payload. */
742               if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs,
743                                    srvtv.nreg, srvtv.regsz))
744                 goto bad;
745               if (BLEFT(&b)) goto bad;
746
747               /* If we're not skipping the test group, then actually try to
748                * run the test.
749                */
750               if (!(srvtv.f&TVSF_SKIP)) {
751
752                 /* Prepare the output registers and reset the test outcome.
753                  * (The environment may force a skip.)
754                  */
755                 for (i = 0; i < srvtv.nrout; i++)
756                   if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
757                     TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
758                 srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK;
759
760                 /* Invoke the environment @before@ function. */
761                 tvec_setprogress("%%SETUP");
762                 if (env && env->before) env->before(&srvtv, ctx);
763
764                 /* Run the actual test. */
765                 if (!(srvtv.f&TVSF_ACTIVE))
766                   /* setup forced a skip */;
767                 else {
768                   tvec_setprogress("%%RUN");
769                   if (env && env->run)
770                     env->run(&srvtv, t->fn, ctx);
771                   else {
772                     t->fn(srvtv.in, srvtv.out, ctx);
773                     tvec_check(&srvtv, 0);
774                   }
775                 }
776
777                 /* Conclude the test. */
778                 tvec_setprogress("%%DONE");
779                 if (env && env->after) env->after(&srvtv, ctx);
780                 tvec_endtest(&srvtv);
781               }
782
783               /* Reset the input registers and report completion. */
784               tvec_releaseregs(&srvtv); tvec_initregs(&srvtv);
785               QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_TEST | TVPF_ACK);
786               else { rc = -1; goto end; }
787               break;
788
789             default:
790               /* Some other kind of packet.  Complain. */
791
792               rc = ioerr(&srvtv, &srvrc,
793                          "unexpected packet type 0x%04x during test group",
794                          pk);
795               goto end;
796
797           }
798         }
799
800       endgroup:
801         /* The test group completed. */
802
803         /* Tear down the environment and release other resources. */
804         if (env && env->teardown) env->teardown(&srvtv, ctx);
805         tvec_releaseregs(&srvtv);
806         xfree(ctx); srvtv.test = 0; env = 0; ctx = 0;
807
808         /* Report completion. */
809         QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_EGROUP | TVPF_ACK);
810         else { rc = -1; goto end; }
811         break;
812
813       default:
814         rc = ioerr(&srvtv, &srvrc,
815                    "unexpected packet type 0x%04x at top level", pk);
816     }
817   }
818   rc = 0;
819
820 end:
821   /* Clean up and return. */
822   if (env && env->teardown) env->teardown(&srvtv, ctx);
823   if (vd) vd->def.ty->release(&r->v, &vd->def);
824   xfree(ctx); xfree(r_alloc);
825   if (srvtv.test) tvec_releaseregs(&srvtv);
826   release_comms(&srvrc); tvec_end(&srvtv);
827   return (rc ? 2 : 0);
828
829 bad:
830   /* Miscellaneous malformed packet. */
831   rc = malformed(&srvtv, &srvrc); goto end;
832 }
833
834 /*----- Server output driver ----------------------------------------------*/
835
836 /* --- @remote_bsession@ --- *
837  *
838  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
839  *              @struct tvec_state *tv@ = the test state producing output
840  *
841  * Returns:     ---
842  *
843  * Use:         Begin a test session.
844  *
845  *              The remote driver does nothing at all.
846  */
847
848 static void remote_bsession(struct tvec_output *o, struct tvec_state *tv)
849   { ; }
850
851 /* --- @remote_esession@ --- *
852  *
853  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
854  *
855  * Returns:     Suggested exit code.
856  *
857  * Use:         End a test session.
858  *
859  *              The remote driver returns a suitable exit code without
860  *              printing anything.
861  */
862
863 static int remote_esession(struct tvec_output *o)
864   { return (srvtv.f&TVSF_ERROR ? 2 : 0); }
865
866 /* --- @remote_bgroup@ --- *
867  *
868  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
869  *
870  * Returns:     ---
871  *
872  * Use:         Begin a test group.
873  *
874  *              This is a stub which should never be called.
875  */
876
877 static void remote_bgroup(struct tvec_output *o)
878   { assert(!"remote_bgroup"); }
879
880 /* --- @remote_skipgroup@ --- *
881  *
882  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
883  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
884  *                      group, or null
885  *
886  * Returns:     ---
887  *
888  * Use:         Report that a test group is being skipped.
889  *
890  *              The remote driver sends a @TVPK_SKIP@ packet to its client.
891  */
892
893 static void remote_skipgroup(struct tvec_output *o,
894                              const char *excuse, va_list *ap)
895 {
896   QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIPGRP)
897     dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
898 }
899
900 /* --- @remote_egroup@ --- *
901  *
902  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
903  *
904  * Returns:     ---
905  *
906  * Use:         Report that a test group has finished.
907  *
908  *              This is a stub which should never be called.
909  */
910
911 static void remote_egroup(struct tvec_output *o)
912   { assert(!"remote_egroup"); }
913
914 /* --- @remote_btest@ --- *
915  *
916  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
917  *
918  * Returns:     ---
919  *
920  * Use:         Report that a test is starting.
921  *
922  *              This is a stub which should never be called.
923  */
924
925 static void remote_btest(struct tvec_output *o)
926   { assert(!"remote_btest"); }
927
928 /* --- @remote_skip@, @remote_fail@ --- *
929  *
930  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
931  *              @unsigned attr@ = attribute to apply to the outcome
932  *              @const char *outcome@ = outcome string to report
933  *              @const char *detail@, @va_list *ap@ = a detail message
934  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
935  *                      test
936  *
937  * Returns:     ---
938  *
939  * Use:         Report that a test has been skipped or failed.
940  *
941  *              The remote driver sends a @TVPK_SKIP@ or @TVPK_FAIL@ packet
942  *              to its client as appropriate.
943  */
944
945 static void remote_skip(struct tvec_output *o,
946                         const char *excuse, va_list *ap)
947 {
948   QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIP)
949     dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
950 }
951
952 static void remote_fail(struct tvec_output *o,
953                         const char *detail, va_list *ap)
954 {
955   QUEUEPK(&srvtv, &srvrc, 0, TVPK_FAIL)
956     if (!detail)
957       dbuf_putbyte(&srvrc.bout, 0);
958     else {
959       dbuf_putbyte(&srvrc.bout, 1);
960       dbuf_vputstrf16l(&srvrc.bout, detail, ap);
961     }
962 }
963
964 /* --- @remote_dumpreg@ --- *
965  *
966  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
967  *              @unsigned disp@ = register disposition
968  *              @const union tvec_regval *rv@ = register value
969  *              @const struct tvec_regdef *rd@ = register definition
970  *
971  * Returns:     ---
972  *
973  * Use:         Dump a register.
974  *
975  *              The remote driver sends a @TVPK_DUMPREG@ packet to its
976  *              client.  This will only work if the register definition is
977  *              one of those listed in the current test definition.
978  */
979
980 static void remote_dumpreg(struct tvec_output *o,
981                            unsigned disp, const union tvec_regval *rv,
982                            const struct tvec_regdef *rd)
983 {
984   const struct tvec_regdef *reg;
985   unsigned r;
986
987   /* Find the register definition. */
988   for (reg = srvtv.test->regs, r = 0; reg->name; reg++, r++)
989     if (reg == rd) goto found;
990   assert(!"unexpected register definition");
991
992 found:
993   QUEUEPK(&srvtv, &srvrc, 0, TVPK_DUMPREG) {
994     dbuf_putu16l(&srvrc.bout, r);
995     dbuf_putu16l(&srvrc.bout, disp);
996     if (!rv)
997       dbuf_putbyte(&srvrc.bout, 0);
998     else {
999       dbuf_putbyte(&srvrc.bout, 1);
1000       rd->ty->tobuf(DBUF_BUF(&srvrc.bout), rv, rd);
1001     }
1002   }
1003 }
1004
1005 /* --- @remote_etest@ --- *
1006  *
1007  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
1008  *              @unsigned outcome@ = the test outcome
1009  *
1010  * Returns:     ---
1011  *
1012  * Use:         Report that a test has finished.
1013  *
1014  *              The remote driver does nothing at all.
1015  */
1016
1017 static void remote_etest(struct tvec_output *o, unsigned outcome)
1018   { ; }
1019
1020 /* --- @remote_bbench@ --- *
1021  *
1022  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
1023  *              @const char *ident@ = identifying register values
1024  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1025  *
1026  * Returns:     ---
1027  *
1028  * Use:         Report that a benchmark has started.
1029  *
1030  *              The remote driver sends a @TVPK_BBENCH@ packet to its client.
1031  */
1032
1033 static void remote_bbench(struct tvec_output *o,
1034                           const char *ident, unsigned unit)
1035 {
1036   QUEUEPK(&srvtv, &srvrc, 0, TVPK_BBENCH) {
1037     dbuf_putstr32l(&srvrc.bout, ident);
1038     dbuf_putu16l(&srvrc.bout, unit);
1039   }
1040 }
1041
1042 /* --- @remote_ebench@ --- *
1043  *
1044  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
1045  *              @const char *ident@ = identifying register values
1046  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1047  *              @const struct bench_timing *tm@ = measurement
1048  *
1049  * Returns:     ---
1050  *
1051  * Use:         Report a benchmark's results
1052  *
1053  *              The remote driver sends a @TVPK_EBENCH@ packet to its client.
1054  */
1055
1056 static void remote_ebench(struct tvec_output *o,
1057                           const char *ident, unsigned unit,
1058                           const struct bench_timing *t)
1059 {
1060   QUEUEPK(&srvtv, &srvrc, 0, TVPK_EBENCH) {
1061     dbuf_putstr32l(&srvrc.bout, ident);
1062     dbuf_putu16l(&srvrc.bout, unit);
1063     if (!t || !(t->f&BTF_ANY))
1064       dbuf_putu16l(&srvrc.bout, 0);
1065     else {
1066       dbuf_putu16l(&srvrc.bout, t->f);
1067       dbuf_putf64l(&srvrc.bout, t->n);
1068       if (t->f&BTF_TIMEOK) dbuf_putf64l(&srvrc.bout, t->t);
1069       if (t->f&BTF_CYOK) dbuf_putf64l(&srvrc.bout, t->cy);
1070     }
1071   }
1072 }
1073
1074 /* --- @remote_report@ --- *
1075  *
1076  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
1077  *              @unsigned level@ = message level (@TVLEV_...@)
1078  *              @const char *msg@, @va_list *ap@ = format string and
1079  *                      arguments
1080  *
1081  * Returns:     ---
1082  *
1083  * Use:         Report a message to the user.
1084  *
1085  *              The remote driver sends a @TVPK_REPORT@ packet to its
1086  *              client.  If its attempt to transmit the packet fails, then
1087  *              the message is written to the standard error stream instead,
1088  *              in the hope that this will help it be noticed.
1089  */
1090
1091 static void remote_report(struct tvec_output *o, unsigned level,
1092                           const char *msg, va_list *ap)
1093 {
1094   QUEUEPK(&srvtv, &srvrc, 0, TVPK_REPORT) {
1095     dbuf_putu16l(&srvrc.bout, level);
1096     dbuf_vputstrf16l(&srvrc.bout, msg, ap);
1097   } else {
1098     fprintf(stderr, "%s %s: ", QUIS, tvec_strlevel(level));
1099     vfprintf(stderr, msg, *ap);
1100     fputc('\n', stderr);
1101   }
1102 }
1103
1104 /* --- @remote_destroy@ --- *
1105  *
1106  * Arguments:   @struct tvec_output *o@ = output sink (ignored)
1107  *
1108  * Returns:     ---
1109  *
1110  * Use:         Release the resources held by the output driver.
1111  *
1112  *              The remote driver does nothing at all.
1113  */
1114
1115 static void remote_destroy(struct tvec_output *o)
1116   { ; }
1117
1118 static const struct tvec_outops remote_ops = {
1119   remote_bsession, remote_esession,
1120   remote_bgroup, remote_skipgroup, remote_egroup,
1121   remote_btest, remote_skip, remote_fail, remote_dumpreg, remote_etest,
1122   remote_bbench, remote_ebench,
1123   remote_report,
1124   remote_destroy
1125 };
1126
1127 /*----- Pseudoregister definitions ----------------------------------------*/
1128
1129 static tvec_setvarfn setvar_local, setvar_remote;
1130
1131 static const struct tvec_flag exit_flags[] = {
1132
1133   /* Cause codes. */
1134   { "running",          TVXF_CAUSEMASK,         TVXST_RUN },
1135   { "exited",           TVXF_CAUSEMASK,         TVXST_EXIT },
1136   { "killed",           TVXF_CAUSEMASK,         TVXST_KILL },
1137   { "stopped",          TVXF_CAUSEMASK,         TVXST_STOP },
1138   { "continued",        TVXF_CAUSEMASK,         TVXST_CONT },
1139   { "disconnected",     TVXF_CAUSEMASK,         TVXST_DISCONN },
1140   { "unknown",          TVXF_CAUSEMASK,         TVXST_UNK },
1141   { "error",            TVXF_CAUSEMASK,         TVXST_ERR },
1142
1143   /*
1144     ;;; The signal name table is very boring to type.  To make life less
1145     ;;; awful, put the signal names in this list and evaluate the code to
1146     ;;; get Emacs to regenerate it.
1147
1148     (let ((signals '(HUP INT QUIT ILL TRAP ABRT IOT EMT FPE KILL BUS SEGV SYS
1149                          PIPE ALRM TERM URG STOP TSTP CONT CHLD CLD TTIN TTOU
1150                          POLL IO TIN XCPU XFSZ VTALRM PROF WINCH USR1 USR2
1151                          STKFLT INFO PWR THR LWP LIBRT LOST)))
1152       (save-excursion
1153         (goto-char (point-min))
1154         (search-forward (concat "***" "BEGIN siglist" "***"))
1155         (beginning-of-line 2)
1156         (delete-region (point)
1157                        (progn
1158                          (search-forward "***END***")
1159                          (beginning-of-line)
1160                          (point)))
1161         (dolist (sig signals)
1162           (insert (format "#ifdef SIG%s\n  { \"SIG%s\", TVXF_VALMASK | TVXF_SIG, SIG%s | TVXF_SIG },\n#endif\n"
1163                           sig sig sig)))))
1164   */
1165
1166   /***BEGIN siglist***/
1167 #ifdef SIGHUP
1168   { "SIGHUP", TVXF_VALMASK | TVXF_SIG, SIGHUP | TVXF_SIG },
1169 #endif
1170 #ifdef SIGINT
1171   { "SIGINT", TVXF_VALMASK | TVXF_SIG, SIGINT | TVXF_SIG },
1172 #endif
1173 #ifdef SIGQUIT
1174   { "SIGQUIT", TVXF_VALMASK | TVXF_SIG, SIGQUIT | TVXF_SIG },
1175 #endif
1176 #ifdef SIGILL
1177   { "SIGILL", TVXF_VALMASK | TVXF_SIG, SIGILL | TVXF_SIG },
1178 #endif
1179 #ifdef SIGTRAP
1180   { "SIGTRAP", TVXF_VALMASK | TVXF_SIG, SIGTRAP | TVXF_SIG },
1181 #endif
1182 #ifdef SIGABRT
1183   { "SIGABRT", TVXF_VALMASK | TVXF_SIG, SIGABRT | TVXF_SIG },
1184 #endif
1185 #ifdef SIGIOT
1186   { "SIGIOT", TVXF_VALMASK | TVXF_SIG, SIGIOT | TVXF_SIG },
1187 #endif
1188 #ifdef SIGEMT
1189   { "SIGEMT", TVXF_VALMASK | TVXF_SIG, SIGEMT | TVXF_SIG },
1190 #endif
1191 #ifdef SIGFPE
1192   { "SIGFPE", TVXF_VALMASK | TVXF_SIG, SIGFPE | TVXF_SIG },
1193 #endif
1194 #ifdef SIGKILL
1195   { "SIGKILL", TVXF_VALMASK | TVXF_SIG, SIGKILL | TVXF_SIG },
1196 #endif
1197 #ifdef SIGBUS
1198   { "SIGBUS", TVXF_VALMASK | TVXF_SIG, SIGBUS | TVXF_SIG },
1199 #endif
1200 #ifdef SIGSEGV
1201   { "SIGSEGV", TVXF_VALMASK | TVXF_SIG, SIGSEGV | TVXF_SIG },
1202 #endif
1203 #ifdef SIGSYS
1204   { "SIGSYS", TVXF_VALMASK | TVXF_SIG, SIGSYS | TVXF_SIG },
1205 #endif
1206 #ifdef SIGPIPE
1207   { "SIGPIPE", TVXF_VALMASK | TVXF_SIG, SIGPIPE | TVXF_SIG },
1208 #endif
1209 #ifdef SIGALRM
1210   { "SIGALRM", TVXF_VALMASK | TVXF_SIG, SIGALRM | TVXF_SIG },
1211 #endif
1212 #ifdef SIGTERM
1213   { "SIGTERM", TVXF_VALMASK | TVXF_SIG, SIGTERM | TVXF_SIG },
1214 #endif
1215 #ifdef SIGURG
1216   { "SIGURG", TVXF_VALMASK | TVXF_SIG, SIGURG | TVXF_SIG },
1217 #endif
1218 #ifdef SIGSTOP
1219   { "SIGSTOP", TVXF_VALMASK | TVXF_SIG, SIGSTOP | TVXF_SIG },
1220 #endif
1221 #ifdef SIGTSTP
1222   { "SIGTSTP", TVXF_VALMASK | TVXF_SIG, SIGTSTP | TVXF_SIG },
1223 #endif
1224 #ifdef SIGCONT
1225   { "SIGCONT", TVXF_VALMASK | TVXF_SIG, SIGCONT | TVXF_SIG },
1226 #endif
1227 #ifdef SIGCHLD
1228   { "SIGCHLD", TVXF_VALMASK | TVXF_SIG, SIGCHLD | TVXF_SIG },
1229 #endif
1230 #ifdef SIGCLD
1231   { "SIGCLD", TVXF_VALMASK | TVXF_SIG, SIGCLD | TVXF_SIG },
1232 #endif
1233 #ifdef SIGTTIN
1234   { "SIGTTIN", TVXF_VALMASK | TVXF_SIG, SIGTTIN | TVXF_SIG },
1235 #endif
1236 #ifdef SIGTTOU
1237   { "SIGTTOU", TVXF_VALMASK | TVXF_SIG, SIGTTOU | TVXF_SIG },
1238 #endif
1239 #ifdef SIGPOLL
1240   { "SIGPOLL", TVXF_VALMASK | TVXF_SIG, SIGPOLL | TVXF_SIG },
1241 #endif
1242 #ifdef SIGIO
1243   { "SIGIO", TVXF_VALMASK | TVXF_SIG, SIGIO | TVXF_SIG },
1244 #endif
1245 #ifdef SIGTIN
1246   { "SIGTIN", TVXF_VALMASK | TVXF_SIG, SIGTIN | TVXF_SIG },
1247 #endif
1248 #ifdef SIGXCPU
1249   { "SIGXCPU", TVXF_VALMASK | TVXF_SIG, SIGXCPU | TVXF_SIG },
1250 #endif
1251 #ifdef SIGXFSZ
1252   { "SIGXFSZ", TVXF_VALMASK | TVXF_SIG, SIGXFSZ | TVXF_SIG },
1253 #endif
1254 #ifdef SIGVTALRM
1255   { "SIGVTALRM", TVXF_VALMASK | TVXF_SIG, SIGVTALRM | TVXF_SIG },
1256 #endif
1257 #ifdef SIGPROF
1258   { "SIGPROF", TVXF_VALMASK | TVXF_SIG, SIGPROF | TVXF_SIG },
1259 #endif
1260 #ifdef SIGWINCH
1261   { "SIGWINCH", TVXF_VALMASK | TVXF_SIG, SIGWINCH | TVXF_SIG },
1262 #endif
1263 #ifdef SIGUSR1
1264   { "SIGUSR1", TVXF_VALMASK | TVXF_SIG, SIGUSR1 | TVXF_SIG },
1265 #endif
1266 #ifdef SIGUSR2
1267   { "SIGUSR2", TVXF_VALMASK | TVXF_SIG, SIGUSR2 | TVXF_SIG },
1268 #endif
1269 #ifdef SIGSTKFLT
1270   { "SIGSTKFLT", TVXF_VALMASK | TVXF_SIG, SIGSTKFLT | TVXF_SIG },
1271 #endif
1272 #ifdef SIGINFO
1273   { "SIGINFO", TVXF_VALMASK | TVXF_SIG, SIGINFO | TVXF_SIG },
1274 #endif
1275 #ifdef SIGPWR
1276   { "SIGPWR", TVXF_VALMASK | TVXF_SIG, SIGPWR | TVXF_SIG },
1277 #endif
1278 #ifdef SIGTHR
1279   { "SIGTHR", TVXF_VALMASK | TVXF_SIG, SIGTHR | TVXF_SIG },
1280 #endif
1281 #ifdef SIGLWP
1282   { "SIGLWP", TVXF_VALMASK | TVXF_SIG, SIGLWP | TVXF_SIG },
1283 #endif
1284 #ifdef SIGLIBRT
1285   { "SIGLIBRT", TVXF_VALMASK | TVXF_SIG, SIGLIBRT | TVXF_SIG },
1286 #endif
1287 #ifdef SIGLOST
1288   { "SIGLOST", TVXF_VALMASK | TVXF_SIG, SIGLOST | TVXF_SIG },
1289 #endif
1290   /***END***/
1291
1292   /* This should be folded into the signal entries above. */
1293   { "signal",           TVXF_SIG,               TVXF_SIG },
1294
1295   TVEC_ENDFLAGS
1296 };
1297 static const struct tvec_flaginfo exit_flaginfo =
1298   { "exit-status", exit_flags, &tvrange_uint };
1299 static const struct tvec_vardef exit_var =
1300   { sizeof(struct tvec_reg), setvar_local,
1301     { "@exit", -1, &tvty_flags, 0, { &exit_flaginfo } } };
1302
1303 /* Progress. */
1304
1305 static const struct tvec_vardef progress_var =
1306   { sizeof(struct tvec_reg), setvar_local,
1307     { "@progress", -1, &tvty_text, 0 } };
1308
1309 /* Reconnection. */
1310
1311 static const struct tvec_uassoc reconn_assocs[] = {
1312   { "on-demand",        TVRCN_DEMAND },
1313   { "force",            TVRCN_FORCE },
1314   { "skip",             TVRCN_SKIP },
1315   TVEC_ENDENUM
1316 };
1317 static const struct tvec_uenuminfo reconn_enuminfo =
1318   { "remote-reconnection", reconn_assocs, &tvrange_uint };
1319 static const struct tvec_vardef reconn_var =
1320   { sizeof(struct tvec_reg), setvar_local,
1321     { "@reconnect", -1, &tvty_uenum, 0, { &reconn_enuminfo } } };
1322
1323 /*----- Client ------------------------------------------------------------*/
1324
1325 /* Connection state. */
1326 enum {
1327   CONN_BROKEN = -2,                     /* previously broken */
1328   CONN_FAILED = -1,                     /* attempt freshly failed */
1329   CONN_ESTABLISHED = 0,                 /* previously established */
1330   CONN_FRESH = 1                        /* freshly connected */
1331 };
1332
1333 /* --- @handle_packets@ --- *
1334  *
1335  * Arguments:   @struct tvec_state *tv@ = test-vector state
1336  *              @struct tvec_remotectx *r@ = remote client context
1337  *              @unsigned f@ = receive flags (@RCVF_...@)
1338  *              @uint16 end@ = expected end packet type
1339  *              @buf *b_out@ = buffer in which to return end packet payload
1340  *
1341  * Returns:     A @RECV_...@ code.
1342  *
1343  * Use:         Handles notification packets from the server until a final
1344  *              termination packet is received.
1345  *
1346  *              The client/server protocol consists of a number of flows,
1347  *              beginning with a request from the client, followed by a
1348  *              number of notifications from the server, and terminated by an
1349  *              acknowledgement to the original request indicating that the
1350  *              server has completed acting on the original request.
1351  *
1352  *              This function handles the notifications issued by the server,
1353  *              returning when one of the following occurs: (a) a packet of
1354  *              type @end@ is received, in which case the function returns
1355  *              @RECV_OK@ and the remainder of the packet payload is left in
1356  *              @b_out@; (b) the flag @RCVF_ALLOWEOF@ was set in @f@ on entry
1357  *              and end-of-file is received at a packet boundary, in which
1358  *              case the function returns @RECV_EOF@; or (c) an I/O error
1359  *              occurs, in which case @ioerr@ is called and the function
1360  *              returns @RECV_FAIL@.
1361  */
1362
1363 static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
1364                           unsigned f, uint16 end, buf *b_out)
1365 {
1366   struct tvec_output *o = tv->output;
1367   uint16 pk, u, v;
1368   const char *p; size_t n;
1369   dstr d = DSTR_INIT;
1370   buf *b = b_out;
1371   const struct tvec_regdef *rd;
1372   struct bench_timing bt;
1373   struct tvec_reg *reg = 0;
1374   unsigned i;
1375   int rc;
1376
1377   for (;;) {
1378
1379     /* Read the next packet.  If we didn't receive one then end the loop.
1380      * Otherwise, retrieve the packet type and check it against @end@: quit
1381      * the loop if we get a match.
1382      */
1383     rc = remote_recv(tv, &r->rc, f, b); if (rc) break;
1384     if (buf_getu16l(b, &pk)) goto bad;
1385     if (pk == end) { rc = 0; break; }
1386
1387     /* Dispatch based on the packet type. */
1388     switch (pk) {
1389
1390       case TVPK_PROGRESS:
1391         /* A progress report.  Update the saved progress. */
1392
1393         p = buf_getmem16l(b, &n); if (!p) goto bad;
1394         if (BLEFT(b)) goto bad;
1395
1396         DRESET(&r->progress); DPUTM(&r->progress, p, n); DPUTZ(&r->progress);
1397         break;
1398
1399       case TVPK_REPORT:
1400         /* A report.  Recover the message and pass it along. */
1401
1402         if (buf_getu16l(b, &u)) goto bad;
1403         p = buf_getmem16l(b, &n); if (!p) goto bad;
1404         if (BLEFT(b)) goto bad;
1405
1406         DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1407         tvec_report(tv, u, "%s", d.buf);
1408         break;
1409
1410       case TVPK_SKIPGRP:
1411         /* A request to skip the group.  Recover the excuse message and pass
1412          * it along.
1413          */
1414
1415         p = buf_getmem16l(b, &n); if (!p) goto bad;
1416         if (BLEFT(b)) goto bad;
1417
1418         DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1419         tvec_skipgroup(tv, "%s", d.buf);
1420         break;
1421
1422       case TVPK_SKIP:
1423         /* A request to skip the test.  Recover the excuse message and pass
1424          * it along, if it's not unreasonable.
1425          */
1426
1427         if (!(tv->f&TVSF_ACTIVE)) {
1428           rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name);
1429           goto end;
1430         }
1431
1432         p = buf_getmem16l(b, &n); if (!p) goto bad;
1433         if (BLEFT(b)) goto bad;
1434
1435         DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1436         tvec_skip(tv, "%s", d.buf);
1437         break;
1438
1439       case TVPK_FAIL:
1440         /* A report that the test failed.  Recover the detail message, if
1441          * any, and pass it along, if it's not unreasonable.
1442          */
1443
1444         if (!(tv->f&TVSF_ACTIVE) &&
1445             ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) {
1446           rc = ioerr(tv, &r->rc, "test `%s' not active or failing",
1447                      tv->test->name);
1448           goto end;
1449         }
1450
1451         rc = buf_getbyte(b); if (rc < 0) goto bad;
1452         if (rc) { p = buf_getmem16l(b, &n); if (!p) goto bad; }
1453         else p = 0;
1454         if (BLEFT(b)) goto bad;
1455
1456         if (!p)
1457           tvec_fail(tv, 0);
1458         else {
1459           DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1460           tvec_fail(tv, "%s", d.buf);
1461         }
1462         break;
1463
1464       case TVPK_DUMPREG:
1465         /* A request to dump a register. */
1466
1467         /* Find the register definition. */
1468         if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
1469         for (rd = tv->test->regs, i = 0; rd->name; rd++, i++)
1470           if (i == u) goto found_reg;
1471         rc = ioerr(tv, &r->rc,
1472                    "register definition %u out of range for test `%s'",
1473                    u, tv->test->name);
1474         goto end;
1475       found_reg:
1476         if (v >= TVRD_LIMIT) {
1477           rc = ioerr(tv, &r->rc, "register disposition %u out of range", v);
1478           goto end;
1479         }
1480
1481         /* Read the flag.  If there's no register value, then `dump' its
1482          * absence.  Otherwise retrieve the register value and dump it.
1483          */
1484         rc = buf_getbyte(b); if (rc < 0) goto bad;
1485         if (!rc)
1486           tvec_dumpreg(tv, v, 0, rd);
1487         else {
1488           if (!reg) reg = xmalloc(tv->regsz);
1489           rd->ty->init(&reg->v, rd);
1490           rc = rd->ty->frombuf(b, &reg->v, rd);
1491           if (!rc) tvec_dumpreg(tv, v, &reg->v, rd);
1492           rd->ty->release(&reg->v, rd);
1493           if (rc) goto bad;
1494         }
1495         if (BLEFT(b)) goto bad;
1496         break;
1497
1498       case TVPK_BBENCH:
1499         /* A report that we're starting a benchmark.  Pass this along. */
1500
1501         p = buf_getmem32l(b, &n); if (!p) goto bad;
1502         if (buf_getu16l(b, &u)) goto bad;
1503         if (BLEFT(b)) goto bad;
1504         if (u >= TVBU_LIMIT) {
1505           rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
1506           goto end;
1507         }
1508
1509         DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1510         o->ops->bbench(o, d.buf, u);
1511         break;
1512
1513       case TVPK_EBENCH:
1514         /* A report that a benchmark completed.  Pass this along. */
1515
1516         p = buf_getmem32l(b, &n); if (!p) goto bad;
1517         if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
1518         if (u >= TVBU_LIMIT) {
1519           rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
1520           goto end;
1521         }
1522         if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad;
1523         if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad;
1524         if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad;
1525         if (BLEFT(b)) goto bad;
1526
1527         DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
1528         o->ops->ebench(o, d.buf, u, v&BTF_ANY ? &bt : 0);
1529         break;
1530
1531       default:
1532         /* Something else.  This is unexpected. */
1533
1534         rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
1535         goto end;
1536     }
1537   }
1538
1539 end:
1540   DDESTROY(&d);
1541   xfree(reg);
1542   return (rc);
1543 bad:
1544   rc = malformed(tv, &r->rc); goto end;
1545 }
1546
1547 /* --- @reap_kid@ --- *
1548  *
1549  * Arguments:   @struct tvec_state *tv@ = test-vector state
1550  *              @struct tvec_remotectx *r@ = remote client context
1551  *
1552  * Returns:     ---
1553  *
1554  * Use:         Determine the exit status of a broken connection, setting
1555  *              @r->exit@ appropriately.
1556  *
1557  *              If @r->kid@ is negative, the exit status has already been
1558  *              set, and nothing further happens; this is not an error.
1559  *
1560  *              If @r->kid@ is zero, then there is no real child process
1561  *              (e.g., because the remote connection is a network connection
1562  *              or similar), so @r->exit@ is set equal to @RVXST_DISCONN@.
1563  *
1564  *              If @r->kid@ is positive, then it holds a child process id;
1565  *              the function waits for it to end and collects its exit status
1566  *
1567  *              It is an error to call this function if the connection is not
1568  *              broken.
1569  */
1570
1571 static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
1572 {
1573   pid_t kid;
1574   int st;
1575
1576   assert(r->rc.f&TVRF_BROKEN);
1577   if (!r->kid)
1578     { r->exit = TVXST_DISCONN; r->kid = -1; }
1579   else if (r->kid > 0) {
1580     kid = waitpid(r->kid, &st, 0);
1581     if (kid < 0) {
1582       tvec_notice(tv, "failed to wait for remote child: %s",
1583                   strerror(errno));
1584       r->exit = TVXST_ERR;
1585     } else if (!kid) {
1586       tvec_notice(tv, "remote child vanished without a trace");
1587       r->exit = TVXST_ERR;
1588     } else if (WIFCONTINUED(st))
1589       r->exit = TVXST_CONT;
1590     else if (WIFSIGNALED(st))
1591       r->exit = TVXST_KILL | TVXF_SIG | WTERMSIG(st);
1592     else if (WIFSTOPPED(st))
1593       r->exit = TVXST_STOP | TVXF_SIG | WSTOPSIG(st);
1594     else if (WIFEXITED(st))
1595       r->exit = TVXST_EXIT | WEXITSTATUS(st);
1596     else {
1597       tvec_notice(tv, "remote child died with unknown status 0x%04x",
1598                   (unsigned)st);
1599       r->exit = TVXST_UNK;
1600     }
1601     r->kid = -1;
1602   }
1603 }
1604
1605 /* --- @report_errline@ --- *
1606  *
1607  * Arguments:   @char *p@ = pointer to the line
1608  *              @size_t n@ = length in characters
1609  *              @void *ctx@ = context, secretly a @struct tvec_remotectx@
1610  *
1611  * Returns:     ---
1612  *
1613  * Use:         Print a line of stderr output from the child.  If
1614  *              @TVRF_MUFFLE@ is set, then discard the line silently.
1615  *
1616  *              This is an @lbuf_func@, invoked via @drain_errfd@.
1617  */
1618
1619 static void report_errline(char *p, size_t n, void *ctx)
1620 {
1621   struct tvec_remotectx *r = ctx;
1622   struct tvec_state *tv = r->tv;
1623
1624   if (p && !(r->rc.f&TVRF_MUFFLE))
1625     tvec_notice(tv, "child process stderr: %s", p);
1626 }
1627
1628 /* --- @drain_errfd@ --- *
1629  *
1630  * Arguments:   @struct tvec_state *tv@ = test-vector state
1631  *              @struct tvec_remotectx *r@ = remote client context
1632  *              @unsigned f@ = receive flags (@ERF_...@)
1633  *
1634  * Returns:     Zero on success, %$-1$% on error.
1635  *
1636  * Use:         Collect material written by the child to its stderr stream
1637  *              and report it.
1638  *
1639  *              If @f@ has @ERF_SILENT@ set, then discard the stderr material
1640  *              without reporting it.  Otherwise it is reported as
1641  *              @TVLEV_NOTE@.
1642  *
1643  *              if @f@ has @ERF_CLOSE@ set, then continue reading until
1644  *              end-of-file is received; also, report any final partial line,
1645  *              and close @r->errfd@.
1646  *
1647  *              If @r->errfd@ is already closed, or never established, then
1648  *              do nothing and return successfully.
1649  */
1650
1651 #define ERF_SILENT 0x0001u
1652 #define ERF_CLOSE 0x0002u
1653 static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
1654                        unsigned f)
1655 {
1656   char *p; size_t sz;
1657   ssize_t n;
1658   int rc;
1659
1660   /* Preliminaries.  Bail if there is no error stream to fetch.  Arrange
1661    * (rather clumsily) to muffle the output if we're supposed to be client.
1662    * And set the nonblocking state on @errfd@ appropriately.
1663    */
1664   if (r->errfd < 0) { rc = 0; goto end; }
1665   if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
1666   else r->rc.f &= ~TVRF_MUFFLE;
1667   if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) {
1668     rc = ioerr(tv, &r->rc, "failed to %s error non-blocking flag",
1669                f&ERF_CLOSE ? "clear" : "set");
1670     goto end;
1671   }
1672
1673   /* Read pieces of error output and feed them into the line buffer. */
1674   for (;;) {
1675     sz = lbuf_free(&r->errbuf, &p);
1676     n = read(r->errfd, p, sz);
1677       if (!n) break;
1678       if (n < 0) {
1679         if (errno == EINTR) continue;
1680         if (!(f&ERF_CLOSE) && (errno == EWOULDBLOCK || errno == EAGAIN))
1681           break;
1682         rc = ioerr(tv, &r->rc, "failed to read child stderr: %s",
1683                    strerror(errno));
1684         goto end;
1685       }
1686     lbuf_flush(&r->errbuf, p, n);
1687   }
1688
1689   /* Done. */
1690   rc = 0;
1691 end:
1692   if (f&ERF_CLOSE) {
1693     lbuf_close(&r->errbuf);
1694     close(r->errfd); r->errfd = -1;
1695   }
1696   return (rc);
1697 }
1698
1699 /* --- @disconnect_remote@ --- *
1700  *
1701  * Arguments:   @struct tvec_state *tv@ = test-vector state
1702  *              @struct tvec_remotectx *r@ = remote client context
1703  *              @unsigned f@ = receive flags (@DCF_...@)
1704  *
1705  * Returns:     ---
1706  *
1707  * Use:         Disconnect and shut down all of the remote client state.
1708  *
1709  *              If @f@ has @DCF_KILL@ set then send the child process (if
1710  *              any) @SIGTERM@ to make sure it shuts down in a timely manner.
1711  *
1712  *              In detail: this function closes the @infd@ and @outfd@
1713  *              descriptors, drains and closes @errfd@, and collects the exit
1714  *              status (if any).
1715  */
1716
1717 #define DCF_KILL 0x0100u
1718 static void disconnect_remote(struct tvec_state *tv,
1719                               struct tvec_remotectx *r, unsigned f)
1720 {
1721   if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
1722   close_comms(&r->rc);
1723   drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
1724 }
1725
1726 /* --- @connect_remote@ --- *
1727  *
1728  * Arguments:   @struct tvec_state *tv@ = test-vector state
1729  *              @struct tvec_remotectx *r@ = remote client context
1730  *
1731  * Returns:     Zero on success, %$-1$% on error.
1732  *
1733  * Use:         Connect to the test server.
1734  */
1735
1736 static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
1737 {
1738   const struct tvec_remoteenv *re = r->re;
1739   pid_t kid = 0;
1740   buf b;
1741   uint16 v;
1742   int infd = -1, outfd = -1, errfd = -1, rc;
1743
1744   /* If we're already connected, then there's nothing to do. */
1745   if (r->kid >= 0) { rc = 0; goto end; }
1746
1747   /* Set the preliminary progress indication. */
1748   DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
1749
1750   /* Call the connection function to establish descriptors. */
1751   if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re))
1752     { rc = -1; goto end; }
1753
1754   /* Establish communications state. */
1755   setup_comms(&r->rc, infd, outfd); r->kid = kid; r->errfd = errfd;
1756   lbuf_init(&r->errbuf, report_errline, r);
1757   r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN;
1758
1759   /* Do version negotiation. */
1760   QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) {
1761     dbuf_putu16l(&r->rc.bout, 0);
1762     dbuf_putu16l(&r->rc.bout, 0);
1763   } else { rc = -1; goto end; }
1764   if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b))
1765     { rc = -1; goto end; }
1766   if (buf_getu16l(&b, &v)) goto bad;
1767   if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1768   if (v) {
1769     rc = ioerr(tv, &r->rc, "protocol version %u not supported", v);
1770     goto end;
1771   }
1772   r->ver = v;
1773
1774   /* Begin the test group at the server. */
1775   QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP)
1776     dbuf_putstr16l(&r->rc.bout, tv->test->name);
1777   else { rc = -1; goto end; }
1778   if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b))
1779     { rc = -1; goto end; }
1780   if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1781
1782   /* Done. */
1783   rc = 0;
1784 end:
1785   if (rc) disconnect_remote(tv, r, DCF_KILL);
1786   return (rc);
1787 bad:
1788   rc = malformed(tv, &r->rc); goto end;
1789 }
1790
1791 /* --- @check_comms@ --- *
1792  *
1793  * Arguments:   @struct tvec_state *tv@ = test-vector state
1794  *              @struct tvec_remotectx *r@ = remote client context
1795  *
1796  * Returns:     A @CONN_...@ code reflecting the current communication
1797  *              state.
1798  *
1799  * Use:         Determine the current connection state.  If the connection
1800  *              has recently broken (i.e., @TVRF_BROKEN@ is set in @r->rc.f@)
1801  *              since the last time we checked then disconnect.
1802  */
1803
1804 static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r)
1805 {
1806   if (r->kid < 0)
1807     return (CONN_BROKEN);
1808   else if (r->rc.f&TVRF_BROKEN)
1809     { disconnect_remote(tv, r, DCF_KILL); return (CONN_FAILED); }
1810   else
1811     return (CONN_ESTABLISHED);
1812 }
1813
1814 /* --- @try_reconnect@ --- *
1815  *
1816  * Arguments:   @struct tvec_state *tv@ = test-vector state
1817  *              @struct tvec_remotectx *r@ = remote client context
1818  *
1819  * Returns:     A @CONN_...@ code reflecting the new communication state.
1820  *
1821  * Use:         Reconnects to the server according to the configured
1822  *              @TVRCN_...@ policy.
1823  */
1824
1825 static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
1826 {
1827   int rc;
1828
1829   switch (r->rc.f&TVRF_RCNMASK) {
1830     case TVRCN_DEMAND:
1831       rc = check_comms(tv, r);
1832       if (rc < CONN_ESTABLISHED) {
1833         close_comms(&r->rc);
1834         if (connect_remote(tv, r)) rc = CONN_FAILED;
1835         else rc = CONN_FRESH;
1836       }
1837       break;
1838     case TVRCN_FORCE:
1839       disconnect_remote(tv, r, DCF_KILL);
1840       if (connect_remote(tv, r)) rc = CONN_FAILED;
1841       else rc = CONN_FRESH;
1842       break;
1843     case TVRCN_SKIP:
1844       rc = check_comms(tv, r);
1845       break;
1846     default:
1847       abort();
1848   }
1849   return (rc);
1850 }
1851
1852 /*----- Remote environment ------------------------------------------------*/
1853
1854 /* --- @reset_vars@ --- *
1855  *
1856  * Arguments:   @struct tvec_remotectx *r@ = remote client context
1857  *
1858  * Returns:     ---
1859  *
1860  * Use:         Reset the pseudoregisters set through @tvec_remoteset@.
1861  */
1862
1863 static void reset_vars(struct tvec_remotectx *r)
1864 {
1865   const struct tvec_remoteenv *re = r->re;
1866
1867   r->exwant = TVXST_RUN;
1868   r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) |
1869             (re->r.dflt_reconn&TVRF_RCNMASK);
1870   DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
1871 }
1872
1873 /* --- @tvec_remotesetup@ --- *
1874  *
1875  * Arguments:   @struct tvec_state *tv@ = test vector state
1876  *              @const struct tvec_env *env@ = environment description
1877  *              @void *pctx@ = parent context (ignored)
1878  *              @void *ctx@ = context pointer to initialize
1879  *
1880  * Returns:     ---
1881  *
1882  * Use:         Initialize a timeout environment context.
1883  *
1884  *              The environment description should be a @struct
1885  *              tvec_remoteenv@ subclass suitable for use by the @connect@
1886  *              function.
1887  */
1888
1889 void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
1890                       void *pctx, void *ctx)
1891 {
1892   struct tvec_remotectx *r = ctx;
1893   const struct tvec_remoteenv *re = (const struct tvec_remoteenv *)env;
1894   const struct tvec_env *subenv = re->r.env;
1895
1896   r->tv = tv;
1897   init_comms(&r->rc);
1898   r->re = re; r->kid = -1;
1899   DCREATE(&r->prgwant); DCREATE(&r->progress);
1900   if (connect_remote(tv, r))
1901     tvec_skipgroup(tv, "failed to connect to test backend");
1902   reset_vars(r);
1903   if (subenv && subenv->ctxsz) r->subctx = xmalloc(subenv->ctxsz);
1904   else r->subctx = 0;
1905   if (subenv && subenv->setup) subenv->setup(tv, subenv, r, r->subctx);
1906 }
1907
1908 /* --- @tvec_remotefindvar@, @setvar_local@, @setvar_remote@ --- *
1909  *
1910  * Arguments:   @struct tvec_state *tv@ = test vector state
1911  *              @const char *var@ = variable name to set
1912  *              @const union tvec_regval *rv@ = register value
1913  *              @void **ctx_out@ = where to put the @setvar@ context
1914  *              @void *ctx@ = context pointer
1915  *
1916  * Returns:     @tvec_remotefindvar@ returns a pointer to the variable
1917  *              definition, or null; @remote_setvar@ returns zero on success
1918  *              or %$-1$% on error.
1919  *
1920  * Use:         Set a special variable.  The following special variables are
1921  *              supported.
1922  *
1923  *                * %|@exit|% is the expected exit status; see @TVXF_...@ and
1924  *                  @TVXST_...@.
1925  *
1926  *                * %|progress|% is the expected progress token when the test
1927  *                  completes.  On successful completion, this will be
1928  *                  %|%DONE|%; it's %|%RUN|% on entry to the test function,
1929  *                  but that can call @tvec_setprogress@ to change it.
1930  *
1931  *                * %|reconnect|% is a reconnection policy; see @TVRCN_...@.
1932  */
1933
1934 static int setvar_local(struct tvec_state *tv, const char *var,
1935                         const union tvec_regval *rv, void *ctx)
1936 {
1937   struct tvec_remotectx *r = ctx;
1938
1939   if (STRCMP(var, ==, "@exit")) {
1940     if (r->rc.f&TVRF_SETEXIT) return (tvec_dupreg(tv, var));
1941     r->exwant = rv->u; r->rc.f |= TVRF_SETEXIT; return (0);
1942   } else if (STRCMP(var, ==, "@progress")) {
1943     if (r->rc.f&TVRF_SETPRG) return (tvec_dupreg(tv, var));
1944     DRESET(&r->prgwant); DPUTM(&r->prgwant, rv->text.p, rv->text.sz);
1945     DPUTZ(&r->prgwant);
1946     r->rc.f |= TVRF_SETPRG; return (0);
1947   } else if (STRCMP(var, ==, "@reconnect")) {
1948     if (r->rc.f&TVRF_SETRCN) return (tvec_dupreg(tv, var));
1949     r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv->u&TVRF_RCNMASK) | TVRF_SETRCN;
1950     return (0);
1951   } else assert(!"unknown var");
1952 }
1953
1954 static int setvar_remote(struct tvec_state *tv, const char *var,
1955                          const union tvec_regval *rv, void *ctx)
1956 {
1957   struct tvec_remotectx *r = ctx;
1958   buf b;
1959   int ch, rc;
1960
1961   if (try_reconnect(tv, r) < 0) { rc = 0; goto end; }
1962
1963   QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_SETVAR) {
1964     dbuf_putstr16l(&r->rc.bout, var);
1965     r->vd.def.ty->tobuf(DBUF_BUF(&r->rc.bout), rv, &r->vd.def);
1966   } else { rc = -1; goto end; }
1967
1968   rc = handle_packets(tv, r, 0, TVPK_SETVAR | TVPF_ACK, &b);
1969     if (rc) goto end;
1970   ch = buf_getbyte(&b);
1971     if (ch < 0) { rc = malformed(tv, &r->rc); goto end; }
1972   if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
1973
1974   rc = ch ? -1 : 0;
1975 end:
1976   return (rc);
1977 }
1978
1979 const struct tvec_vardef *tvec_remotefindvar
1980   (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
1981 {
1982   struct tvec_remotectx *r = ctx;
1983   const struct tvec_remoteenv *re = r->re;
1984   const struct tvec_env *subenv = re->r.env;
1985   const struct tvec_vardef *vd; void *varctx;
1986
1987   if (STRCMP(var, ==, "@exit"))
1988     { *ctx_out = r; return (&exit_var); }
1989   else if (STRCMP(var, ==, "@progress"))
1990     { *ctx_out = r; return (&progress_var); }
1991   else if (STRCMP(var, ==, "@reconnect"))
1992     { *ctx_out = r; return (&reconn_var); }
1993   else if (subenv && subenv->findvar) {
1994     vd = subenv->findvar(tv, var, &varctx, r->subctx);
1995     if (!vd) return (0);
1996     r->vd.regsz = vd->regsz; r->vd.setvar = setvar_remote;
1997     r->vd.def = vd->def;
1998     *ctx_out = r; return (&r->vd);
1999   } else
2000     return (0);
2001 }
2002
2003 /* --- @tvec_remotebefore@ --- *
2004  *
2005  * Arguments:   @struct tvec_state *tv@ = test vector state
2006  *              @void *ctx@ = context pointer
2007  *
2008  * Returns:     ---
2009  *
2010  * Use:         Invoke the subordinate environment's @before@ function.
2011  */
2012
2013 void tvec_remotebefore(struct tvec_state *tv, void *ctx)
2014 {
2015   struct tvec_remotectx *r = ctx;
2016   const struct tvec_remoteenv *re = r->re;
2017   const struct tvec_env *subenv = re->r.env;
2018
2019   if (subenv && subenv->before) subenv->before(tv, r->subctx);
2020 }
2021
2022 /* --- @tvec_remoterun@ --- *
2023  *
2024  * Arguments:   @struct tvec_state *tv@ = test vector state
2025  *              @tvec_testfn *fn@ = test function to run
2026  *              @void *ctx@ = context pointer for the test function
2027  *
2028  * Returns:     ---
2029  *
2030  * Use:         Run a test on a remote server.
2031  */
2032
2033 void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
2034 {
2035   struct tvec_remotectx *r = ctx;
2036   union tvec_regval rv;
2037   unsigned f = 0;
2038 #define f_exit 1u
2039 #define f_progress 2u
2040 #define f_fail 4u
2041   buf b;
2042   int rc;
2043
2044   /* Reconnect to the server according to policy. */
2045   switch (try_reconnect(tv, r)) {
2046     case CONN_FAILED:
2047       tvec_skip(tv, "failed to connect to test backend"); return;
2048     case CONN_BROKEN:
2049       tvec_skip(tv, "no connection"); return;
2050   }
2051
2052   /* Set initial progress state. */
2053   DRESET(&r->progress); DPUTS(&r->progress, "%IDLE");
2054
2055   /* Send the command to the server and handle output. */
2056   QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST)
2057     tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout),
2058                    tv->test->regs, tv->nreg, tv->regsz);
2059   else { goto fail; }
2060   rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
2061
2062   /* Deal with the outcome. */
2063   switch (rc) {
2064
2065     case RECV_FAIL:
2066       /* Some kind of error.  Abandon ship. */
2067
2068     fail:
2069       tvec_skip(tv, "remote test runner communications failed");
2070       disconnect_remote(tv, r, 0);
2071       break;
2072
2073     case RECV_EOF:
2074       /* End-of-file at a packet boundary.  The server crashed trying to run
2075        * our test.  Collect the exit status and continue.
2076        */
2077       reap_kid(tv, r);
2078       /* fall through */
2079
2080     case RECV_OK:
2081       /* Successful completion (or EOF). */
2082
2083       /* Notice if the exit status isn't right. */
2084       if (r->exit != r->exwant) f |= f_exit;
2085
2086       /* Notice if the progress token isn't right. */
2087       if (r->progress.len != r->prgwant.len ||
2088           MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len))
2089         f |= f_progress;
2090
2091       /* If we found something wrong but the test is passing so far, then
2092        * report the failure and dump the input registers.
2093        */
2094       if (f && (tv->f&TVSF_ACTIVE))
2095         { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); }
2096
2097       /* If the test failed, then report the exit and progress states
2098        * relative to their expectations.
2099        */
2100       if (!(tv->f&TVSF_ACTIVE) &&
2101           (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) {
2102
2103         /* Note here that the test failed. */
2104         f |= f_fail;
2105
2106         /* Report exit status. */
2107         rv.u = r->exit;
2108         tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
2109                      &rv, &exit_var.def);
2110         if (f&f_exit) {
2111           rv.u = r->exwant;
2112           tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_var.def);
2113         }
2114
2115         /* Report progress token. */
2116         rv.text.p = r->progress.buf; rv.text.sz = r->progress.len;
2117         tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH,
2118                      &rv, &progress_var.def);
2119         if (f&f_progress) {
2120           rv.text.p = r->prgwant.buf; rv.text.sz = r->prgwant.len;
2121           tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_var.def);
2122         }
2123       }
2124
2125       /* If we received end-of-file, then close the connection.  Suppress
2126        * error output if the test passed: it was presumably expected.
2127        */
2128       if (rc == RECV_EOF)
2129         disconnect_remote(tv, r, f ? 0 : ERF_SILENT);
2130       break;
2131   }
2132
2133 #undef f_exit
2134 #undef f_progress
2135 #undef f_fail
2136 }
2137
2138 /* --- @tvec_remoteafter@ --- *
2139  *
2140  * Arguments:   @struct tvec_state *tv@ = test vector state
2141  *              @void *ctx@ = context pointer
2142  *
2143  * Returns:     ---
2144  *
2145  * Use:         Reset variables to their default values.
2146  */
2147
2148 void tvec_remoteafter(struct tvec_state *tv, void *ctx)
2149 {
2150   struct tvec_remotectx *r = ctx;
2151   const struct tvec_remoteenv *re = r->re;
2152   const struct tvec_env *subenv = re->r.env;
2153
2154   reset_vars(r);
2155   if (subenv && subenv->after) subenv->after(tv, r->subctx);
2156 }
2157
2158 /* --- @tvec_remoteteardown@ --- *
2159  *
2160  * Arguments:   @struct tvec_state *tv@ = test vector state
2161  *              @void *ctx@ = context pointer
2162  *
2163  * Returns:     ---
2164  *
2165  * Use:         Tear down the remote environment.
2166  */
2167
2168 void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
2169 {
2170   struct tvec_remotectx *r = ctx;
2171   const struct tvec_remoteenv *re = r->re;
2172   const struct tvec_env *subenv = re->r.env;
2173   buf b;
2174
2175   if (subenv && subenv->teardown) subenv->teardown(tv, r->subctx);
2176   xfree(r->subctx);
2177   if (r->rc.outfd >= 0) {
2178     QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP);
2179     if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b))
2180       if (BLEFT(&b)) malformed(tv, &r->rc);
2181   }
2182   disconnect_remote(tv, r, 0); release_comms(&r->rc);
2183   DDESTROY(&r->prgwant); DDESTROY(&r->progress);
2184 }
2185
2186 /*----- Connectors --------------------------------------------------------*/
2187
2188 /* --- @fork_common@ --- *
2189  *
2190  * Arguments:   @pid_t *kid_out@ = where to put child process-id
2191  *              @int *infd_out, *outfd_out, *errfd_out@ = where to put file
2192  *                      descriptors
2193  *              @struct tvec_state *tv@ = test vector state
2194  *
2195  * Returns:     Zero on success, %$-1$% on failure.
2196  *
2197  * Use:         Common @fork@ machinery for the connectors.  Create a
2198  *              subprocess.  On successful return, in the subprocess,
2199  *              @*kid_out@ is zero, and the error descriptor replaces the
2200  *              standard-error descriptor; in the parent, @*kid_out@ is the
2201  *              child process-id, and @*errfd_out@ is a descriptor on which
2202  *              the child's standard-error output can be read; in both
2203  *              @*infd_out@ and @*outfd_out@ are descriptors for input and
2204  *              output respectively -- they're opposite ends of pipes, but
2205  *              obviously they're crossed over so that the parent's output
2206  *              matches the child's input and %%\emph{vice versa}%%.
2207  */
2208
2209 static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
2210                        int *errfd_out, struct tvec_state *tv)
2211 {
2212   int p0[2] = { -1, -1 }, p1[2] = { -1, -1 }, pe[2] = { -1, -1 };
2213   pid_t kid = -1;
2214   int rc;
2215
2216   /* Try to create the pipes. */
2217   if (pipe(p0) || pipe(p1) || pipe(pe) ||
2218       fdflags(p0[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
2219       fdflags(p1[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
2220       fdflags(pe[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC)) {
2221     tvec_error(tv, "pipe failed: %s", strerror(errno));
2222     rc = -1; goto end;
2223   }
2224
2225   /* Flush all of the stream buffers so that we don't get duplicated
2226    * output.
2227    */
2228   fflush(0);
2229
2230   /* Try to set up the child process. */
2231   kid = fork();
2232   if (kid < 0) {
2233     tvec_error(tv, "fork failed: %s", strerror(errno));
2234     rc = -1; goto end;
2235   }
2236
2237   if (!kid) {
2238     /* Child process. */
2239
2240     *kid_out = 0;
2241     *infd_out = p0[0]; p0[0] = -1;
2242     *outfd_out = p1[1]; p1[1] = -1;
2243     if (pe[1] != STDERR_FILENO && dup2(pe[1], STDERR_FILENO) < 0) {
2244       fprintf(stderr, "failed to establish child stderr: %s",
2245               strerror(errno));
2246       _exit(127);
2247     }
2248   } else {
2249     /* Parent process. */
2250
2251     *kid_out = kid; kid = -1;
2252     *infd_out = p1[0]; p1[0] = -1;
2253     *outfd_out = p0[1]; p0[1] = -1;
2254     *errfd_out = pe[0]; pe[0] = -1;
2255   }
2256
2257   /* All done. */
2258   rc = 0;
2259
2260 end:
2261   /* Clean up.  So much of this... */
2262   if (p0[0] >= 0) close(p0[0]);
2263   if (p0[1] >= 0) close(p0[1]);
2264   if (p1[0] >= 0) close(p1[0]);
2265   if (p1[1] >= 0) close(p1[1]);
2266   if (pe[0] >= 0) close(pe[0]);
2267   if (pe[1] >= 0) close(pe[1]);
2268   return (rc);
2269 }
2270
2271 /* --- @tvec_fork@ --- *
2272  *
2273  * Arguments:   @pid_t *kid_out@ = where to put child process-id
2274  *              @int *infd_out, *outfd_out, *errfd_out@ = where to put file
2275  *                      descriptors
2276  *              @struct tvec_state *tv@ = test vector state
2277  *              @const struct tvec_remoteenv@ = the remote environment
2278  *
2279  * Returns:     Zero on success, %$-1$% on failure.
2280  *
2281  * Use:         Starts a remote server running in a fork of the main
2282  *              process.  This is useful for testing functions which might --
2283  *              or are even intended to -- crash.
2284  */
2285
2286 int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
2287               struct tvec_state *tv, const struct tvec_remoteenv *env)
2288 {
2289   struct tvec_config config;
2290   const struct tvec_remotefork *rf = (const struct tvec_remotefork *)env;
2291   pid_t kid = -1;
2292   int infd = -1, outfd = -1, errfd = -1;
2293   int rc;
2294
2295   if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
2296   if (!kid) {
2297     if (tv->fp) fclose(tv->fp);
2298     config.tests = rf->f.tests ? rf->f.tests : tv->tests;
2299     config.nrout = tv->nrout; config.nreg = tv->nreg;
2300     config.regsz = tv->regsz;
2301     _exit(tvec_remoteserver(infd, outfd, &config));
2302   }
2303
2304   *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
2305   rc = 0;
2306 end:
2307   return (rc);
2308 }
2309
2310 /* --- @tvec_exec@ --- *
2311  *
2312  * Arguments:   @pid_t *kid_out@ = where to put child process-id
2313  *              @int *infd_out, *outfd_out, *errfd_out@ = where to put file
2314  *                      descriptors
2315  *              @struct tvec_state *tv@ = test vector state
2316  *              @const struct tvec_remoteenv@ = the remote environment
2317  *
2318  * Returns:     Zero on success, %$-1$% on failure.
2319  *
2320  * Use:         Starts a remote server by running some program.  The command
2321  *              given in the environment description will probably some hairy
2322  *              shell rune allowing for configuration via files or
2323  *              environment variables.
2324  */
2325
2326 int tvec_exec(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
2327               struct tvec_state *tv, const struct tvec_remoteenv *env)
2328 {
2329   const struct tvec_remoteexec *rx = (const struct tvec_remoteexec *)env;
2330   pid_t kid = -1;
2331   int infd = -1, outfd = -1, errfd = -1;
2332   mdup_fd v[2];
2333   int rc;
2334
2335   if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
2336   if (!kid) {
2337     v[0].cur = infd; v[0].want = STDIN_FILENO;
2338     v[1].cur = outfd; v[1].want = STDOUT_FILENO;
2339     if (mdup(v, 2)) {
2340       fprintf(stderr, "failed to establish standard file descriptors: %s",
2341               strerror(errno));
2342       exit(127);
2343     }
2344     execvp(rx->x.args[0], (/*uncosnt*/ char *const *)rx->x.args);
2345     fprintf(stderr, "failed to invoke test runner: %s", strerror(errno));
2346     exit(127);
2347   }
2348
2349   *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
2350   rc = 0;
2351 end:
2352   return (rc);
2353 }
2354
2355 /*----- That's all, folks -------------------------------------------------*/