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
error(const char * fmt,...)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, pure, always_inline)) char*
deconst(const char * s)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
xstrlcpy(char * restrict dst,const char * src,size_t dsz)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
xstrlncpy(char * restrict dst,size_t dsz,const char * src,size_t ssz)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*
xdirname(char * restrict fn,const char * fp)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*
xmemmem(const char * hay,const size_t hayz,const char * ndl,const size_t ndlz)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
hextou(const char * sp,char ** ep)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
run(int * fd,...)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
fin(pid_t p)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
find_scm(char * restrict fn,size_t fz,const char * path)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
rd_version(struct yuck_version_s * restrict v,const char * buf,size_t bsz)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
wr_version(char * restrict buf,size_t bsz,const struct yuck_version_s * v)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
git_version(struct yuck_version_s v[static1U])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
hg_version(struct yuck_version_s v[static1U])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
bzr_version(struct yuck_version_s v[static1U])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
yuck_version(struct yuck_version_s * restrict v,const char * path)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
yuck_version_read(struct yuck_version_s * restrict ref,const char * fn)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
yuck_version_write_fd(int fd,const struct yuck_version_s * ref)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
yuck_version_write(const char * fn,const struct yuck_version_s * ref)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
yuck_version_cmp(yuck_version_t v1,yuck_version_t v2)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
main(int argc,char * argv[])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
main(int argc,char * argv[])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 */
1032