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