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