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