1 /*- 2 * Copyright (c) 1992 Diomidis Spinellis. 3 * Copyright (c) 1992 The Regents of the University of California. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * Diomidis Spinellis of Imperial College, University of London. 8 * 9 * %sccs.include.redist.c% 10 */ 11 12 #ifndef lint 13 static char sccsid[] = "@(#)process.c 5.10 (Berkeley) 12/02/92"; 14 #endif /* not lint */ 15 16 #include <sys/types.h> 17 #include <sys/stat.h> 18 #include <sys/ioctl.h> 19 #include <sys/uio.h> 20 21 #include <ctype.h> 22 #include <errno.h> 23 #include <fcntl.h> 24 #include <limits.h> 25 #include <regex.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <unistd.h> 30 31 #include "defs.h" 32 #include "extern.h" 33 34 static SPACE HS, PS, SS; 35 #define pd PS.deleted 36 #define ps PS.space 37 #define psl PS.len 38 #define hs HS.space 39 #define hsl HS.len 40 41 static inline int applies __P((struct s_command *)); 42 static void flush_appends __P((void)); 43 static void lputs __P((char *)); 44 static inline int regexec_e __P((regex_t *, const char *, int, int)); 45 static void regsub __P((SPACE *, char *, char *)); 46 static int substitute __P((struct s_command *)); 47 48 struct s_appends *appends; /* Array of pointers to strings to append. */ 49 static int appendx; /* Index into appends array. */ 50 int appendnum; /* Size of appends array. */ 51 52 static int lastaddr; /* Set by applies if last address of a range. */ 53 static int sdone; /* If any substitutes since last line input. */ 54 /* Iov structure for 'w' commands. */ 55 static struct iovec iov[2] = { NULL, 0, "\n", 1 }; 56 57 static regex_t *defpreg; 58 size_t maxnsub; 59 regmatch_t *match; 60 61 void 62 process() 63 { 64 struct s_command *cp; 65 SPACE tspace; 66 size_t len; 67 int r; 68 char oldc, *p; 69 70 for (linenum = 0; mf_fgets(&PS, REPLACE);) { 71 pd = 0; 72 cp = prog; 73 redirect: 74 while (cp != NULL) { 75 if (!applies(cp)) { 76 cp = cp->next; 77 continue; 78 } 79 switch (cp->code) { 80 case '{': 81 cp = cp->u.c; 82 goto redirect; 83 case 'a': 84 if (appendx >= appendnum) 85 appends = xrealloc(appends, 86 sizeof(struct s_appends) * 87 (appendnum *= 2)); 88 appends[appendx].type = AP_STRING; 89 appends[appendx].s = cp->t; 90 appendx++; 91 break; 92 case 'b': 93 cp = cp->u.c; 94 goto redirect; 95 case 'c': 96 pd = 1; 97 psl = 0; 98 if (cp->a2 == NULL || lastaddr) 99 (void)printf("%s", cp->t); 100 break; 101 case 'd': 102 pd = 1; 103 goto new; 104 case 'D': 105 if (pd) 106 goto new; 107 if ((p = strchr(ps, '\n')) == NULL) 108 pd = 1; 109 else { 110 psl -= (p - ps) - 1; 111 memmove(ps, p + 1, psl); 112 } 113 goto new; 114 case 'g': 115 cspace(&PS, hs, hsl, REPLACE); 116 break; 117 case 'G': 118 cspace(&PS, hs, hsl, APPENDNL); 119 break; 120 case 'h': 121 cspace(&HS, ps, psl, REPLACE); 122 break; 123 case 'H': 124 cspace(&HS, ps, psl, APPENDNL); 125 break; 126 case 'i': 127 (void)printf("%s", cp->t); 128 break; 129 case 'l': 130 lputs(ps); 131 break; 132 case 'n': 133 if (!nflag && !pd) 134 (void)printf("%s\n", ps); 135 flush_appends(); 136 r = mf_fgets(&PS, REPLACE); 137 #ifdef HISTORIC_PRACTICE 138 if (!r) 139 exit(0); 140 #endif 141 pd = 0; 142 break; 143 case 'N': 144 flush_appends(); 145 if (!mf_fgets(&PS, APPENDNL)) { 146 if (!nflag && !pd) 147 (void)printf("%s\n", ps); 148 exit(0); 149 } 150 break; 151 case 'p': 152 if (pd) 153 break; 154 (void)printf("%s\n", ps); 155 break; 156 case 'P': 157 if (pd) 158 break; 159 if ((p = strchr(ps, '\n')) != NULL) { 160 oldc = *p; 161 *p = '\0'; 162 } 163 (void)printf("%s\n", ps); 164 if (p != NULL) 165 *p = oldc; 166 break; 167 case 'q': 168 if (!nflag && !pd) 169 (void)printf("%s\n", ps); 170 flush_appends(); 171 exit(0); 172 case 'r': 173 if (appendx >= appendnum) 174 appends = xrealloc(appends, 175 sizeof(struct s_appends) * 176 (appendnum *= 2)); 177 appends[appendx].type = AP_FILE; 178 appends[appendx].s = cp->t; 179 appendx++; 180 break; 181 case 's': 182 sdone |= substitute(cp); 183 break; 184 case 't': 185 if (sdone) { 186 sdone = 0; 187 cp = cp->u.c; 188 goto redirect; 189 } 190 break; 191 case 'w': 192 if (pd) 193 break; 194 if (cp->u.fd == -1 && (cp->u.fd = open(cp->t, 195 O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, 196 DEFFILEMODE)) == -1) 197 err(FATAL, "%s: %s\n", 198 cp->t, strerror(errno)); 199 iov[0].iov_base = ps; 200 iov[0].iov_len = psl; 201 if (writev(cp->u.fd, iov, 2) != psl + 1) 202 err(FATAL, "%s: %s\n", 203 cp->t, strerror(errno)); 204 break; 205 case 'x': 206 tspace = PS; 207 PS = HS; 208 HS = tspace; 209 break; 210 case 'y': 211 if (pd) 212 break; 213 for (p = ps, len = psl; len--; ++p) 214 *p = cp->u.y[*p]; 215 break; 216 case ':': 217 case '}': 218 break; 219 case '=': 220 (void)printf("%lu\n", linenum); 221 } 222 cp = cp->next; 223 } /* for all cp */ 224 225 new: if (!nflag && !pd) 226 (void)printf("%s\n", ps); 227 flush_appends(); 228 } /* for all lines */ 229 } 230 231 /* 232 * TRUE if the address passed matches the current program state 233 * (lastline, linenumber, ps). 234 */ 235 #define MATCH(a) \ 236 (a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1) : \ 237 (a)->type == AT_LINE ? linenum == (a)->u.l : lastline 238 239 /* 240 * Return TRUE if the command applies to the current line. Sets the inrange 241 * flag to process ranges. Interprets the non-select (``!'') flag. 242 */ 243 static inline int 244 applies(cp) 245 struct s_command *cp; 246 { 247 int r; 248 249 lastaddr = 0; 250 if (cp->a1 == NULL && cp->a2 == NULL) 251 r = 1; 252 else if (cp->a2) 253 if (cp->inrange) { 254 if (MATCH(cp->a2)) { 255 cp->inrange = 0; 256 lastaddr = 1; 257 } 258 r = 1; 259 } else if (MATCH(cp->a1)) { 260 /* 261 * If the second address is a number less than or 262 * equal to the line number first selected, only 263 * one line shall be selected. 264 * -- POSIX 1003.2 265 */ 266 if (cp->a2->type == AT_LINE && 267 linenum >= cp->a2->u.l) 268 lastaddr = 1; 269 else 270 cp->inrange = 1; 271 r = 1; 272 } else 273 r = 0; 274 else 275 r = MATCH(cp->a1); 276 return (cp->nonsel ? ! r : r); 277 } 278 279 /* 280 * substitute -- 281 * Do substitutions in the pattern space. Currently, we build a 282 * copy of the new pattern space in the substitute space structure 283 * and then swap them. 284 */ 285 static int 286 substitute(cp) 287 struct s_command *cp; 288 { 289 SPACE tspace; 290 regex_t *re; 291 size_t re_off; 292 int n; 293 char *s; 294 295 s = ps; 296 re = cp->u.s->re; 297 if (re == NULL) { 298 if (defpreg != NULL && cp->u.s->maxbref > defpreg->re_nsub) { 299 linenum = cp->u.s->linenum; 300 err(COMPILE, "\\%d not defined in the RE", 301 cp->u.s->maxbref); 302 } 303 } 304 if (!regexec_e(re, s, 0, 0)) 305 return (0); 306 307 SS.len = 0; /* Clean substitute space. */ 308 n = cp->u.s->n; 309 switch (n) { 310 case 0: /* Global */ 311 do { 312 /* Locate start of replaced string. */ 313 re_off = match[0].rm_so; 314 /* Copy leading retained string. */ 315 cspace(&SS, s, re_off, APPEND); 316 /* Add in regular expression. */ 317 regsub(&SS, s, cp->u.s->new); 318 /* Move past this match. */ 319 s += match[0].rm_eo; 320 } while(regexec_e(re, s, REG_NOTBOL, 0)); 321 /* Copy trailing retained string. */ 322 cspace(&SS, s, strlen(s), APPEND); 323 break; 324 default: /* Nth occurrence */ 325 while (--n) { 326 s += match[0].rm_eo; 327 if (!regexec_e(re, s, REG_NOTBOL, 0)) 328 return (0); 329 } 330 /* FALLTHROUGH */ 331 case 1: /* 1st occurrence */ 332 /* Locate start of replaced string. */ 333 re_off = match[0].rm_so + (s - ps); 334 /* Copy leading retained string. */ 335 cspace(&SS, ps, re_off, APPEND); 336 /* Add in regular expression. */ 337 regsub(&SS, s, cp->u.s->new); 338 /* Copy trailing retained string. */ 339 s += match[0].rm_eo; 340 cspace(&SS, s, strlen(s), APPEND); 341 break; 342 } 343 344 /* 345 * Swap the substitute space and the pattern space, and make sure 346 * that any leftover pointers into stdio memory get lost. 347 */ 348 tspace = PS; 349 PS = SS; 350 SS = tspace; 351 SS.space = SS.back; 352 353 /* Handle the 'p' flag. */ 354 if (cp->u.s->p) 355 (void)printf("%s\n", ps); 356 357 /* Handle the 'w' flag. */ 358 if (cp->u.s->wfile && !pd) { 359 if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile, 360 O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1) 361 err(FATAL, "%s: %s\n", cp->u.s->wfile, strerror(errno)); 362 iov[0].iov_base = ps; 363 iov[0].iov_len = psl; 364 if (writev(cp->u.s->wfd, iov, 2) != psl + 1) 365 err(FATAL, "%s: %s\n", cp->u.s->wfile, strerror(errno)); 366 } 367 return (1); 368 } 369 370 /* 371 * Flush append requests. Always called before reading a line, 372 * therefore it also resets the substitution done (sdone) flag. 373 */ 374 static void 375 flush_appends() 376 { 377 FILE *f; 378 int count, i; 379 char buf[8 * 1024]; 380 381 for (i = 0; i < appendx; i++) 382 switch (appends[i].type) { 383 case AP_STRING: 384 (void)printf("%s", appends[i].s); 385 break; 386 case AP_FILE: 387 /* 388 * Read files probably shouldn't be cached. Since 389 * it's not an error to read a non-existent file, 390 * it's possible that another program is interacting 391 * with the sed script through the file system. It 392 * would be truly bizarre, but possible. It's probably 393 * not that big a performance win, anyhow. 394 */ 395 if ((f = fopen(appends[i].s, "r")) == NULL) 396 break; 397 while (count = fread(buf, 1, sizeof(buf), f)) 398 (void)fwrite(buf, 1, count, stdout); 399 (void)fclose(f); 400 break; 401 } 402 if (ferror(stdout)) 403 err(FATAL, "stdout: %s", strerror(errno ? errno : EIO)); 404 appendx = sdone = 0; 405 } 406 407 static void 408 lputs(s) 409 register char *s; 410 { 411 register int count; 412 register char *escapes, *p; 413 struct winsize win; 414 static int termwidth = -1; 415 416 if (termwidth == -1) 417 if (p = getenv("COLUMNS")) 418 termwidth = atoi(p); 419 else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && 420 win.ws_col > 0) 421 termwidth = win.ws_col; 422 else 423 termwidth = 60; 424 425 for (count = 0; *s; ++s) { 426 if (count >= termwidth) { 427 (void)printf("\\\n"); 428 count = 0; 429 } 430 if (isascii(*s) && isprint(*s) && *s != '\\') { 431 (void)putchar(*s); 432 count++; 433 } else { 434 escapes = "\\\a\b\f\n\r\t\v"; 435 (void)putchar('\\'); 436 if (p = strchr(escapes, *s)) { 437 (void)putchar("\\abfnrtv"[p - escapes]); 438 count += 2; 439 } else { 440 (void)printf("%03o", (u_char)*s); 441 count += 4; 442 } 443 } 444 } 445 (void)putchar('$'); 446 (void)putchar('\n'); 447 if (ferror(stdout)) 448 err(FATAL, "stdout: %s", strerror(errno ? errno : EIO)); 449 } 450 451 static inline int 452 regexec_e(preg, string, eflags, nomatch) 453 regex_t *preg; 454 const char *string; 455 int eflags, nomatch; 456 { 457 int eval; 458 459 if (preg == NULL) { 460 if (defpreg == NULL) 461 err(FATAL, "first RE may not be empty"); 462 } else 463 defpreg = preg; 464 465 eval = regexec(defpreg, string, 466 nomatch ? 0 : maxnsub + 1, match, eflags); 467 switch(eval) { 468 case 0: 469 return (1); 470 case REG_NOMATCH: 471 return (0); 472 } 473 err(FATAL, "RE error: %s", strregerror(eval, defpreg)); 474 /* NOTREACHED */ 475 } 476 477 /* 478 * regsub - perform substitutions after a regexp match 479 * Based on a routine by Henry Spencer 480 */ 481 static void 482 regsub(sp, string, src) 483 SPACE *sp; 484 char *string, *src; 485 { 486 register int len, no; 487 register char c, *dst; 488 489 #define NEEDSP(reqlen) \ 490 if (sp->len >= sp->blen - (reqlen) - 1) { \ 491 sp->blen += (reqlen) + 1024; \ 492 sp->space = sp->back = xrealloc(sp->back, sp->blen); \ 493 dst = sp->space + sp->len; \ 494 } 495 496 dst = sp->space + sp->len; 497 while ((c = *src++) != '\0') { 498 if (c == '&') 499 no = 0; 500 else if (c == '\\' && isdigit(*src)) 501 no = *src++ - '0'; 502 else 503 no = -1; 504 if (no < 0) { /* Ordinary character. */ 505 if (c == '\\' && (*src == '\\' || *src == '&')) 506 c = *src++; 507 NEEDSP(1); 508 *dst++ = c; 509 ++sp->len; 510 } else if (match[no].rm_so != -1 && match[no].rm_eo != -1) { 511 len = match[no].rm_eo - match[no].rm_so; 512 NEEDSP(len); 513 memmove(dst, string + match[no].rm_so, len); 514 dst += len; 515 sp->len += len; 516 } 517 } 518 NEEDSP(1); 519 *dst = '\0'; 520 } 521 522 /* 523 * aspace -- 524 * Append the source space to the destination space, allocating new 525 * space as necessary. 526 */ 527 void 528 cspace(sp, p, len, spflag) 529 SPACE *sp; 530 char *p; 531 size_t len; 532 enum e_spflag spflag; 533 { 534 size_t tlen; 535 536 /* 537 * Make sure SPACE has enough memory and ramp up quickly. Appends 538 * need two extra bytes, one for the newline, one for a terminating 539 * NULL. 540 */ 541 tlen = sp->len + len + spflag == APPENDNL ? 2 : 1; 542 if (tlen > sp->blen) { 543 sp->blen = tlen + 1024; 544 sp->space = sp->back = xrealloc(sp->back, sp->blen); 545 } 546 547 if (spflag == APPENDNL) 548 sp->space[sp->len++] = '\n'; 549 else if (spflag == REPLACE) 550 sp->len = 0; 551 552 memmove(sp->space + sp->len, p, len); 553 sp->space[sp->len += len] = '\0'; 554 } 555 556 /* 557 * Close all cached opened files and report any errors 558 */ 559 void 560 cfclose(cp, end) 561 register struct s_command *cp, *end; 562 { 563 564 for (; cp != end; cp = cp->next) 565 switch(cp->code) { 566 case 's': 567 if (cp->u.s->wfd != -1 && close(cp->u.s->wfd)) 568 err(FATAL, 569 "%s: %s", cp->u.s->wfile, strerror(errno)); 570 cp->u.s->wfd = -1; 571 break; 572 case 'w': 573 if (cp->u.fd != -1 && close(cp->u.fd)) 574 err(FATAL, "%s: %s", cp->t, strerror(errno)); 575 cp->u.fd = -1; 576 break; 577 case '{': 578 cfclose(cp->u.c, cp->next); 579 break; 580 } 581 } 582