touch.c revision 1.7
1/* $NetBSD: touch.c,v 1.7 1997/10/19 14:06:23 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 39static char sccsid[] = "@(#)touch.c 8.1 (Berkeley) 6/6/93"; 40#endif 41__RCSID("$NetBSD: touch.c,v 1.7 1997/10/19 14:06:23 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++) 67int touchstatus = Q_YES; 68 69void 70findfiles(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 123int 124countfiles(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 145char *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 157int class_count[C_LAST - C_FIRST] = {0}; 158 159void 160filenames(nfiles, files) 161 int nfiles; 162 Eptr **files; 163{ 164 int fi; 165 char *sep = " "; 166 extern char *class_table[]; 167 int someerrors; 168 169 /* 170 * first, simply dump out errors that 171 * don't pertain to any file 172 */ 173 someerrors = nopertain(files); 174 175 if (nfiles){ 176 someerrors++; 177 fprintf(stdout, terse 178 ? "%d file%s" 179 : "%d file%s contain%s errors", 180 nfiles, plural(nfiles), verbform(nfiles)); 181 if (!terse){ 182 FILEITERATE(fi, 1){ 183 fprintf(stdout, "%s\"%s\" (%d)", 184 sep, (*files[fi])->error_text[0], 185 (int)(files[fi+1] - files[fi])); 186 sep = ", "; 187 } 188 } 189 fprintf(stdout, "\n"); 190 } 191 if (!someerrors) 192 fprintf(stdout, "No errors.\n"); 193} 194 195/* 196 * Dump out errors that don't pertain to any file 197 */ 198int 199nopertain(files) 200 Eptr **files; 201{ 202 int type; 203 int someerrors = 0; 204 Eptr *erpp; 205 Eptr errorp; 206 207 if (files[1] - files[0] <= 0) 208 return(0); 209 for(type = C_UNKNOWN; NOTSORTABLE(type); type++){ 210 if (class_count[type] <= 0) 211 continue; 212 if (type > C_SYNC) 213 someerrors++; 214 if (terse){ 215 fprintf(stdout, "\t%d %s errors NOT PRINTED\n", 216 class_count[type], class_table[type]); 217 } else { 218 fprintf(stdout, "\n\t%d %s errors follow\n", 219 class_count[type], class_table[type]); 220 EITERATE(erpp, files, 0){ 221 errorp = *erpp; 222 if (errorp->error_e_class == type){ 223 errorprint(stdout, errorp, TRUE); 224 } 225 } 226 } 227 } 228 return(someerrors); 229} 230 231extern boolean notouch; 232 233boolean 234touchfiles(nfiles, files, r_edargc, r_edargv) 235 int nfiles; 236 Eptr **files; 237 int *r_edargc; 238 char ***r_edargv; 239{ 240 char *name; 241 Eptr errorp; 242 int fi; 243 Eptr *erpp; 244 int ntrueerrors; 245 boolean scribbled; 246 int n_pissed_on; /* # of file touched*/ 247 int spread; 248 249 FILEITERATE(fi, 1){ 250 name = (*files[fi])->error_text[0]; 251 spread = files[fi+1] - files[fi]; 252 fprintf(stdout, terse 253 ? "\"%s\" has %d error%s, " 254 : "\nFile \"%s\" has %d error%s.\n" 255 , name ,spread ,plural(spread)); 256 /* 257 * First, iterate through all error messages in this file 258 * to see how many of the error messages really will 259 * get inserted into the file. 260 */ 261 ntrueerrors = 0; 262 EITERATE(erpp, files, fi){ 263 errorp = *erpp; 264 if (errorp->error_e_class == C_TRUE) 265 ntrueerrors++; 266 } 267 fprintf(stdout, terse 268 ? "insert %d\n" 269 : "\t%d of these errors can be inserted into the file.\n", 270 ntrueerrors); 271 272 hackfile(name, files, fi, ntrueerrors); 273 } 274 scribbled = FALSE; 275 n_pissed_on = 0; 276 FILEITERATE(fi, 1){ 277 scribbled |= touchedfiles[fi]; 278 n_pissed_on++; 279 } 280 if (scribbled){ 281 /* 282 * Construct an execv argument 283 */ 284 execvarg(n_pissed_on, r_edargc, r_edargv); 285 return(TRUE); 286 } else { 287 if (!terse) 288 fprintf(stdout, "You didn't touch any files.\n"); 289 return(FALSE); 290 } 291} 292 293void 294hackfile(name, files, ix, nerrors) 295 char *name; 296 Eptr **files; 297 int ix; 298 int nerrors; 299{ 300 boolean previewed; 301 int errordest; /* where errors go*/ 302 303 if (!oktotouch(name)) { 304 previewed = FALSE; 305 errordest = TOSTDOUT; 306 } else { 307 previewed = preview(name, nerrors, files, ix); 308 errordest = settotouch(name); 309 } 310 311 if (errordest != TOSTDOUT) 312 touchedfiles[ix] = TRUE; 313 314 if (previewed && (errordest == TOSTDOUT)) 315 return; 316 317 diverterrors(name, errordest, files, ix, previewed, nerrors); 318 319 if (errordest == TOTHEFILE){ 320 /* 321 * overwrite the original file 322 */ 323 writetouched(1); 324 } 325} 326 327boolean 328preview(name, nerrors, files, ix) 329 char *name; 330 int nerrors; 331 Eptr **files; 332 int ix; 333{ 334 int back; 335 Eptr *erpp; 336 337 if (nerrors <= 0) 338 return(FALSE); 339 back = FALSE; 340 if(query){ 341 switch(inquire(terse 342 ? "Preview? " 343 : "Do you want to preview the errors first? ")){ 344 case Q_YES: 345 case Q_yes: 346 back = TRUE; 347 EITERATE(erpp, files, ix){ 348 errorprint(stdout, *erpp, TRUE); 349 } 350 if (!terse) 351 fprintf(stdout, "\n"); 352 default: 353 break; 354 } 355 } 356 return(back); 357} 358 359int 360settotouch(name) 361 char *name; 362{ 363 int dest = TOSTDOUT; 364 365 if (query){ 366 switch(touchstatus = inquire(terse 367 ? "Touch? " 368 : "Do you want to touch file \"%s\"? ", 369 name)){ 370 case Q_NO: 371 case Q_no: 372 return(dest); 373 default: 374 break; 375 } 376 } 377 378 switch(probethisfile(name)){ 379 case F_NOTREAD: 380 dest = TOSTDOUT; 381 fprintf(stdout, terse 382 ? "\"%s\" unreadable\n" 383 : "File \"%s\" is unreadable\n", 384 name); 385 break; 386 case F_NOTWRITE: 387 dest = TOSTDOUT; 388 fprintf(stdout, terse 389 ? "\"%s\" unwritable\n" 390 : "File \"%s\" is unwritable\n", 391 name); 392 break; 393 case F_NOTEXIST: 394 dest = TOSTDOUT; 395 fprintf(stdout, terse 396 ? "\"%s\" not found\n" 397 : "Can't find file \"%s\" to insert error messages into.\n", 398 name); 399 break; 400 default: 401 dest = edit(name) ? TOSTDOUT : TOTHEFILE; 402 break; 403 } 404 return(dest); 405} 406 407void 408diverterrors(name, dest, files, ix, previewed, nterrors) 409 char *name; 410 int dest; 411 Eptr **files; 412 int ix; 413 boolean previewed; 414 int nterrors; 415{ 416 int nerrors; 417 Eptr *erpp; 418 Eptr errorp; 419 420 nerrors = files[ix+1] - files[ix]; 421 422 if ( (nerrors != nterrors) 423 && (!previewed) ){ 424 fprintf(stdout, terse 425 ? "Uninserted errors\n" 426 : ">>Uninserted errors for file \"%s\" follow.\n", 427 name); 428 } 429 430 EITERATE(erpp, files, ix){ 431 errorp = *erpp; 432 if (errorp->error_e_class != C_TRUE){ 433 if (previewed || touchstatus == Q_NO) 434 continue; 435 errorprint(stdout, errorp, TRUE); 436 continue; 437 } 438 switch (dest){ 439 case TOSTDOUT: 440 if (previewed || touchstatus == Q_NO) 441 continue; 442 errorprint(stdout,errorp, TRUE); 443 break; 444 case TOTHEFILE: 445 insert(errorp->error_line); 446 text(errorp, FALSE); 447 break; 448 } 449 } 450} 451 452int 453oktotouch(filename) 454 char *filename; 455{ 456 extern char *suffixlist; 457 char *src; 458 char *pat; 459 char *osrc; 460 461 pat = suffixlist; 462 if (pat == 0) 463 return(0); 464 if (*pat == '*') 465 return(1); 466 while (*pat++ != '.') 467 continue; 468 --pat; /* point to the period */ 469 470 for (src = &filename[strlen(filename)], --src; 471 (src > filename) && (*src != '.'); --src) 472 continue; 473 if (*src != '.') 474 return(0); 475 476 for (src++, pat++, osrc = src; *src && *pat; src = osrc, pat++){ 477 for (; *src /* not at end of the source */ 478 && *pat /* not off end of pattern */ 479 && *pat != '.' /* not off end of sub pattern */ 480 && *pat != '*' /* not wild card */ 481 && *src == *pat; /* and equal... */ 482 src++, pat++) 483 continue; 484 if (*src == 0 && (*pat == 0 || *pat == '.' || *pat == '*')) 485 return(1); 486 if (*src != 0 && *pat == '*') 487 return(1); 488 while (*pat && *pat != '.') 489 pat++; 490 if (! *pat) 491 return(0); 492 } 493 return(0); 494} 495 496/* 497 * Construct an execv argument 498 * We need 1 argument for the editor's name 499 * We need 1 argument for the initial search string 500 * We need n_pissed_on arguments for the file names 501 * We need 1 argument that is a null for execv. 502 * The caller fills in the editor's name. 503 * We fill in the initial search string. 504 * We fill in the arguments, and the null. 505 */ 506void 507execvarg(n_pissed_on, r_argc, r_argv) 508 int n_pissed_on; 509 int *r_argc; 510 char ***r_argv; 511{ 512 Eptr p; 513 char *sep; 514 int fi; 515 516 sep = NULL; 517 (*r_argv) = (char **)Calloc(n_pissed_on + 3, sizeof(char *)); 518 (*r_argc) = n_pissed_on + 2; 519 (*r_argv)[1] = "+1;/###/"; 520 n_pissed_on = 2; 521 if (!terse){ 522 fprintf(stdout, "You touched file(s):"); 523 sep = " "; 524 } 525 FILEITERATE(fi, 1){ 526 if (!touchedfiles[fi]) 527 continue; 528 p = *(files[fi]); 529 if (!terse){ 530 fprintf(stdout,"%s\"%s\"", sep, p->error_text[0]); 531 sep = ", "; 532 } 533 (*r_argv)[n_pissed_on++] = p->error_text[0]; 534 } 535 if (!terse) 536 fprintf(stdout, "\n"); 537 (*r_argv)[n_pissed_on] = 0; 538} 539 540FILE *o_touchedfile; /* the old file */ 541FILE *n_touchedfile; /* the new file */ 542char *o_name; 543char n_name[64]; 544char *canon_name = _PATH_TMP; 545int o_lineno; 546int n_lineno; 547boolean tempfileopen = FALSE; 548/* 549 * open the file; guaranteed to be both readable and writable 550 * Well, if it isn't, then return TRUE if something failed 551 */ 552boolean 553edit(name) 554 char *name; 555{ 556 int fd; 557 558 o_name = name; 559 if ( (o_touchedfile = fopen(name, "r")) == NULL){ 560 fprintf(stderr, "%s: Can't open file \"%s\" to touch (read).\n", 561 processname, name); 562 return(TRUE); 563 } 564 (void)strcpy(n_name, canon_name); 565 fd = -1; 566 if ((fd = mkstemp(n_name)) == -1 || 567 (n_touchedfile = fdopen(fd, "w")) == NULL) { 568 if (fd != -1) 569 close(fd); 570 fprintf(stderr,"%s: Can't open file \"%s\" to touch (write).\n", 571 processname, name); 572 return(TRUE); 573 } 574 tempfileopen = TRUE; 575 n_lineno = 0; 576 o_lineno = 0; 577 return(FALSE); 578} 579 580/* 581 * Position to the line (before, after) the line given by place 582 */ 583char edbuf[BUFSIZ]; 584 585void 586insert(place) 587 int place; 588{ 589 --place; /* always insert messages before the offending line*/ 590 for(; o_lineno < place; o_lineno++, n_lineno++){ 591 if(fgets(edbuf, BUFSIZ, o_touchedfile) == NULL) 592 return; 593 fputs(edbuf, n_touchedfile); 594 } 595} 596 597void 598text(p, use_all) 599 Eptr p; 600 boolean use_all; 601{ 602 int offset = use_all ? 0 : 2; 603 604 fputs(lang_table[p->error_language].lang_incomment, n_touchedfile); 605 fprintf(n_touchedfile, "%d [%s] ", 606 p->error_line, 607 lang_table[p->error_language].lang_name); 608 wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset); 609 fputs(lang_table[p->error_language].lang_outcomment,n_touchedfile); 610 n_lineno++; 611} 612 613/* 614 * write the touched file to its temporary copy, 615 * then bring the temporary in over the local file 616 */ 617boolean 618writetouched(overwrite) 619 int overwrite; 620{ 621 int nread; 622 FILE *localfile; 623 FILE *tmpfile; 624 int botch; 625 int oktorm; 626 627 botch = 0; 628 oktorm = 1; 629 while ((nread = fread(edbuf, 1, sizeof(edbuf), o_touchedfile)) != 0) { 630 if (nread != fwrite(edbuf, 1, nread, n_touchedfile)){ 631 /* 632 * Catastrophe in temporary area: file system full? 633 */ 634 botch = 1; 635 fprintf(stderr, 636 "%s: write failure: No errors inserted in \"%s\"\n", 637 processname, o_name); 638 } 639 } 640 fclose(n_touchedfile); 641 fclose(o_touchedfile); 642 /* 643 * Now, copy the temp file back over the original 644 * file, thus preserving links, etc 645 */ 646 if (botch == 0 && overwrite){ 647 botch = 0; 648 localfile = NULL; 649 tmpfile = NULL; 650 if ((localfile = fopen(o_name, "w")) == NULL){ 651 fprintf(stderr, 652 "%s: Can't open file \"%s\" to overwrite.\n", 653 processname, o_name); 654 botch++; 655 } 656 if ((tmpfile = fopen(n_name, "r")) == NULL){ 657 fprintf(stderr, "%s: Can't open file \"%s\" to read.\n", 658 processname, n_name); 659 botch++; 660 } 661 if (!botch) 662 oktorm = mustoverwrite(localfile, tmpfile); 663 if (localfile != NULL) 664 fclose(localfile); 665 if (tmpfile != NULL) 666 fclose(tmpfile); 667 } 668 if (oktorm == 0){ 669 fprintf(stderr, "%s: Catastrophe: A copy of \"%s\": was saved in \"%s\"\n", 670 processname, o_name, n_name); 671 exit(1); 672 } 673 /* 674 * Kiss the temp file good bye 675 */ 676 unlink(n_name); 677 tempfileopen = FALSE; 678 return(TRUE); 679} 680 681/* 682 * return 1 if the tmpfile can be removed after writing it out 683 */ 684int 685mustoverwrite(preciousfile, tmpfile) 686 FILE *preciousfile; 687 FILE *tmpfile; 688{ 689 int nread; 690 691 while ((nread = fread(edbuf, 1, sizeof(edbuf), tmpfile)) != 0) { 692 if (mustwrite(edbuf, nread, preciousfile) == 0) 693 return(0); 694 } 695 return(1); 696} 697/* 698 * return 0 on catastrophe 699 */ 700int 701mustwrite(base, n, preciousfile) 702 char *base; 703 int n; 704 FILE *preciousfile; 705{ 706 int nwrote; 707 708 if (n <= 0) 709 return(1); 710 nwrote = fwrite(base, 1, n, preciousfile); 711 if (nwrote == n) 712 return(1); 713 perror(processname); 714 switch(inquire(terse 715 ? "Botch overwriting: retry? " 716 : "Botch overwriting the source file: retry? ")){ 717 case Q_YES: 718 case Q_yes: 719 mustwrite(base + nwrote, n - nwrote, preciousfile); 720 return(1); 721 case Q_NO: 722 case Q_no: 723 switch(inquire("Are you sure? ")){ 724 case Q_YES: 725 case Q_yes: 726 return(0); 727 case Q_NO: 728 case Q_no: 729 mustwrite(base + nwrote, n - nwrote, preciousfile); 730 return(1); 731 } 732 default: 733 return(0); 734 } 735} 736 737void 738onintr(dummy) 739 int dummy; 740{ 741 switch(inquire(terse 742 ? "\nContinue? " 743 : "\nInterrupt: Do you want to continue? ")){ 744 case Q_YES: 745 case Q_yes: 746 signal(SIGINT, onintr); 747 return; 748 default: 749 if (tempfileopen){ 750 /* 751 * Don't overwrite the original file! 752 */ 753 writetouched(0); 754 } 755 exit(1); 756 } 757 /*NOTREACHED*/ 758} 759 760void 761errorprint(place, errorp, print_all) 762 FILE *place; 763 Eptr errorp; 764 boolean print_all; 765{ 766 int offset = print_all ? 0 : 2; 767 768 if (errorp->error_e_class == C_IGNORE) 769 return; 770 fprintf(place, "[%s] ", lang_table[errorp->error_language].lang_name); 771 wordvprint(place,errorp->error_lgtext-offset,errorp->error_text+offset); 772 putc('\n', place); 773} 774 775int 776#if __STDC__ 777inquire(char *fmt, ...) 778#else 779inquire(fmt, va_alist) 780 char *fmt; 781 va_dcl 782#endif 783{ 784 va_list ap; 785 char buffer[128]; 786 787#if __STDC__ 788 va_start(ap, fmt); 789#else 790 va_start(ap); 791#endif 792 793 if (queryfile == NULL) 794 return(0); 795 for(;;){ 796 do{ 797 fflush(stdout); 798 vfprintf(stderr, fmt, ap); 799 fflush(stderr); 800 } while (fgets(buffer, 127, queryfile) == NULL); 801 switch(buffer[0]){ 802 case 'Y': return(Q_YES); 803 case 'y': return(Q_yes); 804 case 'N': return(Q_NO); 805 case 'n': return(Q_no); 806 default: fprintf(stderr, "Yes or No only!\n"); 807 } 808 } 809} 810 811int 812probethisfile(name) 813 char *name; 814{ 815 struct stat statbuf; 816 if (stat(name, &statbuf) < 0) 817 return(F_NOTEXIST); 818 if((statbuf.st_mode & S_IREAD) == 0) 819 return(F_NOTREAD); 820 if((statbuf.st_mode & S_IWRITE) == 0) 821 return(F_NOTWRITE); 822 return(F_TOUCHIT); 823} 824