(root)/
util-linux-2.39/
libblkid/
src/
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  
      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  {
      53  	while (*cp && isspace(*cp))
      54  		cp++;
      55  	return cp;
      56  }
      57  
      58  static char *skip_over_word(char *cp)
      59  {
      60  	char ch;
      61  
      62  	while ((ch = *cp)) {
      63  		/* If we see a backslash, skip the next character */
      64  		if (ch == '\\') {
      65  			cp++;
      66  			if (*cp == '\0')
      67  				break;
      68  			cp++;
      69  			continue;
      70  		}
      71  		if (isspace(ch) || ch == '<' || ch == '>')
      72  			break;
      73  		cp++;
      74  	}
      75  	return cp;
      76  }
      77  
      78  static char *strip_line(char *line)
      79  {
      80  	char	*p;
      81  
      82  	line = skip_over_blank(line);
      83  
      84  	p = line + strlen(line) - 1;
      85  
      86  	while (*line) {
      87  		if (isspace(*p))
      88  			*p-- = '\0';
      89  		else
      90  			break;
      91  	}
      92  
      93  	return line;
      94  }
      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  {
     130  	char *p;
     131  
     132  	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  	if (*p == '\0' || *p == '#')
     138  		return 0;
     139  
     140  	if (!strncmp(p, "<device", 7)) {
     141  		DBG(READ, ul_debug("found device header: %8s", p));
     142  		p += 7;
     143  
     144  		*cp = p;
     145  		return 1;
     146  	}
     147  
     148  	if (*p == '<')
     149  		return 0;
     150  
     151  	return -BLKID_ERR_CACHE;
     152  }
     153  
     154  /* Consume the remaining XML on the line (cosmetic only) */
     155  static int parse_end(char **cp)
     156  {
     157  	*cp = skip_over_blank(*cp);
     158  
     159  	if (!strncmp(*cp, "</device>", 9)) {
     160  		DBG(READ, ul_debug("found device trailer %9s", *cp));
     161  		*cp += 9;
     162  		return 0;
     163  	}
     164  
     165  	return -BLKID_ERR_CACHE;
     166  }
     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  {
     176  	char *start, *tmp, *end, *name;
     177  	int ret;
     178  
     179  	if ((ret = parse_start(cp)) <= 0)
     180  		return ret;
     181  
     182  	start = tmp = strchr(*cp, '>');
     183  	if (!start) {
     184  		DBG(READ, ul_debug("blkid: short line parsing dev: %s", *cp));
     185  		return -BLKID_ERR_CACHE;
     186  	}
     187  	start = skip_over_blank(start + 1);
     188  	end = skip_over_word(start);
     189  
     190  	DBG(READ, ul_debug("device should be %*s",
     191  			       (int)(end - start), start));
     192  
     193  	if (**cp == '>')
     194  		*cp = end;
     195  	else
     196  		(*cp)++;
     197  
     198  	*tmp = '\0';
     199  
     200  	if (!(tmp = strrchr(end, '<')) || parse_end(&tmp) < 0) {
     201  		DBG(READ, ul_debug("blkid: missing </device> ending: %s", end));
     202  	} else if (tmp)
     203  		*tmp = '\0';
     204  
     205  	if (end - start <= 1) {
     206  		DBG(READ, ul_debug("blkid: empty device name: %s", *cp));
     207  		return -BLKID_ERR_CACHE;
     208  	}
     209  
     210  	name = strndup(start, end - start);
     211  	if (name == NULL)
     212  		return -BLKID_ERR_MEM;
     213  
     214  	DBG(READ, ul_debug("found dev %s", name));
     215  
     216  	if (!(*dev = blkid_get_dev(cache, name, BLKID_DEV_CREATE))) {
     217  		free(name);
     218  		return -BLKID_ERR_MEM;
     219  	}
     220  
     221  	free(name);
     222  	return 1;
     223  }
     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  {
     230  	char *end;
     231  
     232  	if (!name || !value || !cp)
     233  		return -BLKID_ERR_PARAM;
     234  
     235  	if (!(*value = strchr(*cp, '=')))
     236  		return 0;
     237  
     238  	**value = '\0';
     239  	*name = strip_line(*cp);
     240  	*value = skip_over_blank(*value + 1);
     241  
     242  	if (**value == '"') {
     243  		char *p = end = *value + 1;
     244  
     245  		/* convert 'foo\"bar'  to 'foo"bar' */
     246  		while (*p) {
     247  			if (*p == '\\') {
     248  				p++;
     249  				*end = *p;
     250  			} else {
     251  				*end = *p;
     252  				if (*p == '"')
     253  					break;
     254  			}
     255  			p++;
     256  			end++;
     257  		}
     258  
     259  		if (*end != '"') {
     260  			DBG(READ, ul_debug("unbalanced quotes at: %s", *value));
     261  			*cp = *value;
     262  			return -BLKID_ERR_CACHE;
     263  		}
     264  		(*value)++;
     265  		*end = '\0';
     266  		end = ++p;
     267  	} else {
     268  		end = skip_over_word(*value);
     269  		if (*end) {
     270  			*end = '\0';
     271  			end++;
     272  		}
     273  	}
     274  	*cp = end;
     275  
     276  	return 1;
     277  }
     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  {
     288  	char *name = NULL;
     289  	char *value = NULL;
     290  	int ret;
     291  
     292  	if (!cache || !dev)
     293  		return -BLKID_ERR_PARAM;
     294  
     295  	if ((ret = parse_token(&name, &value, cp)) <= 0)
     296  		return ret;
     297  
     298  	DBG(READ, ul_debug("tag: %s=\"%s\"", name, value));
     299  
     300  	errno = 0;
     301  
     302  	/* Some tags are stored directly in the device struct */
     303  	if (!strcmp(name, "DEVNO")) {
     304  		dev->bid_devno = strtoull(value, NULL, 0);
     305  		if (errno)
     306  			return -errno;
     307  	} else if (!strcmp(name, "PRI")) {
     308  		dev->bid_pri = strtol(value, NULL, 0);
     309  		if (errno)
     310  			return -errno;
     311  	} else if (!strcmp(name, "TIME")) {
     312  		char *end = NULL;
     313  
     314  		dev->bid_time = strtoull(value, &end, 0);
     315  		if (errno == 0 && end && *end == '.')
     316  			dev->bid_utime = strtoull(end + 1, NULL, 0);
     317  		if (errno)
     318  			return -errno;
     319  	} else
     320  		ret = blkid_set_tag(dev, name, value, strlen(value));
     321  
     322  	return ret < 0 ? ret : 1;
     323  }
     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  {
     338  	blkid_dev dev;
     339  	int ret;
     340  
     341  	if (!cache || !dev_p)
     342  		return -BLKID_ERR_PARAM;
     343  
     344  	*dev_p = NULL;
     345  
     346  	DBG(READ, ul_debug("line: %s", cp));
     347  
     348  	if ((ret = parse_dev(cache, dev_p, &cp)) <= 0)
     349  		return ret;
     350  
     351  	dev = *dev_p;
     352  
     353  	while ((ret = parse_tag(cache, dev, &cp)) > 0) {
     354  		;
     355  	}
     356  
     357  	if (dev->bid_type == NULL) {
     358  		DBG(READ, ul_debug("blkid: device %s has no TYPE",dev->bid_name));
     359  		blkid_free_dev(dev);
     360  		goto done;
     361  	}
     362  
     363  done:
     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  	/*
     380  	 * If the file doesn't exist, then we just return an empty
     381  	 * struct so that the cache can be populated.
     382  	 */
     383  	if ((fd = open(cache->bic_filename, O_RDONLY|O_CLOEXEC)) < 0)
     384  		return;
     385  	if (fstat(fd, &st) < 0)
     386  		goto errout;
     387  	if ((st.st_mtime == cache->bic_ftime) ||
     388  	    (cache->bic_flags & BLKID_BIC_FL_CHANGED)) {
     389  		DBG(CACHE, ul_debug("skipping re-read of %s",
     390  					cache->bic_filename));
     391  		goto errout;
     392  	}
     393  
     394  	DBG(CACHE, ul_debug("reading cache file %s",
     395  				cache->bic_filename));
     396  
     397  	file = fdopen(fd, "r" UL_CLOEXECSTR);
     398  	if (!file)
     399  		goto errout;
     400  
     401  	while (fgets(buf, sizeof(buf), file)) {
     402  		blkid_dev dev;
     403  		unsigned int end;
     404  
     405  		lineno++;
     406  		if (buf[0] == 0)
     407  			continue;
     408  		end = strlen(buf) - 1;
     409  		/* Continue reading next line if it ends with a backslash */
     410  		while (end < (sizeof(buf) - 2) && buf[end] == '\\' &&
     411  		       fgets(buf + end, sizeof(buf) - end, file)) {
     412  			end = strlen(buf) - 1;
     413  			lineno++;
     414  		}
     415  
     416  		if (blkid_parse_line(cache, &dev, buf) < 0) {
     417  			DBG(READ, ul_debug("blkid: bad format on line %d", lineno));
     418  			continue;
     419  		}
     420  	}
     421  	fclose(file);
     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  }
     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