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