xref: /openbsd-src/usr.bin/patch/pch.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: pch.c,v 1.27 2003/07/29 20:10:17 millert Exp $	*/
2 
3 #ifndef lint
4 static const char rcsid[] = "$OpenBSD: pch.c,v 1.27 2003/07/29 20:10:17 millert Exp $";
5 #endif /* not lint */
6 
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 
10 #include <assert.h>
11 #include <ctype.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 
17 #include "common.h"
18 #include "util.h"
19 #include "pch.h"
20 #include "pathnames.h"
21 
22 /* Patch (diff listing) abstract type. */
23 
24 static long	p_filesize;	/* size of the patch file */
25 static LINENUM	p_first;	/* 1st line number */
26 static LINENUM	p_newfirst;	/* 1st line number of replacement */
27 static LINENUM	p_ptrn_lines;	/* # lines in pattern */
28 static LINENUM	p_repl_lines;	/* # lines in replacement text */
29 static LINENUM	p_end = -1;	/* last line in hunk */
30 static LINENUM	p_max;		/* max allowed value of p_end */
31 static LINENUM	p_context = 3;	/* # of context lines */
32 static LINENUM	p_input_line = 0;	/* current line # from patch file */
33 static char	**p_line = NULL;/* the text of the hunk */
34 static short	*p_len = NULL;	/* length of each line */
35 static char	*p_char = NULL;	/* +, -, and ! */
36 static int	hunkmax = INITHUNKMAX;	/* size of above arrays to begin with */
37 static int	p_indent;	/* indent to patch */
38 static LINENUM	p_base;		/* where to intuit this time */
39 static LINENUM	p_bline;	/* line # of p_base */
40 static LINENUM	p_start;	/* where intuit found a patch */
41 static LINENUM	p_sline;	/* and the line number for it */
42 static LINENUM	p_hunk_beg;	/* line number of current hunk */
43 static LINENUM	p_efake = -1;	/* end of faked up lines--don't free */
44 static LINENUM	p_bfake = -1;	/* beg of faked up lines */
45 static FILE	*pfp = NULL;	/* patch file pointer */
46 static char	*bestguess = NULL;	/* guess at correct filename */
47 
48 static void	grow_hunkmax(void);
49 static int	intuit_diff_type(void);
50 static void	next_intuit_at(LINENUM, LINENUM);
51 static void	skip_to(LINENUM, LINENUM);
52 static char	*pgets(char *, int, FILE *);
53 
54 
55 /*
56  * Prepare to look for the next patch in the patch file.
57  */
58 void
59 re_patch(void)
60 {
61 	p_first = 0;
62 	p_newfirst = 0;
63 	p_ptrn_lines = 0;
64 	p_repl_lines = 0;
65 	p_end = (LINENUM) - 1;
66 	p_max = 0;
67 	p_indent = 0;
68 }
69 
70 /*
71  * Open the patch file at the beginning of time.
72  */
73 void
74 open_patch_file(const char *filename)
75 {
76 	struct stat filestat;
77 
78 	if (filename == NULL || *filename == '\0' || strEQ(filename, "-")) {
79 		pfp = fopen(TMPPATNAME, "w");
80 		if (pfp == NULL)
81 			pfatal("can't create %s", TMPPATNAME);
82 		while (fgets(buf, sizeof buf, stdin) != NULL)
83 			fputs(buf, pfp);
84 		fclose(pfp);
85 		filename = TMPPATNAME;
86 	}
87 	pfp = fopen(filename, "r");
88 	if (pfp == NULL)
89 		pfatal("patch file %s not found", filename);
90 	fstat(fileno(pfp), &filestat);
91 	p_filesize = filestat.st_size;
92 	next_intuit_at(0L, 1L);	/* start at the beginning */
93 	set_hunkmax();
94 }
95 
96 /*
97  * Make sure our dynamically realloced tables are malloced to begin with.
98  */
99 void
100 set_hunkmax(void)
101 {
102 	if (p_line == NULL)
103 		p_line = malloc((size_t) hunkmax * sizeof(char *));
104 	if (p_len == NULL)
105 		p_len = malloc((size_t) hunkmax * sizeof(short));
106 	if (p_char == NULL)
107 		p_char = malloc((size_t) hunkmax * sizeof(char));
108 }
109 
110 /*
111  * Enlarge the arrays containing the current hunk of patch.
112  */
113 static void
114 grow_hunkmax(void)
115 {
116 	hunkmax *= 2;
117 
118 	/*
119 	 * Note that on most systems, only the p_line array ever gets fresh memory
120 	 * since p_len can move into p_line's old space, and p_char can move into
121 	 * p_len's old space.  Not on PDP-11's however.  But it doesn't matter.
122 	 */
123 	assert(p_line != NULL && p_len != NULL && p_char != NULL);
124 
125 	p_line = realloc(p_line, hunkmax * sizeof(char *));
126 	p_len = realloc(p_len, hunkmax * sizeof(short));
127 	p_char = realloc(p_char, hunkmax * sizeof(char));
128 
129 	if (p_line != NULL && p_len != NULL && p_char != NULL)
130 		return;
131 	if (!using_plan_a)
132 		fatal("out of memory\n");
133 	out_of_mem = TRUE;	/* whatever is null will be allocated again */
134 	/* from within plan_a(), of all places */
135 }
136 
137 /* True if the remainder of the patch file contains a diff of some sort. */
138 
139 bool
140 there_is_another_patch(void)
141 {
142 	if (p_base != 0L && p_base >= p_filesize) {
143 		if (verbose)
144 			say("done\n");
145 		return FALSE;
146 	}
147 	if (verbose)
148 		say("Hmm...");
149 	diff_type = intuit_diff_type();
150 	if (!diff_type) {
151 		if (p_base != 0L) {
152 			if (verbose)
153 				say("  Ignoring the trailing garbage.\ndone\n");
154 		} else
155 			say("  I can't seem to find a patch in there anywhere.\n");
156 		return FALSE;
157 	}
158 	if (verbose)
159 		say("  %sooks like %s to me...\n",
160 		    (p_base == 0L ? "L" : "The next patch l"),
161 		    diff_type == UNI_DIFF ? "a unified diff" :
162 		    diff_type == CONTEXT_DIFF ? "a context diff" :
163 		diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" :
164 		    diff_type == NORMAL_DIFF ? "a normal diff" :
165 		    "an ed script");
166 	if (p_indent && verbose)
167 		say("(Patch is indented %d space%s.)\n", p_indent,
168 		    p_indent == 1 ? "" : "s");
169 	skip_to(p_start, p_sline);
170 	while (filearg[0] == NULL) {
171 		if (force || batch) {
172 			say("No file to patch.  Skipping...\n");
173 			filearg[0] = savestr(bestguess);
174 			skip_rest_of_patch = TRUE;
175 			return TRUE;
176 		}
177 		ask("File to patch: ");
178 		if (*buf != '\n') {
179 			free(bestguess);
180 			bestguess = savestr(buf);
181 			filearg[0] = fetchname(buf, 0, FALSE);
182 		}
183 		if (filearg[0] == NULL) {
184 			ask("No file found--skip this patch? [n] ");
185 			if (*buf != 'y')
186 				continue;
187 			if (verbose)
188 				say("Skipping patch...\n");
189 			filearg[0] = fetchname(bestguess, 0, TRUE);
190 			skip_rest_of_patch = TRUE;
191 			return TRUE;
192 		}
193 	}
194 	return TRUE;
195 }
196 
197 /* Determine what kind of diff is in the remaining part of the patch file. */
198 
199 static int
200 intuit_diff_type(void)
201 {
202 	long	this_line = 0, previous_line;
203 	long	first_command_line = -1, fcl_line;
204 	bool	last_line_was_command = FALSE, this_is_a_command = FALSE;
205 	bool	stars_last_line = FALSE, stars_this_line = FALSE;
206 	char	*s, *t;
207 	char	*indtmp = NULL;
208 	char	*oldtmp = NULL;
209 	char	*newtmp = NULL;
210 	char	*indname = NULL;
211 	char	*oldname = NULL;
212 	char	*newname = NULL;
213 	int	indent, retval;
214 	bool	no_filearg = (filearg[0] == NULL);
215 
216 	ok_to_create_file = FALSE;
217 	fseek(pfp, p_base, SEEK_SET);
218 	p_input_line = p_bline - 1;
219 	for (;;) {
220 		previous_line = this_line;
221 		last_line_was_command = this_is_a_command;
222 		stars_last_line = stars_this_line;
223 		this_line = ftell(pfp);
224 		indent = 0;
225 		p_input_line++;
226 		if (fgets(buf, sizeof buf, pfp) == NULL) {
227 			if (first_command_line >= 0L) {
228 				/* nothing but deletes!? */
229 				p_start = first_command_line;
230 				p_sline = fcl_line;
231 				retval = ED_DIFF;
232 				goto scan_exit;
233 			} else {
234 				p_start = this_line;
235 				p_sline = p_input_line;
236 				retval = 0;
237 				goto scan_exit;
238 			}
239 		}
240 		for (s = buf; *s == ' ' || *s == '\t' || *s == 'X'; s++) {
241 			if (*s == '\t')
242 				indent += 8 - (indent % 8);
243 			else
244 				indent++;
245 		}
246 		for (t = s; isdigit(*t) || *t == ','; t++)
247 			;
248 		this_is_a_command = (isdigit(*s) &&
249 		    (*t == 'd' || *t == 'c' || *t == 'a'));
250 		if (first_command_line < 0L && this_is_a_command) {
251 			first_command_line = this_line;
252 			fcl_line = p_input_line;
253 			p_indent = indent;	/* assume this for now */
254 		}
255 		if (!stars_last_line && strnEQ(s, "*** ", 4))
256 			oldtmp = savestr(s + 4);
257 		else if (strnEQ(s, "--- ", 4))
258 			newtmp = savestr(s + 4);
259 		else if (strnEQ(s, "+++ ", 4))
260 			oldtmp = savestr(s + 4); /* pretend it is the old name */
261 		else if (strnEQ(s, "Index:", 6))
262 			indtmp = savestr(s + 6);
263 		else if (strnEQ(s, "Prereq:", 7)) {
264 			for (t = s + 7; isspace(*t); t++)
265 				;
266 			revision = savestr(t);
267 			for (t = revision; *t && !isspace(*t); t++)
268 				;
269 			*t = '\0';
270 			if (*revision == '\0') {
271 				free(revision);
272 				revision = NULL;
273 			}
274 		}
275 		if ((!diff_type || diff_type == ED_DIFF) &&
276 		    first_command_line >= 0L &&
277 		    strEQ(s, ".\n")) {
278 			p_indent = indent;
279 			p_start = first_command_line;
280 			p_sline = fcl_line;
281 			retval = ED_DIFF;
282 			goto scan_exit;
283 		}
284 		if ((!diff_type || diff_type == UNI_DIFF) && strnEQ(s, "@@ -", 4)) {
285 			if (!atol(s + 3))
286 				ok_to_create_file = TRUE;
287 			p_indent = indent;
288 			p_start = this_line;
289 			p_sline = p_input_line;
290 			retval = UNI_DIFF;
291 			goto scan_exit;
292 		}
293 		stars_this_line = strnEQ(s, "********", 8);
294 		if ((!diff_type || diff_type == CONTEXT_DIFF) && stars_last_line &&
295 		    strnEQ(s, "*** ", 4)) {
296 			if (!atol(s + 4))
297 				ok_to_create_file = TRUE;
298 			/*
299 			 * if this is a new context diff the character just
300 			 * before
301 			 */
302 			/* the newline is a '*'. */
303 			while (*s != '\n')
304 				s++;
305 			p_indent = indent;
306 			p_start = previous_line;
307 			p_sline = p_input_line - 1;
308 			retval = (*(s - 1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF);
309 			goto scan_exit;
310 		}
311 		if ((!diff_type || diff_type == NORMAL_DIFF) &&
312 		    last_line_was_command &&
313 		    (strnEQ(s, "< ", 2) || strnEQ(s, "> ", 2))) {
314 			p_start = previous_line;
315 			p_sline = p_input_line - 1;
316 			p_indent = indent;
317 			retval = NORMAL_DIFF;
318 			goto scan_exit;
319 		}
320 	}
321 scan_exit:
322 	if (no_filearg) {
323 		if (indtmp != NULL)
324 			indname = fetchname(indtmp, strippath, ok_to_create_file);
325 		if (oldtmp != NULL)
326 			oldname = fetchname(oldtmp, strippath, ok_to_create_file);
327 		if (newtmp != NULL)
328 			newname = fetchname(newtmp, strippath, ok_to_create_file);
329 		if (indname)
330 			filearg[0] = savestr(indname);
331 		else if (oldname && newname) {
332 			if (strlen(oldname) < strlen(newname))
333 				filearg[0] = savestr(oldname);
334 			else
335 				filearg[0] = savestr(newname);
336 		} else if (oldname)
337 			filearg[0] = savestr(oldname);
338 		else if (newname)
339 			filearg[0] = savestr(newname);
340 	}
341 
342 	free(bestguess);
343 	bestguess = NULL;
344 
345 	if (filearg[0] != NULL)
346 		bestguess = savestr(filearg[0]);
347 	else if (indtmp != NULL)
348 		bestguess = fetchname(indtmp, strippath, TRUE);
349 	else {
350 		if (oldtmp != NULL)
351 			oldname = fetchname(oldtmp, strippath, TRUE);
352 		if (newtmp != NULL)
353 			newname = fetchname(newtmp, strippath, TRUE);
354 		if (oldname && newname) {
355 			if (strlen(oldname) < strlen(newname))
356 				bestguess = savestr(oldname);
357 			else
358 				bestguess = savestr(newname);
359 		} else if (oldname)
360 			bestguess = savestr(oldname);
361 		else if (newname)
362 			bestguess = savestr(newname);
363 	}
364 	free(indtmp);
365 	free(oldtmp);
366 	free(newtmp);
367 	free(indname);
368 	free(oldname);
369 	free(newname);
370 	return retval;
371 }
372 
373 /*
374  * Remember where this patch ends so we know where to start up again.
375  */
376 static void
377 next_intuit_at(LINENUM file_pos, LINENUM file_line)
378 {
379 	p_base = file_pos;
380 	p_bline = file_line;
381 }
382 
383 /*
384  * Basically a verbose fseek() to the actual diff listing.
385  */
386 static void
387 skip_to(LINENUM file_pos, LINENUM file_line)
388 {
389 	char	*ret;
390 
391 	assert(p_base <= file_pos);
392 	if (verbose && p_base < file_pos) {
393 		fseek(pfp, p_base, SEEK_SET);
394 		say("The text leading up to this was:\n--------------------------\n");
395 		while (ftell(pfp) < file_pos) {
396 			ret = fgets(buf, sizeof buf, pfp);
397 			assert(ret != NULL);
398 			say("|%s", buf);
399 		}
400 		say("--------------------------\n");
401 	} else
402 		fseek(pfp, file_pos, SEEK_SET);
403 	p_input_line = file_line - 1;
404 }
405 
406 /* Make this a function for better debugging.  */
407 static void
408 malformed(void)
409 {
410 	fatal("malformed patch at line %ld: %s", p_input_line, buf);
411 	/* about as informative as "Syntax error" in C */
412 }
413 
414 /*
415  * True if the line has been discarded (i.e. it is a line saying
416  *  "\ No newline at end of file".)
417  */
418 static bool
419 remove_special_line(void)
420 {
421 	int	c;
422 
423 	c = fgetc(pfp);
424 	if (c == '\\') {
425 		do {
426 			c = fgetc(pfp);
427 		} while (c != EOF && c != '\n');
428 
429 		return TRUE;
430 	}
431 	if (c != EOF)
432 		fseek(pfp, -1L, SEEK_CUR);
433 
434 	return FALSE;
435 }
436 
437 /*
438  * True if there is more of the current diff listing to process.
439  */
440 bool
441 another_hunk(void)
442 {
443 	long	line_beginning;			/* file pos of the current line */
444 	LINENUM	repl_beginning;			/* index of --- line */
445 	LINENUM	fillcnt;			/* #lines of missing ptrn or repl */
446 	LINENUM	fillsrc;			/* index of first line to copy */
447 	LINENUM	filldst;			/* index of first missing line */
448 	bool	ptrn_spaces_eaten;		/* ptrn was slightly misformed */
449 	bool	repl_could_be_missing;		/* no + or ! lines in this hunk */
450 	bool	repl_missing;			/* we are now backtracking */
451 	long	repl_backtrack_position;	/* file pos of first repl line */
452 	LINENUM	repl_patch_line;		/* input line number for same */
453 	LINENUM	ptrn_copiable;			/* # of copiable lines in ptrn */
454 	char	*s, *ret;
455 	int	context = 0;
456 
457 	while (p_end >= 0) {
458 		if (p_end == p_efake)
459 			p_end = p_bfake;	/* don't free twice */
460 		else
461 			free(p_line[p_end]);
462 		p_end--;
463 	}
464 	assert(p_end == -1);
465 	p_efake = -1;
466 
467 	p_max = hunkmax;	/* gets reduced when --- found */
468 	if (diff_type == CONTEXT_DIFF || diff_type == NEW_CONTEXT_DIFF) {
469 		line_beginning = ftell(pfp);
470 		repl_beginning = 0;
471 		fillcnt = 0;
472 		ptrn_spaces_eaten = FALSE;
473 		repl_could_be_missing = TRUE;
474 		repl_missing = FALSE;
475 		repl_backtrack_position = 0;
476 		ptrn_copiable = 0;
477 
478 		ret = pgets(buf, sizeof buf, pfp);
479 		p_input_line++;
480 		if (ret == NULL || strnNE(buf, "********", 8)) {
481 			next_intuit_at(line_beginning, p_input_line);
482 			return FALSE;
483 		}
484 		p_context = 100;
485 		p_hunk_beg = p_input_line + 1;
486 		while (p_end < p_max) {
487 			line_beginning = ftell(pfp);
488 			ret = pgets(buf, sizeof buf, pfp);
489 			p_input_line++;
490 			if (ret == NULL) {
491 				if (p_max - p_end < 4) {
492 					/* assume blank lines got chopped */
493 					strlcpy(buf, "  \n", sizeof buf);
494 				} else {
495 					if (repl_beginning && repl_could_be_missing) {
496 						repl_missing = TRUE;
497 						goto hunk_done;
498 					}
499 					fatal("unexpected end of file in patch\n");
500 				}
501 			}
502 			p_end++;
503 			assert(p_end < hunkmax);
504 			p_char[p_end] = *buf;
505 			p_line[p_end] = NULL;
506 			switch (*buf) {
507 			case '*':
508 				if (strnEQ(buf, "********", 8)) {
509 					if (repl_beginning && repl_could_be_missing) {
510 						repl_missing = TRUE;
511 						goto hunk_done;
512 					} else
513 						fatal("unexpected end of hunk "
514 						    "at line %ld\n",
515 						    p_input_line);
516 				}
517 				if (p_end != 0) {
518 					if (repl_beginning && repl_could_be_missing) {
519 						repl_missing = TRUE;
520 						goto hunk_done;
521 					}
522 					fatal("unexpected *** at line %ld: %s",
523 					    p_input_line, buf);
524 				}
525 				context = 0;
526 				p_line[p_end] = savestr(buf);
527 				if (out_of_mem) {
528 					p_end--;
529 					return FALSE;
530 				}
531 				for (s = buf; *s && !isdigit(*s); s++)
532 					;
533 				if (!*s)
534 					malformed();
535 				if (strnEQ(s, "0,0", 3))
536 					memmove(s, s + 2, strlen(s + 2) + 1);
537 				p_first = (LINENUM) atol(s);
538 				while (isdigit(*s))
539 					s++;
540 				if (*s == ',') {
541 					for (; *s && !isdigit(*s); s++)
542 						;
543 					if (!*s)
544 						malformed();
545 					p_ptrn_lines = ((LINENUM) atol(s)) - p_first + 1;
546 				} else if (p_first)
547 					p_ptrn_lines = 1;
548 				else {
549 					p_ptrn_lines = 0;
550 					p_first = 1;
551 				}
552 
553 				/* we need this much at least */
554 				p_max = p_ptrn_lines + 6;
555 				while (p_max >= hunkmax)
556 					grow_hunkmax();
557 				p_max = hunkmax;
558 				break;
559 			case '-':
560 				if (buf[1] == '-') {
561 					if (repl_beginning ||
562 					    (p_end != p_ptrn_lines + 1 +
563 					    (p_char[p_end - 1] == '\n'))) {
564 						if (p_end == 1) {
565 							/*
566 							 * `old' lines were omitted;
567 							 * set up to fill them in
568 							 * from 'new' context lines.
569 							 */
570 							p_end = p_ptrn_lines + 1;
571 							fillsrc = p_end + 1;
572 							filldst = 1;
573 							fillcnt = p_ptrn_lines;
574 						} else {
575 							if (repl_beginning) {
576 								if (repl_could_be_missing) {
577 									repl_missing = TRUE;
578 									goto hunk_done;
579 								}
580 								fatal("duplicate \"---\" at line %ld--check line numbers at line %ld\n",
581 								    p_input_line, p_hunk_beg + repl_beginning);
582 							} else {
583 								fatal("%s \"---\" at line %ld--check line numbers at line %ld\n",
584 								    (p_end <= p_ptrn_lines
585 								    ? "Premature"
586 								    : "Overdue"),
587 								    p_input_line, p_hunk_beg);
588 							}
589 						}
590 					}
591 					repl_beginning = p_end;
592 					repl_backtrack_position = ftell(pfp);
593 					repl_patch_line = p_input_line;
594 					p_line[p_end] = savestr(buf);
595 					if (out_of_mem) {
596 						p_end--;
597 						return FALSE;
598 					}
599 					p_char[p_end] = '=';
600 					for (s = buf; *s && !isdigit(*s); s++)
601 						;
602 					if (!*s)
603 						malformed();
604 					p_newfirst = (LINENUM) atol(s);
605 					while (isdigit(*s))
606 						s++;
607 					if (*s == ',') {
608 						for (; *s && !isdigit(*s); s++)
609 							;
610 						if (!*s)
611 							malformed();
612 						p_repl_lines = ((LINENUM) atol(s)) -
613 						    p_newfirst + 1;
614 					} else if (p_newfirst)
615 						p_repl_lines = 1;
616 					else {
617 						p_repl_lines = 0;
618 						p_newfirst = 1;
619 					}
620 					p_max = p_repl_lines + p_end;
621 					if (p_max > MAXHUNKSIZE)
622 						fatal("hunk too large (%ld lines) at line %ld: %s",
623 						    p_max, p_input_line, buf);
624 					while (p_max >= hunkmax)
625 						grow_hunkmax();
626 					if (p_repl_lines != ptrn_copiable &&
627 					    (p_context != 0 || p_repl_lines != 1))
628 						repl_could_be_missing = FALSE;
629 					break;
630 				}
631 				goto change_line;
632 			case '+':
633 			case '!':
634 				repl_could_be_missing = FALSE;
635 		change_line:
636 				if (buf[1] == '\n' && canonicalize)
637 					strlcpy(buf + 1, " \n", sizeof buf - 1);
638 				if (!isspace(buf[1]) && buf[1] != '>' &&
639 				    buf[1] != '<' &&
640 				    repl_beginning && repl_could_be_missing) {
641 					repl_missing = TRUE;
642 					goto hunk_done;
643 				}
644 				if (context >= 0) {
645 					if (context < p_context)
646 						p_context = context;
647 					context = -1000;
648 				}
649 				p_line[p_end] = savestr(buf + 2);
650 				if (out_of_mem) {
651 					p_end--;
652 					return FALSE;
653 				}
654 				if (p_end == p_ptrn_lines) {
655 					if (remove_special_line()) {
656 						int	len;
657 
658 						len = strlen(p_line[p_end]) - 1;
659 						(p_line[p_end])[len] = 0;
660 					}
661 				}
662 				break;
663 			case '\t':
664 			case '\n':	/* assume the 2 spaces got eaten */
665 				if (repl_beginning && repl_could_be_missing &&
666 				    (!ptrn_spaces_eaten ||
667 				    diff_type == NEW_CONTEXT_DIFF)) {
668 					repl_missing = TRUE;
669 					goto hunk_done;
670 				}
671 				p_line[p_end] = savestr(buf);
672 				if (out_of_mem) {
673 					p_end--;
674 					return FALSE;
675 				}
676 				if (p_end != p_ptrn_lines + 1) {
677 					ptrn_spaces_eaten |= (repl_beginning != 0);
678 					context++;
679 					if (!repl_beginning)
680 						ptrn_copiable++;
681 					p_char[p_end] = ' ';
682 				}
683 				break;
684 			case ' ':
685 				if (!isspace(buf[1]) &&
686 				    repl_beginning && repl_could_be_missing) {
687 					repl_missing = TRUE;
688 					goto hunk_done;
689 				}
690 				context++;
691 				if (!repl_beginning)
692 					ptrn_copiable++;
693 				p_line[p_end] = savestr(buf + 2);
694 				if (out_of_mem) {
695 					p_end--;
696 					return FALSE;
697 				}
698 				break;
699 			default:
700 				if (repl_beginning && repl_could_be_missing) {
701 					repl_missing = TRUE;
702 					goto hunk_done;
703 				}
704 				malformed();
705 			}
706 			/* set up p_len for strncmp() so we don't have to */
707 			/* assume null termination */
708 			if (p_line[p_end])
709 				p_len[p_end] = strlen(p_line[p_end]);
710 			else
711 				p_len[p_end] = 0;
712 		}
713 
714 hunk_done:
715 		if (p_end >= 0 && !repl_beginning)
716 			fatal("no --- found in patch at line %ld\n", pch_hunk_beg());
717 
718 		if (repl_missing) {
719 
720 			/* reset state back to just after --- */
721 			p_input_line = repl_patch_line;
722 			for (p_end--; p_end > repl_beginning; p_end--)
723 				free(p_line[p_end]);
724 			fseek(pfp, repl_backtrack_position, SEEK_SET);
725 
726 			/* redundant 'new' context lines were omitted - set */
727 			/* up to fill them in from the old file context */
728 			if (!p_context && p_repl_lines == 1) {
729 				p_repl_lines = 0;
730 				p_max--;
731 			}
732 			fillsrc = 1;
733 			filldst = repl_beginning + 1;
734 			fillcnt = p_repl_lines;
735 			p_end = p_max;
736 		} else if (!p_context && fillcnt == 1) {
737 			/* the first hunk was a null hunk with no context */
738 			/* and we were expecting one line -- fix it up. */
739 			while (filldst < p_end) {
740 				p_line[filldst] = p_line[filldst + 1];
741 				p_char[filldst] = p_char[filldst + 1];
742 				p_len[filldst] = p_len[filldst + 1];
743 				filldst++;
744 			}
745 #if 0
746 			repl_beginning--;	/* this doesn't need to be fixed */
747 #endif
748 			p_end--;
749 			p_first++;	/* do append rather than insert */
750 			fillcnt = 0;
751 			p_ptrn_lines = 0;
752 		}
753 		if (diff_type == CONTEXT_DIFF &&
754 		    (fillcnt || (p_first > 1 && ptrn_copiable > 2 * p_context))) {
755 			if (verbose)
756 				say("%s\n%s\n%s\n",
757 				    "(Fascinating--this is really a new-style context diff but without",
758 				    "the telltale extra asterisks on the *** line that usually indicate",
759 				    "the new style...)");
760 			diff_type = NEW_CONTEXT_DIFF;
761 		}
762 		/* if there were omitted context lines, fill them in now */
763 		if (fillcnt) {
764 			p_bfake = filldst;	/* remember where not to free() */
765 			p_efake = filldst + fillcnt - 1;
766 			while (fillcnt-- > 0) {
767 				while (fillsrc <= p_end && p_char[fillsrc] != ' ')
768 					fillsrc++;
769 				if (fillsrc > p_end)
770 					fatal("replacement text or line numbers mangled in hunk at line %ld\n",
771 					    p_hunk_beg);
772 				p_line[filldst] = p_line[fillsrc];
773 				p_char[filldst] = p_char[fillsrc];
774 				p_len[filldst] = p_len[fillsrc];
775 				fillsrc++;
776 				filldst++;
777 			}
778 			while (fillsrc <= p_end && fillsrc != repl_beginning &&
779 			    p_char[fillsrc] != ' ')
780 				fillsrc++;
781 #ifdef DEBUGGING
782 			if (debug & 64)
783 				printf("fillsrc %ld, filldst %ld, rb %ld, e+1 %ld\n",
784 				fillsrc, filldst, repl_beginning, p_end + 1);
785 #endif
786 			assert(fillsrc == p_end + 1 || fillsrc == repl_beginning);
787 			assert(filldst == p_end + 1 || filldst == repl_beginning);
788 		}
789 		if (p_line[p_end] != NULL) {
790 			if (remove_special_line()) {
791 				p_len[p_end] -= 1;
792 				(p_line[p_end])[p_len[p_end]] = 0;
793 			}
794 		}
795 	} else if (diff_type == UNI_DIFF) {
796 		long	line_beginning = ftell(pfp); /* file pos of the current line */
797 		LINENUM	fillsrc;	/* index of old lines */
798 		LINENUM	filldst;	/* index of new lines */
799 		char	ch;
800 
801 		ret = pgets(buf, sizeof buf, pfp);
802 		p_input_line++;
803 		if (ret == NULL || strnNE(buf, "@@ -", 4)) {
804 			next_intuit_at(line_beginning, p_input_line);
805 			return FALSE;
806 		}
807 		s = buf + 4;
808 		if (!*s)
809 			malformed();
810 		p_first = (LINENUM) atol(s);
811 		while (isdigit(*s))
812 			s++;
813 		if (*s == ',') {
814 			p_ptrn_lines = (LINENUM) atol(++s);
815 			while (isdigit(*s))
816 				s++;
817 		} else
818 			p_ptrn_lines = 1;
819 		if (*s == ' ')
820 			s++;
821 		if (*s != '+' || !*++s)
822 			malformed();
823 		p_newfirst = (LINENUM) atol(s);
824 		while (isdigit(*s))
825 			s++;
826 		if (*s == ',') {
827 			p_repl_lines = (LINENUM) atol(++s);
828 			while (isdigit(*s))
829 				s++;
830 		} else
831 			p_repl_lines = 1;
832 		if (*s == ' ')
833 			s++;
834 		if (*s != '@')
835 			malformed();
836 		if (!p_ptrn_lines)
837 			p_first++;	/* do append rather than insert */
838 		p_max = p_ptrn_lines + p_repl_lines + 1;
839 		while (p_max >= hunkmax)
840 			grow_hunkmax();
841 		fillsrc = 1;
842 		filldst = fillsrc + p_ptrn_lines;
843 		p_end = filldst + p_repl_lines;
844 		snprintf(buf, sizeof buf, "*** %ld,%ld ****\n", p_first,
845 		    p_first + p_ptrn_lines - 1);
846 		p_line[0] = savestr(buf);
847 		if (out_of_mem) {
848 			p_end = -1;
849 			return FALSE;
850 		}
851 		p_char[0] = '*';
852 		snprintf(buf, sizeof buf, "--- %ld,%ld ----\n", p_newfirst,
853 		    p_newfirst + p_repl_lines - 1);
854 		p_line[filldst] = savestr(buf);
855 		if (out_of_mem) {
856 			p_end = 0;
857 			return FALSE;
858 		}
859 		p_char[filldst++] = '=';
860 		p_context = 100;
861 		context = 0;
862 		p_hunk_beg = p_input_line + 1;
863 		while (fillsrc <= p_ptrn_lines || filldst <= p_end) {
864 			line_beginning = ftell(pfp);
865 			ret = pgets(buf, sizeof buf, pfp);
866 			p_input_line++;
867 			if (ret == NULL) {
868 				if (p_max - filldst < 3) {
869 					/* assume blank lines got chopped */
870 					strlcpy(buf, " \n", sizeof buf);
871 				} else {
872 					fatal("unexpected end of file in patch\n");
873 				}
874 			}
875 			if (*buf == '\t' || *buf == '\n') {
876 				ch = ' ';	/* assume the space got eaten */
877 				s = savestr(buf);
878 			} else {
879 				ch = *buf;
880 				s = savestr(buf + 1);
881 			}
882 			if (out_of_mem) {
883 				while (--filldst > p_ptrn_lines)
884 					free(p_line[filldst]);
885 				p_end = fillsrc - 1;
886 				return FALSE;
887 			}
888 			switch (ch) {
889 			case '-':
890 				if (fillsrc > p_ptrn_lines) {
891 					free(s);
892 					p_end = filldst - 1;
893 					malformed();
894 				}
895 				p_char[fillsrc] = ch;
896 				p_line[fillsrc] = s;
897 				p_len[fillsrc++] = strlen(s);
898 				if (fillsrc > p_ptrn_lines) {
899 					if (remove_special_line()) {
900 						p_len[fillsrc - 1] -= 1;
901 						s[p_len[fillsrc - 1]] = 0;
902 					}
903 				}
904 				break;
905 			case '=':
906 				ch = ' ';
907 				/* FALL THROUGH */
908 			case ' ':
909 				if (fillsrc > p_ptrn_lines) {
910 					free(s);
911 					while (--filldst > p_ptrn_lines)
912 						free(p_line[filldst]);
913 					p_end = fillsrc - 1;
914 					malformed();
915 				}
916 				context++;
917 				p_char[fillsrc] = ch;
918 				p_line[fillsrc] = s;
919 				p_len[fillsrc++] = strlen(s);
920 				s = savestr(s);
921 				if (out_of_mem) {
922 					while (--filldst > p_ptrn_lines)
923 						free(p_line[filldst]);
924 					p_end = fillsrc - 1;
925 					return FALSE;
926 				}
927 				/* FALL THROUGH */
928 			case '+':
929 				if (filldst > p_end) {
930 					free(s);
931 					while (--filldst > p_ptrn_lines)
932 						free(p_line[filldst]);
933 					p_end = fillsrc - 1;
934 					malformed();
935 				}
936 				p_char[filldst] = ch;
937 				p_line[filldst] = s;
938 				p_len[filldst++] = strlen(s);
939 				if (fillsrc > p_ptrn_lines) {
940 					if (remove_special_line()) {
941 						p_len[filldst - 1] -= 1;
942 						s[p_len[filldst - 1]] = 0;
943 					}
944 				}
945 				break;
946 			default:
947 				p_end = filldst;
948 				malformed();
949 			}
950 			if (ch != ' ' && context > 0) {
951 				if (context < p_context)
952 					p_context = context;
953 				context = -1000;
954 			}
955 		}		/* while */
956 	} else {		/* normal diff--fake it up */
957 		char	hunk_type;
958 		int	i;
959 		LINENUM	min, max;
960 		long	line_beginning = ftell(pfp);
961 
962 		p_context = 0;
963 		ret = pgets(buf, sizeof buf, pfp);
964 		p_input_line++;
965 		if (ret == NULL || !isdigit(*buf)) {
966 			next_intuit_at(line_beginning, p_input_line);
967 			return FALSE;
968 		}
969 		p_first = (LINENUM) atol(buf);
970 		for (s = buf; isdigit(*s); s++)
971 			;
972 		if (*s == ',') {
973 			p_ptrn_lines = (LINENUM) atol(++s) - p_first + 1;
974 			while (isdigit(*s))
975 				s++;
976 		} else
977 			p_ptrn_lines = (*s != 'a');
978 		hunk_type = *s;
979 		if (hunk_type == 'a')
980 			p_first++;	/* do append rather than insert */
981 		min = (LINENUM) atol(++s);
982 		for (; isdigit(*s); s++)
983 			;
984 		if (*s == ',')
985 			max = (LINENUM) atol(++s);
986 		else
987 			max = min;
988 		if (hunk_type == 'd')
989 			min++;
990 		p_end = p_ptrn_lines + 1 + max - min + 1;
991 		if (p_end > MAXHUNKSIZE)
992 			fatal("hunk too large (%ld lines) at line %ld: %s",
993 			    p_end, p_input_line, buf);
994 		while (p_end >= hunkmax)
995 			grow_hunkmax();
996 		p_newfirst = min;
997 		p_repl_lines = max - min + 1;
998 		snprintf(buf, sizeof buf, "*** %ld,%ld\n", p_first,
999 		    p_first + p_ptrn_lines - 1);
1000 		p_line[0] = savestr(buf);
1001 		if (out_of_mem) {
1002 			p_end = -1;
1003 			return FALSE;
1004 		}
1005 		p_char[0] = '*';
1006 		for (i = 1; i <= p_ptrn_lines; i++) {
1007 			ret = pgets(buf, sizeof buf, pfp);
1008 			p_input_line++;
1009 			if (ret == NULL)
1010 				fatal("unexpected end of file in patch at line %ld\n",
1011 				    p_input_line);
1012 			if (*buf != '<')
1013 				fatal("< expected at line %ld of patch\n",
1014 				    p_input_line);
1015 			p_line[i] = savestr(buf + 2);
1016 			if (out_of_mem) {
1017 				p_end = i - 1;
1018 				return FALSE;
1019 			}
1020 			p_len[i] = strlen(p_line[i]);
1021 			p_char[i] = '-';
1022 		}
1023 
1024 		if (remove_special_line()) {
1025 			p_len[i - 1] -= 1;
1026 			(p_line[i - 1])[p_len[i - 1]] = 0;
1027 		}
1028 		if (hunk_type == 'c') {
1029 			ret = pgets(buf, sizeof buf, pfp);
1030 			p_input_line++;
1031 			if (ret == NULL)
1032 				fatal("unexpected end of file in patch at line %ld\n",
1033 				    p_input_line);
1034 			if (*buf != '-')
1035 				fatal("--- expected at line %ld of patch\n",
1036 				    p_input_line);
1037 		}
1038 		snprintf(buf, sizeof(buf), "--- %ld,%ld\n", min, max);
1039 		p_line[i] = savestr(buf);
1040 		if (out_of_mem) {
1041 			p_end = i - 1;
1042 			return FALSE;
1043 		}
1044 		p_char[i] = '=';
1045 		for (i++; i <= p_end; i++) {
1046 			ret = pgets(buf, sizeof buf, pfp);
1047 			p_input_line++;
1048 			if (ret == NULL)
1049 				fatal("unexpected end of file in patch at line %ld\n",
1050 				    p_input_line);
1051 			if (*buf != '>')
1052 				fatal("> expected at line %ld of patch\n",
1053 				    p_input_line);
1054 			p_line[i] = savestr(buf + 2);
1055 			if (out_of_mem) {
1056 				p_end = i - 1;
1057 				return FALSE;
1058 			}
1059 			p_len[i] = strlen(p_line[i]);
1060 			p_char[i] = '+';
1061 		}
1062 
1063 		if (remove_special_line()) {
1064 			p_len[i - 1] -= 1;
1065 			(p_line[i - 1])[p_len[i - 1]] = 0;
1066 		}
1067 	}
1068 	if (reverse)		/* backwards patch? */
1069 		if (!pch_swap())
1070 			say("Not enough memory to swap next hunk!\n");
1071 #ifdef DEBUGGING
1072 	if (debug & 2) {
1073 		int	i;
1074 		char	special;
1075 
1076 		for (i = 0; i <= p_end; i++) {
1077 			if (i == p_ptrn_lines)
1078 				special = '^';
1079 			else
1080 				special = ' ';
1081 			fprintf(stderr, "%3d %c %c %s", i, p_char[i],
1082 			    special, p_line[i]);
1083 			fflush(stderr);
1084 		}
1085 	}
1086 #endif
1087 	if (p_end + 1 < hunkmax)/* paranoia reigns supreme... */
1088 		p_char[p_end + 1] = '^';	/* add a stopper for apply_hunk */
1089 	return TRUE;
1090 }
1091 
1092 /*
1093  * Input a line from the patch file, worrying about indentation.
1094  */
1095 static char *
1096 pgets(char *bf, int sz, FILE *fp)
1097 {
1098 	char	*s, *ret = fgets(bf, sz, fp);
1099 	int	indent = 0;
1100 
1101 	if (p_indent && ret != NULL) {
1102 		for (s = buf;
1103 		    indent < p_indent && (*s == ' ' || *s == '\t' || *s == 'X');
1104 		    s++) {
1105 			if (*s == '\t')
1106 				indent += 8 - (indent % 7);
1107 			else
1108 				indent++;
1109 		}
1110 		if (buf != s && strlcpy(buf, s, sizeof(buf)) >= sizeof(buf))
1111 			fatal("buffer too small in pgets()\n");
1112 	}
1113 	return ret;
1114 }
1115 
1116 /*
1117  * Reverse the old and new portions of the current hunk.
1118  */
1119 bool
1120 pch_swap(void)
1121 {
1122 	char	**tp_line;	/* the text of the hunk */
1123 	short	*tp_len;	/* length of each line */
1124 	char	*tp_char;	/* +, -, and ! */
1125 	LINENUM	i;
1126 	LINENUM	n;
1127 	bool	blankline = FALSE;
1128 	char	*s;
1129 
1130 	i = p_first;
1131 	p_first = p_newfirst;
1132 	p_newfirst = i;
1133 
1134 	/* make a scratch copy */
1135 
1136 	tp_line = p_line;
1137 	tp_len = p_len;
1138 	tp_char = p_char;
1139 	p_line = NULL;	/* force set_hunkmax to allocate again */
1140 	p_len = NULL;
1141 	p_char = NULL;
1142 	set_hunkmax();
1143 	if (p_line == NULL ||p_len == NULL ||p_char == NULL) {
1144 
1145 		if (p_line == NULL) /* XXX */
1146 			free(p_line);
1147 		p_line = tp_line;
1148 		if (p_len == NULL) /* XXX */
1149 			free(p_len);
1150 		p_len = tp_len;
1151 		if (p_char == NULL) /* XXX */
1152 			free(p_char);
1153 		p_char = tp_char;
1154 		return FALSE;	/* not enough memory to swap hunk! */
1155 	}
1156 	/* now turn the new into the old */
1157 
1158 	i = p_ptrn_lines + 1;
1159 	if (tp_char[i] == '\n') {	/* account for possible blank line */
1160 		blankline = TRUE;
1161 		i++;
1162 	}
1163 	if (p_efake >= 0) {	/* fix non-freeable ptr range */
1164 		if (p_efake <= i)
1165 			n = p_end - i + 1;
1166 		else
1167 			n = -i;
1168 		p_efake += n;
1169 		p_bfake += n;
1170 	}
1171 	for (n = 0; i <= p_end; i++, n++) {
1172 		p_line[n] = tp_line[i];
1173 		p_char[n] = tp_char[i];
1174 		if (p_char[n] == '+')
1175 			p_char[n] = '-';
1176 		p_len[n] = tp_len[i];
1177 	}
1178 	if (blankline) {
1179 		i = p_ptrn_lines + 1;
1180 		p_line[n] = tp_line[i];
1181 		p_char[n] = tp_char[i];
1182 		p_len[n] = tp_len[i];
1183 		n++;
1184 	}
1185 	assert(p_char[0] == '=');
1186 	p_char[0] = '*';
1187 	for (s = p_line[0]; *s; s++)
1188 		if (*s == '-')
1189 			*s = '*';
1190 
1191 	/* now turn the old into the new */
1192 
1193 	assert(tp_char[0] == '*');
1194 	tp_char[0] = '=';
1195 	for (s = tp_line[0]; *s; s++)
1196 		if (*s == '*')
1197 			*s = '-';
1198 	for (i = 0; n <= p_end; i++, n++) {
1199 		p_line[n] = tp_line[i];
1200 		p_char[n] = tp_char[i];
1201 		if (p_char[n] == '-')
1202 			p_char[n] = '+';
1203 		p_len[n] = tp_len[i];
1204 	}
1205 	assert(i == p_ptrn_lines + 1);
1206 	i = p_ptrn_lines;
1207 	p_ptrn_lines = p_repl_lines;
1208 	p_repl_lines = i;
1209 
1210 	if (tp_line == NULL) /* XXX */
1211 		free(tp_line);
1212 	if (tp_len == NULL) /* XXX */
1213 		free(tp_len);
1214 	if (tp_char == NULL) /* XXX */
1215 		free(tp_char);
1216 	return TRUE;
1217 }
1218 
1219 /*
1220  * Return the specified line position in the old file of the old context.
1221  */
1222 LINENUM
1223 pch_first(void)
1224 {
1225 	return p_first;
1226 }
1227 
1228 /*
1229  * Return the number of lines of old context.
1230  */
1231 LINENUM
1232 pch_ptrn_lines(void)
1233 {
1234 	return p_ptrn_lines;
1235 }
1236 
1237 /*
1238  * Return the probable line position in the new file of the first line.
1239  */
1240 LINENUM
1241 pch_newfirst(void)
1242 {
1243 	return p_newfirst;
1244 }
1245 
1246 /*
1247  * Return the number of lines in the replacement text including context.
1248  */
1249 LINENUM
1250 pch_repl_lines(void)
1251 {
1252 	return p_repl_lines;
1253 }
1254 
1255 /*
1256  * Return the number of lines in the whole hunk.
1257  */
1258 LINENUM
1259 pch_end(void)
1260 {
1261 	return p_end;
1262 }
1263 
1264 /*
1265  * Return the number of context lines before the first changed line.
1266  */
1267 LINENUM
1268 pch_context(void)
1269 {
1270 	return p_context;
1271 }
1272 
1273 /*
1274  * Return the length of a particular patch line.
1275  */
1276 short
1277 pch_line_len(LINENUM line)
1278 {
1279 	return p_len[line];
1280 }
1281 
1282 /*
1283  * Return the control character (+, -, *, !, etc) for a patch line.
1284  */
1285 char
1286 pch_char(LINENUM line)
1287 {
1288 	return p_char[line];
1289 }
1290 
1291 /*
1292  * Return a pointer to a particular patch line.
1293  */
1294 char *
1295 pfetch(LINENUM line)
1296 {
1297 	return p_line[line];
1298 }
1299 
1300 /*
1301  * Return where in the patch file this hunk began, for error messages.
1302  */
1303 LINENUM
1304 pch_hunk_beg(void)
1305 {
1306 	return p_hunk_beg;
1307 }
1308 
1309 /*
1310  * Apply an ed script by feeding ed itself.
1311  */
1312 void
1313 do_ed_script(void)
1314 {
1315 	char	*t;
1316 	long	beginning_of_this_line;
1317 	FILE	*pipefp;
1318 
1319 	if (!skip_rest_of_patch) {
1320 		if (copy_file(filearg[0], TMPOUTNAME) < 0) {
1321 			unlink(TMPOUTNAME);
1322 			fatal("can't create temp file %s", TMPOUTNAME);
1323 		}
1324 		snprintf(buf, sizeof buf, "%s%s%s", _PATH_ED,
1325 		    verbose ? " " : " -s ", TMPOUTNAME);
1326 		pipefp = popen(buf, "w");
1327 	}
1328 	for (;;) {
1329 		beginning_of_this_line = ftell(pfp);
1330 		if (pgets(buf, sizeof buf, pfp) == NULL) {
1331 			next_intuit_at(beginning_of_this_line, p_input_line);
1332 			break;
1333 		}
1334 		p_input_line++;
1335 		for (t = buf; isdigit(*t) || *t == ','; t++)
1336 			;
1337 		/* POSIX defines allowed commands as {a,c,d,i,s} */
1338 		if (isdigit(*buf) && (*t == 'a' || *t == 'c' || *t == 'd' ||
1339 		    *t == 'i' || *t == 's')) {
1340 			if (!skip_rest_of_patch)
1341 				fputs(buf, pipefp);
1342 			if (*t != 'd') {
1343 				while (pgets(buf, sizeof buf, pfp) != NULL) {
1344 					p_input_line++;
1345 					if (!skip_rest_of_patch)
1346 						fputs(buf, pipefp);
1347 					if (strEQ(buf, ".\n"))
1348 						break;
1349 				}
1350 			}
1351 		} else {
1352 			next_intuit_at(beginning_of_this_line, p_input_line);
1353 			break;
1354 		}
1355 	}
1356 	if (skip_rest_of_patch)
1357 		return;
1358 	fprintf(pipefp, "w\n");
1359 	fprintf(pipefp, "q\n");
1360 	fflush(pipefp);
1361 	pclose(pipefp);
1362 	ignore_signals();
1363 	if (!check_only) {
1364 		if (move_file(TMPOUTNAME, outname) < 0) {
1365 			toutkeep = TRUE;
1366 			chmod(TMPOUTNAME, filemode);
1367 		} else
1368 			chmod(outname, filemode);
1369 	}
1370 	set_signals(1);
1371 }
1372