1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 */ 27 28 #include <stdio.h> 29 #include <ctype.h> 30 #include <sys/types.h> 31 #include <sys/stat.h> 32 #include <signal.h> 33 #include <string.h> 34 #include <stdlib.h> 35 #include <unistd.h> 36 #include <stdarg.h> 37 #include "error.h" 38 39 static void errorprint(FILE *place, Eptr errorp, boolean print_all); 40 static void text(Eptr p, boolean use_all); 41 static void insert(int place); 42 static void execvarg(int n_pissed_on, int *r_argc, char ***r_argv); 43 static void diverterrors(char *name, int dest, Eptr **files, int ix, 44 boolean previewed, int nterrors); 45 static void hackfile(char *name, Eptr **files, int ix, int nerrors); 46 static int countfiles(Eptr *errors); 47 static int nopertain(Eptr **files); 48 static int oktotouch(char *filename); 49 static boolean preview(int nerrors, Eptr **files, int ix); 50 static int settotouch(char *name); 51 static boolean edit(char *name); 52 static int mustoverwrite(FILE *preciousfile, FILE *tmpfile); 53 static int mustwrite(char *base, int n, FILE *preciousfile); 54 static void writetouched(int overwrite); 55 56 /* 57 * Iterate through errors 58 */ 59 #define EITERATE(p, fv, i) for (p = fv[i]; p < fv[i+1]; p++) 60 #define ECITERATE(ei, p, lb) \ 61 for (ei = lb; p = errors[ei], ei < nerrors; ei++) 62 63 #define FILEITERATE(fi, lb) for (fi = lb; fi <= nfiles; fi++) 64 int touchstatus = Q_YES; 65 66 void 67 findfiles(int nerrors, Eptr *errors, int *r_nfiles, Eptr ***r_files) 68 { 69 int nfiles; 70 Eptr **files; 71 72 char *name; 73 int ei; 74 int fi; 75 Eptr errorp; 76 77 nfiles = countfiles(errors); 78 79 files = Calloc(nfiles + 3, sizeof (Eptr*)); 80 touchedfiles = Calloc(nfiles+3, sizeof (boolean)); 81 /* 82 * Now, partition off the error messages 83 * into those that are synchronization, discarded or 84 * not specific to any file, and those that were 85 * nulled or true errors. 86 */ 87 files[0] = &errors[0]; 88 ECITERATE(ei, errorp, 0) { 89 if (!(NOTSORTABLE(errorp->error_e_class))) 90 break; 91 } 92 /* 93 * Now, and partition off all error messages 94 * for a given file. 95 */ 96 files[1] = &errors[ei]; 97 touchedfiles[0] = touchedfiles[1] = FALSE; 98 name = "\1"; 99 fi = 1; 100 ECITERATE(ei, errorp, ei) { 101 if ((errorp->error_e_class == C_NULLED) || 102 (errorp->error_e_class == C_TRUE)) { 103 if (strcmp(errorp->error_text[0], name) != 0) { 104 name = errorp->error_text[0]; 105 touchedfiles[fi] = FALSE; 106 files[fi] = &errors[ei]; 107 fi++; 108 } 109 } 110 } 111 files[fi] = &errors[nerrors]; 112 *r_nfiles = nfiles; 113 *r_files = files; 114 } 115 116 static int 117 countfiles(Eptr *errors) 118 { 119 char *name; 120 int ei; 121 Eptr errorp; 122 123 int nfiles; 124 nfiles = 0; 125 name = "\1"; 126 ECITERATE(ei, errorp, 0) { 127 if (SORTABLE(errorp->error_e_class)) { 128 if (strcmp(errorp->error_text[0], name) != 0) { 129 nfiles++; 130 name = errorp->error_text[0]; 131 } 132 } 133 } 134 return (nfiles); 135 } 136 137 char *class_table[] = { 138 /* C_UNKNOWN 0 */ "Unknown", 139 /* C_IGNORE 1 */ "ignore", 140 /* C_SYNC 2 */ "synchronization", 141 /* C_DISCARD 3 */ "discarded", 142 /* C_NONSPEC 4 */ "non specific", 143 /* C_THISFILE 5 */ "specific to this file", 144 /* C_NULLED 6 */ "nulled", 145 /* C_TRUE 7 */ "true", 146 /* C_DUPL 8 */ "duplicated" 147 }; 148 149 int class_count[C_LAST - C_FIRST] = {0}; 150 151 void 152 filenames(int nfiles, Eptr **files) 153 { 154 int fi; 155 char *sep = " "; 156 int someerrors; 157 158 /* 159 * first, simply dump out errors that 160 * don't pertain to any file 161 */ 162 someerrors = nopertain(files); 163 164 if (nfiles) { 165 someerrors++; 166 (void) fprintf(stdout, terse 167 ? "%d file%s" 168 : "%d file%s contain%s errors", 169 nfiles, plural(nfiles), verbform(nfiles)); 170 if (!terse) { 171 FILEITERATE(fi, 1) { 172 (void) fprintf(stdout, "%s\"%s\" (%d)", 173 sep, (*files[fi])->error_text[0], 174 files[fi+1] - files[fi]); 175 sep = ", "; 176 } 177 } 178 (void) fprintf(stdout, "\n"); 179 } 180 if (!someerrors) 181 (void) fprintf(stdout, "No errors.\n"); 182 } 183 184 /* 185 * Dump out errors that don't pertain to any file 186 */ 187 static int 188 nopertain(Eptr **files) 189 { 190 int type; 191 int someerrors = 0; 192 Eptr *erpp; 193 Eptr errorp; 194 195 if (files[1] - files[0] <= 0) 196 return (0); 197 for (type = C_UNKNOWN; NOTSORTABLE(type); type++) { 198 if (class_count[type] <= 0) 199 continue; 200 if (type > C_SYNC) 201 someerrors++; 202 if (terse) { 203 (void) fprintf(stdout, "\t%d %s errors NOT PRINTED\n", 204 class_count[type], class_table[type]); 205 } else { 206 (void) fprintf(stdout, "\n\t%d %s errors follow\n", 207 class_count[type], class_table[type]); 208 EITERATE(erpp, files, 0) { 209 errorp = *erpp; 210 if (errorp->error_e_class == type) { 211 errorprint(stdout, errorp, TRUE); 212 } 213 } 214 } 215 } 216 return (someerrors); 217 } 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 (void) 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 (void) fprintf(stdout, terse ? "insert %d\n" : 250 "\t%d of these errors can be inserted into the file.\n", 251 ntrueerrors); 252 253 hackfile(name, files, fi, ntrueerrors); 254 } 255 scribbled = FALSE; 256 n_pissed_on = 0; 257 FILEITERATE(fi, 1) { 258 scribbled |= touchedfiles[fi]; 259 n_pissed_on++; 260 } 261 if (scribbled) { 262 /* 263 * Construct an execv argument 264 */ 265 execvarg(n_pissed_on, r_edargc, r_edargv); 266 return (TRUE); 267 } else { 268 if (!terse) 269 (void) fprintf(stdout, "You didn't touch any files.\n"); 270 return (FALSE); 271 } 272 } 273 274 static void 275 hackfile(char *name, Eptr **files, int ix, int nerrors) 276 { 277 boolean previewed; 278 int errordest; /* where errors go */ 279 280 if (!oktotouch(name)) { 281 previewed = FALSE; 282 errordest = TOSTDOUT; 283 } else { 284 previewed = preview(nerrors, files, ix); 285 errordest = settotouch(name); 286 } 287 288 if (errordest != TOSTDOUT) 289 touchedfiles[ix] = TRUE; 290 291 if (previewed && (errordest == TOSTDOUT)) 292 return; 293 294 diverterrors(name, errordest, files, ix, previewed, nerrors); 295 296 if (errordest == TOTHEFILE) { 297 /* 298 * overwrite the original file 299 */ 300 writetouched(1); 301 } 302 } 303 304 static boolean 305 preview(int nerrors, Eptr **files, int ix) 306 { 307 int back; 308 Eptr *erpp; 309 310 if (nerrors <= 0) 311 return (FALSE); 312 back = FALSE; 313 if (query) { 314 switch (inquire(terse 315 ? "Preview? " 316 : "Do you want to preview the errors first? ")) { 317 case Q_YES: 318 case Q_yes: 319 back = TRUE; 320 EITERATE(erpp, files, ix) { 321 errorprint(stdout, *erpp, TRUE); 322 } 323 if (!terse) 324 (void) fprintf(stdout, "\n"); 325 default: 326 break; 327 } 328 } 329 return (back); 330 } 331 332 static int 333 settotouch(char *name) 334 { 335 int dest = TOSTDOUT; 336 337 if (query) { 338 switch (touchstatus = inquire(terse 339 ? "Touch? " 340 : "Do you want to touch file \"%s\"? ", 341 name)) { 342 case Q_NO: 343 case Q_no: 344 return (dest); 345 default: 346 break; 347 } 348 } 349 350 switch (probethisfile(name)) { 351 case F_NOTREAD: 352 dest = TOSTDOUT; 353 (void) fprintf(stdout, terse 354 ? "\"%s\" unreadable\n" 355 : "File \"%s\" is unreadable\n", 356 name); 357 break; 358 case F_NOTWRITE: 359 dest = TOSTDOUT; 360 (void) fprintf(stdout, terse 361 ? "\"%s\" unwritable\n" 362 : "File \"%s\" is unwritable\n", 363 name); 364 break; 365 case F_NOTEXIST: 366 dest = TOSTDOUT; 367 (void) fprintf(stdout, 368 terse ? "\"%s\" not found\n" : 369 "Can't find file \"%s\" to insert error " 370 "messages into.\n", 371 name); 372 break; 373 default: 374 dest = edit(name) ? TOSTDOUT : TOTHEFILE; 375 break; 376 } 377 return (dest); 378 } 379 380 static void 381 diverterrors(char *name, int dest, Eptr **files, int ix, 382 boolean previewed, 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) && (!previewed)) { 391 (void) fprintf(stdout, terse 392 ? "Uninserted errors\n" 393 : ">>Uninserted errors for file \"%s\" follow.\n", 394 name); 395 } 396 397 EITERATE(erpp, files, ix) { 398 errorp = *erpp; 399 if (errorp->error_e_class != C_TRUE) { 400 if (previewed || touchstatus == Q_NO) 401 continue; 402 errorprint(stdout, errorp, TRUE); 403 continue; 404 } 405 switch (dest) { 406 case TOSTDOUT: 407 if (previewed || touchstatus == Q_NO) 408 continue; 409 errorprint(stdout, errorp, TRUE); 410 break; 411 case TOTHEFILE: 412 insert(errorp->error_line); 413 text(errorp, FALSE); 414 break; 415 } 416 } 417 } 418 419 static int 420 oktotouch(char *filename) 421 { 422 extern char *suffixlist; 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 * Construct an execv argument 463 * We need 1 argument for the editor's name 464 * We need 1 argument for the initial search string 465 * We need n_pissed_on arguments for the file names 466 * We need 1 argument that is a null for execv. 467 * The caller fills in the editor's name. 468 * We fill in the initial search string. 469 * We fill in the arguments, and the null. 470 */ 471 static void 472 execvarg(int n_pissed_on, int *r_argc, char ***r_argv) 473 { 474 Eptr p; 475 char *sep; 476 int fi; 477 478 (*r_argv) = Calloc(n_pissed_on + 3, sizeof (char *)); 479 (*r_argc) = n_pissed_on + 2; 480 (*r_argv)[1] = "+1;/###/"; 481 n_pissed_on = 2; 482 if (!terse) { 483 (void) fprintf(stdout, "You touched file(s):"); 484 sep = " "; 485 } 486 FILEITERATE(fi, 1) { 487 if (!touchedfiles[fi]) 488 continue; 489 p = *(files[fi]); 490 if (!terse) { 491 (void) fprintf(stdout, "%s\"%s\"", sep, 492 p->error_text[0]); 493 sep = ", "; 494 } 495 (*r_argv)[n_pissed_on++] = p->error_text[0]; 496 } 497 if (!terse) 498 (void) fprintf(stdout, "\n"); 499 (*r_argv)[n_pissed_on] = 0; 500 } 501 502 FILE *o_touchedfile; /* the old file */ 503 FILE *n_touchedfile; /* the new file */ 504 char *o_name; 505 char n_name[64]; 506 char *canon_name = "/tmp/ErrorXXXXXX"; 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 static boolean 515 edit(char *name) 516 { 517 o_name = name; 518 if ((o_touchedfile = fopen(name, "r")) == NULL) { 519 (void) fprintf(stderr, 520 "%s: Can't open file \"%s\" to touch (read).\n", 521 processname, name); 522 return (TRUE); 523 } 524 (void) strcpy(n_name, canon_name); 525 (void) mktemp(n_name); 526 if ((n_touchedfile = fopen(n_name, "w")) == NULL) { 527 (void) fprintf(stderr, 528 "%s: Can't open file \"%s\" to touch (write).\n", 529 processname, name); 530 return (TRUE); 531 } 532 tempfileopen = TRUE; 533 n_lineno = 0; 534 o_lineno = 0; 535 return (FALSE); 536 } 537 /* 538 * Position to the line (before, after) the line given by place 539 */ 540 char edbuf[BUFSIZ]; 541 542 static void 543 insert(int place) 544 { 545 --place; /* always insert messages before the offending line */ 546 for (; o_lineno < place; o_lineno++, n_lineno++) { 547 if (fgets(edbuf, BUFSIZ, o_touchedfile) == NULL) 548 return; 549 (void) fputs(edbuf, n_touchedfile); 550 } 551 } 552 553 static void 554 text(Eptr p, boolean use_all) 555 { 556 int offset = use_all ? 0 : 2; 557 558 (void) fputs(lang_table[p->error_language].lang_incomment, 559 n_touchedfile); 560 (void) fprintf(n_touchedfile, "%d [%s] ", 561 p->error_line, 562 lang_table[p->error_language].lang_name); 563 wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset); 564 (void) fputs(lang_table[p->error_language].lang_outcomment, 565 n_touchedfile); 566 n_lineno++; 567 } 568 569 /* 570 * write the touched file to its temporary copy, 571 * then bring the temporary in over the local file 572 */ 573 static void 574 writetouched(int overwrite) 575 { 576 int nread; 577 FILE *localfile; 578 FILE *tmpfile; 579 int botch; 580 int oktorm; 581 582 botch = 0; 583 oktorm = 1; 584 while ((nread = fread(edbuf, 1, sizeof (edbuf), 585 o_touchedfile)) != 0) { 586 if (nread != fwrite(edbuf, 1, nread, n_touchedfile)) { 587 /* 588 * Catastrophe in temporary area: file system full? 589 */ 590 botch = 1; 591 (void) fprintf(stderr, 592 "%s: write failure: No errors inserted in \"%s\"\n", 593 processname, o_name); 594 } 595 } 596 (void) fclose(n_touchedfile); 597 (void) fclose(o_touchedfile); 598 /* 599 * Now, copy the temp file back over the original 600 * file, thus preserving links, etc 601 */ 602 if (botch == 0 && overwrite) { 603 botch = 0; 604 localfile = NULL; 605 tmpfile = NULL; 606 if ((localfile = fopen(o_name, "w")) == NULL) { 607 (void) fprintf(stderr, 608 "%s: Can't open file \"%s\" to overwrite.\n", 609 processname, o_name); 610 botch++; 611 } 612 if ((tmpfile = fopen(n_name, "r")) == NULL) { 613 (void) fprintf(stderr, 614 "%s: Can't open file \"%s\" to read.\n", 615 processname, n_name); 616 botch++; 617 } 618 if (!botch) 619 oktorm = mustoverwrite(localfile, tmpfile); 620 if (localfile != NULL) 621 (void) fclose(localfile); 622 if (tmpfile != NULL) 623 (void) fclose(tmpfile); 624 } 625 if (oktorm == 0) { 626 (void) fprintf(stderr, 627 "%s: Catastrophe: A copy of \"%s: was saved in \"%s\"\n", 628 processname, o_name, n_name); 629 exit(1); 630 } 631 /* 632 * Kiss the temp file good bye 633 */ 634 (void) unlink(n_name); 635 tempfileopen = FALSE; 636 } 637 /* 638 * return 1 if the tmpfile can be removed after writing it out 639 */ 640 static int 641 mustoverwrite(FILE *preciousfile, FILE *tmpfile) 642 { 643 int nread; 644 645 while ((nread = fread(edbuf, 1, sizeof (edbuf), tmpfile)) != 0) { 646 if (mustwrite(edbuf, nread, preciousfile) == 0) 647 return (0); 648 } 649 return (1); 650 } 651 /* 652 * return 0 on catastrophe 653 */ 654 static int 655 mustwrite(char *base, int n, FILE *preciousfile) 656 { 657 int nwrote; 658 659 if (n <= 0) 660 return (1); 661 nwrote = fwrite(base, 1, n, preciousfile); 662 if (nwrote == n) 663 return (1); 664 perror(processname); 665 switch (inquire(terse 666 ? "Botch overwriting: retry? " 667 : "Botch overwriting the source file: retry? ")) { 668 case Q_YES: 669 case Q_yes: 670 (void) mustwrite(base + nwrote, n - nwrote, preciousfile); 671 return (1); 672 case Q_NO: 673 case Q_no: 674 switch (inquire("Are you sure? ")) { 675 case Q_YES: 676 case Q_yes: 677 return (0); 678 case Q_NO: 679 case Q_no: 680 default: 681 (void) mustwrite(base + nwrote, n - nwrote, 682 preciousfile); 683 return (1); 684 } 685 default: 686 return (0); 687 } 688 } 689 690 /* ARGSUSED */ 691 void 692 onintr(int sig) 693 { 694 switch (inquire(terse 695 ? "\nContinue? " 696 : "\nInterrupt: Do you want to continue? ")) { 697 case Q_YES: 698 case Q_yes: 699 (void) signal(SIGINT, onintr); 700 return; 701 default: 702 if (tempfileopen) { 703 /* 704 * Don't overwrite the original file! 705 */ 706 writetouched(0); 707 } 708 exit(1); 709 } 710 /*NOTREACHED*/ 711 } 712 713 static void 714 errorprint(FILE *place, Eptr errorp, boolean print_all) 715 { 716 int offset = print_all ? 0 : 2; 717 718 if (errorp->error_e_class == C_IGNORE) 719 return; 720 (void) fprintf(place, "[%s] ", 721 lang_table[errorp->error_language].lang_name); 722 wordvprint(place, errorp->error_lgtext-offset, 723 errorp->error_text+offset); 724 (void) putc('\n', place); 725 } 726 727 /*PRINTFLIKE1*/ 728 int 729 inquire(char *format, ...) 730 { 731 char buffer[128]; 732 va_list args; 733 734 if (queryfile == NULL) 735 return (0); 736 for (;;) { 737 do { 738 va_start(args, format); 739 (void) fflush(stdout); 740 (void) vfprintf(stderr, format, args); 741 (void) fflush(stderr); 742 va_end(args); 743 } while (fgets(buffer, 127, queryfile) == NULL); 744 switch (buffer[0]) { 745 case 'Y': return (Q_YES); 746 case 'y': return (Q_yes); 747 case 'N': return (Q_NO); 748 case 'n': return (Q_no); 749 default: (void) fprintf(stderr, "Yes or No only!\n"); 750 } 751 } 752 } 753 754 int 755 probethisfile(char *name) 756 { 757 struct stat statbuf; 758 if (stat(name, &statbuf) < 0) 759 return (F_NOTEXIST); 760 if ((statbuf.st_mode & S_IREAD) == 0) 761 return (F_NOTREAD); 762 if ((statbuf.st_mode & S_IWRITE) == 0) 763 return (F_NOTWRITE); 764 return (F_TOUCHIT); 765 } 766