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