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