Make patch's -b option match the old patch's -b option, at least for now,
[dragonfly.git] / usr.bin / patch / patch.c
1 /*
2  * $OpenBSD: patch.c,v 1.41 2004/07/09 19:13:46 otto Exp $
3  * $DragonFly: src/usr.bin/patch/patch.c,v 1.2 2004/10/02 05:52:06 dillon Exp $
4  */
5
6 /*
7  * patch - a program to apply diffs to original files
8  * 
9  * Copyright 1986, Larry Wall
10  * 
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following condition is met:
13  * 1. Redistributions of source code must retain the above copyright notice,
14  * this condition and the following disclaimer.
15  * 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  * 
28  * -C option added in 1998, original code by Marc Espie, based on FreeBSD
29  * behaviour
30  */
31
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35
36 #include <ctype.h>
37 #include <getopt.h>
38 #include <limits.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <stdlib.h>
42
43 #include "common.h"
44 #include "util.h"
45 #include "pch.h"
46 #include "inp.h"
47 #include "backupfile.h"
48 #include "pathnames.h"
49
50 int             filemode = 0644;
51
52 char            buf[MAXLINELEN];        /* general purpose buffer */
53
54 bool            using_plan_a = true;    /* try to keep everything in memory */
55 bool            out_of_mem = false;     /* ran out of memory in plan a */
56
57 #define MAXFILEC 2
58
59 char            *filearg[MAXFILEC];
60 bool            ok_to_create_file = false;
61 char            *outname = NULL;
62 char            *origprae = NULL;
63 char            *TMPOUTNAME;
64 char            *TMPINNAME;
65 char            *TMPREJNAME;
66 char            *TMPPATNAME;
67 bool            toutkeep = false;
68 bool            trejkeep = false;
69 bool            warn_on_invalid_line;
70 bool            last_line_missing_eol;
71
72 #ifdef DEBUGGING
73 int             debug = 0;
74 #endif
75
76 bool            force = false;
77 bool            batch = false;
78 bool            verbose = true;
79 bool            reverse = false;
80 bool            noreverse = false;
81 bool            skip_rest_of_patch = false;
82 int             strippath = 957;
83 bool            canonicalize = false;
84 bool            check_only = false;
85 int             diff_type = 0;
86 char            *revision = NULL;       /* prerequisite revision, if any */
87 LINENUM         input_lines = 0;        /* how long is input file in lines */
88 int             posix = 0;              /* strict POSIX mode? */
89
90 static void     reinitialize_almost_everything(void);
91 static void     get_some_switches(void);
92 static LINENUM  locate_hunk(LINENUM);
93 static void     abort_hunk(void);
94 static void     apply_hunk(LINENUM);
95 static void     init_output(const char *);
96 static void     init_reject(const char *);
97 static void     copy_till(LINENUM, bool);
98 static void     spew_output(void);
99 static void     dump_line(LINENUM, bool);
100 static bool     patch_match(LINENUM, LINENUM, LINENUM);
101 static bool     similar(const char *, const char *, int);
102 static void     usage(void);
103
104 /* true if -E was specified on command line.  */
105 static bool     remove_empty_files = false;
106
107 /* true if -R was specified on command line.  */
108 static bool     reverse_flag_specified = false;
109
110 /* buffer holding the name of the rejected patch file. */
111 static char     rejname[NAME_MAX + 1];
112
113 /* buffer for stderr */
114 static char     serrbuf[BUFSIZ];
115
116 /* how many input lines have been irretractibly output */
117 static LINENUM  last_frozen_line = 0;
118
119 static int      Argc;           /* guess */
120 static char     **Argv;
121 static int      Argc_last;      /* for restarting plan_b */
122 static char     **Argv_last;
123
124 static FILE     *ofp = NULL;    /* output file pointer */
125 static FILE     *rejfp = NULL;  /* reject file pointer */
126
127 static int      filec = 0;      /* how many file arguments? */
128 static LINENUM  last_offset = 0;
129 static LINENUM  maxfuzz = 2;
130
131 /* patch using ifdef, ifndef, etc. */
132 static bool             do_defines = false;
133 /* #ifdef xyzzy */
134 static char             if_defined[128];
135 /* #ifndef xyzzy */
136 static char             not_defined[128];
137 /* #else */
138 static const char       else_defined[] = "#else\n";
139 /* #endif xyzzy */
140 static char             end_defined[128];
141
142
143 /* Apply a set of diffs as appropriate. */
144
145 int
146 main(int argc, char *argv[])
147 {
148         int     error = 0, hunk, failed, patch_seen = 0, i, fd;
149         LINENUM where = 0, newwhere, fuzz, mymaxfuzz;
150         const   char *tmpdir;
151         char    *v;
152
153         setbuf(stderr, serrbuf);
154         for (i = 0; i < MAXFILEC; i++)
155                 filearg[i] = NULL;
156
157         /* Cons up the names of the temporary files.  */
158         if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
159                 tmpdir = _PATH_TMP;
160         for (i = strlen(tmpdir) - 1; i > 0 && tmpdir[i] == '/'; i--)
161                 ;
162         i++;
163         if (asprintf(&TMPOUTNAME, "%.*s/patchoXXXXXXXXXX", i, tmpdir) == -1)
164                 fatal("cannot allocate memory");
165         if ((fd = mkstemp(TMPOUTNAME)) < 0)
166                 pfatal("can't create %s", TMPOUTNAME);
167         close(fd);
168
169         if (asprintf(&TMPINNAME, "%.*s/patchiXXXXXXXXXX", i, tmpdir) == -1)
170                 fatal("cannot allocate memory");
171         if ((fd = mkstemp(TMPINNAME)) < 0)
172                 pfatal("can't create %s", TMPINNAME);
173         close(fd);
174
175         if (asprintf(&TMPREJNAME, "%.*s/patchrXXXXXXXXXX", i, tmpdir) == -1)
176                 fatal("cannot allocate memory");
177         if ((fd = mkstemp(TMPREJNAME)) < 0)
178                 pfatal("can't create %s", TMPREJNAME);
179         close(fd);
180
181         if (asprintf(&TMPPATNAME, "%.*s/patchpXXXXXXXXXX", i, tmpdir) == -1)
182                 fatal("cannot allocate memory");
183         if ((fd = mkstemp(TMPPATNAME)) < 0)
184                 pfatal("can't create %s", TMPPATNAME);
185         close(fd);
186
187         v = getenv("SIMPLE_BACKUP_SUFFIX");
188         if (v)
189                 simple_backup_suffix = v;
190         else
191                 simple_backup_suffix = ORIGEXT;
192
193         /* parse switches */
194         Argc = argc;
195         Argv = argv;
196         get_some_switches();
197
198         if (backup_type == none) {
199                 if ((v = getenv("PATCH_VERSION_CONTROL")) == NULL)
200                         v = getenv("VERSION_CONTROL");
201                 if (v != NULL || !posix)
202                         backup_type = get_version(v);   /* OK to pass NULL. */
203         }
204
205         /* make sure we clean up /tmp in case of disaster */
206         set_signals(0);
207
208         for (open_patch_file(filearg[1]); there_is_another_patch();
209             reinitialize_almost_everything()) {
210                 /* for each patch in patch file */
211
212                 patch_seen = true;
213                 warn_on_invalid_line = true;
214
215                 if (outname == NULL)
216                         outname = savestr(filearg[0]);
217
218                 /* for ed script just up and do it and exit */
219                 if (diff_type == ED_DIFF) {
220                         do_ed_script();
221                         continue;
222                 }
223                 /* initialize the patched file */
224                 if (!skip_rest_of_patch)
225                         init_output(TMPOUTNAME);
226
227                 /* initialize reject file */
228                 init_reject(TMPREJNAME);
229
230                 /* find out where all the lines are */
231                 if (!skip_rest_of_patch)
232                         scan_input(filearg[0]);
233
234                 /* from here on, open no standard i/o files, because malloc */
235                 /* might misfire and we can't catch it easily */
236
237                 /* apply each hunk of patch */
238                 hunk = 0;
239                 failed = 0;
240                 out_of_mem = false;
241                 while (another_hunk()) {
242                         hunk++;
243                         fuzz = 0;
244                         mymaxfuzz = pch_context();
245                         if (maxfuzz < mymaxfuzz)
246                                 mymaxfuzz = maxfuzz;
247                         if (!skip_rest_of_patch) {
248                                 do {
249                                         where = locate_hunk(fuzz);
250                                         if (hunk == 1 && where == 0 && !force) {
251                                                 /* dwim for reversed patch? */
252                                                 if (!pch_swap()) {
253                                                         if (fuzz == 0)
254                                                                 say("Not enough memory to try swapped hunk!  Assuming unswapped.\n");
255                                                         continue;
256                                                 }
257                                                 reverse = !reverse;
258                                                 /* try again */
259                                                 where = locate_hunk(fuzz);
260                                                 if (where == 0) {
261                                                         /* didn't find it swapped */
262                                                         if (!pch_swap())
263                                                                 /* put it back to normal */
264                                                                 fatal("lost hunk on alloc error!\n");
265                                                         reverse = !reverse;
266                                                 } else if (noreverse) {
267                                                         if (!pch_swap())
268                                                                 /* put it back to normal */
269                                                                 fatal("lost hunk on alloc error!\n");
270                                                         reverse = !reverse;
271                                                         say("Ignoring previously applied (or reversed) patch.\n");
272                                                         skip_rest_of_patch = true;
273                                                 } else if (batch) {
274                                                         if (verbose)
275                                                                 say("%seversed (or previously applied) patch detected!  %s -R.",
276                                                                     reverse ? "R" : "Unr",
277                                                                     reverse ? "Assuming" : "Ignoring");
278                                                 } else {
279                                                         ask("%seversed (or previously applied) patch detected!  %s -R? [y] ",
280                                                             reverse ? "R" : "Unr",
281                                                             reverse ? "Assume" : "Ignore");
282                                                         if (*buf == 'n') {
283                                                                 ask("Apply anyway? [n] ");
284                                                                 if (*buf != 'y')
285                                                                         skip_rest_of_patch = true;
286                                                                 where = 0;
287                                                                 reverse = !reverse;
288                                                                 if (!pch_swap())
289                                                                         /* put it back to normal */
290                                                                         fatal("lost hunk on alloc error!\n");
291                                                         }
292                                                 }
293                                         }
294                                 } while (!skip_rest_of_patch && where == 0 &&
295                                     ++fuzz <= mymaxfuzz);
296
297                                 if (skip_rest_of_patch) {       /* just got decided */
298                                         fclose(ofp);
299                                         ofp = NULL;
300                                 }
301                         }
302                         newwhere = pch_newfirst() + last_offset;
303                         if (skip_rest_of_patch) {
304                                 abort_hunk();
305                                 failed++;
306                                 if (verbose)
307                                         say("Hunk #%d ignored at %ld.\n",
308                                             hunk, newwhere);
309                         } else if (where == 0) {
310                                 abort_hunk();
311                                 failed++;
312                                 if (verbose)
313                                         say("Hunk #%d failed at %ld.\n",
314                                             hunk, newwhere);
315                         } else {
316                                 apply_hunk(where);
317                                 if (verbose) {
318                                         say("Hunk #%d succeeded at %ld",
319                                             hunk, newwhere);
320                                         if (fuzz != 0)
321                                                 say(" with fuzz %ld", fuzz);
322                                         if (last_offset)
323                                                 say(" (offset %ld line%s)",
324                                                     last_offset,
325                                                     last_offset == 1L ? "" : "s");
326                                         say(".\n");
327                                 }
328                         }
329                 }
330
331                 if (out_of_mem && using_plan_a) {
332                         Argc = Argc_last;
333                         Argv = Argv_last;
334                         say("\n\nRan out of memory using Plan A--trying again...\n\n");
335                         if (ofp)
336                                 fclose(ofp);
337                         ofp = NULL;
338                         if (rejfp)
339                                 fclose(rejfp);
340                         rejfp = NULL;
341                         continue;
342                 }
343                 if (hunk == 0)
344                         fatal("Internal error: hunk should not be 0\n");
345
346                 /* finish spewing out the new file */
347                 if (!skip_rest_of_patch)
348                         spew_output();
349
350                 /* and put the output where desired */
351                 ignore_signals();
352                 if (!skip_rest_of_patch) {
353                         struct stat     statbuf;
354                         char    *realout = outname;
355
356                         if (!check_only) {
357                                 if (move_file(TMPOUTNAME, outname) < 0) {
358                                         toutkeep = true;
359                                         realout = TMPOUTNAME;
360                                         chmod(TMPOUTNAME, filemode);
361                                 } else
362                                         chmod(outname, filemode);
363
364                                 if (remove_empty_files &&
365                                     stat(realout, &statbuf) == 0 &&
366                                     statbuf.st_size == 0) {
367                                         if (verbose)
368                                                 say("Removing %s (empty after patching).\n",
369                                                     realout);
370                                         unlink(realout);
371                                 }
372                         }
373                 }
374                 fclose(rejfp);
375                 rejfp = NULL;
376                 if (failed) {
377                         error = 1;
378                         if (*rejname == '\0') {
379                                 if (strlcpy(rejname, outname,
380                                     sizeof(rejname)) >= sizeof(rejname))
381                                         fatal("filename %s is too long\n", outname);
382                                 if (strlcat(rejname, REJEXT,
383                                     sizeof(rejname)) >= sizeof(rejname))
384                                         fatal("filename %s is too long\n", outname);
385                         }
386                         if (skip_rest_of_patch) {
387                                 say("%d out of %d hunks ignored--saving rejects to %s\n",
388                                     failed, hunk, rejname);
389                         } else {
390                                 say("%d out of %d hunks failed--saving rejects to %s\n",
391                                     failed, hunk, rejname);
392                         }
393                         if (!check_only && move_file(TMPREJNAME, rejname) < 0)
394                                 trejkeep = true;
395                 }
396                 set_signals(1);
397         }
398         my_exit(error);
399         /* NOTREACHED */
400 }
401
402 /* Prepare to find the next patch to do in the patch file. */
403
404 static void
405 reinitialize_almost_everything(void)
406 {
407         re_patch();
408         re_input();
409
410         input_lines = 0;
411         last_frozen_line = 0;
412
413         filec = 0;
414         if (!out_of_mem) {
415                 free(filearg[0]);
416                 filearg[0] = NULL;
417         }
418
419         free(outname);
420         outname = NULL;
421
422         last_offset = 0;
423         diff_type = 0;
424
425         free(revision);
426         revision = NULL;
427
428         reverse = reverse_flag_specified;
429         skip_rest_of_patch = false;
430
431         get_some_switches();
432 }
433
434 /* Process switches and filenames. */
435
436 static void
437 get_some_switches(void)
438 {
439         const char *options = "b:B:cCd:D:eEfF:i:lnNo:p:r:RstuvV:x:z:";
440         static struct option longopts[] = {
441                 {"backup",              no_argument,            0,      'b'},
442                 {"batch",               no_argument,            0,      't'},
443                 {"check",               no_argument,            0,      'C'},
444                 {"context",             no_argument,            0,      'c'},
445                 {"debug",               required_argument,      0,      'x'},
446                 {"directory",           required_argument,      0,      'd'},
447                 {"ed",                  no_argument,            0,      'e'},
448                 {"force",               no_argument,            0,      'f'},
449                 {"forward",             no_argument,            0,      'N'},
450                 {"fuzz",                required_argument,      0,      'F'},
451                 {"ifdef",               required_argument,      0,      'D'},
452                 {"input",               required_argument,      0,      'i'},
453                 {"ignore-whitespace",   no_argument,            0,      'l'},
454                 {"normal",              no_argument,            0,      'n'},
455                 {"output",              required_argument,      0,      'o'},
456                 {"prefix",              required_argument,      0,      'B'},
457                 {"quiet",               no_argument,            0,      's'},
458                 {"reject-file",         required_argument,      0,      'r'},
459                 {"remove-empty-files",  no_argument,            0,      'E'},
460                 {"reverse",             no_argument,            0,      'R'},
461                 {"silent",              no_argument,            0,      's'},
462                 {"strip",               required_argument,      0,      'p'},
463                 {"suffix",              required_argument,      0,      'z'},
464                 {"unified",             no_argument,            0,      'u'},
465                 {"version",             no_argument,            0,      'v'},
466                 {"version-control",     required_argument,      0,      'V'},
467                 {"posix",               no_argument,            &posix, 1},
468                 {NULL,                  0,                      0,      0}
469         };
470         int ch;
471
472         rejname[0] = '\0';
473         Argc_last = Argc;
474         Argv_last = Argv;
475         if (!Argc)
476                 return;
477         optreset = optind = 1;
478         while ((ch = getopt_long(Argc, Argv, options, longopts, NULL)) != -1) {
479                 switch (ch) {
480                 case 'b':
481                         if (backup_type == none)
482                                 backup_type = numbered_existing;
483                         if (optarg == NULL)
484                                 break;
485                         if (verbose)
486                                 say("Warning, the ``-b suffix'' option has been"
487                                     " obsoleted by the -z option.\n");
488                         /* FALLTHROUGH */
489                 case 'z':
490                         /* must directly follow 'b' case for backwards compat */
491                         simple_backup_suffix = savestr(optarg);
492                         break;
493                 case 'B':
494                         origprae = savestr(optarg);
495                         break;
496                 case 'c':
497                         diff_type = CONTEXT_DIFF;
498                         break;
499                 case 'C':
500                         check_only = true;
501                         break;
502                 case 'd':
503                         if (chdir(optarg) < 0)
504                                 pfatal("can't cd to %s", optarg);
505                         break;
506                 case 'D':
507                         do_defines = true;
508                         if (!isalpha(*optarg) && *optarg != '_')
509                                 fatal("argument to -D is not an identifier\n");
510                         snprintf(if_defined, sizeof if_defined,
511                             "#ifdef %s\n", optarg);
512                         snprintf(not_defined, sizeof not_defined,
513                             "#ifndef %s\n", optarg);
514                         snprintf(end_defined, sizeof end_defined,
515                             "#endif /* %s */\n", optarg);
516                         break;
517                 case 'e':
518                         diff_type = ED_DIFF;
519                         break;
520                 case 'E':
521                         remove_empty_files = true;
522                         break;
523                 case 'f':
524                         force = true;
525                         break;
526                 case 'F':
527                         maxfuzz = atoi(optarg);
528                         break;
529                 case 'i':
530                         if (++filec == MAXFILEC)
531                                 fatal("too many file arguments\n");
532                         filearg[filec] = savestr(optarg);
533                         break;
534                 case 'l':
535                         canonicalize = true;
536                         break;
537                 case 'n':
538                         diff_type = NORMAL_DIFF;
539                         break;
540                 case 'N':
541                         noreverse = true;
542                         break;
543                 case 'o':
544                         outname = savestr(optarg);
545                         break;
546                 case 'p':
547                         strippath = atoi(optarg);
548                         break;
549                 case 'r':
550                         if (strlcpy(rejname, optarg,
551                             sizeof(rejname)) >= sizeof(rejname))
552                                 fatal("argument for -r is too long\n");
553                         break;
554                 case 'R':
555                         reverse = true;
556                         reverse_flag_specified = true;
557                         break;
558                 case 's':
559                         verbose = false;
560                         break;
561                 case 't':
562                         batch = true;
563                         break;
564                 case 'u':
565                         diff_type = UNI_DIFF;
566                         break;
567                 case 'v':
568                         version();
569                         break;
570                 case 'V':
571                         backup_type = get_version(optarg);
572                         break;
573 #ifdef DEBUGGING
574                 case 'x':
575                         debug = atoi(optarg);
576                         break;
577 #endif
578                 default:
579                         if (ch != '\0')
580                                 usage();
581                         break;
582                 }
583         }
584         Argc -= optind;
585         Argv += optind;
586
587         if (Argc > 0) {
588                 filearg[0] = savestr(*Argv++);
589                 Argc--;
590                 while (Argc > 0) {
591                         if (++filec == MAXFILEC)
592                                 fatal("too many file arguments\n");
593                         filearg[filec] = savestr(*Argv++);
594                         Argc--;
595                 }
596         }
597
598         if (getenv("POSIXLY_CORRECT") != NULL)
599                 posix = 1;
600 }
601
602 static void
603 usage(void)
604 {
605         fprintf(stderr,
606 "usage: patch [-bcCeEflnNRstuv] [-B backup-prefix] [-d directory] [-D symbol]\n"
607 "             [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n"
608 "             [-r rej-name] [-V {numbered,existing,simple}] [-z backup-ext]\n"
609 "             [origfile [patchfile]]\n");
610         my_exit(EXIT_SUCCESS);
611 }
612
613 /*
614  * Attempt to find the right place to apply this hunk of patch.
615  */
616 static LINENUM
617 locate_hunk(LINENUM fuzz)
618 {
619         LINENUM first_guess = pch_first() + last_offset;
620         LINENUM offset;
621         LINENUM pat_lines = pch_ptrn_lines();
622         LINENUM max_pos_offset = input_lines - first_guess - pat_lines + 1;
623         LINENUM max_neg_offset = first_guess - last_frozen_line - 1 + pch_context();
624
625         if (pat_lines == 0) {           /* null range matches always */
626                 if (verbose && fuzz == 0 && (diff_type == CONTEXT_DIFF
627                     || diff_type == NEW_CONTEXT_DIFF
628                     || diff_type == UNI_DIFF)) {
629                         say("Empty context always matches.\n");
630                 }
631                 if (diff_type == CONTEXT_DIFF
632                     || diff_type == NEW_CONTEXT_DIFF
633                     || diff_type == UNI_DIFF) {
634                         if (fuzz == 0)
635                                 return (input_lines == 0 ? first_guess : 0);
636                 } else
637                         return (first_guess);
638         }
639         if (max_neg_offset >= first_guess)      /* do not try lines < 0 */
640                 max_neg_offset = first_guess - 1;
641         if (first_guess <= input_lines && patch_match(first_guess, 0, fuzz))
642                 return first_guess;
643         for (offset = 1; ; offset++) {
644                 bool    check_after = (offset <= max_pos_offset);
645                 bool    check_before = (offset <= max_neg_offset);
646
647                 if (check_after && patch_match(first_guess, offset, fuzz)) {
648 #ifdef DEBUGGING
649                         if (debug & 1)
650                                 say("Offset changing from %ld to %ld\n",
651                                     last_offset, offset);
652 #endif
653                         last_offset = offset;
654                         return first_guess + offset;
655                 } else if (check_before && patch_match(first_guess, -offset, fuzz)) {
656 #ifdef DEBUGGING
657                         if (debug & 1)
658                                 say("Offset changing from %ld to %ld\n",
659                                     last_offset, -offset);
660 #endif
661                         last_offset = -offset;
662                         return first_guess - offset;
663                 } else if (!check_before && !check_after)
664                         return 0;
665         }
666 }
667
668 /* We did not find the pattern, dump out the hunk so they can handle it. */
669
670 static void
671 abort_hunk(void)
672 {
673         LINENUM i;
674         const LINENUM   pat_end = pch_end();
675         /*
676          * add in last_offset to guess the same as the previous successful
677          * hunk
678          */
679         const LINENUM   oldfirst = pch_first() + last_offset;
680         const LINENUM   newfirst = pch_newfirst() + last_offset;
681         const LINENUM   oldlast = oldfirst + pch_ptrn_lines() - 1;
682         const LINENUM   newlast = newfirst + pch_repl_lines() - 1;
683         const char      *stars = (diff_type >= NEW_CONTEXT_DIFF ? " ****" : "");
684         const char      *minuses = (diff_type >= NEW_CONTEXT_DIFF ? " ----" : " -----");
685
686         fprintf(rejfp, "***************\n");
687         for (i = 0; i <= pat_end; i++) {
688                 switch (pch_char(i)) {
689                 case '*':
690                         if (oldlast < oldfirst)
691                                 fprintf(rejfp, "*** 0%s\n", stars);
692                         else if (oldlast == oldfirst)
693                                 fprintf(rejfp, "*** %ld%s\n", oldfirst, stars);
694                         else
695                                 fprintf(rejfp, "*** %ld,%ld%s\n", oldfirst,
696                                     oldlast, stars);
697                         break;
698                 case '=':
699                         if (newlast < newfirst)
700                                 fprintf(rejfp, "--- 0%s\n", minuses);
701                         else if (newlast == newfirst)
702                                 fprintf(rejfp, "--- %ld%s\n", newfirst, minuses);
703                         else
704                                 fprintf(rejfp, "--- %ld,%ld%s\n", newfirst,
705                                     newlast, minuses);
706                         break;
707                 case '\n':
708                         fprintf(rejfp, "%s", pfetch(i));
709                         break;
710                 case ' ':
711                 case '-':
712                 case '+':
713                 case '!':
714                         fprintf(rejfp, "%c %s", pch_char(i), pfetch(i));
715                         break;
716                 default:
717                         fatal("fatal internal error in abort_hunk\n");
718                 }
719         }
720 }
721
722 /* We found where to apply it (we hope), so do it. */
723
724 static void
725 apply_hunk(LINENUM where)
726 {
727         LINENUM         old = 1;
728         const LINENUM   lastline = pch_ptrn_lines();
729         LINENUM         new = lastline + 1;
730 #define OUTSIDE 0
731 #define IN_IFNDEF 1
732 #define IN_IFDEF 2
733 #define IN_ELSE 3
734         int             def_state = OUTSIDE;
735         const LINENUM   pat_end = pch_end();
736
737         where--;
738         while (pch_char(new) == '=' || pch_char(new) == '\n')
739                 new++;
740
741         while (old <= lastline) {
742                 if (pch_char(old) == '-') {
743                         copy_till(where + old - 1, false);
744                         if (do_defines) {
745                                 if (def_state == OUTSIDE) {
746                                         fputs(not_defined, ofp);
747                                         def_state = IN_IFNDEF;
748                                 } else if (def_state == IN_IFDEF) {
749                                         fputs(else_defined, ofp);
750                                         def_state = IN_ELSE;
751                                 }
752                                 fputs(pfetch(old), ofp);
753                         }
754                         last_frozen_line++;
755                         old++;
756                 } else if (new > pat_end) {
757                         break;
758                 } else if (pch_char(new) == '+') {
759                         copy_till(where + old - 1, false);
760                         if (do_defines) {
761                                 if (def_state == IN_IFNDEF) {
762                                         fputs(else_defined, ofp);
763                                         def_state = IN_ELSE;
764                                 } else if (def_state == OUTSIDE) {
765                                         fputs(if_defined, ofp);
766                                         def_state = IN_IFDEF;
767                                 }
768                         }
769                         fputs(pfetch(new), ofp);
770                         new++;
771                 } else if (pch_char(new) != pch_char(old)) {
772                         say("Out-of-sync patch, lines %ld,%ld--mangled text or line numbers, maybe?\n",
773                             pch_hunk_beg() + old,
774                             pch_hunk_beg() + new);
775 #ifdef DEBUGGING
776                         say("oldchar = '%c', newchar = '%c'\n",
777                             pch_char(old), pch_char(new));
778 #endif
779                         my_exit(2);
780                 } else if (pch_char(new) == '!') {
781                         copy_till(where + old - 1, false);
782                         if (do_defines) {
783                                 fputs(not_defined, ofp);
784                                 def_state = IN_IFNDEF;
785                         }
786                         while (pch_char(old) == '!') {
787                                 if (do_defines) {
788                                         fputs(pfetch(old), ofp);
789                                 }
790                                 last_frozen_line++;
791                                 old++;
792                         }
793                         if (do_defines) {
794                                 fputs(else_defined, ofp);
795                                 def_state = IN_ELSE;
796                         }
797                         while (pch_char(new) == '!') {
798                                 fputs(pfetch(new), ofp);
799                                 new++;
800                         }
801                 } else {
802                         if (pch_char(new) != ' ')
803                                 fatal("Internal error: expected ' '\n");
804                         old++;
805                         new++;
806                         if (do_defines && def_state != OUTSIDE) {
807                                 fputs(end_defined, ofp);
808                                 def_state = OUTSIDE;
809                         }
810                 }
811         }
812         if (new <= pat_end && pch_char(new) == '+') {
813                 copy_till(where + old - 1, false);
814                 if (do_defines) {
815                         if (def_state == OUTSIDE) {
816                                 fputs(if_defined, ofp);
817                                 def_state = IN_IFDEF;
818                         } else if (def_state == IN_IFNDEF) {
819                                 fputs(else_defined, ofp);
820                                 def_state = IN_ELSE;
821                         }
822                 }
823                 while (new <= pat_end && pch_char(new) == '+') {
824                         fputs(pfetch(new), ofp);
825                         new++;
826                 }
827         }
828         if (do_defines && def_state != OUTSIDE) {
829                 fputs(end_defined, ofp);
830         }
831 }
832
833 /*
834  * Open the new file.
835  */
836 static void
837 init_output(const char *name)
838 {
839         ofp = fopen(name, "w");
840         if (ofp == NULL)
841                 pfatal("can't create %s", name);
842 }
843
844 /*
845  * Open a file to put hunks we can't locate.
846  */
847 static void
848 init_reject(const char *name)
849 {
850         rejfp = fopen(name, "w");
851         if (rejfp == NULL)
852                 pfatal("can't create %s", name);
853 }
854
855 /*
856  * Copy input file to output, up to wherever hunk is to be applied.
857  * If endoffile is true, treat the last line specially since it may
858  * lack a newline.
859  */
860 static void
861 copy_till(LINENUM lastline, bool endoffile)
862 {
863         if (last_frozen_line > lastline)
864                 fatal("misordered hunks! output would be garbled\n");
865         while (last_frozen_line < lastline) {
866                 if (++last_frozen_line == lastline && endoffile)
867                         dump_line(last_frozen_line, !last_line_missing_eol);
868                 else
869                         dump_line(last_frozen_line, true);
870         }
871 }
872
873 /*
874  * Finish copying the input file to the output file.
875  */
876 static void
877 spew_output(void)
878 {
879 #ifdef DEBUGGING
880         if (debug & 256)
881                 say("il=%ld lfl=%ld\n", input_lines, last_frozen_line);
882 #endif
883         if (input_lines)
884                 copy_till(input_lines, true);   /* dump remainder of file */
885         fclose(ofp);
886         ofp = NULL;
887 }
888
889 /*
890  * Copy one line from input to output.
891  */
892 static void
893 dump_line(LINENUM line, bool write_newline)
894 {
895         char    *s;
896
897         s = ifetch(line, 0);
898         if (s == NULL)
899                 return;
900         /* Note: string is not NUL terminated. */
901         for (; *s != '\n'; s++)
902                 putc(*s, ofp);
903         if (write_newline)
904                 putc('\n', ofp);
905 }
906
907 /*
908  * Does the patch pattern match at line base+offset?
909  */
910 static bool
911 patch_match(LINENUM base, LINENUM offset, LINENUM fuzz)
912 {
913         LINENUM         pline = 1 + fuzz;
914         LINENUM         iline;
915         LINENUM         pat_lines = pch_ptrn_lines() - fuzz;
916         const char      *ilineptr;
917         const char      *plineptr;
918         short           plinelen;
919
920         for (iline = base + offset + fuzz; pline <= pat_lines; pline++, iline++) {
921                 ilineptr = ifetch(iline, offset >= 0);
922                 if (ilineptr == NULL)
923                         return false;
924                 plineptr = pfetch(pline);
925                 plinelen = pch_line_len(pline);
926                 if (canonicalize) {
927                         if (!similar(ilineptr, plineptr, plinelen))
928                                 return false;
929                 } else if (strnNE(ilineptr, plineptr, plinelen))
930                         return false;
931                 if (iline == input_lines) {
932                         /*
933                          * We are looking at the last line of the file.
934                          * If the file has no eol, the patch line should
935                          * not have one either and vice-versa. Note that
936                          * plinelen > 0.
937                          */
938                         if (last_line_missing_eol) {
939                                 if (plineptr[plinelen - 1] == '\n')
940                                         return false;
941                         } else {
942                                 if (plineptr[plinelen - 1] != '\n')
943                                         return false;
944                         }
945                 }
946         }
947         return true;
948 }
949
950 /*
951  * Do two lines match with canonicalized white space?
952  */
953 static bool
954 similar(const char *a, const char *b, int len)
955 {
956         while (len) {
957                 if (isspace(*b)) {      /* whitespace (or \n) to match? */
958                         if (!isspace(*a))       /* no corresponding whitespace? */
959                                 return false;
960                         while (len && isspace(*b) && *b != '\n')
961                                 b++, len--;     /* skip pattern whitespace */
962                         while (isspace(*a) && *a != '\n')
963                                 a++;    /* skip target whitespace */
964                         if (*a == '\n' || *b == '\n')
965                                 return (*a == *b);      /* should end in sync */
966                 } else if (*a++ != *b++)        /* match non-whitespace chars */
967                         return false;
968                 else
969                         len--;  /* probably not necessary */
970         }
971         return true;            /* actually, this is not reached */
972         /* since there is always a \n */
973 }