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