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