xref: /openbsd/libexec/tradcpp/files.c (revision 5dea098c)
1 /*-
2  * Copyright (c) 2010, 2013 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The NetBSD Foundation
6  * by David A. Holland.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <errno.h>
36 
37 #include "bool.h"
38 #include "array.h"
39 #include "mode.h"
40 #include "place.h"
41 #include "files.h"
42 #include "directive.h"
43 
44 struct incdir {
45 	const char *name;
46 	bool issystem;
47 };
48 
49 DECLARRAY(incdir, static UNUSED);
50 DEFARRAY(incdir, static);
51 
52 static struct incdirarray quotepath, bracketpath;
53 
54 ////////////////////////////////////////////////////////////
55 // management
56 
57 static
58 struct incdir *
59 incdir_create(const char *name, bool issystem)
60 {
61 	struct incdir *id;
62 
63 	id = domalloc(sizeof(*id));
64 	id->name = name;
65 	id->issystem = issystem;
66 	return id;
67 }
68 
69 static
70 void
71 incdir_destroy(struct incdir *id)
72 {
73 	dofree(id, sizeof(*id));
74 }
75 
76 void
77 files_init(void)
78 {
79 	incdirarray_init(&quotepath);
80 	incdirarray_init(&bracketpath);
81 }
82 
83 DESTROYALL_ARRAY(incdir, );
84 
85 void
86 files_cleanup(void)
87 {
88 	incdirarray_destroyall(&quotepath);
89 	incdirarray_cleanup(&quotepath);
90 	incdirarray_destroyall(&bracketpath);
91 	incdirarray_cleanup(&bracketpath);
92 }
93 
94 ////////////////////////////////////////////////////////////
95 // path setup
96 
97 void
98 files_addquotepath(const char *dir, bool issystem)
99 {
100 	struct incdir *id;
101 
102 	id = incdir_create(dir, issystem);
103 	incdirarray_add(&quotepath, id, NULL);
104 }
105 
106 void
107 files_addbracketpath(const char *dir, bool issystem)
108 {
109 	struct incdir *id;
110 
111 	id = incdir_create(dir, issystem);
112 	incdirarray_add(&bracketpath, id, NULL);
113 }
114 
115 ////////////////////////////////////////////////////////////
116 // parsing
117 
118 /*
119  * Find the end of the logical line. End of line characters that are
120  * commented out do not count.
121  */
122 static
123 size_t
124 findeol(const char *buf, size_t start, size_t limit)
125 {
126 	size_t i;
127 	int incomment = 0;
128 	bool inquote = false;
129 	char quote = '\0';
130 
131 	for (i=start; i<limit; i++) {
132 		if (incomment) {
133 			if (i+1 < limit && buf[i] == '*' && buf[i+1] == '/') {
134 				i++;
135 				incomment = 0;
136 			}
137 		} else if (!inquote && i+1 < limit &&
138 			   buf[i] == '/' && buf[i+1] == '*') {
139 			i++;
140 			incomment = 1;
141 		} else if (i+1 < limit &&
142 			   buf[i] == '\\' && buf[i+1] != '\n') {
143 			i++;
144 		} else if (!inquote && (buf[i] == '"' || buf[i] == '\'')) {
145 			inquote = true;
146 			quote = buf[i];
147 		} else if (inquote && buf[i] == quote) {
148 			inquote = false;
149 		} else if (buf[i] == '\n') {
150 			return i;
151 		}
152 	}
153 	return limit;
154 }
155 
156 static
157 unsigned
158 countnls(const char *buf, size_t start, size_t limit)
159 {
160 	size_t i;
161 	unsigned count = 0;
162 
163 	for (i=start; i<limit; i++) {
164 		if (buf[i] == '\n') {
165 			count++;
166 			if (count == 0) {
167 				/* just return the max and error downstream */
168 				return count - 1;
169 			}
170 		}
171 	}
172 	return count;
173 }
174 
175 static
176 void
177 file_read(const struct placefile *pf, int fd, const char *name, bool toplevel)
178 {
179 	struct lineplace places;
180 	struct place ptmp;
181 	size_t bufend, bufmax, linestart, lineend, nextlinestart, tmp;
182 	ssize_t result;
183 	bool ateof = false;
184 	char *buf;
185 
186 	place_setfilestart(&places.current, pf);
187 	places.nextline = places.current;
188 
189 	if (name) {
190 		debuglog(&places.current, "Reading file %s", name);
191 	} else {
192 		debuglog(&places.current, "Reading standard input");
193 	}
194 
195 	bufmax = 128;
196 	bufend = 0;
197 	linestart = 0;
198 	lineend = 0;
199 	buf = domalloc(bufmax);
200 
201 	while (1) {
202 		if (lineend >= bufend) {
203 			/* do not have a whole line in the buffer; read more */
204 			assert(bufend >= linestart);
205 			if (linestart > 0 && bufend > linestart) {
206 				/* slide to beginning of buffer */
207 				memmove(buf, buf+linestart, bufend-linestart);
208 				bufend -= linestart;
209 				lineend -= linestart;
210 				linestart = 0;
211 			}
212 			if (bufend >= bufmax) {
213 				/* need bigger buffer */
214 				buf = dorealloc(buf, bufmax, bufmax*2);
215 				bufmax = bufmax*2;
216 				/* just in case someone's screwing around */
217 				if (bufmax > 0xffffffff) {
218 					complain(&places.current,
219 						 "Input line too long");
220 					die();
221 				}
222 			}
223 
224 			if (ateof) {
225 				/* don't read again, in case it's a socket */
226 				result = 0;
227 			} else {
228 				result = read(fd, buf+bufend, bufmax - bufend);
229 			}
230 
231 			if (result == -1) {
232 				/* read error */
233 				complain(NULL, "%s: %s",
234 					 name, strerror(errno));
235 				complain_fail();
236 			} else if (result == 0 && bufend == linestart) {
237 				/* eof */
238 				ateof = true;
239 				break;
240 			} else if (result == 0) {
241 				/* eof in middle of line */
242 				ateof = true;
243 				ptmp = places.current;
244 				place_addcolumns(&ptmp, bufend - linestart);
245 				if (buf[bufend - 1] == '\n') {
246 					complain(&ptmp, "Unclosed comment");
247 					complain_fail();
248 				} else {
249 					complain(&ptmp,
250 						 "No newline at end of file");
251 				}
252 				if (mode.werror) {
253 					complain_fail();
254 				}
255 				assert(bufend < bufmax);
256 				lineend = bufend++;
257 				buf[lineend] = '\n';
258 			} else {
259 				bufend += (size_t)result;
260 				lineend = findeol(buf, linestart, bufend);
261 			}
262 			/* loop in case we still don't have a whole line */
263 			continue;
264 		}
265 
266 		/* have a line */
267 		assert(buf[lineend] == '\n');
268 		buf[lineend] = '\0';
269 		nextlinestart = lineend+1;
270 		place_addlines(&places.nextline, 1);
271 
272 		/* check for CR/NL */
273 		if (lineend > 0 && buf[lineend-1] == '\r') {
274 			buf[lineend-1] = '\0';
275 			lineend--;
276 		}
277 
278 		/* check for continuation line */
279 		if (lineend > 0 && buf[lineend-1]=='\\') {
280 			lineend--;
281 			tmp = nextlinestart - lineend;
282 			if (bufend > nextlinestart) {
283 				memmove(buf+lineend, buf+nextlinestart,
284 					bufend - nextlinestart);
285 			}
286 			bufend -= tmp;
287 			nextlinestart -= tmp;
288 			lineend = findeol(buf, linestart, bufend);
289 			/* might not have a whole line, so loop */
290 			continue;
291 		}
292 
293 		/* line now goes from linestart to lineend */
294 		assert(buf[lineend] == '\0');
295 
296 		/* count how many commented-out newlines we swallowed */
297 		place_addlines(&places.nextline,
298 			       countnls(buf, linestart, lineend));
299 
300 		/* process the line (even if it's empty) */
301 		directive_gotline(&places, buf+linestart, lineend-linestart);
302 
303 		linestart = nextlinestart;
304 		lineend = findeol(buf, linestart, bufend);
305 		places.current = places.nextline;
306 	}
307 
308 	if (toplevel) {
309 		directive_goteof(&places.current);
310 	}
311 	dofree(buf, bufmax);
312 }
313 
314 ////////////////////////////////////////////////////////////
315 // path search
316 
317 static
318 char *
319 mkfilename(struct place *place, const char *dir, const char *file)
320 {
321 	size_t dlen, flen, rlen;
322 	char *ret;
323 	bool needslash = false;
324 
325 	if (dir == NULL) {
326 		dir = place_getparsedir(place);
327 	}
328 
329 	dlen = strlen(dir);
330 	flen = strlen(file);
331 	if (dlen > 0 && dir[dlen-1] != '/') {
332 		needslash = true;
333 	}
334 
335 	rlen = dlen + (needslash ? 1 : 0) + flen;
336 	ret = domalloc(rlen + 1);
337 	snprintf(ret, rlen+1, "%s%s%s", dir, needslash ? "/" : "", file);
338 	return ret;
339 }
340 
341 static
342 int
343 file_tryopen(const char *file)
344 {
345 	int fd;
346 
347 	/* XXX check for non-regular files */
348 
349 	fd = open(file, O_RDONLY);
350 	if (fd == -1) {
351 		if (errno != ENOENT && errno != ENOTDIR) {
352 			complain(NULL, "%s: %s", file, strerror(errno));
353 		}
354 		return -1;
355 	}
356 
357 	return fd;
358 }
359 
360 static
361 void
362 file_search(struct place *place, struct incdirarray *path, const char *name)
363 {
364 	unsigned i, num;
365 	struct incdir *id;
366 	const struct placefile *pf;
367 	char *file;
368 	int fd;
369 
370 	assert(place != NULL);
371 
372 	if (name[0] == '/') {
373 		fd = file_tryopen(name);
374 		if (fd >= 0) {
375 			pf = place_addfile(place, name, true);
376 			file_read(pf, fd, name, false);
377 			close(fd);
378 			return;
379 		}
380 	} else {
381 		num = incdirarray_num(path);
382 		for (i=0; i<num; i++) {
383 			id = incdirarray_get(path, i);
384 			file = mkfilename(place, id->name, name);
385 			fd = file_tryopen(file);
386 			if (fd >= 0) {
387 				pf = place_addfile(place, file, id->issystem);
388 				file_read(pf, fd, file, false);
389 				dostrfree(file);
390 				close(fd);
391 				return;
392 			}
393 			dostrfree(file);
394 		}
395 	}
396 	complain(place, "Include file %s not found", name);
397 	complain_fail();
398 }
399 
400 void
401 file_readquote(struct place *place, const char *name)
402 {
403 	file_search(place, &quotepath, name);
404 }
405 
406 void
407 file_readbracket(struct place *place, const char *name)
408 {
409 	file_search(place, &bracketpath, name);
410 }
411 
412 void
413 file_readabsolute(struct place *place, const char *name)
414 {
415 	const struct placefile *pf;
416 	int fd;
417 
418 	assert(place != NULL);
419 
420 	if (name == NULL) {
421 		fd = STDIN_FILENO;
422 		pf = place_addfile(place, "<standard-input>", false);
423 	} else {
424 		fd = file_tryopen(name);
425 		if (fd < 0) {
426 			complain(NULL, "%s: %s", name, strerror(errno));
427 			die();
428 		}
429 		pf = place_addfile(place, name, false);
430 	}
431 
432 	file_read(pf, fd, name, true);
433 
434 	if (name != NULL) {
435 		close(fd);
436 	}
437 }
438