1 /* $NetBSD: qsubst.c,v 1.5 2002/12/08 21:29:27 perry Exp $ */ 2 3 /* 4 * qsubst -- designed for renaming routines existing in a whole bunch 5 * of files. Needs -ltermcap. 6 * 7 * Usage: 8 * 9 * qsubst str1 str2 [ options ] 10 * 11 * qsubst reads its options (see below) to get a list of files. For 12 * each file on this list, it then replaces str1 with str2 wherever 13 * possible in that file, depending on user input (see below). The 14 * result is written back onto the original file. 15 * 16 * For each possible substitution, the user is prompted with a few 17 * lines before and after the line containing the string to be 18 * substituted. The string itself is displayed using the terminal's 19 * standout mode, if any. Then one character is read from the 20 * terminal. This is then interpreted as follows (this is designed to 21 * be like Emacs' query-replace-string): 22 * 23 * space replace this occurrence and go on to the next one 24 * . replace this occurrence and don't change any more in 25 * this file (ie, go on to the next file). 26 * , tentatively replace this occurrence. The lines as they 27 * would look if the substitution were made are printed 28 * out. Then another character is read and it is used to 29 * decide the result (possibly undoing the tentative 30 * replacement). 31 * n don't change this one, but go on to the next one 32 * ^G don't change this one or any others in this file, but 33 * instead go on to the next file. 34 * ! change the rest in this file without asking, then go on 35 * to the next file (at which point qsubst will start 36 * asking again). 37 * ? print out the current filename and ask again. 38 * 39 * The first two arguments to qsubst are always the string to replace 40 * and the string to replace it with. The options are as follows: 41 * 42 * -w The search string is considered as a C symbol; it must 43 * be bounded by non-symbol characters. This option 44 * toggles. (`w' for `word'.) 45 * -! Enter ! mode automatically at the beginning of each 46 * file. 47 * -go Same as -! 48 * -noask Same as -! 49 * -nogo Negate -go 50 * -ask Negate -noask (same as -nogo) 51 * -cN (N is a number) Give N lines of context above and below 52 * the line with the match when prompting the user. 53 * -CAN (N is a number) Give N lines of context above the line 54 * with the match when prompting the user. 55 * -CBN (N is a number) Give N lines of context below the line 56 * with the match when prompting the user. 57 * -f filename 58 * The filename following the -f argument is one of the 59 * files qsubst should perform substitutions in. 60 * -F filename 61 * qsubst should read the named file to get the names of 62 * files to perform substitutions in. The names should 63 * appear one to a line. 64 * 65 * The default amount of context is -c2, that is, two lines above and 66 * two lines below the line with the match. 67 * 68 * Arguments not beginning with a - sign in the options field are 69 * implicitly preceded by -f. Thus, -f is really needed only when the 70 * file name begins with a - sign. 71 * 72 * qsubst reads its options in order and processes files as it gets 73 * them. This means, for example, that a -go will affect only files 74 * from -f or -F options appearing after the -go option. 75 * 76 * The most context you can get is ten lines each, above and below 77 * (corresponding to -c10). 78 * 79 * Str1 is limited to 512 characters; there is no limit on the size of 80 * str2. Neither one may contain a NUL. 81 * 82 * NULs in the file may cause qsubst to make various mistakes. 83 * 84 * If any other program modifies the file while qsubst is running, all 85 * bets are off. 86 * 87 * This program is in the public domain. Anyone may use it in any way 88 * for any purpose. Of course, it's also up to you to determine 89 * whether what it does is suitable for you; the above comments may 90 * help, but I can't promise they're accurate. It's free, and you get 91 * what you pay for. 92 * 93 * If you find any bugs I would appreciate hearing about them, 94 * especially if you also fix them. 95 * 96 * der Mouse 97 * 98 * mouse@rodents.montreal.qc.ca 99 */ 100 101 #include <sys/file.h> 102 103 #include <ctype.h> 104 #include <errno.h> 105 #include <signal.h> 106 #include <stdio.h> 107 #include <stdlib.h> 108 #include <strings.h> 109 #include <termcap.h> 110 #include <termios.h> 111 #include <unistd.h> 112 113 extern const char *__progname; 114 115 #define MAX_C_A 10 116 #define MAX_C_B 10 117 #define BUF_SIZ 1024 118 119 static int debugging; 120 static FILE *tempf; 121 static long tbeg; 122 static FILE *workf; 123 static char *str1; 124 static char *str2; 125 static int s1l; 126 static int s2l; 127 static long nls[MAX_C_A + 1]; 128 static char buf[(BUF_SIZ * 2) + 2]; 129 static char *bufp; 130 static char *bufp0; 131 static char *bufpmax; 132 static int rahead; 133 static int cabove; 134 static int cbelow; 135 static int wordmode; 136 static int flying; 137 static int flystate; 138 static int allfly; 139 static const char *nullstr = ""; 140 static int ul_; 141 static char *current_file; 142 static const char *beginul; 143 static const char *endul; 144 static char tcp_buf[1024]; 145 static char cap_buf[1024]; 146 static struct termios orig_tio; 147 148 static void 149 tstp_self(void) 150 { 151 void (*old_tstp) (int); 152 int mask; 153 154 mask = sigblock(0); 155 kill(getpid(), SIGTSTP); 156 old_tstp = signal(SIGTSTP, SIG_DFL); 157 sigsetmask(mask & ~sigmask(SIGTSTP)); 158 signal(SIGTSTP, old_tstp); 159 } 160 161 /* ARGSUSED */ 162 static void 163 sigtstp(int sig) 164 { 165 struct termios tio; 166 167 if (tcgetattr(0, &tio) < 0) { 168 tstp_self(); 169 return; 170 } 171 tcsetattr(0, TCSAFLUSH | TCSASOFT, &orig_tio); 172 tstp_self(); 173 tcsetattr(0, TCSADRAIN | TCSASOFT, &tio); 174 } 175 176 static void 177 limit_above_below(void) 178 { 179 if (cabove > MAX_C_A) { 180 cabove = MAX_C_A; 181 } 182 if (cbelow > MAX_C_B) { 183 cbelow = MAX_C_B; 184 } 185 } 186 187 static int 188 issymchar(char c) 189 { 190 return (isascii(c) && (isalnum(c) || (c == '_') || (c == '$'))); 191 } 192 193 static int 194 foundit(void) 195 { 196 if (wordmode) { 197 return (!issymchar(bufp[-1]) && 198 !issymchar(bufp[-2 - s1l]) && 199 !bcmp(bufp - 1 - s1l, str1, s1l)); 200 } else { 201 return (!bcmp(bufp - s1l, str1, s1l)); 202 } 203 } 204 205 static int 206 putcharf(int c) 207 { 208 return (putchar(c)); 209 } 210 211 static void 212 put_ul(char *s) 213 { 214 if (ul_) { 215 for (; *s; s++) { 216 printf("_\b%c", *s); 217 } 218 } else { 219 tputs(beginul, 1, putcharf); 220 fputs(s, stdout); 221 tputs(endul, 1, putcharf); 222 } 223 } 224 225 static int 226 getc_cbreak(void) 227 { 228 struct termios tio; 229 struct termios otio; 230 char c; 231 232 if (tcgetattr(0, &tio) < 0) 233 return (getchar()); 234 otio = tio; 235 tio.c_lflag &= ~(ICANON | ECHOKE | ECHOE | ECHO | ECHONL); 236 tio.c_cc[VMIN] = 1; 237 tio.c_cc[VTIME] = 0; 238 tcsetattr(0, TCSANOW | TCSASOFT, &tio); 239 switch (read(0, &c, 1)) { 240 case -1: 241 break; 242 case 0: 243 break; 244 case 1: 245 break; 246 } 247 tcsetattr(0, TCSANOW | TCSASOFT, &otio); 248 return (c); 249 } 250 251 static int 252 doit(void) 253 { 254 long save; 255 int i; 256 int lastnl; 257 int use_replacement; 258 259 if (flying) { 260 return (flystate); 261 } 262 use_replacement = 0; 263 save = ftell(workf); 264 do { 265 for (i = MAX_C_A - cabove; nls[i] < 0; i++); 266 fseek(workf, nls[i], 0); 267 for (i = save - nls[i] - rahead; i; i--) { 268 putchar(getc(workf)); 269 } 270 put_ul(use_replacement ? str2 : str1); 271 fseek(workf, save + s1l - rahead, 0); 272 lastnl = 0; 273 i = cbelow + 1; 274 while (i > 0) { 275 int c; 276 c = getc(workf); 277 if (c == EOF) { 278 clearerr(workf); 279 break; 280 } 281 putchar(c); 282 lastnl = 0; 283 if (c == '\n') { 284 i--; 285 lastnl = 1; 286 } 287 } 288 if (!lastnl) 289 printf("\n[no final newline] "); 290 fseek(workf, save, 0); 291 i = -1; 292 while (i == -1) { 293 switch (getc_cbreak()) { 294 case ' ': 295 i = 1; 296 break; 297 case '.': 298 i = 1; 299 flying = 1; 300 flystate = 0; 301 break; 302 case 'n': 303 i = 0; 304 break; 305 case '\7': 306 i = 0; 307 flying = 1; 308 flystate = 0; 309 break; 310 case '!': 311 i = 1; 312 flying = 1; 313 flystate = 1; 314 break; 315 case ',': 316 use_replacement = !use_replacement; 317 i = -2; 318 printf("(using %s string gives)\n", 319 use_replacement ? "new" : "old"); 320 break; 321 case '?': 322 printf("File is `%s'\n", current_file); 323 break; 324 default: 325 putchar('\7'); 326 break; 327 } 328 } 329 } while (i < 0); 330 if (i) { 331 printf("(replacing"); 332 } else { 333 printf("(leaving"); 334 } 335 if (flying) { 336 if (flystate == i) { 337 printf(" this and all the rest"); 338 } else if (flystate) { 339 printf(" this, replacing all the rest"); 340 } else { 341 printf(" this, leaving all the rest"); 342 } 343 } 344 printf(")\n"); 345 return (i); 346 } 347 348 static void 349 add_shift(long *a, long e, int n) 350 { 351 int i; 352 353 n--; 354 for (i = 0; i < n; i++) { 355 a[i] = a[i + 1]; 356 } 357 a[n] = e; 358 } 359 360 static void 361 process_file(char *fn) 362 { 363 int i; 364 long n; 365 int c; 366 367 workf = fopen(fn, "r+"); 368 if (workf == NULL) { 369 fprintf(stderr, "%s: cannot read %s\n", __progname, fn); 370 return; 371 } 372 printf("(file: %s)\n", fn); 373 current_file = fn; 374 for (i = 0; i <= MAX_C_A; i++) { 375 nls[i] = -1; 376 } 377 nls[MAX_C_A] = 0; 378 tbeg = -1; 379 if (wordmode) { 380 bufp0 = &buf[1]; 381 rahead = s1l + 1; 382 buf[0] = '\0'; 383 } else { 384 bufp0 = &buf[0]; 385 rahead = s1l; 386 } 387 if (debugging) { 388 printf("[rahead = %d, bufp0-buf = %ld]\n", 389 rahead, (long) (bufp0 - &buf[0])); 390 } 391 n = 0; 392 bufp = bufp0; 393 bufpmax = &buf[sizeof(buf) - s1l - 2]; 394 flying = allfly; 395 flystate = 1; 396 while (1) { 397 c = getc(workf); 398 if (c == EOF) { 399 if (tbeg >= 0) { 400 if (bufp > bufp0) 401 fwrite(bufp0, 1, bufp - bufp0, tempf); 402 fseek(workf, tbeg, 0); 403 n = ftell(tempf); 404 fseek(tempf, 0L, 0); 405 for (; n; n--) { 406 putc(getc(tempf), workf); 407 } 408 fflush(workf); 409 ftruncate(fileno(workf), ftell(workf)); 410 } 411 fclose(workf); 412 return; 413 } 414 *bufp++ = c; 415 n++; 416 if (debugging) { 417 printf("[got %c, n now %ld, bufp-buf %ld]\n", 418 c, n, (long) (bufp - bufp0)); 419 } 420 if ((n >= rahead) && foundit() && doit()) { 421 int wbehind; 422 if (debugging) { 423 printf("[doing change]\n"); 424 } 425 wbehind = 1; 426 if (tbeg < 0) { 427 tbeg = ftell(workf) - rahead; 428 fseek(tempf, 0L, 0); 429 if (debugging) { 430 printf("[tbeg set to %d]\n", 431 (int)tbeg); 432 } 433 wbehind = 0; 434 } 435 if (bufp[-1] == '\n') 436 add_shift(nls, ftell(workf), MAX_C_A + 1); 437 if ((n > rahead) && wbehind) { 438 fwrite(bufp0, 1, n - rahead, tempf); 439 if (debugging) { 440 printf("[writing %ld from bufp0]\n", 441 n - rahead); 442 } 443 } 444 fwrite(str2, 1, s2l, tempf); 445 n = rahead - s1l; 446 if (debugging) { 447 printf("[n now %ld]\n", n); 448 } 449 if (n > 0) { 450 bcopy(bufp - n, bufp0, n); 451 if (debugging) { 452 printf("[copying %ld back]\n", n); 453 } 454 } 455 bufp = bufp0 + n; 456 } else { 457 if (bufp[-1] == '\n') 458 add_shift(nls, ftell(workf), MAX_C_A + 1); 459 if (bufp >= bufpmax) { 460 if (tbeg >= 0) { 461 fwrite(bufp0, 1, n - rahead, tempf); 462 if (debugging) { 463 printf("[flushing %ld]\n", 464 n - rahead); 465 } 466 } 467 n = rahead; 468 bcopy(bufp - n, bufp0, n); 469 if (debugging) { 470 printf("[n now %ld]\n[copying %ld back]\n", n, n); 471 } 472 bufp = bufp0 + n; 473 } 474 } 475 } 476 } 477 478 static void 479 process_indir_file(char *fn) 480 { 481 char newfn[1024]; 482 FILE *f; 483 484 f = fopen(fn, "r"); 485 if (f == NULL) { 486 fprintf(stderr, "%s: cannot read %s\n", __progname, fn); 487 return; 488 } 489 while (fgets(newfn, sizeof(newfn), f) == newfn) { 490 newfn[strlen(newfn) - 1] = '\0'; 491 process_file(newfn); 492 } 493 fclose(f); 494 } 495 496 int 497 main(int ac, char **av) 498 { 499 int skip; 500 char *cp; 501 502 if (ac < 3) { 503 fprintf(stderr, "Usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n", 504 __progname); 505 exit(1); 506 } 507 cp = getenv("TERM"); 508 if (cp == 0) { 509 beginul = nullstr; 510 endul = nullstr; 511 } else { 512 if (tgetent(tcp_buf, cp) != 1) { 513 beginul = nullstr; 514 endul = nullstr; 515 } else { 516 cp = cap_buf; 517 if (tgetflag("os") || tgetflag("ul")) { 518 ul_ = 1; 519 } else { 520 ul_ = 0; 521 beginul = tgetstr("us", &cp); 522 if (beginul == 0) { 523 beginul = tgetstr("so", &cp); 524 if (beginul == 0) { 525 beginul = nullstr; 526 endul = nullstr; 527 } else { 528 endul = tgetstr("se", &cp); 529 } 530 } else { 531 endul = tgetstr("ue", &cp); 532 } 533 } 534 } 535 } 536 { 537 static char tmp[] = "/tmp/qsubst.XXXXXX"; 538 int fd; 539 fd = mkstemp(&tmp[0]); 540 if (fd < 0) { 541 fprintf(stderr, "%s: cannot create temp file: %s\n", 542 __progname, strerror(errno)); 543 exit(1); 544 } 545 tempf = fdopen(fd, "w+"); 546 } 547 if ((access(av[1], R_OK | W_OK) == 0) && 548 (access(av[ac - 1], R_OK | W_OK) < 0) && 549 (access(av[ac - 2], R_OK | W_OK) < 0)) { 550 fprintf(stderr, "%s: argument order has changed, it's now: str1 str2 files...\n", __progname); 551 } 552 str1 = av[1]; 553 str2 = av[2]; 554 av += 2; 555 ac -= 2; 556 s1l = strlen(str1); 557 s2l = strlen(str2); 558 if (s1l > BUF_SIZ) { 559 fprintf(stderr, "%s: search string too long (max %d chars)\n", 560 __progname, BUF_SIZ); 561 exit(1); 562 } 563 tcgetattr(0, &orig_tio); 564 signal(SIGTSTP, sigtstp); 565 allfly = 0; 566 cabove = 2; 567 cbelow = 2; 568 skip = 0; 569 for (ac--, av++; ac; ac--, av++) { 570 if (skip > 0) { 571 skip--; 572 continue; 573 } 574 if (**av == '-') { 575 ++*av; 576 if (!strcmp(*av, "debug")) { 577 debugging++; 578 } else if (!strcmp(*av, "w")) { 579 wordmode = !wordmode; 580 } else if ((strcmp(*av, "!") == 0) || 581 (strcmp(*av, "go") == 0) || 582 (strcmp(*av, "noask") == 0)) { 583 allfly = 1; 584 } else if ((strcmp(*av, "nogo") == 0) || 585 (strcmp(*av, "ask") == 0)) { 586 allfly = 0; 587 } else if (**av == 'c') { 588 cabove = atoi(++*av); 589 cbelow = cabove; 590 limit_above_below(); 591 } else if (**av == 'C') { 592 ++*av; 593 if (**av == 'A') { 594 cabove = atoi(++*av); 595 limit_above_below(); 596 } else if (**av == 'B') { 597 cbelow = atoi(++*av); 598 limit_above_below(); 599 } else { 600 fprintf(stderr, "%s: -C must be -CA or -CB\n", __progname); 601 } 602 } else if ((strcmp(*av, "f") == 0) || 603 (strcmp(*av, "F") == 0)) { 604 if (++skip >= ac) { 605 fprintf(stderr, "%s: -%s what?\n", 606 __progname, *av); 607 } else { 608 if (**av == 'f') { 609 process_file(av[skip]); 610 } else { 611 process_indir_file(av[skip]); 612 } 613 } 614 } 615 } else { 616 process_file(*av); 617 } 618 } 619 exit(0); 620 } 621