1 /*** yuck.c -- generate umbrella commands
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 /* for fgetln() */
41 #if !defined _NETBSD_SOURCE
42 # define _NETBSD_SOURCE
43 #endif /* !_NETBSD_SOURCE */
44 #if !defined _DARWIN_SOURCE
45 # define _DARWIN_SOURCE
46 #endif /* !_DARWIN_SOURCE */
47 #if !defined _ALL_SOURCE
48 # define _ALL_SOURCE
49 #endif /* !_ALL_SOURCE */
50 #include <unistd.h>
51 #include <stdarg.h>
52 #include <stdlib.h>
53 #include <stdbool.h>
54 #include <stdio.h>
55 #include <string.h>
56 #include <strings.h>
57 #include <assert.h>
58 #include <errno.h>
59 #include <ctype.h>
60 #include <limits.h>
61 #include <fcntl.h>
62 #include <sys/wait.h>
63 #include <sys/stat.h>
64 #include <sys/types.h>
65 #include <sys/sysctl.h>
66 #include <time.h>
67 #if defined WITH_SCMVER
68 # include <yuck-scmver.h>
69 #endif /* WITH_SCMVER */
70
71 #if !defined LIKELY
72 # define LIKELY(_x) __builtin_expect((_x), 1)
73 #endif /* !LIKELY */
74 #if !defined UNLIKELY
75 # define UNLIKELY(_x) __builtin_expect((_x), 0)
76 #endif /* UNLIKELY */
77 #if !defined UNUSED
78 # define UNUSED(_x) _x __attribute__((unused))
79 #endif /* !UNUSED */
80
81 #if !defined countof
82 # define countof(x) (sizeof(x) / sizeof(*x))
83 #endif /* !countof */
84 #if !defined strlenof
85 # define strlenof(x) (sizeof(x) - 1ULL)
86 #endif /* !strlenof */
87
88 #define _paste(x, y) x ## y
89 #define paste(x, y) _paste(x, y)
90 #if !defined with
91 # define with(args...) \
92 for (args, *paste(__ep, __LINE__) = (void*)1; \
93 paste(__ep, __LINE__); paste(__ep, __LINE__) = 0)
94 #endif /* !with */
95
96 #if !defined HAVE_GETLINE && !defined HAVE_FGETLN
97 /* as a service to people including this file in their project
98 * but who might not necessarily run the corresponding AC_CHECK_FUNS
99 * we assume that a getline() is available. */
100 # define HAVE_GETLINE 1
101 #endif /* !HAVE_GETLINE && !HAVE_FGETLN */
102
103 typedef enum {
104 YOPT_NONE,
105 YOPT_ALLOW_UNKNOWN_DASH,
106 YOPT_ALLOW_UNKNOWN_DASHDASH,
107 } yopt_t;
108
109 struct usg_s {
110 char *umb;
111 char *cmd;
112 char *parg;
113 char *desc;
114 };
115
116 struct opt_s {
117 char sopt;
118 char *lopt;
119 char *larg;
120 char *desc;
121 unsigned int oarg:1U;
122 unsigned int marg:1U;
123 };
124
125 #if !defined BOOTSTRAP && !defined WITH_SCMVER
126 /* just forward declare this type so function signatures will work */
127 struct yuck_version_s;
128 #endif /* WITH_SCMVER */
129
130
131 static __attribute__((format(printf, 1, 2))) void
error(const char * fmt,...)132 error(const char *fmt, ...)
133 {
134 va_list vap;
135 va_start(vap, fmt);
136 vfprintf(stderr, fmt, vap);
137 va_end(vap);
138 if (errno) {
139 fputs(": ", stderr);
140 fputs(strerror(errno), stderr);
141 }
142 fputc('\n', stderr);
143 return;
144 }
145
146 static inline __attribute__((unused)) void*
deconst(const void * cp)147 deconst(const void *cp)
148 {
149 union {
150 const void *c;
151 void *p;
152 } tmp = {cp};
153 return tmp.p;
154 }
155
156 static inline __attribute__((always_inline)) unsigned int
yfls(unsigned int x)157 yfls(unsigned int x)
158 {
159 return x ? sizeof(x) * 8U - __builtin_clz(x) : 0U;
160 }
161
162 static inline __attribute__((always_inline)) size_t
max_zu(size_t x,size_t y)163 max_zu(size_t x, size_t y)
164 {
165 return x > y ? x : y;
166 }
167
168 static size_t
xstrncpy(char * restrict dst,const char * src,size_t ssz)169 xstrncpy(char *restrict dst, const char *src, size_t ssz)
170 {
171 if (UNLIKELY(dst == NULL)) {
172 return 0U;
173 }
174 memcpy(dst, src, ssz);
175 dst[ssz] = '\0';
176 return ssz;
177 }
178
179 static __attribute__((unused)) size_t
xstrlcpy(char * restrict dst,const char * src,size_t dsz)180 xstrlcpy(char *restrict dst, const char *src, size_t dsz)
181 {
182 size_t ssz = strlen(src);
183 if (ssz > dsz) {
184 ssz = dsz - 1U;
185 }
186 memcpy(dst, src, ssz);
187 dst[ssz] = '\0';
188 return ssz;
189 }
190
191 static __attribute__((unused)) size_t
xstrlncpy(char * restrict dst,size_t dsz,const char * src,size_t ssz)192 xstrlncpy(char *restrict dst, size_t dsz, const char *src, size_t ssz)
193 {
194 if (ssz > dsz) {
195 ssz = dsz - 1U;
196 }
197 memcpy(dst, src, ssz);
198 dst[ssz] = '\0';
199 return ssz;
200 }
201
202 static __attribute__((unused)) bool
xstreqp(const char * s1,const char * s2)203 xstreqp(const char *s1, const char *s2)
204 {
205 if (s1 == NULL && s2 == NULL) {
206 return true;
207 } else if (s1 == NULL || s2 == NULL) {
208 /* one of them isn't NULL */
209 return false;
210 }
211 /* resort to normal strcmp */
212 return !strcasecmp(s1, s2);
213 }
214
215 static bool
only_whitespace_p(const char * line,size_t llen)216 only_whitespace_p(const char *line, size_t llen)
217 {
218 for (const char *lp = line, *const ep = line + llen; lp < ep; lp++) {
219 if (!isspace(*lp)) {
220 return false;
221 }
222 }
223 return true;
224 }
225
226 static bool
isdashdash(const char c)227 isdashdash(const char c)
228 {
229 switch (c) {
230 default:
231 return true;
232 case '=':
233 case '[':
234 case ']':
235 case '.':
236 case '\0' ... ' ':
237 return false;
238 }
239 }
240
241 static void
massage_desc(char * str)242 massage_desc(char *str)
243 {
244 /* kick final newline and escape m4 quoting characters */
245 char *sp;
246
247 for (sp = str; *sp; sp++) {
248 switch (*sp) {
249 default:
250 break;
251 case '[':
252 /* map to STX (start of text) */
253 *sp = '\002';
254 break;
255 case ']':
256 /* map to ETX (end of text) */
257 *sp = '\003';
258 break;
259 case '(':
260 /* map to SO (shift out) */
261 *sp = '\016';
262 break;
263 case ')':
264 /* map to SI (shift in) */
265 *sp = '\017';
266 break;
267 }
268 }
269 if (sp > str && sp[-1] == '\n') {
270 *--sp = '\0';
271 }
272 return;
273 }
274
275 #if !defined BOOTSTRAP
276 static void
unmassage_buf(char * restrict buf,size_t bsz)277 unmassage_buf(char *restrict buf, size_t bsz)
278 {
279 /* turn m4 quoting character substitutes into brackets again */
280 for (char *restrict sp = buf, *const ep = buf + bsz; sp < ep; sp++) {
281 switch (*sp) {
282 default:
283 break;
284 case '\002':
285 /* unmap STX (start of text) */
286 *sp = '[';
287 break;
288 case '\003':
289 /* unmap ETX (end of text) */
290 *sp = ']';
291 break;
292 case '\016':
293 /* unmap SO (shift out) */
294 *sp = '(';
295 break;
296 case '\017':
297 /* unmap SI (shift in) */
298 *sp = ')';
299 break;
300 }
301 }
302 return;
303 }
304
305 static int
mktempp(char * restrict tmpl[static1U],int prefixlen)306 mktempp(char *restrict tmpl[static 1U], int prefixlen)
307 {
308 static mode_t umsk;
309 char *bp = *tmpl + prefixlen;
310 char *const ep = *tmpl + strlen(*tmpl);
311 mode_t m;
312 int fd;
313
314 if (UNLIKELY(!umsk)) {
315 umsk = umask(0022);
316 }
317 if (ep[-6] != 'X' || ep[-5] != 'X' || ep[-4] != 'X' ||
318 ep[-3] != 'X' || ep[-2] != 'X' || ep[-1] != 'X') {
319 if ((fd = open(bp, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0 &&
320 (bp -= prefixlen,
321 fd = open(bp, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {
322 /* fuck that then */
323 return -1;
324 }
325 } else if (m = umask(S_IXUSR | S_IRWXG | S_IRWXO),
326 UNLIKELY((fd = mkstemp(bp), umask(m), fd < 0)) &&
327 UNLIKELY((bp -= prefixlen,
328 /* reset to XXXXXX */
329 memset(ep - 6, 'X', 6U),
330 fd = mkstemp(bp)) < 0)) {
331 /* at least we tried */
332 return -1;
333 }
334 /* store result */
335 *tmpl = bp;
336 return fd;
337 }
338
339 static FILE*
mkftempp(char * restrict tmpl[static1U],int prefixlen)340 mkftempp(char *restrict tmpl[static 1U], int prefixlen)
341 {
342 int fd;
343
344 if (UNLIKELY((fd = mktempp(tmpl, prefixlen)) < 0)) {
345 return NULL;
346 }
347 return fdopen(fd, "w");
348 }
349
350 # if defined WITH_SCMVER
351 static bool
regfilep(const char * fn)352 regfilep(const char *fn)
353 {
354 struct stat st[1U];
355 return stat(fn, st) == 0 && S_ISREG(st->st_mode);
356 }
357 # endif /* WITH_SCMVER */
358 #endif /* !BOOTSTRAP */
359
360
361 /* bang buffers */
362 typedef struct {
363 /* the actual buffer (resizable) */
364 char *s;
365 /* current size */
366 size_t n;
367 /* current alloc size */
368 size_t z;
369 } bbuf_t;
370
371 static __attribute__((nonnull(1, 2))) char*
bbuf_cat(bbuf_t * restrict b,const char * str,size_t ssz)372 bbuf_cat(bbuf_t *restrict b, const char *str, size_t ssz)
373 {
374 if (UNLIKELY(b->n + ssz + 1U/*\nul*/ > b->z)) {
375 const size_t nu = max_zu(yfls(b->n + ssz + 1U) + 1U, 6U);
376 char *tmp;
377
378 tmp = realloc(b->s, (b->z = (1ULL << nu) * sizeof(*b->s)));
379 if (UNLIKELY(tmp == NULL)) {
380 goto free;
381 }
382 /* all's good then? */
383 b->s = tmp;
384 }
385 xstrncpy(b->s + b->n, str, ssz);
386 b->n += ssz;
387 return b->s;
388 free:
389 free(b->s);
390 b->s = NULL;
391 b->n = 0U;
392 b->z = 0U;
393 return NULL;
394 }
395
396 static __attribute__((nonnull(1, 2))) char*
bbuf_cpy(bbuf_t * restrict b,const char * str,size_t ssz)397 bbuf_cpy(bbuf_t *restrict b, const char *str, size_t ssz)
398 {
399 /* reduce to bbuf_cat() with zero offset */
400 b->n = 0U;
401 return bbuf_cat(b, str, ssz);
402 }
403
404
405 static void yield_usg(const struct usg_s *arg);
406 static void yield_opt(const struct opt_s *arg);
407 static void yield_inter(const bbuf_t x[static 1U]);
408 static void yield_setopt(yopt_t);
409
410 #define DEBUG(args...)
411
412 static int
usagep(const char * line,size_t llen)413 usagep(const char *line, size_t llen)
414 {
415 #define STREQLITP(x, lit) (!strncasecmp((x), lit, sizeof(lit) - 1))
416 static struct usg_s cur_usg;
417 static bbuf_t umb[1U];
418 static bbuf_t cmd[1U];
419 static bbuf_t parg[1U];
420 static bbuf_t desc[1U];
421 static bool cur_usg_yldd_p;
422 static bool umb_yldd_p;
423 const char *sp;
424 const char *up;
425 const char *cp;
426 const char *const ep = line + llen;
427
428 if (UNLIKELY(line == NULL)) {
429 goto yield;
430 }
431
432 DEBUG("USAGEP CALLED with %s", line);
433
434 if (STREQLITP(line, "setopt")) {
435 /* it's a setopt */
436 return 0;
437 } else if (!STREQLITP(line, "usage:")) {
438 if (only_whitespace_p(line, llen) && !desc->n) {
439 return 1;
440 } else if (!isspace(*line) && !cur_usg_yldd_p) {
441 /* append to description */
442 cur_usg.desc = bbuf_cat(desc, line, llen);
443 return 1;
444 }
445 yield:
446 #define RESET cur_usg.cmd = cur_usg.parg = cur_usg.desc = NULL, desc->n = 0U
447
448 if (!cur_usg_yldd_p) {
449 yield_usg(&cur_usg);
450 /* reset */
451 RESET;
452 cur_usg_yldd_p = true;
453 umb_yldd_p = true;
454 }
455 return 0;
456 } else if (!cur_usg_yldd_p) {
457 /* can't just goto yield because they wander off */
458 yield_usg(&cur_usg);
459 /* reset */
460 RESET;
461 cur_usg_yldd_p = true;
462 umb_yldd_p = true;
463 }
464 /* overread whitespace then */
465 for (sp = line + sizeof("usage:") - 1; sp < ep && isspace(*sp); sp++);
466 /* first thing should name the umbrella, find its end */
467 for (up = sp; sp < ep && !isspace(*sp); sp++);
468
469 if (cur_usg.umb && !strncasecmp(cur_usg.umb, up, sp - up)) {
470 /* nothing new and fresh */
471 ;
472 } else {
473 cur_usg.umb = bbuf_cpy(umb, up, sp - up);
474 umb_yldd_p = false;
475 }
476
477 /* overread more whitespace and [--BLA] decls then */
478 overread:
479 for (; sp < ep && isspace(*sp); sp++);
480 /* we might be strafed with option decls here */
481 switch (*sp) {
482 case '[':
483 if (sp[1U] == '-') {
484 /* might be option spec [-x], read on */
485 ;
486 } else if (STREQLITP(sp + 1U, "OPTION")) {
487 /* definitely an option marker innit? */
488 ;
489 } else {
490 /* could be posarg, better exit here */
491 break;
492 }
493 /* otherwise read till closing bracket */
494 for (sp++; sp < ep && *sp++ != ']';);
495 /* and also read over `...' */
496 for (sp++; sp < ep && *sp == '.'; sp++);
497 goto overread;
498 default:
499 /* best leave the loop */
500 break;
501 }
502
503 /* now it's time for the command innit */
504 for (cp = sp; sp < ep && !isspace(*sp); sp++);
505
506 if (cur_usg.cmd && !strncasecmp(cur_usg.cmd, cp, sp - cp)) {
507 /* nothing new and fresh */
508 ;
509 } else if (!strncasecmp(cp, "command", sp - cp) ||
510 (cp[0] == '<' && sp[-1] == '>' &&
511 !strncasecmp(cp + 1U, "command", sp - cp - 2))) {
512 /* special command COMMAND or <command> */
513 cur_usg.cmd = NULL;
514 } else if (*cp >= 'a' && *cp <= 'z' && umb_yldd_p) {
515 /* we mandate commands start with a lower case alpha char */
516 cur_usg.cmd = bbuf_cpy(cmd, cp, sp - cp);
517 } else {
518 /* not a command, could be posarg innit, so rewind */
519 sp = cp;
520 }
521
522 /* now there might be positional args, snarf them */
523 for (; sp < ep && isspace(*sp); sp++);
524 if (sp < ep) {
525 cur_usg.parg = bbuf_cpy(parg, sp, ep - sp - 1U);
526 }
527 cur_usg_yldd_p = false;
528 return 1;
529 }
530
531 static int
optionp(const char * line,size_t llen)532 optionp(const char *line, size_t llen)
533 {
534 static struct opt_s cur_opt;
535 static bbuf_t desc[1U];
536 static bbuf_t lopt[1U];
537 static bbuf_t larg[1U];
538 const char *sp = line;
539 const char *const ep = line + llen;
540
541 if (UNLIKELY(line == NULL)) {
542 goto yield;
543 }
544
545 DEBUG("OPTIONP CALLED with %s", line);
546
547 /* overread whitespace */
548 for (; sp < ep && isspace(*sp); sp++) {
549 if (*sp == '\t') {
550 /* make a tab character count 8 in total */
551 sp += 7U;
552 }
553 }
554 if ((sp - line >= 8 || (sp - line >= 1 && *sp != '-')) &&
555 (cur_opt.sopt || cur_opt.lopt)) {
556 /* should be description */
557 goto desc;
558 }
559
560 yield:
561 /* must yield the old current option before it's too late */
562 if (cur_opt.sopt || cur_opt.lopt) {
563 yield_opt(&cur_opt);
564 }
565 /* complete reset */
566 memset(&cur_opt, 0, sizeof(cur_opt));
567 if (sp - line < 2) {
568 /* can't be an option, can it? */
569 return 0;
570 } else if (!*sp) {
571 /* not an option either */
572 return 0;
573 }
574
575 /* no yield pressure anymore, try parsing the line */
576 sp++;
577 if (*sp >= '0') {
578 char sopt = *sp++;
579
580 /* eat a comma as well */
581 if (*sp == ',') {
582 sp++;
583 }
584 if (!isspace(*sp)) {
585 /* don't know -x.SOMETHING? */
586 return 0;
587 }
588 /* start over with the new option */
589 sp++;
590 cur_opt.sopt = sopt;
591 if (*sp == '\0') {
592 /* just the short option then innit? */
593 return 1;
594 } else if (isspace(*sp)) {
595 /* no arg name, no longopt */
596 ;
597 } else if (*sp == '-') {
598 /* must be a --long now, maybe */
599 sp++;
600 } else {
601 /* just an arg name */
602 const char *ap;
603
604 if (*sp == '[') {
605 cur_opt.oarg = 1U;
606 sp++;
607 }
608 if (*sp == '=') {
609 sp++;
610 }
611 for (ap = sp; sp < ep && isdashdash(*sp); sp++);
612 cur_opt.larg = bbuf_cpy(larg, ap, sp - ap);
613 if (cur_opt.oarg && *sp++ != ']') {
614 /* maybe not an optarg? */
615 ;
616 }
617 if (*sp == '.') {
618 /* could be mularg */
619 if (sp[1U] == '.' && sp[2U] == '.') {
620 /* yay, 3 dots, read over dots */
621 for (sp += 3U; *sp == '.'; sp++);
622 cur_opt.marg = 1U;
623 }
624 }
625 }
626 } else if (*sp == '-') {
627 /* --option */
628 ;
629 } else {
630 /* don't know what this is */
631 return 0;
632 }
633
634 /* --option */
635 if (*sp++ == '-') {
636 const char *op;
637
638 for (op = sp; sp < ep && isdashdash(*sp); sp++);
639 cur_opt.lopt = bbuf_cpy(lopt, op, sp - op);
640
641 switch (*sp++) {
642 case '[':
643 if (*sp++ != '=') {
644 /* just bullshit then innit? */
645 break;
646 }
647 /* otherwise optarg, fall through */
648 cur_opt.oarg = 1U;
649 case '=':;
650 /* has got an arg */
651 const char *ap;
652 for (ap = sp; sp < ep && isdashdash(*sp); sp++);
653 cur_opt.larg = bbuf_cpy(larg, ap, sp - ap);
654 if (cur_opt.oarg && *sp++ != ']') {
655 /* maybe not an optarg? */
656 ;
657 }
658 if (*sp == '.') {
659 /* could be mularg */
660 if (sp[1U] == '.' && sp[2U] == '.') {
661 /* yay, 3 dots, read over dots */
662 for (sp += 3U; *sp == '.'; sp++);
663 cur_opt.marg = 1U;
664 }
665 }
666 default:
667 break;
668 }
669 }
670 /* require at least one more space? */
671 ;
672 /* space eater */
673 for (; sp < ep && isspace(*sp); sp++);
674 /* don't free but reset the old guy */
675 desc->n = 0U;
676 desc:
677 with (size_t sz = llen - (sp - line)) {
678 if (LIKELY(sz > 0U)) {
679 cur_opt.desc = bbuf_cat(desc, sp, sz);
680 }
681 }
682 return 1;
683 }
684
685 static int
interp(const char * line,size_t llen)686 interp(const char *line, size_t llen)
687 {
688 static bbuf_t desc[1U];
689 bool only_ws_p = only_whitespace_p(line, llen);
690
691 if (UNLIKELY(line == NULL)) {
692 goto yield;
693 }
694
695 DEBUG("INTERP CALLED with %s", line);
696 if (only_ws_p && desc->n) {
697 yield:
698 yield_inter(desc);
699 /* reset */
700 desc->n = 0U;
701 } else if (!only_ws_p) {
702 if (STREQLITP(line, "setopt")) {
703 /* not an inter */
704 return 0;
705 }
706 /* snarf the line */
707 bbuf_cat(desc, line, llen);
708 return 1;
709 }
710 return 0;
711 }
712
713 static int
setoptp(const char * line,size_t UNUSED (llen))714 setoptp(const char *line, size_t UNUSED(llen))
715 {
716 if (UNLIKELY(line == NULL)) {
717 return 0;
718 }
719
720 DEBUG("SETOPTP CALLED with %s", line);
721 if (STREQLITP(line, "setopt")) {
722 /* 'nother option */
723 const char *lp = line + sizeof("setopt");
724
725 if (0) {
726 ;
727 } else if (STREQLITP(lp, "allow-unknown-dash-options")) {
728 yield_setopt(YOPT_ALLOW_UNKNOWN_DASH);
729 } else if (STREQLITP(lp, "allow-unknown-dashdash-options")) {
730 yield_setopt(YOPT_ALLOW_UNKNOWN_DASHDASH);
731 } else {
732 /* unknown setopt option */
733 }
734 }
735 return 0;
736 }
737
738
739 static const char nul_str[] = "";
740 static const char *const auto_types[] = {"auto", "flag"};
741 static FILE *outf;
742
743 static struct {
744 unsigned int no_auto_flags:1U;
745 unsigned int no_auto_action:1U;
746 } global_tweaks;
747
748 static void
__identify(char * restrict idn)749 __identify(char *restrict idn)
750 {
751 if (UNLIKELY(idn == NULL)) {
752 return;
753 }
754 for (char *restrict ip = idn; *ip; ip++) {
755 switch (*ip) {
756 case '0' ... '9':
757 case 'A' ... 'Z':
758 case 'a' ... 'z':
759 break;
760 default:
761 *ip = '_';
762 }
763 }
764 return;
765 }
766
767 static size_t
count_pargs(const char * parg)768 count_pargs(const char *parg)
769 {
770 /* return max posargs as helper for auto-dashdash commands */
771 const char *pp;
772 size_t res;
773
774 for (res = 0U, pp = parg; *pp;) {
775 /* allow [--] or -- as auto-dashdash declarators */
776 if (*pp == '[') {
777 pp++;
778 }
779 if (*pp++ == '-') {
780 if (*pp++ == '-') {
781 if (*pp == ']' || isspace(*pp)) {
782 /* found him! */
783 return res;
784 }
785 }
786 /* otherwise not the declarator we were looking for
787 * fast forward to the end */
788 for (; *pp && !isspace(*pp); pp++);
789 } else {
790 /* we know it's a bog-standard posarg for sure */
791 res++;
792 /* check for ellipsis */
793 for (; *pp && *pp != '.' && !isspace(*pp); pp++);
794 if (!*pp) {
795 /* end of parg string anyway */
796 break;
797 }
798 if (*pp++ == '.' && *pp++ == '.' && *pp++ == '.') {
799 /* ellipsis, set res to infinity and bog off */
800 break;
801 }
802 }
803 /* fast forward over all the whitespace */
804 for (; *pp && isspace(*pp); pp++);
805 }
806 return 0U;
807 }
808
809 static char*
make_opt_ident(const struct opt_s * arg)810 make_opt_ident(const struct opt_s *arg)
811 {
812 static bbuf_t i[1U];
813
814 if (arg->lopt != NULL) {
815 bbuf_cpy(i, arg->lopt, strlen(arg->lopt));
816 } else if (arg->sopt) {
817 if (bbuf_cpy(i, "dash.", 5U) != NULL) {
818 i->s[4U] = arg->sopt;
819 }
820 } else {
821 static unsigned int cnt;
822 if (bbuf_cpy(i, "idnXXXX", 7U) != NULL) {
823 snprintf(i->s + 3U, 5U, "%u", cnt++);
824 }
825 }
826 if (LIKELY(i->s != NULL)) {
827 __identify(i->s);
828 }
829 return i->s;
830 }
831
832 static char*
make_ident(const char * str)833 make_ident(const char *str)
834 {
835 static bbuf_t buf[1U];
836
837 if (LIKELY(bbuf_cpy(buf, str, strlen(str)) != NULL)) {
838 __identify(buf->s);
839 }
840 return buf->s;
841 }
842
843 static void
yield_help(void)844 yield_help(void)
845 {
846 const char *type = auto_types[global_tweaks.no_auto_action];
847
848 fprintf(outf, "yuck_add_option([help], [h], [help], [%s])\n", type);
849 fprintf(outf, "yuck_set_option_desc([help], [\
850 display this help and exit])\n");
851 return;
852 }
853
854 static void
yield_version(void)855 yield_version(void)
856 {
857 const char *type = auto_types[global_tweaks.no_auto_action];
858
859 fprintf(outf,
860 "yuck_add_option([version], [V], [version], [%s])\n", type);
861 fprintf(outf, "yuck_set_option_desc([version], [\
862 output version information and exit])\n");
863 return;
864 }
865
866 static void
yield_usg(const struct usg_s * arg)867 yield_usg(const struct usg_s *arg)
868 {
869 const char *parg = arg->parg ?: nul_str;
870 size_t nparg = count_pargs(parg);
871
872 if (arg->desc != NULL) {
873 /* kick last newline */
874 massage_desc(arg->desc);
875 }
876 if (arg->cmd != NULL) {
877 const char *idn = make_ident(arg->cmd);
878
879 fprintf(outf, "\nyuck_add_command([%s], [%s], [%s])\n",
880 idn, arg->cmd, parg);
881 if (nparg) {
882 fprintf(outf,
883 "yuck_set_command_max_posargs([%s], [%zu])\n",
884 idn, nparg);
885 }
886 if (arg->desc != NULL) {
887 fprintf(outf, "yuck_set_command_desc([%s], [%s])\n",
888 idn, arg->desc);
889 }
890 } else if (arg->umb != NULL) {
891 const char *idn = make_ident(arg->umb);
892
893 fprintf(outf, "\nyuck_set_umbrella([%s], [%s], [%s])\n",
894 idn, arg->umb, parg);
895 if (nparg) {
896 fprintf(outf,
897 "yuck_set_umbrella_max_posargs([%s], [%zu])\n",
898 idn, nparg);
899 }
900 if (arg->desc != NULL) {
901 fprintf(outf, "yuck_set_umbrella_desc([%s], [%s])\n",
902 idn, arg->desc);
903 }
904 /* insert auto-help and auto-version */
905 if (!global_tweaks.no_auto_flags) {
906 yield_help();
907 yield_version();
908 }
909 }
910 return;
911 }
912
913 static void
yield_opt(const struct opt_s * arg)914 yield_opt(const struct opt_s *arg)
915 {
916 char sopt[2U] = {arg->sopt, '\0'};
917 const char *opt = arg->lopt ?: nul_str;
918 const char *idn = make_opt_ident(arg);
919
920 if (arg->larg == NULL) {
921 fprintf(outf, "yuck_add_option([%s], [%s], [%s], "
922 "[flag]);\n", idn, sopt, opt);
923 } else {
924 const char *asufs[] = {
925 nul_str, ", opt", ", mul", ", mul, opt"
926 };
927 const char *asuf = asufs[arg->oarg | arg->marg << 1U];
928 fprintf(outf, "yuck_add_option([%s], [%s], [%s], "
929 "[arg, %s%s]);\n", idn, sopt, opt, arg->larg, asuf);
930 }
931 if (arg->desc != NULL) {
932 massage_desc(arg->desc);
933 fprintf(outf,
934 "yuck_set_option_desc([%s], [%s])\n", idn, arg->desc);
935 }
936 return;
937 }
938
939 static void
yield_inter(const bbuf_t x[static1U])940 yield_inter(const bbuf_t x[static 1U])
941 {
942 if (x->n) {
943 if (x->s[x->n - 1U] == '\n') {
944 x->s[x->n - 1U] = '\0';
945 }
946 massage_desc(x->s);
947 fprintf(outf, "yuck_add_inter([%s])\n", x->s);
948 }
949 return;
950 }
951
952 static void
yield_setopt(yopt_t yo)953 yield_setopt(yopt_t yo)
954 {
955 switch (yo) {
956 default:
957 case YOPT_NONE:
958 break;
959 case YOPT_ALLOW_UNKNOWN_DASH:
960 fputs("yuck_setopt_allow_unknown_dash\n", outf);
961 break;
962 case YOPT_ALLOW_UNKNOWN_DASHDASH:
963 fputs("yuck_setopt_allow_unknown_dashdash\n", outf);
964 break;
965 }
966 return;
967 }
968
969
970 static enum {
971 UNKNOWN,
972 SET_INTER,
973 SET_UMBCMD,
974 SET_OPTION,
975 SET_SETOPT,
976 }
snarf_ln(char * line,size_t llen)977 snarf_ln(char *line, size_t llen)
978 {
979 static unsigned int st;
980
981 switch (st) {
982 case UNKNOWN:
983 case SET_UMBCMD:
984 usage:
985 /* first keep looking for Usage: lines */
986 if (usagep(line, llen)) {
987 st = SET_UMBCMD;
988 break;
989 } else if (st == SET_UMBCMD) {
990 /* reset state, go on with option parsing */
991 st = UNKNOWN;
992 goto option;
993 }
994 case SET_OPTION:
995 option:
996 /* check them option things */
997 if (optionp(line, llen)) {
998 st = SET_OPTION;
999 break;
1000 } else if (st == SET_OPTION) {
1001 /* reset state, go on with usage parsing */
1002 st = UNKNOWN;
1003 goto usage;
1004 }
1005 case SET_INTER:
1006 /* check for some intro texts */
1007 if (interp(line, llen)) {
1008 st = SET_INTER;
1009 break;
1010 } else {
1011 /* reset state, go on with setopt parsing */
1012 st = UNKNOWN;
1013 }
1014 case SET_SETOPT:
1015 /* check for setopt BLA lines */
1016 if (setoptp(line, llen)) {
1017 st = SET_SETOPT;
1018 break;
1019 } else {
1020 /* reset state, go on with option parsing */
1021 st = UNKNOWN;
1022 }
1023 default:
1024 break;
1025 }
1026 return UNKNOWN;
1027 }
1028
1029 static int
snarf_f(FILE * f)1030 snarf_f(FILE *f)
1031 {
1032 char *line = NULL;
1033 size_t llen = 0U;
1034
1035 #if defined HAVE_GETLINE
1036 for (ssize_t nrd; (nrd = getline(&line, &llen, f)) > 0;) {
1037 if (*line == '#') {
1038 continue;
1039 }
1040 snarf_ln(line, nrd);
1041 }
1042 #elif defined HAVE_FGETLN
1043 while ((line = fgetln(f, &llen)) != NULL) {
1044 if (*line == '#') {
1045 continue;
1046 }
1047 snarf_ln(line, llen);
1048 }
1049 #else
1050 # error neither getline() nor fgetln() available, cannot read file line by line
1051 #endif /* GETLINE/FGETLN */
1052 /* drain */
1053 snarf_ln(NULL, 0U);
1054
1055 #if defined HAVE_GETLINE
1056 free(line);
1057 #endif /* HAVE_GETLINE */
1058 return 0;
1059 }
1060
1061
1062 #if defined BOOTSTRAP
1063 static FILE*
get_fn(int argc,char * argv[])1064 get_fn(int argc, char *argv[])
1065 {
1066 FILE *res;
1067
1068 if (argc > 1) {
1069 const char *fn = argv[1];
1070 if (UNLIKELY((res = fopen(fn, "r")) == NULL)) {
1071 error("cannot open file `%s'", fn);
1072 }
1073 } else {
1074 res = stdin;
1075 }
1076 return res;
1077 }
1078
1079 int
main(int argc,char * argv[])1080 main(int argc, char *argv[])
1081 {
1082 int rc = 0;
1083 FILE *yf;
1084
1085 if (UNLIKELY((yf = get_fn(argc, argv)) == NULL)) {
1086 rc = -1;
1087 } else {
1088 /* always use stdout */
1089 outf = stdout;
1090
1091 fputs("\
1092 changequote([,])dnl\n\
1093 divert([-1])\n", outf);
1094
1095 /* let the snarfing begin */
1096 rc = snarf_f(yf);
1097
1098 fputs("\n\
1099 changecom([//])\n\
1100 divert[]dnl\n", outf);
1101
1102 /* clean up */
1103 fclose(yf);
1104 }
1105
1106 return -rc;
1107 }
1108 #endif /* BOOTSTRAP */
1109
1110
1111 #if !defined BOOTSTRAP
1112 #if !defined PATH_MAX
1113 # define PATH_MAX (256U)
1114 #endif /* !PATH_MAX */
1115 static char dslfn[PATH_MAX];
1116
1117 static bool
aux_in_path_p(const char * aux,const char * path,size_t pathz)1118 aux_in_path_p(const char *aux, const char *path, size_t pathz)
1119 {
1120 char fn[PATH_MAX];
1121 char *restrict fp = fn;
1122 struct stat st[1U];
1123
1124 fp += xstrlncpy(fn, sizeof(fn), path, pathz);
1125 *fp++ = '/';
1126 xstrlcpy(fp, aux, sizeof(fn) - (fp - fn));
1127
1128 if (stat(fn, st) < 0) {
1129 return false;
1130 }
1131 return S_ISREG(st->st_mode);
1132 }
1133
1134 static ssize_t
get_myself(char * restrict buf,size_t bsz)1135 get_myself(char *restrict buf, size_t bsz)
1136 {
1137 ssize_t off;
1138 char *mp;
1139
1140 #if defined __linux__
1141 static const char myself[] = "/proc/self/exe";
1142
1143 if (UNLIKELY((off = readlink(myself, buf, bsz)) < 0)) {
1144 /* shame */
1145 return -1;
1146 }
1147 #elif defined __NetBSD__
1148 static const char myself[] = "/proc/curproc/exe";
1149
1150 if (UNLIKELY((off = readlink(myself, buf, bsz)) < 0)) {
1151 /* nawww */
1152 return -1;
1153 }
1154 #elif defined __DragonFly__
1155 static const char myself[] = "/proc/curproc/file";
1156
1157 if (UNLIKELY((off = readlink(myself, buf, bsz)) < 0)) {
1158 /* blimey */
1159 return -1;
1160 }
1161 #elif defined __FreeBSD__
1162 int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
1163
1164 /* make sure that \0 terminator fits */
1165 buf[--bsz] = '\0';
1166 if (UNLIKELY(sysctl(mib, countof(mib), buf, &bsz, NULL, 0) < 0)) {
1167 return -1;
1168 }
1169 /* we can be grateful they gave us the path, counting is our job */
1170 off = strlen(buf);
1171 #elif defined __sun || defined sun
1172
1173 snprintf(buf, bsz, "/proc/%d/path/a.out", getpid());
1174 if (UNLIKELY((off = readlink(buf, buf, bsz)) < 0)) {
1175 return -1;
1176 }
1177 #elif defined __APPLE__ && defined __MACH__
1178 uint32_t z = --bsz;
1179 if (_NSGetExecutablePath(buf, &z) != 0) {
1180 return -1;
1181 }
1182 /* good, do the counting */
1183 off = strlen(buf);
1184 #else
1185 return -1;
1186 #endif
1187
1188 /* go back to the dir bit */
1189 for (mp = buf + off - 1U; mp > buf && *mp != '/'; mp--);
1190 /* should be bin/, go up one level */
1191 *mp = '\0';
1192 for (; mp > buf && *mp != '/'; mp--);
1193 /* check if we're right */
1194 if (UNLIKELY(strcmp(++mp, "bin"))) {
1195 /* oh, it's somewhere but not bin/? */
1196 return -1;
1197 }
1198 /* now just use share/yuck/ */
1199 xstrlcpy(mp, "share/yuck/", bsz - (mp - buf));
1200 mp += sizeof("share/yuck");
1201 return mp - buf;
1202 }
1203
1204 static int
find_aux(char * restrict buf,size_t bsz,const char * aux)1205 find_aux(char *restrict buf, size_t bsz, const char *aux)
1206 {
1207 /* look up path relative to binary position */
1208 static char pkgdatadir[PATH_MAX];
1209 static ssize_t pkgdatalen;
1210 static const char *tmplpath;
1211 static ssize_t tmplplen;
1212 const char *path;
1213 size_t plen;
1214
1215 /* start off by snarfing the environment */
1216 if (tmplplen == 0U) {
1217 if ((tmplpath = getenv("YUCK_TEMPLATE_PATH")) != NULL) {
1218 tmplplen = strlen(tmplpath);
1219 } else {
1220 /* just set it to something non-0 to indicate initting
1221 * and that also works with the loop below */
1222 tmplplen = -1;
1223 tmplpath = (void*)0x1U;
1224 }
1225 }
1226
1227 /* snarf pkgdatadir */
1228 if (pkgdatalen == 0U) {
1229 pkgdatalen = get_myself(pkgdatadir, sizeof(pkgdatadir));
1230 }
1231
1232 /* go through the path first */
1233 for (const char *pp = tmplpath, *ep, *const end = tmplpath + tmplplen;
1234 pp < end; pp = ep + 1U) {
1235 ep = strchr(pp, ':') ?: end;
1236 if (aux_in_path_p(aux, pp, ep - pp)) {
1237 path = pp;
1238 plen = ep - pp;
1239 goto bang;
1240 }
1241 }
1242 /* no luck with the env path then aye */
1243 if (pkgdatalen > 0 && aux_in_path_p(aux, pkgdatadir, pkgdatalen)) {
1244 path = pkgdatadir;
1245 plen = pkgdatalen;
1246 goto bang;
1247 }
1248 #if defined YUCK_TEMPLATE_PATH
1249 path = YUCK_TEMPLATE_PATH;
1250 plen = strlenof(YUCK_TEMPLATE_PATH);
1251 if (aux_in_path_p(aux, path, plen)) {
1252 goto bang;
1253 }
1254 #endif /* YUCK_TEMPLATE_PATH */
1255 /* not what we wanted at all, must be christmas */
1256 return -1;
1257
1258 bang:
1259 with (size_t z) {
1260 z = xstrlncpy(buf, bsz, path, plen);
1261 buf[z++] = '/';
1262 xstrlcpy(buf + z, aux, bsz - z);
1263 }
1264 return 0;
1265 }
1266
1267 static int
find_dsl(void)1268 find_dsl(void)
1269 {
1270 return find_aux(dslfn, sizeof(dslfn), "yuck.m4");
1271 }
1272
1273 static void
unmassage_fd(int tgtfd,int srcfd)1274 unmassage_fd(int tgtfd, int srcfd)
1275 {
1276 static char buf[4096U];
1277
1278 for (ssize_t nrd; (nrd = read(srcfd, buf, sizeof(buf))) > 0;) {
1279 const char *bp = buf;
1280 const char *const ep = buf + nrd;
1281
1282 unmassage_buf(buf, nrd);
1283 for (ssize_t nwr;
1284 bp < ep && (nwr = write(tgtfd, bp, ep - bp)) > 0;
1285 bp += nwr);
1286 }
1287 return;
1288 }
1289
1290
1291 static char *m4_cmdline[16U] = {
1292 YUCK_M4,
1293 };
1294 static size_t cmdln_idx;
1295
1296 static int
prep_m4(void)1297 prep_m4(void)
1298 {
1299 char *p;
1300
1301 /* checkout the environment, look for M4 */
1302 if ((p = getenv("M4")) == NULL) {
1303 cmdln_idx = 1U;
1304 return 0;
1305 }
1306 /* otherwise it's big string massaging business */
1307 do {
1308 m4_cmdline[cmdln_idx++] = p;
1309
1310 /* mimic a shell's IFS */
1311 for (; *p && !isspace(*p); p++) {
1312 const char this = *p;
1313
1314 switch (this) {
1315 default:
1316 break;
1317 case '"':
1318 case '\'':
1319 /* fast forward then */
1320 while (*++p != this) {
1321 if (*p == '\\') {
1322 p++;
1323 }
1324 }
1325 break;
1326 }
1327 }
1328 if (!*p) {
1329 break;
1330 }
1331 /* otherwise it's an IFS */
1332 for (*p++ = '\0'; isspace(*p); p++);
1333 } while (1);
1334 return 0;
1335 }
1336
1337 static __attribute__((noinline)) int
run_m4(const char * outfn,...)1338 run_m4(const char *outfn, ...)
1339 {
1340 pid_t m4p;
1341 /* we need a bidirectional pipe (for the unmassaging) */
1342 int intfd[2];
1343 int outfd = STDOUT_FILENO;
1344 int rc;
1345
1346 if (pipe(intfd) < 0) {
1347 error("pipe setup to/from m4 failed");
1348 return -1;
1349 } else if (!cmdln_idx && prep_m4() < 0) {
1350 error("m4 preparations failed");
1351 return -1;
1352 }
1353
1354 /* command-line prepping */
1355 with (va_list vap) {
1356 va_start(vap, outfn);
1357 for (size_t i = cmdln_idx;
1358 i < countof(m4_cmdline) &&
1359 (m4_cmdline[i] = va_arg(vap, char*)) != NULL; i++);
1360 va_end(vap);
1361 }
1362
1363 switch ((m4p = vfork())) {
1364 case -1:
1365 /* i am an error */
1366 error("vfork for m4 failed");
1367 return -1;
1368
1369 default:
1370 break;
1371
1372 case 0:
1373 /* redirect stdout -> intfd[1] */
1374 dup2(intfd[1], STDOUT_FILENO);
1375 close(intfd[1]);
1376 close(intfd[0]);
1377 /* i am the child */
1378 execvp(m4_cmdline[0U], m4_cmdline);
1379 error("execvp(m4) failed");
1380 _exit(EXIT_FAILURE);
1381 }
1382
1383 /* i am the parent */
1384 close(intfd[1]);
1385
1386 /* prep redirection */
1387 if (outfn != NULL) {
1388 /* --output given */
1389 const int outfl = O_RDWR | O_CREAT | O_TRUNC;
1390
1391 if ((outfd = open(outfn, outfl, 0666)) < 0) {
1392 /* bollocks */
1393 error("cannot open outfile `%s'", outfn);
1394 return -1;
1395 }
1396 }
1397
1398 /* reroute m4's output */
1399 unmassage_fd(outfd, intfd[0]);
1400
1401 rc = 2;
1402 with (int st) {
1403 while (waitpid(m4p, &st, 0) != m4p);
1404 if (WIFEXITED(st)) {
1405 rc = WEXITSTATUS(st);
1406 }
1407 }
1408 if (outfn != NULL) {
1409 close(outfd);
1410 }
1411 return rc;
1412 }
1413
1414
1415 static int
wr_pre(void)1416 wr_pre(void)
1417 {
1418 fputs("\
1419 changequote`'changequote([,])dnl\n\
1420 divert([-1])\n", outf);
1421 return 0;
1422 }
1423
1424 static int
wr_suf(void)1425 wr_suf(void)
1426 {
1427 fputs("\n\
1428 changequote`'dnl\n\
1429 divert`'dnl\n", outf);
1430 return 0;
1431 }
1432
1433 static int
wr_intermediary(char * const args[],size_t nargs)1434 wr_intermediary(char *const args[], size_t nargs)
1435 {
1436 int rc = 0;
1437
1438 wr_pre();
1439
1440 if (nargs == 0U) {
1441 if (snarf_f(stdin) < 0) {
1442 errno = 0;
1443 error("cannot interpret directives on stdin");
1444 rc = 1;
1445 }
1446 }
1447 for (unsigned int i = 0U; i < nargs && rc == 0; i++) {
1448 const char *fn = args[i];
1449 FILE *yf;
1450
1451 if (UNLIKELY((yf = fopen(fn, "r")) == NULL)) {
1452 error("cannot open file `%s'", fn);
1453 rc = 1;
1454 break;
1455 } else if (snarf_f(yf) < 0) {
1456 errno = 0;
1457 error("cannot interpret directives from `%s'", fn);
1458 rc = 1;
1459 }
1460
1461 /* clean up */
1462 fclose(yf);
1463 }
1464 /* reset to sane values */
1465 wr_suf();
1466 return rc;
1467 }
1468
1469 static int
wr_header(const char hdr[static1U])1470 wr_header(const char hdr[static 1U])
1471 {
1472 /* massage the hdr bit a bit */
1473 if (strcmp(hdr, "/dev/null")) {
1474 /* /dev/null just means ignore the header aye? */
1475 const char *hp;
1476
1477 if ((hp = strrchr(hdr, '/')) == NULL) {
1478 hp = hdr;
1479 } else {
1480 hp++;
1481 };
1482 wr_pre();
1483 fprintf(outf, "define([YUCK_HEADER], [%s])dnl\n", hp);
1484 wr_suf();
1485 }
1486 return 0;
1487 }
1488
1489 static int
wr_man_date(void)1490 wr_man_date(void)
1491 {
1492 time_t now;
1493 const struct tm *tp;
1494 char buf[32U];
1495 int rc = 0;
1496
1497 if ((now = time(NULL)) == (time_t)-1) {
1498 rc = -1;
1499 } else if ((tp = gmtime(&now)) == NULL) {
1500 rc = -1;
1501 } else if (!strftime(buf, sizeof(buf), "%B %Y", tp)) {
1502 rc = -1;
1503 } else {
1504 fprintf(outf, "define([YUCK_MAN_DATE], [%s])dnl\n", buf);
1505 }
1506 return rc;
1507 }
1508
1509 static int
wr_man_pkg(const char * pkg)1510 wr_man_pkg(const char *pkg)
1511 {
1512 fprintf(outf, "define([YUCK_PKG_STR], [%s])dnl\n", pkg);
1513 return 0;
1514 }
1515
1516 static int
wr_man_nfo(const char * nfo)1517 wr_man_nfo(const char *nfo)
1518 {
1519 fprintf(outf, "define([YUCK_NFO_STR], [%s])dnl\n", nfo);
1520 return 0;
1521 }
1522
1523 static int
wr_man_incln(FILE * fp,char * restrict ln,size_t lz)1524 wr_man_incln(FILE *fp, char *restrict ln, size_t lz)
1525 {
1526 static int verbp;
1527 static int parap;
1528
1529 if (UNLIKELY(ln == NULL)) {
1530 /* drain mode */
1531 if (verbp) {
1532 fputs(".fi\n", fp);
1533 }
1534 } else if (lz <= 1U/*has at least a newline?*/) {
1535 if (verbp) {
1536 /* close verbatim mode */
1537 fputs(".fi\n", fp);
1538 verbp = 0;
1539 }
1540 if (!parap) {
1541 fputs(".PP\n", fp);
1542 parap = 1;
1543 }
1544 } else if (*ln == '[' && ln[lz - 2U] == ']') {
1545 /* section */
1546 char *restrict lp = ln + 1U;
1547
1548 for (const char *const eol = ln + lz - 2U; lp < eol; lp++) {
1549 *lp = (char)toupper(*lp);
1550 }
1551 *lp = '\0';
1552 fputs(".SH ", fp);
1553 fputs(ln + 1U, fp);
1554 fputs("\n", fp);
1555 /* reset state */
1556 parap = 0;
1557 verbp = 0;
1558 } else if (ln[0U] == ' ' && ln[1U] == ' ' && !verbp) {
1559 fputs(".nf\n", fp);
1560 verbp = 1;
1561 goto cp;
1562 } else {
1563 cp:
1564 /* otherwise copy */
1565 fwrite(ln, lz, sizeof(*ln), fp);
1566 parap = 0;
1567 }
1568 return 0;
1569 }
1570
1571 static int
wr_man_include(char ** const inc)1572 wr_man_include(char **const inc)
1573 {
1574 char _ofn[] = P_tmpdir "/" "yuck_XXXXXX";
1575 char *ofn = _ofn;
1576 FILE *ofp;
1577 char *line = NULL;
1578 size_t llen = 0U;
1579 FILE *fp;
1580
1581 if (UNLIKELY((fp = fopen(*inc, "r")) == NULL)) {
1582 error("Cannot open include file `%s', ignoring", *inc);
1583 *inc = NULL;
1584 return -1;
1585 } else if (UNLIKELY((ofp = mkftempp(&ofn, sizeof(P_tmpdir))) == NULL)) {
1586 error("Cannot open output file `%s', ignoring", ofn);
1587 *inc = NULL;
1588 return -1;
1589 }
1590
1591 /* make sure we pass on ofn */
1592 *inc = strdup(ofn);
1593
1594 #if defined HAVE_GETLINE
1595 for (ssize_t nrd; (nrd = getline(&line, &llen, fp)) > 0;) {
1596 wr_man_incln(ofp, line, nrd);
1597 }
1598 #elif defined HAVE_FGETLN
1599 while ((line = fgetln(fp, &llen)) != NULL) {
1600 wr_man_incln(ofp, line, llen);
1601 }
1602 #else
1603 # error neither getline() nor fgetln() available, cannot read file line by line
1604 #endif /* GETLINE/FGETLN */
1605 /* drain */
1606 wr_man_incln(ofp, NULL, 0U);
1607
1608 #if defined HAVE_GETLINE
1609 free(line);
1610 #endif /* HAVE_GETLINE */
1611
1612 /* close files properly */
1613 fclose(fp);
1614 fclose(ofp);
1615 return 0;
1616 }
1617
1618 static int
wr_man_includes(char * incs[],size_t nincs)1619 wr_man_includes(char *incs[], size_t nincs)
1620 {
1621 for (size_t i = 0U; i < nincs; i++) {
1622 /* massage file */
1623 if (wr_man_include(incs + i) < 0) {
1624 continue;
1625 } else if (incs[i] == NULL) {
1626 /* something else is wrong */
1627 continue;
1628 }
1629 /* otherwise make a note to include this file */
1630 fprintf(outf, "\
1631 append([YUCK_INCLUDES], [%s], [,])dnl\n", incs[i]);
1632 }
1633 return 0;
1634 }
1635
1636 static int
wr_version(const struct yuck_version_s * v,const char * vlit)1637 wr_version(const struct yuck_version_s *v, const char *vlit)
1638 {
1639 wr_pre();
1640
1641 if (v != NULL) {
1642 #if defined WITH_SCMVER
1643 const char *yscm = yscm_strs[v->scm];
1644
1645 fprintf(outf, "define([YUCK_SCMVER_VTAG], [%s])\n", v->vtag);
1646 fprintf(outf, "define([YUCK_SCMVER_SCM], [%s])\n", yscm);
1647 fprintf(outf, "define([YUCK_SCMVER_DIST], [%u])\n", v->dist);
1648 fprintf(outf, "define([YUCK_SCMVER_RVSN], [%0*x])\n",
1649 (int)(v->rvsn & 0x07U), v->rvsn >> 4U);
1650 if (!v->dirty) {
1651 fputs("define([YUCK_SCMVER_FLAG_CLEAN])\n", outf);
1652 } else {
1653 fputs("define([YUCK_SCMVER_FLAG_DIRTY])\n", outf);
1654 }
1655
1656 /* for convenience */
1657 fputs("define([YUCK_SCMVER_VERSION], [", outf);
1658 fputs(v->vtag, outf);
1659 if (v->scm > YUCK_SCM_TARBALL && v->dist) {
1660 fputc('.', outf);
1661 fputs(yscm_strs[v->scm], outf);
1662 fprintf(outf, "%u.%0*x",
1663 v->dist,
1664 (int)(v->rvsn & 0x07U), v->rvsn >> 4U);
1665 }
1666 if (v->dirty) {
1667 fputs(".dirty", outf);
1668 }
1669 fputs("])\n", outf);
1670 #else /* !WITH_SCMVER */
1671 errno = 0, error("\
1672 scmver support not built in but ptr %p given to wr_version()", v);
1673 #endif /* WITH_SCMVER */
1674 }
1675 if (vlit != NULL) {
1676 fputs("define([YUCK_VERSION], [", outf);
1677 fputs(vlit, outf);
1678 fputs("])\n", outf);
1679 }
1680 wr_suf();
1681 return 0;
1682 }
1683
1684 static int
rm_intermediary(const char * fn,int keepp)1685 rm_intermediary(const char *fn, int keepp)
1686 {
1687 if (!keepp) {
1688 if (unlink(fn) < 0) {
1689 error("cannot remove intermediary `%s'", fn);
1690 return -1;
1691 }
1692 } else {
1693 /* otherwise print a nice message so users know
1694 * the file we created */
1695 errno = 0, error("intermediary `%s' kept", fn);
1696 }
1697 return 0;
1698 }
1699
1700 static int
rm_includes(char * const incs[],size_t nincs,int keepp)1701 rm_includes(char *const incs[], size_t nincs, int keepp)
1702 {
1703 int rc = 0;
1704
1705 errno = 0;
1706 for (size_t i = 0U; i < nincs; i++) {
1707 char *restrict fn;
1708
1709 if ((fn = incs[i]) != NULL) {
1710 if (!keepp && unlink(fn) < 0) {
1711 error("cannot remove intermediary `%s'", fn);
1712 rc = -1;
1713 } else if (keepp) {
1714 /* otherwise print a nice message so users know
1715 * the file we created */
1716 errno = 0, error("intermediary `%s' kept", fn);
1717 }
1718 free(fn);
1719 }
1720 }
1721 return rc;
1722 }
1723 #endif /* !BOOTSTRAP */
1724
1725
1726 #if !defined BOOTSTRAP
1727 #include "yuck.yucc"
1728
1729 static int
cmd_gen(const struct yuck_cmd_gen_s argi[static1U])1730 cmd_gen(const struct yuck_cmd_gen_s argi[static 1U])
1731 {
1732 static char _deffn[] = P_tmpdir "/" "yuck_XXXXXX";
1733 static char gencfn[PATH_MAX];
1734 static char genhfn[PATH_MAX];
1735 char *deffn = _deffn;
1736 int rc = 0;
1737
1738 if (argi->no_auto_flags_flag) {
1739 global_tweaks.no_auto_flags = 1U;
1740 }
1741 if (argi->no_auto_actions_flag) {
1742 global_tweaks.no_auto_action = 1U;
1743 }
1744
1745 /* deal with the output first */
1746 if (UNLIKELY((outf = mkftempp(&deffn, sizeof(P_tmpdir))) == NULL)) {
1747 error("cannot open intermediate file `%s'", deffn);
1748 return -1;
1749 }
1750 /* write up our findings in DSL language */
1751 rc = wr_intermediary(argi->args, argi->nargs);
1752 /* deal with hard wired version numbers */
1753 if (argi->version_arg) {
1754 rc += wr_version(NULL, argi->version_arg);
1755 }
1756 /* special directive for the header or is it */
1757 if (argi->header_arg != NULL) {
1758 rc += wr_header(argi->header_arg);
1759 }
1760 /* and we're finished with the intermediary */
1761 fclose(outf);
1762
1763 /* only proceed if there has been no error yet */
1764 if (rc) {
1765 goto out;
1766 } else if (find_dsl() < 0) {
1767 /* error whilst finding our DSL and things */
1768 error("cannot find yuck dsl file");
1769 rc = 2;
1770 goto out;
1771 } else if (find_aux(gencfn, sizeof(gencfn), "yuck-coru.c.m4") < 0 ||
1772 find_aux(genhfn, sizeof(genhfn), "yuck-coru.h.m4") < 0) {
1773 error("cannot find yuck template files");
1774 rc = 2;
1775 goto out;
1776 }
1777 /* now route that stuff through m4 */
1778 with (const char *outfn = argi->output_arg,
1779 *cusfn = argi->custom_arg ?: "/dev/null", *hdrfn) {
1780 if ((hdrfn = argi->header_arg) != NULL) {
1781 /* run a special one for the header */
1782 if ((rc = run_m4(hdrfn, dslfn, deffn, genhfn, NULL))) {
1783 break;
1784 }
1785 /* now run the whole shebang for the beef code */
1786 rc = run_m4(outfn, dslfn, deffn, cusfn, gencfn, NULL);
1787 break;
1788 }
1789 /* standard case: pipe directives, then header, then code */
1790 rc = run_m4(outfn, dslfn, deffn, cusfn, genhfn, gencfn, NULL);
1791 }
1792 out:
1793 /* unlink include files */
1794 rm_intermediary(deffn, argi->keep_flag);
1795 return rc;
1796 }
1797
1798 static int
cmd_genman(const struct yuck_cmd_genman_s argi[static1U])1799 cmd_genman(const struct yuck_cmd_genman_s argi[static 1U])
1800 {
1801 static char _deffn[] = P_tmpdir "/" "yuck_XXXXXX";
1802 static char genmfn[PATH_MAX];
1803 char *deffn = _deffn;
1804 int rc = 0;
1805
1806 /* deal with the output first */
1807 if (UNLIKELY((outf = mkftempp(&deffn, sizeof(P_tmpdir))) == NULL)) {
1808 error("cannot open intermediate file `%s'", deffn);
1809 return -1;
1810 }
1811 /* write up our findings in DSL language */
1812 rc = wr_intermediary(argi->args, argi->nargs);
1813 if (argi->version_string_arg) {
1814 rc += wr_version(NULL, argi->version_string_arg);
1815 } else if (argi->version_file_arg) {
1816 #if defined WITH_SCMVER
1817 struct yuck_version_s v[1U];
1818 const char *verfn = argi->version_file_arg;
1819
1820 if (yuck_version_read(v, verfn) < 0) {
1821 error("cannot read version number from `%s'", verfn);
1822 rc--;
1823 } else {
1824 rc += wr_version(v, NULL);
1825 }
1826 #else /* !WITH_SCMVER */
1827 errno = 0;
1828 error("\
1829 scmver support not built in, --version-file cannot be used");
1830 #endif /* WITH_SCMVER */
1831 }
1832 /* reset to sane values */
1833 wr_pre();
1834 /* at least give the man page template an idea for YUCK_MAN_DATE */
1835 rc += wr_man_date();
1836 if (argi->package_arg) {
1837 /* package != umbrella */
1838 rc += wr_man_pkg(argi->package_arg);
1839 }
1840 if (argi->info_page_arg) {
1841 const char *nfo;
1842
1843 if ((nfo = argi->info_page_arg) == YUCK_OPTARG_NONE) {
1844 nfo = "YUCK_PKG_STR";
1845 }
1846 rc += wr_man_nfo(nfo);
1847 }
1848 /* go through includes */
1849 wr_man_includes(argi->include_args, argi->include_nargs);
1850 /* reset to sane values */
1851 wr_suf();
1852 /* and we're finished with the intermediary */
1853 fclose(outf);
1854
1855 /* only proceed if there has been no error yet */
1856 if (rc) {
1857 goto out;
1858 } else if (find_dsl() < 0) {
1859 /* error whilst finding our DSL and things */
1860 error("cannot find yuck dsl and template files");
1861 rc = 2;
1862 goto out;
1863 } else if (find_aux(genmfn, sizeof(genmfn), "yuck.man.m4") < 0) {
1864 error("cannot find yuck template for man pages");
1865 rc = 2;
1866 goto out;
1867 }
1868 /* now route that stuff through m4 */
1869 with (const char *outfn = argi->output_arg) {
1870 /* standard case: pipe directives, then header, then code */
1871 rc = run_m4(outfn, dslfn, deffn, genmfn, NULL);
1872 }
1873 out:
1874 rm_includes(argi->include_args, argi->include_nargs, argi->keep_flag);
1875 rm_intermediary(deffn, argi->keep_flag);
1876 return rc;
1877 }
1878
1879 static int
cmd_gendsl(const struct yuck_cmd_gendsl_s argi[static1U])1880 cmd_gendsl(const struct yuck_cmd_gendsl_s argi[static 1U])
1881 {
1882 int rc = 0;
1883
1884 if (argi->no_auto_flags_flag) {
1885 global_tweaks.no_auto_flags = 1U;
1886 }
1887 if (argi->no_auto_actions_flag) {
1888 global_tweaks.no_auto_action = 1U;
1889 }
1890
1891 /* bang to stdout or argi->output_arg */
1892 with (const char *outfn = argi->output_arg) {
1893 if (outfn == NULL) {
1894 outf = stdout;
1895 } else if ((outf = fopen(outfn, "w")) == NULL) {
1896 error("cannot open outfile `%s'", outfn);
1897 return 1;
1898 }
1899 }
1900 rc += wr_intermediary(argi->args, argi->nargs);
1901 if (argi->version_arg) {
1902 rc += wr_version(NULL, argi->version_arg);
1903 }
1904 return rc;
1905 }
1906
1907 static int
cmd_scmver(const struct yuck_cmd_scmver_s argi[static1U])1908 cmd_scmver(const struct yuck_cmd_scmver_s argi[static 1U])
1909 {
1910 #if defined WITH_SCMVER
1911 struct yuck_version_s v[1U];
1912 struct yuck_version_s ref[1U];
1913 const char *const reffn = argi->reference_arg;
1914 const char *const infn = argi->args[0U];
1915 int rc = 0;
1916
1917 /* read the reference file before it goes out of fashion */
1918 if (reffn && yuck_version_read(ref, reffn) < 0 &&
1919 /* only be fatal if we actually want to use the reference file */
1920 argi->use_reference_flag) {
1921 error("cannot read reference file `%s'", reffn);
1922 return 1;
1923 } else if (reffn == NULL && argi->use_reference_flag) {
1924 errno = 0, error("\
1925 flag -n|--use-reference requires -r|--reference parameter");
1926 return 1;
1927 } else if (!argi->use_reference_flag && yuck_version(v, infn) < 0) {
1928 if (argi->ignore_noscm_flag) {
1929 /* allow graceful exit through --ignore-noscm */
1930 return 0;
1931 }
1932 errno = 0, error("cannot determine SCM");
1933 return 1;
1934 }
1935
1936 if (reffn && argi->use_reference_flag) {
1937 /* must populate v then */
1938 *v = *ref;
1939 } else if (reffn && yuck_version_cmp(v, ref)) {
1940 /* version stamps differ */
1941 if (argi->verbose_flag) {
1942 errno = 0, error("scm version differs from reference");
1943 }
1944 /* try to update the reference file then */
1945 if (yuck_version_write(argi->reference_arg, v) < 0) {
1946 if (argi->verbose_flag) {
1947 error("cannot write reference file");
1948 }
1949 /* degrade to using the reference file */
1950 *v = *ref;
1951 rc = 0;
1952 } else {
1953 /* reserve exit code 3 for `updated reference file' */
1954 rc = 3;
1955 }
1956 } else if (reffn && !argi->force_flag) {
1957 /* make sure the output file exists */
1958 const char *const outfn = argi->output_arg;
1959
1960 if (outfn == NULL || regfilep(outfn)) {
1961 /* don't worry about anything then */
1962 return 0;
1963 }
1964 /* otherwise create at least one version of the output */
1965 }
1966
1967 if (infn != NULL && regfilep(infn)) {
1968 static char _scmvfn[] = P_tmpdir "/" "yscm_XXXXXX";
1969 static char tmplfn[PATH_MAX];
1970 char *scmvfn = _scmvfn;
1971
1972 /* try the local dir first */
1973 if ((outf = mkftempp(&scmvfn, sizeof(P_tmpdir))) == NULL) {
1974 error("cannot open intermediate file `%s'", scmvfn);
1975 rc = 1;
1976 } else if (find_aux(tmplfn, sizeof(tmplfn),
1977 "yuck-scmver.m4") < 0) {
1978 error("cannot find yuck template for version strings");
1979 rc = 1;
1980 } else {
1981 const char *outfn = argi->output_arg;
1982
1983 /* write the actual version info */
1984 rc += wr_version(v, NULL);
1985 /* and we're finished with the intermediary */
1986 fclose(outf);
1987 /* macro massage, vtmpfn is the template file */
1988 rc += run_m4(outfn, scmvfn, tmplfn, infn, NULL);
1989
1990 rm_intermediary(scmvfn, argi->keep_flag);
1991 }
1992 } else {
1993 fputs(v->vtag, stdout);
1994 if (v->scm > YUCK_SCM_TARBALL && v->dist) {
1995 fputc('.', stdout);
1996 fputs(yscm_strs[v->scm], stdout);
1997 fprintf(stdout, "%u.%0*x",
1998 v->dist,
1999 (int)(v->rvsn & 0x07U), v->rvsn >> 4U);
2000 }
2001 if (v->dirty) {
2002 fputs(".dirty", stdout);
2003 }
2004 fputc('\n', stdout);
2005 }
2006 return rc;
2007 #else /* !WITH_SCMVER */
2008 fputs("scmver support not built in\n", stderr);
2009 return argi->cmd == YUCK_CMD_SCMVER;
2010 #endif /* WITH_SCMVER */
2011 }
2012
2013 static int
cmd_config(const struct yuck_cmd_config_s argi[static1U])2014 cmd_config(const struct yuck_cmd_config_s argi[static 1U])
2015 {
2016 const char *const fn = argi->output_arg;
2017 FILE *of = stdout;
2018
2019 if (fn != NULL && (of = fopen(fn, "w")) == NULL) {
2020 error("cannot open file `%s'", fn);
2021 return -1;
2022 }
2023
2024 if (argi->m4_flag) {
2025 fputs(YUCK_M4, of);
2026 fputc('\n', of);
2027 }
2028 return fclose(of);
2029 }
2030
2031 int
main(int argc,char * argv[])2032 main(int argc, char *argv[])
2033 {
2034 yuck_t argi[1U];
2035 int rc = 0;
2036
2037 if (yuck_parse(argi, argc, argv) < 0) {
2038 rc = 1;
2039 goto out;
2040 }
2041
2042 switch (argi->cmd) {
2043 default:
2044 fputs("\
2045 No valid command specified.\n\
2046 See --help to obtain a list of available commands.\n", stderr);
2047 rc = 1;
2048 goto out;
2049 case YUCK_CMD_GEN:
2050 if ((rc = cmd_gen((const void*)argi)) < 0) {
2051 rc = 1;
2052 }
2053 break;
2054 case YUCK_CMD_GENDSL:
2055 if ((rc = cmd_gendsl((const void*)argi)) < 0) {
2056 rc = 1;
2057 }
2058 break;
2059 case YUCK_CMD_GENMAN:
2060 if ((rc = cmd_genman((const void*)argi)) < 0) {
2061 rc = 1;
2062 }
2063 break;
2064 case YUCK_CMD_SCMVER:
2065 if ((rc = cmd_scmver((const void*)argi)) < 0) {
2066 rc = 1;
2067 }
2068 break;
2069 case YUCK_CMD_CONFIG:
2070 if ((rc = cmd_config((const void*)argi)) < 0) {
2071 rc = 1;
2072 }
2073 break;
2074 }
2075
2076 out:
2077 yuck_free(argi);
2078 return rc;
2079 }
2080 #endif /* !BOOTSTRAP */
2081
2082 /* yuck.c ends here */
2083