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