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