touch.c revision 1.29
1/* $NetBSD: touch.c,v 1.29 2023/08/26 11:38:14 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.29 2023/08/26 11:38:14 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 int nopertain(Eptr **); 76static void hackfile(const char *, Eptr **, int, int); 77static boolean preview(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 *, size_t, 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 = (int)(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(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(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 int answer = inquire(terse 367 ? "Preview? " 368 : "Do you want to preview the errors first? "); 369 if (answer == Q_YES || answer == Q_yes) { 370 back = true; 371 EITERATE(erpp, my_files, ix) { 372 errorprint(stdout, *erpp, true); 373 } 374 if (!terse) 375 fprintf(stdout, "\n"); 376 } 377 } 378 return (back); 379} 380 381static int 382settotouch(const char *name) 383{ 384 int dest = TOSTDOUT; 385 386 if (query) { 387 int reply; 388 if (terse) 389 reply = inquire("Touch? "); 390 else 391 reply = inquire("Do you want to touch file \"%s\"? ", 392 name); 393 switch (reply) { 394 case Q_NO: 395 case Q_no: 396 case Q_error: 397 touchstatus = Q_NO; 398 return (dest); 399 default: 400 touchstatus = Q_YES; 401 break; 402 } 403 } 404 405 switch (probethisfile(name)) { 406 case F_NOTREAD: 407 dest = TOSTDOUT; 408 fprintf(stdout, terse 409 ? "\"%s\" unreadable\n" 410 : "File \"%s\" is unreadable\n", 411 name); 412 break; 413 case F_NOTWRITE: 414 dest = TOSTDOUT; 415 fprintf(stdout, terse 416 ? "\"%s\" unwritable\n" 417 : "File \"%s\" is unwritable\n", 418 name); 419 break; 420 case F_NOTEXIST: 421 dest = TOSTDOUT; 422 fprintf(stdout, terse 423 ? "\"%s\" not found\n" 424 : "Can't find file \"%s\" to insert error messages into.\n", 425 name); 426 break; 427 default: 428 dest = edit(name) ? TOSTDOUT : TOTHEFILE; 429 break; 430 } 431 return (dest); 432} 433 434static void 435diverterrors(const char *name, int dest, Eptr **my_files, int ix, 436 boolean previewed, int nterrors) 437{ 438 int my_nerrors; 439 Eptr *erpp; 440 Eptr errorp; 441 442 my_nerrors = (int)(my_files[ix+1] - my_files[ix]); 443 444 if (my_nerrors != nterrors && !previewed) { 445 if (terse) 446 printf("Uninserted errors\n"); 447 else 448 printf(">>Uninserted errors for file \"%s\" follow.\n", 449 name); 450 } 451 452 EITERATE(erpp, my_files, ix) { 453 errorp = *erpp; 454 if (errorp->error_e_class != C_TRUE) { 455 if (previewed || touchstatus == Q_NO) 456 continue; 457 errorprint(stdout, errorp, true); 458 continue; 459 } 460 switch (dest) { 461 case TOSTDOUT: 462 if (previewed || touchstatus == Q_NO) 463 continue; 464 errorprint(stdout,errorp, true); 465 break; 466 case TOTHEFILE: 467 insert(errorp->error_line); 468 text(errorp, false); 469 break; 470 } 471 } 472} 473 474static int 475oktotouch(const char *filename) 476{ 477 const char *src; 478 const char *pat; 479 const char *osrc; 480 481 pat = suffixlist; 482 if (pat == 0) 483 return (0); 484 if (*pat == '*') 485 return (1); 486 while (*pat++ != '.') 487 continue; 488 --pat; /* point to the period */ 489 490 for (src = &filename[strlen(filename)], --src; 491 src > filename && *src != '.'; --src) 492 continue; 493 if (*src != '.') 494 return (0); 495 496 for (src++, pat++, osrc = src; *src && *pat; src = osrc, pat++) { 497 for (; *src /* not at end of the source */ 498 && *pat /* not off end of pattern */ 499 && *pat != '.' /* not off end of sub pattern */ 500 && *pat != '*' /* not wild card */ 501 && *src == *pat; /* and equal... */ 502 src++, pat++) 503 continue; 504 if (*src == 0 && (*pat == 0 || *pat == '.' || *pat == '*')) 505 return (1); 506 if (*src != 0 && *pat == '*') 507 return (1); 508 while (*pat && *pat != '.') 509 pat++; 510 if (!*pat) 511 return (0); 512 } 513 return (0); 514} 515 516/* 517 * Construct an execv argument 518 * We need 1 argument for the editor's name 519 * We need 1 argument for the initial search string 520 * We need n_pissed_on arguments for the file names 521 * We need 1 argument that is a null for execv. 522 * The caller fills in the editor's name. 523 * We fill in the initial search string. 524 * We fill in the arguments, and the null. 525 */ 526static void 527execvarg(int n_pissed_on, int *r_argc, char ***r_argv) 528{ 529 Eptr p; 530 const char *sep, *name; 531 int fi; 532 533 sep = NULL; 534 (*r_argv) = Calloc(n_pissed_on + 3, sizeof(char *)); 535 (*r_argc) = n_pissed_on + 2; 536 (*r_argv)[1] = Strdup("+1;/###/"); /* XXX leaked */ 537 n_pissed_on = 2; 538 if (!terse) { 539 fprintf(stdout, "You touched file(s):"); 540 sep = " "; 541 } 542 FILEITERATE(fi, 1, nfiles) { 543 if (!touchedfiles[fi]) 544 continue; 545 p = *(files[fi]); 546 name = makename(p->error_text[0], filelevel); 547 if (!terse) { 548 fprintf(stdout,"%s\"%s\"", sep, name); 549 sep = ", "; 550 } 551 (*r_argv)[n_pissed_on++] = __UNCONST(name); 552 } 553 if (!terse) 554 fprintf(stdout, "\n"); 555 (*r_argv)[n_pissed_on] = 0; 556} 557 558static FILE *o_touchedfile; /* the old file */ 559static FILE *n_touchedfile; /* the new file */ 560static const char *o_name; 561static char n_name[MAXPATHLEN]; 562static int o_lineno; 563static int n_lineno; 564static boolean tempfileopen = false; 565 566/* 567 * open the file; guaranteed to be both readable and writable 568 * Well, if it isn't, then return TRUE if something failed 569 */ 570static boolean 571edit(const char *name) 572{ 573 int fd; 574 const char *tmpdir; 575 576 o_name = name; 577 if ((o_touchedfile = fopen(name, "r")) == NULL) { 578 warn("Can't open file `%s' to touch (read)", name); 579 return true; 580 } 581 if ((tmpdir = getenv("TMPDIR")) == NULL) 582 tmpdir = _PATH_TMP; 583 (void)snprintf(n_name, sizeof (n_name), "%s/%s", tmpdir, TMPFILE); 584 fd = -1; 585 if ((fd = mkstemp(n_name)) == -1 || 586 (n_touchedfile = fdopen(fd, "w")) == NULL) { 587 warn("Can't open file `%s' to touch (write)", name); 588 if (fd != -1) 589 close(fd); 590 return true; 591 } 592 tempfileopen = true; 593 n_lineno = 0; 594 o_lineno = 0; 595 return false; 596} 597 598/* 599 * Position to the line (before, after) the line given by place 600 */ 601static char edbuf[BUFSIZ]; 602 603static void 604insert(int place) 605{ 606 --place; /* always insert messages before the offending line */ 607 for (; o_lineno < place; o_lineno++, n_lineno++) { 608 if (fgets(edbuf, BUFSIZ, o_touchedfile) == NULL) 609 return; 610 fputs(edbuf, n_touchedfile); 611 } 612} 613 614static void 615text(Eptr p, boolean use_all) 616{ 617 int offset = use_all ? 0 : 2; 618 619 fputs(lang_table[p->error_language].lang_incomment, n_touchedfile); 620 fprintf(n_touchedfile, "%d [%s] ", 621 p->error_line, 622 lang_table[p->error_language].lang_name); 623 wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset); 624 fputs(lang_table[p->error_language].lang_outcomment, n_touchedfile); 625 n_lineno++; 626} 627 628/* 629 * write the touched file to its temporary copy, 630 * then bring the temporary in over the local file 631 */ 632static boolean 633writetouched(int overwrite) 634{ 635 size_t nread; 636 FILE *localfile; 637 FILE *temp; 638 int botch; 639 int oktorm; 640 641 botch = 0; 642 oktorm = 1; 643 while ((nread = fread(edbuf, 1, sizeof(edbuf), o_touchedfile)) != 0) { 644 if (nread != fwrite(edbuf, 1, nread, n_touchedfile)) { 645 /* 646 * Catastrophe in temporary area: file system full? 647 */ 648 botch = 1; 649 warn("write failure: No errors inserted in `%s'", 650 o_name); 651 } 652 } 653 fclose(n_touchedfile); 654 fclose(o_touchedfile); 655 656 /* 657 * Now, copy the temp file back over the original 658 * file, thus preserving links, etc 659 */ 660 if (botch == 0 && overwrite) { 661 botch = 0; 662 localfile = NULL; 663 temp = NULL; 664 if ((localfile = fopen(o_name, "w")) == NULL) { 665 warn("Can't open file `%s' to overwrite", o_name); 666 botch++; 667 } 668 if ((temp = fopen(n_name, "r")) == NULL) { 669 warn("Can't open file `%s' to read", n_name); 670 botch++; 671 } 672 if (!botch) 673 oktorm = mustoverwrite(localfile, temp); 674 if (localfile != NULL) 675 fclose(localfile); 676 if (temp != NULL) 677 fclose(temp); 678 } 679 if (oktorm == 0) 680 errx(1, "Catastrophe: A copy of `%s': was saved in `%s'", 681 o_name, n_name); 682 /* 683 * Kiss the temp file good bye 684 */ 685 unlink(n_name); 686 tempfileopen = false; 687 return true; 688} 689 690/* 691 * return 1 if the tmpfile can be removed after writing it out 692 */ 693static int 694mustoverwrite(FILE *preciousfile, FILE *temp) 695{ 696 size_t nread; 697 698 while ((nread = fread(edbuf, 1, sizeof(edbuf), temp)) != 0) { 699 if (mustwrite(edbuf, nread, preciousfile) == 0) 700 return (0); 701 } 702 return (1); 703} 704 705/* 706 * return 0 on catastrophe 707 */ 708static int 709mustwrite(const char *base, size_t n, FILE *preciousfile) 710{ 711 size_t nwrote; 712 713 if (n == 0) 714 return (1); 715 nwrote = fwrite(base, 1, n, preciousfile); 716 if (nwrote == n) 717 return (1); 718 warn("write failed"); 719 switch (inquire(terse 720 ? "Botch overwriting: retry? " 721 : "Botch overwriting the source file: retry? ")) { 722 case Q_YES: 723 case Q_yes: 724 mustwrite(base + nwrote, n - nwrote, preciousfile); 725 return (1); 726 case Q_NO: 727 case Q_no: 728 switch (inquire("Are you sure? ")) { 729 case Q_error: 730 case Q_YES: 731 case Q_yes: 732 return (0); 733 case Q_NO: 734 case Q_no: 735 mustwrite(base + nwrote, n - nwrote, preciousfile); 736 return (1); 737 default: 738 abort(); 739 } 740 /* FALLTHROUGH */ 741 case Q_error: 742 default: 743 return (0); 744 } 745} 746 747void 748onintr(int sig) 749{ 750 switch (inquire(terse 751 ? "\nContinue? " 752 : "\nInterrupt: Do you want to continue? ")) { 753 case Q_YES: 754 case Q_yes: 755 signal(sig, onintr); 756 return; 757 case Q_error: 758 default: 759 if (tempfileopen) { 760 /* 761 * Don't overwrite the original file! 762 */ 763 writetouched(0); 764 } 765 (void)raise_default_signal(sig); 766 _exit(127); 767 } 768 /*NOTREACHED*/ 769} 770 771static void 772errorprint(FILE *place, Eptr errorp, boolean print_all) 773{ 774 int offset = print_all ? 0 : 2; 775 776 if (errorp->error_e_class == C_IGNORE) 777 return; 778 fprintf(place, "[%s] ", lang_table[errorp->error_language].lang_name); 779 wordvprint(place,errorp->error_lgtext-offset,errorp->error_text+offset); 780 putc('\n', place); 781} 782 783int 784inquire(const char *fmt, ...) 785{ 786 va_list ap; 787 char buffer[128]; 788 789 if (queryfile == NULL) 790 return (Q_error); 791 for (;;) { 792 fflush(stdout); 793 va_start(ap, fmt); 794 vfprintf(stderr, fmt, ap); 795 va_end(ap); 796 fflush(stderr); 797 if (fgets(buffer, 127, queryfile) == NULL) 798 return (Q_error); 799 switch (buffer[0]) { 800 case 'Y': return (Q_YES); 801 case 'y': return (Q_yes); 802 case 'N': return (Q_NO); 803 case 'n': return (Q_no); 804 default: fprintf(stderr, "Yes or No only!\n"); 805 } 806 } 807} 808 809static int 810probethisfile(const char *name) 811{ 812 struct stat statbuf; 813 814 if (stat(name, &statbuf) < 0) 815 return (F_NOTEXIST); 816 if ((statbuf.st_mode & S_IREAD) == 0) 817 return (F_NOTREAD); 818 if ((statbuf.st_mode & S_IWRITE) == 0) 819 return (F_NOTWRITE); 820 return (F_TOUCHIT); 821} 822