Home | History | Annotate | Line # | Download | only in m4
      1 /* $NetBSD: gnum4.c,v 1.15 2026/08/16 13:19:35 riastradh Exp $ */
      2 /* $OpenBSD: gnum4.c,v 1.55 2025/11/05 17:10:45 tb Exp $ */
      3 
      4 /*
      5  * Copyright (c) 1999-2022 Marc Espie <espie (at) openbsd.org>
      6  *
      7  * Permission to use, copy, modify, and distribute this software for any
      8  * purpose with or without fee is hereby granted, provided that the above
      9  * copyright notice and this permission notice appear in all copies.
     10  *
     11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  */
     19 
     20 /*
     21  * functions needed to support gnu-m4 extensions, including a fake freezing
     22  */
     23 #if HAVE_NBTOOL_CONFIG_H
     24 #include "nbtool_config.h"
     25 #endif
     26 #include <sys/cdefs.h>
     27 __RCSID("$NetBSD: gnum4.c,v 1.15 2026/08/16 13:19:35 riastradh Exp $");
     28 
     29 #include <sys/param.h>
     30 #include <sys/types.h>
     31 #include <sys/wait.h>
     32 #include <ctype.h>
     33 #include <err.h>
     34 #include <paths.h>
     35 #include <regex.h>
     36 #include <stdarg.h>
     37 #include <stddef.h>
     38 #include <stdlib.h>
     39 #include <stdint.h>
     40 #include <stdio.h>
     41 #include <string.h>
     42 #include <errno.h>
     43 #include <unistd.h>
     44 #include <limits.h>
     45 #include "mdef.h"
     46 #include "stdd.h"
     47 #include "extern.h"
     48 
     49 
     50 int mimic_gnu = 0;
     51 
     52 /*
     53  * Support for include path search
     54  * First search in the current directory.
     55  * If not found, and the path is not absolute, include path kicks in.
     56  * First, -I options, in the order found on the command line.
     57  * Then M4PATH env variable
     58  */
     59 
     60 static struct path_entry {
     61 	char *name;
     62 	struct path_entry *next;
     63 } *first, *last;
     64 
     65 static struct path_entry *new_path_entry(const char *);
     66 static void ensure_m4path(void);
     67 static struct input_file *dopath(struct input_file *, const char *);
     68 
     69 static struct path_entry *
     70 new_path_entry(const char *dirname)
     71 {
     72 	struct path_entry *n;
     73 
     74 	n = malloc(sizeof(struct path_entry));
     75 	if (!n)
     76 		errx(1, "out of memory");
     77 	n->name = xstrdup(dirname);
     78 	n->next = 0;
     79 	return n;
     80 }
     81 
     82 void
     83 addtoincludepath(const char *dirname)
     84 {
     85 	struct path_entry *n;
     86 
     87 	n = new_path_entry(dirname);
     88 
     89 	if (last) {
     90 		last->next = n;
     91 		last = n;
     92 	}
     93 	else
     94 		last = first = n;
     95 }
     96 
     97 static void
     98 ensure_m4path(void)
     99 {
    100 	static int envpathdone = 0;
    101 	char *envpath;
    102 	char *sweep;
    103 	char *path;
    104 
    105 	if (envpathdone)
    106 		return;
    107 	envpathdone = TRUE;
    108 	envpath = getenv("M4PATH");
    109 	if (!envpath)
    110 		return;
    111 	/* for portability: getenv result is read-only */
    112 	envpath = xstrdup(envpath);
    113 	for (sweep = envpath;
    114 	    (path = strsep(&sweep, ":")) != NULL;)
    115 	    addtoincludepath(path);
    116 	free(envpath);
    117 }
    118 
    119 static
    120 struct input_file *
    121 dopath(struct input_file *i, const char *filename)
    122 {
    123 	char path[PATH_MAX];
    124 	struct path_entry *pe;
    125 	FILE *f;
    126 
    127 	for (pe = first; pe; pe = pe->next) {
    128 		snprintf(path, sizeof(path), "%s/%s", pe->name, filename);
    129 		if ((f = fopen(path, "r")) != 0) {
    130 			set_input(i, f, path);
    131 			return i;
    132 		}
    133 	}
    134 	return NULL;
    135 }
    136 
    137 struct input_file *
    138 fopen_trypath(struct input_file *i, const char *filename)
    139 {
    140 	FILE *f;
    141 
    142 	f = fopen(filename, "r");
    143 	if (f != NULL) {
    144 		set_input(i, f, filename);
    145 		return i;
    146 	}
    147 	if (filename[0] == '/')
    148 		return NULL;
    149 
    150 	ensure_m4path();
    151 
    152 	return dopath(i, filename);
    153 }
    154 
    155 void
    156 doindir(const char *argv[], int argc)
    157 {
    158 	ndptr n;
    159 	struct macro_definition *p;
    160 
    161 	n = lookup(argv[2]);
    162 	if (n == NULL || (p = macro_getdef(n)) == NULL)
    163 		m4errx(1, "indir: undefined macro %s.", argv[2]);
    164 	argv[1] = p->defn;
    165 
    166 	eval(argv+1, argc-1, p->type, is_traced(n));
    167 }
    168 
    169 void
    170 dobuiltin(const char *argv[], int argc)
    171 {
    172 	ndptr p;
    173 
    174 	argv[1] = NULL;
    175 	p = macro_getbuiltin(argv[2]);
    176 	if (p != NULL)
    177 		eval(argv+1, argc-1, macro_builtin_type(p), is_traced(p));
    178 	else
    179 		m4errx(1, "unknown builtin %s.", argv[2]);
    180 }
    181 
    182 
    183 /* We need some temporary buffer space, as pb pushes BACK and substitution
    184  * proceeds forward... */
    185 static char *buffer;
    186 static size_t bufsize = 0;
    187 static size_t current = 0;
    188 
    189 static void addchars(const char *, size_t);
    190 static void addchar(int);
    191 static char *twiddle(const char *);
    192 static char *getstring(void);
    193 static void exit_regerror(int, regex_t *, const char *) __dead;
    194 static void do_subst(const char *, regex_t *, const char *, const char *,
    195     regmatch_t *);
    196 static void do_regexpindex(const char *, regex_t *, const char *, regmatch_t *);
    197 static void do_regexp(const char *, regex_t *, const char *, const char *,
    198     regmatch_t *);
    199 static void add_sub(size_t, const char *, regex_t *, regmatch_t *);
    200 static void add_replace(const char *, regex_t *, const char *, regmatch_t *);
    201 #define addconstantstring(s) addchars((s), sizeof(s)-1)
    202 
    203 static void
    204 addchars(const char *c, size_t n)
    205 {
    206 	if (n == 0)
    207 		return;
    208 	while (current + n > bufsize) {
    209 		if (bufsize == 0)
    210 			bufsize = 1024;
    211 		else if (bufsize <= SIZE_MAX/2) {
    212 			bufsize *= 2;
    213 		} else {
    214 			errx(1, "size overflow");
    215 		}
    216 		buffer = xrealloc(buffer, bufsize, NULL);
    217 	}
    218 	memcpy(buffer+current, c, n);
    219 	current += n;
    220 }
    221 
    222 static void
    223 addchar(int c)
    224 {
    225 	if (current +1 > bufsize) {
    226 		if (bufsize == 0)
    227 			bufsize = 1024;
    228 		else
    229 			bufsize *= 2;
    230 		buffer = xrealloc(buffer, bufsize, NULL);
    231 	}
    232 	buffer[current++] = c;
    233 }
    234 
    235 static char *
    236 getstring(void)
    237 {
    238 	addchar('\0');
    239 	current = 0;
    240 	return buffer;
    241 }
    242 
    243 
    244 static void
    245 exit_regerror(int er, regex_t *re, const char *source)
    246 {
    247 	size_t	errlen;
    248 	char	*errbuf;
    249 
    250 	errlen = regerror(er, re, NULL, 0);
    251 	errbuf = xalloc(errlen,
    252 	    "malloc in regerror: %lu", (unsigned long)errlen);
    253 	regerror(er, re, errbuf, errlen);
    254 	m4errx(1, "regular expression error in %s: `%s'", source, errbuf);
    255 }
    256 
    257 /* warnx() plus check to see if we need to change exit code or exit.
    258  * -E flag functionality.
    259  */
    260 void
    261 m4_warnx(const char *fmt, ...)
    262 {
    263 	va_list ap;
    264 
    265 	va_start(ap, fmt);
    266 	vwarnx(fmt, ap);
    267 	va_end(ap);
    268 
    269 	if (fatal_warns)
    270 		exit(1);
    271 	if (error_warns)
    272 		exit_code = 1;
    273 }
    274 
    275 static void
    276 add_sub(size_t n, const char *string, regex_t *re, regmatch_t *pm)
    277 {
    278 	if (n > re->re_nsub) {
    279 		if (!quiet)
    280 			warnx("No subexpression %zu", n);
    281 		if (fatal_warns)
    282 			exit(EXIT_FAILURE);
    283 	}
    284 	/* Subexpressions that did not match are
    285 	 * not an error.  */
    286 	else if (pm[n].rm_so != -1 &&
    287 	    pm[n].rm_eo != -1) {
    288 		addchars(string + pm[n].rm_so,
    289 			pm[n].rm_eo - pm[n].rm_so);
    290 	}
    291 }
    292 
    293 /* Add replacement string to the output buffer, recognizing special
    294  * constructs and replacing them with substrings of the original string.
    295  */
    296 static void
    297 add_replace(const char *string, regex_t *re, const char *replace, regmatch_t *pm)
    298 {
    299 	const char *p;
    300 
    301 	for (p = replace; *p != '\0'; p++) {
    302 		if (*p == '&' && !mimic_gnu) {
    303 			add_sub(0, string, re, pm);
    304 			continue;
    305 		}
    306 		if (*p == '\\') {
    307 			if (p[1] == '\\') {
    308 				addchar(p[1]);
    309 				p++;
    310 				continue;
    311 			}
    312 			if (p[1] == '&') {
    313 				if (mimic_gnu)
    314 					add_sub(0, string, re, pm);
    315 				else
    316 					addchar(p[1]);
    317 				p++;
    318 				continue;
    319 			}
    320 			if (isdigit((unsigned char)p[1])) {
    321 				add_sub(*(++p) - '0', string, re, pm);
    322 				continue;
    323 			}
    324 		}
    325 	    	addchar(*p);
    326 	}
    327 }
    328 
    329 static void
    330 do_subst(const char *string, regex_t *re, const char *source,
    331     const char *replace, regmatch_t *pm)
    332 {
    333 	int error;
    334 	int flags = 0;
    335 	const char *last_match = NULL;
    336 
    337 	while ((error = regexec(re, string, re->re_nsub+1, pm, flags)) == 0) {
    338 		if (pm[0].rm_eo != 0) {
    339 			if (string[pm[0].rm_eo-1] == '\n')
    340 				flags = 0;
    341 			else
    342 				flags = REG_NOTBOL;
    343 		}
    344 
    345 		/* NULL length matches are special... We use the `vi-mode'
    346 		 * rule: don't allow a NULL-match at the last match
    347 		 * position.
    348 		 */
    349 		if (pm[0].rm_so == pm[0].rm_eo &&
    350 		    string + pm[0].rm_so == last_match) {
    351 			if (*string == '\0')
    352 				return;
    353 			addchar(*string);
    354 			if (*string++ == '\n')
    355 				flags = 0;
    356 			else
    357 				flags = REG_NOTBOL;
    358 			continue;
    359 		}
    360 		last_match = string + pm[0].rm_so;
    361 		addchars(string, pm[0].rm_so);
    362 		add_replace(string, re, replace, pm);
    363 		string += pm[0].rm_eo;
    364 		buffer[current] = '\0';
    365 	}
    366 	while (*string)
    367 		addchar(*string++);
    368 	if (error != REG_NOMATCH)
    369 		exit_regerror(error, re, source);
    370 	pbstr(string);
    371 }
    372 
    373 static void
    374 do_regexp(const char *string, regex_t *re, const char *source,
    375     const char *replace, regmatch_t *pm)
    376 {
    377 	int error;
    378 
    379 	switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) {
    380 	case 0:
    381 		add_replace(string, re, replace, pm);
    382 		pbstr(getstring());
    383 		break;
    384 	case REG_NOMATCH:
    385 		break;
    386 	default:
    387 		exit_regerror(error, re, source);
    388 	}
    389 }
    390 
    391 static void
    392 do_regexpindex(const char *source, regex_t *re, const char *string, regmatch_t *pm)
    393 {
    394 	int error;
    395 
    396 	switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) {
    397 	case 0:
    398 		pbunsigned(pm[0].rm_so);
    399 		break;
    400 	case REG_NOMATCH:
    401 		pbnum(-1);
    402 		break;
    403 	default:
    404 		exit_regerror(error, re, source);
    405 	}
    406 }
    407 
    408 /* In Gnu m4 mode, parentheses for backmatch don't work like POSIX 1003.2
    409  * says. So we twiddle with the regexp before passing it to regcomp.
    410  */
    411 static char *
    412 twiddle(const char *p)
    413 {
    414 	/* + at start of regexp is a normal character for Gnu m4 */
    415 	if (*p == '^') {
    416 		addchar(*p);
    417 		p++;
    418 	}
    419 	if (*p == '+') {
    420 		addchar('\\');
    421 	}
    422 	/* This could use strcspn for speed... */
    423 	while (*p != '\0') {
    424 		if (*p == '\\') {
    425 			switch(p[1]) {
    426 			case '(':
    427 			case ')':
    428 			case '|':
    429 				addchar(p[1]);
    430 				break;
    431 			case 'w':
    432 				addconstantstring("[_a-zA-Z0-9]");
    433 				break;
    434 			case 'W':
    435 				addconstantstring("[^_a-zA-Z0-9]");
    436 				break;
    437 			case '<':
    438 				addconstantstring("[[:<:]]");
    439 				break;
    440 			case '>':
    441 				addconstantstring("[[:>:]]");
    442 				break;
    443 			default:
    444 				addchars(p, 2);
    445 				break;
    446 			}
    447 			p+=2;
    448 			continue;
    449 		}
    450 		if (strchr("()|{}", *p) != NULL)
    451 			addchar('\\');
    452 
    453 		addchar(*p);
    454 		p++;
    455 	}
    456 	return getstring();
    457 }
    458 
    459 /* patsubst(string, regexp, opt replacement) */
    460 /* argv[2]: string
    461  * argv[3]: regexp
    462  * argv[4]: opt rep
    463  */
    464 void
    465 dopatsubst(const char *argv[], int argc)
    466 {
    467 	if (argc <= 3) {
    468 		if (!quiet)
    469 			warnx("Too few arguments (%d) to patsubst", argc);
    470 
    471 		if (fatal_warns)
    472 			exit(EXIT_FAILURE);
    473 		return;
    474 	}
    475 	/* special case: empty regexp */
    476 	if (argv[3][0] == '\0') {
    477 		const char *s;
    478 		size_t len;
    479 		if (argc > 4 && argv[4])
    480 			len = strlen(argv[4]);
    481 		else
    482 			len = 0;
    483 		for (s = argv[2]; *s != '\0'; s++) {
    484 			addchars(argv[4], len);
    485 			addchar(*s);
    486 		}
    487 	} else {
    488 		int error;
    489 		regex_t re;
    490 		regmatch_t *pmatch;
    491 		int mode = REG_EXTENDED;
    492 		const char *source;
    493 		size_t l = strlen(argv[3]);
    494 
    495 		if (!mimic_gnu ||
    496 		    (argv[3][0] == '^') ||
    497 		    (l > 0 && argv[3][l-1] == '$'))
    498 			mode |= REG_NEWLINE;
    499 
    500 		source = mimic_gnu ? twiddle(argv[3]) : argv[3];
    501 		error = regcomp(&re, source, mode);
    502 		if (error != 0)
    503 			exit_regerror(error, &re, source);
    504 
    505 		pmatch = xreallocarray(NULL, re.re_nsub+1,  sizeof(regmatch_t),
    506 		    NULL);
    507 		do_subst(argv[2], &re, source,
    508 		    argc > 4 && argv[4] != NULL ? argv[4] : "", pmatch);
    509 		free(pmatch);
    510 		regfree(&re);
    511 	}
    512 	pbstr(getstring());
    513 }
    514 
    515 void
    516 doregexp(const char *argv[], int argc)
    517 {
    518 	int error;
    519 	regex_t re;
    520 	regmatch_t *pmatch;
    521 	const char *source;
    522 
    523 	if (argc <= 3) {
    524 		if (!quiet)
    525 			m4_warnx("Too few arguments (%d) to regexp", argc);
    526 		if (fatal_warns)
    527 			exit(EXIT_FAILURE);
    528 		return;
    529 	}
    530 	/* special gnu case */
    531 	if (argv[3][0] == '\0' && mimic_gnu) {
    532 		if (argc == 4 || argv[4] == NULL)
    533 			return;
    534 		else
    535 			pbstr(argv[4]);
    536 	}
    537 
    538 	source = mimic_gnu ? twiddle(argv[3]) : argv[3];
    539 	error = regcomp(&re, source, REG_EXTENDED);
    540 	if (error != 0)
    541 		exit_regerror(error, &re, source);
    542 
    543 	pmatch = xreallocarray(NULL, re.re_nsub+1, sizeof(regmatch_t), NULL);
    544 	if (argc == 4 || argv[4] == NULL)
    545 		do_regexpindex(argv[2], &re, source, pmatch);
    546 	else
    547 		do_regexp(argv[2], &re, source, argv[4], pmatch);
    548 	free(pmatch);
    549 	regfree(&re);
    550 }
    551 
    552 void
    553 doformat(const char *argv[], int argc)
    554 {
    555 	const char *format = argv[2];
    556 	int pos = 3;
    557 	int left_padded;
    558 	long width;
    559 	size_t l;
    560 	const char *thisarg;
    561 	char temp[2];
    562 	size_t extra;
    563 
    564 	while (*format != 0) {
    565 		if (*format != '%') {
    566 			addchar(*format++);
    567 			continue;
    568 		}
    569 
    570 		format++;
    571 		if (*format == '%') {
    572 			addchar(*format++);
    573 			continue;
    574 		}
    575 		if (*format == 0) {
    576 			addchar('%');
    577 			break;
    578 		}
    579 
    580 		if (*format == '*') {
    581 			format++;
    582 			if (pos >= argc)
    583 				m4errx(1,
    584 				    "Format with too many format specifiers.");
    585 			width = strtol(argv[pos++], NULL, 10);
    586 		} else {
    587 			char *eformat;
    588 			width = strtol(format, &eformat, 10);
    589 			format = eformat;
    590 		}
    591 		if (width < 0) {
    592 			left_padded = 1;
    593 			width = -width;
    594 		} else {
    595 			left_padded = 0;
    596 		}
    597 		if (*format == '.') {
    598 			format++;
    599 			if (*format == '*') {
    600 				format++;
    601 				if (pos >= argc)
    602 					m4errx(1,
    603 					    "Format with too many format specifiers.");
    604 				extra = strtol(argv[pos++], NULL, 10);
    605 			} else {
    606 				char *eformat;
    607 				extra = strtol(format, &eformat, 10);
    608 				format = eformat;
    609 			}
    610 		} else {
    611 			extra = SIZE_MAX;
    612 		}
    613 		if (pos >= argc)
    614 			m4errx(1, "Format with too many format specifiers.");
    615 		switch(*format) {
    616 		case 's':
    617 			thisarg = argv[pos++];
    618 			break;
    619 		case 'c':
    620 			temp[0] = (int)strtoul(argv[pos++], NULL, 10);
    621 			temp[1] = 0;
    622 			thisarg = temp;
    623 			break;
    624 		default:
    625 			m4errx(1, "Unsupported format specification: %s.",
    626 			    argv[2]);
    627 		}
    628 		format++;
    629 		l = strlen(thisarg);
    630 		if (l > extra)
    631 			l = extra;
    632 		if (!left_padded) {
    633 			while (l < (size_t)width--)
    634 				addchar(' ');
    635 		}
    636 		addchars(thisarg, l);
    637 		if (left_padded) {
    638 			while (l < (size_t)width--)
    639 				addchar(' ');
    640 		}
    641 	}
    642 	pbstr(getstring());
    643 }
    644 
    645 void
    646 doesyscmd(const char *cmd)
    647 {
    648 	int p[2];
    649 	pid_t cpid;
    650 	const char *argv[4];
    651 	int cc;
    652 	int status;
    653 
    654 	/* Follow gnu m4 documentation: first flush buffers. */
    655 	fflush(NULL);
    656 
    657 	argv[0] = "sh";
    658 	argv[1] = "-c";
    659 	argv[2] = cmd;
    660 	argv[3] = NULL;
    661 
    662 	/* Just set up standard output, share stderr and stdin with m4 */
    663 	if (pipe(p) == -1)
    664 		err(1, "bad pipe");
    665 	switch(cpid = fork()) {
    666 	case -1:
    667 		err(1, "bad fork");
    668 		/* NOTREACHED */
    669 	case 0:
    670 		(void) close(p[0]);
    671 		(void) dup2(p[1], 1);
    672 		(void) close(p[1]);
    673 		execv(_PATH_BSHELL, __UNCONST(argv));
    674 		exit(1);
    675 	default:
    676 		/* Read result in two stages, since m4's buffer is
    677 		 * pushback-only. */
    678 		(void) close(p[1]);
    679 		do {
    680 			char result[BUFSIZE];
    681 			cc = read(p[0], result, sizeof result);
    682 			if (cc > 0)
    683 				addchars(result, cc);
    684 		} while (cc > 0 || (cc == -1 && errno == EINTR));
    685 
    686 		(void) close(p[0]);
    687 		while (waitpid(cpid, &status, 0) == -1) {
    688 			if (errno != EINTR)
    689 				break;
    690 		}
    691 		pbstr(getstring());
    692 	}
    693 }
    694 
    695 void
    696 getdivfile(const char *name)
    697 {
    698 	FILE *f;
    699 	int c;
    700 
    701 	f = fopen(name, "r");
    702 	if (!f)
    703 		return;
    704 
    705 	while ((c = getc(f))!= EOF)
    706 		putc(c, active);
    707 	(void) fclose(f);
    708 }
    709 
    710 #ifdef REAL_FREEZE
    711 void
    712 freeze_state(const char *fname)
    713 {
    714 	FILE *f;
    715 
    716 	if ((f = fopen(fname, "wb")) == NULL)
    717 		m4errx(EXIT_FAILURE, "Can't open output freeze file `%s' (%s)",
    718 		    fname, strerror(errno));
    719 	fprintf(f, "# This is a frozen state file generated by %s\nV1\n",
    720 	    getprogname());
    721 	fprintf(f, "Q%zu,%zu\n%s%s\n", strlen(lquote), strlen(rquote),
    722 	    lquote, rquote);
    723 	fprintf(f, "C%zu,%zu\n%s%s\n", strlen(scommt), strlen(ecommt),
    724 	    scommt, ecommt);
    725 	dump_state(f);
    726 	/* XXX: diversions? */
    727 	fprintf(f, "D-1,0\n");
    728 	fprintf(f, "# End of frozen state file\n");
    729 	fclose(f);
    730 }
    731 
    732 void
    733 thaw_state(const char *fname)
    734 {
    735 	char *name = NULL;
    736 	size_t nl, namelen = 0;
    737 	char *defn = NULL;
    738 	size_t dl, defnlen = 0;
    739 	size_t lineno = 0;
    740 	char line[1024], *ptr, type;
    741 	FILE *f;
    742 
    743 	if ((f = fopen(fname, "rb")) == NULL)
    744 		m4errx(EXIT_FAILURE, "Can't open frozen file `%s' (%s)",
    745 		    fname, strerror(errno));
    746 
    747 #define GET() if (fgets(line, (int)sizeof(line), f) == NULL) goto out
    748 #define GETSTR(s, l) if (fread(s, 1, l, f) != l) goto out; else s[l] = '\0'
    749 
    750 	GET();	/* comment */
    751 	GET();	/* version */
    752 	if ((ptr = strrchr(line, '\n')) != NULL)
    753 		*ptr = '\0';
    754 	if (strcmp(line, "V1") != 0)
    755 		m4errx(EXIT_FAILURE, "Bad frozen version `%s'", line);
    756 
    757 	for (;;) {
    758 		GET();
    759 		lineno++;
    760 		switch (*line) {
    761 		case '\n':
    762 			continue;
    763 		case '#':
    764 			free(name);
    765 			free(defn);
    766 			fclose(f);
    767 			return;
    768 		default:
    769 			if (sscanf(line, "%c%zu,%zu\n", &type, &nl, &dl) != 3)
    770 				m4errx(EXIT_FAILURE, "%s, %zu: Bad line `%s'",
    771 				    fname, lineno, line);
    772 			break;
    773 		}
    774 
    775 		switch (type) {
    776 		case 'Q':
    777 			if (nl >= sizeof(lquote) || dl >= sizeof(rquote))
    778 				m4errx(EXIT_FAILURE, "%s, %zu: Quote too long",
    779 				    fname, lineno);
    780 			GETSTR(lquote, nl);
    781 			GETSTR(rquote, dl);
    782 			break;
    783 
    784 		case 'C':
    785 			if (nl >= sizeof(scommt) || dl >= sizeof(ecommt))
    786 				m4errx(EXIT_FAILURE, "%s, %zu: Comment too long",
    787 				    fname, lineno);
    788 			GETSTR(scommt, nl);
    789 			GETSTR(ecommt, dl);
    790 			break;
    791 
    792 		case 'T':
    793 		case 'F':
    794 			if (nl >= namelen)
    795 				name = xrealloc(name, namelen = nl + 1,
    796 					"name grow");
    797 			if (dl >= defnlen)
    798 				defn = xrealloc(defn, defnlen = dl + 1,
    799 					"defn grow");
    800 			GETSTR(name, nl);
    801 			GETSTR(defn, dl);
    802 			macro_pushdef(name, defn);
    803 			break;
    804 
    805 		case 'D':
    806 			/* XXX: Not implemented */
    807 			break;
    808 
    809 		default:
    810 			m4errx(EXIT_FAILURE, "%s, %zu: Unknown type %c",
    811 			    fname, lineno,type);
    812 		}
    813 	}
    814 out:
    815 	m4errx(EXIT_FAILURE, "Unexpected end of file in `%s'", fname);
    816 }
    817 #endif
    818