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 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <stdio.h> 30 #include <ctype.h> 31 #include <sys/types.h> 32 #include <sys/stat.h> 33 #include <signal.h> 34 #include <string.h> 35 #include <stdlib.h> 36 #include <unistd.h> 37 #include <stdarg.h> 38 #include "error.h" 39 40 static void errorprint(FILE *place, Eptr errorp, boolean print_all); 41 static void text(Eptr p, boolean use_all); 42 static void insert(int place); 43 static void execvarg(int n_pissed_on, int *r_argc, char ***r_argv); 44 static void diverterrors(char *name, int dest, Eptr **files, int ix, 45 boolean previewed, int nterrors); 46 static void hackfile(char *name, Eptr **files, int ix, int nerrors); 47 static int countfiles(Eptr *errors); 48 static int nopertain(Eptr **files); 49 static int oktotouch(char *filename); 50 static boolean preview(int nerrors, Eptr **files, int ix); 51 static int settotouch(char *name); 52 static boolean edit(char *name); 53 static int mustoverwrite(FILE *preciousfile, FILE *tmpfile); 54 static int mustwrite(char *base, int n, FILE *preciousfile); 55 static void writetouched(int overwrite); 56 57 /* 58 * Iterate through errors 59 */ 60 #define EITERATE(p, fv, i) for (p = fv[i]; p < fv[i+1]; p++) 61 #define ECITERATE(ei, p, lb) \ 62 for (ei = lb; p = errors[ei], ei < nerrors; ei++) 63 64 #define FILEITERATE(fi, lb) for (fi = lb; fi <= nfiles; fi++) 65 int touchstatus = Q_YES; 66 67 void 68 findfiles(int nerrors, Eptr *errors, int *r_nfiles, Eptr ***r_files) 69 { 70 int nfiles; 71 Eptr **files; 72 73 char *name; 74 int ei; 75 int fi; 76 Eptr errorp; 77 78 nfiles = countfiles(errors); 79 80 files = Calloc(nfiles + 3, sizeof (Eptr*)); 81 touchedfiles = Calloc(nfiles+3, sizeof (boolean)); 82 /* 83 * Now, partition off the error messages 84 * into those that are synchronization, discarded or 85 * not specific to any file, and those that were 86 * nulled or true errors. 87 */ 88 files[0] = &errors[0]; 89 ECITERATE(ei, errorp, 0) { 90 if (!(NOTSORTABLE(errorp->error_e_class))) 91 break; 92 } 93 /* 94 * Now, and partition off all error messages 95 * for a given file. 96 */ 97 files[1] = &errors[ei]; 98 touchedfiles[0] = touchedfiles[1] = FALSE; 99 name = "\1"; 100 fi = 1; 101 ECITERATE(ei, errorp, ei) { 102 if ((errorp->error_e_class == C_NULLED) || 103 (errorp->error_e_class == C_TRUE)) { 104 if (strcmp(errorp->error_text[0], name) != 0) { 105 name = errorp->error_text[0]; 106 touchedfiles[fi] = FALSE; 107 files[fi] = &errors[ei]; 108 fi++; 109 } 110 } 111 } 112 files[fi] = &errors[nerrors]; 113 *r_nfiles = nfiles; 114 *r_files = files; 115 } 116 117 static int 118 countfiles(Eptr *errors) 119 { 120 char *name; 121 int ei; 122 Eptr errorp; 123 124 int nfiles; 125 nfiles = 0; 126 name = "\1"; 127 ECITERATE(ei, errorp, 0) { 128 if (SORTABLE(errorp->error_e_class)) { 129 if (strcmp(errorp->error_text[0], name) != 0) { 130 nfiles++; 131 name = errorp->error_text[0]; 132 } 133 } 134 } 135 return (nfiles); 136 } 137 138 char *class_table[] = { 139 /* C_UNKNOWN 0 */ "Unknown", 140 /* C_IGNORE 1 */ "ignore", 141 /* C_SYNC 2 */ "synchronization", 142 /* C_DISCARD 3 */ "discarded", 143 /* C_NONSPEC 4 */ "non specific", 144 /* C_THISFILE 5 */ "specific to this file", 145 /* C_NULLED 6 */ "nulled", 146 /* C_TRUE 7 */ "true", 147 /* C_DUPL 8 */ "duplicated" 148 }; 149 150 int class_count[C_LAST - C_FIRST] = {0}; 151 152 void 153 filenames(int nfiles, Eptr **files) 154 { 155 int fi; 156 char *sep = " "; 157 int someerrors; 158 159 /* 160 * first, simply dump out errors that 161 * don't pertain to any file 162 */ 163 someerrors = nopertain(files); 164 165 if (nfiles) { 166 someerrors++; 167 (void) fprintf(stdout, terse 168 ? "%d file%s" 169 : "%d file%s contain%s errors", 170 nfiles, plural(nfiles), verbform(nfiles)); 171 if (!terse) { 172 FILEITERATE(fi, 1) { 173 (void) fprintf(stdout, "%s\"%s\" (%d)", 174 sep, (*files[fi])->error_text[0], 175 files[fi+1] - files[fi]); 176 sep = ", "; 177 } 178 } 179 (void) fprintf(stdout, "\n"); 180 } 181 if (!someerrors) 182 (void) fprintf(stdout, "No errors.\n"); 183 } 184 185 /* 186 * Dump out errors that don't pertain to any file 187 */ 188 static int 189 nopertain(Eptr **files) 190 { 191 int type; 192 int someerrors = 0; 193 Eptr *erpp; 194 Eptr errorp; 195 196 if (files[1] - files[0] <= 0) 197 return (0); 198 for (type = C_UNKNOWN; NOTSORTABLE(type); type++) { 199 if (class_count[type] <= 0) 200 continue; 201 if (type > C_SYNC) 202 someerrors++; 203 if (terse) { 204 (void) fprintf(stdout, "\t%d %s errors NOT PRINTED\n", 205 class_count[type], class_table[type]); 206 } else { 207 (void) fprintf(stdout, "\n\t%d %s errors follow\n", 208 class_count[type], class_table[type]); 209 EITERATE(erpp, files, 0) { 210 errorp = *erpp; 211 if (errorp->error_e_class == type) { 212 errorprint(stdout, errorp, TRUE); 213 } 214 } 215 } 216 } 217 return (someerrors); 218 } 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 (void) 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 (void) fprintf(stdout, terse ? "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 (void) fprintf(stdout, "You didn't touch any files.\n"); 271 return (FALSE); 272 } 273 } 274 275 static 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(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 static boolean 306 preview(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 (void) fprintf(stdout, "\n"); 326 default: 327 break; 328 } 329 } 330 return (back); 331 } 332 333 static 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 (void) 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 (void) 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 (void) fprintf(stdout, 369 terse ? "\"%s\" not found\n" : 370 "Can't find file \"%s\" to insert error " 371 "messages into.\n", 372 name); 373 break; 374 default: 375 dest = edit(name) ? TOSTDOUT : TOTHEFILE; 376 break; 377 } 378 return (dest); 379 } 380 381 static void 382 diverterrors(char *name, int dest, Eptr **files, int ix, 383 boolean previewed, int nterrors) 384 { 385 int nerrors; 386 Eptr *erpp; 387 Eptr errorp; 388 389 nerrors = files[ix+1] - files[ix]; 390 391 if ((nerrors != nterrors) && (!previewed)) { 392 (void) 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 static int 421 oktotouch(char *filename) 422 { 423 extern char *suffixlist; 424 char *src; 425 char *pat; 426 char *osrc; 427 428 pat = suffixlist; 429 if (pat == 0) 430 return (0); 431 if (*pat == '*') 432 return (1); 433 while (*pat++ != '.') 434 continue; 435 --pat; /* point to the period */ 436 437 for (src = &filename[strlen(filename)], --src; 438 (src > filename) && (*src != '.'); --src) 439 continue; 440 if (*src != '.') 441 return (0); 442 443 for (src++, pat++, osrc = src; *src && *pat; src = osrc, pat++) { 444 for (; *src && /* not at end of the source */ 445 *pat && /* not off end of pattern */ 446 *pat != '.' && /* not off end of sub pattern */ 447 *pat != '*' && /* not wild card */ 448 *src == *pat; /* and equal... */ 449 src++, pat++) 450 continue; 451 if (*src == 0 && (*pat == 0 || *pat == '.' || *pat == '*')) 452 return (1); 453 if (*src != 0 && *pat == '*') 454 return (1); 455 while (*pat && *pat != '.') 456 pat++; 457 if (! *pat) 458 return (0); 459 } 460 return (0); 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 static 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 (*r_argv) = Calloc(n_pissed_on + 3, sizeof (char *)); 480 (*r_argc) = n_pissed_on + 2; 481 (*r_argv)[1] = "+1;/###/"; 482 n_pissed_on = 2; 483 if (!terse) { 484 (void) fprintf(stdout, "You touched file(s):"); 485 sep = " "; 486 } 487 FILEITERATE(fi, 1) { 488 if (!touchedfiles[fi]) 489 continue; 490 p = *(files[fi]); 491 if (!terse) { 492 (void) fprintf(stdout, "%s\"%s\"", sep, 493 p->error_text[0]); 494 sep = ", "; 495 } 496 (*r_argv)[n_pissed_on++] = p->error_text[0]; 497 } 498 if (!terse) 499 (void) 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[64]; 507 char *canon_name = "/tmp/ErrorXXXXXX"; 508 int o_lineno; 509 int n_lineno; 510 boolean tempfileopen = FALSE; 511 /* 512 * open the file; guaranteed to be both readable and writable 513 * Well, if it isn't, then return TRUE if something failed 514 */ 515 static boolean 516 edit(char *name) 517 { 518 o_name = name; 519 if ((o_touchedfile = fopen(name, "r")) == NULL) { 520 (void) fprintf(stderr, 521 "%s: Can't open file \"%s\" to touch (read).\n", 522 processname, name); 523 return (TRUE); 524 } 525 (void) strcpy(n_name, canon_name); 526 (void) mktemp(n_name); 527 if ((n_touchedfile = fopen(n_name, "w")) == NULL) { 528 (void) fprintf(stderr, 529 "%s: Can't open file \"%s\" to touch (write).\n", 530 processname, name); 531 return (TRUE); 532 } 533 tempfileopen = TRUE; 534 n_lineno = 0; 535 o_lineno = 0; 536 return (FALSE); 537 } 538 /* 539 * Position to the line (before, after) the line given by place 540 */ 541 char edbuf[BUFSIZ]; 542 543 static void 544 insert(int place) 545 { 546 --place; /* always insert messages before the offending line */ 547 for (; o_lineno < place; o_lineno++, n_lineno++) { 548 if (fgets(edbuf, BUFSIZ, o_touchedfile) == NULL) 549 return; 550 (void) fputs(edbuf, n_touchedfile); 551 } 552 } 553 554 static void 555 text(Eptr p, boolean use_all) 556 { 557 int offset = use_all ? 0 : 2; 558 559 (void) fputs(lang_table[p->error_language].lang_incomment, 560 n_touchedfile); 561 (void) fprintf(n_touchedfile, "%d [%s] ", 562 p->error_line, 563 lang_table[p->error_language].lang_name); 564 wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset); 565 (void) fputs(lang_table[p->error_language].lang_outcomment, 566 n_touchedfile); 567 n_lineno++; 568 } 569 570 /* 571 * write the touched file to its temporary copy, 572 * then bring the temporary in over the local file 573 */ 574 static void 575 writetouched(int overwrite) 576 { 577 int nread; 578 FILE *localfile; 579 FILE *tmpfile; 580 int botch; 581 int oktorm; 582 583 botch = 0; 584 oktorm = 1; 585 while ((nread = fread(edbuf, 1, sizeof (edbuf), 586 o_touchedfile)) != NULL) { 587 if (nread != fwrite(edbuf, 1, nread, n_touchedfile)) { 588 /* 589 * Catastrophe in temporary area: file system full? 590 */ 591 botch = 1; 592 (void) fprintf(stderr, 593 "%s: write failure: No errors inserted in \"%s\"\n", 594 processname, o_name); 595 } 596 } 597 (void) fclose(n_touchedfile); 598 (void) fclose(o_touchedfile); 599 /* 600 * Now, copy the temp file back over the original 601 * file, thus preserving links, etc 602 */ 603 if (botch == 0 && overwrite) { 604 botch = 0; 605 localfile = NULL; 606 tmpfile = NULL; 607 if ((localfile = fopen(o_name, "w")) == NULL) { 608 (void) fprintf(stderr, 609 "%s: Can't open file \"%s\" to overwrite.\n", 610 processname, o_name); 611 botch++; 612 } 613 if ((tmpfile = fopen(n_name, "r")) == NULL) { 614 (void) fprintf(stderr, 615 "%s: Can't open file \"%s\" to read.\n", 616 processname, n_name); 617 botch++; 618 } 619 if (!botch) 620 oktorm = mustoverwrite(localfile, tmpfile); 621 if (localfile != NULL) 622 (void) fclose(localfile); 623 if (tmpfile != NULL) 624 (void) fclose(tmpfile); 625 } 626 if (oktorm == 0) { 627 (void) fprintf(stderr, 628 "%s: Catastrophe: A copy of \"%s: was saved in \"%s\"\n", 629 processname, o_name, n_name); 630 exit(1); 631 } 632 /* 633 * Kiss the temp file good bye 634 */ 635 (void) unlink(n_name); 636 tempfileopen = FALSE; 637 } 638 /* 639 * return 1 if the tmpfile can be removed after writing it out 640 */ 641 static int 642 mustoverwrite(FILE *preciousfile, FILE *tmpfile) 643 { 644 int nread; 645 646 while ((nread = fread(edbuf, 1, sizeof (edbuf), tmpfile)) != NULL) { 647 if (mustwrite(edbuf, nread, preciousfile) == 0) 648 return (0); 649 } 650 return (1); 651 } 652 /* 653 * return 0 on catastrophe 654 */ 655 static int 656 mustwrite(char *base, int n, FILE *preciousfile) 657 { 658 int nwrote; 659 660 if (n <= 0) 661 return (1); 662 nwrote = fwrite(base, 1, n, preciousfile); 663 if (nwrote == n) 664 return (1); 665 perror(processname); 666 switch (inquire(terse 667 ? "Botch overwriting: retry? " 668 : "Botch overwriting the source file: retry? ")) { 669 case Q_YES: 670 case Q_yes: 671 (void) mustwrite(base + nwrote, n - nwrote, preciousfile); 672 return (1); 673 case Q_NO: 674 case Q_no: 675 switch (inquire("Are you sure? ")) { 676 case Q_YES: 677 case Q_yes: 678 return (0); 679 case Q_NO: 680 case Q_no: 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