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