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