]> wimlib.net Git - wimlib/blob - src/textfile.c
Use LGPLv3+ for src/*.c
[wimlib] / src / textfile.c
1 /*
2  * textfile.c
3  */
4
5 /*
6  * Copyright (C) 2014 Eric Biggers
7  *
8  * This file is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU Lesser General Public License as published by the Free
10  * Software Foundation; either version 3 of the License, or (at your option) any
11  * later version.
12  *
13  * This file is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this file; if not, see http://www.gnu.org/licenses/.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include "wimlib/assert.h"
27 #include "wimlib/encoding.h"
28 #include "wimlib/error.h"
29 #include "wimlib/file_io.h"
30 #include "wimlib/textfile.h"
31 #include "wimlib/util.h"
32
33 #include <ctype.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39
40 static int
41 read_file_contents(const tchar *path, void **buf_ret, size_t *bufsize_ret)
42 {
43         int raw_fd;
44         struct filedes fd;
45         struct stat st;
46         void *buf;
47         int ret;
48         int errno_save;
49
50         if (!path || !*path)
51                 return WIMLIB_ERR_INVALID_PARAM;
52
53         raw_fd = topen(path, O_RDONLY | O_BINARY);
54         if (raw_fd < 0) {
55                 ERROR_WITH_ERRNO("Can't open \"%"TS"\"", path);
56                 return WIMLIB_ERR_OPEN;
57         }
58         if (fstat(raw_fd, &st)) {
59                 ERROR_WITH_ERRNO("Can't stat \"%"TS"\"", path);
60                 close(raw_fd);
61                 return WIMLIB_ERR_STAT;
62         }
63         if ((size_t)st.st_size != st.st_size ||
64             (buf = MALLOC(st.st_size)) == NULL)
65         {
66                 close(raw_fd);
67                 ERROR("Not enough memory to read \"%"TS"\"", path);
68                 return WIMLIB_ERR_NOMEM;
69         }
70
71         filedes_init(&fd, raw_fd);
72         ret = full_read(&fd, buf, st.st_size);
73         errno_save = errno;
74         filedes_close(&fd);
75         errno = errno_save;
76         if (ret) {
77                 ERROR_WITH_ERRNO("Error reading \"%"TS"\"", path);
78                 FREE(buf);
79                 return ret;
80         }
81
82         *buf_ret = buf;
83         *bufsize_ret = st.st_size;
84         return 0;
85 }
86
87 static int
88 translate_text_buffer(const u8 *buf_raw, size_t bufsize_raw,
89                       tchar **tstr_ret, size_t *tstr_nchars_ret)
90 {
91         size_t offset_raw;
92         bool utf8;
93         tchar *buf_tstr;
94         size_t bufsize_tstr;
95         int ret;
96
97         /* Guess the encoding: UTF-8 or UTF-16LE.  (Something weirder and you're
98          * out of luck, sorry...)  */
99         if (bufsize_raw >= 2 &&
100             buf_raw[0] == 0xFF &&
101             buf_raw[1] == 0xFE)
102         {
103                 utf8 = false;
104                 offset_raw = 2;
105         }
106         else if (bufsize_raw >= 2 &&
107                  buf_raw[0] <= 0x7F &&
108                  buf_raw[1] == 0x00)
109         {
110                 utf8 = false;
111                 offset_raw = 0;
112         }
113         else if (bufsize_raw >= 3 &&
114                  buf_raw[0] == 0xEF &&
115                  buf_raw[1] == 0xBB &&
116                  buf_raw[2] == 0xBF)
117         {
118                 utf8 = true;
119                 offset_raw = 3;
120         }
121         else
122         {
123                 utf8 = true;
124                 offset_raw = 0;
125         }
126
127         if (utf8) {
128                 ret = utf8_to_tstr((const char *)(buf_raw + offset_raw),
129                                    bufsize_raw - offset_raw,
130                                    &buf_tstr, &bufsize_tstr);
131         } else {
132                 ret = utf16le_to_tstr((const utf16lechar *)(buf_raw + offset_raw),
133                                       bufsize_raw - offset_raw,
134                                       &buf_tstr, &bufsize_tstr);
135         }
136         if (ret)
137                 return ret;
138
139         *tstr_ret = buf_tstr;
140         *tstr_nchars_ret = bufsize_tstr / sizeof(tchar);
141         return 0;
142 }
143
144 static int
145 string_set_append(struct string_set *set, tchar *str)
146 {
147         size_t num_alloc_strings = set->num_alloc_strings;
148
149         if (set->num_strings == num_alloc_strings) {
150                 tchar **new_strings;
151
152                 num_alloc_strings = max(num_alloc_strings * 3 / 2,
153                                         num_alloc_strings + 4);
154                 new_strings = REALLOC(set->strings,
155                                       sizeof(set->strings[0]) * num_alloc_strings);
156                 if (!new_strings)
157                         return WIMLIB_ERR_NOMEM;
158                 set->strings = new_strings;
159                 set->num_alloc_strings = num_alloc_strings;
160         }
161         set->strings[set->num_strings++] = str;
162         return 0;
163 }
164
165 #define NOT_IN_SECTION          -1
166 #define IN_UNKNOWN_SECTION      -2
167
168 static int
169 parse_text_file(const tchar *path, tchar *buf, size_t buflen,
170                 const struct text_file_section *pos_sections,
171                 int num_pos_sections, int flags, line_mangle_t mangle_line)
172 {
173         int current_section = NOT_IN_SECTION;
174         bool have_named_sections = false;
175         tchar *p;
176         tchar *nl;
177         unsigned long line_no = 1;
178
179         for (int i = 0; i < num_pos_sections; i++) {
180                 if (*pos_sections[i].name)
181                         have_named_sections = true;
182                 else
183                         current_section = i;
184         }
185
186         for (p = buf; p != buf + buflen; p = nl + 1, line_no++) {
187                 tchar *line_begin, *line_end;
188                 size_t line_len;
189                 int ret;
190
191                 nl = tmemchr(p, T('\n'), buf + buflen - p);
192                 if (!nl)
193                         break;
194
195                 line_begin = p;
196                 line_end = nl;
197
198                 /* Ignore leading whitespace.  */
199                 while (line_begin < nl && istspace(*line_begin))
200                         line_begin++;
201
202                 /* Ignore trailing whitespace.  */
203                 while (line_end > line_begin && istspace(*(line_end - 1)))
204                         line_end--;
205
206                 line_len = line_end - line_begin;
207
208                 /* Ignore comments and empty lines.  */
209                 if (line_len == 0 || *line_begin == T(';') || *line_begin == T('#'))
210                         continue;
211
212                 line_begin[line_len] = T('\0');
213
214                 /* Check for beginning of new section.  */
215                 if (line_begin[0] == T('[') &&
216                     line_begin[line_len - 1] == T(']') &&
217                     have_named_sections)
218                 {
219                         line_begin[line_len - 1] = T('\0');
220                         current_section = IN_UNKNOWN_SECTION;
221                         for (int i = 0; i < num_pos_sections; i++) {
222                                 if (!tstrcmp(line_begin + 1,
223                                              pos_sections[i].name))
224                                 {
225                                         current_section = i;
226                                         break;
227                                 }
228                         }
229                         line_begin[line_len - 1] = T(']');
230                         if (current_section < 0) {
231                                 if (!(flags & LOAD_TEXT_FILE_NO_WARNINGS)) {
232                                         WARNING("%"TS":%lu: Unrecognized section \"%"TS"\"",
233                                                 path, line_no, line_begin);
234                                 }
235                         }
236                         continue;
237                 }
238
239                 if (current_section < 0) {
240                         if (current_section == NOT_IN_SECTION) {
241                                 if (!(flags & LOAD_TEXT_FILE_NO_WARNINGS)) {
242                                         WARNING("%"TS":%lu: Not in a bracketed section!",
243                                                 path, line_no);
244                                 }
245                         }
246                         continue;
247                 }
248
249                 if (flags & LOAD_TEXT_FILE_REMOVE_QUOTES) {
250                         if (line_begin[0] == T('"') || line_begin[0] == T('\'')) {
251                                 tchar quote = line_begin[0];
252                                 if (line_len >= 2 &&
253                                     line_begin[line_len - 1] == quote)
254                                 {
255                                         line_begin++;
256                                         line_len -= 2;
257                                         line_begin[line_len] = T('\0');
258                                 }
259                         }
260                 }
261
262                 if (mangle_line) {
263                         ret = (*mangle_line)(line_begin, path, line_no);
264                         if (ret)
265                                 return ret;
266                 }
267
268                 ret = string_set_append(pos_sections[current_section].strings,
269                                         line_begin);
270                 if (ret)
271                         return ret;
272         }
273         return 0;
274 }
275
276 /**
277  * do_load_text_file -
278  *
279  * Read and parse lines from a text file from an on-disk file or a buffer.
280  * The file may contain sections, like in an INI file.
281  *
282  * @path
283  *      Path to the file on disk to read, or a dummy name for the buffer.
284  * @buf
285  *      If NULL, the data will be read from the @path file.  Otherwise the data
286  *      will be read from this buffer.
287  * @bufsize
288  *      Length of buffer in bytes; ignored if @buf is NULL.
289  * @mem_ret
290  *      On success, a pointer to a buffer backing the parsed lines is stored
291  *      here.  This must be freed after the parsed lines are done being used.
292  * @pos_sections
293  *      Specifications of allowed sections in the file.  Each such specification
294  *      consists of the name of the section (e.g. [ExclusionList], like in the
295  *      INI file format), along with a pointer to the list of lines parsed for
296  *      that section.  Use an empty name to indicate the destination of lines
297  *      not in any section.  Each list must be initialized to an empty string
298  *      set.
299  * @num_pos_sections
300  *      Number of entries in the @pos_sections array.
301  * @flags
302  *      Flags: LOAD_TEXT_FILE_REMOVE_QUOTES, LOAD_TEXT_FILE_NO_WARNINGS.
303  * @mangle_line
304  *      Optional callback to validate and/or modify each line being read.
305  *
306  * Returns 0 on success; nonzero on failure.
307  *
308  * Unknown sections are ignored, but a warning is printed for each, unless
309  * LOAD_TEXT_FILE_NO_WARNINGS is specified.
310  */
311 int
312 do_load_text_file(const tchar *path,
313                   const void *buf, size_t bufsize,
314                   void **mem_ret,
315                   const struct text_file_section *pos_sections,
316                   int num_pos_sections,
317                   int flags,
318                   line_mangle_t mangle_line)
319 {
320         int ret;
321         bool pathmode = (buf == NULL);
322         tchar *tstr;
323         size_t tstr_nchars;
324
325         if (pathmode) {
326                 ret = read_file_contents(path, (void **)&buf, &bufsize);
327                 if (ret)
328                         return ret;
329         }
330
331         ret = translate_text_buffer(buf, bufsize, &tstr, &tstr_nchars);
332         if (pathmode)
333                 FREE((void *)buf);
334         if (ret)
335                 return ret;
336
337         tstr[tstr_nchars++] = T('\n');
338
339         ret = parse_text_file(path, tstr, tstr_nchars, pos_sections,
340                               num_pos_sections, flags, mangle_line);
341         if (ret) {
342                 for (int i = 0; i < num_pos_sections; i++)
343                         FREE(pos_sections[i].strings->strings);
344                 FREE(tstr);
345                 return ret;
346         }
347
348         *mem_ret = tstr;
349         return 0;
350 }