Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / gnu / usr.bin / patch / patch.c
1 /* patch - a program to apply diffs to original files
2  *
3  * $FreeBSD: src/gnu/usr.bin/patch/patch.c,v 1.16.2.4 2003/02/15 05:33:42 kris Exp $
4  * $DragonFly: src/gnu/usr.bin/patch/Attic/patch.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
5  *
6  * Copyright 1986, Larry Wall
7  *
8  * This program may be copied as long as you don't try to make any
9  * money off of it, or pretend that you wrote it.
10  *
11  * $Log: patch.c,v $
12  * Revision 2.0.2.0  90/05/01  22:17:50  davison
13  * patch12u: unidiff support added
14  *
15  * Revision 2.0.1.6  88/06/22  20:46:39  lwall
16  * patch12: rindex() wasn't declared
17  *
18  * Revision 2.0.1.5  88/06/03  15:09:37  lwall
19  * patch10: exit code improved.
20  * patch10: better support for non-flexfilenames.
21  *
22  * Revision 2.0.1.4  87/02/16  14:00:04  lwall
23  * Short replacement caused spurious "Out of sync" message.
24  *
25  * Revision 2.0.1.3  87/01/30  22:45:50  lwall
26  * Improved diagnostic on sync error.
27  * Moved do_ed_script() to pch.c.
28  *
29  * Revision 2.0.1.2  86/11/21  09:39:15  lwall
30  * Fuzz factor caused offset of installed lines.
31  *
32  * Revision 2.0.1.1  86/10/29  13:10:22  lwall
33  * Backwards search could terminate prematurely.
34  *
35  * Revision 2.0  86/09/17  15:37:32  lwall
36  * Baseline for netwide release.
37  *
38  * Revision 1.5  86/08/01  20:53:24  lwall
39  * Changed some %d's to %ld's.
40  * Linted.
41  *
42  * Revision 1.4  86/08/01  19:17:29  lwall
43  * Fixes for machines that can't vararg.
44  * Added fuzz factor.
45  * Generalized -p.
46  * General cleanup.
47  *
48  * 85/08/15 van%ucbmonet@berkeley
49  * Changes for 4.3bsd diff -c.
50  *
51  * Revision 1.3  85/03/26  15:07:43  lwall
52  * Frozen.
53  *
54  * Revision 1.2.1.9  85/03/12  17:03:35  lwall
55  * Changed pfp->_file to fileno(pfp).
56  *
57  * Revision 1.2.1.8  85/03/12  16:30:43  lwall
58  * Check i_ptr and i_womp to make sure they aren't null before freeing.
59  * Also allow ed output to be suppressed.
60  *
61  * Revision 1.2.1.7  85/03/12  15:56:13  lwall
62  * Added -p option from jromine@uci-750a.
63  *
64  * Revision 1.2.1.6  85/03/12  12:12:51  lwall
65  * Now checks for normalness of file to patch.
66  *
67  * Revision 1.2.1.5  85/03/12  11:52:12  lwall
68  * Added -D (#ifdef) option from joe@fluke.
69  *
70  * Revision 1.2.1.4  84/12/06  11:14:15  lwall
71  * Made smarter about SCCS subdirectories.
72  *
73  * Revision 1.2.1.3  84/12/05  11:18:43  lwall
74  * Added -l switch to do loose string comparison.
75  *
76  * Revision 1.2.1.2  84/12/04  09:47:13  lwall
77  * Failed hunk count not reset on multiple patch file.
78  *
79  * Revision 1.2.1.1  84/12/04  09:42:37  lwall
80  * Branch for sdcrdcf changes.
81  *
82  * Revision 1.2  84/11/29  13:29:51  lwall
83  * Linted.  Identifiers uniqified.  Fixed i_ptr malloc() bug.  Fixed
84  * multiple calls to mktemp().  Will now work on machines that can only
85  * read 32767 chars.  Added -R option for diffs with new and old swapped.
86  * Various cosmetic changes.
87  *
88  * Revision 1.1  84/11/09  17:03:58  lwall
89  * Initial revision
90  *
91  */
92
93 #include <paths.h>
94 #include "INTERN.h"
95 #include "common.h"
96 #include "EXTERN.h"
97 #include "version.h"
98 #include "util.h"
99 #include "pch.h"
100 #include "inp.h"
101 #include "backupfile.h"
102 #include "getopt.h"
103
104 /* procedures */
105
106 void    reinitialize_almost_everything(void);
107 void    get_some_switches(void);
108 LINENUM locate_hunk(LINENUM _fuzz);
109 void    abort_hunk(void);
110 void    apply_hunk(LINENUM _where);
111 void    init_output(char *_name);
112 void    init_reject(char *_name);
113 void    copy_till(LINENUM _lastline);
114 void    spew_output(void);
115 void    dump_line(LINENUM _line);
116 bool    patch_match(LINENUM _base, LINENUM _offset, LINENUM _fuzz);
117 bool    similar(char *_a, char *_b, int _len);
118 void    my_exit(int _status);
119
120
121 /* TRUE if -E was specified on command line.  */
122 static int remove_empty_files = FALSE;
123
124 /* TRUE if -R was specified on command line.  */
125 static int reverse_flag_specified = FALSE;
126
127 /* TRUE if -C was specified on command line.  */
128 int check_patch = FALSE;
129
130 /* TRUE if -I was specified on command line  */
131 /* or PATCH_INDEX_FIRST env. variable is set */
132 int index_first;
133
134 /* TRUE if -S was specified on command line. */
135 int skip_flag_specified = FALSE;
136
137 /* Apply a set of diffs as appropriate. */
138
139 int
140 main(argc,argv)
141 int argc;
142 char **argv;
143 {
144     LINENUM where;
145     LINENUM newwhere;
146     LINENUM fuzz;
147     LINENUM mymaxfuzz;
148     int hunk = 0;
149     int failed = 0;
150     int failtotal = 0;
151     bool rev_okayed = 0;
152     int i;
153
154     setbuf(stderr, serrbuf);
155     for (i = 0; i<MAXFILEC; i++)
156         filearg[i] = Nullch;
157
158     myuid = getuid();
159
160     index_first = getenv ("PATCH_INDEX_FIRST") != 0;
161
162     /* Cons up the names of the temporary files.  */
163     {
164       /* Directory for temporary files.  */
165       char *tmpdir;
166       int tmpname_len;
167
168       tmpdir = getenv ("TMPDIR");
169       if (tmpdir == NULL) {
170         tmpdir = _PATH_TMP;
171       }
172       tmpname_len = strlen (tmpdir) + 20;
173
174       TMPOUTNAME = (char *) malloc (tmpname_len);
175       strcpy (TMPOUTNAME, tmpdir);
176       strcat (TMPOUTNAME, "/patchoXXXXXX");
177       Mktemp(TMPOUTNAME);
178
179       TMPINNAME = (char *) malloc (tmpname_len);
180       strcpy (TMPINNAME, tmpdir);
181       strcat (TMPINNAME, "/patchiXXXXXX");
182       Mktemp(TMPINNAME);
183
184       TMPREJNAME = (char *) malloc (tmpname_len);
185       strcpy (TMPREJNAME, tmpdir);
186       strcat (TMPREJNAME, "/patchrXXXXXX");
187       Mktemp(TMPREJNAME);
188
189       TMPPATNAME = (char *) malloc (tmpname_len);
190       strcpy (TMPPATNAME, tmpdir);
191       strcat (TMPPATNAME, "/patchpXXXXXX");
192       Mktemp(TMPPATNAME);
193     }
194
195     {
196       char *v;
197
198       v = getenv ("SIMPLE_BACKUP_SUFFIX");
199       if (v)
200         simple_backup_suffix = v;
201       else
202         simple_backup_suffix = ".orig";
203 #ifndef NODIR
204       v = getenv ("VERSION_CONTROL");
205       backup_type = get_version (v); /* OK to pass NULL. */
206 #endif
207     }
208
209     /* parse switches */
210     Argc = argc;
211     Argv = argv;
212     get_some_switches();
213
214     /* make sure we clean up /tmp in case of disaster */
215     set_signals(0);
216
217     for (
218         open_patch_file(filearg[1]);
219         there_is_another_patch();
220         reinitialize_almost_everything()
221     ) {                                 /* for each patch in patch file */
222
223         if (outname == Nullch)
224             outname = savestr(filearg[0]);
225
226         /* for ed script just up and do it and exit */
227         if (diff_type == ED_DIFF) {
228             do_ed_script();
229             continue;
230         }
231
232         /* initialize the patched file */
233         if (!skip_rest_of_patch)
234             init_output(TMPOUTNAME);
235
236         /* initialize reject file */
237         init_reject(TMPREJNAME);
238
239         /* find out where all the lines are */
240         if (!skip_rest_of_patch)
241             scan_input(filearg[0]);
242
243         /* from here on, open no standard i/o files, because malloc */
244         /* might misfire and we can't catch it easily */
245
246         /* apply each hunk of patch */
247         hunk = 0;
248         failed = 0;
249         rev_okayed = FALSE;
250         out_of_mem = FALSE;
251         while (another_hunk()) {
252             hunk++;
253             fuzz = Nulline;
254             mymaxfuzz = pch_context();
255             if (maxfuzz < mymaxfuzz)
256                 mymaxfuzz = maxfuzz;
257             if (!skip_rest_of_patch) {
258                 do {
259                     where = locate_hunk(fuzz);
260                     if (hunk == 1 && where == Nulline && !(force|rev_okayed)) {
261                                                 /* dwim for reversed patch? */
262                         if (!pch_swap()) {
263                             if (fuzz == Nulline)
264                                 say1(
265 "Not enough memory to try swapped hunk!  Assuming unswapped.\n");
266                             continue;
267                         }
268                         reverse = !reverse;
269                         where = locate_hunk(fuzz);  /* try again */
270                         if (where == Nulline) {     /* didn't find it swapped */
271                             if (!pch_swap())         /* put it back to normal */
272                                 fatal1("lost hunk on alloc error!\n");
273                             reverse = !reverse;
274                         }
275                         else if (noreverse) {
276                             if (!pch_swap())         /* put it back to normal */
277                                 fatal1("lost hunk on alloc error!\n");
278                             reverse = !reverse;
279                             say1(
280 "Ignoring previously applied (or reversed) patch.\n");
281                             skip_rest_of_patch = TRUE;
282                         }
283                         else if (batch) {
284                             if (verbose)
285                                 say3(
286 "%seversed (or previously applied) patch detected!  %s -R.",
287                                 reverse ? "R" : "Unr",
288                                 reverse ? "Assuming" : "Ignoring");
289                         }
290                         else {
291                             (void) ask3(
292 "%seversed (or previously applied) patch detected!  %s -R? [y] ",
293                                 reverse ? "R" : "Unr",
294                                 reverse ? "Assume" : "Ignore");
295                             if (*buf == 'n') {
296                                 (void) ask1("Apply anyway? [n] ");
297                                 if (*buf == 'y')
298                                     rev_okayed = TRUE;
299                                 else
300                                     skip_rest_of_patch = TRUE;
301                                 where = Nulline;
302                                 reverse = !reverse;
303                                 if (!pch_swap())  /* put it back to normal */
304                                     fatal1("lost hunk on alloc error!\n");
305                             }
306                         }
307                     }
308                 } while (!skip_rest_of_patch && where == Nulline &&
309                     ++fuzz <= mymaxfuzz);
310
311                 if (skip_rest_of_patch) {               /* just got decided */
312                     Fclose(ofp);
313                     ofp = Nullfp;
314                 }
315             }
316
317             newwhere = pch_newfirst() + last_offset;
318             if (skip_rest_of_patch) {
319                 abort_hunk();
320                 if (! skip_flag_specified)
321                     failed++;
322                 if (verbose)
323                     say3("Hunk #%d ignored at %ld.\n", hunk, newwhere);
324             }
325             else if (where == Nulline) {
326                 abort_hunk();
327                 failed++;
328                 if (verbose)
329                     say3("Hunk #%d failed at %ld.\n", hunk, newwhere);
330             }
331             else {
332                 apply_hunk(where);
333                 if (verbose) {
334                     say3("Hunk #%d succeeded at %ld", hunk, newwhere);
335                     if (fuzz)
336                         say2(" with fuzz %ld", fuzz);
337                     if (last_offset)
338                         say3(" (offset %ld line%s)",
339                             last_offset, last_offset==1L?"":"s");
340                     say1(".\n");
341                 }
342             }
343         }
344
345         if (out_of_mem && using_plan_a) {
346             optind = optind_last;
347             say1("\n\nRan out of memory using Plan A--trying again...\n\n");
348             if (ofp)
349                 Fclose(ofp);
350             ofp = Nullfp;
351             if (rejfp)
352                 Fclose(rejfp);
353             rejfp = Nullfp;
354             continue;
355         }
356
357         assert(hunk);
358
359         /* finish spewing out the new file */
360         if (!skip_rest_of_patch)
361             spew_output();
362
363         /* and put the output where desired */
364         ignore_signals();
365         if (!skip_rest_of_patch) {
366             struct stat statbuf;
367             char *realout = outname;
368
369             if (check_patch) {
370                 ;
371             } else if (move_file(TMPOUTNAME, outname) < 0) {
372                 toutkeep = TRUE;
373                 realout = TMPOUTNAME;
374                 chmod(TMPOUTNAME, filemode);
375             }
376             else
377                 chmod(outname, filemode);
378
379             if (remove_empty_files && stat(realout, &statbuf) == 0
380                 && statbuf.st_size == 0) {
381                 if (verbose)
382                     say2("Removing %s (empty after patching).\n", realout);
383                 while (unlink(realout) >= 0) ; /* while is for Eunice.  */
384             }
385         }
386         Fclose(rejfp);
387         rejfp = Nullfp;
388         if (failed) {
389             failtotal += failed;
390             if (!*rejname) {
391                 Strlcpy(rejname, outname, sizeof(rejname));
392                 addext(rejname, ".rej", '#');
393             }
394             if (skip_rest_of_patch) {
395                 say4("%d out of %d hunks ignored--saving rejects to %s\n",
396                     failed, hunk, rejname);
397             }
398             else {
399                 say4("%d out of %d hunks failed--saving rejects to %s\n",
400                     failed, hunk, rejname);
401             }
402             if (check_patch) {
403                 ;
404             } else if (move_file(TMPREJNAME, rejname) < 0)
405                 trejkeep = TRUE;
406         }
407         set_signals(1);
408     }
409     my_exit(failtotal);
410     return (failtotal);
411 }
412
413 /* Prepare to find the next patch to do in the patch file. */
414
415 void
416 reinitialize_almost_everything(void)
417 {
418     re_patch();
419     re_input();
420
421     input_lines = 0;
422     last_frozen_line = 0;
423
424     filec = 0;
425     if (filearg[0] != Nullch && !out_of_mem) {
426         free(filearg[0]);
427         filearg[0] = Nullch;
428     }
429
430     if (outname != Nullch) {
431         free(outname);
432         outname = Nullch;
433     }
434
435     last_offset = 0;
436
437     diff_type = 0;
438
439     if (revision != Nullch) {
440         free(revision);
441         revision = Nullch;
442     }
443
444     reverse = reverse_flag_specified;
445     skip_rest_of_patch = FALSE;
446     skip_flag_specified = FALSE;
447
448     get_some_switches();
449
450     if (filec >= 2)
451         fatal1("you may not change to a different patch file\n");
452 }
453
454 static char *shortopts = "-b:B:cCd:D:eEfF:i:IlnNo:p::r:RsStuvV:x:";
455 static struct option longopts[] =
456 {
457   {"suffix", 1, NULL, 'b'},
458   {"prefix", 1, NULL, 'B'},
459   {"check", 0, NULL, 'C'},
460   {"context", 0, NULL, 'c'},
461   {"directory", 1, NULL, 'd'},
462   {"ifdef", 1, NULL, 'D'},
463   {"ed", 0, NULL, 'e'},
464   {"remove-empty-files", 0, NULL, 'E'},
465   {"force", 0, NULL, 'f'},
466   {"fuzz", 1, NULL, 'F'},
467   {"index-first", 0, NULL, 'I'},
468   {"ignore-whitespace", 0, NULL, 'l'},
469   {"normal", 0, NULL, 'n'},
470   {"forward", 0, NULL, 'N'},
471   {"output", 1, NULL, 'o'},
472   {"strip", 2, NULL, 'p'},
473   {"reject-file", 1, NULL, 'r'},
474   {"reverse", 0, NULL, 'R'},
475   {"quiet", 0, NULL, 's'},
476   {"silent", 0, NULL, 's'},
477   {"skip", 0, NULL, 'S'},
478   {"batch", 0, NULL, 't'},
479   {"unified", 0, NULL, 'u'},
480   {"version", 0, NULL, 'v'},
481   {"version-control", 1, NULL, 'V'},
482   {"debug", 1, NULL, 'x'},
483   {0, 0, 0, 0}
484 };
485
486 /* Process switches and filenames up to next '+' or end of list. */
487
488 void
489 get_some_switches(void)
490 {
491     Reg1 int optc;
492
493     rejname[0] = '\0';
494     optind_last = optind;
495     if (optind == Argc)
496         return;
497     while ((optc = getopt_long (Argc, Argv, shortopts, longopts, (int *) 0))
498            != -1) {
499         if (optc == 1) {
500             if (strEQ(optarg, "+"))
501                 return;
502             if (filec == MAXFILEC)
503                 fatal1("too many file arguments\n");
504             filearg[filec++] = savestr(optarg);
505         }
506         else {
507             switch (optc) {
508             case 'b':
509                 simple_backup_suffix = savestr(optarg);
510                 break;
511             case 'B':
512                 origprae = savestr(optarg);
513                 break;
514             case 'c':
515                 diff_type = CONTEXT_DIFF;
516                 break;
517             case 'C':
518                 check_patch = TRUE;
519                 break;
520             case 'd':
521                 if (chdir(optarg) < 0)
522                     pfatal2("can't cd to %s", optarg);
523                 break;
524             case 'D':
525                 do_defines = TRUE;
526                 if (!isalpha((unsigned char)*optarg) && '_' != *optarg)
527                     fatal1("argument to -D is not an identifier\n");
528                 Snprintf(if_defined, sizeof(if_defined), "#ifdef %s\n", optarg);
529                 Snprintf(not_defined, sizeof(not_defined), "#ifndef %s\n", optarg);
530                 Snprintf(end_defined, sizeof(end_defined), "#endif /* %s */\n", optarg);
531                 break;
532             case 'e':
533                 diff_type = ED_DIFF;
534                 break;
535             case 'E':
536                 remove_empty_files = TRUE;
537                 break;
538             case 'f':
539                 force = TRUE;
540                 break;
541             case 'F':
542                 maxfuzz = atoi(optarg);
543                 break;
544             case 'i':
545                 filearg[1] = savestr(optarg);
546                 break;
547             case 'I':
548                 index_first = TRUE;
549                 break;
550             case 'l':
551                 canonicalize = TRUE;
552                 break;
553             case 'n':
554                 diff_type = NORMAL_DIFF;
555                 break;
556             case 'N':
557                 noreverse = TRUE;
558                 break;
559             case 'o':
560                 outname = savestr(optarg);
561                 break;
562             case 'p':
563                 if (optarg)
564                     strippath = atoi(optarg);
565                 else
566                     strippath = 0;
567                 break;
568             case 'r':
569                 Strlcpy(rejname, optarg, sizeof(rejname));
570                 break;
571             case 'R':
572                 reverse = TRUE;
573                 reverse_flag_specified = TRUE;
574                 break;
575             case 's':
576                 verbose = FALSE;
577                 break;
578             case 'S':
579                 skip_rest_of_patch = TRUE;
580                 skip_flag_specified = TRUE;
581                 break;
582             case 't':
583                 batch = TRUE;
584                 break;
585             case 'u':
586                 diff_type = UNI_DIFF;
587                 break;
588             case 'v':
589                 version();
590                 break;
591             case 'V':
592 #ifndef NODIR
593                 backup_type = get_version (optarg);
594 #endif
595                 break;
596 #ifdef DEBUGGING
597             case 'x':
598                 debug = atoi(optarg);
599                 break;
600 #endif
601             default:
602                 fprintf(stderr, "\
603 Usage: %s [options] [origfile [patchfile]] [+ [options] [origfile]]...\n",
604                         Argv[0]);
605                 fprintf(stderr, "\
606 Options:\n\
607        [-cCeEflnNRsStuv] [-b backup-ext] [-B backup-prefix] [-d directory]\n\
608        [-D symbol] [-F max-fuzz] [-i patchfile] [-o out-file] [-p[strip-count]]\n\
609        [-r rej-name] [-V {numbered,existing,simple}] [--check] [--context]\n\
610        [--prefix=backup-prefix] [--suffix=backup-ext] [--ifdef=symbol]\n\
611        [--directory=directory] [--ed] [--fuzz=max-fuzz] [--force] [--batch]\n\
612        [--ignore-whitespace] [--forward] [--reverse] [--output=out-file]\n");
613                 fprintf(stderr, "\
614        [--strip[=strip-count]] [--normal] [--reject-file=rej-name] [--skip]\n\
615        [--remove-empty-files] [--quiet] [--silent] [--unified] [--version]\n\
616        [--version-control={numbered,existing,simple}] [--index-first]\n");
617                 my_exit(1);
618             }
619         }
620     }
621
622     /* Process any filename args given after "--".  */
623     for (; optind < Argc; ++optind) {
624         if (filec == MAXFILEC)
625             fatal1("too many file arguments\n");
626         filearg[filec++] = savestr(Argv[optind]);
627     }
628 }
629
630 /*
631  * Attempt to find the right place to apply this hunk of patch.
632  */
633 LINENUM
634 locate_hunk(LINENUM fuzz)
635 {
636     Reg1 LINENUM first_guess = pch_first() + last_offset;
637     Reg2 LINENUM offset;
638     LINENUM pat_lines = pch_ptrn_lines();
639     Reg3 LINENUM max_pos_offset = input_lines - first_guess
640                                 - pat_lines + 1;
641     Reg4 LINENUM max_neg_offset = first_guess - last_frozen_line - 1
642                                 + pch_context();
643
644     if (!pat_lines)                     /* null range matches always */
645         return first_guess;
646     if (max_neg_offset >= first_guess)  /* do not try lines < 0 */
647         max_neg_offset = first_guess - 1;
648     if (first_guess <= input_lines && patch_match(first_guess, Nulline, fuzz))
649         return first_guess;
650     for (offset = 1; ; offset++) {
651         Reg5 bool check_after = (offset <= max_pos_offset);
652         Reg6 bool check_before = (offset <= max_neg_offset);
653
654         if (check_after && patch_match(first_guess, offset, fuzz)) {
655 #ifdef DEBUGGING
656             if (debug & 1)
657                 say3("Offset changing from %ld to %ld\n", last_offset, offset);
658 #endif
659             last_offset = offset;
660             return first_guess+offset;
661         }
662         else if (check_before && patch_match(first_guess, -offset, fuzz)) {
663 #ifdef DEBUGGING
664             if (debug & 1)
665                 say3("Offset changing from %ld to %ld\n", last_offset, -offset);
666 #endif
667             last_offset = -offset;
668             return first_guess-offset;
669         }
670         else if (!check_before && !check_after)
671             return Nulline;
672     }
673 }
674
675 /* We did not find the pattern, dump out the hunk so they can handle it. */
676
677 void
678 abort_hunk(void)
679 {
680     Reg1 LINENUM i;
681     Reg2 LINENUM pat_end = pch_end();
682     /* add in last_offset to guess the same as the previous successful hunk */
683     LINENUM oldfirst = pch_first() + last_offset;
684     LINENUM newfirst = pch_newfirst() + last_offset;
685     LINENUM oldlast = oldfirst + pch_ptrn_lines() - 1;
686     LINENUM newlast = newfirst + pch_repl_lines() - 1;
687     char *stars = (diff_type >= NEW_CONTEXT_DIFF ? " ****" : "");
688     char *minuses = (diff_type >= NEW_CONTEXT_DIFF ? " ----" : " -----");
689
690     fprintf(rejfp, "***************\n");
691     for (i=0; i<=pat_end; i++) {
692         switch (pch_char(i)) {
693         case '*':
694             if (oldlast < oldfirst)
695                 fprintf(rejfp, "*** 0%s\n", stars);
696             else if (oldlast == oldfirst)
697                 fprintf(rejfp, "*** %ld%s\n", oldfirst, stars);
698             else
699                 fprintf(rejfp, "*** %ld,%ld%s\n", oldfirst, oldlast, stars);
700             break;
701         case '=':
702             if (newlast < newfirst)
703                 fprintf(rejfp, "--- 0%s\n", minuses);
704             else if (newlast == newfirst)
705                 fprintf(rejfp, "--- %ld%s\n", newfirst, minuses);
706             else
707                 fprintf(rejfp, "--- %ld,%ld%s\n", newfirst, newlast, minuses);
708             break;
709         case '\n':
710             fprintf(rejfp, "%s", pfetch(i));
711             break;
712         case ' ': case '-': case '+': case '!':
713             fprintf(rejfp, "%c %s", pch_char(i), pfetch(i));
714             break;
715         default:
716             fatal1("fatal internal error in abort_hunk\n");
717         }
718     }
719 }
720
721 /*
722  * We found where to apply it (we hope), so do it.
723  */
724 void
725 apply_hunk(LINENUM where)
726 {
727     Reg1 LINENUM old = 1;
728     Reg2 LINENUM lastline = pch_ptrn_lines();
729     Reg3 LINENUM new = lastline+1;
730 #define OUTSIDE 0
731 #define IN_IFNDEF 1
732 #define IN_IFDEF 2
733 #define IN_ELSE 3
734     Reg4 int def_state = OUTSIDE;
735     Reg5 bool R_do_defines = do_defines;
736     Reg6 LINENUM pat_end = pch_end();
737
738     where--;
739     while (pch_char(new) == '=' || pch_char(new) == '\n')
740         new++;
741
742     while (old <= lastline) {
743         if (pch_char(old) == '-') {
744             copy_till(where + old - 1);
745             if (R_do_defines) {
746                 if (def_state == OUTSIDE) {
747                     fputs(not_defined, ofp);
748                     def_state = IN_IFNDEF;
749                 }
750                 else if (def_state == IN_IFDEF) {
751                     fputs(else_defined, ofp);
752                     def_state = IN_ELSE;
753                 }
754                 fputs(pfetch(old), ofp);
755             }
756             last_frozen_line++;
757             old++;
758         }
759         else if (new > pat_end) {
760             break;
761         }
762         else if (pch_char(new) == '+') {
763             copy_till(where + old - 1);
764             if (R_do_defines) {
765                 if (def_state == IN_IFNDEF) {
766                     fputs(else_defined, ofp);
767                     def_state = IN_ELSE;
768                 }
769                 else if (def_state == OUTSIDE) {
770                     fputs(if_defined, ofp);
771                     def_state = IN_IFDEF;
772                 }
773             }
774             fputs(pfetch(new), ofp);
775             new++;
776         }
777         else if (pch_char(new) != pch_char(old)) {
778             say3("Out-of-sync patch, lines %ld,%ld--mangled text or line numbers, maybe?\n",
779                 pch_hunk_beg() + old,
780                 pch_hunk_beg() + new);
781 #ifdef DEBUGGING
782             say3("oldchar = '%c', newchar = '%c'\n",
783                 pch_char(old), pch_char(new));
784 #endif
785             my_exit(1);
786         }
787         else if (pch_char(new) == '!') {
788             copy_till(where + old - 1);
789             if (R_do_defines) {
790                fputs(not_defined, ofp);
791                def_state = IN_IFNDEF;
792             }
793             while (pch_char(old) == '!') {
794                 if (R_do_defines) {
795                     fputs(pfetch(old), ofp);
796                 }
797                 last_frozen_line++;
798                 old++;
799             }
800             if (R_do_defines) {
801                 fputs(else_defined, ofp);
802                 def_state = IN_ELSE;
803             }
804             while (pch_char(new) == '!') {
805                 fputs(pfetch(new), ofp);
806                 new++;
807             }
808         }
809         else {
810             assert(pch_char(new) == ' ');
811             old++;
812             new++;
813             if (R_do_defines && def_state != OUTSIDE) {
814                 fputs(end_defined, ofp);
815                 def_state = OUTSIDE;
816             }
817         }
818     }
819     if (new <= pat_end && pch_char(new) == '+') {
820         copy_till(where + old - 1);
821         if (R_do_defines) {
822             if (def_state == OUTSIDE) {
823                 fputs(if_defined, ofp);
824                 def_state = IN_IFDEF;
825             }
826             else if (def_state == IN_IFNDEF) {
827                 fputs(else_defined, ofp);
828                 def_state = IN_ELSE;
829             }
830         }
831         while (new <= pat_end && pch_char(new) == '+') {
832             fputs(pfetch(new), ofp);
833             new++;
834         }
835     }
836     if (R_do_defines && def_state != OUTSIDE) {
837         fputs(end_defined, ofp);
838     }
839 }
840
841 /* Open the new file. */
842
843 void
844 init_output(char *name)
845 {
846     ofp = fopen(name, "w");
847     if (ofp == Nullfp)
848         pfatal2("can't create %s", name);
849 }
850
851 /* Open a file to put hunks we can't locate. */
852
853 void
854 init_reject(char *name)
855 {
856     rejfp = fopen(name, "w");
857     if (rejfp == Nullfp)
858         pfatal2("can't create %s", name);
859 }
860
861 /* Copy input file to output, up to wherever hunk is to be applied. */
862
863 void
864 copy_till(LINENUM lastline)
865 {
866     Reg2 LINENUM R_last_frozen_line = last_frozen_line;
867
868     if (R_last_frozen_line > lastline)
869         fatal1("misordered hunks! output would be garbled\n");
870     while (R_last_frozen_line < lastline) {
871         dump_line(++R_last_frozen_line);
872     }
873     last_frozen_line = R_last_frozen_line;
874 }
875
876 /* Finish copying the input file to the output file. */
877
878 void
879 spew_output(void)
880 {
881 #ifdef DEBUGGING
882     if (debug & 256)
883         say3("il=%ld lfl=%ld\n",input_lines,last_frozen_line);
884 #endif
885     if (input_lines)
886         copy_till(input_lines);         /* dump remainder of file */
887     Fclose(ofp);
888     ofp = Nullfp;
889 }
890
891 /* Copy one line from input to output. */
892
893 void
894 dump_line(LINENUM line)
895 {
896     Reg1 char *s;
897     Reg2 char R_newline = '\n';
898
899     /* Note: string is not null terminated. */
900     for (s=ifetch(line, 0); putc(*s, ofp) != R_newline; s++) ;
901 }
902
903 /* Does the patch pattern match at line base+offset? */
904
905 bool
906 patch_match(LINENUM base, LINENUM offset, LINENUM fuzz)
907 {
908     Reg1 LINENUM pline = 1 + fuzz;
909     Reg2 LINENUM iline;
910     Reg3 LINENUM pat_lines = pch_ptrn_lines() - fuzz;
911
912     for (iline=base+offset+fuzz; pline <= pat_lines; pline++,iline++) {
913         if (canonicalize) {
914             if (!similar(ifetch(iline, (offset >= 0)),
915                          pfetch(pline),
916                          pch_line_len(pline) ))
917                 return FALSE;
918         }
919         else if (strnNE(ifetch(iline, (offset >= 0)),
920                    pfetch(pline),
921                    pch_line_len(pline) ))
922             return FALSE;
923     }
924     return TRUE;
925 }
926
927 /* Do two lines match with canonicalized white space? */
928
929 bool
930 similar(char *a, char *b, int len)
931 {
932     while (len) {
933         if (isspace((unsigned char)*b)) {              /* whitespace (or \n) to match? */
934             if (!isspace((unsigned char)*a))           /* no corresponding whitespace? */
935                 return FALSE;
936             while (len && isspace((unsigned char)*b) && *b != '\n')
937                 b++,len--;              /* skip pattern whitespace */
938             while (isspace((unsigned char)*a) && *a != '\n')
939                 a++;                    /* skip target whitespace */
940             if (*a == '\n' || *b == '\n')
941                 return (*a == *b);      /* should end in sync */
942         }
943         else if (*a++ != *b++)          /* match non-whitespace chars */
944             return FALSE;
945         else
946             len--;                      /* probably not necessary */
947     }
948     return TRUE;                        /* actually, this is not reached */
949                                         /* since there is always a \n */
950 }
951
952 /* Exit with cleanup. */
953
954 void
955 my_exit(int status)
956 {
957     Unlink(TMPINNAME);
958     if (!toutkeep) {
959         Unlink(TMPOUTNAME);
960     }
961     if (!trejkeep) {
962         Unlink(TMPREJNAME);
963     }
964     Unlink(TMPPATNAME);
965     exit(status);
966 }