1 /* $NetBSD: show.c,v 1.60 2026/04/18 09:37:51 kre Exp $ */ 2 3 /*- 4 * Copyright (c) 1991, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Copyright (c) 2017 The NetBSD Foundation, Inc. All rights reserved. 8 * 9 * This code is derived from software contributed to Berkeley by 10 * Kenneth Almquist. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #include <sys/cdefs.h> 38 #ifndef lint 39 #if 0 40 static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; 41 #else 42 __RCSID("$NetBSD: show.c,v 1.60 2026/04/18 09:37:51 kre Exp $"); 43 #endif 44 #endif /* not lint */ 45 46 #include <stdio.h> 47 #include <stdarg.h> 48 #include <stdlib.h> 49 #include <unistd.h> 50 #include <fcntl.h> 51 #include <errno.h> 52 #include <limits.h> 53 54 #include <sys/types.h> 55 #include <sys/uio.h> 56 57 #include "shell.h" 58 #include "parser.h" 59 #include "nodes.h" 60 #include "mystring.h" 61 #include "show.h" 62 #include "options.h" 63 #include "redir.h" 64 #include "error.h" 65 #include "syntax.h" 66 #include "input.h" 67 #include "output.h" 68 #include "var.h" 69 #include "builtins.h" 70 71 #define DEFINE_NODENAMES 72 #include "nodenames.h" /* does almost nothing if !defined(DEBUG) */ 73 74 #define TR_STD_WIDTH 60 /* tend to fold lines wider than this */ 75 #define TR_IOVECS 10 /* number of lines or trace (max) / write */ 76 77 typedef struct traceinfo { 78 int tfd; /* file descriptor for open trace file */ 79 int nxtiov; /* the buffer we should be writing to */ 80 char lastc; /* the last non-white character output */ 81 uint8_t supr; /* char classes to suppress after \n */ 82 pid_t pid; /* process id of process that opened that file */ 83 size_t llen; /* number of chars in current output line */ 84 size_t blen; /* chars used in current buffer being filled */ 85 char * tracefile; /* name of the tracefile */ 86 struct iovec lines[TR_IOVECS]; /* filled, flling, pending buffers */ 87 } TFILE; 88 89 /* These are auto turned off when non white space is printed */ 90 #define SUP_NL 0x01 /* don't print \n */ 91 #define SUP_SP 0x03 /* suppress spaces */ 92 #define SUP_WSP 0x04 /* suppress all white space */ 93 94 #ifdef DEBUG /* from here to end of file ... */ 95 96 TFILE tracedata, *tracetfile; 97 FILE *tracefile; /* just for histedit */ 98 99 uint64_t DFlags; /* currently enabled debug flags */ 100 int ShNest; /* depth of shell (internal) nesting */ 101 102 static void shtree(union node *, int, int, int, TFILE *); 103 static void shcmd(union node *, TFILE *); 104 static void shsubsh(union node *, TFILE *); 105 static void shredir(union node *, TFILE *, int); 106 static void sharg(union node *, TFILE *); 107 static void indent(int, TFILE *); 108 static void trstring(const char *); 109 static void trace_putc(char, TFILE *); 110 static void trace_puts(const char *, TFILE *); 111 static void trace_flush(TFILE *, int); 112 static char *trace_id(TFILE *); 113 static void trace_fd_swap(int, int); 114 115 inline static int trlinelen(TFILE *); 116 117 118 /* 119 * These functions are the externally visible interface 120 * (but only for a DEBUG shell.) 121 */ 122 123 void 124 opentrace(void) 125 { 126 char *s; 127 int fd; 128 int i; 129 pid_t pid; 130 131 if (debug != 1) { 132 /* leave fd open because libedit might be using it */ 133 if (tracefile) 134 fflush(tracefile); 135 if (tracetfile) 136 trace_flush(tracetfile, 1); 137 return; 138 } 139 #if DBG_PID == 1 /* using old shell.h, old tracing method */ 140 DFlags = DBG_PID; /* just force DBG_PID on, and leave it ... */ 141 #endif 142 pid = getpid(); 143 if (asprintf(&s, "trace.%jd", (intmax_t)pid) <= 0) { 144 debug = 0; 145 error("Cannot asprintf tracefilename"); 146 }; 147 148 fd = open(s, O_WRONLY|O_APPEND|O_CREAT, 0666); 149 if (fd == -1) { 150 debug = 0; 151 error("Can't open tracefile: %s (%s)\n", s, strerror(errno)); 152 } 153 fd = to_upper_fd(fd); 154 if (fd <= 2) { 155 (void) close(fd); 156 debug = 0; 157 error("Attempt to use fd %d as tracefile thwarted\n", fd); 158 } 159 register_sh_fd(fd, trace_fd_swap); 160 161 /* 162 * This stuff is just so histedit has a FILE * to use 163 */ 164 if (tracefile) 165 (void) fclose(tracefile); /* also closes tfd */ 166 tracefile = fdopen(fd, "ae"); /* don't care if it is NULL */ 167 if (tracefile) /* except here... */ 168 setlinebuf(tracefile); 169 170 /* 171 * Now the real tracing setup 172 */ 173 if (tracedata.tfd > 0 && tracedata.tfd != fd) 174 (void) close(tracedata.tfd); /* usually done by fclose() */ 175 176 tracedata.tfd = fd; 177 tracedata.pid = pid; 178 tracedata.nxtiov = 0; 179 tracedata.blen = 0; 180 tracedata.llen = 0; 181 tracedata.lastc = '\0'; 182 tracedata.supr = SUP_NL | SUP_WSP; 183 184 #define replace(f, v) do { \ 185 if (tracedata.f != NULL) \ 186 free(tracedata.f); \ 187 tracedata.f = v; \ 188 } while (0) 189 190 replace(tracefile, s); 191 192 for (i = 0; i < TR_IOVECS; i++) { 193 replace(lines[i].iov_base, NULL); 194 tracedata.lines[i].iov_len = 0; 195 } 196 197 #undef replace 198 199 tracetfile = &tracedata; 200 201 trace_puts("\nTracing started.\n", tracetfile); 202 } 203 204 void 205 trace(const char *fmt, ...) 206 { 207 va_list va; 208 char *s; 209 210 if (debug != 1 || !tracetfile) 211 return; 212 va_start(va, fmt); 213 (void) vasprintf(&s, fmt, va); 214 va_end(va); 215 216 trace_puts(s, tracetfile); 217 free(s); 218 if (tracetfile->llen == 0) 219 trace_flush(tracetfile, 0); 220 } 221 222 void 223 tracev(const char *fmt, va_list va) 224 { 225 va_list ap; 226 char *s; 227 228 if (debug != 1 || !tracetfile) 229 return; 230 va_copy(ap, va); 231 (void) vasprintf(&s, fmt, ap); 232 va_end(ap); 233 234 trace_puts(s, tracetfile); 235 free(s); 236 if (tracetfile->llen == 0) 237 trace_flush(tracetfile, 0); 238 } 239 240 241 void 242 trputs(const char *s) 243 { 244 if (debug != 1 || !tracetfile) 245 return; 246 trace_puts(s, tracetfile); 247 } 248 249 void 250 trputc(int c) 251 { 252 if (debug != 1 || !tracetfile) 253 return; 254 trace_putc(c, tracetfile); 255 } 256 257 void 258 showtree(union node *n) 259 { 260 TFILE *fp; 261 262 if ((fp = tracetfile) == NULL) 263 return; 264 265 trace_puts("showtree(", fp); 266 if (n == NULL) 267 trace_puts("NULL", fp); 268 else if (n == NEOF) 269 trace_puts("NEOF", fp); 270 else 271 trace("%p", n); 272 trace_puts(") called\n", fp); 273 if (n != NULL && n != NEOF) 274 shtree(n, 1, 1, 1, fp); 275 } 276 277 void 278 trargs(char **ap) 279 { 280 if (debug != 1 || !tracetfile) 281 return; 282 while (*ap) { 283 trstring(*ap++); 284 if (*ap) 285 trace_putc(' ', tracetfile); 286 } 287 trace_putc('\n', tracetfile); 288 } 289 290 void 291 trargstr(union node *n) 292 { 293 sharg(n, tracetfile); 294 } 295 296 297 /* 298 * Beyond here we just have the implementation of all of that 299 */ 300 301 302 inline static int 303 trlinelen(TFILE * fp) 304 { 305 return fp->llen; 306 } 307 308 static void 309 shtree(union node *n, int ind, int ilvl, int nl, TFILE *fp) 310 { 311 struct nodelist *lp; 312 const char *s; 313 314 if (n == NULL) { 315 if (nl) 316 trace_putc('\n', fp); 317 return; 318 } 319 320 indent(ind, fp); 321 switch (n->type) { 322 case NSEMI: 323 s = NULL; 324 goto binop; 325 case NAND: 326 s = " && "; 327 goto binop; 328 case NOR: 329 s = " || "; 330 binop: 331 shtree(n->nbinary.ch1, 0, ilvl, 0, fp); 332 if (s != NULL) 333 trace_puts(s, fp); 334 if (trlinelen(fp) >= TR_STD_WIDTH) { 335 trace_putc('\n', fp); 336 indent(ind < 0 ? 2 : ind + 1, fp); 337 } else if (s == NULL) { 338 if (fp->lastc != '&') 339 trace_puts("; ", fp); 340 else 341 trace_putc(' ', fp); 342 } 343 shtree(n->nbinary.ch2, 0, ilvl, nl, fp); 344 break; 345 case NCMD: 346 shcmd(n, fp); 347 if (n->ncmd.backgnd) 348 trace_puts(" &", fp); 349 if (nl && trlinelen(fp) > 0) 350 trace_putc('\n', fp); 351 break; 352 case NPIPE: 353 for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { 354 shtree(lp->n, 0, ilvl, 0, fp); 355 if (lp->next) { 356 trace_puts(" |", fp); 357 if (trlinelen(fp) >= TR_STD_WIDTH) { 358 trace_putc('\n', fp); 359 indent((ind < 0 ? ilvl : ind) + 1, fp); 360 } else 361 trace_putc(' ', fp); 362 } 363 } 364 if (n->npipe.backgnd) 365 trace_puts(" &", fp); 366 if (nl || trlinelen(fp) >= TR_STD_WIDTH) 367 trace_putc('\n', fp); 368 break; 369 case NBACKGND: 370 case NSUBSHELL: 371 shsubsh(n, fp); 372 if (n->type == NBACKGND) 373 trace_puts(" &", fp); 374 if (nl && trlinelen(fp) > 0) 375 trace_putc('\n', fp); 376 break; 377 case NDEFUN: 378 trace_puts(n->narg.text, fp); 379 trace_puts("() {\n", fp); 380 indent(ind, fp); 381 shtree(n->narg.next, (ind < 0 ? ilvl : ind) + 1, ilvl+1, 1, fp); 382 indent(ind, fp); 383 trace_puts("}\n", fp); 384 break; 385 case NDNOT: 386 trace_puts("! ", fp); 387 /* FALLTHROUGH */ 388 case NNOT: 389 trace_puts("! ", fp); 390 shtree(n->nnot.com, -1, ilvl, nl, fp); 391 break; 392 case NREDIR: 393 shtree(n->nredir.n, -1, ilvl, 0, fp); 394 shredir(n->nredir.redirect, fp, n->nredir.n == NULL); 395 if (nl) 396 trace_putc('\n', fp); 397 break; 398 399 case NIF: 400 itsif: 401 trace_puts("if ", fp); 402 shtree(n->nif.test, -1, ilvl, 0, fp); 403 if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { 404 if (fp->lastc != '&') 405 trace_puts(" ;", fp); 406 } else 407 indent(ilvl, fp); 408 trace_puts(" then ", fp); 409 if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) 410 indent(ilvl+1, fp); 411 shtree(n->nif.ifpart, -1, ilvl + 1, 0, fp); 412 if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { 413 if (fp->lastc != '&') 414 trace_puts(" ;", fp); 415 } else 416 indent(ilvl, fp); 417 if (n->nif.elsepart && n->nif.elsepart->type == NIF) { 418 if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) 419 indent(ilvl, fp); 420 n = n->nif.elsepart; 421 trace_puts(" el", fp); 422 goto itsif; 423 } 424 if (n->nif.elsepart) { 425 if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) 426 indent(ilvl+1, fp); 427 trace_puts(" else ", fp); 428 shtree(n->nif.elsepart, -1, ilvl + 1, 0, fp); 429 if (fp->lastc != '&') 430 trace_puts(" ;", fp); 431 } 432 trace_puts(" fi", fp); 433 if (nl) 434 trace_putc('\n', fp); 435 break; 436 437 case NWHILE: 438 trace_puts("while ", fp); 439 goto aloop; 440 case NUNTIL: 441 trace_puts("until ", fp); 442 aloop: 443 shtree(n->nbinary.ch1, -1, ilvl, 0, fp); 444 if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { 445 if (fp->lastc != '&') 446 trace_puts(" ;", fp); 447 } else 448 trace_putc('\n', fp); 449 trace_puts(" do ", fp); 450 shtree(n->nbinary.ch1, -1, ilvl + 1, 1, fp); 451 trace_puts(" done ", fp); 452 if (nl) 453 trace_putc('\n', fp); 454 break; 455 456 case NFOR: 457 trace_puts("for ", fp); 458 trace_puts(n->nfor.var, fp); 459 if (n->nfor.args) { 460 union node *argp; 461 462 trace_puts(" in ", fp); 463 for (argp = n->nfor.args; argp; argp=argp->narg.next) { 464 sharg(argp, fp); 465 trace_putc(' ', fp); 466 } 467 if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { 468 if (fp->lastc != '&') 469 trace_putc(';', fp); 470 } else 471 trace_putc('\n', fp); 472 } 473 trace_puts(" do ", fp); 474 shtree(n->nfor.body, -1, ilvl + 1, 0, fp); 475 if (fp->lastc != '&') 476 trace_putc(';', fp); 477 trace_puts(" done", fp); 478 if (nl) 479 trace_putc('\n', fp); 480 break; 481 482 case NCASE: 483 trace_puts("case ", fp); 484 sharg(n->ncase.expr, fp); 485 trace_puts(" in", fp); 486 if (nl) 487 trace_putc('\n', fp); 488 { 489 union node *cp; 490 491 for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { 492 union node *patp; 493 494 if (nl || trlinelen(fp) > TR_STD_WIDTH - 16) 495 indent(ilvl, fp); 496 else 497 trace_putc(' ', fp); 498 trace_putc('(', fp); 499 patp = cp->nclist.pattern; 500 while (patp != NULL) { 501 trace_putc(' ', fp); 502 sharg(patp, fp); 503 trace_putc(' ', fp); 504 if ((patp = patp->narg.next) != NULL) 505 trace_putc('|', fp); 506 } 507 trace_putc(')', fp); 508 if (nl) 509 indent(ilvl + 1, fp); 510 else 511 trace_putc(' ', fp); 512 shtree(cp->nclist.body, -1, ilvl+2, 0, fp); 513 if (cp->type == NCLISTCONT) 514 trace_puts(" ;&", fp); 515 else 516 trace_puts(" ;;", fp); 517 if (nl) 518 trace_putc('\n', fp); 519 } 520 } 521 if (nl) { 522 trace_putc('\n', fp); 523 indent(ind, fp); 524 } else 525 trace_putc(' ', fp); 526 trace_puts("esac", fp); 527 if (nl) 528 trace_putc('\n', fp); 529 break; 530 531 default: { 532 char *str; 533 534 asprintf(&str, "<node type %d [%s]>", n->type, 535 NODETYPENAME(n->type)); 536 trace_puts(str, fp); 537 free(str); 538 if (nl) 539 trace_putc('\n', fp); 540 } 541 break; 542 } 543 } 544 545 546 static void 547 shcmd(union node *cmd, TFILE *fp) 548 { 549 union node *np; 550 int first; 551 552 first = 1; 553 for (np = cmd->ncmd.args ; np ; np = np->narg.next) { 554 if (! first) 555 trace_putc(' ', fp); 556 sharg(np, fp); 557 first = 0; 558 } 559 shredir(cmd->ncmd.redirect, fp, first); 560 } 561 562 static void 563 shsubsh(union node *cmd, TFILE *fp) 564 { 565 trace_puts(" ( ", fp); 566 shtree(cmd->nredir.n, -1, 3, 0, fp); 567 trace_puts(" ) ", fp); 568 shredir(cmd->ncmd.redirect, fp, 1); 569 } 570 571 static void 572 shredir(union node *np, TFILE *fp, int first) 573 { 574 const char *s; 575 int dftfd; 576 char buf[106]; 577 578 for ( ; np ; np = np->nfile.next) { 579 if (! first) 580 trace_putc(' ', fp); 581 switch (np->nfile.type) { 582 case NTO: s = ">"; dftfd = 1; break; 583 case NCLOBBER: s = ">|"; dftfd = 1; break; 584 case NAPPEND: s = ">>"; dftfd = 1; break; 585 case NTOFD: s = ">&"; dftfd = 1; break; 586 case NFROM: s = "<"; dftfd = 0; break; 587 case NFROMFD: s = "<&"; dftfd = 0; break; 588 case NFROMTO: s = "<>"; dftfd = 0; break; 589 case NXHERE: /* FALLTHROUGH */ 590 case NHERE: s = "<<"; dftfd = 0; break; 591 default: s = "*error*"; dftfd = 0; break; 592 } 593 if (np->nfile.fd != dftfd) { 594 sprintf(buf, "%d", np->nfile.fd); 595 trace_puts(buf, fp); 596 } 597 trace_puts(s, fp); 598 if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { 599 if (np->ndup.vname) 600 sharg(np->ndup.vname, fp); 601 else { 602 if (np->ndup.dupfd < 0) 603 trace_puts("-", fp); 604 else { 605 sprintf(buf, "%d", np->ndup.dupfd); 606 trace_puts(buf, fp); 607 } 608 } 609 } else 610 if (np->nfile.type == NHERE || np->nfile.type == NXHERE) { 611 if (np->nfile.type == NHERE) 612 trace_putc('\\', fp); 613 trace_puts("!!!\n", fp); 614 s = np->nhere.doc->narg.text; 615 if (strlen(s) > 100) { 616 memmove(buf, s, 100); 617 buf[100] = '\0'; 618 strcat(buf, " ...\n"); 619 s = buf; 620 } 621 trace_puts(s, fp); 622 trace_puts("!!! ", fp); 623 } else { 624 sharg(np->nfile.fname, fp); 625 } 626 first = 0; 627 } 628 } 629 630 static void 631 sharg(union node *arg, TFILE *fp) 632 { 633 char *p, *s; 634 struct nodelist *bqlist; 635 int subtype = 0; 636 int quoted = 0; 637 638 if (arg->type != NARG) { 639 asprintf(&s, "<node type %d> ! NARG\n", arg->type); 640 trace_puts(s, fp); 641 abort(); /* no need to free s, better not to */ 642 } 643 644 bqlist = arg->narg.backquote; 645 for (p = arg->narg.text ; *p ; p++) { 646 switch (*p) { 647 case CTLESC: 648 if (BASESYNTAX[(int)p[1]] != CCTL) 649 trace_putc('\\', fp); 650 trace_putc(*++p, fp); 651 break; 652 653 case CTLNONL: 654 trace_putc('\\', fp); 655 trace_putc('\n', fp); 656 break; 657 658 case CTLVAR: 659 subtype = *++p; 660 if (!quoted != !(subtype & VSQUOTE)) 661 trace_putc('"', fp); 662 trace_putc('$', fp); 663 trace_putc('{', fp); /*}*/ 664 if ((subtype & VSTYPE) == VSLENGTH) 665 trace_putc('#', fp); 666 667 while (*++p != '=') 668 trace_putc(*p, fp); 669 670 if (subtype & VSNUL) 671 trace_putc(':', fp); 672 673 switch (subtype & VSTYPE) { 674 case VSNORMAL: 675 /* { */ 676 trace_putc('}', fp); 677 if (!quoted != !(subtype & VSQUOTE)) 678 trace_putc('"', fp); 679 break; 680 case VSMINUS: 681 trace_putc('-', fp); 682 break; 683 case VSPLUS: 684 trace_putc('+', fp); 685 break; 686 case VSQUESTION: 687 trace_putc('?', fp); 688 break; 689 case VSASSIGN: 690 trace_putc('=', fp); 691 break; 692 case VSTRIMLEFTMAX: 693 trace_putc('#', fp); 694 /* FALLTHROUGH */ 695 case VSTRIMLEFT: 696 trace_putc('#', fp); 697 break; 698 case VSTRIMRIGHTMAX: 699 trace_putc('%', fp); 700 /* FALLTHROUGH */ 701 case VSTRIMRIGHT: 702 trace_putc('%', fp); 703 break; 704 case VSLENGTH: 705 break; 706 default: { 707 char str[32]; 708 709 snprintf(str, sizeof str, 710 "<subtype %d>", subtype); 711 trace_puts(str, fp); 712 } 713 break; 714 } 715 break; 716 case CTLENDVAR: 717 /* { */ 718 trace_putc('}', fp); 719 if (!quoted != !(subtype & VSQUOTE)) 720 trace_putc('"', fp); 721 subtype = 0; 722 break; 723 724 case CTLBACKQ|CTLQUOTE: 725 if (!quoted) 726 trace_putc('"', fp); 727 /* FALLTHRU */ 728 case CTLBACKQ: 729 trace_putc('$', fp); 730 trace_putc('(', fp); 731 if (bqlist) { 732 shtree(bqlist->n, -1, 3, 0, fp); 733 bqlist = bqlist->next; 734 } else 735 trace_puts("???", fp); 736 trace_putc(')', fp); 737 if (!quoted && *p == (CTLBACKQ|CTLQUOTE)) 738 trace_putc('"', fp); 739 break; 740 741 case CTLQUOTEMARK: 742 if (subtype != 0 || !quoted) { 743 trace_putc('"', fp); 744 quoted++; 745 } 746 break; 747 case CTLQUOTEEND: 748 trace_putc('"', fp); 749 quoted--; 750 break; 751 case CTLARI: 752 if (*p == ' ') 753 p++; 754 trace_puts("$(( ", fp); 755 break; 756 case CTLENDARI: 757 trace_puts(" ))", fp); 758 break; 759 760 default: 761 if (*p == '$') 762 trace_putc('\\', fp); 763 trace_putc(*p, fp); 764 break; 765 } 766 } 767 if (quoted) 768 trace_putc('"', fp); 769 } 770 771 772 static void 773 indent(int amount, TFILE *fp) 774 { 775 int i; 776 777 if (amount <= 0) 778 return; 779 780 amount <<= 2; /* indent slots -> chars */ 781 782 i = trlinelen(fp); 783 fp->supr = SUP_NL; 784 if (i > amount) { 785 trace_putc('\n', fp); 786 i = 0; 787 } 788 fp->supr = 0; 789 for (; i < amount - 7 ; i++) { 790 trace_putc('\t', fp); 791 i |= 7; 792 } 793 while (i < amount) { 794 trace_putc(' ', fp); 795 i++; 796 } 797 fp->supr = SUP_WSP; 798 } 799 800 static void 801 trace_putc(char c, TFILE *fp) 802 { 803 char *p; 804 805 if (c == '\0') 806 return; 807 if (debug == 0 || fp == NULL) 808 return; 809 810 if (fp->llen == 0) { 811 if (fp->blen != 0) 812 abort(); 813 814 if ((fp->supr & SUP_NL) && c == '\n') 815 return; 816 if ((fp->supr & (SUP_WSP|SUP_SP)) && c == ' ') 817 return; 818 if ((fp->supr & SUP_WSP) && c == '\t') 819 return; 820 821 if (fp->nxtiov >= TR_IOVECS - 1) /* should be rare */ 822 trace_flush(fp, 0); 823 824 p = trace_id(fp); 825 if (p != NULL) { 826 fp->lines[fp->nxtiov].iov_base = p; 827 fp->lines[fp->nxtiov].iov_len = strlen(p); 828 fp->nxtiov++; 829 } 830 } else if (fp->blen && fp->blen >= fp->lines[fp->nxtiov].iov_len) { 831 fp->blen = 0; 832 if (++fp->nxtiov >= TR_IOVECS) 833 trace_flush(fp, 0); 834 } 835 836 if (fp->lines[fp->nxtiov].iov_len == 0) { 837 p = (char *)malloc(2 * TR_STD_WIDTH); 838 if (p == NULL) { 839 trace_flush(fp, 1); 840 debug = 0; 841 return; 842 } 843 *p = '\0'; 844 fp->lines[fp->nxtiov].iov_base = p; 845 fp->lines[fp->nxtiov].iov_len = 2 * TR_STD_WIDTH; 846 fp->blen = 0; 847 } 848 849 p = (char *)fp->lines[fp->nxtiov].iov_base + fp->blen++; 850 *p++ = c; 851 *p = 0; 852 853 if (c != ' ' && c != '\t' && c != '\n') { 854 fp->lastc = c; 855 fp->supr = 0; 856 } 857 858 if (c == '\n') { 859 fp->lines[fp->nxtiov++].iov_len = fp->blen; 860 fp->blen = 0; 861 fp->llen = 0; 862 fp->supr |= SUP_NL; 863 return; 864 } 865 866 if (c == '\t') 867 fp->llen |= 7; 868 fp->llen++; 869 } 870 871 void 872 trace_flush(TFILE *fp, int all) 873 { 874 int niov, i; 875 ssize_t written; 876 877 niov = fp->nxtiov; 878 if (all && fp->blen > 0) { 879 fp->lines[niov].iov_len = fp->blen; 880 fp->blen = 0; 881 fp->llen = 0; 882 niov++; 883 } 884 if (niov == 0) 885 return; 886 if (fp->blen > 0 && --niov == 0) 887 return; 888 written = writev(fp->tfd, fp->lines, niov); 889 for (i = 0; i < niov; i++) { 890 free(fp->lines[i].iov_base); 891 fp->lines[i].iov_base = NULL; 892 fp->lines[i].iov_len = 0; 893 } 894 if (written == -1) { 895 if (fp->blen > 0) { 896 free(fp->lines[niov].iov_base); 897 fp->lines[niov].iov_base = NULL; 898 fp->lines[niov].iov_len = 0; 899 } 900 debug = 0; 901 fp->blen = 0; 902 fp->llen = 0; 903 return; 904 } 905 if (fp->blen > 0) { 906 fp->lines[0].iov_base = fp->lines[niov].iov_base; 907 fp->lines[0].iov_len = fp->lines[niov].iov_len; 908 fp->lines[niov].iov_base = NULL; 909 fp->lines[niov].iov_len = 0; 910 } 911 fp->nxtiov = 0; 912 } 913 914 void 915 trace_puts(const char *s, TFILE *fp) 916 { 917 char c; 918 919 while ((c = *s++) != '\0') 920 trace_putc(c, fp); 921 } 922 923 inline static char * 924 trace_id(TFILE *tf) 925 { 926 int i; 927 char indent[16]; 928 char *p; 929 int lno; 930 char c; 931 932 if (DFlags & DBG_NEST) { 933 if ((unsigned)ShNest >= sizeof indent - 1) { 934 (void) snprintf(indent, sizeof indent, 935 "### %*d ###", (int)(sizeof indent) - 9, ShNest); 936 p = strchr(indent, '\0'); 937 } else { 938 p = indent; 939 for (i = 0; i < 6; i++) 940 *p++ = (i < ShNest) ? '#' : ' '; 941 while (i++ < ShNest && p < &indent[sizeof indent - 1]) 942 *p++ = '#'; 943 *p = '\0'; 944 } 945 } else 946 indent[0] = '\0'; 947 948 /* 949 * If we are in the parser, then plinno is the current line 950 * number being processed (parser line no). 951 * If we are elsewhere, then line_number gives the source 952 * line of whatever we are currently doing (close enough.) 953 */ 954 if (parsing) 955 lno = plinno; 956 else 957 lno = line_number; 958 959 c = ((i = getpid()) == tf->pid) ? ':' : '='; 960 961 if (DFlags & DBG_PID) { 962 if (DFlags & DBG_LINE) 963 (void) asprintf(&p, "%5d%c%s\t%4d%c@\t", i, c, 964 indent, lno, parsing?'-':'+'); 965 else 966 (void) asprintf(&p, "%5d%c%s\t", i, c, indent); 967 return p; 968 } else if (DFlags & DBG_NEST) { 969 if (DFlags & DBG_LINE) 970 (void) asprintf(&p, "%c%s\t%4d%c@\t", c, indent, lno, 971 parsing?'-':'+'); 972 else 973 (void) asprintf(&p, "%c%s\t", c, indent); 974 return p; 975 } else if (DFlags & DBG_LINE) { 976 (void) asprintf(&p, "%c%4d%c@\t", c, lno, parsing?'-':'+'); 977 return p; 978 } 979 return NULL; 980 } 981 982 /* 983 * Used only from trargs(), which itself is used only to print 984 * arg lists (argv[]) either that passed into this shell, or 985 * the arg list about to be given to some other command (incl 986 * builtin, and function) as their argv[]. If any of the CTL* 987 * chars seem to appear, they really should be just treated as data, 988 * not special... But this is just debug, so, who cares! 989 */ 990 static void 991 trstring(const char *s) 992 { 993 const char *p; 994 char c; 995 TFILE *fp; 996 997 if (debug != 1 || !tracetfile) 998 return; 999 fp = tracetfile; 1000 trace_putc('"', fp); 1001 for (p = s ; *p ; p++) { 1002 switch (*p) { 1003 case '\n': c = 'n'; goto backslash; 1004 case '\t': c = 't'; goto backslash; 1005 case '\r': c = 'r'; goto backslash; 1006 case '"': c = '"'; goto backslash; 1007 case '\\': c = '\\'; goto backslash; 1008 case CTLESC: c = 'e'; goto backslash; 1009 case CTLVAR: c = 'v'; goto backslash; 1010 case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; 1011 case CTLBACKQ: c = 'q'; goto backslash; 1012 case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; 1013 backslash: trace_putc('\\', fp); 1014 trace_putc(c, fp); 1015 break; 1016 default: 1017 if (*p >= ' ' && *p <= '~') 1018 trace_putc(*p, fp); 1019 else { 1020 trace_putc('\\', fp); 1021 trace_putc(*p >> 6 & 03, fp); 1022 trace_putc(*p >> 3 & 07, fp); 1023 trace_putc(*p & 07, fp); 1024 } 1025 break; 1026 } 1027 } 1028 trace_putc('"', fp); 1029 } 1030 1031 /* 1032 * deal with the user "accidentally" picking our fd to use. 1033 */ 1034 static void 1035 trace_fd_swap(int from, int to) 1036 { 1037 if (tracetfile == NULL || from == to) 1038 return; 1039 1040 tracetfile->tfd = to; 1041 1042 /* 1043 * This is just so histedit has a stdio FILE* to use. 1044 */ 1045 if (tracefile) 1046 fclose(tracefile); 1047 tracefile = fdopen(to, "a"); 1048 if (tracefile) 1049 setlinebuf(tracefile); 1050 } 1051 1052 1053 static struct debug_flag { 1054 char label; 1055 uint64_t flag; 1056 } debug_flags[] = { 1057 { 'a', DBG_ARITH }, /* arithmetic ( $(( )) ) */ 1058 { 'c', DBG_CMDS }, /* command searching, ... */ 1059 { 'e', DBG_EVAL }, /* evaluation of the parse tree */ 1060 { 'f', DBG_REDIR }, /* file descriptors & redirections */ 1061 { 'g', DBG_MATCH }, /* pattern matching (glob) */ 1062 { 'h', DBG_HISTORY }, /* history & cmd line editing */ 1063 { 'i', DBG_INPUT }, /* shell input routines */ 1064 { 'j', DBG_JOBS }, /* job control, structures */ 1065 { 'l', DBG_LEXER }, /* lexical analysis */ 1066 { 'm', DBG_MEM }, /* memory management */ 1067 { 'o', DBG_OUTPUT }, /* output routines */ 1068 { 'p', DBG_PROCS }, /* process management, fork, ... */ 1069 { 'r', DBG_PARSE }, /* parser, lexer, ... tree building */ 1070 { 's', DBG_SIG }, /* signals and everything related */ 1071 { 't', DBG_TRAP }, /* traps & signals */ 1072 { 'v', DBG_VARS }, /* variables and parameters */ 1073 { 'w', DBG_WAIT }, /* waits for processes to finish */ 1074 { 'x', DBG_EXPAND }, /* word expansion ${} $() $(( )) */ 1075 { 'z', DBG_ERRS }, /* error control, jumps, cleanup */ 1076 1077 { '0', DBG_U0 }, /* ad-hoc temp debug flag #0 */ 1078 { '1', DBG_U1 }, /* ad-hoc temp debug flag #1 */ 1079 { '2', DBG_U2 }, /* ad-hoc temp debug flag #2 */ 1080 { '3', DBG_U3 }, /* ad-hoc temp debug flag #3 */ 1081 1082 { '@', DBG_LINE }, /* prefix trace lines with line# */ 1083 { '$', DBG_PID }, /* prefix trace lines with sh pid */ 1084 { '^', DBG_NEST }, /* show shell nesting level */ 1085 1086 /* alpha options only - but not DBG_LEXER */ 1087 { '_', DBG_PARSE | DBG_EVAL | DBG_EXPAND | DBG_JOBS | DBG_SIG | 1088 DBG_PROCS | DBG_REDIR | DBG_CMDS | DBG_ERRS | 1089 DBG_WAIT | DBG_TRAP | DBG_VARS | DBG_MEM | DBG_MATCH | 1090 DBG_INPUT | DBG_OUTPUT | DBG_ARITH | DBG_HISTORY }, 1091 1092 /* { '*', DBG_ALLVERBOSE }, is handled in the code */ 1093 1094 { '#', DBG_U0 | DBG_U1 | DBG_U2 | DBG_U3 }, 1095 1096 { 0, 0 } 1097 }; 1098 1099 void 1100 set_debug(const char * flags, int on) 1101 { 1102 char f; 1103 struct debug_flag *df; 1104 int verbose; 1105 1106 while ((f = *flags++) != '\0') { 1107 verbose = 0; 1108 if (is_upper(f)) { 1109 verbose = 1; 1110 f += 'a' - 'A'; 1111 } 1112 if (f == '*') 1113 f = '_', verbose = 1; 1114 if (f == '+') { 1115 if (*flags == '+') 1116 flags++, verbose=1; 1117 } 1118 1119 /* 1120 * Note: turning on any debug option also enables DBG_ALWAYS 1121 * turning on any verbose option also enables DBG_VERBOSE 1122 * Once enabled, those flags cannot be disabled. 1123 * (tracing can still be turned off with "set +o debug") 1124 */ 1125 for (df = debug_flags; df->label != '\0'; df++) { 1126 if (f == '+' || df->label == f) { 1127 if (on) { 1128 DFlags |= DBG_ALWAYS | df->flag; 1129 if (verbose) 1130 DFlags |= DBG_VERBOSE | 1131 (df->flag << DBG_VBOSE_SHIFT); 1132 } else { 1133 DFlags &= ~(df->flag<<DBG_VBOSE_SHIFT); 1134 if (!verbose) 1135 DFlags &= ~df->flag; 1136 } 1137 } 1138 } 1139 } 1140 } 1141 1142 1143 int 1144 debugcmd(int argc, char **argv) 1145 { 1146 if (argc == 1) { 1147 struct debug_flag *df; 1148 1149 out1fmt("Debug: %sabled. Flags: ", debug ? "en" : "dis"); 1150 for (df = debug_flags; df->label != '\0'; df++) { 1151 if (df->flag & (df->flag - 1)) 1152 continue; 1153 if (is_alpha(df->label) && 1154 (df->flag << DBG_VBOSE_SHIFT) & DFlags) 1155 out1c(df->label - ('a' - 'A')); 1156 else if (df->flag & DFlags) 1157 out1c(df->label); 1158 } 1159 out1c('\n'); 1160 return 0; 1161 } 1162 1163 while (*++argv) { 1164 if (**argv == '-') 1165 set_debug(*argv + 1, 1); 1166 else if (**argv == '+') 1167 set_debug(*argv + 1, 0); 1168 else 1169 return 1; 1170 } 1171 return 0; 1172 } 1173 1174 #endif /* DEBUG */ 1175