1/*	$NetBSD: touch.c,v 1.32 2023/08/26 14:59:44 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.32 2023/08/26 14:59:44 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 bool nopertain(Eptr **);
76static void hackfile(const char *, Eptr **, int, int);
77static bool preview(int, Eptr **, int);
78static int settotouch(const char *);
79static void diverterrors(const char *, int, Eptr **, int, bool, int);
80static bool oktotouch(const char *);
81static void execvarg(int, int *, char ***);
82static bool edit(const char *);
83static void insert(int);
84static void text(Eptr, bool);
85static bool writetouched(bool);
86static bool mustoverwrite(FILE *, FILE *);
87static bool mustwrite(const char *, size_t, FILE *);
88static void errorprint(FILE *, Eptr, bool);
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(touchedfiles[0]));
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] = false;
141	touchedfiles[1] = false;
142	name = "\1";
143	fi = 1;
144	ECITERATE(ei, errorp, ei, my_errors, my_nerrors) {
145		const char *fname = makename(errorp->error_text[0], filelevel);
146		if (errorp->error_e_class == C_NULLED
147		    || errorp->error_e_class == C_TRUE) {
148			if (strcmp(fname, name) != 0) {
149				name = fname;
150				touchedfiles[fi] = false;
151				my_files[fi] = &my_errors[ei];
152				fi++;
153			}
154		}
155	}
156	my_files[fi] = &my_errors[my_nerrors];
157	*r_nfiles = my_nfiles;
158	*r_files = my_files;
159}
160
161static int
162countfiles(Eptr *errors)
163{
164	const char *name;
165	int ei;
166	Eptr errorp;
167	int my_nfiles;
168
169	my_nfiles = 0;
170	name = "\1";
171	ECITERATE(ei, errorp, 0, errors, nerrors) {
172		if (SORTABLE(errorp->error_e_class)) {
173			const char *fname = makename(errorp->error_text[0],
174			    filelevel);
175			if (strcmp(fname, name) != 0) {
176				my_nfiles++;
177				name = fname;
178			}
179		}
180	}
181	return my_nfiles;
182}
183
184const char *class_table[] = {
185	/*C_UNKNOWN	0	*/	"Unknown",
186	/*C_IGNORE	1	*/	"ignore",
187	/*C_SYNC	2	*/	"synchronization",
188	/*C_DISCARD	3	*/	"discarded",
189	/*C_NONSPEC	4	*/	"non specific",
190	/*C_THISFILE	5	*/	"specific to this file",
191	/*C_NULLED	6	*/	"nulled",
192	/*C_TRUE	7	*/	"true",
193	/*C_DUPL	8	*/	"duplicated"
194};
195
196int class_count[C_LAST - C_FIRST] = {0};
197
198void
199filenames(int my_nfiles, Eptr **my_files)
200{
201	int fi;
202	const char *sep = " ";
203	bool someerrors;
204
205	/*
206	 * first, simply dump out errors that
207	 * don't pertain to any file
208	 */
209	someerrors = nopertain(my_files);
210
211	if (my_nfiles > 0) {
212		someerrors = true;
213		if (terse)
214			fprintf(stdout, "%d file%s", my_nfiles, plural(my_nfiles));
215		else
216			fprintf(stdout, "%d file%s contain%s errors",
217				my_nfiles, plural(my_nfiles), verbform(my_nfiles));
218		if (!terse) {
219			FILEITERATE(fi, 1, my_nfiles) {
220				const char *fname = makename(
221				    (*my_files[fi])->error_text[0], filelevel);
222				fprintf(stdout, "%s\"%s\" (%d)",
223					sep, fname,
224					(int)(my_files[fi+1] - my_files[fi]));
225				sep = ", ";
226			}
227		}
228		fprintf(stdout, "\n");
229	}
230	if (!someerrors)
231		fprintf(stdout, "No errors.\n");
232}
233
234/*
235 * Dump out errors that don't pertain to any file
236 */
237static bool
238nopertain(Eptr **my_files)
239{
240	int type;
241	bool someerrors = false;
242	Eptr *erpp;
243	Eptr errorp;
244
245	if (my_files[1] - my_files[0] <= 0)
246		return false;
247	for (type = C_UNKNOWN; NOTSORTABLE(type); type++) {
248		if (class_count[type] <= 0)
249			continue;
250		if (type > C_SYNC)
251			someerrors = true;
252		if (terse) {
253			fprintf(stdout, "\t%d %s errors NOT PRINTED\n",
254				class_count[type], class_table[type]);
255		} else {
256			fprintf(stdout, "\n\t%d %s errors follow\n",
257				class_count[type], class_table[type]);
258			EITERATE(erpp, my_files, 0) {
259				errorp = *erpp;
260				if (errorp->error_e_class == type) {
261					errorprint(stdout, errorp, true);
262				}
263			}
264		}
265	}
266	return someerrors;
267}
268
269
270bool
271touchfiles(int my_nfiles, Eptr **my_files, int *r_edargc, char ***r_edargv)
272{
273	const char *name;
274	Eptr errorp;
275	int fi;
276	Eptr *erpp;
277	int ntrueerrors;
278	bool scribbled;
279	int n_pissed_on;	/* # of file touched*/
280	int spread;
281
282	FILEITERATE(fi, 1, my_nfiles) {
283		name = makename((*my_files[fi])->error_text[0], filelevel);
284		spread = (int)(my_files[fi+1] - my_files[fi]);
285
286		fprintf(stdout, terse
287			? "\"%s\" has %d error%s, "
288			: "\nFile \"%s\" has %d error%s.\n"
289			, name ,spread ,plural(spread));
290		/*
291		 * First, iterate through all error messages in this file
292		 * to see how many of the error messages really will
293		 * get inserted into the file.
294		 */
295		ntrueerrors = 0;
296		EITERATE(erpp, my_files, fi) {
297			errorp = *erpp;
298			if (errorp->error_e_class == C_TRUE)
299				ntrueerrors++;
300		}
301		fprintf(stdout, terse
302		  ? "insert %d\n"
303		  : "\t%d of these errors can be inserted into the file.\n",
304			ntrueerrors);
305
306		hackfile(name, my_files, fi, ntrueerrors);
307	}
308	scribbled = false;
309	n_pissed_on = 0;
310	FILEITERATE(fi, 1, my_nfiles) {
311		scribbled |= touchedfiles[fi];
312		n_pissed_on++;
313	}
314	if (scribbled) {
315		/*
316		 * Construct an execv argument
317		 */
318		execvarg(n_pissed_on, r_edargc, r_edargv);
319		return true;
320	} else {
321		if (!terse)
322			fprintf(stdout, "You didn't touch any files.\n");
323		return false;
324	}
325}
326
327static void
328hackfile(const char *name, Eptr **my_files, int ix, int my_nerrors)
329{
330	bool previewed;
331	int errordest;	/* where errors go */
332
333	if (!oktotouch(name)) {
334		previewed = false;
335		errordest = TOSTDOUT;
336	} else {
337		previewed = preview(my_nerrors, my_files, ix);
338		errordest = settotouch(name);
339	}
340
341	if (errordest != TOSTDOUT)
342		touchedfiles[ix] = true;
343
344	if (previewed && errordest == TOSTDOUT)
345		return;
346
347	diverterrors(name, errordest, my_files, ix, previewed, my_nerrors);
348
349	if (errordest == TOTHEFILE) {
350		/*
351		 * overwrite the original file
352		 */
353		writetouched(true);
354	}
355}
356
357static bool
358preview(int my_nerrors, Eptr **my_files, int ix)
359{
360	bool back;
361	Eptr *erpp;
362
363	if (my_nerrors <= 0)
364		return false;
365	back = false;
366	if (query) {
367		int answer = inquire(terse
368		    ? "Preview? "
369		    : "Do you want to preview the errors first? ");
370		if (answer == Q_YES || answer == Q_yes) {
371			back = true;
372			EITERATE(erpp, my_files, ix) {
373				errorprint(stdout, *erpp, true);
374			}
375			if (!terse)
376				fprintf(stdout, "\n");
377		}
378	}
379	return back;
380}
381
382static int
383settotouch(const char *name)
384{
385	int dest = TOSTDOUT;
386
387	if (query) {
388		int reply;
389		if (terse)
390			reply = inquire("Touch? ");
391		else
392			reply = inquire("Do you want to touch file \"%s\"? ",
393			    name);
394		switch (reply) {
395		case Q_NO:
396		case Q_no:
397		case Q_error:
398			touchstatus = Q_NO;
399			return dest;
400		default:
401			touchstatus = Q_YES;
402			break;
403		}
404	}
405
406	switch (probethisfile(name)) {
407	case F_NOTREAD:
408		dest = TOSTDOUT;
409		fprintf(stdout, terse
410			? "\"%s\" unreadable\n"
411			: "File \"%s\" is unreadable\n",
412			name);
413		break;
414	case F_NOTWRITE:
415		dest = TOSTDOUT;
416		fprintf(stdout, terse
417			? "\"%s\" unwritable\n"
418			: "File \"%s\" is unwritable\n",
419			name);
420		break;
421	case F_NOTEXIST:
422		dest = TOSTDOUT;
423		fprintf(stdout, terse
424			? "\"%s\" not found\n"
425			: "Can't find file \"%s\" to insert error messages into.\n",
426			name);
427		break;
428	default:
429		dest = edit(name) ? TOSTDOUT : TOTHEFILE;
430		break;
431	}
432	return dest;
433}
434
435static void
436diverterrors(const char *name, int dest, Eptr **my_files, int ix,
437	     bool previewed, int nterrors)
438{
439	int my_nerrors;
440	Eptr *erpp;
441	Eptr errorp;
442
443	my_nerrors = (int)(my_files[ix+1] - my_files[ix]);
444
445	if (my_nerrors != nterrors && !previewed) {
446		if (terse)
447			printf("Uninserted errors\n");
448		else
449			printf(">>Uninserted errors for file \"%s\" follow.\n",
450			    name);
451	}
452
453	EITERATE(erpp, my_files, ix) {
454		errorp = *erpp;
455		if (errorp->error_e_class != C_TRUE) {
456			if (previewed || touchstatus == Q_NO)
457				continue;
458			errorprint(stdout, errorp, true);
459			continue;
460		}
461		switch (dest) {
462		case TOSTDOUT:
463			if (previewed || touchstatus == Q_NO)
464				continue;
465			errorprint(stdout,errorp, true);
466			break;
467		case TOTHEFILE:
468			insert(errorp->error_line);
469			text(errorp, false);
470			break;
471		}
472	}
473}
474
475static bool
476oktotouch(const char *filename)
477{
478	const char *src;
479	const char *pat;
480	const char *osrc;
481
482	pat = suffixlist;
483	if (pat == 0)
484		return false;
485	if (*pat == '*')
486		return true;
487	while (*pat++ != '.')
488		continue;
489	--pat;		/* point to the period */
490
491	for (src = &filename[strlen(filename)], --src;
492	     src > filename && *src != '.'; --src)
493		continue;
494	if (*src != '.')
495		return false;
496
497	for (src++, pat++, osrc = src;
498	    *src != '\0' && *pat != '\0'; src = osrc, pat++) {
499		for (;   *src != '\0'		/* not at end of the source */
500		      && *pat != '\0'		/* not off end of pattern */
501		      && *pat != '.'		/* not off end of sub pattern */
502		      && *pat != '*'		/* not wild card */
503		      && *src == *pat;		/* and equal... */
504		      src++, pat++)
505			continue;
506		if (*src == '\0'
507		    && (*pat == '\0' || *pat == '.' || *pat == '*'))
508			return true;
509		if (*src != '\0' && *pat == '*')
510			return true;
511		while (*pat != '\0' && *pat != '.')
512			pat++;
513		if (*pat == '\0')
514			return false;
515	}
516	return false;
517}
518
519/*
520 * Construct an execv argument
521 * We need 1 argument for the editor's name
522 * We need 1 argument for the initial search string
523 * We need n_pissed_on arguments for the file names
524 * We need 1 argument that is a null for execv.
525 * The caller fills in the editor's name.
526 * We fill in the initial search string.
527 * We fill in the arguments, and the null.
528 */
529static void
530execvarg(int n_pissed_on, int *r_argc, char ***r_argv)
531{
532	Eptr p;
533	const char *sep, *name;
534	int fi;
535
536	sep = NULL;
537	(*r_argv) = Calloc(n_pissed_on + 3, sizeof(char *));
538	(*r_argc) =  n_pissed_on + 2;
539	(*r_argv)[1] = Strdup("+1;/###/"); /* XXX leaked */
540	n_pissed_on = 2;
541	if (!terse) {
542		fprintf(stdout, "You touched file(s):");
543		sep = " ";
544	}
545	FILEITERATE(fi, 1, nfiles) {
546		if (!touchedfiles[fi])
547			continue;
548		p = *(files[fi]);
549		name = makename(p->error_text[0], filelevel);
550		if (!terse) {
551			fprintf(stdout,"%s\"%s\"", sep, name);
552			sep = ", ";
553		}
554		(*r_argv)[n_pissed_on++] = __UNCONST(name);
555	}
556	if (!terse)
557		fprintf(stdout, "\n");
558	(*r_argv)[n_pissed_on] = NULL;
559}
560
561static FILE *o_touchedfile;	/* the old file */
562static FILE *n_touchedfile;	/* the new file */
563static const char *o_name;
564static char n_name[MAXPATHLEN];
565static int o_lineno;
566static int n_lineno;
567static bool tempfileopen = false;
568
569/*
570 * open the file; guaranteed to be both readable and writable
571 * Well, if it isn't, then return TRUE if something failed
572 */
573static bool
574edit(const char *name)
575{
576	int fd;
577	const char *tmpdir;
578
579	o_name = name;
580	if ((o_touchedfile = fopen(name, "r")) == NULL) {
581		warn("Can't open file `%s' to touch (read)", name);
582		return true;
583	}
584	if ((tmpdir = getenv("TMPDIR")) == NULL)
585		tmpdir = _PATH_TMP;
586	(void)snprintf(n_name, sizeof (n_name), "%s/%s", tmpdir, TMPFILE);
587	fd = -1;
588	if ((fd = mkstemp(n_name)) == -1 ||
589	    (n_touchedfile = fdopen(fd, "w")) == NULL) {
590		warn("Can't open file `%s' to touch (write)", name);
591		if (fd != -1)
592			close(fd);
593		return true;
594	}
595	tempfileopen = true;
596	n_lineno = 0;
597	o_lineno = 0;
598	return false;
599}
600
601/*
602 * Position to the line (before, after) the line given by place
603 */
604static char edbuf[BUFSIZ];
605
606static void
607insert(int place)
608{
609	--place;	/* always insert messages before the offending line */
610	for (; o_lineno < place; o_lineno++, n_lineno++) {
611		if (fgets(edbuf, BUFSIZ, o_touchedfile) == NULL)
612			return;
613		fputs(edbuf, n_touchedfile);
614	}
615}
616
617static void
618text(Eptr p, bool use_all)
619{
620	int offset = use_all ? 0 : 2;
621
622	fputs(lang_table[p->error_language].lang_incomment, n_touchedfile);
623	fprintf(n_touchedfile, "%d [%s] ",
624		p->error_line,
625		lang_table[p->error_language].lang_name);
626	wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset);
627	fputs(lang_table[p->error_language].lang_outcomment, n_touchedfile);
628	n_lineno++;
629}
630
631/*
632 * write the touched file to its temporary copy,
633 * then bring the temporary in over the local file
634 */
635static bool
636writetouched(bool overwrite)
637{
638	size_t nread;
639	FILE *localfile;
640	FILE *temp;
641	bool botch;
642	bool oktorm;
643
644	botch = false;
645	oktorm = true;
646	while ((nread = fread(edbuf, 1, sizeof(edbuf), o_touchedfile)) != 0) {
647		if (nread != fwrite(edbuf, 1, nread, n_touchedfile)) {
648			/*
649			 * Catastrophe in temporary area: file system full?
650			 */
651			botch = true;
652			warn("write failure: No errors inserted in `%s'",
653			    o_name);
654		}
655	}
656	fclose(n_touchedfile);
657	fclose(o_touchedfile);
658
659	/*
660	 * Now, copy the temp file back over the original
661	 * file, thus preserving links, etc
662	 */
663	if (!botch && overwrite) {
664		localfile = NULL;
665		temp = NULL;
666		if ((localfile = fopen(o_name, "w")) == NULL) {
667			warn("Can't open file `%s' to overwrite", o_name);
668			botch = true;
669		}
670		if ((temp = fopen(n_name, "r")) == NULL) {
671			warn("Can't open file `%s' to read", n_name);
672			botch = true;
673		}
674		if (!botch)
675			oktorm = mustoverwrite(localfile, temp);
676		if (localfile != NULL)
677			fclose(localfile);
678		if (temp != NULL)
679			fclose(temp);
680	}
681	if (!oktorm)
682		errx(1, "Catastrophe: A copy of `%s': was saved in `%s'",
683		    o_name, n_name);
684	/*
685	 * Kiss the temp file good bye
686	 */
687	unlink(n_name);
688	tempfileopen = false;
689	return true;
690}
691
692/*
693 * return whether the tmpfile can be removed after writing it out
694 */
695static bool
696mustoverwrite(FILE *preciousfile, FILE *temp)
697{
698	size_t nread;
699
700	while ((nread = fread(edbuf, 1, sizeof(edbuf), temp)) != 0) {
701		if (!mustwrite(edbuf, nread, preciousfile))
702			return false;
703	}
704	return true;
705}
706
707/*
708 * return false on catastrophe
709 */
710static bool
711mustwrite(const char *base, size_t n, FILE *preciousfile)
712{
713	size_t nwrote;
714
715	if (n == 0)
716		return true;
717	nwrote = fwrite(base, 1, n, preciousfile);
718	if (nwrote == n)
719		return true;
720	warn("write failed");
721	switch (inquire(terse
722	    ? "Botch overwriting: retry? "
723	    : "Botch overwriting the source file: retry? ")) {
724	case Q_YES:
725	case Q_yes:
726		mustwrite(base + nwrote, n - nwrote, preciousfile);
727		return true;
728	case Q_NO:
729	case Q_no:
730		switch (inquire("Are you sure? ")) {
731		case Q_error:
732		case Q_YES:
733		case Q_yes:
734			return false;
735		case Q_NO:
736		case Q_no:
737			mustwrite(base + nwrote, n - nwrote, preciousfile);
738			return true;
739		default:
740			abort();
741		}
742		/* FALLTHROUGH */
743	case Q_error:
744	default:
745		return false;
746	}
747}
748
749void
750onintr(int sig)
751{
752	switch (inquire(terse
753	    ? "\nContinue? "
754	    : "\nInterrupt: Do you want to continue? ")) {
755	case Q_YES:
756	case Q_yes:
757		signal(sig, onintr);
758		return;
759	case Q_error:
760	default:
761		if (tempfileopen) {
762			/*
763			 * Don't overwrite the original file!
764			 */
765			writetouched(false);
766		}
767		(void)raise_default_signal(sig);
768		_exit(127);
769	}
770	/*NOTREACHED*/
771}
772
773static void
774errorprint(FILE *place, Eptr errorp, bool print_all)
775{
776	int offset = print_all ? 0 : 2;
777
778	if (errorp->error_e_class == C_IGNORE)
779		return;
780	fprintf(place, "[%s] ", lang_table[errorp->error_language].lang_name);
781	wordvprint(place,errorp->error_lgtext-offset,errorp->error_text+offset);
782	putc('\n', place);
783}
784
785int
786inquire(const char *fmt, ...)
787{
788	va_list ap;
789	char buffer[128];
790
791	if (queryfile == NULL)
792		return Q_error;
793	for (;;) {
794		fflush(stdout);
795		va_start(ap, fmt);
796		vfprintf(stderr, fmt, ap);
797		va_end(ap);
798		fflush(stderr);
799		if (fgets(buffer, 127, queryfile) == NULL)
800			return Q_error;
801		switch (buffer[0]) {
802		case 'Y': return Q_YES;
803		case 'y': return Q_yes;
804		case 'N': return Q_NO;
805		case 'n': return Q_no;
806		default: fprintf(stderr, "Yes or No only!\n");
807		}
808	}
809}
810
811static int
812probethisfile(const char *name)
813{
814	struct stat statbuf;
815
816	if (stat(name, &statbuf) < 0)
817		return F_NOTEXIST;
818	if ((statbuf.st_mode & S_IREAD) == 0)
819		return F_NOTREAD;
820	if ((statbuf.st_mode & S_IWRITE) == 0)
821		return F_NOTWRITE;
822	return F_TOUCHIT;
823}
824