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