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