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