xref: /original-bsd/usr.bin/patch/patch.c (revision 64af1d24)
1 #ifndef lint
2 static char sccsid[] = "@(#)patch.c	5.8 (Berkeley) 05/18/86";
3 #endif not lint
4 
5 /* patch - a program to apply diffs to original files
6  *
7  * $Header: patch.c,v 1.3 85/03/26 15:07:43 lwall Exp $
8  *
9  * Copyright 1984, Larry Wall
10  *
11  * This program may be copied as long as you don't try to make any
12  * money off of it, or pretend that you wrote it.
13  *
14  * $Log:	patch.c,v $
15  * 85/08/15 van%ucbmonet@berkeley
16  * Changes for 4.3bsd diff -c.
17  *
18  * Revision 1.3  85/03/26  15:07:43  lwall
19  * Frozen.
20  *
21  * Revision 1.2.1.9  85/03/12  17:03:35  lwall
22  * Changed pfp->_file to fileno(pfp).
23  *
24  * Revision 1.2.1.8  85/03/12  16:30:43  lwall
25  * Check i_ptr and i_womp to make sure they aren't null before freeing.
26  * Also allow ed output to be suppressed.
27  *
28  * Revision 1.2.1.7  85/03/12  15:56:13  lwall
29  * Added -p option from jromine@uci-750a.
30  *
31  * Revision 1.2.1.6  85/03/12  12:12:51  lwall
32  * Now checks for normalness of file to patch.
33  *
34  * Revision 1.2.1.5  85/03/12  11:52:12  lwall
35  * Added -D (#ifdef) option from joe@fluke.
36  *
37  * Revision 1.2.1.4  84/12/06  11:14:15  lwall
38  * Made smarter about SCCS subdirectories.
39  *
40  * Revision 1.2.1.3  84/12/05  11:18:43  lwall
41  * Added -l switch to do loose string comparison.
42  *
43  * Revision 1.2.1.2  84/12/04  09:47:13  lwall
44  * Failed hunk count not reset on multiple patch file.
45  *
46  * Revision 1.2.1.1  84/12/04  09:42:37  lwall
47  * Branch for sdcrdcf changes.
48  *
49  * Revision 1.2  84/11/29  13:29:51  lwall
50  * Linted.  Identifiers uniqified.  Fixed i_ptr malloc() bug.  Fixed
51  * multiple calls to mktemp().  Will now work on machines that can only
52  * read 32767 chars.  Added -R option for diffs with new and old swapped.
53  * Various cosmetic changes.
54  *
55  * Revision 1.1  84/11/09  17:03:58  lwall
56  * Initial revision
57  *
58  */
59 
60 #define DEBUGGING
61 
62 /* shut lint up about the following when return value ignored */
63 
64 #define Signal (void)signal
65 #define Unlink (void)unlink
66 #define Lseek (void)lseek
67 #define Fseek (void)fseek
68 #define Fstat (void)fstat
69 #define Pclose (void)pclose
70 #define Close (void)close
71 #define Fclose (void)fclose
72 #define Fflush (void)fflush
73 #define Sprintf (void)sprintf
74 #define Mktemp (void)mktemp
75 #define Strcpy (void)strcpy
76 #define Strcat (void)strcat
77 
78 #include <stdio.h>
79 #include <assert.h>
80 #include <sys/types.h>
81 #include <sys/stat.h>
82 #include <ctype.h>
83 #include <signal.h>
84 
85 /* constants */
86 
87 #define TRUE (1)
88 #define FALSE (0)
89 
90 #define MAXHUNKSIZE 2000
91 #define MAXLINELEN 1024
92 #define BUFFERSIZE 1024
93 #define ORIGEXT ".orig"
94 #define SCCSPREFIX "s."
95 #define GET "get -e %s"
96 #define RCSSUFFIX ",v"
97 #define CHECKOUT "co -l %s"
98 
99 /* handy definitions */
100 
101 #define Null(t) ((t)0)
102 #define Nullch Null(char *)
103 #define Nullfp Null(FILE *)
104 
105 #define Ctl(ch) (ch & 037)
106 
107 #define strNE(s1,s2) (strcmp(s1,s2))
108 #define strEQ(s1,s2) (!strcmp(s1,s2))
109 #define strnNE(s1,s2,l) (strncmp(s1,s2,l))
110 #define strnEQ(s1,s2,l) (!strncmp(s1,s2,l))
111 
112 /* typedefs */
113 
114 typedef char bool;
115 typedef long LINENUM;			/* must be signed */
116 typedef unsigned MEM;			/* what to feed malloc */
117 
118 /* globals */
119 
120 int Argc;				/* guess */
121 char **Argv;
122 
123 struct stat filestat;			/* file statistics area */
124 
125 char serrbuf[BUFSIZ];			/* buffer for stderr */
126 char buf[MAXLINELEN];			/* general purpose buffer */
127 FILE *pfp = Nullfp;			/* patch file pointer */
128 FILE *ofp = Nullfp;			/* output file pointer */
129 FILE *rejfp = Nullfp;			/* reject file pointer */
130 
131 LINENUM input_lines = 0;		/* how long is input file in lines */
132 LINENUM last_frozen_line = 0;		/* how many input lines have been */
133 					/* irretractibly output */
134 
135 #define MAXFILEC 2
136 int filec = 0;				/* how many file arguments? */
137 char *filearg[MAXFILEC];
138 
139 char *outname = Nullch;
140 char rejname[128];
141 
142 char *origext = Nullch;
143 
144 char TMPOUTNAME[] = "/tmp/patchoXXXXXX";
145 char TMPINNAME[] = "/tmp/patchiXXXXXX";	/* you might want /usr/tmp here */
146 char TMPREJNAME[] = "/tmp/patchrXXXXXX";
147 char TMPPATNAME[] = "/tmp/patchpXXXXXX";
148 
149 LINENUM last_offset = 0;
150 #ifdef DEBUGGING
151 int debug = 0;
152 #endif
153 bool verbose = TRUE;
154 bool reverse = FALSE;
155 bool noreverse = FALSE;
156 bool skip_this_patch = FALSE;
157 bool usepath = FALSE;
158 bool canonicalize = FALSE;
159 
160 #define CONTEXT_DIFF 1
161 #define NORMAL_DIFF 2
162 #define ED_DIFF 3
163 #define NEW_CONTEXT_DIFF 4
164 int diff_type = 0;
165 
166 int do_defines = 0;			/* patch using ifdef, ifndef, etc. */
167 char if_defined[128];			/* #ifdef xyzzy */
168 char not_defined[128];			/* #ifndef xyzzy */
169 char else_defined[] = "#else\n";	/* #else */
170 char end_defined[128];			/* #endif xyzzy */
171 
172 char *revision = Nullch;		/* prerequisite revision, if any */
173 
174 /* procedures */
175 
176 LINENUM locate_hunk();
177 bool patch_match();
178 bool similar();
179 char *malloc();
180 char *savestr();
181 char *strcpy();
182 char *strcat();
183 char *sprintf();		/* usually */
184 int my_exit();
185 bool rev_in_string();
186 char *fetchname();
187 long atol();
188 long lseek();
189 char *mktemp();
190 
191 /* patch type */
192 
193 bool there_is_another_patch();
194 bool another_hunk();
195 char *pfetch();
196 int pch_line_len();
197 LINENUM pch_first();
198 LINENUM pch_ptrn_lines();
199 LINENUM pch_newfirst();
200 LINENUM pch_repl_lines();
201 LINENUM pch_end();
202 LINENUM pch_context();
203 LINENUM pch_hunk_beg();
204 char pch_char();
205 char *pfetch();
206 char *pgets();
207 
208 /* input file type */
209 
210 char *ifetch();
211 
212 /* apply a context patch to a named file */
213 
214 main(argc,argv)
215 int argc;
216 char **argv;
217 {
218     LINENUM where;
219     int hunk = 0;
220     int failed = 0;
221     int i;
222 
223     setbuf(stderr,serrbuf);
224     for (i = 0; i<MAXFILEC; i++)
225 	filearg[i] = Nullch;
226     Mktemp(TMPOUTNAME);
227     Mktemp(TMPINNAME);
228     Mktemp(TMPREJNAME);
229     Mktemp(TMPPATNAME);
230 
231     /* parse switches */
232     Argc = argc;
233     Argv = argv;
234     get_some_switches();
235 
236     /* make sure we clean up /tmp in case of disaster */
237     set_signals();
238 
239     for (
240 	open_patch_file(filearg[1]);
241 	there_is_another_patch();
242 	reinitialize_almost_everything()
243     ) {					/* for each patch in patch file */
244 
245 	if (outname == Nullch)
246 	    outname = savestr(filearg[0]);
247 
248 	/* initialize the patched file */
249 	init_output(TMPOUTNAME);
250 
251 	/* for ed script just up and do it and exit */
252 	if (diff_type == ED_DIFF) {
253 	    do_ed_script();
254 	    continue;
255 	}
256 
257 	/* initialize reject file */
258 	init_reject(TMPREJNAME);
259 
260 	/* find out where all the lines are */
261 	scan_input(filearg[0]);
262 
263 	/* from here on, open no standard i/o files, because malloc */
264 	/* might misfire */
265 
266 	/* apply each hunk of patch */
267 	hunk = 0;
268 	failed = 0;
269 	while (another_hunk()) {
270 	    hunk++;
271 	    where = locate_hunk();
272 	    if (hunk == 1 && where == Null(LINENUM)) {
273 					/* dwim for reversed patch? */
274 		pch_swap();
275 		reverse = !reverse;
276 		where = locate_hunk();	/* try again */
277 		if (where == Null(LINENUM)) {
278 		    pch_swap();		/* no, put it back to normal */
279 		    reverse = !reverse;
280 		} else if (noreverse) {
281 		    pch_swap();		/* put it back to normal */
282 		    reverse = !reverse;
283 		    say("Ignoring previously applied (or reversed) patch.\n");
284 		    skip_this_patch = TRUE;
285 		}
286 		else {
287 		    say("%seversed (or previously applied) patch detected!  %s -R.\n",
288 			reverse ? "R" : "Unr",
289 			reverse ? "Assuming" : "Ignoring");
290 		}
291 	    }
292 	    if (where == Null(LINENUM) || skip_this_patch) {
293 		abort_hunk();
294 		failed++;
295 		if (verbose)
296 		    say("Hunk #%d failed.\n",hunk);
297 	    }
298 	    else {
299 		apply_hunk(where);
300 		if (verbose)
301 		    if (last_offset)
302 			say("Hunk #%d succeeded (offset %d line%s).\n",
303 			  hunk,last_offset,last_offset==1?"":"s");
304 		    else
305 			say("Hunk #%d succeeded.\n", hunk);
306 	    }
307 	}
308 
309 	assert(hunk);
310 
311 	/* finish spewing out the new file */
312 	spew_output();
313 
314 	/* and put the output where desired */
315 	ignore_signals();
316 	move_file(TMPOUTNAME,outname);
317 	Fclose(rejfp);
318 	rejfp = Nullfp;
319 	if (failed) {
320 	    if (!*rejname) {
321 		Strcpy(rejname, outname);
322 		Strcat(rejname, ".rej");
323 	    }
324 	    say("%d out of %d hunks failed--saving rejects to %s\n",
325 		failed, hunk, rejname);
326 	    move_file(TMPREJNAME,rejname);
327 	}
328 	set_signals();
329     }
330     my_exit(0);
331 }
332 
333 reinitialize_almost_everything()
334 {
335     re_patch();
336     re_input();
337 
338     input_lines = 0;
339     last_frozen_line = 0;
340 
341     filec = 0;
342     if (filearg[0] != Nullch) {
343 	free(filearg[0]);
344 	filearg[0] = Nullch;
345     }
346 
347     if (outname != Nullch) {
348 	free(outname);
349 	outname = Nullch;
350     }
351 
352     last_offset = 0;
353 
354     diff_type = 0;
355 
356     if (revision != Nullch) {
357 	free(revision);
358 	revision = Nullch;
359     }
360 
361     reverse = FALSE;
362     skip_this_patch = FALSE;
363 
364     get_some_switches();
365 
366     if (filec >= 2)
367 	fatal("You may not change to a different patch file.\n");
368 }
369 
370 get_some_switches()
371 {
372     register char *s;
373 
374     rejname[0] = '\0';
375     if (!Argc)
376 	return;
377     for (Argc--,Argv++; Argc; Argc--,Argv++) {
378 	s = Argv[0];
379 	if (strEQ(s,"+")) {
380 	    return;			/* + will be skipped by for loop */
381 	}
382 	if (*s != '-' || !s[1]) {
383 	    if (filec == MAXFILEC)
384 		fatal("Too many file arguments.\n");
385 	    filearg[filec++] = savestr(s);
386 	}
387 	else {
388 	    switch (*++s) {
389 	    case 'b':
390 		origext = savestr(Argv[1]);
391 		Argc--,Argv++;
392 		break;
393 	    case 'c':
394 		diff_type = CONTEXT_DIFF;
395 		break;
396 	    case 'd':
397 		if (chdir(Argv[1]) < 0)
398 		    fatal("Can't cd to %s.\n",Argv[1]);
399 		Argc--,Argv++;
400 		break;
401 	    case 'D':
402 	    	do_defines++;
403 		Sprintf(if_defined, "#ifdef %s\n", Argv[1]);
404 		Sprintf(not_defined, "#ifndef %s\n", Argv[1]);
405 		Sprintf(end_defined, "#endif %s\n", Argv[1]);
406 		Argc--,Argv++;
407 		break;
408 	    case 'e':
409 		diff_type = ED_DIFF;
410 		break;
411 	    case 'l':
412 		canonicalize = TRUE;
413 		break;
414 	    case 'n':
415 		diff_type = NORMAL_DIFF;
416 		break;
417 	    case 'o':
418 		outname = savestr(Argv[1]);
419 		Argc--,Argv++;
420 		break;
421 	    case 'p':
422 		usepath = TRUE;	/* do not strip path names */
423 		break;
424 	    case 'r':
425 		Strcpy(rejname,Argv[1]);
426 		Argc--,Argv++;
427 		break;
428 	    case 'R':
429 		reverse = TRUE;
430 		break;
431 	    case 'N':
432 		noreverse = TRUE;
433 		break;
434 	    case 's':
435 		verbose = FALSE;
436 		break;
437 #ifdef DEBUGGING
438 	    case 'x':
439 		debug = atoi(s+1);
440 		break;
441 #endif
442 	    default:
443 		fatal("Unrecognized switch: %s\n",Argv[0]);
444 	    }
445 	}
446     }
447 }
448 
449 LINENUM
450 locate_hunk()
451 {
452     register LINENUM first_guess = pch_first() + last_offset;
453     register LINENUM offset;
454     LINENUM pat_lines = pch_ptrn_lines();
455     register LINENUM max_pos_offset = input_lines - first_guess
456 				- pat_lines + 1;
457     register LINENUM max_neg_offset = first_guess - last_frozen_line - 1
458 				- pch_context();
459 
460     if (!pat_lines)			/* null range matches always */
461 	return first_guess;
462     if (max_neg_offset >= first_guess)	/* do not try lines < 0 */
463 	max_neg_offset = first_guess - 1;
464     if (first_guess <= input_lines && patch_match(first_guess,(LINENUM)0))
465 	return first_guess;
466     for (offset = 1; ; offset++) {
467 	bool check_after = (offset <= max_pos_offset);
468 	bool check_before = (offset <= max_pos_offset);
469 
470 	if (check_after && patch_match(first_guess,offset)) {
471 #ifdef DEBUGGING
472 	    if (debug & 1)
473 		printf("Offset changing from %d to %d\n",last_offset,offset);
474 #endif
475 	    last_offset = offset;
476 	    return first_guess+offset;
477 	}
478 	else if (check_before && patch_match(first_guess,-offset)) {
479 #ifdef DEBUGGING
480 	    if (debug & 1)
481 		printf("Offset changing from %d to %d\n",last_offset,-offset);
482 #endif
483 	    last_offset = -offset;
484 	    return first_guess-offset;
485 	}
486 	else if (!check_before && !check_after)
487 	    return Null(LINENUM);
488     }
489 }
490 
491 /* we did not find the pattern, dump out the hunk so they can handle it */
492 
493 abort_hunk()
494 {
495     register LINENUM i;
496     register LINENUM pat_end = pch_end();
497     /* add in last_offset to guess the same as the previous successful hunk */
498     int oldfirst = pch_first() + last_offset;
499     int newfirst = pch_newfirst() + last_offset;
500     int oldlast = oldfirst + pch_ptrn_lines() - 1;
501     int newlast = newfirst + pch_repl_lines() - 1;
502 
503     fprintf(rejfp,"***************\n");
504     for (i=0; i<=pat_end; i++) {
505 	switch (pch_char(i)) {
506 	case '*':
507 	    if (diff_type == NEW_CONTEXT_DIFF)
508 		fprintf(rejfp,"*** %d,%d ****\n", oldfirst, oldlast);
509 	    else
510 		fprintf(rejfp,"*** %d,%d\n", oldfirst, oldlast);
511 	    break;
512 	case '=':
513 	    fprintf(rejfp,"--- %d,%d -----\n", newfirst, newlast);
514 	    break;
515 	case '\n':
516 	    fprintf(rejfp,"%s", pfetch(i));
517 	    break;
518 	case ' ': case '-': case '+': case '!':
519 	    fprintf(rejfp,"%c %s", pch_char(i), pfetch(i));
520 	    break;
521 	default:
522 	    say("Fatal internal error in abort_hunk().\n");
523 	    abort();
524 	}
525     }
526 }
527 
528 /* we found where to apply it (we hope), so do it */
529 
530 apply_hunk(where)
531 LINENUM where;
532 {
533     register LINENUM old = 1;
534     register LINENUM lastline = pch_ptrn_lines();
535     register LINENUM new = lastline+1;
536     register int def_state = 0;	/* -1 = ifndef, 1 = ifdef */
537 
538     where--;
539     while (pch_char(new) == '=' || pch_char(new) == '\n')
540 	new++;
541 
542     while (old <= lastline) {
543 	if (pch_char(old) == '-') {
544 	    copy_till(where + old - 1);
545 	    if (do_defines) {
546 		if (def_state == 0) {
547 		    fputs(not_defined, ofp);
548 		    def_state = -1;
549 		} else
550 		if (def_state == 1) {
551 		    fputs(else_defined, ofp);
552 		    def_state = 2;
553 		}
554 		fputs(pfetch(old), ofp);
555 	    }
556 	    last_frozen_line++;
557 	    old++;
558 	}
559 	else if (pch_char(new) == '+') {
560 	    copy_till(where + old - 1);
561 	    if (do_defines) {
562 		if (def_state == -1) {
563 		    fputs(else_defined, ofp);
564 		    def_state = 2;
565 		} else
566 		if (def_state == 0) {
567 		    fputs(if_defined, ofp);
568 		    def_state = 1;
569 		}
570 	    }
571 	    fputs(pfetch(new),ofp);
572 	    new++;
573 	}
574 	else {
575 	    if (pch_char(new) != pch_char(old)) {
576 		say("Out-of-sync patch, lines %d,%d\n",
577 		    pch_hunk_beg() + old - 1,
578 		    pch_hunk_beg() + new - 1);
579 #ifdef DEBUGGING
580 		printf("oldchar = '%c', newchar = '%c'\n",
581 		    pch_char(old), pch_char(new));
582 #endif
583 		my_exit(1);
584 	    }
585 	    if (pch_char(new) == '!') {
586 		copy_till(where + old - 1);
587 		if (do_defines) {
588 		   fputs(not_defined,ofp);
589 		   def_state = -1;
590 		}
591 		while (pch_char(old) == '!') {
592 		    if (do_defines) {
593 			fputs(pfetch(old),ofp);
594 		    }
595 		    last_frozen_line++;
596 		    old++;
597 		}
598 		if (do_defines) {
599 		    fputs(else_defined, ofp);
600 		    def_state = 2;
601 		}
602 		while (pch_char(new) == '!') {
603 		    fputs(pfetch(new),ofp);
604 		    new++;
605 		}
606 		if (do_defines) {
607 		    fputs(end_defined, ofp);
608 		    def_state = 0;
609 		}
610 	    }
611 	    else {
612 		assert(pch_char(new) == ' ');
613 		old++;
614 		new++;
615 	    }
616 	}
617     }
618     if (new <= pch_end() && pch_char(new) == '+') {
619 	copy_till(where + old - 1);
620 	if (do_defines) {
621 	    if (def_state == 0) {
622 	    	fputs(if_defined, ofp);
623 		def_state = 1;
624 	    } else
625 	    if (def_state == -1) {
626 		fputs(else_defined, ofp);
627 		def_state = 2;
628 	    }
629 	}
630 	while (new <= pch_end() && pch_char(new) == '+') {
631 	    fputs(pfetch(new),ofp);
632 	    new++;
633 	}
634     }
635     if (do_defines && def_state) {
636 	fputs(end_defined, ofp);
637     }
638 }
639 
640 do_ed_script()
641 {
642     FILE *pipefp, *popen();
643     bool this_line_is_command = FALSE;
644     register char *t;
645     long beginning_of_this_line;
646 
647     Unlink(TMPOUTNAME);
648     copy_file(filearg[0],TMPOUTNAME);
649     if (verbose)
650 	Sprintf(buf,"/bin/ed %s",TMPOUTNAME);
651     else
652 	Sprintf(buf,"/bin/ed - %s",TMPOUTNAME);
653     pipefp = popen(buf,"w");
654     for (;;) {
655 	beginning_of_this_line = ftell(pfp);
656 	if (pgets(buf,sizeof buf,pfp) == Nullch) {
657 	    next_intuit_at(beginning_of_this_line);
658 	    break;
659 	}
660 	for (t=buf; isdigit(*t) || *t == ','; t++) ;
661 	this_line_is_command = (isdigit(*buf) &&
662 	  (*t == 'd' || *t == 'c' || *t == 'a') );
663 	if (this_line_is_command) {
664 	    fputs(buf,pipefp);
665 	    if (*t != 'd') {
666 		while (pgets(buf,sizeof buf,pfp) != Nullch) {
667 		    fputs(buf,pipefp);
668 		    if (strEQ(buf,".\n"))
669 			break;
670 		}
671 	    }
672 	}
673 	else {
674 	    next_intuit_at(beginning_of_this_line);
675 	    break;
676 	}
677     }
678     fprintf(pipefp,"w\n");
679     fprintf(pipefp,"q\n");
680     Fflush(pipefp);
681     Pclose(pipefp);
682     ignore_signals();
683     move_file(TMPOUTNAME,outname);
684     set_signals();
685 }
686 
687 init_output(name)
688 char *name;
689 {
690     ofp = fopen(name,"w");
691     if (ofp == Nullfp)
692 	fatal("patch: can't create %s.\n",name);
693 }
694 
695 init_reject(name)
696 char *name;
697 {
698     rejfp = fopen(name,"w");
699     if (rejfp == Nullfp)
700 	fatal("patch: can't create %s.\n",name);
701 }
702 
703 move_file(from,to)
704 char *from, *to;
705 {
706     char bakname[512];
707     register char *s;
708     int fromfd;
709     register int i;
710 
711     /* to stdout? */
712 
713     if (strEQ(to,"-")) {
714 #ifdef DEBUGGING
715 	if (debug & 4)
716 	    say("Moving %s to stdout.\n",from);
717 #endif
718 	fromfd = open(from,0);
719 	if (fromfd < 0)
720 	    fatal("patch: internal error, can't reopen %s\n",from);
721 	while ((i=read(fromfd,buf,sizeof buf)) > 0)
722 	    if (write(1,buf,i) != 1)
723 		fatal("patch: write failed\n");
724 	Close(fromfd);
725 	return;
726     }
727 
728     Strcpy(bakname,to);
729     Strcat(bakname,origext?origext:ORIGEXT);
730     if (stat(to,&filestat) >= 0) {	/* output file exists */
731 	dev_t to_device = filestat.st_dev;
732 	ino_t to_inode  = filestat.st_ino;
733 	char *simplename = bakname;
734 
735 	for (s=bakname; *s; s++) {
736 	    if (*s == '/')
737 		simplename = s+1;
738 	}
739 	/* find a backup name that is not the same file */
740 	while (stat(bakname,&filestat) >= 0 &&
741 		to_device == filestat.st_dev && to_inode == filestat.st_ino) {
742 	    for (s=simplename; *s && !islower(*s); s++) ;
743 	    if (*s)
744 		*s = toupper(*s);
745 	    else
746 		Strcpy(simplename, simplename+1);
747 	}
748 	while (unlink(bakname) >= 0) ;	/* while() is for benefit of Eunice */
749 #ifdef DEBUGGING
750 	if (debug & 4)
751 	    say("Moving %s to %s.\n",to,bakname);
752 #endif
753 	if (link(to,bakname) < 0) {
754 	    say("patch: can't backup %s, output is in %s\n",
755 		to,from);
756 	    return;
757 	}
758 	while (unlink(to) >= 0) ;
759     }
760 #ifdef DEBUGGING
761     if (debug & 4)
762 	say("Moving %s to %s.\n",from,to);
763 #endif
764     if (link(from,to) < 0) {		/* different file system? */
765 	int tofd;
766 
767 	tofd = creat(to,0666);
768 	if (tofd < 0) {
769 	    say("patch: can't create %s, output is in %s.\n",
770 	      to, from);
771 	    return;
772 	}
773 	fromfd = open(from,0);
774 	if (fromfd < 0)
775 	    fatal("patch: internal error, can't reopen %s\n",from);
776 	while ((i=read(fromfd,buf,sizeof buf)) > 0)
777 	    if (write(tofd,buf,i) != i)
778 		fatal("patch: write failed\n");
779 	Close(fromfd);
780 	Close(tofd);
781     }
782     Unlink(from);
783 }
784 
785 copy_file(from,to)
786 char *from, *to;
787 {
788     int tofd;
789     int fromfd;
790     register int i;
791 
792     tofd = creat(to,0666);
793     if (tofd < 0)
794 	fatal("patch: can't create %s.\n", to);
795     fromfd = open(from,0);
796     if (fromfd < 0)
797 	fatal("patch: internal error, can't reopen %s\n",from);
798     while ((i=read(fromfd,buf,sizeof buf)) > 0)
799 	if (write(tofd,buf,i) != i)
800 	    fatal("patch: write (%s) failed\n", to);
801     Close(fromfd);
802     Close(tofd);
803 }
804 
805 copy_till(lastline)
806 register LINENUM lastline;
807 {
808     if (last_frozen_line > lastline)
809 	say("patch: misordered hunks! output will be garbled.\n");
810     while (last_frozen_line < lastline) {
811 	dump_line(++last_frozen_line);
812     }
813 }
814 
815 spew_output()
816 {
817     copy_till(input_lines);		/* dump remainder of file */
818     Fclose(ofp);
819     ofp = Nullfp;
820 }
821 
822 dump_line(line)
823 LINENUM line;
824 {
825     register char *s;
826 
827     for (s=ifetch(line,0); putc(*s,ofp) != '\n'; s++) ;
828 }
829 
830 /* does the patch pattern match at line base+offset? */
831 
832 bool
833 patch_match(base,offset)
834 LINENUM base;
835 LINENUM offset;
836 {
837     register LINENUM pline;
838     register LINENUM iline;
839     register LINENUM pat_lines = pch_ptrn_lines();
840 
841     for (pline = 1, iline=base+offset; pline <= pat_lines; pline++,iline++) {
842 	if (canonicalize) {
843 	    if (!similar(ifetch(iline,(offset >= 0)),
844 			 pfetch(pline),
845 			 pch_line_len(pline) ))
846 		return FALSE;
847 	}
848 	else if (strnNE(ifetch(iline,(offset >= 0)),
849 		   pfetch(pline),
850 		   pch_line_len(pline) ))
851 	    return FALSE;
852     }
853     return TRUE;
854 }
855 
856 /* match two lines with canonicalized white space */
857 
858 bool
859 similar(a,b,len)
860 register char *a, *b;
861 register int len;
862 {
863     while (len) {
864 	if (isspace(*b)) {		/* whitespace (or \n) to match? */
865 	    if (!isspace(*a))		/* no corresponding whitespace? */
866 		return FALSE;
867 	    while (len && isspace(*b) && *b != '\n')
868 		b++,len--;		/* skip pattern whitespace */
869 	    while (isspace(*a) && *a != '\n')
870 		a++;			/* skip target whitespace */
871 	    if (*a == '\n' || *b == '\n')
872 		return (*a == *b);	/* should end in sync */
873 	}
874 	else if (*a++ != *b++)		/* match non-whitespace chars */
875 	    return FALSE;
876 	else
877 	    len--;			/* probably not necessary */
878     }
879     return TRUE;			/* actually, this is not reached */
880 					/* since there is always a \n */
881 }
882 
883 /* input file with indexable lines abstract type */
884 
885 bool using_plan_a = TRUE;
886 static long i_size;			/* size of the input file */
887 static char *i_womp;			/* plan a buffer for entire file */
888 static char **i_ptr;			/* pointers to lines in i_womp */
889 
890 static int tifd = -1;			/* plan b virtual string array */
891 static char *tibuf[2];			/* plan b buffers */
892 static LINENUM tiline[2] = {-1,-1};	/* 1st line in each buffer */
893 static LINENUM lines_per_buf;		/* how many lines per buffer */
894 static int tireclen;			/* length of records in tmp file */
895 
896 re_input()
897 {
898     if (using_plan_a) {
899 	i_size = 0;
900 	/*NOSTRICT*/
901 	if (i_ptr != Null(char**))
902 	    free((char *)i_ptr);
903 	if (i_womp != Nullch)
904 	    free(i_womp);
905 	i_womp = Nullch;
906 	i_ptr = Null(char **);
907     }
908     else {
909 	using_plan_a = TRUE;		/* maybe the next one is smaller */
910 	Close(tifd);
911 	tifd = -1;
912 	free(tibuf[0]);
913 	free(tibuf[1]);
914 	tibuf[0] = tibuf[1] = Nullch;
915 	tiline[0] = tiline[1] = -1;
916 	tireclen = 0;
917     }
918 }
919 
920 scan_input(filename)
921 char *filename;
922 {
923     bool plan_a();
924 
925     if (!plan_a(filename))
926 	plan_b(filename);
927 }
928 
929 /* try keeping everything in memory */
930 
931 bool
932 plan_a(filename)
933 char *filename;
934 {
935     int ifd;
936     register char *s;
937     register LINENUM iline;
938 
939     if (stat(filename,&filestat) < 0) {
940 	Sprintf(buf,"RCS/%s%s",filename,RCSSUFFIX);
941 	if (stat(buf,&filestat) >= 0 || stat(buf+4,&filestat) >= 0) {
942 	    Sprintf(buf,CHECKOUT,filename);
943 	    if (verbose)
944 		say("Can't find %s--attempting to check it out from RCS.\n",
945 		    filename);
946 	    if (system(buf) || stat(filename,&filestat))
947 		fatal("Can't check out %s.\n",filename);
948 	}
949 	else {
950 	    Sprintf(buf,"SCCS/%s%s",SCCSPREFIX,filename);
951 	    if (stat(buf,&filestat) >= 0 || stat(buf+5,&filestat) >= 0) {
952 		Sprintf(buf,GET,filename);
953 		if (verbose)
954 		    say("Can't find %s--attempting to get it from SCCS.\n",
955 			filename);
956 		if (system(buf) || stat(filename,&filestat))
957 		    fatal("Can't get %s.\n",filename);
958 	    }
959 	    else
960 		fatal("Can't find %s.\n",filename);
961 	}
962     }
963     if ((filestat.st_mode & S_IFMT) & ~S_IFREG)
964 	fatal("%s is not a normal file--can't patch.\n",filename);
965     i_size = filestat.st_size;
966     /*NOSTRICT*/
967     i_womp = malloc((MEM)(i_size+2));
968     if (i_womp == Nullch)
969 	return FALSE;
970     if ((ifd = open(filename,0)) < 0)
971 	fatal("Can't open file %s\n",filename);
972     /*NOSTRICT*/
973     if (read(ifd,i_womp,(int)i_size) != i_size) {
974 	Close(ifd);
975 	free(i_womp);
976 	return FALSE;
977     }
978     Close(ifd);
979     if (i_womp[i_size-1] != '\n')
980 	i_womp[i_size++] = '\n';
981     i_womp[i_size] = '\0';
982 
983     /* count the lines in the buffer so we know how many pointers we need */
984 
985     iline = 0;
986     for (s=i_womp; *s; s++) {
987 	if (*s == '\n')
988 	    iline++;
989     }
990     /*NOSTRICT*/
991     i_ptr = (char **)malloc((MEM)((iline + 2) * sizeof(char *)));
992     if (i_ptr == Null(char **)) {	/* shucks, it was a near thing */
993 	free((char *)i_womp);
994 	return FALSE;
995     }
996 
997     /* now scan the buffer and build pointer array */
998 
999     iline = 1;
1000     i_ptr[iline] = i_womp;
1001     for (s=i_womp; *s; s++) {
1002 	if (*s == '\n')
1003 	    i_ptr[++iline] = s+1;	/* these are NOT null terminated */
1004     }
1005     input_lines = iline - 1;
1006 
1007     /* now check for revision, if any */
1008 
1009     if (revision != Nullch) {
1010 	if (!rev_in_string(i_womp)) {
1011 	    ask("This file doesn't appear to be the %s version--patch anyway? [n] ",
1012 		revision);
1013 	    if (*buf != 'y')
1014 		fatal("Aborted.\n");
1015 	}
1016 	else if (verbose)
1017 	    say("Good.  This file appears to be the %s version.\n",
1018 		revision);
1019     }
1020     return TRUE;			/* plan a will work */
1021 }
1022 
1023 /* keep (virtually) nothing in memory */
1024 
1025 plan_b(filename)
1026 char *filename;
1027 {
1028     FILE *ifp;
1029     register int i = 0;
1030     register int maxlen = 1;
1031     bool found_revision = (revision == Nullch);
1032 
1033     using_plan_a = FALSE;
1034     if ((ifp = fopen(filename,"r")) == Nullfp)
1035 	fatal("Can't open file %s\n",filename);
1036     if ((tifd = creat(TMPINNAME,0666)) < 0)
1037 	fatal("Can't open file %s\n",TMPINNAME);
1038     while (fgets(buf,sizeof buf, ifp) != Nullch) {
1039 	if (revision != Nullch && !found_revision && rev_in_string(buf))
1040 	    found_revision = TRUE;
1041 	if ((i = strlen(buf)) > maxlen)
1042 	    maxlen = i;			/* find longest line */
1043     }
1044     if (revision != Nullch) {
1045 	if (!found_revision) {
1046 	    ask("This file doesn't appear to be the %s version--patch anyway? [n] ",
1047 		revision);
1048 	    if (*buf != 'y')
1049 		fatal("Aborted.\n");
1050 	}
1051 	else if (verbose)
1052 	    say("Good.  This file appears to be the %s version.\n",
1053 		revision);
1054     }
1055     Fseek(ifp,0L,0);		/* rewind file */
1056     lines_per_buf = BUFFERSIZE / maxlen;
1057     tireclen = maxlen;
1058     tibuf[0] = malloc((MEM)(BUFFERSIZE + 1));
1059     tibuf[1] = malloc((MEM)(BUFFERSIZE + 1));
1060     if (tibuf[1] == Nullch)
1061 	fatal("Can't seem to get enough memory.\n");
1062     for (i=1; ; i++) {
1063 	if (! (i % lines_per_buf))	/* new block */
1064 	    if (write(tifd,tibuf[0],BUFFERSIZE) < BUFFERSIZE)
1065 		fatal("patch: can't write temp file.\n");
1066 	if (fgets(tibuf[0] + maxlen * (i%lines_per_buf), maxlen + 1, ifp)
1067 	  == Nullch) {
1068 	    input_lines = i - 1;
1069 	    if (i % lines_per_buf)
1070 		if (write(tifd,tibuf[0],BUFFERSIZE) < BUFFERSIZE)
1071 		    fatal("patch: can't write temp file.\n");
1072 	    break;
1073 	}
1074     }
1075     Fclose(ifp);
1076     Close(tifd);
1077     if ((tifd = open(TMPINNAME,0)) < 0) {
1078 	fatal("Can't reopen file %s\n",TMPINNAME);
1079     }
1080 }
1081 
1082 /* fetch a line from the input file, \n terminated, not necessarily \0 */
1083 char *
1084 ifetch(line,whichbuf)
1085 register LINENUM line;
1086 int whichbuf;				/* ignored when file in memory */
1087 {
1088     if (line < 1 || line > input_lines)
1089 	return "";
1090     if (using_plan_a)
1091 	return i_ptr[line];
1092     else {
1093 	LINENUM offline = line % lines_per_buf;
1094 	LINENUM baseline = line - offline;
1095 
1096 	if (tiline[0] == baseline)
1097 	    whichbuf = 0;
1098 	else if (tiline[1] == baseline)
1099 	    whichbuf = 1;
1100 	else {
1101 	    tiline[whichbuf] = baseline;
1102 	    Lseek(tifd,(long)baseline / lines_per_buf * BUFFERSIZE,0);
1103 	    if (read(tifd,tibuf[whichbuf],BUFFERSIZE) < 0)
1104 		fatal("Error reading tmp file %s.\n",TMPINNAME);
1105 	}
1106 	return tibuf[whichbuf] + (tireclen*offline);
1107     }
1108 }
1109 
1110 /* patch abstract type */
1111 
1112 static long p_filesize;			/* size of the patch file */
1113 static LINENUM p_first;			/* 1st line number */
1114 static LINENUM p_newfirst;		/* 1st line number of replacement */
1115 static LINENUM p_ptrn_lines;		/* # lines in pattern */
1116 static LINENUM p_repl_lines;		/* # lines in replacement text */
1117 static LINENUM p_end = -1;		/* last line in hunk */
1118 static LINENUM p_max;			/* max allowed value of p_end */
1119 static LINENUM p_context = 3;		/* # of context lines */
1120 static LINENUM p_input_line = 0;	/* current line # from patch file */
1121 static char *p_line[MAXHUNKSIZE];	/* the text of the hunk */
1122 static char p_char[MAXHUNKSIZE];	/* +, -, and ! */
1123 static int p_len[MAXHUNKSIZE];		/* length of each line */
1124 static int p_indent;			/* indent to patch */
1125 static long p_base;			/* where to intuit this time */
1126 static long p_start;			/* where intuit found a patch */
1127 
1128 re_patch()
1129 {
1130     p_first = (LINENUM)0;
1131     p_newfirst = (LINENUM)0;
1132     p_ptrn_lines = (LINENUM)0;
1133     p_repl_lines = (LINENUM)0;
1134     p_end = (LINENUM)-1;
1135     p_max = (LINENUM)0;
1136     p_indent = 0;
1137 }
1138 
1139 open_patch_file(filename)
1140 char *filename;
1141 {
1142     if (filename == Nullch || !*filename || strEQ(filename,"-")) {
1143 	pfp = fopen(TMPPATNAME,"w");
1144 	if (pfp == Nullfp)
1145 	    fatal("patch: can't create %s.\n",TMPPATNAME);
1146 	while (fgets(buf,sizeof buf,stdin) != NULL)
1147 	    fputs(buf,pfp);
1148 	Fclose(pfp);
1149 	filename = TMPPATNAME;
1150     }
1151     pfp = fopen(filename,"r");
1152     if (pfp == Nullfp)
1153 	fatal("patch file %s not found\n",filename);
1154     Fstat(fileno(pfp), &filestat);
1155     p_filesize = filestat.st_size;
1156     next_intuit_at(0L);			/* start at the beginning */
1157 }
1158 
1159 bool
1160 there_is_another_patch()
1161 {
1162     bool no_input_file = (filearg[0] == Nullch);
1163 
1164     if (p_base != 0L && p_base >= p_filesize) {
1165 	if (verbose)
1166 	    say("done\n");
1167 	return FALSE;
1168     }
1169     if (verbose)
1170 	say("Hmm...");
1171     diff_type = intuit_diff_type();
1172     if (!diff_type) {
1173 	if (p_base != 0L) {
1174 	    if (verbose)
1175 		say("  Ignoring the trailing garbage.\ndone\n");
1176 	}
1177 	else
1178 	    say("  I can't seem to find a patch in there anywhere.\n");
1179 	return FALSE;
1180     }
1181     if (verbose)
1182 	say("  %sooks like %s to me...\n",
1183 	    (p_base == 0L ? "L" : "The next patch l"),
1184 	    diff_type == CONTEXT_DIFF ? "a context diff" :
1185 	    diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" :
1186 	    diff_type == NORMAL_DIFF ? "a normal diff" :
1187 	    "an ed script" );
1188     if (p_indent && verbose)
1189 	say("(Patch is indented %d space%s.)\n",p_indent,p_indent==1?"":"s");
1190     skip_to(p_start);
1191     if (no_input_file) {
1192 	while (filearg[0] == Nullch) {
1193 	    ask("File to patch: ");
1194 	    filearg[0] = fetchname(buf);
1195 	}
1196 	if (verbose) {
1197 	    say("Patching file %s...\n",filearg[0]);
1198 	}
1199     }
1200     return TRUE;
1201 }
1202 
1203 intuit_diff_type()
1204 {
1205     long this_line = 0;
1206     long previous_line;
1207     long first_command_line = -1;
1208     bool last_line_was_command = FALSE;
1209     bool this_line_is_command = FALSE;
1210     bool last_line_was_stars = FALSE;
1211     bool this_line_is_stars = FALSE;
1212     register int indent;
1213     register char *s, *t;
1214     char *oldname = Nullch;
1215     char *newname = Nullch;
1216     bool no_filearg = (filearg[0] == Nullch);
1217 
1218     Fseek(pfp,p_base,0);
1219     for (;;) {
1220 	previous_line = this_line;
1221 	last_line_was_command = this_line_is_command;
1222 	last_line_was_stars = this_line_is_stars;
1223 	this_line = ftell(pfp);
1224 	indent = 0;
1225 	if (fgets(buf,sizeof buf,pfp) == Nullch) {
1226 	    if (first_command_line >= 0L) {
1227 					/* nothing but deletes!? */
1228 		p_start = first_command_line;
1229 		return ED_DIFF;
1230 	    }
1231 	    else {
1232 		p_start = this_line;
1233 		return 0;
1234 	    }
1235 	}
1236 	for (s = buf; *s == ' ' || *s == '\t'; s++) {
1237 	    if (*s == '\t')
1238 		indent += 8 - (indent % 8);
1239 	    else
1240 		indent++;
1241 	}
1242 	for (t=s; isdigit(*t) || *t == ','; t++) ;
1243 	this_line_is_command = (isdigit(*s) &&
1244 	  (*t == 'd' || *t == 'c' || *t == 'a') );
1245 	if (first_command_line < 0L && this_line_is_command) {
1246 	    first_command_line = this_line;
1247 	    p_indent = indent;		/* assume this for now */
1248 	}
1249 	if (strnEQ(s,"*** ",4))
1250 	    oldname = fetchname(s+4);
1251 	else if (strnEQ(s,"--- ",4)) {
1252 	    newname = fetchname(s+4);
1253 	    if (no_filearg) {
1254 		if (oldname && newname) {
1255 		    if (strlen(oldname) < strlen(newname))
1256 			filearg[0] = oldname;
1257 		    else
1258 			filearg[0] = newname;
1259 		}
1260 		else if (oldname)
1261 		    filearg[0] = oldname;
1262 		else if (newname)
1263 		    filearg[0] = newname;
1264 	    }
1265 	}
1266 	else if (strnEQ(s,"Index:",6)) {
1267 	    if (no_filearg)
1268 		filearg[0] = fetchname(s+6);
1269 					/* this filearg might get limboed */
1270 	}
1271 	else if (strnEQ(s,"Prereq:",7)) {
1272 	    for (t=s+7; isspace(*t); t++) ;
1273 	    revision = savestr(t);
1274 	    for (t=revision; *t && !isspace(*t); t++) ;
1275 	    *t = '\0';
1276 	    if (!*revision) {
1277 		free(revision);
1278 		revision = Nullch;
1279 	    }
1280 	}
1281 	if ((!diff_type || diff_type == ED_DIFF) &&
1282 	  first_command_line >= 0L &&
1283 	  strEQ(s,".\n") ) {
1284 	    p_indent = indent;
1285 	    p_start = first_command_line;
1286 	    return ED_DIFF;
1287 	}
1288 	this_line_is_stars = strnEQ(s,"********",8);
1289 	if ((!diff_type || diff_type == CONTEXT_DIFF) && last_line_was_stars &&
1290 		 strnEQ(s,"*** ",4)) {
1291 	    /* if this is a new context diff the character just before */
1292 	    /* the newline is a '*'. */
1293 	    while (*s != '\n')
1294 		s++;
1295 	    p_indent = indent;
1296 	    p_start = previous_line;
1297 	    return (*(s-1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF);
1298 	}
1299 	if ((!diff_type || diff_type == NORMAL_DIFF) &&
1300 	  last_line_was_command &&
1301 	  (strnEQ(s,"< ",2) || strnEQ(s,"> ",2)) ) {
1302 	    p_start = previous_line;
1303 	    p_indent = indent;
1304 	    return NORMAL_DIFF;
1305 	}
1306     }
1307 }
1308 
1309 char *
1310 fetchname(at)
1311 char *at;
1312 {
1313     char *s = savestr(at);
1314     char *name;
1315     register char *t;
1316     char tmpbuf[200];
1317 
1318     for (t=s; isspace(*t); t++) ;
1319     name = t;
1320     for (; *t && !isspace(*t); t++)
1321 	if (!usepath)
1322 	    if (*t == '/')
1323 		name = t+1;
1324     *t = '\0';
1325     name = savestr(name);
1326     Sprintf(tmpbuf,"RCS/%s",name);
1327     free(s);
1328     if (stat(name,&filestat) < 0) {
1329 	Strcat(tmpbuf,RCSSUFFIX);
1330 	if (stat(tmpbuf,&filestat) < 0 && stat(tmpbuf+4,&filestat) < 0) {
1331 	    Sprintf(tmpbuf,"SCCS/%s%s",SCCSPREFIX,name);
1332 	    if (stat(tmpbuf,&filestat) < 0 && stat(tmpbuf+5,&filestat) < 0) {
1333 		free(name);
1334 		name = Nullch;
1335 	    }
1336 	}
1337     }
1338     return name;
1339 }
1340 
1341 next_intuit_at(file_pos)
1342 long file_pos;
1343 {
1344     p_base = file_pos;
1345 }
1346 
1347 skip_to(file_pos)
1348 long file_pos;
1349 {
1350     char *ret;
1351 
1352     assert(p_base <= file_pos);
1353     if (verbose && p_base < file_pos) {
1354 	Fseek(pfp,p_base,0);
1355 	say("The text leading up to this was:\n--------------------------\n");
1356 	while (ftell(pfp) < file_pos) {
1357 	    ret = fgets(buf,sizeof buf,pfp);
1358 	    assert(ret != Nullch);
1359 	    say("|%s",buf);
1360 	}
1361 	say("--------------------------\n");
1362     }
1363     else
1364 	Fseek(pfp,file_pos,0);
1365 }
1366 
1367 bool
1368 another_hunk()
1369 {
1370     register char *s;
1371     char *ret;
1372     register int context = 0;
1373 
1374     while (p_end >= 0) {
1375 	free(p_line[p_end--]);
1376     }
1377     assert(p_end == -1);
1378 
1379     p_max = MAXHUNKSIZE;		/* gets reduced when --- found */
1380     if (diff_type == CONTEXT_DIFF) {
1381 	long line_beginning = ftell(pfp);
1382 	LINENUM repl_beginning = 0;
1383 
1384 	ret = pgets(buf,sizeof buf, pfp);
1385 	if (ret == Nullch || strnNE(buf,"********",8)) {
1386 	    next_intuit_at(line_beginning);
1387 	    return FALSE;
1388 	}
1389 	p_context = 100;
1390 	while (p_end < p_max) {
1391 	    ret = pgets(buf,sizeof buf, pfp);
1392 	    if (ret == Nullch) {
1393 		if (p_max - p_end < 4)
1394 		    Strcpy(buf,"  \n");	/* assume blank lines got chopped */
1395 		else
1396 		    fatal("Unexpected end of file in patch.\n");
1397 	    }
1398 	    p_input_line++;
1399 	    if (strnEQ(buf,"********",8))
1400 		fatal("Unexpected end of hunk at line %d.\n",
1401 		    p_input_line);
1402 	    p_char[++p_end] = *buf;
1403 	    switch (*buf) {
1404 	    case '*':
1405 		if (p_end != 0)
1406 		    fatal("Unexpected *** at line %d: %s", p_input_line, buf);
1407 		context = 0;
1408 		p_line[p_end] = savestr(buf);
1409 		for (s=buf; *s && !isdigit(*s); s++) ;
1410 		if (!isdigit(*s))
1411 		    fatal("Malformed patch at line %d: %s", p_input_line, buf);
1412 		p_first = (LINENUM) atol(s);
1413 		while (isdigit(*s)) s++;
1414 		for (; *s && !isdigit(*s); s++) ;
1415 		if (!isdigit(*s))
1416 		    p_ptrn_lines = 1;
1417 		else
1418 		    p_ptrn_lines = ((LINENUM)atol(s)) - p_first + 1;
1419 		break;
1420 	    case '-':
1421 		if (buf[1] == '-') {
1422 		    if (p_end != p_ptrn_lines + 1 &&
1423 			p_end != p_ptrn_lines + 2)
1424 			fatal("Unexpected --- at line %d: %s",
1425 			    p_input_line,buf);
1426 		    repl_beginning = p_end;
1427 		    context = 0;
1428 		    p_line[p_end] = savestr(buf);
1429 		    p_char[p_end] = '=';
1430 		    for (s=buf; *s && !isdigit(*s); s++) ;
1431 		    if (!isdigit(*s))
1432 			fatal("Malformed patch at line %d: %s",
1433 			      p_input_line, buf);
1434 		    p_newfirst = (LINENUM) atol(s);
1435 		    while (isdigit(*s)) s++;
1436 		    for (; *s && !isdigit(*s); s++) ;
1437 		    if (!isdigit(*s))
1438 			p_max = p_newfirst;
1439 		    else
1440 			p_max = ((LINENUM)atol(s));
1441 		    p_max += 1 + p_end - p_newfirst;
1442 		    if (p_max >= MAXHUNKSIZE)
1443 			fatal("Hunk too large (%d lines) at line %d: %s",
1444 			      p_max - p_end, p_input_line, buf);
1445 		    break;
1446 		}
1447 		/* FALL THROUGH */
1448 	    case '+': case '!':
1449 		if (context > 0) {
1450 		    if (context < p_context)
1451 			p_context = context;
1452 		    context = -100;
1453 		}
1454 		p_line[p_end] = savestr(buf+2);
1455 		break;
1456 	    case '\t': case '\n':	/* assume the 2 spaces got eaten */
1457 		p_line[p_end] = savestr(buf);
1458 		if (p_end != p_ptrn_lines + 1) {
1459 		    context++;
1460 		    p_char[p_end] = ' ';
1461 		}
1462 		break;
1463 	    case ' ':
1464 		context++;
1465 		p_line[p_end] = savestr(buf+2);
1466 		break;
1467 	    default:
1468 		fatal("Malformed patch at line %d: %s",p_input_line,buf);
1469 	    }
1470 	    /* set up p_len for strncmp() so we don't have to */
1471 	    /* assume null termination */
1472 	    if (p_line[p_end])
1473 		p_len[p_end] = strlen(p_line[p_end]);
1474 	    else
1475 		p_len[p_end] = 0;
1476 	}
1477 	if (p_end >=0 && !p_ptrn_lines)
1478 	    fatal("No --- found in patch at line %d\n", pch_hunk_beg());
1479 	p_repl_lines = p_end - repl_beginning;
1480 	p_char[p_end+1] = '^';	/* add a stopper for apply_hunk */
1481     }
1482     else if (diff_type == NEW_CONTEXT_DIFF) {
1483 	long line_beginning = ftell(pfp);
1484 	LINENUM repl_beginning = 0;
1485 	LINENUM fillcnt = 0;
1486 	LINENUM fillsrc;
1487 	LINENUM filldst;
1488 
1489 	ret = pgets(buf,sizeof buf, pfp);
1490 	if (ret == Nullch || strnNE(buf,"********",8)) {
1491 	    next_intuit_at(line_beginning);
1492 	    return FALSE;
1493 	}
1494 	p_context = 0;
1495 	while (p_end < p_max) {
1496 	    ret = pgets(buf,sizeof buf, pfp);
1497 	    if (ret == Nullch) {
1498 		if (p_max - p_end < 4)
1499 		    Strcpy(buf,"  \n");	/* assume blank lines got chopped */
1500 		else
1501 		    fatal("Unexpected end of file in patch.\n");
1502 	    }
1503 	    p_input_line++;
1504 	    p_char[++p_end] = *buf;
1505 	    switch (*buf) {
1506 	    case '*':	/* another hunk */
1507 		if (strnEQ(buf,"********",8))
1508 		    fatal("Unexpected end of hunk at line %d.\n",
1509 			    p_input_line);
1510 
1511 		if (p_end != 0)
1512 		    fatal("Unexpected *** at line %d: %s", p_input_line, buf);
1513 		context = 0;
1514 		p_line[p_end] = savestr(buf);
1515 		for (s=buf; *s && !isdigit(*s); s++) ;
1516 		if (!isdigit(*s))
1517 		    fatal("Malformed patch at line %d: %s", p_input_line, buf);
1518 		p_first = (LINENUM) atol(s);
1519 		while (isdigit(*s)) s++;
1520 		for (; *s && !isdigit(*s); s++) ;
1521 		if (!isdigit(*s))
1522 		    p_ptrn_lines = 1;
1523 		else
1524 		    p_ptrn_lines = ((LINENUM)atol(s)) - p_first + 1;
1525 		break;
1526 	    case '-':
1527 		if (buf[1] == '-') {
1528 		    if (p_end != p_ptrn_lines + 1) {
1529 			if (p_end == 1) {
1530 			    /* `old' lines were omitted - set up to fill them */
1531 			    /* in from 'new' context lines. */
1532 			    p_end = p_ptrn_lines + 1;
1533 			    fillsrc = p_end + 1;
1534 			    filldst = 1;
1535 			    fillcnt = p_ptrn_lines;
1536 			} else
1537 			    fatal("Unexpected --- at line %d: %s",
1538 				p_input_line,buf);
1539 		    }
1540 		    repl_beginning = p_end;
1541 		    p_line[p_end] = savestr(buf);
1542 		    p_char[p_end] = '=';
1543 		    for (s=buf; *s && !isdigit(*s); s++) ;
1544 		    if (!isdigit(*s))
1545 			fatal("Malformed patch at line %d: %s",
1546 			      p_input_line, buf);
1547 		    p_newfirst = (LINENUM) atol(s);
1548 		    while (isdigit(*s)) s++;
1549 		    for (; *s && !isdigit(*s); s++) ;
1550 		    if (!isdigit(*s))
1551 			p_max = p_newfirst;
1552 		    else
1553 			p_max = ((LINENUM)atol(s));
1554 		    p_max += 1 + p_end - p_newfirst;
1555 		    if (p_max >= MAXHUNKSIZE)
1556 			fatal("Hunk too large (%d lines) at line %d: %s",
1557 			      p_max - p_end, p_input_line, buf);
1558 		    if (p_max - p_end == context) {
1559 			/* redundant 'new' context lines were omitted */
1560 			/* set up to fill them in from the the old file's */
1561 			/* context */
1562 			fillsrc = 1;
1563 			filldst = p_end + 1;
1564 			fillcnt = p_max - repl_beginning;
1565 			p_end = p_max;
1566 		    }
1567 		    break;
1568 		}
1569 		/* FALL THROUGH */
1570 	    case '+': case '!':
1571 		if (context > 0 && p_context == 0) {
1572 		    p_context = context;
1573 		}
1574 		p_line[p_end] = savestr(buf+2);
1575 		break;
1576 	    case '\t': case '\n':	/* assume the 2 spaces got eaten */
1577 		p_line[p_end] = savestr(buf);
1578 		context++;
1579 		p_char[p_end] = ' ';
1580 		break;
1581 	    case ' ':
1582 		context++;
1583 		p_line[p_end] = savestr(buf+2);
1584 		break;
1585 	    default:
1586 		fatal("Malformed patch at line %d: %s",p_input_line,buf);
1587 	    }
1588 	    /* set up p_len for strncmp() so we don't have to */
1589 	    /* assume null termination */
1590 	    if (p_line[p_end])
1591 		p_len[p_end] = strlen(p_line[p_end]);
1592 	    else
1593 		p_len[p_end] = 0;
1594 	}
1595 	if (p_end >=0 && !p_ptrn_lines)
1596 	    fatal("No --- found in patch at line %d\n", pch_hunk_beg());
1597 
1598 	/* if there were omitted context lines, fill them in */
1599 	if (fillcnt) {
1600 	    while (fillcnt-- > 0) {
1601 		while (p_char[fillsrc] != ' ')
1602 		    fillsrc++;
1603 		if (p_line[fillsrc])
1604 		    p_line[filldst] = savestr (p_line[fillsrc]);
1605 		else
1606 		    p_line[filldst] = p_line[fillsrc];
1607 		p_char[filldst] = p_char[fillsrc];
1608 		p_len[filldst] = p_len[fillsrc];
1609 		fillsrc++; filldst++;
1610 	    }
1611 	    assert(filldst==p_end+1 || filldst==repl_beginning);
1612 	}
1613 	p_repl_lines = p_end - repl_beginning;
1614 	p_char[p_end+1] = '^';	/* add a stopper for apply_hunk */
1615     }
1616     else {				/* normal diff--fake it up */
1617 	char hunk_type;
1618 	register int i;
1619 	LINENUM min, max;
1620 	long line_beginning = ftell(pfp);
1621 
1622 	p_context = 0;
1623 	ret = pgets(buf,sizeof buf, pfp);
1624 	p_input_line++;
1625 	if (ret == Nullch || !isdigit(*buf)) {
1626 	    next_intuit_at(line_beginning);
1627 	    return FALSE;
1628 	}
1629 	p_first = (LINENUM)atol(buf);
1630 	for (s=buf; isdigit(*s); s++) ;
1631 	if (*s == ',') {
1632 	    p_ptrn_lines = (LINENUM)atol(++s) - p_first + 1;
1633 	    while (isdigit(*s)) s++;
1634 	}
1635 	else
1636 	    p_ptrn_lines = (*s != 'a');
1637 	hunk_type = *s;
1638 	if (hunk_type == 'a')
1639 	    p_first++;			/* do append rather than insert */
1640 	min = (LINENUM)atol(++s);
1641 	for (; isdigit(*s); s++) ;
1642 	if (*s == ',')
1643 	    max = (LINENUM)atol(++s);
1644 	else
1645 	    max = min;
1646 	if (hunk_type == 'd')
1647 	    min++;
1648 	p_end = p_ptrn_lines + 1 + max - min + 1;
1649 	p_newfirst = min;
1650 	p_repl_lines = max - min + 1;
1651 	Sprintf(buf,"*** %d,%d\n", p_first, p_first + p_ptrn_lines - 1);
1652 	p_line[0] = savestr(buf);
1653 	p_char[0] = '*';
1654 	for (i=1; i<=p_ptrn_lines; i++) {
1655 	    ret = pgets(buf,sizeof buf, pfp);
1656 	    p_input_line++;
1657 	    if (ret == Nullch)
1658 		fatal("Unexpected end of file in patch at line %d.\n",
1659 		  p_input_line);
1660 	    if (*buf != '<')
1661 		fatal("< expected at line %d of patch.\n", p_input_line);
1662 	    p_line[i] = savestr(buf+2);
1663 	    if (p_line[i])
1664 		p_len[i] = strlen(p_line[i]);
1665 	    else
1666 		p_len[i] = 0;
1667 	    p_char[i] = '-';
1668 	}
1669 	if (hunk_type == 'c') {
1670 	    ret = pgets(buf,sizeof buf, pfp);
1671 	    p_input_line++;
1672 	    if (ret == Nullch)
1673 		fatal("Unexpected end of file in patch at line %d.\n",
1674 		    p_input_line);
1675 	    if (*buf != '-')
1676 		fatal("--- expected at line %d of patch.\n", p_input_line);
1677 	}
1678 	Sprintf(buf,"--- %d,%d\n",min,max);
1679 	p_line[i] = savestr(buf);
1680 	p_char[i] = '=';
1681 	for (i++; i<=p_end; i++) {
1682 	    ret = pgets(buf,sizeof buf, pfp);
1683 	    p_input_line++;
1684 	    if (ret == Nullch)
1685 		fatal("Unexpected end of file in patch at line %d.\n",
1686 		    p_input_line);
1687 	    if (*buf != '>')
1688 		fatal("> expected at line %d of patch.\n", p_input_line);
1689 	    p_line[i] = savestr(buf+2);
1690 	    /* set up p_len for strncmp() so we don't have to */
1691 	    /* assume null termination */
1692 	    if (p_line[i])
1693 		p_len[i] = strlen(p_line[i]);
1694 	    else
1695 		p_len[i] = 0;
1696 	    p_char[i] = '+';
1697 	}
1698     }
1699     if (reverse)			/* backwards patch? */
1700 	pch_swap();
1701 #ifdef DEBUGGING
1702     if (debug & 2) {
1703 	int i;
1704 	char special;
1705 
1706 	for (i=0; i <= p_end; i++) {
1707 	    if (i == p_ptrn_lines)
1708 		special = '^';
1709 	    else
1710 		special = ' ';
1711 	    printf("%3d %c %c %s",i,p_char[i],special,p_line[i]);
1712 	}
1713     }
1714 #endif
1715     return TRUE;
1716 }
1717 
1718 char *
1719 pgets(bf,sz,fp)
1720 char *bf;
1721 int sz;
1722 FILE *fp;
1723 {
1724     char *ret = fgets(bf,sz,fp);
1725     register char *s;
1726     register int indent = 0;
1727 
1728     if (p_indent && ret != Nullch) {
1729 	for (s=buf; indent < p_indent && (*s == ' ' || *s == '\t'); s++) {
1730 	    if (*s == '\t')
1731 		indent += 8 - (indent % 7);
1732 	    else
1733 		indent++;
1734 	}
1735 	if (buf != s)
1736 	    Strcpy(buf,s);
1737     }
1738     return ret;
1739 }
1740 
1741 pch_swap()
1742 {
1743     char *tp_line[MAXHUNKSIZE];		/* the text of the hunk */
1744     char tp_char[MAXHUNKSIZE];		/* +, -, and ! */
1745     int tp_len[MAXHUNKSIZE];		/* length of each line */
1746     register LINENUM i, n;
1747     bool blankline = FALSE;
1748     register char *s;
1749 
1750     i = p_first;
1751     p_first = p_newfirst;
1752     p_newfirst = i;
1753 
1754     /* make a scratch copy */
1755 
1756     for (i=0; i<=p_end; i++) {
1757 	tp_line[i] = p_line[i];
1758 	tp_char[i] = p_char[i];
1759 	tp_len[i] = p_len[i];
1760     }
1761 
1762     /* now turn the new into the old */
1763 
1764     i = p_ptrn_lines + 1;
1765     if (tp_char[i] == '\n') {		/* account for possible blank line */
1766 	blankline = TRUE;
1767 	i++;
1768     }
1769     for (n=0; i <= p_end; i++,n++) {
1770 	p_line[n] = tp_line[i];
1771 	p_char[n] = tp_char[i];
1772 	if (p_char[n] == '+')
1773 	    p_char[n] = '-';
1774 	p_len[n] = tp_len[i];
1775     }
1776     if (blankline) {
1777 	i = p_ptrn_lines + 1;
1778 	p_line[n] = tp_line[i];
1779 	p_char[n] = tp_char[i];
1780 	p_len[n] = tp_len[i];
1781 	n++;
1782     }
1783     assert(p_char[0] == '=');
1784     p_char[0] = '*';
1785     for (s=p_line[0]; *s; s++)
1786 	if (*s == '-')
1787 	    *s = '*';
1788 
1789     /* now turn the old into the new */
1790 
1791     assert(tp_char[0] == '*');
1792     tp_char[0] = '=';
1793     for (s=tp_line[0]; *s; s++)
1794 	if (*s == '*')
1795 	    *s = '-';
1796     for (i=0; n <= p_end; i++,n++) {
1797 	p_line[n] = tp_line[i];
1798 	p_char[n] = tp_char[i];
1799 	if (p_char[n] == '-')
1800 	    p_char[n] = '+';
1801 	p_len[n] = tp_len[i];
1802     }
1803     assert(i == p_ptrn_lines + 1);
1804     i = p_ptrn_lines;
1805     p_ptrn_lines = p_repl_lines;
1806     p_repl_lines = i;
1807 }
1808 
1809 LINENUM
1810 pch_first()
1811 {
1812     return p_first;
1813 }
1814 
1815 LINENUM
1816 pch_ptrn_lines()
1817 {
1818     return p_ptrn_lines;
1819 }
1820 
1821 LINENUM
1822 pch_newfirst()
1823 {
1824     return p_newfirst;
1825 }
1826 
1827 LINENUM
1828 pch_repl_lines()
1829 {
1830     return p_repl_lines;
1831 }
1832 
1833 LINENUM
1834 pch_end()
1835 {
1836     return p_end;
1837 }
1838 
1839 LINENUM
1840 pch_context()
1841 {
1842     return p_context;
1843 }
1844 
1845 pch_line_len(line)
1846 LINENUM line;
1847 {
1848     return p_len[line];
1849 }
1850 
1851 char
1852 pch_char(line)
1853 LINENUM line;
1854 {
1855     return p_char[line];
1856 }
1857 
1858 char *
1859 pfetch(line)
1860 LINENUM line;
1861 {
1862     return p_line[line];
1863 }
1864 
1865 LINENUM
1866 pch_hunk_beg()
1867 {
1868     return p_input_line - p_end - 1;
1869 }
1870 
1871 char *
1872 savestr(s)
1873 register char *s;
1874 {
1875     register char  *rv,
1876                    *t;
1877 
1878     t = s;
1879     while (*t++);
1880     rv = malloc((MEM) (t - s));
1881     if (rv == NULL)
1882 	fatal ("patch: out of memory (savestr)\n");
1883     t = rv;
1884     while (*t++ = *s++);
1885     return rv;
1886 }
1887 
1888 my_exit(status)
1889 int status;
1890 {
1891     Unlink(TMPINNAME);
1892     Unlink(TMPOUTNAME);
1893     Unlink(TMPREJNAME);
1894     Unlink(TMPPATNAME);
1895     exit(status);
1896 }
1897 
1898 #ifdef lint
1899 
1900 /*VARARGS ARGSUSED*/
1901 say(pat) char *pat; { ; }
1902 /*VARARGS ARGSUSED*/
1903 fatal(pat) char *pat; { ; }
1904 /*VARARGS ARGSUSED*/
1905 ask(pat) char *pat; { ; }
1906 
1907 #else lint
1908 
1909 say(pat,arg1,arg2,arg3)
1910 char *pat;
1911 int arg1,arg2,arg3;
1912 {
1913     fprintf(stderr,pat,arg1,arg2,arg3);
1914     Fflush(stderr);
1915 }
1916 
1917 fatal(pat,arg1,arg2,arg3)
1918 char *pat;
1919 int arg1,arg2,arg3;
1920 {
1921     say(pat,arg1,arg2,arg3);
1922     my_exit(1);
1923 }
1924 
1925 ask(pat,arg1,arg2,arg3)
1926 char *pat;
1927 int arg1,arg2,arg3;
1928 {
1929     int ttyfd = open("/dev/tty",2);
1930     int r;
1931 
1932     say(pat,arg1,arg2,arg3);
1933     if (ttyfd >= 0) {
1934 	r = read(ttyfd, buf, sizeof buf);
1935 	Close(ttyfd);
1936     }
1937     else
1938 	r = read(2, buf, sizeof buf);
1939     if (r <= 0)
1940 	buf[0] = 0;
1941 }
1942 #endif lint
1943 
1944 bool
1945 rev_in_string(string)
1946 char *string;
1947 {
1948     register char *s;
1949     register int patlen;
1950 
1951     if (revision == Nullch)
1952 	return TRUE;
1953     patlen = strlen(revision);
1954     for (s = string; *s; s++) {
1955 	if (isspace(*s) && strnEQ(s+1,revision,patlen) &&
1956 		isspace(s[patlen+1] )) {
1957 	    return TRUE;
1958 	}
1959     }
1960     return FALSE;
1961 }
1962 
1963 set_signals()
1964 {
1965     /*NOSTRICT*/
1966     if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
1967 	Signal(SIGHUP, my_exit);
1968     /*NOSTRICT*/
1969     if (signal(SIGINT, SIG_IGN) != SIG_IGN)
1970 	Signal(SIGINT, my_exit);
1971 }
1972 
1973 ignore_signals()
1974 {
1975     /*NOSTRICT*/
1976     Signal(SIGHUP, SIG_IGN);
1977     /*NOSTRICT*/
1978     Signal(SIGINT, SIG_IGN);
1979 }
1980