]>
Commit | Line | Data |
---|---|---|
a0948ffe KZ |
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 | ||
a0948ffe KZ |
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> | |
fbc333fe | 22 | #ifdef HAVE_ERRNO_H |
a0948ffe KZ |
23 | #include <errno.h> |
24 | #endif | |
25 | ||
26 | #include "blkidP.h" | |
a0948ffe | 27 | |
fbc333fe | 28 | #ifdef HAVE_STDLIB_H |
d58c47d9 FG |
29 | # ifndef _XOPEN_SOURCE |
30 | # define _XOPEN_SOURCE 600 /* for inclusion of strtoull */ | |
31 | # endif | |
32 | # include <stdlib.h> | |
33 | #endif | |
34 | ||
a0948ffe KZ |
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 | |
6c2f2b9d KZ |
42 | * <TIME="sec.usec"> (time_t and suseconds_t) time this entry was last |
43 | * read from disk | |
a0948ffe KZ |
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 | { | |
53 | while (*cp && isspace(*cp)) | |
54 | cp++; | |
55 | return cp; | |
56 | } | |
57 | ||
58 | static char *skip_over_word(char *cp) | |
59 | { | |
60 | char ch; | |
61 | ||
62 | while ((ch = *cp)) { | |
63 | /* If we see a backslash, skip the next character */ | |
64 | if (ch == '\\') { | |
65 | cp++; | |
66 | if (*cp == '\0') | |
67 | break; | |
68 | cp++; | |
69 | continue; | |
70 | } | |
71 | if (isspace(ch) || ch == '<' || ch == '>') | |
72 | break; | |
73 | cp++; | |
74 | } | |
75 | return cp; | |
76 | } | |
77 | ||
78 | static char *strip_line(char *line) | |
79 | { | |
80 | char *p; | |
81 | ||
82 | line = skip_over_blank(line); | |
83 | ||
84 | p = line + strlen(line) - 1; | |
85 | ||
86 | while (*line) { | |
87 | if (isspace(*p)) | |
88 | *p-- = '\0'; | |
89 | else | |
90 | break; | |
91 | } | |
92 | ||
93 | return line; | |
94 | } | |
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 | { | |
130 | char *p; | |
131 | ||
132 | 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 | if (*p == '\0' || *p == '#') | |
138 | return 0; | |
139 | ||
140 | if (!strncmp(p, "<device", 7)) { | |
c62a6311 | 141 | DBG(READ, ul_debug("found device header: %8s", p)); |
a0948ffe KZ |
142 | p += 7; |
143 | ||
144 | *cp = p; | |
145 | return 1; | |
146 | } | |
147 | ||
148 | if (*p == '<') | |
149 | return 0; | |
150 | ||
151 | return -BLKID_ERR_CACHE; | |
152 | } | |
153 | ||
154 | /* Consume the remaining XML on the line (cosmetic only) */ | |
155 | static int parse_end(char **cp) | |
156 | { | |
157 | *cp = skip_over_blank(*cp); | |
158 | ||
159 | if (!strncmp(*cp, "</device>", 9)) { | |
c62a6311 | 160 | DBG(READ, ul_debug("found device trailer %9s", *cp)); |
a0948ffe KZ |
161 | *cp += 9; |
162 | return 0; | |
163 | } | |
164 | ||
165 | return -BLKID_ERR_CACHE; | |
166 | } | |
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 | { | |
176 | char *start, *tmp, *end, *name; | |
177 | int ret; | |
178 | ||
179 | if ((ret = parse_start(cp)) <= 0) | |
180 | return ret; | |
181 | ||
182 | start = tmp = strchr(*cp, '>'); | |
183 | if (!start) { | |
c62a6311 | 184 | DBG(READ, ul_debug("blkid: short line parsing dev: %s", *cp)); |
a0948ffe KZ |
185 | return -BLKID_ERR_CACHE; |
186 | } | |
187 | start = skip_over_blank(start + 1); | |
188 | end = skip_over_word(start); | |
189 | ||
c62a6311 | 190 | DBG(READ, ul_debug("device should be %*s", |
a0948ffe KZ |
191 | (int)(end - start), start)); |
192 | ||
193 | if (**cp == '>') | |
194 | *cp = end; | |
195 | else | |
196 | (*cp)++; | |
197 | ||
198 | *tmp = '\0'; | |
199 | ||
200 | if (!(tmp = strrchr(end, '<')) || parse_end(&tmp) < 0) { | |
c62a6311 | 201 | DBG(READ, ul_debug("blkid: missing </device> ending: %s", end)); |
a0948ffe KZ |
202 | } else if (tmp) |
203 | *tmp = '\0'; | |
204 | ||
205 | if (end - start <= 1) { | |
c62a6311 | 206 | DBG(READ, ul_debug("blkid: empty device name: %s", *cp)); |
a0948ffe KZ |
207 | return -BLKID_ERR_CACHE; |
208 | } | |
209 | ||
e0a9b8cf | 210 | name = strndup(start, end - start); |
a0948ffe KZ |
211 | if (name == NULL) |
212 | return -BLKID_ERR_MEM; | |
213 | ||
c62a6311 | 214 | DBG(READ, ul_debug("found dev %s", name)); |
a0948ffe KZ |
215 | |
216 | if (!(*dev = blkid_get_dev(cache, name, BLKID_DEV_CREATE))) { | |
217 | free(name); | |
218 | return -BLKID_ERR_MEM; | |
219 | } | |
220 | ||
221 | free(name); | |
222 | return 1; | |
223 | } | |
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 | { | |
230 | char *end; | |
231 | ||
232 | if (!name || !value || !cp) | |
233 | return -BLKID_ERR_PARAM; | |
234 | ||
235 | if (!(*value = strchr(*cp, '='))) | |
236 | return 0; | |
237 | ||
238 | **value = '\0'; | |
239 | *name = strip_line(*cp); | |
240 | *value = skip_over_blank(*value + 1); | |
241 | ||
242 | if (**value == '"') { | |
89e90ae7 KZ |
243 | char *p = end = *value + 1; |
244 | ||
245 | /* convert 'foo\"bar' to 'foo"bar' */ | |
246 | while (*p) { | |
247 | if (*p == '\\') { | |
248 | p++; | |
249 | *end = *p; | |
250 | } else { | |
251 | *end = *p; | |
252 | if (*p == '"') | |
253 | break; | |
254 | } | |
255 | p++; | |
256 | end++; | |
257 | } | |
258 | ||
259 | if (*end != '"') { | |
c62a6311 | 260 | DBG(READ, ul_debug("unbalanced quotes at: %s", *value)); |
a0948ffe KZ |
261 | *cp = *value; |
262 | return -BLKID_ERR_CACHE; | |
263 | } | |
264 | (*value)++; | |
265 | *end = '\0'; | |
89e90ae7 | 266 | end = ++p; |
a0948ffe KZ |
267 | } else { |
268 | end = skip_over_word(*value); | |
269 | if (*end) { | |
270 | *end = '\0'; | |
271 | end++; | |
272 | } | |
273 | } | |
274 | *cp = end; | |
275 | ||
276 | return 1; | |
277 | } | |
278 | ||
a0948ffe KZ |
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 | { | |
3d604b43 KZ |
288 | char *name = NULL; |
289 | char *value = NULL; | |
a0948ffe KZ |
290 | int ret; |
291 | ||
292 | if (!cache || !dev) | |
293 | return -BLKID_ERR_PARAM; | |
294 | ||
f6b6beaf | 295 | if ((ret = parse_token(&name, &value, cp)) <= 0) |
a0948ffe KZ |
296 | return ret; |
297 | ||
14308bc3 KZ |
298 | DBG(READ, ul_debug("tag: %s=\"%s\"", name, value)); |
299 | ||
a0948ffe KZ |
300 | /* Some tags are stored directly in the device struct */ |
301 | if (!strcmp(name, "DEVNO")) | |
87918040 | 302 | dev->bid_devno = strtoull(value, NULL, 0); |
a0948ffe | 303 | else if (!strcmp(name, "PRI")) |
87918040 | 304 | dev->bid_pri = strtol(value, NULL, 0); |
6c2f2b9d KZ |
305 | else if (!strcmp(name, "TIME")) { |
306 | char *end = NULL; | |
9385a11d | 307 | dev->bid_time = strtoull(value, &end, 0); |
6c2f2b9d | 308 | if (end && *end == '.') |
87918040 | 309 | dev->bid_utime = strtoull(end + 1, NULL, 0); |
6c2f2b9d | 310 | } else |
a0948ffe KZ |
311 | ret = blkid_set_tag(dev, name, value, strlen(value)); |
312 | ||
a0948ffe KZ |
313 | return ret < 0 ? ret : 1; |
314 | } | |
315 | ||
316 | /* | |
317 | * Parse a single line of data, and return a newly allocated dev struct. | |
318 | * Add the new device to the cache struct, if one was read. | |
319 | * | |
320 | * Lines are of the form <device [TAG="value" ...]>/dev/foo</device> | |
321 | * | |
322 | * Returns -ve value on error. | |
323 | * Returns 0 otherwise. | |
324 | * If a valid device was read, *dev_p is non-NULL, otherwise it is NULL | |
325 | * (e.g. comment lines, unknown XML content, etc). | |
326 | */ | |
327 | static int blkid_parse_line(blkid_cache cache, blkid_dev *dev_p, char *cp) | |
328 | { | |
329 | blkid_dev dev; | |
330 | int ret; | |
331 | ||
332 | if (!cache || !dev_p) | |
333 | return -BLKID_ERR_PARAM; | |
334 | ||
335 | *dev_p = NULL; | |
336 | ||
c62a6311 | 337 | DBG(READ, ul_debug("line: %s", cp)); |
a0948ffe KZ |
338 | |
339 | if ((ret = parse_dev(cache, dev_p, &cp)) <= 0) | |
340 | return ret; | |
341 | ||
342 | dev = *dev_p; | |
343 | ||
344 | while ((ret = parse_tag(cache, dev, &cp)) > 0) { | |
345 | ; | |
346 | } | |
347 | ||
348 | if (dev->bid_type == NULL) { | |
c62a6311 | 349 | DBG(READ, ul_debug("blkid: device %s has no TYPE",dev->bid_name)); |
a0948ffe | 350 | blkid_free_dev(dev); |
fa5241d1 | 351 | goto done; |
a0948ffe KZ |
352 | } |
353 | ||
fa5241d1 | 354 | done: |
a0948ffe KZ |
355 | return ret; |
356 | } | |
357 | ||
358 | /* | |
359 | * Parse the specified filename, and return the data in the supplied or | |
360 | * a newly allocated cache struct. If the file doesn't exist, return a | |
361 | * new empty cache struct. | |
362 | */ | |
363 | void blkid_read_cache(blkid_cache cache) | |
364 | { | |
365 | FILE *file; | |
366 | char buf[4096]; | |
367 | int fd, lineno = 0; | |
368 | struct stat st; | |
369 | ||
a0948ffe KZ |
370 | /* |
371 | * If the file doesn't exist, then we just return an empty | |
372 | * struct so that the cache can be populated. | |
373 | */ | |
49a8f58e | 374 | if ((fd = open(cache->bic_filename, O_RDONLY|O_CLOEXEC)) < 0) |
a0948ffe KZ |
375 | return; |
376 | if (fstat(fd, &st) < 0) | |
377 | goto errout; | |
378 | if ((st.st_mtime == cache->bic_ftime) || | |
379 | (cache->bic_flags & BLKID_BIC_FL_CHANGED)) { | |
c62a6311 | 380 | DBG(CACHE, ul_debug("skipping re-read of %s", |
a0948ffe KZ |
381 | cache->bic_filename)); |
382 | goto errout; | |
383 | } | |
384 | ||
c62a6311 | 385 | DBG(CACHE, ul_debug("reading cache file %s", |
a0948ffe KZ |
386 | cache->bic_filename)); |
387 | ||
4000fc12 | 388 | file = fdopen(fd, "r" UL_CLOEXECSTR); |
a0948ffe KZ |
389 | if (!file) |
390 | goto errout; | |
391 | ||
392 | while (fgets(buf, sizeof(buf), file)) { | |
393 | blkid_dev dev; | |
394 | unsigned int end; | |
395 | ||
396 | lineno++; | |
397 | if (buf[0] == 0) | |
398 | continue; | |
399 | end = strlen(buf) - 1; | |
400 | /* Continue reading next line if it ends with a backslash */ | |
358ef9c0 | 401 | while (end < (sizeof(buf) - 2) && buf[end] == '\\' && |
a0948ffe KZ |
402 | fgets(buf + end, sizeof(buf) - end, file)) { |
403 | end = strlen(buf) - 1; | |
404 | lineno++; | |
405 | } | |
406 | ||
407 | if (blkid_parse_line(cache, &dev, buf) < 0) { | |
c62a6311 | 408 | DBG(READ, ul_debug("blkid: bad format on line %d", lineno)); |
a0948ffe KZ |
409 | continue; |
410 | } | |
411 | } | |
412 | fclose(file); | |
413 | ||
414 | /* | |
415 | * Initially we do not need to write out the cache file. | |
416 | */ | |
417 | cache->bic_flags &= ~BLKID_BIC_FL_CHANGED; | |
418 | cache->bic_ftime = st.st_mtime; | |
419 | ||
420 | return; | |
421 | errout: | |
422 | close(fd); | |
423 | return; | |
424 | } | |
425 | ||
426 | #ifdef TEST_PROGRAM | |
a0948ffe KZ |
427 | |
428 | int main(int argc, char**argv) | |
429 | { | |
430 | blkid_cache cache = NULL; | |
431 | int ret; | |
432 | ||
0540ea54 | 433 | blkid_init_debug(BLKID_DEBUG_ALL); |
a0948ffe KZ |
434 | if (argc > 2) { |
435 | fprintf(stderr, "Usage: %s [filename]\n" | |
436 | "Test parsing of the cache (filename)\n", argv[0]); | |
437 | exit(1); | |
438 | } | |
439 | if ((ret = blkid_get_cache(&cache, argv[1])) < 0) | |
440 | fprintf(stderr, "error %d reading cache file %s\n", ret, | |
b82590ad | 441 | argv[1] ? argv[1] : blkid_get_cache_filename(NULL)); |
a0948ffe KZ |
442 | |
443 | blkid_put_cache(cache); | |
444 | ||
445 | return ret; | |
446 | } | |
447 | #endif |