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