inp.c revision 285979
1/*-
2 * Copyright 1986, Larry Wall
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following condition is met:
6 * 1. Redistributions of source code must retain the above copyright notice,
7 * this condition and the following disclaimer.
8 *
9 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
10 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
12 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
13 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
14 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
15 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
16 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
17 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
18 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
19 * SUCH DAMAGE.
20 *
21 * patch - a program to apply diffs to original files
22 *
23 * -C option added in 1998, original code by Marc Espie, based on FreeBSD
24 * behaviour
25 *
26 * $OpenBSD: inp.c,v 1.36 2012/04/10 14:46:34 ajacoutot Exp $
27 * $FreeBSD: releng/10.1/usr.bin/patch/inp.c 285979 2015-07-28 19:59:11Z delphij $
28 */
29
30#include <sys/types.h>
31#include <sys/file.h>
32#include <sys/stat.h>
33#include <sys/mman.h>
34#include <sys/wait.h>
35
36#include <ctype.h>
37#include <errno.h>
38#include <libgen.h>
39#include <limits.h>
40#include <stddef.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45
46#include "common.h"
47#include "util.h"
48#include "pch.h"
49#include "inp.h"
50
51
52/* Input-file-with-indexable-lines abstract type */
53
54static size_t	i_size;		/* size of the input file */
55static char	*i_womp;	/* plan a buffer for entire file */
56static char	**i_ptr;	/* pointers to lines in i_womp */
57static char	empty_line[] = { '\0' };
58
59static int	tifd = -1;	/* plan b virtual string array */
60static char	*tibuf[2];	/* plan b buffers */
61static LINENUM	tiline[2] = {-1, -1};	/* 1st line in each buffer */
62static LINENUM	lines_per_buf;	/* how many lines per buffer */
63static int	tireclen;	/* length of records in tmp file */
64
65static bool	rev_in_string(const char *);
66static bool	reallocate_lines(size_t *);
67
68/* returns false if insufficient memory */
69static bool	plan_a(const char *);
70
71static void	plan_b(const char *);
72
73/* New patch--prepare to edit another file. */
74
75void
76re_input(void)
77{
78	if (using_plan_a) {
79		free(i_ptr);
80		i_ptr = NULL;
81		if (i_womp != NULL) {
82			munmap(i_womp, i_size);
83			i_womp = NULL;
84		}
85		i_size = 0;
86	} else {
87		using_plan_a = true;	/* maybe the next one is smaller */
88		close(tifd);
89		tifd = -1;
90		free(tibuf[0]);
91		free(tibuf[1]);
92		tibuf[0] = tibuf[1] = NULL;
93		tiline[0] = tiline[1] = -1;
94		tireclen = 0;
95	}
96}
97
98/* Construct the line index, somehow or other. */
99
100void
101scan_input(const char *filename)
102{
103	if (!plan_a(filename))
104		plan_b(filename);
105	if (verbose) {
106		say("Patching file %s using Plan %s...\n", filename,
107		    (using_plan_a ? "A" : "B"));
108	}
109}
110
111static bool
112reallocate_lines(size_t *lines_allocated)
113{
114	char	**p;
115	size_t	new_size;
116
117	new_size = *lines_allocated * 3 / 2;
118	p = realloc(i_ptr, (new_size + 2) * sizeof(char *));
119	if (p == NULL) {	/* shucks, it was a near thing */
120		munmap(i_womp, i_size);
121		i_womp = NULL;
122		free(i_ptr);
123		i_ptr = NULL;
124		*lines_allocated = 0;
125		return false;
126	}
127	*lines_allocated = new_size;
128	i_ptr = p;
129	return true;
130}
131
132/* Try keeping everything in memory. */
133
134static bool
135plan_a(const char *filename)
136{
137	int		ifd, statfailed, devnull, pstat;
138	char		*p, *s, lbuf[INITLINELEN];
139	struct stat	filestat;
140	ptrdiff_t	sz;
141	size_t		i;
142	size_t		iline, lines_allocated;
143	pid_t		pid;
144	char		*argp[4] = {NULL};
145
146#ifdef DEBUGGING
147	if (debug & 8)
148		return false;
149#endif
150
151	if (filename == NULL || *filename == '\0')
152		return false;
153
154	statfailed = stat(filename, &filestat);
155	if (statfailed && ok_to_create_file) {
156		if (verbose)
157			say("(Creating file %s...)\n", filename);
158
159		/*
160		 * in check_patch case, we still display `Creating file' even
161		 * though we're not. The rule is that -C should be as similar
162		 * to normal patch behavior as possible
163		 */
164		if (check_only)
165			return true;
166		makedirs(filename, true);
167		close(creat(filename, 0666));
168		statfailed = stat(filename, &filestat);
169	}
170	if (statfailed && check_only)
171		fatal("%s not found, -C mode, can't probe further\n", filename);
172	/* For nonexistent or read-only files, look for RCS versions.  */
173
174	if (statfailed ||
175	    /* No one can write to it.  */
176	    (filestat.st_mode & 0222) == 0 ||
177	    /* I can't write to it.  */
178	    ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) {
179		char	*filebase, *filedir;
180		struct stat	cstat;
181		char *tmp_filename1, *tmp_filename2;
182
183		tmp_filename1 = strdup(filename);
184		tmp_filename2 = strdup(filename);
185		if (tmp_filename1 == NULL || tmp_filename2 == NULL)
186			fatal("strdupping filename");
187
188		filebase = basename(tmp_filename1);
189		filedir = dirname(tmp_filename2);
190
191#define try(f, a1, a2, a3) \
192	(snprintf(lbuf, sizeof(lbuf), f, a1, a2, a3), stat(lbuf, &cstat) == 0)
193
194		/*
195		 * else we can't write to it but it's not under a version
196		 * control system, so just proceed.
197		 */
198		if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
199		    try("%s/RCS/%s%s", filedir, filebase, "") ||
200		    try("%s/%s%s", filedir, filebase, RCSSUFFIX)) {
201			if (!statfailed) {
202				if ((filestat.st_mode & 0222) != 0)
203					/* The owner can write to it.  */
204					fatal("file %s seems to be locked "
205					    "by somebody else under RCS\n",
206					    filename);
207				/*
208				 * It might be checked out unlocked.  See if
209				 * it's safe to check out the default version
210				 * locked.
211				 */
212				if (verbose)
213					say("Comparing file %s to default "
214					    "RCS version...\n", filename);
215
216				switch (pid = fork()) {
217				case -1:
218					fatal("can't fork: %s\n",
219					    strerror(errno));
220				case 0:
221					devnull = open("/dev/null", O_RDONLY);
222					if (devnull == -1) {
223						fatal("can't open /dev/null: %s",
224						    strerror(errno));
225					}
226					(void)dup2(devnull, STDOUT_FILENO);
227					argp[0] = strdup(RCSDIFF);
228					argp[1] = strdup(filename);
229					execv(RCSDIFF, argp);
230					exit(127);
231				}
232				pid = waitpid(pid, &pstat, 0);
233				if (pid == -1 || WEXITSTATUS(pstat) != 0) {
234					fatal("can't check out file %s: "
235					    "differs from default RCS version\n",
236					    filename);
237				}
238			}
239
240			if (verbose)
241				say("Checking out file %s from RCS...\n",
242				    filename);
243
244			switch (pid = fork()) {
245			case -1:
246				fatal("can't fork: %s\n", strerror(errno));
247			case 0:
248				argp[0] = strdup(CHECKOUT);
249				argp[1] = strdup("-l");
250				argp[2] = strdup(filename);
251				execv(CHECKOUT, argp);
252				exit(127);
253			}
254			pid = waitpid(pid, &pstat, 0);
255			if (pid == -1 || WEXITSTATUS(pstat) != 0 ||
256			    stat(filename, &filestat)) {
257				fatal("can't check out file %s from RCS\n",
258				    filename);
259			}
260		} else if (statfailed) {
261			fatal("can't find %s\n", filename);
262		}
263		free(tmp_filename1);
264		free(tmp_filename2);
265	}
266
267	filemode = filestat.st_mode;
268	if (!S_ISREG(filemode))
269		fatal("%s is not a normal file--can't patch\n", filename);
270	if ((uint64_t)filestat.st_size > SIZE_MAX) {
271		say("block too large to mmap\n");
272		return false;
273	}
274	i_size = (size_t)filestat.st_size;
275	if (out_of_mem) {
276		set_hunkmax();	/* make sure dynamic arrays are allocated */
277		out_of_mem = false;
278		return false;	/* force plan b because plan a bombed */
279	}
280	if ((ifd = open(filename, O_RDONLY)) < 0)
281		pfatal("can't open file %s", filename);
282
283	if (i_size) {
284		i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0);
285		if (i_womp == MAP_FAILED) {
286			perror("mmap failed");
287			i_womp = NULL;
288			close(ifd);
289			return false;
290		}
291	} else {
292		i_womp = NULL;
293	}
294
295	close(ifd);
296	if (i_size)
297		madvise(i_womp, i_size, MADV_SEQUENTIAL);
298
299	/* estimate the number of lines */
300	lines_allocated = i_size / 25;
301	if (lines_allocated < 100)
302		lines_allocated = 100;
303
304	if (!reallocate_lines(&lines_allocated))
305		return false;
306
307	/* now scan the buffer and build pointer array */
308	iline = 1;
309	i_ptr[iline] = i_womp;
310	/* test for NUL too, to maintain the behavior of the original code */
311	for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) {
312		if (*s == '\n') {
313			if (iline == lines_allocated) {
314				if (!reallocate_lines(&lines_allocated))
315					return false;
316			}
317			/* these are NOT NUL terminated */
318			i_ptr[++iline] = s + 1;
319		}
320	}
321	/* if the last line contains no EOL, append one */
322	if (i_size > 0 && i_womp[i_size - 1] != '\n') {
323		last_line_missing_eol = true;
324		/* fix last line */
325		sz = s - i_ptr[iline];
326		p = malloc(sz + 1);
327		if (p == NULL) {
328			free(i_ptr);
329			i_ptr = NULL;
330			munmap(i_womp, i_size);
331			i_womp = NULL;
332			return false;
333		}
334
335		memcpy(p, i_ptr[iline], sz);
336		p[sz] = '\n';
337		i_ptr[iline] = p;
338		/* count the extra line and make it point to some valid mem */
339		i_ptr[++iline] = empty_line;
340	} else
341		last_line_missing_eol = false;
342
343	input_lines = iline - 1;
344
345	/* now check for revision, if any */
346
347	if (revision != NULL) {
348		if (!rev_in_string(i_womp)) {
349			if (force) {
350				if (verbose)
351					say("Warning: this file doesn't appear "
352					    "to be the %s version--patching anyway.\n",
353					    revision);
354			} else if (batch) {
355				fatal("this file doesn't appear to be the "
356				    "%s version--aborting.\n",
357				    revision);
358			} else {
359				ask("This file doesn't appear to be the "
360				    "%s version--patch anyway? [n] ",
361				    revision);
362				if (*buf != 'y')
363					fatal("aborted\n");
364			}
365		} else if (verbose)
366			say("Good.  This file appears to be the %s version.\n",
367			    revision);
368	}
369	return true;		/* plan a will work */
370}
371
372/* Keep (virtually) nothing in memory. */
373
374static void
375plan_b(const char *filename)
376{
377	FILE	*ifp;
378	size_t	i = 0, j, maxlen = 1;
379	char	*p;
380	bool	found_revision = (revision == NULL);
381
382	using_plan_a = false;
383	if ((ifp = fopen(filename, "r")) == NULL)
384		pfatal("can't open file %s", filename);
385	unlink(TMPINNAME);
386	if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0)
387		pfatal("can't open file %s", TMPINNAME);
388	while (fgets(buf, buf_size, ifp) != NULL) {
389		if (revision != NULL && !found_revision && rev_in_string(buf))
390			found_revision = true;
391		if ((i = strlen(buf)) > maxlen)
392			maxlen = i;	/* find longest line */
393	}
394	last_line_missing_eol = i > 0 && buf[i - 1] != '\n';
395	if (last_line_missing_eol && maxlen == i)
396		maxlen++;
397
398	if (revision != NULL) {
399		if (!found_revision) {
400			if (force) {
401				if (verbose)
402					say("Warning: this file doesn't appear "
403					    "to be the %s version--patching anyway.\n",
404					    revision);
405			} else if (batch) {
406				fatal("this file doesn't appear to be the "
407				    "%s version--aborting.\n",
408				    revision);
409			} else {
410				ask("This file doesn't appear to be the %s "
411				    "version--patch anyway? [n] ",
412				    revision);
413				if (*buf != 'y')
414					fatal("aborted\n");
415			}
416		} else if (verbose)
417			say("Good.  This file appears to be the %s version.\n",
418			    revision);
419	}
420	fseek(ifp, 0L, SEEK_SET);	/* rewind file */
421	lines_per_buf = BUFFERSIZE / maxlen;
422	tireclen = maxlen;
423	tibuf[0] = malloc(BUFFERSIZE + 1);
424	if (tibuf[0] == NULL)
425		fatal("out of memory\n");
426	tibuf[1] = malloc(BUFFERSIZE + 1);
427	if (tibuf[1] == NULL)
428		fatal("out of memory\n");
429	for (i = 1;; i++) {
430		p = tibuf[0] + maxlen * (i % lines_per_buf);
431		if (i % lines_per_buf == 0)	/* new block */
432			if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
433				pfatal("can't write temp file");
434		if (fgets(p, maxlen + 1, ifp) == NULL) {
435			input_lines = i - 1;
436			if (i % lines_per_buf != 0)
437				if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
438					pfatal("can't write temp file");
439			break;
440		}
441		j = strlen(p);
442		/* These are '\n' terminated strings, so no need to add a NUL */
443		if (j == 0 || p[j - 1] != '\n')
444			p[j] = '\n';
445	}
446	fclose(ifp);
447	close(tifd);
448	if ((tifd = open(TMPINNAME, O_RDONLY)) < 0)
449		pfatal("can't reopen file %s", TMPINNAME);
450}
451
452/*
453 * Fetch a line from the input file, \n terminated, not necessarily \0.
454 */
455char *
456ifetch(LINENUM line, int whichbuf)
457{
458	if (line < 1 || line > input_lines) {
459		if (warn_on_invalid_line) {
460			say("No such line %ld in input file, ignoring\n", line);
461			warn_on_invalid_line = false;
462		}
463		return NULL;
464	}
465	if (using_plan_a)
466		return i_ptr[line];
467	else {
468		LINENUM	offline = line % lines_per_buf;
469		LINENUM	baseline = line - offline;
470
471		if (tiline[0] == baseline)
472			whichbuf = 0;
473		else if (tiline[1] == baseline)
474			whichbuf = 1;
475		else {
476			tiline[whichbuf] = baseline;
477
478			if (lseek(tifd, (off_t) (baseline / lines_per_buf *
479			    BUFFERSIZE), SEEK_SET) < 0)
480				pfatal("cannot seek in the temporary input file");
481
482			if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0)
483				pfatal("error reading tmp file %s", TMPINNAME);
484		}
485		return tibuf[whichbuf] + (tireclen * offline);
486	}
487}
488
489/*
490 * True if the string argument contains the revision number we want.
491 */
492static bool
493rev_in_string(const char *string)
494{
495	const char	*s;
496	size_t		patlen;
497
498	if (revision == NULL)
499		return true;
500	patlen = strlen(revision);
501	if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen]))
502		return true;
503	for (s = string; *s; s++) {
504		if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) &&
505		    isspace((unsigned char)s[patlen + 1])) {
506			return true;
507		}
508	}
509	return false;
510}
511