Fri Jul 24 18:56:00 2015 UTC ()
From Martin Natano @bitrig: Use execve(2) instead of system to apply patches
that require rcs command execution instead system(3) to avoid malicious
filenames in patches causing bad things to happen. In the process, lose SCCS
support. It is not like we are shipping sccs commands for that to work.


(christos)
diff -r1.23 -r1.24 src/usr.bin/patch/inp.c

cvs diff -r1.23 -r1.24 src/usr.bin/patch/inp.c (switch to unified diff)

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