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