f0ee45eee55651a590919aaf4c7ca94c4b957bda
[oweals/cde.git] / cde / util / tradcpp / files.c
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) {
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 }