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