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