Home | History | Annotate | Line # | Download | only in patch
patch.c revision 1.4
      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