Coverage Report

Created: 2026-01-07 06:10

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