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