"Fossies" - the Fresh Open Source Software Archive

Member "dateutils-0.4.6/build-aux/yuck-scmver.c" (19 Mar 2019, 21728 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. For more information about "yuck-scmver.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.4.5_vs_0.4.6.

    1 /*** yuck-scmver.c -- snarf versions off project cwds
    2  *
    3  * Copyright (C) 2013-2016 Sebastian Freundt
    4  *
    5  * Author:  Sebastian Freundt <freundt@ga-group.nl>
    6  *
    7  * This file is part of yuck.
    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 <stdbool.h>
   44 #include <stdio.h>
   45 #include <string.h>
   46 #include <assert.h>
   47 #include <errno.h>
   48 #include <fcntl.h>
   49 #include <sys/wait.h>
   50 #include <sys/stat.h>
   51 #include "yuck-scmver.h"
   52 
   53 #if !defined LIKELY
   54 # define LIKELY(_x) __builtin_expect((_x), 1)
   55 #endif  /* !LIKELY */
   56 #if !defined UNLIKELY
   57 # define UNLIKELY(_x)   __builtin_expect((_x), 0)
   58 #endif  /* UNLIKELY */
   59 #if !defined UNUSED
   60 # define UNUSED(_x) _x __attribute__((unused))
   61 #endif  /* !UNUSED */
   62 
   63 #if !defined countof
   64 # define countof(x) (sizeof(x) / sizeof(*x))
   65 #endif  /* !countof */
   66 
   67 #define _paste(x, y)    x ## y
   68 #define paste(x, y) _paste(x, y)
   69 #if !defined with
   70 # define with(args...)                          \
   71     for (args, *paste(__ep, __LINE__) = (void*)1;           \
   72          paste(__ep, __LINE__); paste(__ep, __LINE__) = 0)
   73 #endif  /* !with */
   74 
   75 #define DEBUG(args...)
   76 
   77 /* globals */
   78 const char *const yscm_strs[] = {
   79     [YUCK_SCM_TARBALL] = "tarball",
   80     [YUCK_SCM_GIT] = "git",
   81     [YUCK_SCM_BZR] = "bzr",
   82     [YUCK_SCM_HG] = "hg",
   83 };
   84 
   85 
   86 static __attribute__((format(printf, 1, 2))) void
   87 error(const char *fmt, ...)
   88 {
   89     va_list vap;
   90     va_start(vap, fmt);
   91     vfprintf(stderr, fmt, vap);
   92     va_end(vap);
   93     if (errno) {
   94         fputc(':', stderr);
   95         fputc(' ', stderr);
   96         fputs(strerror(errno), stderr);
   97     }
   98     fputc('\n', stderr);
   99     return;
  100 }
  101 
  102 static inline __attribute__((const, always_inline)) char*
  103 deconst(const char *s)
  104 {
  105     union {
  106         const char *c;
  107         char *p;
  108     } x = {s};
  109     return x.p;
  110 }
  111 
  112 static __attribute__((unused)) size_t
  113 xstrlcpy(char *restrict dst, const char *src, size_t dsz)
  114 {
  115     size_t ssz;
  116 
  117     if (UNLIKELY(dsz == 0U)) {
  118         return 0U;
  119     }
  120     if ((ssz = strlen(src)) > dsz) {
  121         ssz = dsz - 1U;
  122     }
  123     memcpy(dst, src, ssz);
  124     dst[ssz] = '\0';
  125     return ssz;
  126 }
  127 
  128 static __attribute__((unused)) size_t
  129 xstrlncpy(char *restrict dst, size_t dsz, const char *src, size_t ssz)
  130 {
  131     if (UNLIKELY(dsz == 0U)) {
  132         return 0U;
  133     }
  134     if (ssz > dsz) {
  135         ssz = dsz - 1U;
  136     }
  137     memcpy(dst, src, ssz);
  138     dst[ssz] = '\0';
  139     return ssz;
  140 }
  141 
  142 static char*
  143 xdirname(char *restrict fn, const char *fp)
  144 {
  145 /* find next dir in FN from FP backwards */
  146     if (fp == NULL) {
  147         fp = fn + strlen(fn);
  148     } else if (fp <= fn) {
  149         return NULL;
  150     }
  151 
  152     for (--fp; fp >= fn && *fp != '/'; fp--);
  153     while (fp >= fn && *--fp == '/');
  154     if (fp >= fn) {
  155         /* replace / by \nul and return pointer */
  156         char *dp = fn + (++fp - fn);
  157         *dp = '\0';
  158         return dp;
  159     }
  160     /* return \nul */
  161     return NULL;
  162 }
  163 
  164 static char*
  165 xmemmem(const char *hay, const size_t hayz, const char *ndl, const size_t ndlz)
  166 {
  167     const char *const eoh = hay + hayz;
  168     const char *const eon = ndl + ndlz;
  169     const char *hp;
  170     const char *np;
  171     const char *cand;
  172     unsigned int hsum;
  173     unsigned int nsum;
  174     unsigned int eqp;
  175 
  176     /* trivial checks first
  177          * a 0-sized needle is defined to be found anywhere in haystack
  178          * then run strchr() to find a candidate in HAYSTACK (i.e. a portion
  179          * that happens to begin with *NEEDLE) */
  180     if (ndlz == 0UL) {
  181         return deconst(hay);
  182     } else if ((hay = memchr(hay, *ndl, hayz)) == NULL) {
  183         /* trivial */
  184         return NULL;
  185     }
  186 
  187     /* First characters of haystack and needle are the same now. Both are
  188      * guaranteed to be at least one character long.  Now computes the sum
  189      * of characters values of needle together with the sum of the first
  190      * needle_len characters of haystack. */
  191     for (hp = hay + 1U, np = ndl + 1U, hsum = *hay, nsum = *hay, eqp = 1U;
  192          hp < eoh && np < eon;
  193          hsum ^= *hp, nsum ^= *np, eqp &= *hp == *np, hp++, np++);
  194 
  195     /* HP now references the (NZ + 1)-th character. */
  196     if (np < eon) {
  197         /* haystack is smaller than needle, :O */
  198         return NULL;
  199     } else if (eqp) {
  200         /* found a match */
  201         return deconst(hay);
  202     }
  203 
  204     /* now loop through the rest of haystack,
  205      * updating the sum iteratively */
  206     for (cand = hay; hp < eoh; hp++) {
  207         hsum ^= *cand++;
  208         hsum ^= *hp;
  209 
  210         /* Since the sum of the characters is already known to be
  211          * equal at that point, it is enough to check just NZ - 1
  212          * characters for equality,
  213          * also CAND is by design < HP, so no need for range checks */
  214         if (hsum == nsum && memcmp(cand, ndl, ndlz - 1U) == 0) {
  215             return deconst(cand);
  216         }
  217     }
  218     return NULL;
  219 }
  220 
  221 static unsigned int
  222 hextou(const char *sp, char **ep)
  223 {
  224     register unsigned int res = 0U;
  225     size_t i;
  226 
  227     if (UNLIKELY(sp == NULL)) {
  228         goto out;
  229     } else if (*sp == '\0') {
  230         goto out;
  231     }
  232     for (i = 0U; i < sizeof(res) * 8U / 4U - 1U; sp++, i++) {
  233         register unsigned int this;
  234         switch (*sp) {
  235         case '0' ... '9':
  236             this = *sp - '0';
  237             break;
  238         case 'a' ... 'f':
  239             this = *sp - 'a' + 10U;
  240             break;
  241         case 'A' ... 'F':
  242             this = *sp - 'A' + 10U;
  243             break;
  244         default:
  245             goto fucked;
  246         }
  247 
  248         res <<= 4U;
  249         res |= this;
  250     }
  251 fucked:
  252     res <<= 4U;
  253     res |= i;
  254 
  255     /* keep reading the hexstring as long as it lasts */
  256     for (;; sp++) {
  257         switch (*sp) {
  258         case '0' ... '9':
  259         case 'a' ... 'f':
  260         case 'A' ... 'F':
  261             continue;
  262         default:
  263             goto out;
  264         }
  265     }
  266 out:
  267     if (ep != NULL) {
  268         *ep = (char*)1U + (sp - (char*)1U);
  269     }
  270     return res;
  271 }
  272 
  273 
  274 /* version snarfers */
  275 static __attribute__((noinline)) pid_t
  276 run(int *fd, ...)
  277 {
  278     static char *cmdline[16U];
  279     va_list vap;
  280     pid_t p;
  281     /* to snarf off traffic from the child */
  282     int intfd[2];
  283 
  284     va_start(vap, fd);
  285     for (size_t i = 0U;
  286          i < countof(cmdline) &&
  287              (cmdline[i] = va_arg(vap, char*)) != NULL; i++);
  288     va_end(vap);
  289     assert(*cmdline);
  290 
  291     if (pipe(intfd) < 0) {
  292         error("pipe setup to/from %s failed", cmdline[0U]);
  293         return -1;
  294     }
  295 
  296     switch ((p = vfork())) {
  297     case -1:
  298         /* i am an error */
  299         error("vfork for %s failed", cmdline[0U]);
  300         return -1;
  301 
  302     default:
  303         /* i am the parent */
  304         close(intfd[1]);
  305         if (fd != NULL) {
  306             *fd = intfd[0];
  307         } else {
  308             close(intfd[0]);
  309         }
  310         return p;
  311 
  312     case 0:
  313         /* i am the child */
  314         break;
  315     }
  316 
  317     /* child code here */
  318     close(intfd[0]);
  319     dup2(intfd[1], STDOUT_FILENO);
  320 
  321     execvp(cmdline[0U], cmdline);
  322     error("execvp(%s) failed", cmdline[0U]);
  323     _exit(EXIT_FAILURE);
  324 }
  325 
  326 static int
  327 fin(pid_t p)
  328 {
  329     int rc = 2;
  330     int st;
  331 
  332     while (waitpid(p, &st, 0) != p);
  333     if (WIFEXITED(st)) {
  334         rc = WEXITSTATUS(st);
  335     }
  336     return rc;
  337 }
  338 
  339 static yuck_scm_t
  340 find_scm(char *restrict fn, size_t fz, const char *path)
  341 {
  342     struct stat st[1U];
  343     char *restrict dp = fn;
  344 
  345     /* make a copy so we can fiddle with it */
  346     if (UNLIKELY(path == NULL)) {
  347     cwd:
  348         /* just use "." then */
  349         *dp++ = '.';
  350         *dp = '\0';
  351     } else if ((dp += xstrlcpy(fn, path, fz)) == fn) {
  352         goto cwd;
  353     }
  354 again:
  355     if (stat(fn, st) < 0) {
  356         return YUCK_SCM_ERROR;
  357     } else if (UNLIKELY((size_t)(dp - fn) + 5U >= fz)) {
  358         /* not enough space */
  359         return YUCK_SCM_ERROR;
  360     } else if (!S_ISDIR(st->st_mode)) {
  361         /* not a directory, get the dir bit and start over */
  362         if ((dp = xdirname(fn, dp)) == NULL) {
  363             dp = fn;
  364             goto cwd;
  365         }
  366         goto again;
  367     }
  368 
  369 scm_chk:
  370     /* now check for .git, .bzr, .hg */
  371     xstrlcpy(dp, "/.git", fz - (dp - fn));
  372     DEBUG("trying %s ...\n", fn);
  373     if (stat(fn, st) == 0 && S_ISDIR(st->st_mode)) {
  374         /* yay it's a .git */
  375         *dp = '\0';
  376         return YUCK_SCM_GIT;
  377     }
  378 
  379     xstrlcpy(dp, "/.bzr", fz - (dp - fn));
  380     DEBUG("trying %s ...\n", fn);
  381     if (stat(fn, st) == 0 && S_ISDIR(st->st_mode)) {
  382         /* yay it's a .git */
  383         *dp = '\0';
  384         return YUCK_SCM_BZR;
  385     }
  386 
  387     xstrlcpy(dp, "/.hg", fz - (dp - fn));
  388     DEBUG("trying %s ...\n", fn);
  389     if (stat(fn, st) == 0 && S_ISDIR(st->st_mode)) {
  390         /* yay it's a .git */
  391         *dp = '\0';
  392         return YUCK_SCM_HG;
  393     }
  394     /* nothing then, traverse upwards */
  395     if (*fn != '/') {
  396         /* make sure we don't go up indefinitely
  397          * comparing the current inode to ./.. */
  398         with (ino_t curino) {
  399             *dp = '\0';
  400             if (stat(fn, st) < 0) {
  401                 return YUCK_SCM_ERROR;
  402             }
  403             /* memorise inode */
  404             curino = st->st_ino;
  405             /* go upwards by appending /.. */
  406             dp += xstrlcpy(dp, "/..", fz - (dp - fn));
  407             /* check inode again */
  408             if (stat(fn, st) < 0) {
  409                 return YUCK_SCM_ERROR;
  410             } else if (st->st_ino == curino) {
  411                 break;
  412             }
  413             goto scm_chk;
  414         }
  415     } else if ((dp = xdirname(fn, dp)) != NULL) {
  416         goto scm_chk;
  417     }
  418     return YUCK_SCM_TARBALL;
  419 }
  420 
  421 
  422 static int
  423 rd_version(struct yuck_version_s *restrict v, const char *buf, size_t bsz)
  424 {
  425 /* reads a normalised version string vX.Y.Z-DIST-SCM RVSN[-dirty] */
  426     static const char dflag[] = "dirty";
  427     const char *vtag = NULL;
  428     const char *eov;
  429     const char *dist = NULL;
  430     const char *eod;
  431     const char *bp = buf;
  432     const char *const ep = buf + bsz;
  433 
  434     /* parse buf */
  435     switch (*bp) {
  436     case 'v':
  437     case 'V':
  438         bp++;
  439     case '0':
  440     case '1':
  441     case '2':
  442     case '3':
  443     case '4':
  444     case '5':
  445     case '6':
  446     case '7':
  447     case '8':
  448     case '9':
  449         break;
  450     default:
  451         /* weird, we req'd v-tags */
  452         return -1;
  453     }
  454 
  455     if ((eov = memchr(vtag = bp, '-', ep - bp)) == NULL) {
  456         /* last field */
  457         eov = ep;
  458     } else {
  459         dist = eov + 1U;
  460     }
  461     /* just for the fun of it, look for .git, .hg and .bzr as well */
  462     with (const char *altp) {
  463         if ((altp = xmemmem(vtag, ep - vtag, ".git", 4U))) {
  464             v->scm = YUCK_SCM_GIT;
  465             eov = altp;
  466             dist = altp + 4U;
  467         } else if ((altp = xmemmem(vtag, ep - vtag, ".bzr", 4U))) {
  468             /* oooh looks like the alternative version
  469              * vX.Y.Z.gitDD.HASH */
  470             v->scm = YUCK_SCM_BZR;
  471             eov = altp;
  472             dist = altp + 4U;
  473         } else if ((altp = xmemmem(vtag, ep - vtag, ".hg", 3U))) {
  474             /* oooh looks like the alternative version
  475              * vX.Y.Z.hgDD.HASH */
  476             v->scm = YUCK_SCM_HG;
  477             eov = altp;
  478             dist = altp + 3U;
  479         }
  480     }
  481 
  482     /* bang vtag */
  483     xstrlncpy(v->vtag, sizeof(v->vtag), vtag, eov - vtag);
  484 
  485     /* snarf distance */
  486     if (dist == NULL) {
  487         return 0;
  488     }
  489     /* read distance */
  490     with (char *on) {
  491         v->dist = strtoul(dist, &on, 10);
  492         eod = on;
  493     }
  494 
  495     switch (*eod) {
  496     default:
  497     case '\0':
  498         return 0;
  499     case '.':
  500         if (v->scm <= YUCK_SCM_TARBALL) {
  501             /* huh? */
  502             return -1;
  503         }
  504         /*@fallthrough@*/
  505     case '-':
  506         /* the show is going on, like it must */
  507         bp = eod + 1U;
  508         break;
  509     }
  510     switch (*bp++) {
  511     case 'g':
  512         /* git repo */
  513         v->scm = YUCK_SCM_GIT;
  514         break;
  515     case 'h':
  516         /* hg repo */
  517         v->scm = YUCK_SCM_HG;
  518         break;
  519     case 'b':
  520         if (v->scm <= YUCK_SCM_TARBALL) {
  521             v->scm = YUCK_SCM_BZR;
  522             break;
  523         }
  524         /* else probably git or hg hash starting with b */
  525         /*@fallthrough@*/
  526     default:
  527         /* could have been set already then */
  528         if (v->scm > YUCK_SCM_TARBALL) {
  529             /* rewind bp and continue */
  530             bp--;
  531             break;
  532         }
  533         /* otherwise we simply don't know */
  534         return 0;
  535     }
  536     /* read scm revision */
  537     with (char *on) {
  538         v->rvsn = hextou(bp, &on);
  539         bp = on;
  540     }
  541 
  542     if (bp >= ep) {
  543         ;
  544     } else if (*bp != '-' && *bp != '.') {
  545         ;
  546     } else if (bp + sizeof(dflag) > ep) {
  547         /* too short to fit `dirty' */
  548         ;
  549     } else if (!memcmp(++bp, dflag, sizeof(dflag) - 1U)) {
  550         v->dirty = 1U;
  551     }
  552     return 0;
  553 }
  554 
  555 static ssize_t
  556 wr_version(char *restrict buf, size_t bsz, const struct yuck_version_s *v)
  557 {
  558     static const char yscm_abbr[] = "tgbh";
  559     const char *const ep = buf + bsz;
  560     char *bp = buf;
  561 
  562     if (UNLIKELY(buf == NULL || bsz == 0U)) {
  563         return -1;
  564     }
  565     *bp++ = 'v';
  566     bp += xstrlcpy(bp, v->vtag, ep - bp);
  567     if (!v->dist) {
  568         goto out;
  569     } else if (bp + 1U >= ep) {
  570         /* not enough space */
  571         return -1;
  572     }
  573     /* get the dist bit on the wire */
  574     *bp++ = '-';
  575     bp += snprintf(bp, ep - bp, "%u", v->dist);
  576     if (!v->rvsn || v->scm <= YUCK_SCM_TARBALL) {
  577         goto out;
  578     } else if (bp + 2U + 8U >= ep) {
  579         /* not enough space */
  580         return -1;
  581     }
  582     *bp++ = '-';
  583     *bp++ = yscm_abbr[v->scm];
  584     bp += snprintf(bp, ep - bp, "%0*x",
  585                (int)(v->rvsn & 0x07U), v->rvsn >> 4U);
  586     if (!v->dirty) {
  587         goto out;
  588     } else if (bp + 1U + 5U >= ep) {
  589         /* not enough space */
  590         return -1;
  591     }
  592     bp += xstrlcpy(bp, "-dirty", ep - bp);
  593 out:
  594     return bp - buf;
  595 }
  596 
  597 static int
  598 git_version(struct yuck_version_s v[static 1U])
  599 {
  600     pid_t chld;
  601     int fd[1U];
  602     int rc = 0;
  603 
  604     if ((chld = run(fd, "git", "describe",
  605             "--tags", "--match=v[0-9]*",
  606             "--abbrev=8", "--dirty", NULL)) < 0) {
  607         return -1;
  608     }
  609     /* shouldn't be heaps, so just use a single read */
  610     with (char buf[256U]) {
  611         const char *vtag;
  612         const char *dist;
  613         char *bp;
  614         ssize_t nrd;
  615 
  616         if ((nrd = read(*fd, buf, sizeof(buf))) <= 0) {
  617             /* no version then aye */
  618             rc = -1;
  619             break;
  620         }
  621         buf[nrd - 1U/* for \n*/] = '\0';
  622         /* parse buf */
  623         bp = buf;
  624         if (*bp++ != 'v') {
  625             /* weird, we req'd v-tags though */
  626             rc = -1;
  627             break;
  628         } else if ((bp = strchr(vtag = bp, '-')) != NULL) {
  629             /* tokenise sting */
  630             *bp++ = '\0';
  631         }
  632         /* bang vtag */
  633         xstrlcpy(v->vtag, vtag, sizeof(v->vtag));
  634 
  635         /* snarf distance */
  636         if (bp == NULL) {
  637             break;
  638         } else if ((bp = strchr(dist = bp, '-')) != NULL) {
  639             /* tokenize */
  640             *bp++ = '\0';
  641         }
  642         /* read distance */
  643         v->dist = strtoul(dist, &bp, 10);
  644 
  645         if (*++bp == 'g') {
  646             bp++;
  647             /* read scm revision */
  648             v->rvsn = hextou(bp, &bp);
  649         }
  650         if (*bp == '\0') {
  651             break;
  652         } else if (*bp == '-') {
  653             bp++;
  654         }
  655         if (!strcmp(bp, "dirty")) {
  656             v->dirty = 1U;
  657         }
  658     }
  659     close(*fd);
  660     if (fin(chld) != 0) {
  661         rc = -1;
  662     }
  663     return rc;
  664 }
  665 
  666 static int
  667 hg_version(struct yuck_version_s v[static 1U])
  668 {
  669     pid_t chld;
  670     int fd[1U];
  671     int rc = 0;
  672 
  673     if ((chld = run(fd, "hg", "log",
  674             "--rev", ".",
  675             "--template",
  676             "{latesttag}\t{latesttagdistance}\t{node|short}\n",
  677             NULL)) < 0) {
  678         return -1;
  679     }
  680     /* shouldn't be heaps, so just use a single read */
  681     with (char buf[256U]) {
  682         const char *vtag;
  683         const char *dist;
  684         char *bp;
  685         ssize_t nrd;
  686 
  687         if ((nrd = read(*fd, buf, sizeof(buf))) <= 0) {
  688             /* no version then aye */
  689             rc = -1;
  690             break;
  691         }
  692         buf[nrd - 1U/* for \n*/] = '\0';
  693         /* parse buf */
  694         bp = buf;
  695         if (*bp++ != 'v') {
  696             /* technically we could request the latest v-tag
  697              * but i'm no hg buff so fuck it */
  698             rc = -1;
  699             break;
  700         } else if ((bp = strchr(vtag = bp, '\t')) != NULL) {
  701             /* tokenise */
  702             *bp++ = '\0';
  703         }
  704         /* bang vtag */
  705         xstrlcpy(v->vtag, vtag, sizeof(v->vtag));
  706 
  707         if (UNLIKELY(bp == NULL)) {
  708             /* huh? */
  709             rc = -1;
  710             break;
  711         } else if ((bp = strchr(dist = bp, '\t')) != NULL) {
  712             /* tokenise */
  713             *bp++ = '\0';
  714         }
  715         /* bang distance */
  716         v->dist = strtoul(dist, NULL, 10);
  717 
  718         /* bang revision */
  719         v->rvsn = hextou(bp, NULL);
  720     }
  721     close(*fd);
  722     if (fin(chld) != 0) {
  723         rc = -1;
  724     }
  725     return rc;
  726 }
  727 
  728 static int
  729 bzr_version(struct yuck_version_s v[static 1U])
  730 {
  731     pid_t chld;
  732     int fd[1U];
  733     int rc = 0;
  734 
  735     /* first get current revision number */
  736     if ((chld = run(fd, "bzr", "revno", NULL)) < 0) {
  737         return -1;
  738     }
  739     /* shouldn't be heaps, so just use a single read */
  740     with (char buf[256U]) {
  741         ssize_t nrd;
  742 
  743         if ((nrd = read(*fd, buf, sizeof(buf))) <= 0) {
  744             /* no version then aye */
  745             break;
  746         }
  747         with (char *on) {
  748             v->rvsn = strtoul(buf, &on, 10);
  749             if (LIKELY(on != NULL)) {
  750                 v->rvsn <<= 4U;
  751                 v->rvsn |= on - buf;
  752             }
  753         }
  754     }
  755     close(*fd);
  756     if (fin(chld) != 0) {
  757         return -1;
  758     }
  759 
  760     if ((chld = run(fd, "bzr", "tags",
  761             "--sort=time", NULL)) < 0) {
  762         return -1;
  763     }
  764     /* could be a lot, we only need the last line though */
  765     with (char buf[4096U]) {
  766         const char *vtag;
  767         size_t bz;
  768         char *bp;
  769         ssize_t nrd;
  770 
  771         bp = buf;
  772         bz = sizeof(buf);
  773         while ((nrd = read(*fd, bp, bz)) == (ssize_t)bz) {
  774             /* find last line */
  775             while (bz-- > 0 && buf[bz] != '\n');
  776             /* reassess bz */
  777             bz++;
  778             /* reassess bp */
  779             bp = buf + (sizeof(buf) - bz);
  780             if (LIKELY(bz < sizeof(buf))) {
  781                 memmove(buf, buf + bz, sizeof(buf) - bz);
  782             }
  783         }
  784         if (nrd <= 0) {
  785             /* no version then aye */
  786             break;
  787         }
  788         bp[nrd - 1U/* for \n*/] = '\0';
  789         /* find last line */
  790         bp += nrd;
  791         while (--bp >= buf && *bp != '\n');
  792 
  793         /* parse buf */
  794         if (*++bp != 'v') {
  795             /* we want v tags, we could go back and see if
  796              * there are any */
  797             rc = -1;
  798             break;
  799         } else if ((bp = strchr(vtag = ++bp, ' ')) != NULL) {
  800             /* tokenise */
  801             *bp++ = '\0';
  802         }
  803         /* bang vtag */
  804         xstrlcpy(v->vtag, vtag, sizeof(v->vtag));
  805 
  806         if (bp == NULL) {
  807             break;
  808         }
  809         /* read over all the whitespace to find the tag's revno */
  810         with (unsigned int rno = strtoul(bp, NULL, 10)) {
  811             v->dist = v->rvsn - rno;
  812         }
  813     }
  814     close(*fd);
  815     if (fin(chld) != 0) {
  816         rc = -1;
  817     }
  818     return rc;
  819 }
  820 
  821 
  822 /* public api */
  823 #if !defined PATH_MAX
  824 # define PATH_MAX   (256U)
  825 #endif  /* !PATH_MAX */
  826 
  827 int
  828 yuck_version(struct yuck_version_s *restrict v, const char *path)
  829 {
  830     char cwd[PATH_MAX];
  831     char fn[PATH_MAX];
  832     int rc = -1;
  833 
  834     /* initialise result structure */
  835     memset(v, 0, sizeof(*v));
  836 
  837     if (getcwd(cwd, sizeof(cwd)) == NULL) {
  838         return -1;
  839     }
  840 
  841     switch ((v->scm = find_scm(fn, sizeof(fn), path))) {
  842     case YUCK_SCM_ERROR:
  843     case YUCK_SCM_TARBALL:
  844     default:
  845         /* can't determine version numbers in tarball, can we? */
  846         return -1;
  847     case YUCK_SCM_GIT:
  848     case YUCK_SCM_BZR:
  849     case YUCK_SCM_HG:
  850         if (chdir(fn) < 0) {
  851             break;
  852         }
  853         switch (v->scm) {
  854         case YUCK_SCM_GIT:
  855             rc = git_version(v);
  856             break;
  857         case YUCK_SCM_BZR:
  858             rc = bzr_version(v);
  859             break;
  860         case YUCK_SCM_HG:
  861             rc = hg_version(v);
  862             break;
  863         default:
  864             break;
  865         }
  866         if (chdir(cwd) < 0) {
  867             /* oh big cluster fuck */
  868             rc = -1;
  869         }
  870         break;
  871     }
  872     return rc;
  873 }
  874 
  875 int
  876 yuck_version_read(struct yuck_version_s *restrict ref, const char *fn)
  877 {
  878     int rc = 0;
  879     int fd;
  880 
  881     /* initialise result structure */
  882     memset(ref, 0, sizeof(*ref));
  883 
  884     if (fn[0U] == '-' && fn[1U] == '\0') {
  885         fd = STDIN_FILENO;
  886     } else if ((fd = open(fn, O_RDONLY)) < 0) {
  887         return -1;
  888     }
  889     /* otherwise read and parse the string */
  890     with (char buf[256U]) {
  891         ssize_t nrd;
  892         char *bp;
  893 
  894         if ((nrd = read(fd, buf, sizeof(buf))) <= 0) {
  895             /* no version then aye */
  896             rc = -1;
  897             break;
  898         } else if ((bp = memchr(buf, '\n', nrd)) != NULL) {
  899             /* just go with the first line */
  900             *bp = '\0';
  901             nrd = bp - buf;
  902         } else if ((size_t)nrd < sizeof(buf)) {
  903             /* finalise with \nul */
  904             buf[nrd] = '\0';
  905         } else {
  906             /* finalise with \nul, cutting off the last byte */
  907             buf[--nrd] = '\0';
  908         }
  909         /* otherwise just read him */
  910         rc = rd_version(ref, buf, nrd);
  911     }
  912     close(fd);
  913     return rc;
  914 }
  915 
  916 ssize_t
  917 yuck_version_write_fd(int fd, const struct yuck_version_s *ref)
  918 {
  919     char buf[256U];
  920     ssize_t nwr;
  921 
  922     if ((nwr = wr_version(buf, sizeof(buf), ref)) <= 0) {
  923         return -1;
  924     }
  925     /* otherwise write */
  926     buf[nwr++] = '\n';
  927     return write(fd, buf, nwr);
  928 }
  929 
  930 int
  931 yuck_version_write(const char *fn, const struct yuck_version_s *ref)
  932 {
  933     int rc = 0;
  934     int fd;
  935 
  936     if (fn[0U] == '-' && fn[1U] == '\0') {
  937         fd = STDOUT_FILENO;
  938     } else if ((fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, 0666)) < 0) {
  939         return -1;
  940     }
  941     if (yuck_version_write_fd(fd, ref) < 0) {
  942         rc = -1;
  943     }
  944     close(fd);
  945     return rc;
  946 }
  947 
  948 int
  949 yuck_version_cmp(yuck_version_t v1, yuck_version_t v2)
  950 {
  951     if (v1->dist == 0U && v2->dist == 0U) {
  952         /* must be a tag then, innit? */
  953         return memcmp(v1->vtag, v2->vtag, sizeof(v1->vtag));
  954     }
  955     /* just brute force the comparison, consider -dirty > -clean */
  956     return memcmp(v1, v2, sizeof(*v1));
  957 }
  958 
  959 
  960 #if defined BOOTSTRAP
  961 int
  962 main(int argc, char *argv[])
  963 {
  964 /* usage would be yuck-scmver SCMDIR [REFERENCE] */
  965     static struct yuck_version_s v[1U];
  966     int rc = 0;
  967 
  968     /* prefer reference file */
  969     if (argc > 2 && (rc = yuck_version_read(v, argv[2U])) == 0) {
  970         /* just use this one */
  971         ;
  972     } else {
  973         rc = yuck_version(v, argv[1U]);
  974     }
  975     if (rc == 0) {
  976         fputs("define(YUCK_SCMVER_VERSION, ", stdout);
  977         fputs(v->vtag, stdout);
  978         if (v->scm > YUCK_SCM_TARBALL && v->dist) {
  979             fputc('.', stdout);
  980             fputs(yscm_strs[v->scm], stdout);
  981             fprintf(stdout, "%u.%0*x",
  982                 v->dist,
  983                 (int)(v->rvsn & 0x07U), v->rvsn >> 4U);
  984         }
  985         if (v->dirty) {
  986             fputs(".dirty", stdout);
  987         }
  988         fputs(")\n", stdout);
  989     }
  990     return -rc;
  991 }
  992 #endif  /* BOOTSTRAP */
  993 
  994 
  995 #if defined CONFIGURE
  996 int
  997 main(int argc, char *argv[])
  998 {
  999 /* usage would be yuck-scmver [REFERENCE] */
 1000     static struct yuck_version_s v[1U];
 1001     int rc = 0;
 1002 
 1003     if (argc > 1) {
 1004         rc = yuck_version_read(v, argv[1U]);
 1005 #if defined VERSION_FILE
 1006     } else if ((rc = yuck_version_read(v, VERSION_FILE)) == 0) {
 1007         ;
 1008 #endif  /* VERSION_FILE */
 1009     } else {
 1010         rc = yuck_version(v, NULL);
 1011     }
 1012     /* print if successful */
 1013     if (rc == 0) {
 1014         fputs(v->vtag, stdout);
 1015         if (v->scm > YUCK_SCM_TARBALL && v->dist) {
 1016             fputc('.', stdout);
 1017             fputs(yscm_strs[v->scm], stdout);
 1018             fprintf(stdout, "%u.%0*x",
 1019                 v->dist,
 1020                 (int)(v->rvsn & 0x07U), v->rvsn >> 4U);
 1021         }
 1022         if (v->dirty) {
 1023             fputs(".dirty", stdout);
 1024         }
 1025         fputc('\n', stdout);
 1026     }
 1027     return -rc;
 1028 }
 1029 #endif  /* CONFIGURE */
 1030 
 1031 /* yuck-scmver.c ends here */