GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/patch/inp.c Lines: 67 222 30.2 %
Date: 2017-11-07 Branches: 36 166 21.7 %

Line Branch Exec Source
1
/*	$OpenBSD: inp.c,v 1.47 2017/03/25 23:13:45 deraadt Exp $	*/
2
3
/*
4
 * patch - a program to apply diffs to original files
5
 *
6
 * Copyright 1986, Larry Wall
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following condition is met:
10
 * 1. Redistributions of source code must retain the above copyright notice,
11
 * this condition and the following disclaimer.
12
 *
13
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
14
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
17
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23
 * SUCH DAMAGE.
24
 *
25
 * -C option added in 1998, original code by Marc Espie, based on FreeBSD
26
 * behaviour
27
 */
28
29
#include <sys/types.h>
30
#include <sys/file.h>
31
#include <sys/stat.h>
32
#include <sys/mman.h>
33
34
#include <ctype.h>
35
#include <libgen.h>
36
#include <stddef.h>
37
#include <stdint.h>
38
#include <stdio.h>
39
#include <stdlib.h>
40
#include <string.h>
41
#include <unistd.h>
42
43
#include "common.h"
44
#include "util.h"
45
#include "pch.h"
46
#include "inp.h"
47
48
49
/* Input-file-with-indexable-lines abstract type */
50
51
static off_t	i_size;		/* size of the input file */
52
static char	*i_womp;	/* plan a buffer for entire file */
53
static char	**i_ptr;	/* pointers to lines in i_womp */
54
55
static int	tifd = -1;	/* plan b virtual string array */
56
static char	*tibuf[2];	/* plan b buffers */
57
static LINENUM	tiline[2] = {-1, -1};	/* 1st line in each buffer */
58
static size_t	lines_per_buf;	/* how many lines per buffer */
59
static size_t	tibuflen;	/* plan b buffer length */
60
static size_t	tireclen;	/* length of records in tmp file */
61
62
static bool	rev_in_string(const char *);
63
static bool	reallocate_lines(size_t *);
64
65
/* returns false if insufficient memory */
66
static bool	plan_a(const char *);
67
68
static void	plan_b(const char *);
69
70
/* New patch--prepare to edit another file. */
71
72
void
73
re_input(void)
74
{
75
936
	if (using_plan_a) {
76
468
		free(i_ptr);
77
468
		i_ptr = NULL;
78
468
		if (i_womp != NULL) {
79
432
			munmap(i_womp, i_size);
80
432
			i_womp = NULL;
81
432
		}
82
		i_size = 0;
83
	} else {
84
		using_plan_a = true;	/* maybe the next one is smaller */
85
		close(tifd);
86
		tifd = -1;
87
		free(tibuf[0]);
88
		free(tibuf[1]);
89
		tibuf[0] = tibuf[1] = NULL;
90
		tiline[0] = tiline[1] = -1;
91
		tireclen = 0;
92
	}
93
468
}
94
95
/* Construct the line index, somehow or other. */
96
97
void
98
scan_input(const char *filename)
99
{
100
936
	if (!plan_a(filename))
101
		plan_b(filename);
102
468
	if (verbose) {
103
		say("Patching file %s using Plan %s...\n", filename,
104
		    (using_plan_a ? "A" : "B"));
105
	}
106
468
}
107
108
static bool
109
reallocate_lines(size_t *lines_allocatedp)
110
{
111
	char	**p;
112
	size_t	new_size;
113
114
936
	new_size = *lines_allocatedp * 3 / 2;
115
468
	p = reallocarray(i_ptr, new_size + 2, sizeof(char *));
116
468
	if (p == NULL) {	/* shucks, it was a near thing */
117
		munmap(i_womp, i_size);
118
		i_womp = NULL;
119
		free(i_ptr);
120
		i_ptr = NULL;
121
		*lines_allocatedp = 0;
122
		return false;
123
	}
124
468
	*lines_allocatedp = new_size;
125
468
	i_ptr = p;
126
468
	return true;
127
468
}
128
129
/* Try keeping everything in memory. */
130
131
static bool
132
plan_a(const char *filename)
133
{
134
	int		ifd, statfailed;
135
	char		*p, *s;
136
936
	struct stat	filestat;
137
	off_t		i;
138
	ptrdiff_t	sz;
139
468
	size_t		iline, lines_allocated;
140
141
#ifdef DEBUGGING
142
468
	if (debug & 8)
143
		return false;
144
#endif
145
146

936
	if (filename == NULL || *filename == '\0')
147
		return false;
148
149
468
	statfailed = stat(filename, &filestat);
150

468
	if (statfailed && ok_to_create_file) {
151
		int fd;
152
153
		if (verbose)
154
			say("(Creating file %s...)\n", filename);
155
156
		/*
157
		 * in check_patch case, we still display `Creating file' even
158
		 * though we're not. The rule is that -C should be as similar
159
		 * to normal patch behavior as possible
160
		 */
161
		if (check_only)
162
			return true;
163
		makedirs(filename, true);
164
		if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0666)) != -1)
165
			close(fd);
166
167
		statfailed = stat(filename, &filestat);
168
	}
169
468
	if (statfailed)
170
		fatal("can't find %s\n", filename);
171
468
	filemode = filestat.st_mode;
172
468
	if (!S_ISREG(filemode))
173
		fatal("%s is not a normal file--can't patch\n", filename);
174
468
	i_size = filestat.st_size;
175
468
	if (out_of_mem) {
176
		set_hunkmax();	/* make sure dynamic arrays are allocated */
177
		out_of_mem = false;
178
		return false;	/* force plan b because plan a bombed */
179
	}
180
468
	if (i_size > SIZE_MAX) {
181
		say("block too large to mmap\n");
182
		return false;
183
	}
184
468
	if ((ifd = open(filename, O_RDONLY)) < 0)
185
		pfatal("can't open file %s", filename);
186
187
468
	if (i_size) {
188
432
		i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0);
189
432
		if (i_womp == MAP_FAILED) {
190
			perror("mmap failed");
191
			i_womp = NULL;
192
			close(ifd);
193
			return false;
194
		}
195
	} else {
196
36
		i_womp = NULL;
197
	}
198
199
468
	close(ifd);
200
468
	if (i_size)
201
432
		madvise(i_womp, i_size, MADV_SEQUENTIAL);
202
203
	/* estimate the number of lines */
204
468
	lines_allocated = i_size / 25;
205
468
	if (lines_allocated < 100)
206
468
		lines_allocated = 100;
207
208
468
	if (!reallocate_lines(&lines_allocated))
209
		return false;
210
211
	/* now scan the buffer and build pointer array */
212
	iline = 1;
213
468
	i_ptr[iline] = i_womp;
214
	/* test for NUL too, to maintain the behavior of the original code */
215

8887716
	for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) {
216
2962260
		if (*s == '\n') {
217
127080
			if (iline == lines_allocated) {
218
				if (!reallocate_lines(&lines_allocated))
219
					return false;
220
			}
221
			/* these are NOT NUL terminated */
222
127080
			i_ptr[++iline] = s + 1;
223
127080
		}
224
	}
225
	/* if the last line contains no EOL, append one */
226

900
	if (i_size > 0 && i_womp[i_size - 1] != '\n') {
227
180
		last_line_missing_eol = true;
228
		/* fix last line */
229
180
		sz = s - i_ptr[iline];
230
180
		p = malloc(sz + 1);
231
180
		if (p == NULL) {
232
			free(i_ptr);
233
			i_ptr = NULL;
234
			munmap(i_womp, i_size);
235
			i_womp = NULL;
236
			return false;
237
		}
238
239
180
		memcpy(p, i_ptr[iline], sz);
240
180
		p[sz] = '\n';
241
180
		i_ptr[iline] = p;
242
		/* count the extra line and make it point to some valid mem */
243
180
		i_ptr[++iline] = "";
244
180
	} else
245
288
		last_line_missing_eol = false;
246
247
468
	input_lines = iline - 1;
248
249
	/* now check for revision, if any */
250
251
468
	if (revision != NULL) {
252
		if (i_womp == NULL || !rev_in_string(i_womp)) {
253
			if (force) {
254
				if (verbose)
255
					say("Warning: this file doesn't appear "
256
					    "to be the %s version--patching anyway.\n",
257
					    revision);
258
			} else if (batch) {
259
				fatal("this file doesn't appear to be the "
260
				    "%s version--aborting.\n",
261
				    revision);
262
			} else {
263
				ask("This file doesn't appear to be the "
264
				    "%s version--patch anyway? [n] ",
265
				    revision);
266
				if (*buf != 'y')
267
					fatal("aborted\n");
268
			}
269
		} else if (verbose)
270
			say("Good.  This file appears to be the %s version.\n",
271
			    revision);
272
	}
273
468
	return true;		/* plan a will work */
274
468
}
275
276
/* Keep (virtually) nothing in memory. */
277
278
static void
279
plan_b(const char *filename)
280
{
281
	FILE	*ifp;
282
	size_t	i = 0, j, len, maxlen = 1;
283
	char	*lbuf = NULL, *p;
284
	bool	found_revision = (revision == NULL);
285
286
	using_plan_a = false;
287
	if ((ifp = fopen(filename, "r")) == NULL)
288
		pfatal("can't open file %s", filename);
289
	(void) unlink(TMPINNAME);
290
	if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0)
291
		pfatal("can't open file %s", TMPINNAME);
292
	while ((p = fgetln(ifp, &len)) != NULL) {
293
		if (p[len - 1] == '\n')
294
			p[len - 1] = '\0';
295
		else {
296
			/* EOF without EOL, copy and add the NUL */
297
			if ((lbuf = malloc(len + 1)) == NULL)
298
				fatal("out of memory\n");
299
			memcpy(lbuf, p, len);
300
			lbuf[len] = '\0';
301
			p = lbuf;
302
303
			last_line_missing_eol = true;
304
			len++;
305
		}
306
		if (revision != NULL && !found_revision && rev_in_string(p))
307
			found_revision = true;
308
		if (len > maxlen)
309
			maxlen = len;	/* find longest line */
310
	}
311
	free(lbuf);
312
	if (ferror(ifp))
313
		pfatal("can't read file %s", filename);
314
315
	if (revision != NULL) {
316
		if (!found_revision) {
317
			if (force) {
318
				if (verbose)
319
					say("Warning: this file doesn't appear "
320
					    "to be the %s version--patching anyway.\n",
321
					    revision);
322
			} else if (batch) {
323
				fatal("this file doesn't appear to be the "
324
				    "%s version--aborting.\n",
325
				    revision);
326
			} else {
327
				ask("This file doesn't appear to be the %s "
328
				    "version--patch anyway? [n] ",
329
				    revision);
330
				if (*buf != 'y')
331
					fatal("aborted\n");
332
			}
333
		} else if (verbose)
334
			say("Good.  This file appears to be the %s version.\n",
335
			    revision);
336
	}
337
	fseek(ifp, 0L, SEEK_SET);	/* rewind file */
338
	tireclen = maxlen;
339
	tibuflen = maxlen > BUFFERSIZE ? maxlen : BUFFERSIZE;
340
	lines_per_buf = tibuflen / maxlen;
341
	tibuf[0] = malloc(tibuflen + 1);
342
	if (tibuf[0] == NULL)
343
		fatal("out of memory\n");
344
	tibuf[1] = malloc(tibuflen + 1);
345
	if (tibuf[1] == NULL)
346
		fatal("out of memory\n");
347
	for (i = 1;; i++) {
348
		p = tibuf[0] + maxlen * (i % lines_per_buf);
349
		if (i % lines_per_buf == 0)	/* new block */
350
			if (write(tifd, tibuf[0], tibuflen) !=
351
			    (ssize_t) tibuflen)
352
				pfatal("can't write temp file");
353
		if (fgets(p, maxlen + 1, ifp) == NULL) {
354
			input_lines = i - 1;
355
			if (i % lines_per_buf != 0)
356
				if (write(tifd, tibuf[0], tibuflen) !=
357
				    (ssize_t) tibuflen)
358
					pfatal("can't write temp file");
359
			break;
360
		}
361
		j = strlen(p);
362
		/* These are '\n' terminated strings, so no need to add a NUL */
363
		if (j == 0 || p[j - 1] != '\n')
364
			p[j] = '\n';
365
	}
366
	fclose(ifp);
367
	close(tifd);
368
	if ((tifd = open(TMPINNAME, O_RDONLY)) < 0)
369
		pfatal("can't reopen file %s", TMPINNAME);
370
}
371
372
/*
373
 * Fetch a line from the input file, \n terminated, not necessarily \0.
374
 */
375
char *
376
ifetch(LINENUM line, int whichbuf)
377
{
378

427734
	if (line < 1 || line > input_lines) {
379
		if (warn_on_invalid_line) {
380
			say("No such line %ld in input file, ignoring\n", line);
381
			warn_on_invalid_line = false;
382
		}
383
		return NULL;
384
	}
385
142578
	if (using_plan_a)
386
142578
		return i_ptr[line];
387
	else {
388
		LINENUM	offline = line % lines_per_buf;
389
		LINENUM	baseline = line - offline;
390
391
		if (tiline[0] == baseline)
392
			whichbuf = 0;
393
		else if (tiline[1] == baseline)
394
			whichbuf = 1;
395
		else {
396
			tiline[whichbuf] = baseline;
397
398
			if (lseek(tifd, (off_t) (baseline / lines_per_buf *
399
			    tibuflen), SEEK_SET) < 0)
400
				pfatal("cannot seek in the temporary input file");
401
402
			if (read(tifd, tibuf[whichbuf], tibuflen)
403
			    != (ssize_t) tibuflen)
404
				pfatal("error reading tmp file %s", TMPINNAME);
405
		}
406
		return tibuf[whichbuf] + (tireclen * offline);
407
	}
408
142578
}
409
410
/*
411
 * True if the string argument contains the revision number we want.
412
 */
413
static bool
414
rev_in_string(const char *string)
415
{
416
	const char	*s;
417
	size_t		patlen;
418
419
	if (revision == NULL)
420
		return true;
421
	patlen = strlen(revision);
422
	if (strnEQ(string, revision, patlen) &&
423
	    isspace((unsigned char)string[patlen]))
424
		return true;
425
	for (s = string; *s; s++) {
426
		if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) &&
427
		    isspace((unsigned char)s[patlen + 1])) {
428
			return true;
429
		}
430
	}
431
	return false;
432
}