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