xref: /openbsd-src/libexec/tradcpp/files.c (revision d1df930ffab53da22f3324c32bed7ac5709915e6)
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 		}
167 	}
168 	return count;
169 }
170 
171 static
172 void
173 file_read(const struct placefile *pf, int fd, const char *name, bool toplevel)
174 {
175 	struct lineplace places;
176 	struct place ptmp;
177 	size_t bufend, bufmax, linestart, lineend, nextlinestart, tmp;
178 	ssize_t result;
179 	bool ateof = false;
180 	char *buf;
181 
182 	place_setfilestart(&places.current, pf);
183 	places.nextline = places.current;
184 
185 	if (name) {
186 		debuglog(&places.current, "Reading file %s", name);
187 	} else {
188 		debuglog(&places.current, "Reading standard input");
189 	}
190 
191 	bufmax = 128;
192 	bufend = 0;
193 	linestart = 0;
194 	lineend = 0;
195 	buf = domalloc(bufmax);
196 
197 	while (1) {
198 		if (lineend >= bufend) {
199 			/* do not have a whole line in the buffer; read more */
200 			assert(bufend >= linestart);
201 			if (linestart > 0 && bufend > linestart) {
202 				/* slide to beginning of buffer */
203 				memmove(buf, buf+linestart, bufend-linestart);
204 				bufend -= linestart;
205 				lineend -= linestart;
206 				linestart = 0;
207 			}
208 			if (bufend >= bufmax) {
209 				/* need bigger buffer */
210 				buf = dorealloc(buf, bufmax, bufmax*2);
211 				bufmax = bufmax*2;
212 			}
213 
214 			if (ateof) {
215 				/* don't read again, in case it's a socket */
216 				result = 0;
217 			} else {
218 				result = read(fd, buf+bufend, bufmax - bufend);
219 			}
220 
221 			if (result == -1) {
222 				/* read error */
223 				complain(NULL, "%s: %s",
224 					 name, strerror(errno));
225 				complain_fail();
226 			} else if (result == 0 && bufend == linestart) {
227 				/* eof */
228 				ateof = true;
229 				break;
230 			} else if (result == 0) {
231 				/* eof in middle of line */
232 				ateof = true;
233 				ptmp = places.current;
234 				ptmp.column += bufend - linestart;
235 				if (buf[bufend - 1] == '\n') {
236 					complain(&ptmp, "Unclosed comment");
237 					complain_fail();
238 				} else {
239 					complain(&ptmp,
240 						 "No newline at end of file");
241 				}
242 				if (mode.werror) {
243 					complain_fail();
244 				}
245 				assert(bufend < bufmax);
246 				lineend = bufend++;
247 				buf[lineend] = '\n';
248 			} else {
249 				bufend += (size_t)result;
250 				lineend = findeol(buf, linestart, bufend);
251 			}
252 			/* loop in case we still don't have a whole line */
253 			continue;
254 		}
255 
256 		/* have a line */
257 		assert(buf[lineend] == '\n');
258 		buf[lineend] = '\0';
259 		nextlinestart = lineend+1;
260 		places.nextline.line++;
261 
262 		/* check for CR/NL */
263 		if (lineend > 0 && buf[lineend-1] == '\r') {
264 			buf[lineend-1] = '\0';
265 			lineend--;
266 		}
267 
268 		/* check for continuation line */
269 		if (lineend > 0 && buf[lineend-1]=='\\') {
270 			lineend--;
271 			tmp = nextlinestart - lineend;
272 			if (bufend > nextlinestart) {
273 				memmove(buf+lineend, buf+nextlinestart,
274 					bufend - nextlinestart);
275 			}
276 			bufend -= tmp;
277 			nextlinestart -= tmp;
278 			lineend = findeol(buf, linestart, bufend);
279 			/* might not have a whole line, so loop */
280 			continue;
281 		}
282 
283 		/* line now goes from linestart to lineend */
284 		assert(buf[lineend] == '\0');
285 
286 		/* count how many commented-out newlines we swallowed */
287 		places.nextline.line += countnls(buf, linestart, lineend);
288 
289 		/* process the line (even if it's empty) */
290 		directive_gotline(&places, buf+linestart, lineend-linestart);
291 
292 		linestart = nextlinestart;
293 		lineend = findeol(buf, linestart, bufend);
294 		places.current = places.nextline;
295 	}
296 
297 	if (toplevel) {
298 		directive_goteof(&places.current);
299 	}
300 	dofree(buf, bufmax);
301 }
302 
303 ////////////////////////////////////////////////////////////
304 // path search
305 
306 static
307 char *
308 mkfilename(struct place *place, const char *dir, const char *file)
309 {
310 	size_t dlen, flen, rlen;
311 	char *ret;
312 	bool needslash = false;
313 
314 	if (dir == NULL) {
315 		dir = place_getparsedir(place);
316 	}
317 
318 	dlen = strlen(dir);
319 	flen = strlen(file);
320 	if (dlen > 0 && dir[dlen-1] != '/') {
321 		needslash = true;
322 	}
323 
324 	rlen = dlen + (needslash ? 1 : 0) + flen;
325 	ret = domalloc(rlen + 1);
326 	snprintf(ret, rlen+1, "%s%s%s", dir, needslash ? "/" : "", file);
327 	return ret;
328 }
329 
330 static
331 int
332 file_tryopen(const char *file)
333 {
334 	int fd;
335 
336 	/* XXX check for non-regular files */
337 
338 	fd = open(file, O_RDONLY);
339 	if (fd < 0) {
340 		if (errno != ENOENT && errno != ENOTDIR) {
341 			complain(NULL, "%s: %s", file, strerror(errno));
342 		}
343 		return -1;
344 	}
345 
346 	return fd;
347 }
348 
349 static
350 void
351 file_search(struct place *place, struct incdirarray *path, const char *name)
352 {
353 	unsigned i, num;
354 	struct incdir *id;
355 	const struct placefile *pf;
356 	char *file;
357 	int fd;
358 
359 	assert(place != NULL);
360 
361 	if (name[0] == '/') {
362 		fd = file_tryopen(name);
363 		if (fd >= 0) {
364 			pf = place_addfile(place, name, true);
365 			file_read(pf, fd, name, false);
366 			close(fd);
367 			return;
368 		}
369 	} else {
370 		num = incdirarray_num(path);
371 		for (i=0; i<num; i++) {
372 			id = incdirarray_get(path, i);
373 			file = mkfilename(place, id->name, name);
374 			fd = file_tryopen(file);
375 			if (fd >= 0) {
376 				pf = place_addfile(place, file, id->issystem);
377 				file_read(pf, fd, file, false);
378 				dostrfree(file);
379 				close(fd);
380 				return;
381 			}
382 			dostrfree(file);
383 		}
384 	}
385 	complain(place, "Include file %s not found", name);
386 	complain_fail();
387 }
388 
389 void
390 file_readquote(struct place *place, const char *name)
391 {
392 	file_search(place, &quotepath, name);
393 }
394 
395 void
396 file_readbracket(struct place *place, const char *name)
397 {
398 	file_search(place, &bracketpath, name);
399 }
400 
401 void
402 file_readabsolute(struct place *place, const char *name)
403 {
404 	const struct placefile *pf;
405 	int fd;
406 
407 	assert(place != NULL);
408 
409 	if (name == NULL) {
410 		fd = STDIN_FILENO;
411 		pf = place_addfile(place, "<standard-input>", false);
412 	} else {
413 		fd = file_tryopen(name);
414 		if (fd < 0) {
415 			complain(NULL, "%s: %s", name, strerror(errno));
416 			die();
417 		}
418 		pf = place_addfile(place, name, false);
419 	}
420 
421 	file_read(pf, fd, name, true);
422 
423 	if (name != NULL) {
424 		close(fd);
425 	}
426 }
427