xref: /openbsd-src/libexec/tradcpp/files.c (revision e5157e49389faebcb42b7237d55fbf096d9c2523)
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 	strcpy(ret, dir);
317 	if (needslash) {
318 		strcat(ret, "/");
319 	}
320 	strcat(ret, file);
321 	return ret;
322 }
323 
324 static
325 int
326 file_tryopen(const char *file)
327 {
328 	int fd;
329 
330 	/* XXX check for non-regular files */
331 
332 	fd = open(file, O_RDONLY);
333 	if (fd < 0) {
334 		if (errno != ENOENT && errno != ENOTDIR) {
335 			complain(NULL, "%s: %s", file, strerror(errno));
336 		}
337 		return -1;
338 	}
339 
340 	return fd;
341 }
342 
343 static
344 void
345 file_search(struct place *place, struct incdirarray *path, const char *name)
346 {
347 	unsigned i, num;
348 	struct incdir *id;
349 	const struct placefile *pf;
350 	char *file;
351 	int fd;
352 
353 	assert(place != NULL);
354 
355 	if (name[0] == '/') {
356 		fd = file_tryopen(name);
357 		if (fd >= 0) {
358 			pf = place_addfile(place, name, true);
359 			file_read(pf, fd, name, false);
360 			close(fd);
361 			return;
362 		}
363 	} else {
364 		num = incdirarray_num(path);
365 		for (i=0; i<num; i++) {
366 			id = incdirarray_get(path, i);
367 			file = mkfilename(place, id->name, name);
368 			fd = file_tryopen(file);
369 			if (fd >= 0) {
370 				pf = place_addfile(place, file, id->issystem);
371 				file_read(pf, fd, file, false);
372 				dostrfree(file);
373 				close(fd);
374 				return;
375 			}
376 			dostrfree(file);
377 		}
378 	}
379 	complain(place, "Include file %s not found", name);
380 	complain_fail();
381 }
382 
383 void
384 file_readquote(struct place *place, const char *name)
385 {
386 	file_search(place, &quotepath, name);
387 }
388 
389 void
390 file_readbracket(struct place *place, const char *name)
391 {
392 	file_search(place, &bracketpath, name);
393 }
394 
395 void
396 file_readabsolute(struct place *place, const char *name)
397 {
398 	const struct placefile *pf;
399 	int fd;
400 
401 	assert(place != NULL);
402 
403 	if ((name == NULL) || !strcmp(name, "-")) {
404 		fd = STDIN_FILENO;
405 		pf = place_addfile(place, "<standard-input>", false);
406 	} else {
407 		fd = file_tryopen(name);
408 		if (fd < 0) {
409 			complain(NULL, "%s: %s", name, strerror(errno));
410 			die();
411 		}
412 		pf = place_addfile(place, name, false);
413 	}
414 
415 	file_read(pf, fd, name, true);
416 
417 	if (name != NULL) {
418 		close(fd);
419 	}
420 }
421