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