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
findfiles(int nerrors,Eptr * errors,int * r_nfiles,Eptr *** r_files)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
countfiles(Eptr * errors)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
filenames(int nfiles,Eptr ** files)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
nopertain(Eptr ** files)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
touchfiles(int nfiles,Eptr ** files,int * r_edargc,char *** r_edargv)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
hackfile(char * name,Eptr ** files,int ix,int nerrors)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
preview(int nerrors,Eptr ** files,int ix)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
settotouch(char * name)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
diverterrors(char * name,int dest,Eptr ** files,int ix,boolean previewed,int nterrors)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
oktotouch(char * filename)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
execvarg(int n_pissed_on,int * r_argc,char *** r_argv)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
edit(char * name)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
insert(int place)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
text(Eptr p,boolean use_all)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
writetouched(int overwrite)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
mustoverwrite(FILE * preciousfile,FILE * tmpfile)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
mustwrite(char * base,int n,FILE * preciousfile)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
onintr(int sig)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
errorprint(FILE * place,Eptr errorp,boolean print_all)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
inquire(char * format,...)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
probethisfile(char * name)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