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