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