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