xref: /csrg-svn/usr.bin/patch/patch.c (revision 26490)
1 #ifndef lint
2 static char sccsid[] = "@(#)patch.c	5.6 (Berkeley) 03/07/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 500
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 	    fprintf(rejfp,"*** %d,%d\n", oldfirst, oldlast);
508 	    break;
509 	case '=':
510 	    fprintf(rejfp,"--- %d,%d -----\n", newfirst, newlast);
511 	    break;
512 	case '\n':
513 	    fprintf(rejfp,"%s", pfetch(i));
514 	    break;
515 	case ' ': case '-': case '+': case '!':
516 	    fprintf(rejfp,"%c %s", pch_char(i), pfetch(i));
517 	    break;
518 	default:
519 	    say("Fatal internal error in abort_hunk().\n");
520 	    abort();
521 	}
522     }
523 }
524 
525 /* we found where to apply it (we hope), so do it */
526 
527 apply_hunk(where)
528 LINENUM where;
529 {
530     register LINENUM old = 1;
531     register LINENUM lastline = pch_ptrn_lines();
532     register LINENUM new = lastline+1;
533     register int def_state = 0;	/* -1 = ifndef, 1 = ifdef */
534 
535     where--;
536     while (pch_char(new) == '=' || pch_char(new) == '\n')
537 	new++;
538 
539     while (old <= lastline) {
540 	if (pch_char(old) == '-') {
541 	    copy_till(where + old - 1);
542 	    if (do_defines) {
543 		if (def_state == 0) {
544 		    fputs(not_defined, ofp);
545 		    def_state = -1;
546 		} else
547 		if (def_state == 1) {
548 		    fputs(else_defined, ofp);
549 		    def_state = 2;
550 		}
551 		fputs(pfetch(old), ofp);
552 	    }
553 	    last_frozen_line++;
554 	    old++;
555 	}
556 	else if (pch_char(new) == '+') {
557 	    copy_till(where + old - 1);
558 	    if (do_defines) {
559 		if (def_state == -1) {
560 		    fputs(else_defined, ofp);
561 		    def_state = 2;
562 		} else
563 		if (def_state == 0) {
564 		    fputs(if_defined, ofp);
565 		    def_state = 1;
566 		}
567 	    }
568 	    fputs(pfetch(new),ofp);
569 	    new++;
570 	}
571 	else {
572 	    if (pch_char(new) != pch_char(old)) {
573 		say("Out-of-sync patch, lines %d,%d\n",
574 		    pch_hunk_beg() + old - 1,
575 		    pch_hunk_beg() + new - 1);
576 #ifdef DEBUGGING
577 		printf("oldchar = '%c', newchar = '%c'\n",
578 		    pch_char(old), pch_char(new));
579 #endif
580 		my_exit(1);
581 	    }
582 	    if (pch_char(new) == '!') {
583 		copy_till(where + old - 1);
584 		if (do_defines) {
585 		   fputs(not_defined,ofp);
586 		   def_state = -1;
587 		}
588 		while (pch_char(old) == '!') {
589 		    if (do_defines) {
590 			fputs(pfetch(old),ofp);
591 		    }
592 		    last_frozen_line++;
593 		    old++;
594 		}
595 		if (do_defines) {
596 		    fputs(else_defined, ofp);
597 		    def_state = 2;
598 		}
599 		while (pch_char(new) == '!') {
600 		    fputs(pfetch(new),ofp);
601 		    new++;
602 		}
603 		if (do_defines) {
604 		    fputs(end_defined, ofp);
605 		    def_state = 0;
606 		}
607 	    }
608 	    else {
609 		assert(pch_char(new) == ' ');
610 		old++;
611 		new++;
612 	    }
613 	}
614     }
615     if (new <= pch_end() && pch_char(new) == '+') {
616 	copy_till(where + old - 1);
617 	if (do_defines) {
618 	    if (def_state == 0) {
619 	    	fputs(if_defined, ofp);
620 		def_state = 1;
621 	    } else
622 	    if (def_state == -1) {
623 		fputs(else_defined, ofp);
624 		def_state = 2;
625 	    }
626 	}
627 	while (new <= pch_end() && pch_char(new) == '+') {
628 	    fputs(pfetch(new),ofp);
629 	    new++;
630 	}
631     }
632     if (do_defines && def_state) {
633 	fputs(end_defined, ofp);
634     }
635 }
636 
637 do_ed_script()
638 {
639     FILE *pipefp, *popen();
640     bool this_line_is_command = FALSE;
641     register char *t;
642     long beginning_of_this_line;
643 
644     Unlink(TMPOUTNAME);
645     copy_file(filearg[0],TMPOUTNAME);
646     if (verbose)
647 	Sprintf(buf,"/bin/ed %s",TMPOUTNAME);
648     else
649 	Sprintf(buf,"/bin/ed - %s",TMPOUTNAME);
650     pipefp = popen(buf,"w");
651     for (;;) {
652 	beginning_of_this_line = ftell(pfp);
653 	if (pgets(buf,sizeof buf,pfp) == Nullch) {
654 	    next_intuit_at(beginning_of_this_line);
655 	    break;
656 	}
657 	for (t=buf; isdigit(*t) || *t == ','; t++) ;
658 	this_line_is_command = (isdigit(*buf) &&
659 	  (*t == 'd' || *t == 'c' || *t == 'a') );
660 	if (this_line_is_command) {
661 	    fputs(buf,pipefp);
662 	    if (*t != 'd') {
663 		while (pgets(buf,sizeof buf,pfp) != Nullch) {
664 		    fputs(buf,pipefp);
665 		    if (strEQ(buf,".\n"))
666 			break;
667 		}
668 	    }
669 	}
670 	else {
671 	    next_intuit_at(beginning_of_this_line);
672 	    break;
673 	}
674     }
675     fprintf(pipefp,"w\n");
676     fprintf(pipefp,"q\n");
677     Fflush(pipefp);
678     Pclose(pipefp);
679     ignore_signals();
680     move_file(TMPOUTNAME,outname);
681     set_signals();
682 }
683 
684 init_output(name)
685 char *name;
686 {
687     ofp = fopen(name,"w");
688     if (ofp == Nullfp)
689 	fatal("patch: can't create %s.\n",name);
690 }
691 
692 init_reject(name)
693 char *name;
694 {
695     rejfp = fopen(name,"w");
696     if (rejfp == Nullfp)
697 	fatal("patch: can't create %s.\n",name);
698 }
699 
700 move_file(from,to)
701 char *from, *to;
702 {
703     char bakname[512];
704     register char *s;
705     int fromfd;
706     register int i;
707 
708     /* to stdout? */
709 
710     if (strEQ(to,"-")) {
711 #ifdef DEBUGGING
712 	if (debug & 4)
713 	    say("Moving %s to stdout.\n",from);
714 #endif
715 	fromfd = open(from,0);
716 	if (fromfd < 0)
717 	    fatal("patch: internal error, can't reopen %s\n",from);
718 	while ((i=read(fromfd,buf,sizeof buf)) > 0)
719 	    if (write(1,buf,i) != 1)
720 		fatal("patch: write failed\n");
721 	Close(fromfd);
722 	return;
723     }
724 
725     Strcpy(bakname,to);
726     Strcat(bakname,origext?origext:ORIGEXT);
727     if (stat(to,&filestat) >= 0) {	/* output file exists */
728 	dev_t to_device = filestat.st_dev;
729 	ino_t to_inode  = filestat.st_ino;
730 	char *simplename = bakname;
731 
732 	for (s=bakname; *s; s++) {
733 	    if (*s == '/')
734 		simplename = s+1;
735 	}
736 	/* find a backup name that is not the same file */
737 	while (stat(bakname,&filestat) >= 0 &&
738 		to_device == filestat.st_dev && to_inode == filestat.st_ino) {
739 	    for (s=simplename; *s && !islower(*s); s++) ;
740 	    if (*s)
741 		*s = toupper(*s);
742 	    else
743 		Strcpy(simplename, simplename+1);
744 	}
745 	while (unlink(bakname) >= 0) ;	/* while() is for benefit of Eunice */
746 #ifdef DEBUGGING
747 	if (debug & 4)
748 	    say("Moving %s to %s.\n",to,bakname);
749 #endif
750 	if (link(to,bakname) < 0) {
751 	    say("patch: can't backup %s, output is in %s\n",
752 		to,from);
753 	    return;
754 	}
755 	while (unlink(to) >= 0) ;
756     }
757 #ifdef DEBUGGING
758     if (debug & 4)
759 	say("Moving %s to %s.\n",from,to);
760 #endif
761     if (link(from,to) < 0) {		/* different file system? */
762 	int tofd;
763 
764 	tofd = creat(to,0666);
765 	if (tofd < 0) {
766 	    say("patch: can't create %s, output is in %s.\n",
767 	      to, from);
768 	    return;
769 	}
770 	fromfd = open(from,0);
771 	if (fromfd < 0)
772 	    fatal("patch: internal error, can't reopen %s\n",from);
773 	while ((i=read(fromfd,buf,sizeof buf)) > 0)
774 	    if (write(tofd,buf,i) != i)
775 		fatal("patch: write failed\n");
776 	Close(fromfd);
777 	Close(tofd);
778     }
779     Unlink(from);
780 }
781 
782 copy_file(from,to)
783 char *from, *to;
784 {
785     int tofd;
786     int fromfd;
787     register int i;
788 
789     tofd = creat(to,0666);
790     if (tofd < 0)
791 	fatal("patch: can't create %s.\n", to);
792     fromfd = open(from,0);
793     if (fromfd < 0)
794 	fatal("patch: internal error, can't reopen %s\n",from);
795     while ((i=read(fromfd,buf,sizeof buf)) > 0)
796 	if (write(tofd,buf,i) != i)
797 	    fatal("patch: write (%s) failed\n", to);
798     Close(fromfd);
799     Close(tofd);
800 }
801 
802 copy_till(lastline)
803 register LINENUM lastline;
804 {
805     if (last_frozen_line > lastline)
806 	say("patch: misordered hunks! output will be garbled.\n");
807     while (last_frozen_line < lastline) {
808 	dump_line(++last_frozen_line);
809     }
810 }
811 
812 spew_output()
813 {
814     copy_till(input_lines);		/* dump remainder of file */
815     Fclose(ofp);
816     ofp = Nullfp;
817 }
818 
819 dump_line(line)
820 LINENUM line;
821 {
822     register char *s;
823 
824     for (s=ifetch(line,0); putc(*s,ofp) != '\n'; s++) ;
825 }
826 
827 /* does the patch pattern match at line base+offset? */
828 
829 bool
830 patch_match(base,offset)
831 LINENUM base;
832 LINENUM offset;
833 {
834     register LINENUM pline;
835     register LINENUM iline;
836     register LINENUM pat_lines = pch_ptrn_lines();
837 
838     for (pline = 1, iline=base+offset; pline <= pat_lines; pline++,iline++) {
839 	if (canonicalize) {
840 	    if (!similar(ifetch(iline,(offset >= 0)),
841 			 pfetch(pline),
842 			 pch_line_len(pline) ))
843 		return FALSE;
844 	}
845 	else if (strnNE(ifetch(iline,(offset >= 0)),
846 		   pfetch(pline),
847 		   pch_line_len(pline) ))
848 	    return FALSE;
849     }
850     return TRUE;
851 }
852 
853 /* match two lines with canonicalized white space */
854 
855 bool
856 similar(a,b,len)
857 register char *a, *b;
858 register int len;
859 {
860     while (len) {
861 	if (isspace(*b)) {		/* whitespace (or \n) to match? */
862 	    if (!isspace(*a))		/* no corresponding whitespace? */
863 		return FALSE;
864 	    while (len && isspace(*b) && *b != '\n')
865 		b++,len--;		/* skip pattern whitespace */
866 	    while (isspace(*a) && *a != '\n')
867 		a++;			/* skip target whitespace */
868 	    if (*a == '\n' || *b == '\n')
869 		return (*a == *b);	/* should end in sync */
870 	}
871 	else if (*a++ != *b++)		/* match non-whitespace chars */
872 	    return FALSE;
873 	else
874 	    len--;			/* probably not necessary */
875     }
876     return TRUE;			/* actually, this is not reached */
877 					/* since there is always a \n */
878 }
879 
880 /* input file with indexable lines abstract type */
881 
882 bool using_plan_a = TRUE;
883 static long i_size;			/* size of the input file */
884 static char *i_womp;			/* plan a buffer for entire file */
885 static char **i_ptr;			/* pointers to lines in i_womp */
886 
887 static int tifd = -1;			/* plan b virtual string array */
888 static char *tibuf[2];			/* plan b buffers */
889 static LINENUM tiline[2] = {-1,-1};	/* 1st line in each buffer */
890 static LINENUM lines_per_buf;		/* how many lines per buffer */
891 static int tireclen;			/* length of records in tmp file */
892 
893 re_input()
894 {
895     if (using_plan_a) {
896 	i_size = 0;
897 	/*NOSTRICT*/
898 	if (i_ptr != Null(char**))
899 	    free((char *)i_ptr);
900 	if (i_womp != Nullch)
901 	    free(i_womp);
902 	i_womp = Nullch;
903 	i_ptr = Null(char **);
904     }
905     else {
906 	using_plan_a = TRUE;		/* maybe the next one is smaller */
907 	Close(tifd);
908 	tifd = -1;
909 	free(tibuf[0]);
910 	free(tibuf[1]);
911 	tibuf[0] = tibuf[1] = Nullch;
912 	tiline[0] = tiline[1] = -1;
913 	tireclen = 0;
914     }
915 }
916 
917 scan_input(filename)
918 char *filename;
919 {
920     bool plan_a();
921 
922     if (!plan_a(filename))
923 	plan_b(filename);
924 }
925 
926 /* try keeping everything in memory */
927 
928 bool
929 plan_a(filename)
930 char *filename;
931 {
932     int ifd;
933     register char *s;
934     register LINENUM iline;
935 
936     if (stat(filename,&filestat) < 0) {
937 	Sprintf(buf,"RCS/%s%s",filename,RCSSUFFIX);
938 	if (stat(buf,&filestat) >= 0 || stat(buf+4,&filestat) >= 0) {
939 	    Sprintf(buf,CHECKOUT,filename);
940 	    if (verbose)
941 		say("Can't find %s--attempting to check it out from RCS.\n",
942 		    filename);
943 	    if (system(buf) || stat(filename,&filestat))
944 		fatal("Can't check out %s.\n",filename);
945 	}
946 	else {
947 	    Sprintf(buf,"SCCS/%s%s",SCCSPREFIX,filename);
948 	    if (stat(buf,&filestat) >= 0 || stat(buf+5,&filestat) >= 0) {
949 		Sprintf(buf,GET,filename);
950 		if (verbose)
951 		    say("Can't find %s--attempting to get it from SCCS.\n",
952 			filename);
953 		if (system(buf) || stat(filename,&filestat))
954 		    fatal("Can't get %s.\n",filename);
955 	    }
956 	    else
957 		fatal("Can't find %s.\n",filename);
958 	}
959     }
960     if ((filestat.st_mode & S_IFMT) & ~S_IFREG)
961 	fatal("%s is not a normal file--can't patch.\n",filename);
962     i_size = filestat.st_size;
963     /*NOSTRICT*/
964     i_womp = malloc((MEM)(i_size+2));
965     if (i_womp == Nullch)
966 	return FALSE;
967     if ((ifd = open(filename,0)) < 0)
968 	fatal("Can't open file %s\n",filename);
969     /*NOSTRICT*/
970     if (read(ifd,i_womp,(int)i_size) != i_size) {
971 	Close(ifd);
972 	free(i_womp);
973 	return FALSE;
974     }
975     Close(ifd);
976     if (i_womp[i_size-1] != '\n')
977 	i_womp[i_size++] = '\n';
978     i_womp[i_size] = '\0';
979 
980     /* count the lines in the buffer so we know how many pointers we need */
981 
982     iline = 0;
983     for (s=i_womp; *s; s++) {
984 	if (*s == '\n')
985 	    iline++;
986     }
987     /*NOSTRICT*/
988     i_ptr = (char **)malloc((MEM)((iline + 2) * sizeof(char *)));
989     if (i_ptr == Null(char **)) {	/* shucks, it was a near thing */
990 	free((char *)i_womp);
991 	return FALSE;
992     }
993 
994     /* now scan the buffer and build pointer array */
995 
996     iline = 1;
997     i_ptr[iline] = i_womp;
998     for (s=i_womp; *s; s++) {
999 	if (*s == '\n')
1000 	    i_ptr[++iline] = s+1;	/* these are NOT null terminated */
1001     }
1002     input_lines = iline - 1;
1003 
1004     /* now check for revision, if any */
1005 
1006     if (revision != Nullch) {
1007 	if (!rev_in_string(i_womp)) {
1008 	    ask("This file doesn't appear to be the %s version--patch anyway? [n] ",
1009 		revision);
1010 	    if (*buf != 'y')
1011 		fatal("Aborted.\n");
1012 	}
1013 	else if (verbose)
1014 	    say("Good.  This file appears to be the %s version.\n",
1015 		revision);
1016     }
1017     return TRUE;			/* plan a will work */
1018 }
1019 
1020 /* keep (virtually) nothing in memory */
1021 
1022 plan_b(filename)
1023 char *filename;
1024 {
1025     FILE *ifp;
1026     register int i = 0;
1027     register int maxlen = 1;
1028     bool found_revision = (revision == Nullch);
1029 
1030     using_plan_a = FALSE;
1031     if ((ifp = fopen(filename,"r")) == Nullfp)
1032 	fatal("Can't open file %s\n",filename);
1033     if ((tifd = creat(TMPINNAME,0666)) < 0)
1034 	fatal("Can't open file %s\n",TMPINNAME);
1035     while (fgets(buf,sizeof buf, ifp) != Nullch) {
1036 	if (revision != Nullch && !found_revision && rev_in_string(buf))
1037 	    found_revision = TRUE;
1038 	if ((i = strlen(buf)) > maxlen)
1039 	    maxlen = i;			/* find longest line */
1040     }
1041     if (revision != Nullch) {
1042 	if (!found_revision) {
1043 	    ask("This file doesn't appear to be the %s version--patch anyway? [n] ",
1044 		revision);
1045 	    if (*buf != 'y')
1046 		fatal("Aborted.\n");
1047 	}
1048 	else if (verbose)
1049 	    say("Good.  This file appears to be the %s version.\n",
1050 		revision);
1051     }
1052     Fseek(ifp,0L,0);		/* rewind file */
1053     lines_per_buf = BUFFERSIZE / maxlen;
1054     tireclen = maxlen;
1055     tibuf[0] = malloc((MEM)(BUFFERSIZE + 1));
1056     tibuf[1] = malloc((MEM)(BUFFERSIZE + 1));
1057     if (tibuf[1] == Nullch)
1058 	fatal("Can't seem to get enough memory.\n");
1059     for (i=1; ; i++) {
1060 	if (! (i % lines_per_buf))	/* new block */
1061 	    if (write(tifd,tibuf[0],BUFFERSIZE) < BUFFERSIZE)
1062 		fatal("patch: can't write temp file.\n");
1063 	if (fgets(tibuf[0] + maxlen * (i%lines_per_buf), maxlen + 1, ifp)
1064 	  == Nullch) {
1065 	    input_lines = i - 1;
1066 	    if (i % lines_per_buf)
1067 		if (write(tifd,tibuf[0],BUFFERSIZE) < BUFFERSIZE)
1068 		    fatal("patch: can't write temp file.\n");
1069 	    break;
1070 	}
1071     }
1072     Fclose(ifp);
1073     Close(tifd);
1074     if ((tifd = open(TMPINNAME,0)) < 0) {
1075 	fatal("Can't reopen file %s\n",TMPINNAME);
1076     }
1077 }
1078 
1079 /* fetch a line from the input file, \n terminated, not necessarily \0 */
1080 char *
1081 ifetch(line,whichbuf)
1082 register LINENUM line;
1083 int whichbuf;				/* ignored when file in memory */
1084 {
1085     if (line < 1 || line > input_lines)
1086 	return "";
1087     if (using_plan_a)
1088 	return i_ptr[line];
1089     else {
1090 	LINENUM offline = line % lines_per_buf;
1091 	LINENUM baseline = line - offline;
1092 
1093 	if (tiline[0] == baseline)
1094 	    whichbuf = 0;
1095 	else if (tiline[1] == baseline)
1096 	    whichbuf = 1;
1097 	else {
1098 	    tiline[whichbuf] = baseline;
1099 	    Lseek(tifd,(long)baseline / lines_per_buf * BUFFERSIZE,0);
1100 	    if (read(tifd,tibuf[whichbuf],BUFFERSIZE) < 0)
1101 		fatal("Error reading tmp file %s.\n",TMPINNAME);
1102 	}
1103 	return tibuf[whichbuf] + (tireclen*offline);
1104     }
1105 }
1106 
1107 /* patch abstract type */
1108 
1109 static long p_filesize;			/* size of the patch file */
1110 static LINENUM p_first;			/* 1st line number */
1111 static LINENUM p_newfirst;		/* 1st line number of replacement */
1112 static LINENUM p_ptrn_lines;		/* # lines in pattern */
1113 static LINENUM p_repl_lines;		/* # lines in replacement text */
1114 static LINENUM p_end = -1;		/* last line in hunk */
1115 static LINENUM p_max;			/* max allowed value of p_end */
1116 static LINENUM p_context = 3;		/* # of context lines */
1117 static LINENUM p_input_line = 0;	/* current line # from patch file */
1118 static char *p_line[MAXHUNKSIZE];	/* the text of the hunk */
1119 static char p_char[MAXHUNKSIZE];	/* +, -, and ! */
1120 static int p_len[MAXHUNKSIZE];		/* length of each line */
1121 static int p_indent;			/* indent to patch */
1122 static long p_base;			/* where to intuit this time */
1123 static long p_start;			/* where intuit found a patch */
1124 
1125 re_patch()
1126 {
1127     p_first = (LINENUM)0;
1128     p_newfirst = (LINENUM)0;
1129     p_ptrn_lines = (LINENUM)0;
1130     p_repl_lines = (LINENUM)0;
1131     p_end = (LINENUM)-1;
1132     p_max = (LINENUM)0;
1133     p_indent = 0;
1134 }
1135 
1136 open_patch_file(filename)
1137 char *filename;
1138 {
1139     if (filename == Nullch || !*filename || strEQ(filename,"-")) {
1140 	pfp = fopen(TMPPATNAME,"w");
1141 	if (pfp == Nullfp)
1142 	    fatal("patch: can't create %s.\n",TMPPATNAME);
1143 	while (fgets(buf,sizeof buf,stdin) != NULL)
1144 	    fputs(buf,pfp);
1145 	Fclose(pfp);
1146 	filename = TMPPATNAME;
1147     }
1148     pfp = fopen(filename,"r");
1149     if (pfp == Nullfp)
1150 	fatal("patch file %s not found\n",filename);
1151     Fstat(fileno(pfp), &filestat);
1152     p_filesize = filestat.st_size;
1153     next_intuit_at(0L);			/* start at the beginning */
1154 }
1155 
1156 bool
1157 there_is_another_patch()
1158 {
1159     bool no_input_file = (filearg[0] == Nullch);
1160 
1161     if (p_base != 0L && p_base >= p_filesize) {
1162 	if (verbose)
1163 	    say("done\n");
1164 	return FALSE;
1165     }
1166     if (verbose)
1167 	say("Hmm...");
1168     diff_type = intuit_diff_type();
1169     if (!diff_type) {
1170 	if (p_base != 0L) {
1171 	    if (verbose)
1172 		say("  Ignoring the trailing garbage.\ndone\n");
1173 	}
1174 	else
1175 	    say("  I can't seem to find a patch in there anywhere.\n");
1176 	return FALSE;
1177     }
1178     if (verbose)
1179 	say("  %sooks like %s to me...\n",
1180 	    (p_base == 0L ? "L" : "The next patch l"),
1181 	    diff_type == CONTEXT_DIFF ? "a context diff" :
1182 	    diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" :
1183 	    diff_type == NORMAL_DIFF ? "a normal diff" :
1184 	    "an ed script" );
1185     if (p_indent && verbose)
1186 	say("(Patch is indented %d space%s.)\n",p_indent,p_indent==1?"":"s");
1187     skip_to(p_start);
1188     if (no_input_file) {
1189 	while (filearg[0] == Nullch) {
1190 	    ask("File to patch: ");
1191 	    filearg[0] = fetchname(buf);
1192 	}
1193 	if (verbose) {
1194 	    say("Patching file %s...\n",filearg[0]);
1195 	}
1196     }
1197     return TRUE;
1198 }
1199 
1200 intuit_diff_type()
1201 {
1202     long this_line = 0;
1203     long previous_line;
1204     long first_command_line = -1;
1205     bool last_line_was_command = FALSE;
1206     bool this_line_is_command = FALSE;
1207     bool last_line_was_stars = FALSE;
1208     bool this_line_is_stars = FALSE;
1209     register int indent;
1210     register char *s, *t;
1211     char *oldname = Nullch;
1212     char *newname = Nullch;
1213     bool no_filearg = (filearg[0] == Nullch);
1214 
1215     Fseek(pfp,p_base,0);
1216     for (;;) {
1217 	previous_line = this_line;
1218 	last_line_was_command = this_line_is_command;
1219 	last_line_was_stars = this_line_is_stars;
1220 	this_line = ftell(pfp);
1221 	indent = 0;
1222 	if (fgets(buf,sizeof buf,pfp) == Nullch) {
1223 	    if (first_command_line >= 0L) {
1224 					/* nothing but deletes!? */
1225 		p_start = first_command_line;
1226 		return ED_DIFF;
1227 	    }
1228 	    else {
1229 		p_start = this_line;
1230 		return 0;
1231 	    }
1232 	}
1233 	for (s = buf; *s == ' ' || *s == '\t'; s++) {
1234 	    if (*s == '\t')
1235 		indent += 8 - (indent % 8);
1236 	    else
1237 		indent++;
1238 	}
1239 	for (t=s; isdigit(*t) || *t == ','; t++) ;
1240 	this_line_is_command = (isdigit(*s) &&
1241 	  (*t == 'd' || *t == 'c' || *t == 'a') );
1242 	if (first_command_line < 0L && this_line_is_command) {
1243 	    first_command_line = this_line;
1244 	    p_indent = indent;		/* assume this for now */
1245 	}
1246 	if (strnEQ(s,"*** ",4))
1247 	    oldname = fetchname(s+4);
1248 	else if (strnEQ(s,"--- ",4)) {
1249 	    newname = fetchname(s+4);
1250 	    if (no_filearg) {
1251 		if (oldname && newname) {
1252 		    if (strlen(oldname) < strlen(newname))
1253 			filearg[0] = oldname;
1254 		    else
1255 			filearg[0] = newname;
1256 		}
1257 		else if (oldname)
1258 		    filearg[0] = oldname;
1259 		else if (newname)
1260 		    filearg[0] = newname;
1261 	    }
1262 	}
1263 	else if (strnEQ(s,"Index:",6)) {
1264 	    if (no_filearg)
1265 		filearg[0] = fetchname(s+6);
1266 					/* this filearg might get limboed */
1267 	}
1268 	else if (strnEQ(s,"Prereq:",7)) {
1269 	    for (t=s+7; isspace(*t); t++) ;
1270 	    revision = savestr(t);
1271 	    for (t=revision; *t && !isspace(*t); t++) ;
1272 	    *t = '\0';
1273 	    if (!*revision) {
1274 		free(revision);
1275 		revision = Nullch;
1276 	    }
1277 	}
1278 	if ((!diff_type || diff_type == ED_DIFF) &&
1279 	  first_command_line >= 0L &&
1280 	  strEQ(s,".\n") ) {
1281 	    p_indent = indent;
1282 	    p_start = first_command_line;
1283 	    return ED_DIFF;
1284 	}
1285 	this_line_is_stars = strnEQ(s,"********",8);
1286 	if ((!diff_type || diff_type == CONTEXT_DIFF) && last_line_was_stars &&
1287 		 strnEQ(s,"*** ",4)) {
1288 	    /* if this is a new context diff the character just before */
1289 	    /* the newline is a '*'. */
1290 	    while (*s != '\n')
1291 		s++;
1292 	    p_indent = indent;
1293 	    p_start = previous_line;
1294 	    return (*(s-1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF);
1295 	}
1296 	if ((!diff_type || diff_type == NORMAL_DIFF) &&
1297 	  last_line_was_command &&
1298 	  (strnEQ(s,"< ",2) || strnEQ(s,"> ",2)) ) {
1299 	    p_start = previous_line;
1300 	    p_indent = indent;
1301 	    return NORMAL_DIFF;
1302 	}
1303     }
1304 }
1305 
1306 char *
1307 fetchname(at)
1308 char *at;
1309 {
1310     char *s = savestr(at);
1311     char *name;
1312     register char *t;
1313     char tmpbuf[200];
1314 
1315     for (t=s; isspace(*t); t++) ;
1316     name = t;
1317     for (; *t && !isspace(*t); t++)
1318 	if (!usepath)
1319 	    if (*t == '/')
1320 		name = t+1;
1321     *t = '\0';
1322     name = savestr(name);
1323     Sprintf(tmpbuf,"RCS/%s",name);
1324     free(s);
1325     if (stat(name,&filestat) < 0) {
1326 	Strcat(tmpbuf,RCSSUFFIX);
1327 	if (stat(tmpbuf,&filestat) < 0 && stat(tmpbuf+4,&filestat) < 0) {
1328 	    Sprintf(tmpbuf,"SCCS/%s%s",SCCSPREFIX,name);
1329 	    if (stat(tmpbuf,&filestat) < 0 && stat(tmpbuf+5,&filestat) < 0) {
1330 		free(name);
1331 		name = Nullch;
1332 	    }
1333 	}
1334     }
1335     return name;
1336 }
1337 
1338 next_intuit_at(file_pos)
1339 long file_pos;
1340 {
1341     p_base = file_pos;
1342 }
1343 
1344 skip_to(file_pos)
1345 long file_pos;
1346 {
1347     char *ret;
1348 
1349     assert(p_base <= file_pos);
1350     if (verbose && p_base < file_pos) {
1351 	Fseek(pfp,p_base,0);
1352 	say("The text leading up to this was:\n--------------------------\n");
1353 	while (ftell(pfp) < file_pos) {
1354 	    ret = fgets(buf,sizeof buf,pfp);
1355 	    assert(ret != Nullch);
1356 	    say("|%s",buf);
1357 	}
1358 	say("--------------------------\n");
1359     }
1360     else
1361 	Fseek(pfp,file_pos,0);
1362 }
1363 
1364 bool
1365 another_hunk()
1366 {
1367     register char *s;
1368     char *ret;
1369     register int context = 0;
1370 
1371     while (p_end >= 0) {
1372 	free(p_line[p_end--]);
1373     }
1374     assert(p_end == -1);
1375 
1376     p_max = MAXHUNKSIZE;		/* gets reduced when --- found */
1377     if (diff_type == CONTEXT_DIFF) {
1378 	long line_beginning = ftell(pfp);
1379 	LINENUM repl_beginning = 0;
1380 
1381 	ret = pgets(buf,sizeof buf, pfp);
1382 	if (ret == Nullch || strnNE(buf,"********",8)) {
1383 	    next_intuit_at(line_beginning);
1384 	    return FALSE;
1385 	}
1386 	p_context = 100;
1387 	while (p_end < p_max) {
1388 	    ret = pgets(buf,sizeof buf, pfp);
1389 	    if (ret == Nullch) {
1390 		if (p_max - p_end < 4)
1391 		    Strcpy(buf,"  \n");	/* assume blank lines got chopped */
1392 		else
1393 		    fatal("Unexpected end of file in patch.\n");
1394 	    }
1395 	    p_input_line++;
1396 	    if (strnEQ(buf,"********",8))
1397 		fatal("Unexpected end of hunk at line %d.\n",
1398 		    p_input_line);
1399 	    p_char[++p_end] = *buf;
1400 	    switch (*buf) {
1401 	    case '*':
1402 		if (p_end != 0)
1403 		    fatal("Unexpected *** at line %d: %s", p_input_line, buf);
1404 		context = 0;
1405 		p_line[p_end] = savestr(buf);
1406 		for (s=buf; *s && !isdigit(*s); s++) ;
1407 		p_first = (LINENUM) atol(s);
1408 		while (isdigit(*s)) s++;
1409 		for (; *s && !isdigit(*s); s++) ;
1410 		p_ptrn_lines = ((LINENUM)atol(s)) - p_first + 1;
1411 		break;
1412 	    case '-':
1413 		if (buf[1] == '-') {
1414 		    if (p_end != p_ptrn_lines + 1 &&
1415 			p_end != p_ptrn_lines + 2)
1416 			fatal("Unexpected --- at line %d: %s",
1417 			    p_input_line,buf);
1418 		    repl_beginning = p_end;
1419 		    context = 0;
1420 		    p_line[p_end] = savestr(buf);
1421 		    p_char[p_end] = '=';
1422 		    for (s=buf; *s && !isdigit(*s); s++) ;
1423 		    p_newfirst = (LINENUM) atol(s);
1424 		    while (isdigit(*s)) s++;
1425 		    for (; *s && !isdigit(*s); s++) ;
1426 		    p_max = ((LINENUM)atol(s)) - p_newfirst + 1 + p_end;
1427 		    break;
1428 		}
1429 		/* FALL THROUGH */
1430 	    case '+': case '!':
1431 		if (context > 0) {
1432 		    if (context < p_context)
1433 			p_context = context;
1434 		    context = -100;
1435 		}
1436 		p_line[p_end] = savestr(buf+2);
1437 		break;
1438 	    case '\t': case '\n':	/* assume the 2 spaces got eaten */
1439 		p_line[p_end] = savestr(buf);
1440 		if (p_end != p_ptrn_lines + 1) {
1441 		    context++;
1442 		    p_char[p_end] = ' ';
1443 		}
1444 		break;
1445 	    case ' ':
1446 		context++;
1447 		p_line[p_end] = savestr(buf+2);
1448 		break;
1449 	    default:
1450 		fatal("Malformed patch at line %d: %s",p_input_line,buf);
1451 	    }
1452 	    p_len[p_end] = 0;
1453 	    if (p_line[p_end] != 0)
1454 		p_len[p_end] = strlen(p_line[p_end]);
1455 					/* for strncmp() so we do not have */
1456 					/* to assume null termination */
1457 	}
1458 	if (p_end >=0 && !p_ptrn_lines)
1459 	    fatal("No --- found in patch at line %d\n", pch_hunk_beg());
1460 	p_repl_lines = p_end - repl_beginning;
1461     }
1462     else if (diff_type == NEW_CONTEXT_DIFF) {
1463 	long line_beginning = ftell(pfp);
1464 	LINENUM repl_beginning = 0;
1465 	LINENUM fillcnt = 0;
1466 	LINENUM fillsrc;
1467 	LINENUM filldst;
1468 
1469 	ret = pgets(buf,sizeof buf, pfp);
1470 	if (ret == Nullch || strnNE(buf,"********",8)) {
1471 	    next_intuit_at(line_beginning);
1472 	    return FALSE;
1473 	}
1474 	p_context = 0;
1475 	while (p_end < p_max) {
1476 	    line_beginning = ftell(pfp);
1477 	    ret = pgets(buf,sizeof buf, pfp);
1478 	    if (ret == Nullch) {
1479 		if (p_max - p_end < 4)
1480 		    Strcpy(buf,"  \n");	/* assume blank lines got chopped */
1481 		else if (p_end == repl_beginning) {
1482 		    /* redundant 'new' context lines were omitted - set up */
1483 		    /* to fill them in from the the old file's context */
1484 		    fillsrc = 1;
1485 		    filldst = p_end + 1;
1486 		    fillcnt = p_max - repl_beginning;
1487 		    p_end = p_max;
1488 		    break;
1489 		} else
1490 		    fatal("Unexpected end of file in patch.\n");
1491 	    }
1492 	    p_input_line++;
1493 	    p_char[++p_end] = *buf;
1494 	    switch (*buf) {
1495 	    case '*':	/* another hunk */
1496 	    case 'd':	/* another hunk in a different file */
1497 	    case 'B':	/* ditto */
1498 	    case 'C':	/* ditto */
1499 	    case 'F':	/* ditto */
1500 	    case 'O':	/* ditto */
1501 		if (strnEQ(buf,"********",8) ||
1502 		    strnEQ(buf,"diff",4) ||
1503 	    	    strnEQ(buf,"Binary files ",13) ||
1504 	    	    strnEQ(buf,"Files ",6) ||
1505 		    strnEQ(buf,"Common subdirectories: ",23) ||
1506 		    strnEQ(buf,"Only in ",8)) {
1507 		    if (p_end != repl_beginning + 1)
1508 			fatal("Unexpected end of hunk at line %d.\n",
1509 				p_input_line);
1510 		    /* redundant 'new' context lines were omitted - set up */
1511 		    /* to fill them in from the the old file's context */
1512 		    fillsrc = 1;
1513 		    filldst = p_end;
1514 		    fillcnt = p_max - repl_beginning;
1515 		    p_end = p_max;
1516 		    Fseek(pfp, line_beginning, 0); /* backup the diff input */
1517 		    break;
1518 		}
1519 		if (p_end != 0)
1520 		    fatal("Unexpected *** at line %d: %s", p_input_line, buf);
1521 		context = 0;
1522 		p_line[p_end] = savestr(buf);
1523 		for (s=buf; *s && !isdigit(*s); s++) ;
1524 		p_first = (LINENUM) atol(s);
1525 		while (isdigit(*s)) s++;
1526 		for (; *s && !isdigit(*s); s++) ;
1527 		p_ptrn_lines = ((LINENUM)atol(s)) - p_first + 1;
1528 		break;
1529 	    case '-':
1530 		if (buf[1] == '-') {
1531 		    if (p_end != p_ptrn_lines + 1) {
1532 			if (p_end == 1) {
1533 			    /* `old' lines were omitted - set up to fill them */
1534 			    /* in from 'new' context lines. */
1535 			    p_end = p_ptrn_lines + 1;
1536 			    fillsrc = p_end + 1;
1537 			    filldst = 1;
1538 			    fillcnt = p_ptrn_lines;
1539 			} else
1540 			    fatal("Unexpected --- at line %d: %s",
1541 				p_input_line,buf);
1542 		    }
1543 		    repl_beginning = p_end;
1544 		    p_line[p_end] = savestr(buf);
1545 		    p_char[p_end] = '=';
1546 		    for (s=buf; *s && !isdigit(*s); s++) ;
1547 		    p_newfirst = (LINENUM) atol(s);
1548 		    while (isdigit(*s)) s++;
1549 		    for (; *s && !isdigit(*s); s++) ;
1550 		    p_max = ((LINENUM)atol(s)) - p_newfirst + 1 + p_end;
1551 		    break;
1552 		}
1553 		/* FALL THROUGH */
1554 	    case '+': case '!':
1555 		if (context > 0 && p_context == 0) {
1556 		    p_context = context;
1557 		}
1558 		p_line[p_end] = savestr(buf+2);
1559 		break;
1560 	    case '\t': case '\n':	/* assume the 2 spaces got eaten */
1561 		p_line[p_end] = savestr(buf);
1562 		if (p_end != p_ptrn_lines + 1) {
1563 		    context++;
1564 		    p_char[p_end] = ' ';
1565 		}
1566 		break;
1567 	    case ' ':
1568 		context++;
1569 		p_line[p_end] = savestr(buf+2);
1570 		break;
1571 	    default:
1572 		fatal("Malformed patch at line %d: %s",p_input_line,buf);
1573 	    }
1574 	    p_len[p_end] = 0;
1575 	    if (p_line[p_end] != 0)
1576 		p_len[p_end] = strlen(p_line[p_end]);
1577 					/* for strncmp() so we do not have */
1578 					/* to assume null termination */
1579 	}
1580 	if (p_end >=0 && !p_ptrn_lines)
1581 	    fatal("No --- found in patch at line %d\n", pch_hunk_beg());
1582 
1583 	/* if there were omitted context lines, fill them in */
1584 	if (fillcnt) {
1585 	    while (fillcnt-- > 0) {
1586 		while (p_char[fillsrc] != ' ')
1587 		    fillsrc++;
1588 		p_line[filldst] = p_line[fillsrc];
1589 		p_char[filldst] = p_char[fillsrc];
1590 		p_len[filldst] = p_len[fillsrc];
1591 		fillsrc++; filldst++;
1592 	    }
1593 	}
1594 	p_repl_lines = p_end - repl_beginning;
1595     }
1596     else {				/* normal diff--fake it up */
1597 	char hunk_type;
1598 	register int i;
1599 	LINENUM min, max;
1600 	long line_beginning = ftell(pfp);
1601 
1602 	p_context = 0;
1603 	ret = pgets(buf,sizeof buf, pfp);
1604 	p_input_line++;
1605 	if (ret == Nullch || !isdigit(*buf)) {
1606 	    next_intuit_at(line_beginning);
1607 	    return FALSE;
1608 	}
1609 	p_first = (LINENUM)atol(buf);
1610 	for (s=buf; isdigit(*s); s++) ;
1611 	if (*s == ',') {
1612 	    p_ptrn_lines = (LINENUM)atol(++s) - p_first + 1;
1613 	    while (isdigit(*s)) s++;
1614 	}
1615 	else
1616 	    p_ptrn_lines = (*s != 'a');
1617 	hunk_type = *s;
1618 	if (hunk_type == 'a')
1619 	    p_first++;			/* do append rather than insert */
1620 	min = (LINENUM)atol(++s);
1621 	for (; isdigit(*s); s++) ;
1622 	if (*s == ',')
1623 	    max = (LINENUM)atol(++s);
1624 	else
1625 	    max = min;
1626 	if (hunk_type == 'd')
1627 	    min++;
1628 	p_end = p_ptrn_lines + 1 + max - min + 1;
1629 	p_newfirst = min;
1630 	p_repl_lines = max - min + 1;
1631 	Sprintf(buf,"*** %d,%d\n", p_first, p_first + p_ptrn_lines - 1);
1632 	p_line[0] = savestr(buf);
1633 	p_char[0] = '*';
1634 	for (i=1; i<=p_ptrn_lines; i++) {
1635 	    ret = pgets(buf,sizeof buf, pfp);
1636 	    p_input_line++;
1637 	    if (ret == Nullch)
1638 		fatal("Unexpected end of file in patch at line %d.\n",
1639 		  p_input_line);
1640 	    if (*buf != '<')
1641 		fatal("< expected at line %d of patch.\n", p_input_line);
1642 	    p_line[i] = savestr(buf+2);
1643 	    p_len[i] = 0;
1644 	    if (p_line[i] != 0)
1645 		p_len[i] = strlen(p_line[i]);
1646 	    p_char[i] = '-';
1647 	}
1648 	if (hunk_type == 'c') {
1649 	    ret = pgets(buf,sizeof buf, pfp);
1650 	    p_input_line++;
1651 	    if (ret == Nullch)
1652 		fatal("Unexpected end of file in patch at line %d.\n",
1653 		    p_input_line);
1654 	    if (*buf != '-')
1655 		fatal("--- expected at line %d of patch.\n", p_input_line);
1656 	}
1657 	Sprintf(buf,"--- %d,%d\n",min,max);
1658 	p_line[i] = savestr(buf);
1659 	p_char[i] = '=';
1660 	for (i++; i<=p_end; i++) {
1661 	    ret = pgets(buf,sizeof buf, pfp);
1662 	    p_input_line++;
1663 	    if (ret == Nullch)
1664 		fatal("Unexpected end of file in patch at line %d.\n",
1665 		    p_input_line);
1666 	    if (*buf != '>')
1667 		fatal("> expected at line %d of patch.\n", p_input_line);
1668 	    p_line[i] = savestr(buf+2);
1669 	    p_len[i] = 0;
1670 	    if (p_line[i] != 0)
1671 		p_len[i] = strlen(p_line[i]);
1672 	    p_char[i] = '+';
1673 	}
1674     }
1675     if (reverse)			/* backwards patch? */
1676 	pch_swap();
1677 #ifdef DEBUGGING
1678     if (debug & 2) {
1679 	int i;
1680 	char special;
1681 
1682 	for (i=0; i <= p_end; i++) {
1683 	    if (i == p_ptrn_lines)
1684 		special = '^';
1685 	    else
1686 		special = ' ';
1687 	    printf("%3d %c %c %s",i,p_char[i],special,p_line[i]);
1688 	}
1689     }
1690 #endif
1691     return TRUE;
1692 }
1693 
1694 char *
1695 pgets(bf,sz,fp)
1696 char *bf;
1697 int sz;
1698 FILE *fp;
1699 {
1700     char *ret = fgets(bf,sz,fp);
1701     register char *s;
1702     register int indent = 0;
1703 
1704     if (p_indent && ret != Nullch) {
1705 	for (s=buf; indent < p_indent && (*s == ' ' || *s == '\t'); s++) {
1706 	    if (*s == '\t')
1707 		indent += 8 - (indent % 7);
1708 	    else
1709 		indent++;
1710 	}
1711 	if (buf != s)
1712 	    Strcpy(buf,s);
1713     }
1714     return ret;
1715 }
1716 
1717 pch_swap()
1718 {
1719     char *tp_line[MAXHUNKSIZE];		/* the text of the hunk */
1720     char tp_char[MAXHUNKSIZE];		/* +, -, and ! */
1721     int tp_len[MAXHUNKSIZE];		/* length of each line */
1722     register LINENUM i, n;
1723     bool blankline = FALSE;
1724     register char *s;
1725 
1726     i = p_first;
1727     p_first = p_newfirst;
1728     p_newfirst = i;
1729 
1730     /* make a scratch copy */
1731 
1732     for (i=0; i<=p_end; i++) {
1733 	tp_line[i] = p_line[i];
1734 	tp_char[i] = p_char[i];
1735 	tp_len[i] = p_len[i];
1736     }
1737 
1738     /* now turn the new into the old */
1739 
1740     i = p_ptrn_lines + 1;
1741     if (tp_char[i] == '\n') {		/* account for possible blank line */
1742 	blankline = TRUE;
1743 	i++;
1744     }
1745     for (n=0; i <= p_end; i++,n++) {
1746 	p_line[n] = tp_line[i];
1747 	p_char[n] = tp_char[i];
1748 	if (p_char[n] == '+')
1749 	    p_char[n] = '-';
1750 	p_len[n] = tp_len[i];
1751     }
1752     if (blankline) {
1753 	i = p_ptrn_lines + 1;
1754 	p_line[n] = tp_line[i];
1755 	p_char[n] = tp_char[i];
1756 	p_len[n] = tp_len[i];
1757 	n++;
1758     }
1759     assert(p_char[0] == '=');
1760     p_char[0] = '*';
1761     for (s=p_line[0]; *s; s++)
1762 	if (*s == '-')
1763 	    *s = '*';
1764 
1765     /* now turn the old into the new */
1766 
1767     assert(tp_char[0] == '*');
1768     tp_char[0] = '=';
1769     for (s=tp_line[0]; *s; s++)
1770 	if (*s == '*')
1771 	    *s = '-';
1772     for (i=0; n <= p_end; i++,n++) {
1773 	p_line[n] = tp_line[i];
1774 	p_char[n] = tp_char[i];
1775 	if (p_char[n] == '-')
1776 	    p_char[n] = '+';
1777 	p_len[n] = tp_len[i];
1778     }
1779     assert(i == p_ptrn_lines + 1);
1780     i = p_ptrn_lines;
1781     p_ptrn_lines = p_repl_lines;
1782     p_repl_lines = i;
1783 }
1784 
1785 LINENUM
1786 pch_first()
1787 {
1788     return p_first;
1789 }
1790 
1791 LINENUM
1792 pch_ptrn_lines()
1793 {
1794     return p_ptrn_lines;
1795 }
1796 
1797 LINENUM
1798 pch_newfirst()
1799 {
1800     return p_newfirst;
1801 }
1802 
1803 LINENUM
1804 pch_repl_lines()
1805 {
1806     return p_repl_lines;
1807 }
1808 
1809 LINENUM
1810 pch_end()
1811 {
1812     return p_end;
1813 }
1814 
1815 LINENUM
1816 pch_context()
1817 {
1818     return p_context;
1819 }
1820 
1821 pch_line_len(line)
1822 LINENUM line;
1823 {
1824     return p_len[line];
1825 }
1826 
1827 char
1828 pch_char(line)
1829 LINENUM line;
1830 {
1831     return p_char[line];
1832 }
1833 
1834 char *
1835 pfetch(line)
1836 LINENUM line;
1837 {
1838     return p_line[line];
1839 }
1840 
1841 LINENUM
1842 pch_hunk_beg()
1843 {
1844     return p_input_line - p_end - 1;
1845 }
1846 
1847 char *
1848 savestr(s)
1849 register char *s;
1850 {
1851     register char  *rv,
1852                    *t;
1853 
1854     t = s;
1855     while (*t++);
1856     rv = malloc((MEM) (t - s));
1857     if (rv == NULL)
1858 	fatal ("patch: out of memory (savestr)\n");
1859     t = rv;
1860     while (*t++ = *s++);
1861     return rv;
1862 }
1863 
1864 my_exit(status)
1865 int status;
1866 {
1867     Unlink(TMPINNAME);
1868     Unlink(TMPOUTNAME);
1869     Unlink(TMPREJNAME);
1870     Unlink(TMPPATNAME);
1871     exit(status);
1872 }
1873 
1874 #ifdef lint
1875 
1876 /*VARARGS ARGSUSED*/
1877 say(pat) char *pat; { ; }
1878 /*VARARGS ARGSUSED*/
1879 fatal(pat) char *pat; { ; }
1880 /*VARARGS ARGSUSED*/
1881 ask(pat) char *pat; { ; }
1882 
1883 #else lint
1884 
1885 say(pat,arg1,arg2,arg3)
1886 char *pat;
1887 int arg1,arg2,arg3;
1888 {
1889     fprintf(stderr,pat,arg1,arg2,arg3);
1890     Fflush(stderr);
1891 }
1892 
1893 fatal(pat,arg1,arg2,arg3)
1894 char *pat;
1895 int arg1,arg2,arg3;
1896 {
1897     say(pat,arg1,arg2,arg3);
1898     my_exit(1);
1899 }
1900 
1901 ask(pat,arg1,arg2,arg3)
1902 char *pat;
1903 int arg1,arg2,arg3;
1904 {
1905     int ttyfd = open("/dev/tty",2);
1906     int r;
1907 
1908     say(pat,arg1,arg2,arg3);
1909     if (ttyfd >= 0) {
1910 	r = read(ttyfd, buf, sizeof buf);
1911 	Close(ttyfd);
1912     }
1913     else
1914 	r = read(2, buf, sizeof buf);
1915     if (r <= 0)
1916 	buf[0] = 0;
1917 }
1918 #endif lint
1919 
1920 bool
1921 rev_in_string(string)
1922 char *string;
1923 {
1924     register char *s;
1925     register int patlen;
1926 
1927     if (revision == Nullch)
1928 	return TRUE;
1929     patlen = strlen(revision);
1930     for (s = string; *s; s++) {
1931 	if (isspace(*s) && strnEQ(s+1,revision,patlen) &&
1932 		isspace(s[patlen+1] )) {
1933 	    return TRUE;
1934 	}
1935     }
1936     return FALSE;
1937 }
1938 
1939 set_signals()
1940 {
1941     /*NOSTRICT*/
1942     if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
1943 	Signal(SIGHUP, my_exit);
1944     /*NOSTRICT*/
1945     if (signal(SIGINT, SIG_IGN) != SIG_IGN)
1946 	Signal(SIGINT, my_exit);
1947 }
1948 
1949 ignore_signals()
1950 {
1951     /*NOSTRICT*/
1952     Signal(SIGHUP, SIG_IGN);
1953     /*NOSTRICT*/
1954     Signal(SIGINT, SIG_IGN);
1955 }
1956