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