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