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