1 /* 2 * Copyright (c) 1980 Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 static char sccsid[] = "@(#)touch.c 5.6 (Berkeley) 06/01/90"; 10 #endif /* not lint */ 11 12 #include <sys/types.h> 13 #include <sys/stat.h> 14 #include <sys/signal.h> 15 #include <stdio.h> 16 #include <ctype.h> 17 #include "error.h" 18 #include "pathnames.h" 19 20 /* 21 * Iterate through errors 22 */ 23 #define EITERATE(p, fv, i) for (p = fv[i]; p < fv[i+1]; p++) 24 #define ECITERATE(ei, p, lb) for (ei = lb; p = errors[ei],ei < nerrors; ei++) 25 26 #define FILEITERATE(fi, lb) for (fi = lb; fi <= nfiles; fi++) 27 int touchstatus = Q_YES; 28 29 findfiles(nerrors, errors, r_nfiles, r_files) 30 int nerrors; 31 Eptr *errors; 32 int *r_nfiles; 33 Eptr ***r_files; 34 { 35 int nfiles; 36 Eptr **files; 37 38 char *name; 39 reg int ei; 40 int fi; 41 reg Eptr errorp; 42 43 nfiles = countfiles(errors); 44 45 files = (Eptr**)Calloc(nfiles + 3, sizeof (Eptr*)); 46 touchedfiles = (boolean *)Calloc(nfiles+3, sizeof(boolean)); 47 /* 48 * Now, partition off the error messages 49 * into those that are synchronization, discarded or 50 * not specific to any file, and those that were 51 * nulled or true errors. 52 */ 53 files[0] = &errors[0]; 54 ECITERATE(ei, errorp, 0){ 55 if ( ! (NOTSORTABLE(errorp->error_e_class))) 56 break; 57 } 58 /* 59 * Now, and partition off all error messages 60 * for a given file. 61 */ 62 files[1] = &errors[ei]; 63 touchedfiles[0] = touchedfiles[1] = FALSE; 64 name = "\1"; 65 fi = 1; 66 ECITERATE(ei, errorp, ei){ 67 if ( (errorp->error_e_class == C_NULLED) 68 || (errorp->error_e_class == C_TRUE) ){ 69 if (strcmp(errorp->error_text[0], name) != 0){ 70 name = errorp->error_text[0]; 71 touchedfiles[fi] = FALSE; 72 files[fi] = &errors[ei]; 73 fi++; 74 } 75 } 76 } 77 files[fi] = &errors[nerrors]; 78 *r_nfiles = nfiles; 79 *r_files = files; 80 } 81 82 int countfiles(errors) 83 Eptr *errors; 84 { 85 char *name; 86 int ei; 87 reg Eptr errorp; 88 89 int nfiles; 90 nfiles = 0; 91 name = "\1"; 92 ECITERATE(ei, errorp, 0){ 93 if (SORTABLE(errorp->error_e_class)){ 94 if (strcmp(errorp->error_text[0],name) != 0){ 95 nfiles++; 96 name = errorp->error_text[0]; 97 } 98 } 99 } 100 return(nfiles); 101 } 102 char *class_table[] = { 103 /*C_UNKNOWN 0 */ "Unknown", 104 /*C_IGNORE 1 */ "ignore", 105 /*C_SYNC 2 */ "synchronization", 106 /*C_DISCARD 3 */ "discarded", 107 /*C_NONSPEC 4 */ "non specific", 108 /*C_THISFILE 5 */ "specific to this file", 109 /*C_NULLED 6 */ "nulled", 110 /*C_TRUE 7 */ "true", 111 /*C_DUPL 8 */ "duplicated" 112 }; 113 114 int class_count[C_LAST - C_FIRST] = {0}; 115 116 filenames(nfiles, files) 117 int nfiles; 118 Eptr **files; 119 { 120 reg int fi; 121 char *sep = " "; 122 extern char *class_table[]; 123 int someerrors; 124 125 /* 126 * first, simply dump out errors that 127 * don't pertain to any file 128 */ 129 someerrors = nopertain(files); 130 131 if (nfiles){ 132 someerrors++; 133 fprintf(stdout, terse 134 ? "%d file%s" 135 : "%d file%s contain%s errors", 136 nfiles, plural(nfiles), verbform(nfiles)); 137 if (!terse){ 138 FILEITERATE(fi, 1){ 139 fprintf(stdout, "%s\"%s\" (%d)", 140 sep, (*files[fi])->error_text[0], 141 files[fi+1] - files[fi]); 142 sep = ", "; 143 } 144 } 145 fprintf(stdout, "\n"); 146 } 147 if (!someerrors) 148 fprintf(stdout, "No errors.\n"); 149 } 150 151 /* 152 * Dump out errors that don't pertain to any file 153 */ 154 int nopertain(files) 155 Eptr **files; 156 { 157 int type; 158 int someerrors = 0; 159 reg Eptr *erpp; 160 reg Eptr errorp; 161 162 if (files[1] - files[0] <= 0) 163 return(0); 164 for(type = C_UNKNOWN; NOTSORTABLE(type); type++){ 165 if (class_count[type] <= 0) 166 continue; 167 if (type > C_SYNC) 168 someerrors++; 169 if (terse){ 170 fprintf(stdout, "\t%d %s errors NOT PRINTED\n", 171 class_count[type], class_table[type]); 172 } else { 173 fprintf(stdout, "\n\t%d %s errors follow\n", 174 class_count[type], class_table[type]); 175 EITERATE(erpp, files, 0){ 176 errorp = *erpp; 177 if (errorp->error_e_class == type){ 178 errorprint(stdout, errorp, TRUE); 179 } 180 } 181 } 182 } 183 return(someerrors); 184 } 185 186 extern boolean notouch; 187 188 boolean touchfiles(nfiles, files, r_edargc, r_edargv) 189 int nfiles; 190 Eptr **files; 191 int *r_edargc; 192 char ***r_edargv; 193 { 194 char *name; 195 reg Eptr errorp; 196 reg int fi; 197 reg Eptr *erpp; 198 int ntrueerrors; 199 boolean scribbled; 200 int n_pissed_on; /* # of file touched*/ 201 int spread; 202 203 FILEITERATE(fi, 1){ 204 name = (*files[fi])->error_text[0]; 205 spread = files[fi+1] - files[fi]; 206 fprintf(stdout, terse 207 ? "\"%s\" has %d error%s, " 208 : "\nFile \"%s\" has %d error%s.\n" 209 , name ,spread ,plural(spread)); 210 /* 211 * First, iterate through all error messages in this file 212 * to see how many of the error messages really will 213 * get inserted into the file. 214 */ 215 ntrueerrors = 0; 216 EITERATE(erpp, files, fi){ 217 errorp = *erpp; 218 if (errorp->error_e_class == C_TRUE) 219 ntrueerrors++; 220 } 221 fprintf(stdout, terse 222 ? "insert %d\n" 223 : "\t%d of these errors can be inserted into the file.\n", 224 ntrueerrors); 225 226 hackfile(name, files, fi, ntrueerrors); 227 } 228 scribbled = FALSE; 229 n_pissed_on = 0; 230 FILEITERATE(fi, 1){ 231 scribbled |= touchedfiles[fi]; 232 n_pissed_on++; 233 } 234 if (scribbled){ 235 /* 236 * Construct an execv argument 237 */ 238 execvarg(n_pissed_on, r_edargc, r_edargv); 239 return(TRUE); 240 } else { 241 if (!terse) 242 fprintf(stdout, "You didn't touch any files.\n"); 243 return(FALSE); 244 } 245 } 246 247 hackfile(name, files, ix, nerrors) 248 char *name; 249 Eptr **files; 250 int ix; 251 { 252 boolean previewed; 253 int errordest; /* where errors go*/ 254 255 if (!oktotouch(name)) { 256 previewed = FALSE; 257 errordest = TOSTDOUT; 258 } else { 259 previewed = preview(name, nerrors, files, ix); 260 errordest = settotouch(name); 261 } 262 263 if (errordest != TOSTDOUT) 264 touchedfiles[ix] = TRUE; 265 266 if (previewed && (errordest == TOSTDOUT)) 267 return; 268 269 diverterrors(name, errordest, files, ix, previewed, nerrors); 270 271 if (errordest == TOTHEFILE){ 272 /* 273 * overwrite the original file 274 */ 275 writetouched(1); 276 } 277 } 278 279 boolean preview(name, nerrors, files, ix) 280 char *name; 281 int nerrors; 282 Eptr **files; 283 int ix; 284 { 285 int back; 286 reg Eptr *erpp; 287 288 if (nerrors <= 0) 289 return(FALSE); 290 back = FALSE; 291 if(query){ 292 switch(inquire(terse 293 ? "Preview? " 294 : "Do you want to preview the errors first? ")){ 295 case Q_YES: 296 case Q_yes: 297 back = TRUE; 298 EITERATE(erpp, files, ix){ 299 errorprint(stdout, *erpp, TRUE); 300 } 301 if (!terse) 302 fprintf(stdout, "\n"); 303 default: 304 break; 305 } 306 } 307 return(back); 308 } 309 310 int settotouch(name) 311 char *name; 312 { 313 int dest = TOSTDOUT; 314 315 if (query){ 316 switch(touchstatus = inquire(terse 317 ? "Touch? " 318 : "Do you want to touch file \"%s\"? ", 319 name)){ 320 case Q_NO: 321 case Q_no: 322 return(dest); 323 default: 324 break; 325 } 326 } 327 328 switch(probethisfile(name)){ 329 case F_NOTREAD: 330 dest = TOSTDOUT; 331 fprintf(stdout, terse 332 ? "\"%s\" unreadable\n" 333 : "File \"%s\" is unreadable\n", 334 name); 335 break; 336 case F_NOTWRITE: 337 dest = TOSTDOUT; 338 fprintf(stdout, terse 339 ? "\"%s\" unwritable\n" 340 : "File \"%s\" is unwritable\n", 341 name); 342 break; 343 case F_NOTEXIST: 344 dest = TOSTDOUT; 345 fprintf(stdout, terse 346 ? "\"%s\" not found\n" 347 : "Can't find file \"%s\" to insert error messages into.\n", 348 name); 349 break; 350 default: 351 dest = edit(name) ? TOSTDOUT : TOTHEFILE; 352 break; 353 } 354 return(dest); 355 } 356 357 diverterrors(name, dest, files, ix, previewed, nterrors) 358 char *name; 359 int dest; 360 Eptr **files; 361 int ix; 362 boolean previewed; 363 int nterrors; 364 { 365 int nerrors; 366 reg Eptr *erpp; 367 reg Eptr errorp; 368 369 nerrors = files[ix+1] - files[ix]; 370 371 if ( (nerrors != nterrors) 372 && (!previewed) ){ 373 fprintf(stdout, terse 374 ? "Uninserted errors\n" 375 : ">>Uninserted errors for file \"%s\" follow.\n", 376 name); 377 } 378 379 EITERATE(erpp, files, ix){ 380 errorp = *erpp; 381 if (errorp->error_e_class != C_TRUE){ 382 if (previewed || touchstatus == Q_NO) 383 continue; 384 errorprint(stdout, errorp, TRUE); 385 continue; 386 } 387 switch (dest){ 388 case TOSTDOUT: 389 if (previewed || touchstatus == Q_NO) 390 continue; 391 errorprint(stdout,errorp, TRUE); 392 break; 393 case TOTHEFILE: 394 insert(errorp->error_line); 395 text(errorp, FALSE); 396 break; 397 } 398 } 399 } 400 401 int oktotouch(filename) 402 char *filename; 403 { 404 extern char *suffixlist; 405 reg char *src; 406 reg char *pat; 407 char *osrc; 408 409 pat = suffixlist; 410 if (pat == 0) 411 return(0); 412 if (*pat == '*') 413 return(1); 414 while (*pat++ != '.') 415 continue; 416 --pat; /* point to the period */ 417 418 for (src = &filename[strlen(filename)], --src; 419 (src > filename) && (*src != '.'); --src) 420 continue; 421 if (*src != '.') 422 return(0); 423 424 for (src++, pat++, osrc = src; *src && *pat; src = osrc, pat++){ 425 for (; *src /* not at end of the source */ 426 && *pat /* not off end of pattern */ 427 && *pat != '.' /* not off end of sub pattern */ 428 && *pat != '*' /* not wild card */ 429 && *src == *pat; /* and equal... */ 430 src++, pat++) 431 continue; 432 if (*src == 0 && (*pat == 0 || *pat == '.' || *pat == '*')) 433 return(1); 434 if (*src != 0 && *pat == '*') 435 return(1); 436 while (*pat && *pat != '.') 437 pat++; 438 if (! *pat) 439 return(0); 440 } 441 return(0); 442 } 443 /* 444 * Construct an execv argument 445 * We need 1 argument for the editor's name 446 * We need 1 argument for the initial search string 447 * We need n_pissed_on arguments for the file names 448 * We need 1 argument that is a null for execv. 449 * The caller fills in the editor's name. 450 * We fill in the initial search string. 451 * We fill in the arguments, and the null. 452 */ 453 execvarg(n_pissed_on, r_argc, r_argv) 454 int n_pissed_on; 455 int *r_argc; 456 char ***r_argv; 457 { 458 Eptr p; 459 char *sep; 460 int fi; 461 462 (*r_argv) = (char **)Calloc(n_pissed_on + 3, sizeof(char *)); 463 (*r_argc) = n_pissed_on + 2; 464 (*r_argv)[1] = "+1;/###/"; 465 n_pissed_on = 2; 466 if (!terse){ 467 fprintf(stdout, "You touched file(s):"); 468 sep = " "; 469 } 470 FILEITERATE(fi, 1){ 471 if (!touchedfiles[fi]) 472 continue; 473 p = *(files[fi]); 474 if (!terse){ 475 fprintf(stdout,"%s\"%s\"", sep, p->error_text[0]); 476 sep = ", "; 477 } 478 (*r_argv)[n_pissed_on++] = p->error_text[0]; 479 } 480 if (!terse) 481 fprintf(stdout, "\n"); 482 (*r_argv)[n_pissed_on] = 0; 483 } 484 485 FILE *o_touchedfile; /* the old file */ 486 FILE *n_touchedfile; /* the new file */ 487 char *o_name; 488 char n_name[64]; 489 char *canon_name = _PATH_TMP; 490 int o_lineno; 491 int n_lineno; 492 boolean tempfileopen = FALSE; 493 /* 494 * open the file; guaranteed to be both readable and writable 495 * Well, if it isn't, then return TRUE if something failed 496 */ 497 boolean edit(name) 498 char *name; 499 { 500 o_name = name; 501 if ( (o_touchedfile = fopen(name, "r")) == NULL){ 502 fprintf(stderr, "%s: Can't open file \"%s\" to touch (read).\n", 503 processname, name); 504 return(TRUE); 505 } 506 (void)strcpy(n_name, canon_name); 507 (void)mktemp(n_name); 508 if ( (n_touchedfile = fopen(n_name, "w")) == NULL){ 509 fprintf(stderr,"%s: Can't open file \"%s\" to touch (write).\n", 510 processname, name); 511 return(TRUE); 512 } 513 tempfileopen = TRUE; 514 n_lineno = 0; 515 o_lineno = 0; 516 return(FALSE); 517 } 518 /* 519 * Position to the line (before, after) the line given by place 520 */ 521 char edbuf[BUFSIZ]; 522 insert(place) 523 int place; 524 { 525 --place; /* always insert messages before the offending line*/ 526 for(; o_lineno < place; o_lineno++, n_lineno++){ 527 if(fgets(edbuf, BUFSIZ, o_touchedfile) == NULL) 528 return; 529 fputs(edbuf, n_touchedfile); 530 } 531 } 532 533 text(p, use_all) 534 reg Eptr p; 535 boolean use_all; 536 { 537 int offset = use_all ? 0 : 2; 538 539 fputs(lang_table[p->error_language].lang_incomment, n_touchedfile); 540 fprintf(n_touchedfile, "%d [%s] ", 541 p->error_line, 542 lang_table[p->error_language].lang_name); 543 wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset); 544 fputs(lang_table[p->error_language].lang_outcomment,n_touchedfile); 545 n_lineno++; 546 } 547 548 /* 549 * write the touched file to its temporary copy, 550 * then bring the temporary in over the local file 551 */ 552 writetouched(overwrite) 553 int overwrite; 554 { 555 reg int nread; 556 reg FILE *localfile; 557 reg FILE *tmpfile; 558 int botch; 559 int oktorm; 560 561 botch = 0; 562 oktorm = 1; 563 while((nread = fread(edbuf, 1, sizeof(edbuf), o_touchedfile)) != NULL){ 564 if (nread != fwrite(edbuf, 1, nread, n_touchedfile)){ 565 /* 566 * Catastrophe in temporary area: file system full? 567 */ 568 botch = 1; 569 fprintf(stderr, 570 "%s: write failure: No errors inserted in \"%s\"\n", 571 processname, o_name); 572 } 573 } 574 fclose(n_touchedfile); 575 fclose(o_touchedfile); 576 /* 577 * Now, copy the temp file back over the original 578 * file, thus preserving links, etc 579 */ 580 if (botch == 0 && overwrite){ 581 botch = 0; 582 localfile = NULL; 583 tmpfile = NULL; 584 if ((localfile = fopen(o_name, "w")) == NULL){ 585 fprintf(stderr, 586 "%s: Can't open file \"%s\" to overwrite.\n", 587 processname, o_name); 588 botch++; 589 } 590 if ((tmpfile = fopen(n_name, "r")) == NULL){ 591 fprintf(stderr, "%s: Can't open file \"%s\" to read.\n", 592 processname, n_name); 593 botch++; 594 } 595 if (!botch) 596 oktorm = mustoverwrite(localfile, tmpfile); 597 if (localfile != NULL) 598 fclose(localfile); 599 if (tmpfile != NULL) 600 fclose(tmpfile); 601 } 602 if (oktorm == 0){ 603 fprintf(stderr, "%s: Catastrophe: A copy of \"%s\": was saved in \"%s\"\n", 604 processname, o_name, n_name); 605 exit(1); 606 } 607 /* 608 * Kiss the temp file good bye 609 */ 610 unlink(n_name); 611 tempfileopen = FALSE; 612 return(TRUE); 613 } 614 /* 615 * return 1 if the tmpfile can be removed after writing it out 616 */ 617 int mustoverwrite(preciousfile, tmpfile) 618 FILE *preciousfile; 619 FILE *tmpfile; 620 { 621 int nread; 622 623 while((nread = fread(edbuf, 1, sizeof(edbuf), tmpfile)) != NULL){ 624 if (mustwrite(edbuf, nread, preciousfile) == 0) 625 return(0); 626 } 627 return(1); 628 } 629 /* 630 * return 0 on catastrophe 631 */ 632 mustwrite(base, n, preciousfile) 633 char *base; 634 int n; 635 FILE *preciousfile; 636 { 637 int nwrote; 638 639 if (n <= 0) 640 return(1); 641 nwrote = fwrite(base, 1, n, preciousfile); 642 if (nwrote == n) 643 return(1); 644 perror(processname); 645 switch(inquire(terse 646 ? "Botch overwriting: retry? " 647 : "Botch overwriting the source file: retry? ")){ 648 case Q_YES: 649 case Q_yes: 650 mustwrite(base + nwrote, n - nwrote, preciousfile); 651 return(1); 652 case Q_NO: 653 case Q_no: 654 switch(inquire("Are you sure? ")){ 655 case Q_YES: 656 case Q_yes: 657 return(0); 658 case Q_NO: 659 case Q_no: 660 mustwrite(base + nwrote, n - nwrote, preciousfile); 661 return(1); 662 } 663 default: 664 return(0); 665 } 666 } 667 668 onintr() 669 { 670 switch(inquire(terse 671 ? "\nContinue? " 672 : "\nInterrupt: Do you want to continue? ")){ 673 case Q_YES: 674 case Q_yes: 675 signal(SIGINT, onintr); 676 return; 677 default: 678 if (tempfileopen){ 679 /* 680 * Don't overwrite the original file! 681 */ 682 writetouched(0); 683 } 684 exit(1); 685 } 686 /*NOTREACHED*/ 687 } 688 689 errorprint(place, errorp, print_all) 690 FILE *place; 691 Eptr errorp; 692 boolean print_all; 693 { 694 int offset = print_all ? 0 : 2; 695 696 if (errorp->error_e_class == C_IGNORE) 697 return; 698 fprintf(place, "[%s] ", lang_table[errorp->error_language].lang_name); 699 wordvprint(place,errorp->error_lgtext-offset,errorp->error_text+offset); 700 putc('\n', place); 701 } 702 703 int inquire(fmt, a1, a2) 704 char *fmt; 705 /*VARARGS1*/ 706 { 707 char buffer[128]; 708 709 if (queryfile == NULL) 710 return(0); 711 for(;;){ 712 do{ 713 fflush(stdout); 714 fprintf(stderr, fmt, a1, a2); 715 fflush(stderr); 716 } while (fgets(buffer, 127, queryfile) == NULL); 717 switch(buffer[0]){ 718 case 'Y': return(Q_YES); 719 case 'y': return(Q_yes); 720 case 'N': return(Q_NO); 721 case 'n': return(Q_no); 722 default: fprintf(stderr, "Yes or No only!\n"); 723 } 724 } 725 } 726 727 int probethisfile(name) 728 char *name; 729 { 730 struct stat statbuf; 731 if (stat(name, &statbuf) < 0) 732 return(F_NOTEXIST); 733 if((statbuf.st_mode & S_IREAD) == 0) 734 return(F_NOTREAD); 735 if((statbuf.st_mode & S_IWRITE) == 0) 736 return(F_NOTWRITE); 737 return(F_TOUCHIT); 738 } 739