files.c revision 1.3
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
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 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
296static
297char *
298mkfilename(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
320static
321int
322file_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
339static
340void
341file_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
379void
380file_readquote(struct place *place, const char *name)
381{
382	file_search(place, &quotepath, name);
383}
384
385void
386file_readbracket(struct place *place, const char *name)
387{
388	file_search(place, &bracketpath, name);
389}
390
391void
392file_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