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