"Fossies" - the Fresh Open Source Software Archive

Member "dateutils-0.4.6/test/clitosis.c" (19 Mar 2019, 38757 Bytes) of package /linux/privat/dateutils-0.4.6.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "clitosis.c": 0.4.5_vs_0.4.6.

    1 /*** clitosis.c -- command-line-interface tester on shell input syntax
    2  *
    3  * Copyright (C) 2013-2019 Sebastian Freundt
    4  *
    5  * Author:  Sebastian Freundt <freundt@ga-group.nl>
    6  *
    7  * This file is part of clitosis.
    8  *
    9  * Redistribution and use in source and binary forms, with or without
   10  * modification, are permitted provided that the following conditions
   11  * are met:
   12  *
   13  * 1. Redistributions of source code must retain the above copyright
   14  *    notice, this list of conditions and the following disclaimer.
   15  *
   16  * 2. Redistributions in binary form must reproduce the above copyright
   17  *    notice, this list of conditions and the following disclaimer in the
   18  *    documentation and/or other materials provided with the distribution.
   19  *
   20  * 3. Neither the name of the author nor the names of any contributors
   21  *    may be used to endorse or promote products derived from this
   22  *    software without specific prior written permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
   25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   27  * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
   31  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
   33  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
   34  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   35  *
   36  ***/
   37 #if defined HAVE_CONFIG_H
   38 # include "config.h"
   39 #endif  /* HAVE_CONFIG_H */
   40 #include <unistd.h>
   41 #include <stdarg.h>
   42 #include <stdlib.h>
   43 #include <stddef.h>
   44 #include <stdint.h>
   45 #include <stdio.h>
   46 #include <stdbool.h>
   47 #include <limits.h>
   48 #include <fcntl.h>
   49 #include <signal.h>
   50 #include <sys/mman.h>
   51 #include <sys/stat.h>
   52 #include <sys/wait.h>
   53 #include <sys/types.h>
   54 #include <sys/socket.h>
   55 #include <sys/time.h>
   56 #include <string.h>
   57 #include <ctype.h>
   58 #include <errno.h>
   59 #include <assert.h>
   60 #if defined HAVE_PTY_H
   61 # include <pty.h>
   62 #endif  /* HAVE_PTY_H */
   63 /* check for me */
   64 #include <wordexp.h>
   65 
   66 #if !defined LIKELY
   67 # define LIKELY(_x) __builtin_expect((_x), 1)
   68 #endif  /* !LIKELY */
   69 #if !defined UNLIKELY
   70 # define UNLIKELY(_x)   __builtin_expect((_x), 0)
   71 #endif  /* UNLIKELY */
   72 
   73 #if !defined countof
   74 # define countof(x) (sizeof(x) / sizeof(*x))
   75 #endif  /* !countof */
   76 
   77 #if !defined strlenof
   78 # define strlenof(x)    (sizeof(x) - 1U)
   79 #endif  /* !strlenof */
   80 
   81 #if !defined with
   82 # define with(args...)  for (args, *__ep__ = (void*)1; __ep__; __ep__ = 0)
   83 #endif  /* !with */
   84 
   85 #if !defined PATH_MAX
   86 # define PATH_MAX   256U
   87 #endif  /* !PATH_MAX */
   88 
   89 #if defined HAVE_SPLICE && !defined SPLICE_F_MOVE && !defined _AIX
   90 /* just so we don't have to use _GNU_SOURCE declare prototype of splice() */
   91 # if defined __INTEL_COMPILER
   92 #  pragma warning(disable:1419)
   93 # endif /* __INTEL_COMPILER */
   94 extern ssize_t splice(int, loff_t*, int, loff_t*, size_t, unsigned int);
   95 # define SPLICE_F_MOVE  (0U)
   96 # if defined __INTEL_COMPILER
   97 #  pragma warning(default:1419)
   98 # endif /* __INTEL_COMPILER */
   99 #elif !defined SPLICE_F_MOVE
  100 # define SPLICE_F_MOVE  (0)
  101 #endif  /* !SPLICE_F_MOVE */
  102 
  103 typedef struct clitf_s clitf_t;
  104 typedef struct clit_buf_s clit_buf_t;
  105 typedef struct clit_bit_s clit_bit_t;
  106 typedef struct clit_tst_s *clit_tst_t;
  107 
  108 struct clitf_s {
  109     size_t z;
  110     void *d;
  111 };
  112 
  113 struct clit_buf_s {
  114     size_t z;
  115     const char *d;
  116 };
  117 
  118 /**
  119  * A clit bit can be an ordinary memory buffer (z > 0 && d),
  120  * a file descriptor (fd != 0 && d == NULL), or a file name (z == -1UL && fn) */
  121 struct clit_bit_s {
  122     size_t z;
  123     const char *d;
  124 };
  125 
  126 struct clit_opt_s {
  127     unsigned int timeo;
  128 
  129     unsigned int verbosep:1;
  130     unsigned int ptyp:1;
  131     unsigned int keep_going_p:1;
  132     unsigned int shcmdp:1;
  133     /* just some padding to start afresh on an 8-bit boundary */
  134     unsigned int:4;
  135     unsigned int xcod:8;
  136 
  137     /* use this instead of /bin/sh */
  138     char *shcmd;
  139 };
  140 
  141 struct clit_chld_s {
  142     int pin;
  143     int pou;
  144     int per;
  145 
  146     pid_t chld;
  147     pid_t diff;
  148     pid_t feed;
  149 
  150     /* options to control behaviour */
  151     struct clit_opt_s options;
  152 
  153     /* cmd'ified version of options.shell */
  154     char **huskv;
  155 };
  156 
  157 /* a test is the command (including stdin), stdout result, and stderr result */
  158 struct clit_tst_s {
  159     clit_bit_t cmd;
  160     clit_bit_t out;
  161     clit_bit_t err;
  162     clit_bit_t rest;
  163 
  164     /* specific per-test flags */
  165     /** don't fail when the actual output differs from th expected output */
  166     unsigned int ign_out:1;
  167     /** don't fail when the command returns non-SUCCESS */
  168     unsigned int ign_ret:1;
  169 
  170     /** don't pass the output on to external differ */
  171     unsigned int supp_diff:1;
  172     /** expand the proto-output as though it was a shell here-document */
  173     unsigned int xpnd_proto:1;
  174 
  175     /* padding */
  176     unsigned int:5;
  177 
  178     /** expect this return code, or any but 0 if all bits are set */
  179     unsigned int exp_ret:8;
  180 };
  181 
  182 typedef enum {
  183     CLIT_END_UNKNOWN,
  184     CLIT_END_BIG,
  185     CLIT_END_LITTLE,
  186     CLIT_END_MIDDLE,
  187 } clit_end_t;
  188 
  189 static sigset_t fatal_signal_set[1];
  190 static sigset_t empty_signal_set[1];
  191 
  192 static const char dflt_diff[] = "diff";
  193 static const char *cmd_diff = dflt_diff;
  194 
  195 
  196 static void
  197 __attribute__((format(printf, 1, 2)))
  198 error(const char *fmt, ...)
  199 {
  200     va_list vap;
  201     va_start(vap, fmt);
  202     vfprintf(stderr, fmt, vap);
  203     va_end(vap);
  204     if (errno) {
  205         fputs(": ", stderr);
  206         fputs(strerror(errno), stderr);
  207     }
  208     fputc('\n', stderr);
  209     return;
  210 }
  211 
  212 static inline __attribute__((const, always_inline)) char*
  213 deconst(const char *s)
  214 {
  215     union {
  216         const char *c;
  217         char *p;
  218     } x = {s};
  219     return x.p;
  220 }
  221 
  222 static clit_end_t
  223 get_endianness(void)
  224 {
  225     static const unsigned int u = 0x01234567U;
  226     unsigned char p[sizeof(u)];
  227 
  228     memcpy(p, &u, sizeof(u));
  229 
  230     switch (*p) {
  231     case 0x01U:
  232         return CLIT_END_BIG;
  233     case 0x67U:
  234         return CLIT_END_LITTLE;
  235     case 0x23U:
  236         if (p[1U] == 0x01U && p[2U] == 0x67U && p[3U] == 0x45U) {
  237             return CLIT_END_MIDDLE;
  238         }
  239         /*@fallthrough@*/
  240     default:
  241         break;
  242         }
  243     return CLIT_END_UNKNOWN;
  244 }
  245 
  246 
  247 /* imported code */
  248 /*** fast_strstr.c
  249  *
  250  * This algorithm is licensed under the open-source BSD3 license
  251  *
  252  * Copyright (c) 2014, Raphael Javaux
  253  * All rights reserved.
  254  *
  255  * Licence text, see above.
  256  *
  257  * The code has been modified to mimic memmem().
  258  *
  259  **/
  260 /**
  261 * Finds the first occurrence of the sub-string needle in the string haystack.
  262 * Returns NULL if needle was not found.
  263 */
  264 static char*
  265 xmemmem(const char *hay, const size_t hayz, const char *ndl, const size_t ndlz)
  266 {
  267     const char *const eoh = hay + hayz;
  268     const char *const eon = ndl + ndlz;
  269     const char *hp;
  270     const char *np;
  271     const char *cand;
  272     unsigned int hsum;
  273     unsigned int nsum;
  274     unsigned int eqp;
  275 
  276     /* trivial checks first
  277          * a 0-sized needle is defined to be found anywhere in haystack
  278          * then run strchr() to find a candidate in HAYSTACK (i.e. a portion
  279          * that happens to begin with *NEEDLE) */
  280     if (ndlz == 0UL) {
  281         return deconst(hay);
  282     } else if ((hay = memchr(hay, *ndl, hayz)) == NULL) {
  283         /* trivial */
  284         return NULL;
  285     }
  286 
  287     /* First characters of haystack and needle are the same now. Both are
  288      * guaranteed to be at least one character long.  Now computes the sum
  289      * of characters values of needle together with the sum of the first
  290      * needle_len characters of haystack. */
  291     for (hp = hay + 1U, np = ndl + 1U, hsum = *hay, nsum = *hay, eqp = 1U;
  292          hp < eoh && np < eon;
  293          hsum ^= *hp, nsum ^= *np, eqp &= *hp == *np, hp++, np++);
  294 
  295     /* HP now references the (NZ + 1)-th character. */
  296     if (np < eon) {
  297         /* haystack is smaller than needle, :O */
  298         return NULL;
  299     } else if (eqp) {
  300         /* found a match */
  301         return deconst(hay);
  302     }
  303 
  304     /* now loop through the rest of haystack,
  305      * updating the sum iteratively */
  306     for (cand = hay; hp < eoh; hp++) {
  307         hsum ^= *cand++;
  308         hsum ^= *hp;
  309 
  310         /* Since the sum of the characters is already known to be
  311          * equal at that point, it is enough to check just NZ - 1
  312          * characters for equality,
  313          * also CAND is by design < HP, so no need for range checks */
  314         if (hsum == nsum && memcmp(cand, ndl, ndlz - 1U) == 0) {
  315             return deconst(cand);
  316         }
  317     }
  318     return NULL;
  319 }
  320 
  321 static char*
  322 xstrndup(const char *s, size_t z)
  323 {
  324     char *res;
  325     if ((res = malloc(z + 1U))) {
  326         memcpy(res, s, z);
  327         res[z] = '\0';
  328     }
  329     return res;
  330 }
  331 
  332 static char**
  333 cmdify(char *restrict cmd)
  334 {
  335     /* prep for about 16 params */
  336     char **v = calloc(16U, sizeof(*v));
  337 
  338     if (LIKELY(v != NULL)) {
  339         const char *ifs = getenv("IFS") ?: " \t\n";
  340         size_t i = 0U;
  341 
  342         v[0U] = strtok(cmd, ifs);
  343         do {
  344             if (UNLIKELY((i % 16U) == 15U)) {
  345                 void *nuv;
  346 
  347                 nuv = realloc(v, (i + 1U + 16U) * sizeof(*v));
  348                 if (UNLIKELY(nuv == NULL)) {
  349                     free(v);
  350                     return NULL;
  351                 }
  352                 v = nuv;
  353             }
  354         } while ((v[++i] = strtok(NULL, ifs)) != NULL);
  355     }
  356     return v;
  357 }
  358 
  359 /* takes ideas from Gregory Pakosz's whereami */
  360 static char*
  361 get_argv0dir(const char *argv0)
  362 {
  363 /* return current executable */
  364     char *res;
  365 
  366     if (0) {
  367 #if 0
  368 
  369 #elif defined __linux__
  370 /* don't rely on argv0 at all */
  371     } else if (1) {
  372         if ((res = realpath("/proc/self/exe", NULL)) == NULL) {
  373             /* we've got a plan B */
  374             goto planb;
  375         }
  376 #elif defined __APPLE__ && defined __MACH__
  377     } else if (1) {
  378         char buf[PATH_MAX];
  379         uint32_t bsz = strlenof(buf);
  380 
  381         buf[bsz] = '\0';
  382         if (_NSGetExecutablePath(buf, &bsz) < 0) {
  383             /* plan B again */
  384             goto planb;
  385         }
  386         /* strdup BUF quickly */
  387         res = strdup(buf);
  388 #elif defined __NetBSD__
  389     } else if (1) {
  390         static const char myself[] = "/proc/curproc/exe";
  391         char buf[PATH_MAX];
  392         ssize_t z;
  393 
  394         if (UNLIKELY((z = readlink(myself, buf, bsz)) < 0)) {
  395             /* plan B */
  396             goto planb;
  397         }
  398         /* strndup him */
  399         res = xstrndup(buf, z);
  400 #elif defined __DragonFly__
  401     } else if (1) {
  402         static const char myself[] = "/proc/curproc/file";
  403         char buf[PATH_MAX];
  404         ssize_t z;
  405 
  406         if (UNLIKELY((z = readlink(myself, buf, bsz)) < 0)) {
  407             /* blimey, proceed with plan B */
  408             goto planb;
  409         }
  410         /* strndup him */
  411         res = xstrndup(buf, z);
  412 #elif defined __FreeBSD__
  413     } else if (1) {
  414         int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
  415         char buf[PATH_MAX];
  416         size_t z = strlenof(buf);
  417 
  418         /* make sure that \0 terminator fits */
  419         buf[z] = '\0';
  420         if (UNLIKELY(sysctl(mib, countof(mib), buf, &z, NULL, 0) < 0)) {
  421             /* no luck today */
  422             goto planb;
  423         }
  424         /* make the result our own */
  425         res = strdup(buf);
  426 #elif defined __sun || defined sun
  427     } else if (1) {
  428         char buf[PATH_MAX];
  429         ssize_t z;
  430 
  431         snprintf(buf, sizeof(buf), "/proc/%d/path/a.out", getpid());
  432         if (UNLIKELY((z = readlink(buf, buf, sizeof(buf))) < 0)) {
  433             /* nope, plan A failed */
  434             goto planb;
  435         }
  436         res = xstrndup(buf, z);
  437 #endif  /* OS */
  438     } else {
  439         size_t argz0;
  440 
  441     planb:
  442         /* backup plan, massage argv0 */
  443         if (argv0 == NULL) {
  444             return NULL;
  445         }
  446         /* otherwise copy ARGV0, or rather the first PATH_MAX chars */
  447         for (argz0 = 0U; argz0 < PATH_MAX && argv0[argz0]; argz0++);
  448         res = xstrndup(argv0, argz0);
  449     }
  450 
  451     /* path extraction aka dirname'ing, absolute or otherwise */
  452     if (res == NULL) {
  453         return NULL;
  454     }
  455     with (char *dir0 = strrchr(res, '/')) {
  456         if (dir0 == NULL) {
  457             free(res);
  458             return NULL;
  459         }
  460         *dir0 = '\0';
  461     }
  462     return res;
  463 }
  464 
  465 static void
  466 free_argv0dir(char *a0)
  467 {
  468     free(a0);
  469     return;
  470 }
  471 
  472 
  473 /* clit bit handling */
  474 #define CLIT_BIT_FD(x)  (clit_bit_fd_p(x) ? (int)(x).z : -1)
  475 
  476 static inline __attribute__((const)) bool
  477 clit_bit_buf_p(clit_bit_t x)
  478 {
  479     return x.z != -1UL && x.d != NULL;
  480 }
  481 
  482 static inline __attribute__((const)) bool
  483 clit_bit_fd_p(clit_bit_t x)
  484 {
  485     return x.d == NULL;
  486 }
  487 
  488 static inline __attribute__((const)) bool
  489 clit_bit_fn_p(clit_bit_t x)
  490 {
  491     return x.z == -1UL && x.d != NULL;
  492 }
  493 
  494 /* ctors */
  495 static inline __attribute__((unused)) clit_bit_t
  496 clit_make_fd(int fd)
  497 {
  498     return (clit_bit_t){.z = fd};
  499 }
  500 
  501 static inline clit_bit_t
  502 clit_make_fn(const char *fn)
  503 {
  504     return (clit_bit_t){.z = -1UL, .d = fn};
  505 }
  506 
  507 static const char*
  508 bufexp(const char src[static 1], size_t ssz)
  509 {
  510     static char *buf;
  511     static size_t bsz;
  512     wordexp_t xp[1];
  513 
  514     if (UNLIKELY(ssz == 0)) {
  515         return NULL;
  516     }
  517 
  518 #define CHKBSZ(x)                          \
  519     if ((x) >= bsz) {                      \
  520         bsz = ((x) / 256U + 1U) * 256U;            \
  521         if (UNLIKELY((buf = realloc(buf, bsz)) == NULL)) { \
  522             /* well we'll leak XP here */          \
  523             return NULL;                   \
  524         }                          \
  525     }
  526 
  527     /* get our own copy for deep vein massages */
  528     CHKBSZ(ssz);
  529     memcpy(buf, src, ssz);
  530     buf[ssz] = '\0';
  531 
  532     switch (wordexp(buf, xp, WRDE_UNDEF)) {
  533     case 0:
  534         if (xp->we_wordc > 0) {
  535             /* everything's fine */
  536             break;
  537         }
  538     case WRDE_NOSPACE:
  539         wordfree(xp);
  540     default:
  541         return NULL;
  542     }
  543 
  544     /* copy the first `argument', back into BUF,
  545      * which is hopefully big enough */
  546     with (size_t wz = strlen(xp->we_wordv[0])) {
  547         CHKBSZ(wz);
  548         memcpy(buf, xp->we_wordv[0], wz);
  549         buf[wz] = '\0';
  550     }
  551 
  552     wordfree(xp);
  553     return buf;
  554 }
  555 
  556 
  557 static clitf_t
  558 mmap_fd(int fd, size_t fz)
  559 {
  560     void *p;
  561 
  562     if ((p = mmap(NULL, fz, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
  563         return (clitf_t){.z = 0U, .d = NULL};
  564     }
  565     return (clitf_t){.z = fz, .d = p};
  566 }
  567 
  568 static int
  569 munmap_fd(clitf_t map)
  570 {
  571     return munmap(map.d, map.z);
  572 }
  573 
  574 static void
  575 block_sigs(void)
  576 {
  577     (void)sigprocmask(SIG_BLOCK, fatal_signal_set, (sigset_t*)NULL);
  578     return;
  579 }
  580 
  581 static void
  582 unblock_sigs(void)
  583 {
  584     sigprocmask(SIG_SETMASK, empty_signal_set, (sigset_t*)NULL);
  585     return;
  586 }
  587 
  588 static void
  589 unblock_sig(int sig)
  590 {
  591     static sigset_t unblk_set[1];
  592 
  593     sigemptyset(unblk_set);
  594     sigaddset(unblk_set, sig);
  595     (void)sigprocmask(SIG_UNBLOCK, unblk_set, (sigset_t*)NULL);
  596     return;
  597 }
  598 
  599 #if defined HAVE_PTY_H
  600 static pid_t
  601 pfork(int *pty)
  602 {
  603     if (UNLIKELY(pty == NULL)) {
  604         errno = ENOMEM;
  605         return -1;
  606     }
  607     return forkpty(pty, NULL, NULL, NULL);
  608 }
  609 #else  /* !HAVE_PTY_H */
  610 static pid_t
  611 pfork(int *pty)
  612 {
  613     fputs("pseudo-tty not supported\n", stderr);
  614     return *pty = -1;
  615 }
  616 #endif  /* HAVE_PTY_H */
  617 
  618 static inline int
  619 xsplice(int tgtfd, int srcfd, unsigned int flags)
  620 {
  621 #if defined HAVE_SPLICE && defined __linux__
  622     for (ssize_t nsp;
  623          (nsp = splice(
  624               srcfd, NULL, tgtfd, NULL,
  625               4096U, flags)) == 4096U;);
  626 #else  /* !HAVE_SPLICE || !__linux__ */
  627 /* in particular, AIX's splice works on tcp sockets only */
  628     with (char *buf[16U * 4096U]) {
  629         ssize_t nrd;
  630 
  631         while ((nrd = read(srcfd, buf, sizeof(buf))) > 0) {
  632             for (ssize_t nwr, totw = 0;
  633                  totw < nrd &&
  634                      (nwr = write(
  635                           tgtfd,
  636                           buf + totw, nrd - totw)) >= 0;
  637                  totw += nwr);
  638         }
  639     }
  640 #endif  /* HAVE_SPLICE && __linux__ */
  641     return 0;
  642 }
  643 
  644 
  645 static const char *
  646 find_shtok(const char *bp, size_t bz)
  647 {
  648 /* finds a (lone) occurrence of $ at the beginning of a line */
  649     for (const char *res;
  650          (res = memchr(bp, '$', bz)) != NULL;
  651          bz -= (res + 1 - bp), bp = res + 1) {
  652         /* we're actually after a "\n$" or
  653          * a "$" at the beginning of the buffer pointer (bp)
  654          * now check that either the buffer ends there or
  655          * the $ is followed by a newline, or the $ is followed
  656          * by a space, which is the line-to-exec indicator */
  657         if ((res == bp || res[-1] == '\n') &&
  658             (bz <= 1U || (res[1] == '\n' || res[1] == ' '))) {
  659             return res;
  660         }
  661     }
  662     return NULL;
  663 }
  664 
  665 static clit_bit_t
  666 find_cmd(const char *bp, size_t bz)
  667 {
  668     clit_bit_t resbit = {0U};
  669     clit_bit_t tok;
  670 
  671     /* find the bit where it says '$ ' */
  672     with (const char *res) {
  673         if (UNLIKELY((res = find_shtok(bp, bz)) == NULL)) {
  674             return (clit_bit_t){0U};
  675         } else if (UNLIKELY(res[1] != ' ')) {
  676             return (clit_bit_t){0U};
  677         }
  678         /* otherwise */
  679         resbit.d = res += 2U;
  680         bz -= res - bp;
  681         bp = res;
  682     }
  683 
  684     /* find the new line bit */
  685     for (const char *res;
  686          (res = memchr(bp, '\n', bz)) != NULL;
  687          bz -= (res + 1U - bp), bp = res + 1U) {
  688         size_t lz = (res + 1U - bp);
  689 
  690         /* check for trailing \ or <<EOF (in that line) */
  691         if (UNLIKELY((tok.d = xmemmem(bp, lz, "<<", 2U)) != NULL)) {
  692             tok.d += 2U;
  693             tok.z = res - tok.d;
  694             /* analyse this eof token */
  695             bp = res + 1U;
  696             goto here_doc;
  697         } else if (res == bp || res[-1] != '\\') {
  698             resbit.z = res + 1 - resbit.d;
  699             break;
  700         }
  701     }
  702     return resbit;
  703 
  704 here_doc:
  705     /* massage tok so that it starts on a non-space and ends on one */
  706     for (; tok.z && (*tok.d == ' ' || *tok.d == '\t'); tok.d++, tok.z--);
  707     for (;
  708          tok.z && (tok.d[tok.z - 1] == ' ' || tok.d[tok.z - 1] == '\t');
  709          tok.z--);
  710     if (tok.z &&
  711         (*tok.d == '\'' || *tok.d == '"') && tok.d[tok.z - 1] == *tok.d) {
  712         tok.d++;
  713         tok.z -= 2U;
  714     }
  715     /* now find the opposite EOF token */
  716     for (const char *eotok;
  717          (eotok = xmemmem(bp, bz, tok.d, tok.z)) != NULL;
  718          bz -= eotok + 1U - bp, bp = eotok + 1U) {
  719         if (LIKELY(eotok[-1] == '\n' && eotok[tok.z] == '\n')) {
  720             resbit.z = eotok + tok.z + 1U - resbit.d;
  721             break;
  722         }
  723     }
  724     return resbit;
  725 }
  726 
  727 static int
  728 find_ignore(struct clit_tst_s tst[static 1])
  729 {
  730     with (const char *cmd = tst->cmd.d, *const ec = cmd + tst->cmd.z) {
  731         static char tok_ign[] = "ignore";
  732         static char tok_out[] = "output";
  733         static char tok_ret[] = "return";
  734 
  735         if (strncmp(cmd, tok_ign, strlenof(tok_ign))) {
  736             /* don't bother */
  737             break;
  738         }
  739         /* fast-forward a little */
  740         cmd += strlenof(tok_ign);
  741 
  742         if (isspace(*cmd)) {
  743             /* it's our famous ignore token it seems */
  744             tst->ign_out = tst->ign_ret = 1U;
  745         } else if (*cmd++ != '-') {
  746             /* unknown token then */
  747             break;
  748         } else if (!strncmp(cmd, tok_out, strlenof(tok_out))) {
  749             /* ignore-output it is */
  750             tst->ign_out = 1U;
  751             cmd += strlenof(tok_out);
  752         } else if (!strncmp(cmd, tok_ret, strlenof(tok_ret))) {
  753             /* ignore-return it is */
  754             tst->ign_ret = 1U;
  755             cmd += strlenof(tok_ret);
  756         } else {
  757             /* don't know what's going on */
  758             break;
  759         }
  760 
  761         /* now, fast-forward to the actual command, and reass */
  762         while (++cmd < ec && isspace(*cmd));
  763         tst->cmd.z -= (cmd - tst->cmd.d);
  764         tst->cmd.d = cmd;
  765         return 0;
  766     }
  767     return -1;
  768 }
  769 
  770 static int
  771 find_negexp(struct clit_tst_s tst[static 1])
  772 {
  773     with (const char *cmd = tst->cmd.d, *const ec = cmd + tst->cmd.z) {
  774         unsigned int exp = 0U;
  775 
  776         switch (*cmd) {
  777         case '!'/*NEG*/:
  778             exp = 255U;
  779             break;
  780         case '?'/*EXP*/:;
  781             char *p;
  782             exp = strtoul(cmd + 1U, &p, 10);
  783             cmd = p;
  784             if (isspace(*cmd)) {
  785                 break;
  786             }
  787         default:
  788             tst->exp_ret = 0U;
  789             return -1;
  790         }
  791 
  792         /* now, fast-forward to the actual command, and reass */
  793         while (++cmd < ec && isspace(*cmd));
  794         tst->cmd.z -= (cmd - tst->cmd.d);
  795         tst->cmd.d = cmd;
  796         tst->exp_ret = exp;
  797     }
  798     return 0;
  799 }
  800 
  801 static int
  802 find_suppdiff(struct clit_tst_s tst[static 1])
  803 {
  804     with (const char *cmd = tst->cmd.d, *const ec = cmd + tst->cmd.z) {
  805         switch (*cmd) {
  806         case '@':
  807             break;
  808         default:
  809             return -1;
  810         }
  811 
  812         /* now, fast-forward to the actual command, and reass */
  813         while (++cmd < ec && isspace(*cmd));
  814         tst->cmd.z -= (cmd - tst->cmd.d);
  815         tst->cmd.d = cmd;
  816         tst->supp_diff = 1U;
  817         tst->ign_out = 1U;
  818     }
  819     return 0;
  820 }
  821 
  822 static int
  823 find_xpnd_proto(struct clit_tst_s tst[static 1])
  824 {
  825     with (const char *cmd = tst->cmd.d, *const ec = cmd + tst->cmd.z) {
  826         switch (*cmd) {
  827         case '$':
  828             break;
  829         default:
  830             return -1;
  831         }
  832 
  833         /* now, fast-forward to the actual command, and reass */
  834         while (++cmd < ec && isspace(*cmd));
  835         tst->cmd.z -= (cmd - tst->cmd.d);
  836         tst->cmd.d = cmd;
  837         tst->xpnd_proto = 1U;
  838     }
  839     return 0;
  840 }
  841 
  842 static int
  843 find_tst(struct clit_tst_s tst[static 1], const char *bp, size_t bz)
  844 {
  845     if (UNLIKELY(!(tst->cmd = find_cmd(bp, bz)).z)) {
  846         goto fail;
  847     }
  848     /* reset bp and bz */
  849     bz = bz - (tst->cmd.d + tst->cmd.z - bp);
  850     bp = tst->cmd.d + tst->cmd.z;
  851     if (UNLIKELY((tst->rest.d = find_shtok(bp, bz)) == NULL)) {
  852         goto fail;
  853     }
  854     /* otherwise set the rest bit already */
  855     tst->rest.z = bz - (tst->rest.d - bp);
  856 
  857     /* now the stdout bit must be in between (or 0) */
  858     with (size_t outz = tst->rest.d - bp) {
  859         if (outz &&
  860             /* prefixed '< '? */
  861             UNLIKELY(bp[0] == '<' && bp[1] == ' ')) {
  862             /* it's a < FILE comparison */
  863             const char *fn;
  864 
  865             if ((fn = bufexp(bp + 2, outz - 2U - 1U)) != NULL) {
  866                 tst->out = clit_make_fn(fn);
  867             } else {
  868                 error("expansion failed");
  869                 goto fail;
  870             }
  871         } else {
  872             tst->out = (clit_bit_t){.z = outz, bp};
  873         }
  874     }
  875 
  876     /* oh let's see if we should ignore things */
  877     (void)find_ignore(tst);
  878     /* check for suppress diff */
  879     (void)find_suppdiff(tst);
  880     /* check for expect and negate operators */
  881     (void)find_negexp(tst);
  882     /* check for proto-output expander */
  883     (void)find_xpnd_proto(tst);
  884 
  885     tst->err = (clit_bit_t){0U};
  886     return 0;
  887 fail:
  888     memset(tst, 0, sizeof(*tst));
  889     return -1;
  890 }
  891 
  892 static struct clit_opt_s
  893 find_opt(struct clit_opt_s options, const char *bp, size_t bz)
  894 {
  895     static const char magic[] = "setopt ";
  896 
  897     for (const char *mp;
  898          (mp = xmemmem(bp, bz, magic, strlenof(magic))) != NULL;
  899          bz -= (mp + 1U) - bp, bp = mp + 1U) {
  900         unsigned int opt;
  901 
  902         /* check if it's setopt or unsetopt */
  903         if (mp == bp || LIKELY(mp[-1] == '\n')) {
  904             /* yay, it's a genuine setopt */
  905             opt = 1U;
  906         } else if (mp >= bp + 2U && mp[-2] == 'u' && mp[-1] == 'n' &&
  907                (mp == bp + 2U || mp > bp + 2U && mp[-3] == '\n')) {
  908             /* it's a genuine unsetopt */
  909             opt = 0U;
  910         } else {
  911             /* found rubbish then */
  912             mp += strlenof(magic);
  913             continue;
  914         }
  915 #define CMP(x, lit) (strncmp((x), (lit), strlenof(lit)))
  916         /* parse the option value */
  917         mp += strlenof(magic);
  918         if (NULL) {
  919             /* not reached */
  920             ;
  921         } else if (CMP(mp, "verbose\n") == 0) {
  922             options.verbosep = opt;
  923         } else if (CMP(mp, "pseudo-tty\n") == 0) {
  924             options.ptyp = opt;
  925         } else if (CMP(mp, "timeout") == 0) {
  926             const char *arg = mp + sizeof("timeout");
  927             char *p;
  928             long unsigned int timeo;
  929 
  930             if ((timeo = strtoul(arg, &p, 0), *p == '\n')) {
  931                 options.timeo = (unsigned int)timeo;
  932             }
  933         } else if (CMP(mp, "keep-going\n") == 0) {
  934             options.keep_going_p = opt;
  935         } else if (CMP(mp, "shell") == 0) {
  936             const char *arg = mp + sizeof("shell");
  937             char *eol = memchr(arg, '\n', bz - (arg - mp));
  938 
  939             if (UNLIKELY(eol == NULL)) {
  940                 /* ignore and get on with it */
  941                 continue;
  942             }
  943             options.shcmd = xstrndup(arg, eol - arg);
  944             options.shcmdp = 1U;
  945         } else if (CMP(mp, "exit-code") == 0) {
  946             const char *arg = mp + sizeof("exit-code");
  947             char *p;
  948             long unsigned int xc;
  949 
  950             if ((xc = strtoul(arg, &p, 0), *p == '\n')) {
  951                 options.xcod = (unsigned int)xc;
  952             }
  953         }
  954 #undef CMP
  955     }
  956     return options;
  957 }
  958 
  959 static int
  960 init_chld(struct clit_chld_s ctx[static 1] __attribute__((unused)))
  961 {
  962     /* set up the set of fatal signals */
  963     sigemptyset(fatal_signal_set);
  964     sigaddset(fatal_signal_set, SIGHUP);
  965     sigaddset(fatal_signal_set, SIGQUIT);
  966     sigaddset(fatal_signal_set, SIGINT);
  967     sigaddset(fatal_signal_set, SIGTERM);
  968     sigaddset(fatal_signal_set, SIGXCPU);
  969     sigaddset(fatal_signal_set, SIGXFSZ);
  970     /* also the empty set */
  971     sigemptyset(empty_signal_set);
  972 
  973     /* cmdify the shell command, if any */
  974     if (ctx->options.shcmd != NULL) {
  975         ctx->huskv = cmdify(ctx->options.shcmd);
  976     }
  977     return 0;
  978 }
  979 
  980 static int
  981 fini_chld(struct clit_chld_s ctx[static 1])
  982 {
  983     if (ctx->huskv != NULL) {
  984         free(ctx->huskv);
  985     }
  986     if (ctx->options.shcmdp) {
  987         free(ctx->options.shcmd);
  988     }
  989     return 0;
  990 }
  991 
  992 static char*
  993 mkfifofn(const char *key, unsigned int tid)
  994 {
  995     size_t len = strlen(key) + 9U + 8U + 1U;
  996     char *buf;
  997 
  998     if (UNLIKELY((buf = malloc(len)) == NULL)) {
  999         return NULL;
 1000     }
 1001 redo:
 1002     /* otherwise generate a name for use as fifo */
 1003     snprintf(buf, len, "%s output  %x", key, tid);
 1004     if (mkfifo(buf, 0666) < 0) {
 1005         switch (errno) {
 1006         case EEXIST:
 1007             /* try and generate a different name */
 1008             tid++;
 1009             goto redo;
 1010         default:
 1011             free(buf);
 1012             buf = NULL;
 1013             break;
 1014         }
 1015     }
 1016     return buf;
 1017 }
 1018 
 1019 static pid_t
 1020 feeder(clit_bit_t exp, int expfd)
 1021 {
 1022     pid_t feed;
 1023 
 1024     switch ((feed = fork())) {
 1025     case -1:
 1026         /* ah good then */
 1027         break;
 1028     case 0:;
 1029         /* i am the child */
 1030         ssize_t nwr;
 1031 
 1032         while (exp.z > 0 &&
 1033                (nwr = write(expfd, exp.d, exp.z)) > 0) {
 1034             exp.d += nwr;
 1035             if ((size_t)nwr <= exp.z) {
 1036                 exp.z -= nwr;
 1037             } else {
 1038                 exp.z = 0;
 1039             }
 1040         }
 1041 
 1042         /* we're done */
 1043         close(expfd);
 1044 
 1045         /* and out, always succeed */
 1046         exit(EXIT_SUCCESS);
 1047     default:
 1048         /* i'm the parent */
 1049         break;
 1050     }
 1051     return feed;
 1052 }
 1053 
 1054 static pid_t
 1055 xpnder(clit_bit_t exp, int expfd)
 1056 {
 1057     pid_t feed;
 1058 
 1059     switch ((feed = fork())) {
 1060     case -1:
 1061         /* ah good then */
 1062         break;
 1063     case 0:;
 1064         /* i am the child */
 1065         ssize_t nwr;
 1066         int xin[2U];
 1067         pid_t sh;
 1068 
 1069         if (UNLIKELY(pipe(xin) < 0)) {
 1070         fail:
 1071             /* whatever */
 1072             exit(EXIT_FAILURE);
 1073         }
 1074 
 1075         switch ((sh = fork())) {
 1076             static char *const sh_args[] = {"sh", "-s", NULL};
 1077         case -1:
 1078             /* big fucking problem */
 1079             goto fail;
 1080         case 0:
 1081             /* close write end of pipe */
 1082             close(xin[1U]);
 1083             /* redir xin[0U] -> stdin */
 1084             dup2(xin[0U], STDIN_FILENO);
 1085             /* close read end of pipe */
 1086             close(xin[0U]);
 1087 
 1088             /* redir stdout -> expfd */
 1089             dup2(expfd, STDOUT_FILENO);
 1090             /* close expfd */
 1091             close(expfd);
 1092 
 1093             /* child again */
 1094             execv("/bin/sh", sh_args);
 1095             exit(EXIT_SUCCESS);
 1096         default:
 1097             /* parent i am */
 1098             close(xin[0U]);
 1099             /* also forget about expfd */
 1100             close(expfd);
 1101             break;
 1102         }
 1103 
 1104         if (write(xin[1U], "cat <<EOF\n", 10U) < 10) {
 1105             goto fail;
 1106         }
 1107         while (exp.z > 0 &&
 1108                (nwr = write(xin[1U], exp.d, exp.z)) > 0) {
 1109             exp.d += nwr;
 1110             if ((size_t)nwr <= exp.z) {
 1111                 exp.z -= nwr;
 1112             } else {
 1113                 exp.z = 0;
 1114             }
 1115         }
 1116         if (write(xin[1U], "EOF\n", 4U) < 4) {
 1117             goto fail;
 1118         }
 1119 
 1120         /* we're done */
 1121         close(xin[1U]);
 1122 
 1123         /* wait for child process */
 1124         with (int st) {
 1125             while (waitpid(sh, &st, 0) != sh);
 1126         }
 1127 
 1128         /* and out, always succeed */
 1129         exit(EXIT_SUCCESS);
 1130     default:
 1131         /* i'm the parent */
 1132         break;
 1133     }
 1134     return feed;
 1135 }
 1136 
 1137 static pid_t
 1138 differ(struct clit_chld_s ctx[static 1], clit_bit_t exp, bool xpnd_proto_p)
 1139 {
 1140     char *expfn = NULL;
 1141     char *actfn = NULL;
 1142     pid_t difftool = -1;
 1143     unsigned int test_id;
 1144 
 1145     assert(!clit_bit_fd_p(exp));
 1146 
 1147     /* obtain a test id */
 1148     with (struct timeval tv[1]) {
 1149         (void)gettimeofday(tv, NULL);
 1150         test_id = (unsigned int)(tv->tv_sec ^ tv->tv_usec);
 1151     }
 1152 
 1153     if (clit_bit_fn_p(exp)) {
 1154         expfn = malloc(strlen(exp.d) + 1U);
 1155         if (UNLIKELY(expfn == NULL || strcpy(expfn, exp.d) == NULL)) {
 1156             error("cannot prepare file `%s'", exp.d);
 1157             goto out;
 1158         }
 1159     } else {
 1160         expfn = mkfifofn("expected", test_id);
 1161         if (expfn == NULL) {
 1162             error("cannot create fifo `%s'", expfn);
 1163             goto out;
 1164         }
 1165     }
 1166     actfn = mkfifofn("actual", test_id);
 1167     if (actfn == NULL) {
 1168         error("cannot create fifo `%s'", actfn);
 1169         goto out;
 1170     }
 1171 
 1172     block_sigs();
 1173 
 1174     switch ((difftool = vfork())) {
 1175     case -1:
 1176         /* i am an error */
 1177         error("vfork for diff failed");
 1178         break;
 1179 
 1180     case 0: {
 1181         /* i am the child */
 1182         char *const diff_opt[] = {
 1183             "diff",
 1184             "-u",
 1185             expfn, actfn, NULL,
 1186         };
 1187 
 1188         unblock_sigs();
 1189 
 1190         /* don't allow input at all */
 1191         close(STDIN_FILENO);
 1192 
 1193         /* diff stdout -> stderr */
 1194         dup2(STDERR_FILENO, STDOUT_FILENO);
 1195 
 1196         execvp(cmd_diff, diff_opt);
 1197 
 1198         /* just unlink the files the WRONLY is waiting for
 1199          * ACTFN is always something that we create and unlink,
 1200          * so delete that one now to trigger an error in the
 1201          * parent's open() code below */
 1202         unlink(actfn);
 1203         /* EXPFN is opened in the parent code below if it's
 1204          * a fifo created by us, unlink that one to break the hang */
 1205         if (clit_bit_buf_p(exp)) {
 1206             unlink(expfn);
 1207         }
 1208         _exit(EXIT_FAILURE);
 1209     }
 1210     default:;
 1211         /* i am the parent */
 1212         static const int ofl = O_WRONLY;
 1213         int expfd = -1;
 1214         int actfd = -1;
 1215 
 1216         /* clean up descriptors */
 1217         if (clit_bit_buf_p(exp) &&
 1218             (expfd = open(expfn, ofl, 0666)) < 0) {
 1219             goto clobrk;
 1220         } else if ((actfd = open(actfn, ofl, 0666)) < 0) {
 1221             goto clobrk;
 1222         }
 1223 
 1224         /* assign actfd as out descriptor */
 1225         ctx->pou = actfd;
 1226 
 1227         /* fork out the feeder guy */
 1228         if (clit_bit_buf_p(exp)) {
 1229             /* check if we need the expander */
 1230             if (LIKELY(!xpnd_proto_p)) {
 1231                 ctx->feed = feeder(exp, expfd);
 1232             } else {
 1233                 ctx->feed = xpnder(exp, expfd);
 1234             }
 1235             /* forget about expfd lest we leak it */
 1236             close(expfd);
 1237         } else {
 1238             /* best to let everyone know that we chose
 1239              * not to use a feeder */
 1240             ctx->feed = -1;
 1241         }
 1242         break;
 1243     clobrk:
 1244         error("exec'ing %s failed", cmd_diff);
 1245         if (expfd >= 0) {
 1246             close(expfd);
 1247         }
 1248         kill(difftool, SIGTERM);
 1249         difftool = -1;
 1250         break;
 1251     }
 1252 
 1253     unblock_sigs();
 1254 out:
 1255     if (expfn) {
 1256         if (!clit_bit_fn_p(exp)) {
 1257             unlink(expfn);
 1258         }
 1259         free(expfn);
 1260     }
 1261     if (actfn) {
 1262         unlink(actfn);
 1263         free(actfn);
 1264     }
 1265     return difftool;
 1266 }
 1267 
 1268 static int
 1269 init_tst(struct clit_chld_s ctx[static 1], struct clit_tst_s tst[static 1])
 1270 {
 1271 /* set up a connection with /bin/sh to pipe to and read from */
 1272     int pty;
 1273     int pin[2];
 1274     int per[2];
 1275 
 1276     if (!tst->supp_diff) {
 1277         ctx->diff = differ(ctx, tst->out, tst->xpnd_proto);
 1278     } else {
 1279         ctx->diff = -1;
 1280         ctx->feed = -1;
 1281         ctx->pou = -1;
 1282     }
 1283 
 1284     if (0) {
 1285         ;
 1286     } else if (UNLIKELY(pipe(pin) < 0)) {
 1287         ctx->chld = -1;
 1288         return -1;
 1289     } else if (UNLIKELY(ctx->options.ptyp && pipe(per) < 0)) {
 1290         ctx->chld = -1;
 1291         return -1;
 1292     }
 1293 
 1294     block_sigs();
 1295     switch ((ctx->chld = !ctx->options.ptyp ? vfork() : pfork(&pty))) {
 1296     case -1:
 1297         /* i am an error */
 1298         unblock_sigs();
 1299         return -1;
 1300 
 1301     case 0:
 1302         /* i am the child */
 1303         unblock_sigs();
 1304         if (UNLIKELY(ctx->options.ptyp)) {
 1305             /* in pty mode connect child's stderr to parent's */
 1306             ;
 1307         }
 1308 
 1309         /* read from pin and write to pou */
 1310         if (LIKELY(!ctx->options.ptyp)) {
 1311             /* pin[0] ->stdin */
 1312             dup2(pin[0], STDIN_FILENO);
 1313         } else {
 1314             dup2(per[1], STDERR_FILENO);
 1315             close(per[0]);
 1316             close(per[1]);
 1317         }
 1318         close(pin[0]);
 1319         close(pin[1]);
 1320 
 1321         /* stdout -> pou[1] */
 1322         if (!tst->supp_diff) {
 1323             dup2(ctx->pou, STDOUT_FILENO);
 1324             close(ctx->pou);
 1325         }
 1326 
 1327         if (!ctx->huskv) {
 1328             execl("/bin/sh", "sh", NULL);
 1329         } else {
 1330             execvp(*ctx->huskv, ctx->huskv);
 1331         }
 1332         error("exec'ing /bin/sh failed");
 1333         _exit(EXIT_FAILURE);
 1334 
 1335     default:
 1336         /* i am the parent, clean up descriptors */
 1337         close(pin[0]);
 1338         if (UNLIKELY(ctx->options.ptyp)) {
 1339             close(pin[1]);
 1340         }
 1341         if (LIKELY(ctx->pou >= 0)) {
 1342             close(ctx->pou);
 1343         }
 1344         ctx->pou = -1;
 1345 
 1346         /* assign desc, write end of pin */
 1347         if (LIKELY(!ctx->options.ptyp)) {
 1348             ctx->pin = pin[1];
 1349         } else {
 1350             ctx->pin = pty;
 1351             ctx->per = per[0];
 1352             close(per[1]);
 1353         }
 1354         break;
 1355     }
 1356     return 0;
 1357 }
 1358 
 1359 static struct {
 1360     void (*old_hdl)(int);
 1361     pid_t feed;
 1362     pid_t diff;
 1363 } alrm_handler_closure;
 1364 
 1365 static void
 1366 alrm_handler(int signum)
 1367 {
 1368     assert(signum == SIGALRM);
 1369     if (alrm_handler_closure.feed > 0) {
 1370         kill(alrm_handler_closure.feed, SIGALRM);
 1371     }
 1372     if (alrm_handler_closure.diff > 0) {
 1373         kill(alrm_handler_closure.diff, SIGALRM);
 1374     }
 1375     signal(SIGALRM, alrm_handler_closure.old_hdl);
 1376     with (pid_t self = getpid()) {
 1377         if (LIKELY(self > 0)) {
 1378             kill(self, SIGALRM);
 1379         }
 1380     }
 1381     return;
 1382 }
 1383 
 1384 static int
 1385 run_tst(struct clit_chld_s ctx[static 1], struct clit_tst_s tst[static 1])
 1386 {
 1387     int rc = 0;
 1388     int st;
 1389 
 1390     if (UNLIKELY(init_tst(ctx, tst) < 0)) {
 1391         rc = -1;
 1392         if (ctx->feed > 0) {
 1393             kill(ctx->feed, SIGTERM);
 1394         }
 1395         if (ctx->diff > 0) {
 1396             kill(ctx->diff, SIGTERM);
 1397         }
 1398         goto wait;
 1399     }
 1400     if (ctx->options.timeo > 0 && (ctx->feed > 0 || ctx->diff > 0)) {
 1401         alrm_handler_closure.feed = ctx->feed;
 1402         alrm_handler_closure.diff = ctx->diff;
 1403         alrm_handler_closure.old_hdl = signal(SIGALRM, alrm_handler);
 1404     }
 1405     with (const char *p = tst->cmd.d, *const ep = tst->cmd.d + tst->cmd.z) {
 1406         for (ssize_t nwr;
 1407              p < ep && (nwr = write(ctx->pin, p, ep - p)) > 0;
 1408              p += nwr);
 1409     }
 1410     unblock_sigs();
 1411 
 1412     if (LIKELY(!ctx->options.ptyp) ||
 1413         write(ctx->pin, "exit $?\n", 8U) < 8) {
 1414         /* indicate we're not writing anymore on the child's stdin
 1415          * or in case of a pty, send exit command and keep fingers
 1416          * crossed the pty will close itself */
 1417         close(ctx->pin);
 1418         ctx->pin = -1;
 1419     }
 1420 
 1421     /* wait for the beef child */
 1422     while (ctx->chld > 0 && waitpid(ctx->chld, &st, 0) != ctx->chld);
 1423     if (LIKELY(ctx->chld > 0 && WIFEXITED(st))) {
 1424         rc = WEXITSTATUS(st);
 1425 
 1426         if (tst->exp_ret == rc) {
 1427             rc = 0;
 1428         } else if (tst->exp_ret == 255U && rc) {
 1429             rc = 0;
 1430         } else if (ctx->options.xcod) {
 1431             rc = ctx->options.xcod;
 1432         }
 1433     } else {
 1434         rc = 1;
 1435     }
 1436 
 1437 wait:
 1438     /* wait for the feeder */
 1439     while (ctx->feed > 0 && waitpid(ctx->feed, &st, 0) != ctx->feed);
 1440     if (LIKELY(ctx->feed > 0 && WIFEXITED(st))) {
 1441         int tmp_rc = WEXITSTATUS(st);
 1442 
 1443         if (tst->ign_out) {
 1444             /* don't worry */
 1445             ;
 1446         } else if (tmp_rc > rc) {
 1447             rc = tmp_rc;
 1448         }
 1449     }
 1450 
 1451     /* finally wait for the differ */
 1452     while (ctx->diff > 0 && waitpid(ctx->diff, &st, 0) != ctx->diff);
 1453     if (LIKELY(ctx->diff > 0 && WIFEXITED(st))) {
 1454         int tmp_rc = WEXITSTATUS(st);
 1455 
 1456         if (tst->ign_out) {
 1457             /* don't worry */
 1458             ;
 1459         } else if (tmp_rc > rc) {
 1460             rc = tmp_rc;
 1461         }
 1462     }
 1463 
 1464     /* and after all, if we ignore the rcs just reset them to zero */
 1465     if (tst->ign_ret) {
 1466         rc = 0;
 1467     }
 1468 
 1469 #if defined HAVE_PTY_H
 1470     if (UNLIKELY(ctx->options.ptyp && ctx->pin >= 0)) {
 1471         /* also close child's stdin here */
 1472         close(ctx->pin);
 1473     }
 1474 
 1475     /* also connect per's out end with stderr */
 1476     if (UNLIKELY(ctx->options.ptyp)) {
 1477         xsplice(ctx->per, STDERR_FILENO, SPLICE_F_MOVE);
 1478         close(ctx->per);
 1479     }
 1480 #endif  /* HAVE_PTY_H */
 1481     if (ctx->options.timeo > 0 && (ctx->feed > 0 || ctx->diff > 0)) {
 1482         signal(SIGALRM, alrm_handler_closure.old_hdl);
 1483     }
 1484     return rc;
 1485 }
 1486 
 1487 static void
 1488 set_timeout(unsigned int tdiff)
 1489 {
 1490     if (UNLIKELY(tdiff == 0U)) {
 1491         return;
 1492     }
 1493 
 1494     /* unblock just this one signal */
 1495     unblock_sig(SIGALRM);
 1496     alarm(tdiff);
 1497     return;
 1498 }
 1499 
 1500 static void
 1501 prepend_path(const char *p)
 1502 {
 1503 #define free_path() prepend_path(NULL);
 1504     static char *paths;
 1505     static size_t pathz;
 1506     static char *restrict pp;
 1507     size_t pz;
 1508 
 1509     if (UNLIKELY(p == NULL)) {
 1510         /* freeing */
 1511         if (paths == NULL) {
 1512             free(paths);
 1513             paths = pp = NULL;
 1514         }
 1515         return;
 1516     }
 1517     /* otherwise it'd be safe to compute the strlen() methinks */
 1518     pz = strlen(p);
 1519 
 1520     if (UNLIKELY(paths == NULL)) {
 1521         char *envp;
 1522 
 1523         if (LIKELY((envp = getenv("PATH")) != NULL)) {
 1524             const size_t envz = strlen(envp);
 1525 
 1526             /* get us a nice big cushion */
 1527             pathz = ((envz + pz + 1U/*\nul*/) / 256U + 2U) * 256U;
 1528             if (UNLIKELY((paths = malloc(pathz)) == NULL)) {
 1529                 /* don't bother then */
 1530                 return;
 1531             }
 1532             /* set pp for further reference */
 1533             pp = (paths + pathz) - (envz + 1U/*\nul*/);
 1534             /* glue the current path at the end of the array */
 1535             memccpy(pp, envp, '\0', envz);
 1536             /* terminate pp at least at the very end */
 1537             pp[envz] = '\0';
 1538         } else {
 1539             /* just alloc space for P */
 1540             pathz = ((pz + 1U/*\nul*/) / 256U + 2U) * 256U;
 1541             if (UNLIKELY((paths = malloc(pathz)) == NULL)) {
 1542                 /* don't bother then */
 1543                 return;
 1544             }
 1545             /* set pp for further reference */
 1546             pp = (paths + pathz) - (pz + 1U/*\nul*/);
 1547             /* copy P and then exit */
 1548             memcpy(pp, p, pz + 1U/*\nul*/);
 1549             goto out;
 1550         }
 1551     }
 1552 
 1553     /* calc prepension pointer */
 1554     pp -= pz + 1U/*:*/;
 1555 
 1556     if (UNLIKELY(pp < paths)) {
 1557         /* awww, not enough space, is there */
 1558         ptrdiff_t ppoff = pp - paths;
 1559         size_t newsz = ((pathz + pz + 1U/*:*/) / 256U + 1U) * 256U;
 1560 
 1561         if (UNLIKELY((paths = realloc(paths, newsz)) == NULL)) {
 1562             /* just leave things be */
 1563             return;
 1564         }
 1565         /* memmove to the back */
 1566         memmove(paths + (newsz - pathz), paths, pathz);
 1567         /* recalc paths pointer */
 1568         pp = paths + (newsz - pathz) + ppoff;
 1569         pathz = newsz;
 1570     }
 1571 
 1572     /* actually prepend now */
 1573     memcpy(pp, p, pz);
 1574     pp[pz] = ':';
 1575 out:
 1576     setenv("PATH", pp, 1);
 1577     return;
 1578 }
 1579 
 1580 
 1581 static int
 1582 test_f(clitf_t tf, struct clit_opt_s options)
 1583 {
 1584     static struct clit_chld_s ctx[1];
 1585     static struct clit_tst_s tst[1];
 1586     const char *bp = tf.d;
 1587     size_t bz = tf.z;
 1588     int rc = 0;
 1589 
 1590     /* find options in the test script or from the proto */
 1591     ctx->options = find_opt(options, bp, bz);
 1592 
 1593     if (UNLIKELY(init_chld(ctx) < 0)) {
 1594         return -1;
 1595     }
 1596 
 1597     /* prepare */
 1598     if (ctx->options.timeo > 0) {
 1599         set_timeout(ctx->options.timeo);
 1600     }
 1601     for (; find_tst(tst, bp, bz) == 0; bp = tst->rest.d, bz = tst->rest.z) {
 1602         if (ctx->options.verbosep) {
 1603             fputs("$ ", stderr);
 1604             fwrite(tst->cmd.d, sizeof(char), tst->cmd.z, stderr);
 1605         }
 1606         with (int tst_rc = run_tst(ctx, tst)) {
 1607             if (ctx->options.verbosep) {
 1608                 fprintf(stderr, "$? %d\n", tst_rc);
 1609             }
 1610             rc = rc ?: tst_rc;
 1611         }
 1612         if (rc && !ctx->options.keep_going_p) {
 1613             break;
 1614         }
 1615     }
 1616     if (UNLIKELY(fini_chld(ctx)) < 0) {
 1617         rc = -1;
 1618     }
 1619     return rc;
 1620 }
 1621 
 1622 static int
 1623 test(const char *testfile, struct clit_opt_s options)
 1624 {
 1625     int fd;
 1626     struct stat st;
 1627     clitf_t tf;
 1628     int rc = -1;
 1629 
 1630     if ((fd = open(testfile, O_RDONLY)) < 0) {
 1631         error("Error: cannot open file `%s'", testfile);
 1632         goto out;
 1633     } else if (fstat(fd, &st) < 0) {
 1634         error("Error: cannot stat file `%s'", testfile);
 1635         goto clo;
 1636     } else if ((tf = mmap_fd(fd, st.st_size)).d == NULL) {
 1637         error("Error: cannot map file `%s'", testfile);
 1638         goto clo;
 1639     }
 1640     /* yaay, perform the test */
 1641     rc = test_f(tf, options);
 1642 
 1643     /* and out we are */
 1644     munmap_fd(tf);
 1645 clo:
 1646     close(fd);
 1647 out:
 1648     return rc;
 1649 }
 1650 
 1651 
 1652 #include "clitosis.yucc"
 1653 
 1654 int
 1655 main(int argc, char *argv[])
 1656 {
 1657     static const char *const ends[] = {
 1658         [CLIT_END_UNKNOWN] = "unknown",
 1659         [CLIT_END_BIG] = "big",
 1660         [CLIT_END_LITTLE] = "little",
 1661         [CLIT_END_MIDDLE] = "middle",
 1662     };
 1663     yuck_t argi[1U];
 1664     struct clit_opt_s options = {0U};
 1665     int rc = 99;
 1666 
 1667     if (yuck_parse(argi, argc, argv)) {
 1668         goto out;
 1669     } else if (argi->nargs != 1U) {
 1670         yuck_auto_help(argi);
 1671         goto out;
 1672     }
 1673 
 1674     if (argi->builddir_arg) {
 1675         setenv("builddir", argi->builddir_arg, 1);
 1676     }
 1677     if (argi->srcdir_arg) {
 1678         setenv("srcdir", argi->srcdir_arg, 1);
 1679     }
 1680     if (argi->shell_arg) {
 1681         options.shcmd = argi->shell_arg;
 1682     }
 1683     if (argi->verbose_flag) {
 1684         options.verbosep = 1U;
 1685     }
 1686     if (argi->pseudo_tty_flag) {
 1687         options.ptyp = 1U;
 1688     }
 1689     if (argi->timeout_arg) {
 1690         options.timeo = strtoul(argi->timeout_arg, NULL, 10);
 1691     }
 1692     if (argi->keep_going_flag) {
 1693         options.keep_going_p = 1U;
 1694     }
 1695     if (argi->diff_arg) {
 1696         cmd_diff = argi->diff_arg;
 1697     } else if (getenv("DIFF") != NULL) {
 1698         cmd_diff = getenv("DIFF");
 1699     }
 1700     if (argi->exit_code_arg == (const char*)0x1U) {
 1701         options.xcod = 0U;
 1702     } else if (argi->exit_code_arg) {
 1703         options.xcod = strtoul(argi->exit_code_arg, NULL, 0);
 1704     } else {
 1705         options.xcod = 1U;
 1706     }
 1707 
 1708     /* Although I cannot support my claim with a hard survey, I would
 1709      * say in 99.9 cases out of a hundred the cli tool in question
 1710      * has not been installed at the time of testing it, so somehow
 1711      * we must make sure to test the version in the build directory
 1712      * rather than a globally installed one.
 1713      *
 1714      * On the other hand, in general we won't know where the build
 1715      * directory is, we've got --builddir for that, however we can
 1716      * assist our users by prepending the current working directory
 1717      * and the directory we're run from to PATH.
 1718      *
 1719      * So let's prepend our argv[0] directory */
 1720     with (char *arg0 = get_argv0dir(argv[0])) {
 1721         if (LIKELY(arg0 != NULL)) {
 1722             prepend_path(arg0);
 1723             free_argv0dir(arg0);
 1724         }
 1725     }
 1726     /* ... and our current directory */
 1727     prepend_path(".");
 1728     /* also bang builddir to path */
 1729     with (char *blddir = getenv("builddir")) {
 1730         if (LIKELY(blddir != NULL)) {
 1731             /* use at most 256U bytes for blddir */
 1732             char _blddir[256U];
 1733 
 1734             memccpy(_blddir, blddir, '\0', strlenof(_blddir));
 1735             _blddir[strlenof(_blddir)] = '\0';
 1736             prepend_path(_blddir);
 1737         }
 1738     }
 1739 
 1740     /* just to be clear about this */
 1741     setenv("endian", ends[get_endianness()], 1);
 1742 
 1743     if ((rc = test(argi->args[0U], options)) < 0) {
 1744         rc = 99;
 1745     }
 1746 
 1747     /* resource freeing */
 1748     free_path();
 1749 out:
 1750     yuck_free(argi);
 1751     return rc;
 1752 }
 1753 
 1754 /* clitosis.c ends here */