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