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