xref: /openbsd-src/usr.bin/patch/inp.c (revision 04e367a696f076f52b51f63ff89d03bf74c24ca4)
1 /*	$OpenBSD: inp.c,v 1.20 2003/07/28 19:15:34 deraadt Exp $	*/
2 
3 #ifndef lint
4 static const char     rcsid[] = "$OpenBSD: inp.c,v 1.20 2003/07/28 19:15:34 deraadt Exp $";
5 #endif /* not lint */
6 
7 #include <sys/types.h>
8 #include <sys/file.h>
9 #include <sys/stat.h>
10 
11 #include <ctype.h>
12 #include <libgen.h>
13 #include <limits.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 
19 #include "common.h"
20 #include "util.h"
21 #include "pch.h"
22 #include "inp.h"
23 
24 
25 /* Input-file-with-indexable-lines abstract type */
26 
27 static off_t	i_size;		/* size of the input file */
28 static char	*i_womp;	/* plan a buffer for entire file */
29 static char	**i_ptr;	/* pointers to lines in i_womp */
30 
31 static int	tifd = -1;	/* plan b virtual string array */
32 static char	*tibuf[2];	/* plan b buffers */
33 static LINENUM	tiline[2] = {-1, -1};	/* 1st line in each buffer */
34 static LINENUM	lines_per_buf;	/* how many lines per buffer */
35 static int	tireclen;	/* length of records in tmp file */
36 
37 static bool	rev_in_string(const char *);
38 
39 /* returns false if insufficient memory */
40 static bool	plan_a(const char *);
41 
42 static void	plan_b(const char *);
43 
44 /* New patch--prepare to edit another file. */
45 
46 void
47 re_input(void)
48 {
49 	if (using_plan_a) {
50 		i_size = 0;
51 		free(i_ptr);
52 		free(i_womp);
53 		i_womp = NULL;
54 		i_ptr = NULL;
55 	} else {
56 		using_plan_a = TRUE;	/* maybe the next one is smaller */
57 		close(tifd);
58 		tifd = -1;
59 		free(tibuf[0]);
60 		free(tibuf[1]);
61 		tibuf[0] = tibuf[1] = NULL;
62 		tiline[0] = tiline[1] = -1;
63 		tireclen = 0;
64 	}
65 }
66 
67 /* Constuct the line index, somehow or other. */
68 
69 void
70 scan_input(const char *filename)
71 {
72 	if (!plan_a(filename))
73 		plan_b(filename);
74 	if (verbose) {
75 		say("Patching file %s using Plan %s...\n", filename,
76 		    (using_plan_a ? "A" : "B"));
77 	}
78 }
79 
80 /* Try keeping everything in memory. */
81 
82 static bool
83 plan_a(const char *filename)
84 {
85 	int		ifd, statfailed;
86 	char		*s, lbuf[MAXLINELEN];
87 	LINENUM		iline;
88 	struct stat	filestat;
89 
90 	if (filename == NULL || *filename == '\0')
91 		return FALSE;
92 
93 	statfailed = stat(filename, &filestat);
94 	if (statfailed && ok_to_create_file) {
95 		if (verbose)
96 			say("(Creating file %s...)\n", filename);
97 
98 		/*
99 		 * in check_patch case, we still display `Creating file' even
100 		 * though we're not. The rule is that -C should be as similar
101 		 * to normal patch behavior as possible
102 		 */
103 		if (check_only)
104 			return TRUE;
105 		makedirs(filename, TRUE);
106 		close(creat(filename, 0666));
107 		statfailed = stat(filename, &filestat);
108 	}
109 	if (statfailed && check_only)
110 		fatal("%s not found, -C mode, can't probe further\n", filename);
111 	/* For nonexistent or read-only files, look for RCS or SCCS versions.  */
112 	if (statfailed ||
113 	    /* No one can write to it.  */
114 	    (filestat.st_mode & 0222) == 0 ||
115 	    /* I can't write to it.  */
116 	    ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) {
117 		char	*cs = NULL, *filebase, *filedir;
118 		struct stat	cstat;
119 
120 		filebase = basename(filename);
121 		filedir = dirname(filename);
122 
123 		/* Leave room in lbuf for the diff command.  */
124 		s = lbuf + 20;
125 
126 #define try(f, a1, a2, a3) \
127 	(snprintf(s, sizeof lbuf - 20, f, a1, a2, a3), stat(s, &cstat) == 0)
128 
129 		if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) ||
130 		    try("%s/RCS/%s%s", filedir, filebase, "") ||
131 		    try("%s/%s%s", filedir, filebase, RCSSUFFIX)) {
132 			snprintf(buf, sizeof buf, CHECKOUT, filename);
133 			snprintf(lbuf, sizeof lbuf, RCSDIFF, filename);
134 			cs = "RCS";
135 		} else if (try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) ||
136 		    try("%s/%s%s", filedir, SCCSPREFIX, filebase)) {
137 			snprintf(buf, sizeof buf, GET, s);
138 			snprintf(lbuf, sizeof lbuf, SCCSDIFF, s, filename);
139 			cs = "SCCS";
140 		} else if (statfailed)
141 			fatal("can't find %s\n", filename);
142 		/*
143 		 * else we can't write to it but it's not under a version
144 		 * control system, so just proceed.
145 		 */
146 		if (cs) {
147 			if (!statfailed) {
148 				if ((filestat.st_mode & 0222) != 0)
149 					/* The owner can write to it.  */
150 					fatal("file %s seems to be locked "
151 					    "by somebody else under %s\n",
152 					    filename, cs);
153 				/*
154 				 * It might be checked out unlocked.  See if
155 				 * it's safe to check out the default version
156 				 * locked.
157 				 */
158 				if (verbose)
159 					say("Comparing file %s to default "
160 					    "%s version...\n",
161 					    filename, cs);
162 				if (system(lbuf))
163 					fatal("can't check out file %s: "
164 					    "differs from default %s version\n",
165 					    filename, cs);
166 			}
167 			if (verbose)
168 				say("Checking out file %s from %s...\n",
169 				    filename, cs);
170 			if (system(buf) || stat(filename, &filestat))
171 				fatal("can't check out file %s from %s\n",
172 				    filename, cs);
173 		}
174 	}
175 	filemode = filestat.st_mode;
176 	if (!S_ISREG(filemode))
177 		fatal("%s is not a normal file--can't patch\n", filename);
178 	i_size = filestat.st_size;
179 	if (out_of_mem) {
180 		set_hunkmax();	/* make sure dynamic arrays are allocated */
181 		out_of_mem = FALSE;
182 		return FALSE;	/* force plan b because plan a bombed */
183 	}
184 	if (i_size > SIZE_MAX - 2)
185 		fatal("block too large to allocate");
186 	i_womp = malloc((size_t)(i_size + 2));
187 	if (i_womp == NULL)
188 		return FALSE;
189 	if ((ifd = open(filename, O_RDONLY)) < 0)
190 		pfatal("can't open file %s", filename);
191 
192 	if (read(ifd, i_womp, (size_t) i_size) != i_size) {
193 		close(ifd);	/* probably means i_size > 15 or 16 bits worth */
194 		free(i_womp);	/* at this point it doesn't matter if i_womp was */
195 		return FALSE;	/* undersized. */
196 	}
197 
198 	close(ifd);
199 	if (i_size && i_womp[i_size - 1] != '\n')
200 		i_womp[i_size++] = '\n';
201 	i_womp[i_size] = '\0';
202 
203 	/* count the lines in the buffer so we know how many pointers we need */
204 
205 	iline = 0;
206 	for (s = i_womp; *s; s++) {
207 		if (*s == '\n')
208 			iline++;
209 	}
210 
211 	i_ptr = (char **) malloc((iline + 2) * sizeof(char *));
212 
213 	if (i_ptr == NULL) {	/* shucks, it was a near thing */
214 		free(i_womp);
215 		return FALSE;
216 	}
217 	/* now scan the buffer and build pointer array */
218 
219 	iline = 1;
220 	i_ptr[iline] = i_womp;
221 	for (s = i_womp; *s; s++) {
222 		if (*s == '\n')
223 			i_ptr[++iline] = s + 1;	/* these are NOT NUL terminated */
224 	}
225 	input_lines = iline - 1;
226 
227 	/* now check for revision, if any */
228 
229 	if (revision != NULL) {
230 		if (!rev_in_string(i_womp)) {
231 			if (force) {
232 				if (verbose)
233 					say("Warning: this file doesn't appear "
234 					    "to be the %s version--patching anyway.\n",
235 					    revision);
236 			} else if (batch) {
237 				fatal("this file doesn't appear to be the "
238 				    "%s version--aborting.\n",
239 				    revision);
240 			} else {
241 				ask("This file doesn't appear to be the "
242 				    "%s version--patch anyway? [n] ",
243 				    revision);
244 				if (*buf != 'y')
245 					fatal("aborted\n");
246 			}
247 		} else if (verbose)
248 			say("Good.  This file appears to be the %s version.\n",
249 			    revision);
250 	}
251 	return TRUE;		/* plan a will work */
252 }
253 
254 /* Keep (virtually) nothing in memory. */
255 
256 static void
257 plan_b(const char *filename)
258 {
259 	FILE	*ifp;
260 	int	i = 0, maxlen = 1;
261 	bool	found_revision = (revision == NULL);
262 
263 	using_plan_a = FALSE;
264 	if ((ifp = fopen(filename, "r")) == NULL)
265 		pfatal("can't open file %s", filename);
266 	(void) unlink(TMPINNAME);
267 	if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0)
268 		pfatal("can't open file %s", TMPINNAME);
269 	while (fgets(buf, sizeof buf, ifp) != NULL) {
270 		if (revision != NULL && !found_revision && rev_in_string(buf))
271 			found_revision = TRUE;
272 		if ((i = strlen(buf)) > maxlen)
273 			maxlen = i;	/* find longest line */
274 	}
275 	if (revision != NULL) {
276 		if (!found_revision) {
277 			if (force) {
278 				if (verbose)
279 					say("Warning: this file doesn't appear "
280 					    "to be the %s version--patching anyway.\n",
281 					    revision);
282 			} else if (batch) {
283 				fatal("this file doesn't appear to be the "
284 				    "%s version--aborting.\n",
285 				    revision);
286 			} else {
287 				ask("This file doesn't appear to be the %s "
288 				    "version--patch anyway? [n] ",
289 				    revision);
290 				if (*buf != 'y')
291 					fatal("aborted\n");
292 			}
293 		} else if (verbose)
294 			say("Good.  This file appears to be the %s version.\n",
295 			    revision);
296 	}
297 	fseek(ifp, 0L, SEEK_SET);	/* rewind file */
298 	lines_per_buf = BUFFERSIZE / maxlen;
299 	tireclen = maxlen;
300 	tibuf[0] = malloc(BUFFERSIZE + 1);
301 	if (tibuf[0] == NULL)
302 		fatal("out of memory\n");
303 	tibuf[1] = malloc(BUFFERSIZE + 1);
304 	if (tibuf[1] == NULL)
305 		fatal("out of memory\n");
306 	for (i = 1;; i++) {
307 		if (!(i % lines_per_buf))	/* new block */
308 			if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
309 				pfatal("can't write temp file");
310 		if (fgets(tibuf[0] + maxlen * (i % lines_per_buf),
311 		    maxlen + 1, ifp) == NULL) {
312 			input_lines = i - 1;
313 			if (i % lines_per_buf)
314 				if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
315 					pfatal("can't write temp file");
316 			break;
317 		}
318 	}
319 	fclose(ifp);
320 	close(tifd);
321 	if ((tifd = open(TMPINNAME, O_RDONLY)) < 0)
322 		pfatal("can't reopen file %s", TMPINNAME);
323 }
324 
325 /*
326  * Fetch a line from the input file, \n terminated, not necessarily \0.
327  */
328 char *
329 ifetch(LINENUM line, int whichbuf)
330 {
331 	if (line < 1 || line > input_lines) {
332 		say("No such line %ld in input file, ignoring\n", line);
333 		return NULL;
334 	}
335 	if (using_plan_a)
336 		return i_ptr[line];
337 	else {
338 		LINENUM	offline = line % lines_per_buf;
339 		LINENUM	baseline = line - offline;
340 
341 		if (tiline[0] == baseline)
342 			whichbuf = 0;
343 		else if (tiline[1] == baseline)
344 			whichbuf = 1;
345 		else {
346 			tiline[whichbuf] = baseline;
347 
348 			lseek(tifd, (off_t) (baseline / lines_per_buf *
349 			    BUFFERSIZE), SEEK_SET);
350 
351 			if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0)
352 				pfatal("error reading tmp file %s", TMPINNAME);
353 		}
354 		return tibuf[whichbuf] + (tireclen * offline);
355 	}
356 }
357 
358 /*
359  * True if the string argument contains the revision number we want.
360  */
361 static bool
362 rev_in_string(const char *string)
363 {
364 	const char	*s;
365 	int		patlen;
366 
367 	if (revision == NULL)
368 		return TRUE;
369 	patlen = strlen(revision);
370 	if (strnEQ(string, revision, patlen) && isspace(string[patlen]))
371 		return TRUE;
372 	for (s = string; *s; s++) {
373 		if (isspace(*s) && strnEQ(s + 1, revision, patlen) &&
374 		    isspace(s[patlen + 1])) {
375 			return TRUE;
376 		}
377 	}
378 	return FALSE;
379 }
380