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