files.c revision 1.5
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
44struct incdir {
45	const char *name;
46	bool issystem;
47};
48
49DECLARRAY(incdir, static UNUSED);
50DEFARRAY(incdir, static);
51
52static struct incdirarray quotepath, bracketpath;
53
54////////////////////////////////////////////////////////////
55// management
56
57static
58struct incdir *
59incdir_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
69static
70void
71incdir_destroy(struct incdir *id)
72{
73	dofree(id, sizeof(*id));
74}
75
76void
77files_init(void)
78{
79	incdirarray_init(&quotepath);
80	incdirarray_init(&bracketpath);
81}
82
83DESTROYALL_ARRAY(incdir, );
84
85void
86files_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
97void
98files_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
106void
107files_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 */
122static
123size_t
124findeol(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
156static
157unsigned
158countnls(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
171static
172void
173file_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
306static
307char *
308mkfilename(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
330static
331int
332file_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 == -1) {
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
349static
350void
351file_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
389void
390file_readquote(struct place *place, const char *name)
391{
392	file_search(place, &quotepath, name);
393}
394
395void
396file_readbracket(struct place *place, const char *name)
397{
398	file_search(place, &bracketpath, name);
399}
400
401void
402file_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