Initial import from FreeBSD RELENG_4:
[games.git] / gnu / usr.bin / patch / pch.c
1 /* $FreeBSD: src/gnu/usr.bin/patch/pch.c,v 1.16.2.3 2002/04/30 20:40:02 gad Exp $
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
43 static long p_filesize;                 /* size of the patch file */
44 static LINENUM p_first;                 /* 1st line number */
45 static LINENUM p_newfirst;              /* 1st line number of replacement */
46 static LINENUM p_ptrn_lines;            /* # lines in pattern */
47 static LINENUM p_repl_lines;            /* # lines in replacement text */
48 static LINENUM p_end = -1;              /* last line in hunk */
49 static LINENUM p_max;                   /* max allowed value of p_end */
50 static LINENUM p_context = 3;           /* # of context lines */
51 static LINENUM p_input_line = 0;        /* current line # from patch file */
52 static char **p_line = Null(char**);    /* the text of the hunk */
53 static short *p_len = Null(short*);     /* length of each line */
54 static char *p_Char = Nullch;           /* +, -, and ! */
55 static int hunkmax = INITHUNKMAX;       /* size of above arrays to begin with */
56 static int p_indent;                    /* indent to patch */
57 static LINENUM p_base;                  /* where to intuit this time */
58 static LINENUM p_bline;                 /* line # of p_base */
59 static LINENUM p_start;                 /* where intuit found a patch */
60 static LINENUM p_sline;                 /* and the line number for it */
61 static LINENUM p_hunk_beg;              /* line number of current hunk */
62 static LINENUM p_efake = -1;            /* end of faked up lines--don't free */
63 static LINENUM p_bfake = -1;            /* beg of faked up lines */
64
65 /*
66  * Prepare to look for the next patch in the patch file.
67  */
68 void
69 re_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
82 void
83 open_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
105 void
106 set_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
120 void
121 grow_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  */
146 bool
147 there_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  */
210 int
211 intuit_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  */
398 void
399 next_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  */
408 void
409 skip_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.  */
430 static void
431 malformed(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  */
441 static bool
442 remove_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
463 bool
464 another_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  */
1101 char *
1102 pgets(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  */
1126 bool
1127 pch_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  */
1232 LINENUM
1233 pch_first(void)
1234 {
1235     return p_first;
1236 }
1237
1238 /*
1239  * Return the number of lines of old context.
1240  */
1241 LINENUM
1242 pch_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  */
1250 LINENUM
1251 pch_newfirst(void)
1252 {
1253     return p_newfirst;
1254 }
1255
1256 /*
1257  * Return the number of lines in the replacement text including context.
1258  */
1259 LINENUM
1260 pch_repl_lines(void)
1261 {
1262     return p_repl_lines;
1263 }
1264
1265 /*
1266  * Return the number of lines in the whole hunk.
1267  */
1268 LINENUM
1269 pch_end(void)
1270 {
1271     return p_end;
1272 }
1273
1274 /*
1275  * Return the number of context lines before the first changed line.
1276  */
1277 LINENUM
1278 pch_context(void)
1279 {
1280     return p_context;
1281 }
1282
1283 /*
1284  * Return the length of a particular patch line.
1285  */
1286 short
1287 pch_line_len(LINENUM line)
1288 {
1289     return p_len[line];
1290 }
1291
1292 /*
1293  * Return the control character (+, -, *, !, etc) for a patch line.
1294  */
1295 char
1296 pch_char(LINENUM line)
1297 {
1298     return p_Char[line];
1299 }
1300
1301 /*
1302  * Return a pointer to a particular patch line.
1303  */
1304 char *
1305 pfetch(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  */
1313 LINENUM
1314 pch_hunk_beg(void)
1315 {
1316     return p_hunk_beg;
1317 }
1318
1319 /*
1320  * Apply an ed script by feeding ed itself.
1321  */
1322 void
1323 do_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 }