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