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