/src/util-linux/libblkid/src/read.c
Line | Count | Source |
1 | | /* |
2 | | * read.c - read the blkid cache from disk, to avoid scanning all devices |
3 | | * |
4 | | * Copyright (C) 2001, 2003 Theodore Y. Ts'o |
5 | | * Copyright (C) 2001 Andreas Dilger |
6 | | * |
7 | | * %Begin-Header% |
8 | | * This file may be redistributed under the terms of the |
9 | | * GNU Lesser General Public License. |
10 | | * %End-Header% |
11 | | */ |
12 | | |
13 | | |
14 | | #include <stdio.h> |
15 | | #include <ctype.h> |
16 | | #include <string.h> |
17 | | #include <time.h> |
18 | | #include <sys/types.h> |
19 | | #include <sys/stat.h> |
20 | | #include <fcntl.h> |
21 | | #include <unistd.h> |
22 | | #ifdef HAVE_ERRNO_H |
23 | | #include <errno.h> |
24 | | #endif |
25 | | |
26 | | #include "blkidP.h" |
27 | | |
28 | | #ifdef HAVE_STDLIB_H |
29 | | # ifndef _XOPEN_SOURCE |
30 | | # define _XOPEN_SOURCE 600 /* for inclusion of strtoull */ |
31 | | # endif |
32 | | # include <stdlib.h> |
33 | | #endif |
34 | | |
35 | | /* |
36 | | * File format: |
37 | | * |
38 | | * <device [<NAME="value"> ...]>device_name</device> |
39 | | * |
40 | | * The following tags are required for each entry: |
41 | | * <ID="id"> unique (within this file) ID number of this device |
42 | | * <TIME="sec.usec"> (time_t and suseconds_t) time this entry was last |
43 | | * read from disk |
44 | | * <TYPE="type"> (detected) type of filesystem/data for this partition |
45 | | * |
46 | | * The following tags may be present, depending on the device contents |
47 | | * <LABEL="label"> (user supplied) label (volume name, etc) |
48 | | * <UUID="uuid"> (generated) universally unique identifier (serial no) |
49 | | */ |
50 | | |
51 | | static char *skip_over_blank(char *cp) |
52 | 0 | { |
53 | 0 | while (*cp && isspace(*cp)) |
54 | 0 | cp++; |
55 | 0 | return cp; |
56 | 0 | } |
57 | | |
58 | | static char *skip_over_word(char *cp) |
59 | 0 | { |
60 | 0 | char ch; |
61 | |
|
62 | 0 | while ((ch = *cp)) { |
63 | | /* If we see a backslash, skip the next character */ |
64 | 0 | if (ch == '\\') { |
65 | 0 | cp++; |
66 | 0 | if (*cp == '\0') |
67 | 0 | break; |
68 | 0 | cp++; |
69 | 0 | continue; |
70 | 0 | } |
71 | 0 | if (isspace(ch) || ch == '<' || ch == '>') |
72 | 0 | break; |
73 | 0 | cp++; |
74 | 0 | } |
75 | 0 | return cp; |
76 | 0 | } |
77 | | |
78 | | static char *strip_line(char *line) |
79 | 0 | { |
80 | 0 | char *p; |
81 | |
|
82 | 0 | line = skip_over_blank(line); |
83 | |
|
84 | 0 | p = line + strlen(line) - 1; |
85 | |
|
86 | 0 | while (*line) { |
87 | 0 | if (isspace(*p)) |
88 | 0 | *p-- = '\0'; |
89 | 0 | else |
90 | 0 | break; |
91 | 0 | } |
92 | |
|
93 | 0 | return line; |
94 | 0 | } |
95 | | |
96 | | #if 0 |
97 | | static char *parse_word(char **buf) |
98 | | { |
99 | | char *word, *next; |
100 | | |
101 | | word = *buf; |
102 | | if (*word == '\0') |
103 | | return NULL; |
104 | | |
105 | | word = skip_over_blank(word); |
106 | | next = skip_over_word(word); |
107 | | if (*next) { |
108 | | char *end = next - 1; |
109 | | if (*end == '"' || *end == '\'') |
110 | | *end = '\0'; |
111 | | *next++ = '\0'; |
112 | | } |
113 | | *buf = next; |
114 | | |
115 | | if (*word == '"' || *word == '\'') |
116 | | word++; |
117 | | return word; |
118 | | } |
119 | | #endif |
120 | | |
121 | | /* |
122 | | * Start parsing a new line from the cache. |
123 | | * |
124 | | * line starts with "<device" return 1 -> continue parsing line |
125 | | * line starts with "<foo", empty, or # return 0 -> skip line |
126 | | * line starts with other, return -BLKID_ERR_CACHE -> error |
127 | | */ |
128 | | static int parse_start(char **cp) |
129 | 0 | { |
130 | 0 | char *p; |
131 | |
|
132 | 0 | p = strip_line(*cp); |
133 | | |
134 | | /* Skip comment or blank lines. We can't just NUL the first '#' char, |
135 | | * in case it is inside quotes, or escaped. |
136 | | */ |
137 | 0 | if (*p == '\0' || *p == '#') |
138 | 0 | return 0; |
139 | | |
140 | 0 | if (!strncmp(p, "<device", 7)) { |
141 | 0 | DBG(READ, ul_debug("found device header: %8s", p)); |
142 | 0 | p += 7; |
143 | |
|
144 | 0 | *cp = p; |
145 | 0 | return 1; |
146 | 0 | } |
147 | | |
148 | 0 | if (*p == '<') |
149 | 0 | return 0; |
150 | | |
151 | 0 | return -BLKID_ERR_CACHE; |
152 | 0 | } |
153 | | |
154 | | /* Consume the remaining XML on the line (cosmetic only) */ |
155 | | static int parse_end(char **cp) |
156 | 0 | { |
157 | 0 | *cp = skip_over_blank(*cp); |
158 | |
|
159 | 0 | if (!strncmp(*cp, "</device>", 9)) { |
160 | 0 | DBG(READ, ul_debug("found device trailer %9s", *cp)); |
161 | 0 | *cp += 9; |
162 | 0 | return 0; |
163 | 0 | } |
164 | | |
165 | 0 | return -BLKID_ERR_CACHE; |
166 | 0 | } |
167 | | |
168 | | /* |
169 | | * Allocate a new device struct with device name filled in. Will handle |
170 | | * finding the device on lines of the form: |
171 | | * <device foo=bar>devname</device> |
172 | | * <device>devname<foo>bar</foo></device> |
173 | | */ |
174 | | static int parse_dev(blkid_cache cache, blkid_dev *dev, char **cp) |
175 | 0 | { |
176 | 0 | char *start, *tmp, *end, *name; |
177 | 0 | int ret; |
178 | |
|
179 | 0 | if ((ret = parse_start(cp)) <= 0) |
180 | 0 | return ret; |
181 | | |
182 | 0 | start = tmp = strchr(*cp, '>'); |
183 | 0 | if (!start) { |
184 | 0 | DBG(READ, ul_debug("blkid: short line parsing dev: %s", *cp)); |
185 | 0 | return -BLKID_ERR_CACHE; |
186 | 0 | } |
187 | 0 | start = skip_over_blank(start + 1); |
188 | 0 | end = skip_over_word(start); |
189 | |
|
190 | 0 | DBG(READ, ul_debug("device should be %*s", |
191 | 0 | (int)(end - start), start)); |
192 | |
|
193 | 0 | if (**cp == '>') |
194 | 0 | *cp = end; |
195 | 0 | else |
196 | 0 | (*cp)++; |
197 | |
|
198 | 0 | *tmp = '\0'; |
199 | |
|
200 | 0 | if (!(tmp = strrchr(end, '<')) || parse_end(&tmp) < 0) { |
201 | 0 | DBG(READ, ul_debug("blkid: missing </device> ending: %s", end)); |
202 | 0 | } else if (tmp) |
203 | 0 | *tmp = '\0'; |
204 | |
|
205 | 0 | if (end - start <= 1) { |
206 | 0 | DBG(READ, ul_debug("blkid: empty device name: %s", *cp)); |
207 | 0 | return -BLKID_ERR_CACHE; |
208 | 0 | } |
209 | | |
210 | 0 | name = strndup(start, end - start); |
211 | 0 | if (name == NULL) |
212 | 0 | return -BLKID_ERR_MEM; |
213 | | |
214 | 0 | DBG(READ, ul_debug("found dev %s", name)); |
215 | |
|
216 | 0 | if (!(*dev = blkid_get_dev(cache, name, BLKID_DEV_CREATE))) { |
217 | 0 | free(name); |
218 | 0 | return -BLKID_ERR_MEM; |
219 | 0 | } |
220 | | |
221 | 0 | free(name); |
222 | 0 | return 1; |
223 | 0 | } |
224 | | |
225 | | /* |
226 | | * Extract a tag of the form NAME="value" from the line. |
227 | | */ |
228 | | static int parse_token(char **name, char **value, char **cp) |
229 | 0 | { |
230 | 0 | char *end; |
231 | |
|
232 | 0 | if (!name || !value || !cp) |
233 | 0 | return -BLKID_ERR_PARAM; |
234 | | |
235 | 0 | if (!(*value = strchr(*cp, '='))) |
236 | 0 | return 0; |
237 | | |
238 | 0 | **value = '\0'; |
239 | 0 | *name = strip_line(*cp); |
240 | 0 | *value = skip_over_blank(*value + 1); |
241 | |
|
242 | 0 | if (**value == '"') { |
243 | 0 | char *p = end = *value + 1; |
244 | | |
245 | | /* convert 'foo\"bar' to 'foo"bar' */ |
246 | 0 | while (*p) { |
247 | 0 | if (*p == '\\') { |
248 | 0 | p++; |
249 | 0 | *end = *p; |
250 | 0 | } else { |
251 | 0 | *end = *p; |
252 | 0 | if (*p == '"') |
253 | 0 | break; |
254 | 0 | } |
255 | 0 | p++; |
256 | 0 | end++; |
257 | 0 | } |
258 | |
|
259 | 0 | if (*end != '"') { |
260 | 0 | DBG(READ, ul_debug("unbalanced quotes at: %s", *value)); |
261 | 0 | *cp = *value; |
262 | 0 | return -BLKID_ERR_CACHE; |
263 | 0 | } |
264 | 0 | (*value)++; |
265 | 0 | *end = '\0'; |
266 | 0 | end = ++p; |
267 | 0 | } else { |
268 | 0 | end = skip_over_word(*value); |
269 | 0 | if (*end) { |
270 | 0 | *end = '\0'; |
271 | 0 | end++; |
272 | 0 | } |
273 | 0 | } |
274 | 0 | *cp = end; |
275 | |
|
276 | 0 | return 1; |
277 | 0 | } |
278 | | |
279 | | /* |
280 | | * Extract a tag from the line. |
281 | | * |
282 | | * Return 1 if a valid tag was found. |
283 | | * Return 0 if no tag found. |
284 | | * Return -ve error code. |
285 | | */ |
286 | | static int parse_tag(blkid_cache cache, blkid_dev dev, char **cp) |
287 | 0 | { |
288 | 0 | char *name = NULL; |
289 | 0 | char *value = NULL; |
290 | 0 | int ret; |
291 | |
|
292 | 0 | if (!cache || !dev) |
293 | 0 | return -BLKID_ERR_PARAM; |
294 | | |
295 | 0 | if ((ret = parse_token(&name, &value, cp)) <= 0) |
296 | 0 | return ret; |
297 | | |
298 | 0 | DBG(READ, ul_debug("tag: %s=\"%s\"", name, value)); |
299 | |
|
300 | 0 | errno = 0; |
301 | | |
302 | | /* Some tags are stored directly in the device struct */ |
303 | 0 | if (!strcmp(name, "DEVNO")) { |
304 | 0 | dev->bid_devno = strtoull(value, NULL, 0); |
305 | 0 | if (errno) |
306 | 0 | return -errno; |
307 | 0 | } else if (!strcmp(name, "PRI")) { |
308 | 0 | dev->bid_pri = strtol(value, NULL, 0); |
309 | 0 | if (errno) |
310 | 0 | return -errno; |
311 | 0 | } else if (!strcmp(name, "TIME")) { |
312 | 0 | char *end = NULL; |
313 | |
|
314 | 0 | dev->bid_time = strtoull(value, &end, 0); |
315 | 0 | if (errno == 0 && end && *end == '.') |
316 | 0 | dev->bid_utime = strtoull(end + 1, NULL, 0); |
317 | 0 | if (errno) |
318 | 0 | return -errno; |
319 | 0 | } else |
320 | 0 | ret = blkid_set_tag(dev, name, value, strlen(value)); |
321 | | |
322 | 0 | return ret < 0 ? ret : 1; |
323 | 0 | } |
324 | | |
325 | | /* |
326 | | * Parse a single line of data, and return a newly allocated dev struct. |
327 | | * Add the new device to the cache struct, if one was read. |
328 | | * |
329 | | * Lines are of the form <device [TAG="value" ...]>/dev/foo</device> |
330 | | * |
331 | | * Returns -ve value on error. |
332 | | * Returns 0 otherwise. |
333 | | * If a valid device was read, *dev_p is non-NULL, otherwise it is NULL |
334 | | * (e.g. comment lines, unknown XML content, etc). |
335 | | */ |
336 | | static int blkid_parse_line(blkid_cache cache, blkid_dev *dev_p, char *cp) |
337 | 0 | { |
338 | 0 | blkid_dev dev; |
339 | 0 | int ret; |
340 | |
|
341 | 0 | if (!cache || !dev_p) |
342 | 0 | return -BLKID_ERR_PARAM; |
343 | | |
344 | 0 | *dev_p = NULL; |
345 | |
|
346 | 0 | DBG(READ, ul_debug("line: %s", cp)); |
347 | |
|
348 | 0 | if ((ret = parse_dev(cache, dev_p, &cp)) <= 0) |
349 | 0 | return ret; |
350 | | |
351 | 0 | dev = *dev_p; |
352 | |
|
353 | 0 | while ((ret = parse_tag(cache, dev, &cp)) > 0) { |
354 | 0 | ; |
355 | 0 | } |
356 | |
|
357 | 0 | if (dev->bid_type == NULL) { |
358 | 0 | DBG(READ, ul_debug("blkid: device %s has no TYPE",dev->bid_name)); |
359 | 0 | blkid_free_dev(dev); |
360 | 0 | goto done; |
361 | 0 | } |
362 | | |
363 | 0 | done: |
364 | 0 | return ret; |
365 | 0 | } |
366 | | |
367 | | /* |
368 | | * Parse the specified filename, and return the data in the supplied or |
369 | | * a newly allocated cache struct. If the file doesn't exist, return a |
370 | | * new empty cache struct. |
371 | | */ |
372 | | void blkid_read_cache(blkid_cache cache) |
373 | 0 | { |
374 | 0 | FILE *file; |
375 | 0 | char buf[4096]; |
376 | 0 | int fd, lineno = 0; |
377 | 0 | struct stat st; |
378 | | |
379 | | /* |
380 | | * If the file doesn't exist, then we just return an empty |
381 | | * struct so that the cache can be populated. |
382 | | */ |
383 | 0 | if ((fd = open(cache->bic_filename, O_RDONLY|O_CLOEXEC)) < 0) |
384 | 0 | return; |
385 | 0 | if (fstat(fd, &st) < 0) |
386 | 0 | goto errout; |
387 | 0 | if ((st.st_mtime == cache->bic_ftime) || |
388 | 0 | (cache->bic_flags & BLKID_BIC_FL_CHANGED)) { |
389 | 0 | DBG(CACHE, ul_debug("skipping re-read of %s", |
390 | 0 | cache->bic_filename)); |
391 | 0 | goto errout; |
392 | 0 | } |
393 | | |
394 | 0 | DBG(CACHE, ul_debug("reading cache file %s", |
395 | 0 | cache->bic_filename)); |
396 | |
|
397 | 0 | file = fdopen(fd, "r" UL_CLOEXECSTR); |
398 | 0 | if (!file) |
399 | 0 | goto errout; |
400 | | |
401 | 0 | while (fgets(buf, sizeof(buf), file)) { |
402 | 0 | blkid_dev dev; |
403 | 0 | unsigned int end; |
404 | |
|
405 | 0 | lineno++; |
406 | 0 | if (buf[0] == 0) |
407 | 0 | continue; |
408 | 0 | end = strlen(buf) - 1; |
409 | | /* Continue reading next line if it ends with a backslash */ |
410 | 0 | while (end < (sizeof(buf) - 2) && buf[end] == '\\' && |
411 | 0 | fgets(buf + end, sizeof(buf) - end, file)) { |
412 | 0 | end = strlen(buf) - 1; |
413 | 0 | lineno++; |
414 | 0 | } |
415 | |
|
416 | 0 | if (blkid_parse_line(cache, &dev, buf) < 0) { |
417 | 0 | DBG(READ, ul_debug("blkid: bad format on line %d", lineno)); |
418 | 0 | continue; |
419 | 0 | } |
420 | 0 | } |
421 | 0 | fclose(file); |
422 | | |
423 | | /* |
424 | | * Initially we do not need to write out the cache file. |
425 | | */ |
426 | 0 | cache->bic_flags &= ~BLKID_BIC_FL_CHANGED; |
427 | 0 | cache->bic_ftime = st.st_mtime; |
428 | |
|
429 | 0 | return; |
430 | 0 | errout: |
431 | 0 | close(fd); |
432 | 0 | } |
433 | | |
434 | | #ifdef TEST_PROGRAM |
435 | | |
436 | | int main(int argc, char**argv) |
437 | | { |
438 | | blkid_cache cache = NULL; |
439 | | int ret; |
440 | | |
441 | | blkid_init_debug(BLKID_DEBUG_ALL); |
442 | | if (argc > 2) { |
443 | | fprintf(stderr, "Usage: %s [filename]\n" |
444 | | "Test parsing of the cache (filename)\n", argv[0]); |
445 | | exit(1); |
446 | | } |
447 | | if ((ret = blkid_get_cache(&cache, argv[1])) < 0) |
448 | | fprintf(stderr, "error %d reading cache file %s\n", ret, |
449 | | argv[1] ? argv[1] : blkid_get_cache_filename(NULL)); |
450 | | |
451 | | blkid_put_cache(cache); |
452 | | |
453 | | return ret; |
454 | | } |
455 | | #endif |