1/*	$NetBSD: touch.c,v 1.26 2011/05/24 12:24:22 joerg 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.26 2011/05/24 12:24:22 joerg 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(const char *,  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 *, unsigned, 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 = 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(name, 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(const char *name, 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		switch (inquire(terse
367		    ? "Preview? "
368		    : "Do you want to preview the errors first? ")) {
369		case Q_YES:
370		case 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		case Q_error:
378		default:
379			break;
380		}
381	}
382	return (back);
383}
384
385static int
386settotouch(const char *name)
387{
388	int dest = TOSTDOUT;
389
390	if (query) {
391		int reply;
392		if (terse)
393			reply = inquire("Touch? ");
394		else
395			reply = inquire("Do you want to touch file \"%s\"? ",
396			    name);
397		switch (reply) {
398		case Q_NO:
399		case Q_no:
400		case Q_error:
401			touchstatus = Q_NO;
402			return (dest);
403		default:
404			touchstatus = Q_YES;
405			break;
406		}
407	}
408
409	switch (probethisfile(name)) {
410	case F_NOTREAD:
411		dest = TOSTDOUT;
412		fprintf(stdout, terse
413			? "\"%s\" unreadable\n"
414			: "File \"%s\" is unreadable\n",
415			name);
416		break;
417	case F_NOTWRITE:
418		dest = TOSTDOUT;
419		fprintf(stdout, terse
420			? "\"%s\" unwritable\n"
421			: "File \"%s\" is unwritable\n",
422			name);
423		break;
424	case F_NOTEXIST:
425		dest = TOSTDOUT;
426		fprintf(stdout, terse
427			? "\"%s\" not found\n"
428			: "Can't find file \"%s\" to insert error messages into.\n",
429			name);
430		break;
431	default:
432		dest = edit(name) ? TOSTDOUT : TOTHEFILE;
433		break;
434	}
435	return (dest);
436}
437
438static void
439diverterrors(const char *name, int dest, Eptr **my_files, int ix,
440	     boolean previewed, int nterrors)
441{
442	int my_nerrors;
443	Eptr *erpp;
444	Eptr errorp;
445
446	my_nerrors = my_files[ix+1] - my_files[ix];
447
448	if (my_nerrors != nterrors && !previewed) {
449		if (terse)
450			printf("Uninserted errors\n");
451		else
452			printf(">>Uninserted errors for file \"%s\" follow.\n",
453			    name);
454	}
455
456	EITERATE(erpp, my_files, ix) {
457		errorp = *erpp;
458		if (errorp->error_e_class != C_TRUE) {
459			if (previewed || touchstatus == Q_NO)
460				continue;
461			errorprint(stdout, errorp, true);
462			continue;
463		}
464		switch (dest) {
465		case TOSTDOUT:
466			if (previewed || touchstatus == Q_NO)
467				continue;
468			errorprint(stdout,errorp, true);
469			break;
470		case TOTHEFILE:
471			insert(errorp->error_line);
472			text(errorp, false);
473			break;
474		}
475	}
476}
477
478static int
479oktotouch(const char *filename)
480{
481	const char *src;
482	const char *pat;
483	const char *osrc;
484
485	pat = suffixlist;
486	if (pat == 0)
487		return (0);
488	if (*pat == '*')
489		return (1);
490	while (*pat++ != '.')
491		continue;
492	--pat;		/* point to the period */
493
494	for (src = &filename[strlen(filename)], --src;
495	     src > filename && *src != '.'; --src)
496		continue;
497	if (*src != '.')
498		return (0);
499
500	for (src++, pat++, osrc = src; *src && *pat; src = osrc, pat++) {
501		for (;   *src			/* not at end of the source */
502		      && *pat			/* not off end of pattern */
503		      && *pat != '.'		/* not off end of sub pattern */
504		      && *pat != '*'		/* not wild card */
505		      && *src == *pat;		/* and equal... */
506		      src++, pat++)
507			continue;
508		if (*src == 0 && (*pat == 0 || *pat == '.' || *pat == '*'))
509			return (1);
510		if (*src != 0 && *pat == '*')
511			return (1);
512		while (*pat && *pat != '.')
513			pat++;
514		if (!*pat)
515			return (0);
516	}
517	return (0);
518}
519
520/*
521 * Construct an execv argument
522 * We need 1 argument for the editor's name
523 * We need 1 argument for the initial search string
524 * We need n_pissed_on arguments for the file names
525 * We need 1 argument that is a null for execv.
526 * The caller fills in the editor's name.
527 * We fill in the initial search string.
528 * We fill in the arguments, and the null.
529 */
530static void
531execvarg(int n_pissed_on, int *r_argc, char ***r_argv)
532{
533	Eptr p;
534	const char *sep, *name;
535	int fi;
536
537	sep = NULL;
538	(*r_argv) = Calloc(n_pissed_on + 3, sizeof(char *));
539	(*r_argc) =  n_pissed_on + 2;
540	(*r_argv)[1] = Strdup("+1;/###/"); /* XXX leaked */
541	n_pissed_on = 2;
542	if (!terse) {
543		fprintf(stdout, "You touched file(s):");
544		sep = " ";
545	}
546	FILEITERATE(fi, 1, nfiles) {
547		if (!touchedfiles[fi])
548			continue;
549		p = *(files[fi]);
550		name = makename(p->error_text[0], filelevel);
551		if (!terse) {
552			fprintf(stdout,"%s\"%s\"", sep, name);
553			sep = ", ";
554		}
555		(*r_argv)[n_pissed_on++] = __UNCONST(name);
556	}
557	if (!terse)
558		fprintf(stdout, "\n");
559	(*r_argv)[n_pissed_on] = 0;
560}
561
562static FILE *o_touchedfile;	/* the old file */
563static FILE *n_touchedfile;	/* the new file */
564static const char *o_name;
565static char n_name[MAXPATHLEN];
566static int o_lineno;
567static int n_lineno;
568static boolean tempfileopen = false;
569
570/*
571 * open the file; guaranteed to be both readable and writable
572 * Well, if it isn't, then return TRUE if something failed
573 */
574static boolean
575edit(const char *name)
576{
577	int fd;
578	const char *tmpdir;
579
580	o_name = name;
581	if ((o_touchedfile = fopen(name, "r")) == NULL) {
582		warn("Can't open file `%s' to touch (read)", name);
583		return true;
584	}
585	if ((tmpdir = getenv("TMPDIR")) == NULL)
586		tmpdir = _PATH_TMP;
587	(void)snprintf(n_name, sizeof (n_name), "%s/%s", tmpdir, TMPFILE);
588	fd = -1;
589	if ((fd = mkstemp(n_name)) == -1 ||
590	    (n_touchedfile = fdopen(fd, "w")) == NULL) {
591		warn("Can't open file `%s' to touch (write)", name);
592		if (fd != -1)
593			close(fd);
594		return true;
595	}
596	tempfileopen = true;
597	n_lineno = 0;
598	o_lineno = 0;
599	return false;
600}
601
602/*
603 * Position to the line (before, after) the line given by place
604 */
605static char edbuf[BUFSIZ];
606
607static void
608insert(int place)
609{
610	--place;	/* always insert messages before the offending line */
611	for (; o_lineno < place; o_lineno++, n_lineno++) {
612		if (fgets(edbuf, BUFSIZ, o_touchedfile) == NULL)
613			return;
614		fputs(edbuf, n_touchedfile);
615	}
616}
617
618static void
619text(Eptr p, boolean use_all)
620{
621	int offset = use_all ? 0 : 2;
622
623	fputs(lang_table[p->error_language].lang_incomment, n_touchedfile);
624	fprintf(n_touchedfile, "%d [%s] ",
625		p->error_line,
626		lang_table[p->error_language].lang_name);
627	wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset);
628	fputs(lang_table[p->error_language].lang_outcomment, n_touchedfile);
629	n_lineno++;
630}
631
632/*
633 * write the touched file to its temporary copy,
634 * then bring the temporary in over the local file
635 */
636static boolean
637writetouched(int overwrite)
638{
639	unsigned nread;
640	FILE *localfile;
641	FILE *temp;
642	int botch;
643	int oktorm;
644
645	botch = 0;
646	oktorm = 1;
647	while ((nread = fread(edbuf, 1, sizeof(edbuf), o_touchedfile)) != 0) {
648		if (nread != fwrite(edbuf, 1, nread, n_touchedfile)) {
649			/*
650			 * Catastrophe in temporary area: file system full?
651			 */
652			botch = 1;
653			warn("write failure: No errors inserted in `%s'",
654			    o_name);
655		}
656	}
657	fclose(n_touchedfile);
658	fclose(o_touchedfile);
659
660	/*
661	 * Now, copy the temp file back over the original
662	 * file, thus preserving links, etc
663	 */
664	if (botch == 0 && overwrite) {
665		botch = 0;
666		localfile = NULL;
667		temp = NULL;
668		if ((localfile = fopen(o_name, "w")) == NULL) {
669			warn("Can't open file `%s' to overwrite", o_name);
670			botch++;
671		}
672		if ((temp = fopen(n_name, "r")) == NULL) {
673			warn("Can't open file `%s' to read", n_name);
674			botch++;
675		}
676		if (!botch)
677			oktorm = mustoverwrite(localfile, temp);
678		if (localfile != NULL)
679			fclose(localfile);
680		if (temp != NULL)
681			fclose(temp);
682	}
683	if (oktorm == 0)
684		errx(1, "Catastrophe: A copy of `%s': was saved in `%s'",
685		    o_name, n_name);
686	/*
687	 * Kiss the temp file good bye
688	 */
689	unlink(n_name);
690	tempfileopen = false;
691	return true;
692}
693
694/*
695 * return 1 if the tmpfile can be removed after writing it out
696 */
697static int
698mustoverwrite(FILE *preciousfile, FILE *temp)
699{
700	unsigned nread;
701
702	while ((nread = fread(edbuf, 1, sizeof(edbuf), temp)) != 0) {
703		if (mustwrite(edbuf, nread, preciousfile) == 0)
704			return (0);
705	}
706	return (1);
707}
708
709/*
710 * return 0 on catastrophe
711 */
712static int
713mustwrite(const char *base, unsigned n, FILE *preciousfile)
714{
715	unsigned nwrote;
716
717	if (n <= 0)
718		return (1);
719	nwrote = fwrite(base, 1, n, preciousfile);
720	if (nwrote == n)
721		return (1);
722	warn("write failed");
723	switch (inquire(terse
724	    ? "Botch overwriting: retry? "
725	    : "Botch overwriting the source file: retry? ")) {
726	case Q_YES:
727	case Q_yes:
728		mustwrite(base + nwrote, n - nwrote, preciousfile);
729		return (1);
730	case Q_NO:
731	case Q_no:
732		switch (inquire("Are you sure? ")) {
733		case Q_error:
734		case Q_YES:
735		case Q_yes:
736			return (0);
737		case Q_NO:
738		case Q_no:
739			mustwrite(base + nwrote, n - nwrote, preciousfile);
740			return (1);
741		}
742	case Q_error:
743	default:
744		return (0);
745	}
746}
747
748void
749onintr(int sig)
750{
751	switch (inquire(terse
752	    ? "\nContinue? "
753	    : "\nInterrupt: Do you want to continue? ")) {
754	case Q_YES:
755	case Q_yes:
756		signal(sig, onintr);
757		return;
758	case Q_error:
759	default:
760		if (tempfileopen) {
761			/*
762			 * Don't overwrite the original file!
763			 */
764			writetouched(0);
765		}
766		(void)raise_default_signal(sig);
767		_exit(127);
768	}
769	/*NOTREACHED*/
770}
771
772static void
773errorprint(FILE *place, Eptr errorp, boolean print_all)
774{
775	int offset = print_all ? 0 : 2;
776
777	if (errorp->error_e_class == C_IGNORE)
778		return;
779	fprintf(place, "[%s] ", lang_table[errorp->error_language].lang_name);
780	wordvprint(place,errorp->error_lgtext-offset,errorp->error_text+offset);
781	putc('\n', place);
782}
783
784int
785inquire(const char *fmt, ...)
786{
787	va_list ap;
788	char buffer[128];
789
790	if (queryfile == NULL)
791		return (Q_error);
792	for (;;) {
793		fflush(stdout);
794		va_start(ap, fmt);
795		vfprintf(stderr, fmt, ap);
796		va_end(ap);
797		fflush(stderr);
798		if (fgets(buffer, 127, queryfile) == NULL)
799			return (Q_error);
800		switch (buffer[0]) {
801		case 'Y': return (Q_YES);
802		case 'y': return (Q_yes);
803		case 'N': return (Q_NO);
804		case 'n': return (Q_no);
805		default: fprintf(stderr, "Yes or No only!\n");
806		}
807	}
808}
809
810static int
811probethisfile(const char *name)
812{
813	struct stat statbuf;
814
815	if (stat(name, &statbuf) < 0)
816		return (F_NOTEXIST);
817	if ((statbuf.st_mode & S_IREAD) == 0)
818		return (F_NOTREAD);
819	if ((statbuf.st_mode & S_IWRITE) == 0)
820		return (F_NOTWRITE);
821	return (F_TOUCHIT);
822}
823