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