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