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