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