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