Home | History | Annotate | Line # | Download | only in error
      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
     35 static 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 
     64 static 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 
     74 static int countfiles(Eptr *);
     75 static bool nopertain(Eptr **);
     76 static void hackfile(const char *, Eptr **, int, int);
     77 static bool preview(int, Eptr **, int);
     78 static int settotouch(const char *);
     79 static void diverterrors(const char *, int, Eptr **, int, bool, int);
     80 static bool oktotouch(const char *);
     81 static void execvarg(int, int *, char ***);
     82 static bool edit(const char *);
     83 static void insert(int);
     84 static void text(Eptr, bool);
     85 static bool writetouched(bool);
     86 static bool mustoverwrite(FILE *, FILE *);
     87 static bool mustwrite(const char *, size_t, FILE *);
     88 static void errorprint(FILE *, Eptr, bool);
     89 static int probethisfile(const char *);
     90 
     91 static const char *
     92 makename(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 }
    110 void
    111 findfiles(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 
    161 static int
    162 countfiles(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 
    184 const 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 
    196 int class_count[C_LAST - C_FIRST] = {0};
    197 
    198 void
    199 filenames(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  */
    237 static bool
    238 nopertain(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 
    270 bool
    271 touchfiles(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 
    327 static void
    328 hackfile(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 
    357 static bool
    358 preview(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 
    382 static int
    383 settotouch(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 
    435 static void
    436 diverterrors(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 
    475 static bool
    476 oktotouch(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  */
    529 static void
    530 execvarg(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 
    561 static FILE *o_touchedfile;	/* the old file */
    562 static FILE *n_touchedfile;	/* the new file */
    563 static const char *o_name;
    564 static char n_name[MAXPATHLEN];
    565 static int o_lineno;
    566 static int n_lineno;
    567 static 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  */
    573 static bool
    574 edit(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  */
    604 static char edbuf[BUFSIZ];
    605 
    606 static void
    607 insert(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 
    617 static void
    618 text(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  */
    635 static bool
    636 writetouched(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  */
    695 static bool
    696 mustoverwrite(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  */
    710 static bool
    711 mustwrite(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 
    749 void
    750 onintr(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 
    773 static void
    774 errorprint(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 
    785 int
    786 inquire(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 
    811 static int
    812 probethisfile(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