1/* $NetBSD: qsubst.c,v 1.7 2004/01/05 23:23:36 jmmv 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#include <sys/cdefs.h> 101 102#ifndef lint 103__RCSID("$NetBSD: qsubst.c,v 1.7 2004/01/05 23:23:36 jmmv Exp $"); 104#endif 105 106#include <sys/file.h> 107 108#include <ctype.h> 109#include <errno.h> 110#include <signal.h> 111#include <stdio.h> 112#include <stdlib.h> 113#include <strings.h> 114#include <termcap.h> 115#include <termios.h> 116#include <unistd.h> 117 118extern const char *__progname; 119 120#define MAX_C_A 10 121#define MAX_C_B 10 122#define BUF_SIZ 1024 123 124static int debugging; 125static FILE *tempf; 126static long tbeg; 127static FILE *workf; 128static char *str1; 129static char *str2; 130static int s1l; 131static int s2l; 132static long nls[MAX_C_A + 1]; 133static char buf[(BUF_SIZ * 2) + 2]; 134static char *bufp; 135static char *bufp0; 136static char *bufpmax; 137static int rahead; 138static int cabove; 139static int cbelow; 140static int wordmode; 141static int flying; 142static int flystate; 143static int allfly; 144static const char *nullstr = ""; 145static int ul_; 146static char *current_file; 147static const char *beginul; 148static const char *endul; 149static char tcp_buf[1024]; 150static char cap_buf[1024]; 151static struct termios orig_tio; 152 153static void 154tstp_self(void) 155{ 156 void (*old_tstp) (int); 157 int mask; 158 159 mask = sigblock(0); 160 kill(getpid(), SIGTSTP); 161 old_tstp = signal(SIGTSTP, SIG_DFL); 162 sigsetmask(mask & ~sigmask(SIGTSTP)); 163 signal(SIGTSTP, old_tstp); 164} 165 166/* ARGSUSED */ 167static void 168sigtstp(int sig) 169{ 170 struct termios tio; 171 172 if (tcgetattr(0, &tio) < 0) { 173 tstp_self(); 174 return; 175 } 176 tcsetattr(0, TCSAFLUSH | TCSASOFT, &orig_tio); 177 tstp_self(); 178 tcsetattr(0, TCSADRAIN | TCSASOFT, &tio); 179} 180 181static void 182limit_above_below(void) 183{ 184 if (cabove > MAX_C_A) { 185 cabove = MAX_C_A; 186 } 187 if (cbelow > MAX_C_B) { 188 cbelow = MAX_C_B; 189 } 190} 191 192static int 193issymchar(unsigned char c) 194{ 195 return (isascii(c) && (isalnum(c) || (c == '_') || (c == '$'))); 196} 197 198static int 199foundit(void) 200{ 201 if (wordmode) { 202 return (!issymchar(bufp[-1]) && 203 !issymchar(bufp[-2 - s1l]) && 204 !bcmp(bufp - 1 - s1l, str1, s1l)); 205 } else { 206 return (!bcmp(bufp - s1l, str1, s1l)); 207 } 208} 209 210static int 211putcharf(int c) 212{ 213 return (putchar(c)); 214} 215 216static void 217put_ul(char *s) 218{ 219 if (ul_) { 220 for (; *s; s++) { 221 printf("_\b%c", *s); 222 } 223 } else { 224 tputs(beginul, 1, putcharf); 225 fputs(s, stdout); 226 tputs(endul, 1, putcharf); 227 } 228} 229 230static int 231getc_cbreak(void) 232{ 233 struct termios tio; 234 struct termios otio; 235 char c; 236 237 if (tcgetattr(0, &tio) < 0) 238 return (getchar()); 239 otio = tio; 240 tio.c_lflag &= ~(ICANON | ECHOKE | ECHOE | ECHO | ECHONL); 241 tio.c_cc[VMIN] = 1; 242 tio.c_cc[VTIME] = 0; 243 tcsetattr(0, TCSANOW | TCSASOFT, &tio); 244 switch (read(0, &c, 1)) { 245 case -1: 246 break; 247 case 0: 248 break; 249 case 1: 250 break; 251 } 252 tcsetattr(0, TCSANOW | TCSASOFT, &otio); 253 return (c); 254} 255 256static int 257doit(void) 258{ 259 long save; 260 int i; 261 int lastnl; 262 int use_replacement; 263 264 if (flying) { 265 return (flystate); 266 } 267 use_replacement = 0; 268 save = ftell(workf); 269 do { 270 for (i = MAX_C_A - cabove; nls[i] < 0; i++); 271 fseek(workf, nls[i], 0); 272 for (i = save - nls[i] - rahead; i; i--) { 273 putchar(getc(workf)); 274 } 275 put_ul(use_replacement ? str2 : str1); 276 fseek(workf, save + s1l - rahead, 0); 277 lastnl = 0; 278 i = cbelow + 1; 279 while (i > 0) { 280 int c; 281 c = getc(workf); 282 if (c == EOF) { 283 clearerr(workf); 284 break; 285 } 286 putchar(c); 287 lastnl = 0; 288 if (c == '\n') { 289 i--; 290 lastnl = 1; 291 } 292 } 293 if (!lastnl) 294 printf("\n[no final newline] "); 295 fseek(workf, save, 0); 296 i = -1; 297 while (i == -1) { 298 switch (getc_cbreak()) { 299 case ' ': 300 i = 1; 301 break; 302 case '.': 303 i = 1; 304 flying = 1; 305 flystate = 0; 306 break; 307 case 'n': 308 i = 0; 309 break; 310 case '\7': 311 i = 0; 312 flying = 1; 313 flystate = 0; 314 break; 315 case '!': 316 i = 1; 317 flying = 1; 318 flystate = 1; 319 break; 320 case ',': 321 use_replacement = !use_replacement; 322 i = -2; 323 printf("(using %s string gives)\n", 324 use_replacement ? "new" : "old"); 325 break; 326 case '?': 327 printf("File is `%s'\n", current_file); 328 break; 329 default: 330 putchar('\7'); 331 break; 332 } 333 } 334 } while (i < 0); 335 if (i) { 336 printf("(replacing"); 337 } else { 338 printf("(leaving"); 339 } 340 if (flying) { 341 if (flystate == i) { 342 printf(" this and all the rest"); 343 } else if (flystate) { 344 printf(" this, replacing all the rest"); 345 } else { 346 printf(" this, leaving all the rest"); 347 } 348 } 349 printf(")\n"); 350 return (i); 351} 352 353static void 354add_shift(long *a, long e, int n) 355{ 356 int i; 357 358 n--; 359 for (i = 0; i < n; i++) { 360 a[i] = a[i + 1]; 361 } 362 a[n] = e; 363} 364 365static void 366process_file(char *fn) 367{ 368 int i; 369 long n; 370 int c; 371 372 workf = fopen(fn, "r+"); 373 if (workf == NULL) { 374 fprintf(stderr, "%s: cannot read %s\n", __progname, fn); 375 return; 376 } 377 printf("(file: %s)\n", fn); 378 current_file = fn; 379 for (i = 0; i <= MAX_C_A; i++) { 380 nls[i] = -1; 381 } 382 nls[MAX_C_A] = 0; 383 tbeg = -1; 384 if (wordmode) { 385 bufp0 = &buf[1]; 386 rahead = s1l + 1; 387 buf[0] = '\0'; 388 } else { 389 bufp0 = &buf[0]; 390 rahead = s1l; 391 } 392 if (debugging) { 393 printf("[rahead = %d, bufp0-buf = %ld]\n", 394 rahead, (long) (bufp0 - &buf[0])); 395 } 396 n = 0; 397 bufp = bufp0; 398 bufpmax = &buf[sizeof(buf) - s1l - 2]; 399 flying = allfly; 400 flystate = 1; 401 while (1) { 402 c = getc(workf); 403 if (c == EOF) { 404 if (tbeg >= 0) { 405 if (bufp > bufp0) 406 fwrite(bufp0, 1, bufp - bufp0, tempf); 407 fseek(workf, tbeg, 0); 408 n = ftell(tempf); 409 fseek(tempf, 0L, 0); 410 for (; n; n--) { 411 putc(getc(tempf), workf); 412 } 413 fflush(workf); 414 ftruncate(fileno(workf), ftell(workf)); 415 } 416 fclose(workf); 417 return; 418 } 419 *bufp++ = c; 420 n++; 421 if (debugging) { 422 printf("[got %c, n now %ld, bufp-buf %ld]\n", 423 c, n, (long) (bufp - bufp0)); 424 } 425 if ((n >= rahead) && foundit() && doit()) { 426 int wbehind; 427 if (debugging) { 428 printf("[doing change]\n"); 429 } 430 wbehind = 1; 431 if (tbeg < 0) { 432 tbeg = ftell(workf) - rahead; 433 fseek(tempf, 0L, 0); 434 if (debugging) { 435 printf("[tbeg set to %d]\n", 436 (int)tbeg); 437 } 438 wbehind = 0; 439 } 440 if (bufp[-1] == '\n') 441 add_shift(nls, ftell(workf), MAX_C_A + 1); 442 if ((n > rahead) && wbehind) { 443 fwrite(bufp0, 1, n - rahead, tempf); 444 if (debugging) { 445 printf("[writing %ld from bufp0]\n", 446 n - rahead); 447 } 448 } 449 fwrite(str2, 1, s2l, tempf); 450 n = rahead - s1l; 451 if (debugging) { 452 printf("[n now %ld]\n", n); 453 } 454 if (n > 0) { 455 bcopy(bufp - n, bufp0, n); 456 if (debugging) { 457 printf("[copying %ld back]\n", n); 458 } 459 } 460 bufp = bufp0 + n; 461 } else { 462 if (bufp[-1] == '\n') 463 add_shift(nls, ftell(workf), MAX_C_A + 1); 464 if (bufp >= bufpmax) { 465 if (tbeg >= 0) { 466 fwrite(bufp0, 1, n - rahead, tempf); 467 if (debugging) { 468 printf("[flushing %ld]\n", 469 n - rahead); 470 } 471 } 472 n = rahead; 473 bcopy(bufp - n, bufp0, n); 474 if (debugging) { 475 printf("[n now %ld]\n[copying %ld back]\n", n, n); 476 } 477 bufp = bufp0 + n; 478 } 479 } 480 } 481} 482 483static void 484process_indir_file(char *fn) 485{ 486 char newfn[1024]; 487 FILE *f; 488 489 f = fopen(fn, "r"); 490 if (f == NULL) { 491 fprintf(stderr, "%s: cannot read %s\n", __progname, fn); 492 return; 493 } 494 while (fgets(newfn, sizeof(newfn), f) == newfn) { 495 newfn[strlen(newfn) - 1] = '\0'; 496 process_file(newfn); 497 } 498 fclose(f); 499} 500 501int 502main(int ac, char **av) 503{ 504 int skip; 505 char *cp; 506 507 if (ac < 3) { 508 fprintf(stderr, "usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n", 509 __progname); 510 exit(1); 511 } 512 cp = getenv("TERM"); 513 if (cp == 0) { 514 beginul = nullstr; 515 endul = nullstr; 516 } else { 517 if (tgetent(tcp_buf, cp) != 1) { 518 beginul = nullstr; 519 endul = nullstr; 520 } else { 521 cp = cap_buf; 522 if (tgetflag("os") || tgetflag("ul")) { 523 ul_ = 1; 524 } else { 525 ul_ = 0; 526 beginul = tgetstr("us", &cp); 527 if (beginul == 0) { 528 beginul = tgetstr("so", &cp); 529 if (beginul == 0) { 530 beginul = nullstr; 531 endul = nullstr; 532 } else { 533 endul = tgetstr("se", &cp); 534 } 535 } else { 536 endul = tgetstr("ue", &cp); 537 } 538 } 539 } 540 } 541 { 542 static char tmp[] = "/tmp/qsubst.XXXXXX"; 543 int fd; 544 fd = mkstemp(&tmp[0]); 545 if (fd < 0) { 546 fprintf(stderr, "%s: cannot create temp file: %s\n", 547 __progname, strerror(errno)); 548 exit(1); 549 } 550 tempf = fdopen(fd, "w+"); 551 } 552 if ((access(av[1], R_OK | W_OK) == 0) && 553 (access(av[ac - 1], R_OK | W_OK) < 0) && 554 (access(av[ac - 2], R_OK | W_OK) < 0)) { 555 fprintf(stderr, "%s: argument order has changed, it's now: str1 str2 files...\n", __progname); 556 } 557 str1 = av[1]; 558 str2 = av[2]; 559 av += 2; 560 ac -= 2; 561 s1l = strlen(str1); 562 s2l = strlen(str2); 563 if (s1l > BUF_SIZ) { 564 fprintf(stderr, "%s: search string too long (max %d chars)\n", 565 __progname, BUF_SIZ); 566 exit(1); 567 } 568 tcgetattr(0, &orig_tio); 569 signal(SIGTSTP, sigtstp); 570 allfly = 0; 571 cabove = 2; 572 cbelow = 2; 573 skip = 0; 574 for (ac--, av++; ac; ac--, av++) { 575 if (skip > 0) { 576 skip--; 577 continue; 578 } 579 if (**av == '-') { 580 ++*av; 581 if (!strcmp(*av, "debug")) { 582 debugging++; 583 } else if (!strcmp(*av, "w")) { 584 wordmode = !wordmode; 585 } else if ((strcmp(*av, "!") == 0) || 586 (strcmp(*av, "go") == 0) || 587 (strcmp(*av, "noask") == 0)) { 588 allfly = 1; 589 } else if ((strcmp(*av, "nogo") == 0) || 590 (strcmp(*av, "ask") == 0)) { 591 allfly = 0; 592 } else if (**av == 'c') { 593 cabove = atoi(++*av); 594 cbelow = cabove; 595 limit_above_below(); 596 } else if (**av == 'C') { 597 ++*av; 598 if (**av == 'A') { 599 cabove = atoi(++*av); 600 limit_above_below(); 601 } else if (**av == 'B') { 602 cbelow = atoi(++*av); 603 limit_above_below(); 604 } else { 605 fprintf(stderr, "%s: -C must be -CA or -CB\n", __progname); 606 } 607 } else if ((strcmp(*av, "f") == 0) || 608 (strcmp(*av, "F") == 0)) { 609 if (++skip >= ac) { 610 fprintf(stderr, "%s: -%s what?\n", 611 __progname, *av); 612 } else { 613 if (**av == 'f') { 614 process_file(av[skip]); 615 } else { 616 process_indir_file(av[skip]); 617 } 618 } 619 } 620 } else { 621 process_file(*av); 622 } 623 } 624 exit(0); 625} 626