1/* $FreeBSD$
2 *
3 * $Log: inp.c,v $
4 * Revision 2.0.1.1  88/06/03  15:06:13  lwall
5 * patch10: made a little smarter about sccs files
6 *
7 * Revision 2.0  86/09/17  15:37:02  lwall
8 * Baseline for netwide release.
9 *
10 */
11
12#include "EXTERN.h"
13#include "common.h"
14#include "util.h"
15#include "pch.h"
16#include "INTERN.h"
17#include "inp.h"
18
19/* Input-file-with-indexable-lines abstract type. */
20
21static long i_size;			/* Size of the input file */
22static char *i_womp;			/* Plan a buffer for entire file */
23static char **i_ptr;			/* Pointers to lines in i_womp */
24
25static int tifd = -1;			/* Plan b virtual string array */
26static char *tibuf[2];			/* Plan b buffers */
27static LINENUM tiline[2] = {-1, -1};	/* 1st line in each buffer */
28static LINENUM lines_per_buf;		/* How many lines per buffer */
29static int tireclen;			/* Length of records in tmp file */
30
31/*
32 * New patch--prepare to edit another file.
33 */
34void
35re_input(void)
36{
37	if (using_plan_a) {
38		i_size = 0;
39#ifndef lint
40		if (i_ptr != Null(char**))
41			free((char *)i_ptr);
42#endif
43		if (i_womp != Nullch)
44			free(i_womp);
45		i_womp = Nullch;
46		i_ptr = Null(char **);
47	} else {
48		using_plan_a = TRUE;	/* maybe the next one is smaller */
49		Close(tifd);
50		tifd = -1;
51		free(tibuf[0]);
52		free(tibuf[1]);
53		tibuf[0] = tibuf[1] = Nullch;
54		tiline[0] = tiline[1] = -1;
55		tireclen = 0;
56	}
57}
58
59/*
60 * Constuct the line index, somehow or other.
61 */
62void
63scan_input(char *filename)
64{
65	if (!plan_a(filename))
66		plan_b(filename);
67	if (verbose) {
68		say3("Patching file %s using Plan %s...\n", filename,
69		    (using_plan_a ? "A" : "B") );
70	}
71}
72
73/*
74 * Try keeping everything in memory.
75 */
76bool
77plan_a(char *filename)
78{
79	int ifd, statfailed;
80	Reg1 char *s;
81	Reg2 LINENUM iline;
82	char lbuf[INITLINELEN];
83	int output_elsewhere = strcmp(filename, outname);
84	extern int check_patch;
85
86	statfailed = stat(filename, &filestat);
87	if (statfailed && ok_to_create_file) {
88		if (verbose)
89			say2("(Creating file %s...)\n",filename);
90		if (check_patch)
91			return TRUE;
92		makedirs(filename, TRUE);
93		close(creat(filename, 0666));
94		statfailed = stat(filename, &filestat);
95	}
96	if (statfailed && check_patch) {
97		fatal2("%s not found and in check_patch mode.", filename);
98	}
99	/*
100	 * For nonexistent or read-only files, look for RCS or SCCS
101	 * versions.
102	 */
103	if (statfailed ||
104	    (! output_elsewhere &&
105	    (/* No one can write to it. */
106	    (filestat.st_mode & 0222) == 0 ||
107	    /* I can't write to it. */
108	    ((filestat.st_mode & 0022) == 0 && filestat.st_uid != myuid)))) {
109		struct stat cstat;
110		char *cs = Nullch;
111		char *filebase;
112		int pathlen;
113
114		filebase = basename(filename);
115		pathlen = filebase - filename;
116
117		/*
118		 * Put any leading path into `s'.
119		 * Leave room in lbuf for the diff command.
120		 */
121		s = lbuf + 20;
122		strncpy(s, filename, pathlen);
123
124#define try(f,a1,a2) (Sprintf(s + pathlen, f, a1, a2), stat(s, &cstat) == 0)
125		if ((try("RCS/%s%s", filebase, RCSSUFFIX) ||
126		    try("RCS/%s%s", filebase, "") ||
127		    try("%s%s", filebase, RCSSUFFIX)) &&
128		    /*
129		     * Check that RCS file is not working file.
130		     * Some hosts don't report file name length errors.
131		     */
132		    (statfailed ||
133		    ((filestat.st_dev ^ cstat.st_dev) |
134		    (filestat.st_ino ^ cstat.st_ino)))) {
135			Sprintf(buf, output_elsewhere?CHECKOUT:CHECKOUT_LOCKED, filename);
136			Sprintf(lbuf, RCSDIFF, filename);
137			cs = "RCS";
138		} else if (try("SCCS/%s%s", SCCSPREFIX, filebase) ||
139		    try("%s%s", SCCSPREFIX, filebase)) {
140			Sprintf(buf, output_elsewhere?GET:GET_LOCKED, s);
141			Sprintf(lbuf, SCCSDIFF, s, filename);
142			cs = "SCCS";
143		} else if (statfailed)
144			fatal2("can't find %s\n", filename);
145		/*
146		 * else we can't write to it but it's not under a version
147		 * control system, so just proceed.
148		 */
149		if (cs) {
150			if (!statfailed) {
151				if ((filestat.st_mode & 0222) != 0)
152					/* The owner can write to it.  */
153					fatal3(
154"file %s seems to be locked by somebody else under %s\n",
155					    filename, cs);
156				/*
157				 * It might be checked out unlocked.  See if
158				 * it's safe to check out the default version
159				 * locked.
160				 */
161				if (verbose)
162					say3(
163"Comparing file %s to default %s version...\n",
164					    filename, cs);
165				if (system(lbuf))
166					fatal3(
167"can't check out file %s: differs from default %s version\n",
168					    filename, cs);
169			}
170			if (verbose)
171				say3("Checking out file %s from %s...\n", filename, cs);
172			if (system(buf) || stat(filename, &filestat))
173				fatal3("can't check out file %s from %s\n",
174				    filename, cs);
175		}
176	}
177	filemode = filestat.st_mode;
178	if (!S_ISREG(filemode))
179		fatal2("%s is not a normal file--can't patch\n", filename);
180	i_size = filestat.st_size;
181	if (out_of_mem) {
182		set_hunkmax();	/* make sure dynamic arrays are allocated */
183		out_of_mem = FALSE;
184		return FALSE;	/* force plan b because plan a bombed */
185	}
186#ifdef lint
187	i_womp = Nullch;
188#else
189	/*
190	 * Lint says this may alloc less than i_size,
191	 * but that's okay, I think.
192	 */
193	i_womp = malloc((MEM)(i_size + 2));
194#endif
195	if (i_womp == Nullch)
196		return FALSE;
197	if ((ifd = open(filename, 0)) < 0)
198		pfatal2("can't open file %s", filename);
199#ifndef lint
200	if (read(ifd, i_womp, (int)i_size) != i_size) {
201		/*
202		 * This probably means i_size > 15 or 16 bits worth.  At
203		 * this point it doesn't matter if i_womp was undersized.
204		 */
205		Close(ifd);
206		free(i_womp);
207		return FALSE;
208	}
209#endif
210	Close(ifd);
211	if (i_size && i_womp[i_size - 1] != '\n')
212		i_womp[i_size++] = '\n';
213	i_womp[i_size] = '\0';
214
215	/*
216	 * Count the lines in the buffer so we know how many pointers we
217	 * need.
218	 */
219	iline = 0;
220	for (s = i_womp; *s; s++) {
221		if (*s == '\n')
222			iline++;
223	}
224#ifdef lint
225	i_ptr = Null(char**);
226#else
227	i_ptr = (char **)malloc((MEM)((iline + 2) * sizeof(char *)));
228#endif
229	if (i_ptr == Null(char **)) {	/* shucks, it was a near thing */
230		free((char *)i_womp);
231		return FALSE;
232	}
233
234	/* Now scan the buffer and build pointer array. */
235	iline = 1;
236	i_ptr[iline] = i_womp;
237	for (s = i_womp; *s; s++) {
238		if (*s == '\n') {
239			/* These are NOT null terminated. */
240			i_ptr[++iline] = s + 1;
241		}
242	}
243	input_lines = iline - 1;
244
245	/* Now check for revision, if any. */
246	if (revision != Nullch) {
247		if (!rev_in_string(i_womp)) {
248			if (force) {
249				if (verbose)
250				    say2(
251"Warning: this file doesn't appear to be the %s version--patching anyway.\n",
252					revision);
253			} else if (batch) {
254				fatal2(
255"this file doesn't appear to be the %s version--aborting.\n", revision);
256			} else {
257				(void) ask2(
258"This file doesn't appear to be the %s version--patch anyway? [n] ",
259				    revision);
260				if (*buf != 'y')
261					fatal1("aborted\n");
262			}
263		} else if (verbose)
264			say2("Good.  This file appears to be the %s version.\n",
265			    revision);
266	}
267
268	return TRUE;		/* Plan a will work. */
269}
270
271/*
272 * Keep (virtually) nothing in memory.
273 */
274void
275plan_b(char *filename)
276{
277	Reg3 FILE *ifp;
278	Reg1 int i = 0;
279	Reg2 int maxlen = 1;
280	Reg4 bool found_revision = (revision == Nullch);
281
282	using_plan_a = FALSE;
283	if ((ifp = fopen(filename, "r")) == Nullfp)
284		pfatal2("can't open file %s", filename);
285	if ((tifd = creat(TMPINNAME, 0666)) < 0)
286		pfatal2("can't open file %s", TMPINNAME);
287	while (fgets(buf, buf_size, ifp) != Nullch) {
288		if (revision != Nullch && !found_revision && rev_in_string(buf))
289			found_revision = TRUE;
290		if ((i = strlen(buf)) > maxlen)
291			maxlen = i;		/* Find longest line. */
292	}
293	if (revision != Nullch) {
294		if (!found_revision) {
295			if (force) {
296				if (verbose)
297					say2(
298"Warning: this file doesn't appear to be the %s version--patching anyway.\n",
299					    revision);
300			} else if (batch) {
301				fatal2(
302"this file doesn't appear to be the %s version--aborting.\n", revision);
303			} else {
304				(void) ask2(
305"This file doesn't appear to be the %s version--patch anyway? [n] ",
306				    revision);
307				if (*buf != 'y')
308					fatal1("aborted\n");
309			}
310		} else if (verbose)
311			say2("Good.  This file appears to be the %s version.\n",
312			    revision);
313	}
314	Fseek(ifp, 0L, 0);		/* Rewind file. */
315	lines_per_buf = BUFFERSIZE / maxlen;
316	tireclen = maxlen;
317	tibuf[0] = malloc((MEM)(BUFFERSIZE + 1));
318	tibuf[1] = malloc((MEM)(BUFFERSIZE + 1));
319	if (tibuf[1] == Nullch)
320		fatal1("out of memory\n");
321	for (i = 1; ; i++) {
322		if (! (i % lines_per_buf))	/* New block. */
323			if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE)
324				pfatal1("can't write temp file");
325		if (fgets(tibuf[0] + maxlen * (i%lines_per_buf),
326		    maxlen + 1, ifp) == Nullch) {
327			input_lines = i - 1;
328			if (i % lines_per_buf)
329				if (write(tifd, tibuf[0], BUFFERSIZE) <
330				    BUFFERSIZE)
331					pfatal1("can't write temp file");
332			break;
333		}
334	}
335	Fclose(ifp);
336	Close(tifd);
337	if ((tifd = open(TMPINNAME, 0)) < 0) {
338		pfatal2("can't reopen file %s", TMPINNAME);
339	}
340}
341
342/*
343 * Fetch a line from the input file, \n terminated, not necessarily \0.
344 */
345char *
346ifetch(line,whichbuf)
347Reg1 LINENUM line;
348int whichbuf;				/* ignored when file in memory */
349{
350	if (line < 1 || line > input_lines)
351		return "";
352	if (using_plan_a)
353		return i_ptr[line];
354	else {
355		LINENUM offline = line % lines_per_buf;
356		LINENUM baseline = line - offline;
357
358		if (tiline[0] == baseline)
359			whichbuf = 0;
360		else if (tiline[1] == baseline)
361			whichbuf = 1;
362		else {
363			tiline[whichbuf] = baseline;
364#ifndef lint		/* complains of long accuracy */
365			Lseek(tifd, (long)baseline / lines_per_buf * BUFFERSIZE, 0);
366#endif
367			if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0)
368				pfatal2("error reading tmp file %s", TMPINNAME);
369		}
370		return tibuf[whichbuf] + (tireclen * offline);
371	}
372}
373
374/*
375 * True if the string argument contains the revision number we want.
376 */
377bool
378rev_in_string(char *string)
379{
380	Reg1 char *s;
381	Reg2 int patlen;
382
383	if (revision == Nullch)
384		return TRUE;
385	patlen = strlen(revision);
386	if (strnEQ(string,revision,patlen) &&
387	    isspace((unsigned char)string[patlen]))
388		return TRUE;
389	for (s = string; *s; s++) {
390		if (isspace((unsigned char)*s) &&
391		    strnEQ(s + 1, revision, patlen) &&
392		    isspace((unsigned char)s[patlen + 1] )) {
393			return TRUE;
394		}
395	}
396	return FALSE;
397}
398