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