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