Fail safe when overwriting the error source file, to protect
[unix-history] / usr / src / usr.bin / error / touch.c
CommitLineData
4d9f5f65 1static char *sccsid = "@(#)touch.c 1.5 (Berkeley) %G%";
c99ba90a
BJ
2#include <stdio.h>
3#include <ctype.h>
4#include <sys/types.h>
5#include <sys/stat.h>
6#include <signal.h>
7#include "error.h"
8
58195f21
RH
9/*
10 * Iterate through errors
11 */
12#define EITERATE(p, fv, i) for (p = fv[i]; p < fv[i+1]; p++)
13#define ECITERATE(ei, p, lb) for (ei = lb; p = errors[ei],ei < nerrors; ei++)
14
15#define FILEITERATE(fi, lb) for (fi = lb; fi <= nfiles; fi++)
16int touchstatus = Q_YES;
17
c99ba90a 18findfiles(nerrors, errors, r_nfiles, r_files)
58195f21
RH
19 int nerrors;
20 Eptr *errors;
21 int *r_nfiles;
22 Eptr ***r_files;
c99ba90a 23{
58195f21
RH
24 int nfiles;
25 Eptr **files;
c99ba90a 26
58195f21
RH
27 char *name;
28 reg int ei;
29 int fi;
30 reg Eptr errorp;
31
32 nfiles = countfiles(errors);
33
34 files = (Eptr**)Calloc(nfiles + 3, sizeof (Eptr*));
c99ba90a
BJ
35 touchedfiles = (boolean *)Calloc(nfiles+3, sizeof(boolean));
36 /*
58195f21 37 * Now, partition off the error messages
c99ba90a
BJ
38 * into those that are synchronization, discarded or
39 * not specific to any file, and those that were
40 * nulled or true errors.
41 */
42 files[0] = &errors[0];
58195f21
RH
43 ECITERATE(ei, errorp, 0){
44 if ( ! (NOTSORTABLE(errorp->error_e_class)))
45 break;
c99ba90a
BJ
46 }
47 /*
58195f21 48 * Now, and partition off all error messages
c99ba90a
BJ
49 * for a given file.
50 */
58195f21 51 files[1] = &errors[ei];
c99ba90a 52 touchedfiles[0] = touchedfiles[1] = FALSE;
58195f21
RH
53 name = "\1";
54 fi = 1;
55 ECITERATE(ei, errorp, ei){
56 if ( (errorp->error_e_class == C_NULLED)
57 || (errorp->error_e_class == C_TRUE) ){
58 if (strcmp(errorp->error_text[0], name) != 0){
59 name = errorp->error_text[0];
60 touchedfiles[fi] = FALSE;
61 files[fi] = &errors[ei];
62 fi++;
c99ba90a
BJ
63 }
64 }
65 }
58195f21 66 files[fi] = &errors[nerrors];
c99ba90a
BJ
67 *r_nfiles = nfiles;
68 *r_files = files;
69}
70
58195f21
RH
71int countfiles(errors)
72 Eptr *errors;
73{
74 char *name;
75 int ei;
76 reg Eptr errorp;
77
78 int nfiles;
79 nfiles = 0;
80 name = "\1";
81 ECITERATE(ei, errorp, 0){
82 if (SORTABLE(errorp->error_e_class)){
83 if (strcmp(errorp->error_text[0],name) != 0){
84 nfiles++;
85 name = errorp->error_text[0];
86 }
87 }
88 }
89 return(nfiles);
90}
c99ba90a
BJ
91char *class_table[] = {
92 /*C_UNKNOWN 0 */ "Unknown",
93 /*C_IGNORE 1 */ "ignore",
94 /*C_SYNC 2 */ "synchronization",
95 /*C_DISCARD 3 */ "discarded",
96 /*C_NONSPEC 4 */ "non specific",
97 /*C_THISFILE 5 */ "specific to this file",
98 /*C_NULLED 6 */ "nulled",
99 /*C_TRUE 7 */ "true",
100 /*C_DUPL 8 */ "duplicated"
101};
102
103int class_count[C_LAST - C_FIRST] = {0};
104
105filenames(nfiles, files)
106 int nfiles;
58195f21 107 Eptr **files;
c99ba90a 108{
58195f21
RH
109 reg int fi;
110 char *sep = " ";
111 extern char *class_table[];
112 int someerrors;
c99ba90a
BJ
113
114 /*
58195f21 115 * first, simply dump out errors that
c99ba90a
BJ
116 * don't pertain to any file
117 */
58195f21
RH
118 someerrors = nopertain(files);
119
c99ba90a
BJ
120 if (nfiles){
121 someerrors++;
58195f21
RH
122 fprintf(stdout, terse
123 ? "%d file%s"
124 : "%d file%s contain%s errors",
125 nfiles, plural(nfiles), verbform(nfiles));
126 if (!terse){
127 FILEITERATE(fi, 1){
128 fprintf(stdout, "%s\"%s\" (%d)",
129 sep, (*files[fi])->error_text[0],
130 files[fi+1] - files[fi]);
131 sep = ", ";
132 }
c99ba90a
BJ
133 }
134 fprintf(stdout, "\n");
135 }
136 if (!someerrors)
137 fprintf(stdout, "No errors.\n");
138}
139
58195f21
RH
140/*
141 * Dump out errors that don't pertain to any file
142 */
143int nopertain(files)
144 Eptr **files;
145{
146 int type;
147 int someerrors = 0;
148 reg Eptr *erpp;
149 reg Eptr errorp;
150
151 if (files[1] - files[0] <= 0)
152 return(0);
153 for(type = C_UNKNOWN; NOTSORTABLE(type); type++){
154 if (class_count[type] <= 0)
155 continue;
156 if (type > C_SYNC)
157 someerrors++;
158 if (terse){
159 fprintf(stdout, "\t%d %s errors NOT PRINTED\n",
160 class_count[type], class_table[type]);
161 } else {
162 fprintf(stdout, "\n\t%d %s errors follow\n",
163 class_count[type], class_table[type]);
164 EITERATE(erpp, files, 0){
165 errorp = *erpp;
166 if (errorp->error_e_class == type){
167 errorprint(stdout, errorp, TRUE);
168 }
169 }
170 }
171 }
172 return(someerrors);
173}
174
c99ba90a
BJ
175extern boolean notouch;
176
177boolean touchfiles(nfiles, files, r_edargc, r_edargv)
178 int nfiles;
58195f21 179 Eptr **files;
c99ba90a
BJ
180 int *r_edargc;
181 char ***r_edargv;
182{
58195f21
RH
183 char *name;
184 reg Eptr errorp;
185 reg int fi;
186 reg Eptr *erpp;
187 int ntrueerrors;
188 boolean scribbled;
189 int n_pissed_on; /* # of file touched*/
190 int spread;
191
192 FILEITERATE(fi, 1){
193 name = (*files[fi])->error_text[0];
194 spread = files[fi+1] - files[fi];
195 fprintf(stdout, terse
196 ? "\"%s\" has %d error%s, "
197 : "\nFile \"%s\" has %d error%s.\n"
198 , name ,spread ,plural(spread));
c99ba90a
BJ
199 /*
200 * First, iterate through all error messages in this file
201 * to see how many of the error messages really will
202 * get inserted into the file.
203 */
58195f21
RH
204 ntrueerrors = 0;
205 EITERATE(erpp, files, fi){
c99ba90a
BJ
206 errorp = *erpp;
207 if (errorp->error_e_class == C_TRUE)
208 ntrueerrors++;
209 }
58195f21
RH
210 fprintf(stdout, terse
211 ? "insert %d\n"
212 : "\t%d of these errors can be inserted into the file.\n",
c99ba90a
BJ
213 ntrueerrors);
214
58195f21
RH
215 hackfile(name, files, fi, ntrueerrors);
216 }
c99ba90a 217 scribbled = FALSE;
58195f21
RH
218 n_pissed_on = 0;
219 FILEITERATE(fi, 1){
220 scribbled |= touchedfiles[fi];
c99ba90a
BJ
221 n_pissed_on++;
222 }
223 if (scribbled){
224 /*
225 * Construct an execv argument
c99ba90a 226 */
58195f21 227 execvarg(n_pissed_on, r_edargc, r_edargv);
c99ba90a
BJ
228 return(TRUE);
229 } else {
58195f21
RH
230 if (!terse)
231 fprintf(stdout, "You didn't touch any files.\n");
c99ba90a
BJ
232 return(FALSE);
233 }
58195f21 234}
c99ba90a 235
58195f21
RH
236hackfile(name, files, ix, nerrors)
237 char *name;
238 Eptr **files;
239 int ix;
240{
241 boolean previewed;
242 int errordest; /* where errors go*/
243
244 previewed = preview(name, nerrors, files, ix);
245
246 errordest = settotouch(name);
247
248 if (errordest != TOSTDOUT)
249 touchedfiles[ix] = TRUE;
250
251 if (previewed && (errordest == TOSTDOUT))
252 return;
253
254 diverterrors(name, errordest, files, ix, previewed, nerrors);
255
d31142de
RH
256 if (errordest == TOTHEFILE){
257 /*
258 * overwrite the original file
259 */
260 writetouched(1);
261 }
58195f21
RH
262}
263
264boolean preview(name, nerrors, files, ix)
265 char *name;
266 int nerrors;
267 Eptr **files;
268 int ix;
269{
270 int back;
271 reg Eptr *erpp;
272
273 if (!oktotouch(name))
274 return(false);
275 if (nerrors <= 0)
276 return(false);
277 back = false;
278 if(query){
279 switch(inquire(terse
280 ? "Preview? "
281 : "Do you want to preview the errors first? ")){
282 case Q_YES:
283 case Q_yes:
284 back = true;
285 EITERATE(erpp, files, ix){
286 errorprint(stdout, *erpp, TRUE);
287 }
288 if (!terse)
289 fprintf(stdout, "\n");
290 default:
291 break;
292 }
293 }
294 return(back);
295}
296
297int settotouch(name)
298 char *name;
299{
300 int dest = TOSTDOUT;
301
302 if (query){
303 switch(touchstatus = inquire(terse
304 ? "Touch? "
305 : "Do you want to touch file \"%s\"? ",
306 name)){
307 case Q_NO:
308 case Q_no:
309 return(dest);
310 default:
311 break;
312 }
313 }
314
315 switch(probethisfile(name)){
316 case F_NOTREAD:
317 dest = TOSTDOUT;
318 fprintf(stdout, terse
319 ? "\"%s\" unreadable\n"
320 : "File \"%s\" is unreadable\n",
321 name);
322 break;
323 case F_NOTWRITE:
324 dest = TOSTDOUT;
325 fprintf(stdout, terse
326 ? "\"%s\" unwritable\n"
327 : "File \"%s\" is unwritable\n",
328 name);
329 break;
330 case F_NOTEXIST:
331 dest = TOSTDOUT;
332 fprintf(stdout, terse
333 ? "\"%s\" not found\n"
334 : "Can't find file \"%s\" to insert error messages into.\n",
335 name);
336 break;
337 default:
338 dest = edit(name) ? TOSTDOUT : TOTHEFILE;
339 break;
340 }
341 return(dest);
342}
343
344diverterrors(name, dest, files, ix, previewed, nterrors)
345 char *name;
346 int dest;
347 Eptr **files;
348 int ix;
349 boolean previewed;
350 int nterrors;
351{
352 int nerrors;
353 reg Eptr *erpp;
354 reg Eptr errorp;
355
356 nerrors = files[ix+1] - files[ix];
357
358 if ( (nerrors != nterrors)
359 && (!previewed) ){
360 fprintf(stdout, terse
361 ? "Uninserted errors\n"
362 : ">>Uninserted errors for file \"%s\" follow.\n",
363 name);
364 }
365
366 EITERATE(erpp, files, ix){
367 errorp = *erpp;
368 if (errorp->error_e_class != C_TRUE){
369 if (previewed || touchstatus == Q_NO)
370 continue;
371 errorprint(stdout, errorp, TRUE);
372 continue;
373 }
374 switch (dest){
375 case TOSTDOUT:
376 if (previewed || touchstatus == Q_NO)
377 continue;
378 errorprint(stdout,errorp, TRUE);
379 break;
380 case TOTHEFILE:
381 insert(errorp->error_line);
382 text(errorp, FALSE);
383 break;
384 }
385 }
386}
387
388int oktotouch(filename)
c99ba90a
BJ
389 char *filename;
390{
391 extern char *suffixlist;
58195f21
RH
392 reg char *src;
393 reg char *pat;
c99ba90a
BJ
394 char *osrc;
395
396 pat = suffixlist;
397 if (pat == 0)
398 return(0);
399 if (*pat == '*')
400 return(1);
401 while (*pat++ != '.')
402 continue;
403 --pat; /* point to the period */
404
405 for (src = &filename[strlen(filename)], --src;
406 (src > filename) && (*src != '.'); --src)
407 continue;
408 if (*src != '.')
409 return(0);
410
411 for (src++, pat++, osrc = src; *src && *pat; src = osrc, pat++){
412 for (; *src /* not at end of the source */
413 && *pat /* not off end of pattern */
414 && *pat != '.' /* not off end of sub pattern */
415 && *pat != '*' /* not wild card */
416 && *src == *pat; /* and equal... */
417 src++, pat++)
418 continue;
419 if (*src == 0 && (*pat == 0 || *pat == '.' || *pat == '*'))
420 return(1);
421 if (*src != 0 && *pat == '*')
422 return(1);
423 while (*pat && *pat != '.')
424 pat++;
425 if (! *pat)
426 return(0);
427 }
428 return(0);
429}
58195f21
RH
430/*
431 * Construct an execv argument
432 * We need 1 argument for the editor's name
433 * We need 1 argument for the initial search string
434 * We need n_pissed_on arguments for the file names
435 * We need 1 argument that is a null for execv.
436 * The caller fills in the editor's name.
437 * We fill in the initial search string.
438 * We fill in the arguments, and the null.
439 */
440execvarg(n_pissed_on, r_argc, r_argv)
441 int n_pissed_on;
442 int *r_argc;
443 char ***r_argv;
444{
445 Eptr p;
446 char *sep;
447 int fi;
448
449 (*r_argv) = (char **)Calloc(n_pissed_on + 3, sizeof(char *));
450 (*r_argc) = n_pissed_on + 2;
451 (*r_argv)[1] = "+1;/###/";
452 n_pissed_on = 2;
453 if (!terse){
454 fprintf(stdout, "You touched file(s):");
455 sep = " ";
456 }
457 FILEITERATE(fi, 1){
458 if (!touchedfiles[fi])
459 continue;
460 p = *(files[fi]);
461 if (!terse){
462 fprintf(stdout,"%s\"%s\"", sep, p->error_text[0]);
463 sep = ", ";
464 }
465 (*r_argv)[n_pissed_on++] = p->error_text[0];
466 }
467 if (!terse)
468 fprintf(stdout, "\n");
469 (*r_argv)[n_pissed_on] = 0;
470}
c99ba90a
BJ
471
472FILE *o_touchedfile; /* the old file */
473FILE *n_touchedfile; /* the new file */
474char *o_name;
d31142de
RH
475char n_name[64];
476char *canon_name = "/tmp/ErrorXXXXXX";
c99ba90a
BJ
477int o_lineno;
478int n_lineno;
479boolean tempfileopen = FALSE;
480/*
481 * open the file; guaranteed to be both readable and writable
482 * Well, if it isn't, then return TRUE if something failed
483 */
484boolean edit(name)
485 char *name;
486{
487 o_name = name;
488 if ( (o_touchedfile = fopen(name, "r")) == NULL){
489 fprintf(stderr, "%s: Can't open file \"%s\" to touch (read).\n",
490 processname, name);
491 return(TRUE);
492 }
58195f21
RH
493 (void)strcpy(n_name, canon_name);
494 (void)mktemp(n_name);
c99ba90a
BJ
495 if ( (n_touchedfile = fopen(n_name, "w")) == NULL){
496 fprintf(stderr,"%s: Can't open file \"%s\" to touch (write).\n",
497 processname, name);
498 return(TRUE);
499 }
500 tempfileopen = TRUE;
501 n_lineno = 0;
502 o_lineno = 0;
503 return(FALSE);
504}
505/*
506 * Position to the line (before, after) the line given by place
507 */
58195f21 508char edbuf[BUFSIZ];
c99ba90a
BJ
509insert(place)
510 int place;
511{
512 --place; /* always insert messages before the offending line*/
513 for(; o_lineno < place; o_lineno++, n_lineno++){
58195f21 514 if(fgets(edbuf, BUFSIZ, o_touchedfile) == NULL)
c99ba90a 515 return;
58195f21 516 fputs(edbuf, n_touchedfile);
c99ba90a
BJ
517 }
518}
519
58195f21
RH
520text(p, use_all)
521 reg Eptr p;
522 boolean use_all;
c99ba90a
BJ
523{
524 int offset = use_all ? 0 : 2;
58195f21
RH
525
526 fputs(lang_table[p->error_language].lang_incomment, n_touchedfile);
c99ba90a 527 fprintf(n_touchedfile, "%d [%s] ",
58195f21
RH
528 p->error_line,
529 lang_table[p->error_language].lang_name);
530 wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset);
531 fputs(lang_table[p->error_language].lang_outcomment,n_touchedfile);
c99ba90a
BJ
532 n_lineno++;
533}
534
d31142de
RH
535/*
536 * write the touched file to its temporary copy,
537 * then bring the temporary in over the local file
538 */
539writetouched(overwrite)
540 int overwrite;
c99ba90a 541{
d31142de
RH
542 reg int nread;
543 reg FILE *localfile;
544 reg FILE *tmpfile;
545 int botch;
58195f21
RH
546
547 while((nread = fread(edbuf, 1, sizeof(edbuf), o_touchedfile)) != NULL){
548 fwrite(edbuf, 1, nread, n_touchedfile);
c99ba90a
BJ
549 }
550 fclose(n_touchedfile);
551 fclose(o_touchedfile);
d31142de
RH
552 /*
553 * Now, copy the temp file back over the original
554 * file, thus preserving links, etc
555 */
556 if (overwrite){
557 botch = 0;
558 localfile = NULL;
559 tmpfile = NULL;
560 if ((localfile = fopen(o_name, "w")) == NULL){
561 fprintf(stderr,
562 "%s: Can't open file \"%s\" to overwrite.\n",
563 processname, o_name);
564 botch++;
565 }
566 if ((tmpfile = fopen(n_name, "r")) == NULL){
567 fprintf(stderr, "%s: Can't open file \"%s\" to read.\n",
568 processname, n_name);
569 botch++;
570 }
571 if (!botch){
572 while((nread=fread(edbuf, 1, sizeof(edbuf), tmpfile))
573 != NULL){
574 fwrite(edbuf, 1, nread, localfile);
575 }
576 }
577 if (localfile != NULL)
578 fclose(localfile);
579 if (tmpfile != NULL)
580 fclose(tmpfile);
581 }
582 /*
583 * Kiss the temp file good bye
584 */
c99ba90a
BJ
585 unlink(n_name);
586 tempfileopen = FALSE;
d31142de 587 return(TRUE);
c99ba90a 588}
58195f21 589
c99ba90a
BJ
590onintr()
591{
58195f21
RH
592 switch(inquire(terse
593 ? "\nContinue? "
594 : "\nInterrupt: Do you want to continue? ")){
595 case Q_YES:
596 case Q_yes:
c99ba90a
BJ
597 signal(SIGINT, onintr);
598 return;
58195f21 599 default:
d31142de
RH
600 if (tempfileopen){
601 /*
602 * Don't overwrite the original file!
603 */
604 writetouched(0);
605 }
58195f21 606 exit(1);
c99ba90a 607 }
58195f21 608 /*NOTREACHED*/
c99ba90a 609}
58195f21 610
c99ba90a
BJ
611errorprint(place, errorp, print_all)
612 FILE *place;
58195f21 613 Eptr errorp;
c99ba90a
BJ
614 boolean print_all;
615{
616 int offset = print_all ? 0 : 2;
617
618 if (errorp->error_e_class == C_IGNORE)
619 return;
620 fprintf(place, "[%s] ", lang_table[errorp->error_language].lang_name);
621 wordvprint(place,errorp->error_lgtext-offset,errorp->error_text+offset);
622 putc('\n', place);
623}
624
58195f21 625int inquire(fmt, a1, a2)
c99ba90a
BJ
626 char *fmt;
627 /*VARARGS1*/
628{
629 char buffer[128];
c99ba90a
BJ
630 for(;;){
631 do{
632 fflush(stdout);
633 fprintf(stderr, fmt, a1, a2);
634 fflush(stderr);
635 } while (fgets(buffer, 127, queryfile) == NULL);
58195f21
RH
636 switch(buffer[0]){
637 case 'Y': return(Q_YES);
638 case 'y': return(Q_yes);
639 case 'N': return(Q_NO);
640 case 'n': return(Q_no);
641 default: fprintf(stderr, "Yes or No only!\n");
642 }
c99ba90a
BJ
643 }
644}
645
58195f21
RH
646int probethisfile(name)
647 char *name;
c99ba90a
BJ
648{
649 struct stat statbuf;
58195f21
RH
650 if (stat(name, &statbuf) < 0)
651 return(F_NOTEXIST);
652 if((statbuf.st_mode & S_IREAD) == 0)
653 return(F_NOTREAD);
654 if((statbuf.st_mode & S_IWRITE) == 0)
655 return(F_NOTWRITE);
656 return(F_TOUCHIT);
c99ba90a 657}