(root)/
util-linux-2.39/
libmount/
src/
cache.c
       1  /* SPDX-License-Identifier: LGPL-2.1-or-later */
       2  
       3  /*
       4   * This file is part of libmount from util-linux project.
       5   *
       6   * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
       7   *
       8   * libmount is free software; you can redistribute it and/or modify it
       9   * under the terms of the GNU Lesser General Public License as published by
      10   * the Free Software Foundation; either version 2.1 of the License, or
      11   * (at your option) any later version.
      12   */
      13  
      14  /**
      15   * SECTION: cache
      16   * @title: Cache
      17   * @short_description: paths and tags (UUID/LABEL) caching
      18   *
      19   * The cache is a very simple API for working with tags (LABEL, UUID, ...) and
      20   * paths. The cache uses libblkid as a backend for TAGs resolution.
      21   *
      22   * All returned paths are always canonicalized.
      23   */
      24  #include <string.h>
      25  #include <stdlib.h>
      26  #include <ctype.h>
      27  #include <limits.h>
      28  #include <sys/stat.h>
      29  #include <unistd.h>
      30  #include <fcntl.h>
      31  #include <blkid.h>
      32  
      33  #include "canonicalize.h"
      34  #include "mountP.h"
      35  #include "loopdev.h"
      36  #include "strutils.h"
      37  
      38  /*
      39   * Canonicalized (resolved) paths & tags cache
      40   */
      41  #define MNT_CACHE_CHUNKSZ	128
      42  
      43  #define MNT_CACHE_ISTAG		(1 << 1) /* entry is TAG */
      44  #define MNT_CACHE_ISPATH	(1 << 2) /* entry is path */
      45  #define MNT_CACHE_TAGREAD	(1 << 3) /* tag read by mnt_cache_read_tags() */
      46  
      47  /* path cache entry */
      48  struct mnt_cache_entry {
      49  	char			*key;	/* search key (e.g. uncanonicalized path) */
      50  	char			*value;	/* value (e.g. canonicalized path) */
      51  	int			flag;
      52  };
      53  
      54  struct libmnt_cache {
      55  	struct mnt_cache_entry	*ents;
      56  	size_t			nents;
      57  	size_t			nallocs;
      58  	int			refcount;
      59  	int			probe_sb_extra;	/* extra BLKID_SUBLKS_* flags */
      60  
      61  	/* blkid_evaluate_tag() works in two ways:
      62  	 *
      63  	 * 1/ all tags are evaluated by udev /dev/disk/by-* symlinks,
      64  	 *    then the blkid_cache is NULL.
      65  	 *
      66  	 * 2/ all tags are read from blkid.tab and verified by /dev
      67  	 *    scanning, then the blkid_cache is not NULL and then it's
      68  	 *    better to reuse the blkid_cache.
      69  	 */
      70  	blkid_cache		bc;
      71  
      72  	struct libmnt_table	*mountinfo;
      73  };
      74  
      75  /**
      76   * mnt_new_cache:
      77   *
      78   * Returns: new struct libmnt_cache instance or NULL in case of ENOMEM error.
      79   */
      80  struct libmnt_cache *mnt_new_cache(void)
      81  {
      82  	struct libmnt_cache *cache = calloc(1, sizeof(*cache));
      83  	if (!cache)
      84  		return NULL;
      85  	DBG(CACHE, ul_debugobj(cache, "alloc"));
      86  	cache->refcount = 1;
      87  	return cache;
      88  }
      89  
      90  /**
      91   * mnt_free_cache:
      92   * @cache: pointer to struct libmnt_cache instance
      93   *
      94   * Deallocates the cache. This function does not care about reference count. Don't
      95   * use this function directly -- it's better to use mnt_unref_cache().
      96   */
      97  void mnt_free_cache(struct libmnt_cache *cache)
      98  {
      99  	size_t i;
     100  
     101  	if (!cache)
     102  		return;
     103  
     104  	DBG(CACHE, ul_debugobj(cache, "free [refcount=%d]", cache->refcount));
     105  
     106  	for (i = 0; i < cache->nents; i++) {
     107  		struct mnt_cache_entry *e = &cache->ents[i];
     108  		if (e->value != e->key)
     109  			free(e->value);
     110  		free(e->key);
     111  	}
     112  	free(cache->ents);
     113  	if (cache->bc)
     114  		blkid_put_cache(cache->bc);
     115  	free(cache);
     116  }
     117  
     118  /**
     119   * mnt_ref_cache:
     120   * @cache: cache pointer
     121   *
     122   * Increments reference counter.
     123   */
     124  void mnt_ref_cache(struct libmnt_cache *cache)
     125  {
     126  	if (cache) {
     127  		cache->refcount++;
     128  		/*DBG(CACHE, ul_debugobj(cache, "ref=%d", cache->refcount));*/
     129  	}
     130  }
     131  
     132  /**
     133   * mnt_unref_cache:
     134   * @cache: cache pointer
     135   *
     136   * De-increments reference counter, on zero the cache is automatically
     137   * deallocated by mnt_free_cache().
     138   */
     139  void mnt_unref_cache(struct libmnt_cache *cache)
     140  {
     141  	if (cache) {
     142  		cache->refcount--;
     143  		/*DBG(CACHE, ul_debugobj(cache, "unref=%d", cache->refcount));*/
     144  		if (cache->refcount <= 0) {
     145  			mnt_unref_table(cache->mountinfo);
     146  
     147  			mnt_free_cache(cache);
     148  		}
     149  	}
     150  }
     151  
     152  /**
     153   * mnt_cache_set_targets:
     154   * @cache: cache pointer
     155   * @mountinfo: table with already canonicalized mountpoints
     156   *
     157   * Add to @cache reference to @mountinfo. This can be used to avoid unnecessary paths
     158   * canonicalization in mnt_resolve_target().
     159   *
     160   * Returns: negative number in case of error, or 0 o success.
     161   */
     162  int mnt_cache_set_targets(struct libmnt_cache *cache,
     163  				struct libmnt_table *mountinfo)
     164  {
     165  	if (!cache)
     166  		return -EINVAL;
     167  
     168  	mnt_ref_table(mountinfo);
     169  	mnt_unref_table(cache->mountinfo);
     170  	cache->mountinfo = mountinfo;
     171  	return 0;
     172  }
     173  
     174  /**
     175   * mnt_cache_set_sbprobe:
     176   * @cache: cache pointer
     177   * @flags: BLKID_SUBLKS_* flags
     178   *
     179   * Add extra flags to the libblkid prober. Don't use if not sure.
     180   *
     181   * Returns: negative number in case of error, or 0 o success.
     182   */
     183  int mnt_cache_set_sbprobe(struct libmnt_cache *cache, int flags)
     184  {
     185  	if (!cache)
     186  		return -EINVAL;
     187  
     188  	cache->probe_sb_extra = flags;
     189  	return 0;
     190  }
     191  
     192  /* note that the @key could be the same pointer as @value */
     193  static int cache_add_entry(struct libmnt_cache *cache, char *key,
     194  					char *value, int flag)
     195  {
     196  	struct mnt_cache_entry *e;
     197  
     198  	assert(cache);
     199  	assert(value);
     200  	assert(key);
     201  
     202  	if (cache->nents == cache->nallocs) {
     203  		size_t sz = cache->nallocs + MNT_CACHE_CHUNKSZ;
     204  
     205  		e = realloc(cache->ents, sz * sizeof(struct mnt_cache_entry));
     206  		if (!e)
     207  			return -ENOMEM;
     208  		cache->ents = e;
     209  		cache->nallocs = sz;
     210  	}
     211  
     212  	e = &cache->ents[cache->nents];
     213  	e->key = key;
     214  	e->value = value;
     215  	e->flag = flag;
     216  	cache->nents++;
     217  
     218  	DBG(CACHE, ul_debugobj(cache, "add entry [%2zd] (%s): %s: %s",
     219  			cache->nents,
     220  			(flag & MNT_CACHE_ISPATH) ? "path" : "tag",
     221  			value, key));
     222  	return 0;
     223  }
     224  
     225  /* add tag to the cache, @devname has to be an allocated string */
     226  static int cache_add_tag(struct libmnt_cache *cache, const char *tagname,
     227  				const char *tagval, char *devname, int flag)
     228  {
     229  	size_t tksz, vlsz;
     230  	char *key;
     231  	int rc;
     232  
     233  	assert(cache);
     234  	assert(devname);
     235  	assert(tagname);
     236  	assert(tagval);
     237  
     238  	/* add into cache -- cache format for TAGs is
     239  	 *	key    = "TAG_NAME\0TAG_VALUE\0"
     240  	 *	value  = "/dev/foo"
     241  	 */
     242  	tksz = strlen(tagname);
     243  	vlsz = strlen(tagval);
     244  
     245  	key = malloc(tksz + vlsz + 2);
     246  	if (!key)
     247  		return -ENOMEM;
     248  
     249  	memcpy(key, tagname, tksz + 1);	   /* include '\0' */
     250  	memcpy(key + tksz + 1, tagval, vlsz + 1);
     251  
     252  	rc = cache_add_entry(cache, key, devname, flag | MNT_CACHE_ISTAG);
     253  	if (!rc)
     254  		return 0;
     255  
     256  	free(key);
     257  	return rc;
     258  }
     259  
     260  
     261  /*
     262   * Returns cached canonicalized path or NULL.
     263   */
     264  static const char *cache_find_path(struct libmnt_cache *cache, const char *path)
     265  {
     266  	size_t i;
     267  
     268  	if (!cache || !path)
     269  		return NULL;
     270  
     271  	for (i = 0; i < cache->nents; i++) {
     272  		struct mnt_cache_entry *e = &cache->ents[i];
     273  		if (!(e->flag & MNT_CACHE_ISPATH))
     274  			continue;
     275  		if (streq_paths(path, e->key))
     276  			return e->value;
     277  	}
     278  	return NULL;
     279  }
     280  
     281  /*
     282   * Returns cached path or NULL.
     283   */
     284  static const char *cache_find_tag(struct libmnt_cache *cache,
     285  			const char *token, const char *value)
     286  {
     287  	size_t i;
     288  	size_t tksz;
     289  
     290  	if (!cache || !token || !value)
     291  		return NULL;
     292  
     293  	tksz = strlen(token);
     294  
     295  	for (i = 0; i < cache->nents; i++) {
     296  		struct mnt_cache_entry *e = &cache->ents[i];
     297  		if (!(e->flag & MNT_CACHE_ISTAG))
     298  			continue;
     299  		if (strcmp(token, e->key) == 0 &&
     300  		    strcmp(value, e->key + tksz + 1) == 0)
     301  			return e->value;
     302  	}
     303  	return NULL;
     304  }
     305  
     306  static char *cache_find_tag_value(struct libmnt_cache *cache,
     307  			const char *devname, const char *token)
     308  {
     309  	size_t i;
     310  
     311  	assert(cache);
     312  	assert(devname);
     313  	assert(token);
     314  
     315  	for (i = 0; i < cache->nents; i++) {
     316  		struct mnt_cache_entry *e = &cache->ents[i];
     317  		if (!(e->flag & MNT_CACHE_ISTAG))
     318  			continue;
     319  		if (strcmp(e->value, devname) == 0 &&	/* dev name */
     320  		    strcmp(token, e->key) == 0)	/* tag name */
     321  			return e->key + strlen(token) + 1;	/* tag value */
     322  	}
     323  
     324  	return NULL;
     325  }
     326  
     327  /**
     328   * mnt_cache_read_tags
     329   * @cache: pointer to struct libmnt_cache instance
     330   * @devname: path device
     331   *
     332   * Reads @devname LABEL and UUID to the @cache.
     333   *
     334   * Returns: 0 if at least one tag was added, 1 if no tag was added or
     335   *          negative number in case of error.
     336   */
     337  int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname)
     338  {
     339  	blkid_probe pr;
     340  	size_t i, ntags = 0;
     341  	int rc;
     342  	const char *tags[] = { "LABEL", "UUID", "TYPE", "PARTUUID", "PARTLABEL" };
     343  	const char *blktags[] = { "LABEL", "UUID", "TYPE", "PART_ENTRY_UUID", "PART_ENTRY_NAME" };
     344  
     345  	if (!cache || !devname)
     346  		return -EINVAL;
     347  
     348  	DBG(CACHE, ul_debugobj(cache, "tags for %s requested", devname));
     349  
     350  	/* check if device is already cached */
     351  	for (i = 0; i < cache->nents; i++) {
     352  		struct mnt_cache_entry *e = &cache->ents[i];
     353  		if (!(e->flag & MNT_CACHE_TAGREAD))
     354  			continue;
     355  		if (strcmp(e->value, devname) == 0)
     356  			/* tags have already been read */
     357  			return 0;
     358  	}
     359  
     360  	pr =  blkid_new_probe_from_filename(devname);
     361  	if (!pr)
     362  		return -1;
     363  
     364  	blkid_probe_enable_superblocks(pr, 1);
     365  	blkid_probe_set_superblocks_flags(pr,
     366  			BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
     367  			BLKID_SUBLKS_TYPE | cache->probe_sb_extra);
     368  
     369  	blkid_probe_enable_partitions(pr, 1);
     370  	blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
     371  
     372  	rc = blkid_do_safeprobe(pr);
     373  	if (rc)
     374  		goto error;
     375  
     376  	DBG(CACHE, ul_debugobj(cache, "reading tags for: %s", devname));
     377  
     378  	for (i = 0; i < ARRAY_SIZE(tags); i++) {
     379  		const char *data;
     380  		char *dev;
     381  
     382  		if (cache_find_tag_value(cache, devname, tags[i])) {
     383  			DBG(CACHE, ul_debugobj(cache,
     384  					"\ntag %s already cached", tags[i]));
     385  			continue;
     386  		}
     387  		if (blkid_probe_lookup_value(pr, blktags[i], &data, NULL))
     388  			continue;
     389  		dev = strdup(devname);
     390  		if (!dev)
     391  			goto error;
     392  		if (cache_add_tag(cache, tags[i], data, dev,
     393  					MNT_CACHE_TAGREAD)) {
     394  			free(dev);
     395  			goto error;
     396  		}
     397  		ntags++;
     398  	}
     399  
     400  	DBG(CACHE, ul_debugobj(cache, "\tread %zd tags", ntags));
     401  	blkid_free_probe(pr);
     402  	return ntags ? 0 : 1;
     403  error:
     404  	blkid_free_probe(pr);
     405  	return rc < 0 ? rc : -1;
     406  }
     407  
     408  /**
     409   * mnt_cache_device_has_tag:
     410   * @cache: paths cache
     411   * @devname: path to the device
     412   * @token: tag name (e.g "LABEL")
     413   * @value: tag value
     414   *
     415   * Look up @cache to check if @tag+@value are associated with @devname.
     416   *
     417   * Returns: 1 on success or 0.
     418   */
     419  int mnt_cache_device_has_tag(struct libmnt_cache *cache, const char *devname,
     420  				const char *token, const char *value)
     421  {
     422  	const char *path = cache_find_tag(cache, token, value);
     423  
     424  	if (path && devname && strcmp(path, devname) == 0)
     425  		return 1;
     426  	return 0;
     427  }
     428  
     429  static int __mnt_cache_find_tag_value(struct libmnt_cache *cache,
     430  		const char *devname, const char *token, char **data)
     431  {
     432  	int rc = 0;
     433  
     434  	if (!cache || !devname || !token || !data)
     435  		return -EINVAL;
     436  
     437  	rc = mnt_cache_read_tags(cache, devname);
     438  	if (rc)
     439  		return rc;
     440  
     441  	*data = cache_find_tag_value(cache, devname, token);
     442  	return *data ? 0 : -1;
     443  }
     444  
     445  /**
     446   * mnt_cache_find_tag_value:
     447   * @cache: cache for results
     448   * @devname: device name
     449   * @token: tag name ("LABEL" or "UUID")
     450   *
     451   * Returns: LABEL or UUID for the @devname or NULL in case of error.
     452   */
     453  char *mnt_cache_find_tag_value(struct libmnt_cache *cache,
     454  		const char *devname, const char *token)
     455  {
     456  	char *data = NULL;
     457  
     458  	if (__mnt_cache_find_tag_value(cache, devname, token, &data) == 0)
     459  		return data;
     460  	return NULL;
     461  }
     462  
     463  /**
     464   * mnt_get_fstype:
     465   * @devname: device name
     466   * @ambi: returns TRUE if probing result is ambivalent (optional argument)
     467   * @cache: cache for results or NULL
     468   *
     469   * Returns: filesystem type or NULL in case of error. The result has to be
     470   * deallocated by free() if @cache is NULL.
     471   */
     472  char *mnt_get_fstype(const char *devname, int *ambi, struct libmnt_cache *cache)
     473  {
     474  	blkid_probe pr;
     475  	const char *data;
     476  	char *type = NULL;
     477  	int rc;
     478  
     479  	DBG(CACHE, ul_debugobj(cache, "get %s FS type", devname));
     480  
     481  	if (cache) {
     482  		char *val = NULL;
     483  		rc = __mnt_cache_find_tag_value(cache, devname, "TYPE", &val);
     484  		if (ambi)
     485  			*ambi = rc == -2 ? TRUE : FALSE;
     486  		return rc ? NULL : val;
     487  	}
     488  
     489  	/*
     490  	 * no cache, probe directly
     491  	 */
     492  	pr =  blkid_new_probe_from_filename(devname);
     493  	if (!pr)
     494  		return NULL;
     495  
     496  	blkid_probe_enable_superblocks(pr, 1);
     497  	blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE);
     498  
     499  	rc = blkid_do_safeprobe(pr);
     500  
     501  	DBG(CACHE, ul_debugobj(cache, "libblkid rc=%d", rc));
     502  
     503  	if (!rc && !blkid_probe_lookup_value(pr, "TYPE", &data, NULL))
     504  		type = strdup(data);
     505  
     506  	if (ambi)
     507  		*ambi = rc == -2 ? TRUE : FALSE;
     508  
     509  	blkid_free_probe(pr);
     510  	return type;
     511  }
     512  
     513  static char *canonicalize_path_and_cache(const char *path,
     514  						struct libmnt_cache *cache)
     515  {
     516  	char *p;
     517  	char *key;
     518  	char *value;
     519  
     520  	DBG(CACHE, ul_debugobj(cache, "canonicalize path %s", path));
     521  	p = canonicalize_path(path);
     522  
     523  	if (p && cache) {
     524  		value = p;
     525  		key = strcmp(path, p) == 0 ? value : strdup(path);
     526  
     527  		if (!key || !value)
     528  			goto error;
     529  
     530  		if (cache_add_entry(cache, key, value,
     531  				MNT_CACHE_ISPATH))
     532  			goto error;
     533  	}
     534  
     535  	return p;
     536  error:
     537  	if (value != key)
     538  		free(value);
     539  	free(key);
     540  	return NULL;
     541  }
     542  
     543  /**
     544   * mnt_resolve_path:
     545   * @path: "native" path
     546   * @cache: cache for results or NULL
     547   *
     548   * Converts path:
     549   *	- to the absolute path
     550   *	- /dev/dm-N to /dev/mapper/name
     551   *
     552   * Returns: absolute path or NULL in case of error. The result has to be
     553   * deallocated by free() if @cache is NULL.
     554   */
     555  char *mnt_resolve_path(const char *path, struct libmnt_cache *cache)
     556  {
     557  	char *p = NULL;
     558  
     559  	/*DBG(CACHE, ul_debugobj(cache, "resolving path %s", path));*/
     560  
     561  	if (!path)
     562  		return NULL;
     563  	if (cache)
     564  		p = (char *) cache_find_path(cache, path);
     565  	if (!p)
     566  		p = canonicalize_path_and_cache(path, cache);
     567  
     568  	return p;
     569  }
     570  
     571  /**
     572   * mnt_resolve_target:
     573   * @path: "native" path, a potential mount point
     574   * @cache: cache for results or NULL.
     575   *
     576   * Like mnt_resolve_path(), unless @cache is not NULL and
     577   * mnt_cache_set_targets(cache, mountinfo) was called: if @path is found in the
     578   * cached @mountinfo and the matching entry was provided by the kernel, assume that
     579   * @path is already canonicalized. By avoiding a call to realpath(2) on
     580   * known mount points, there is a lower risk of stepping on a stale mount
     581   * point, which can result in an application freeze. This is also faster in
     582   * general, as stat(2) on a mount point is slower than on a regular file.
     583   *
     584   * Returns: absolute path or NULL in case of error. The result has to be
     585   * deallocated by free() if @cache is NULL.
     586   */
     587  char *mnt_resolve_target(const char *path, struct libmnt_cache *cache)
     588  {
     589  	char *p = NULL;
     590  
     591  	/*DBG(CACHE, ul_debugobj(cache, "resolving target %s", path));*/
     592  
     593  	if (!cache || !cache->mountinfo)
     594  		return mnt_resolve_path(path, cache);
     595  
     596  	p = (char *) cache_find_path(cache, path);
     597  	if (p)
     598  		return p;
     599  
     600  	{
     601  		struct libmnt_iter itr;
     602  		struct libmnt_fs *fs = NULL;
     603  
     604  		mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
     605  		while (mnt_table_next_fs(cache->mountinfo, &itr, &fs) == 0) {
     606  
     607  			if (!mnt_fs_is_kernel(fs)
     608  			     || mnt_fs_is_swaparea(fs)
     609  			     || !mnt_fs_streq_target(fs, path))
     610  				continue;
     611  
     612  			p = strdup(path);
     613  			if (!p)
     614  				return NULL;	/* ENOMEM */
     615  
     616  			if (cache_add_entry(cache, p, p, MNT_CACHE_ISPATH)) {
     617  				free(p);
     618  				return NULL;	/* ENOMEM */
     619  			}
     620  			break;
     621  		}
     622  	}
     623  
     624  	if (!p)
     625  		p = canonicalize_path_and_cache(path, cache);
     626  	return p;
     627  }
     628  
     629  /**
     630   * mnt_pretty_path:
     631   * @path: any path
     632   * @cache: NULL or pointer to the cache
     633   *
     634   * Converts path:
     635   *	- to the absolute path
     636   *	- /dev/dm-N to /dev/mapper/name
     637   *	- /dev/loopN to the loop backing filename
     638   *	- empty path (NULL) to 'none'
     639   *
     640   * Returns: newly allocated string with path, result always has to be deallocated
     641   *          by free().
     642   */
     643  char *mnt_pretty_path(const char *path, struct libmnt_cache *cache)
     644  {
     645  	char *pretty = mnt_resolve_path(path, cache);
     646  
     647  	if (!pretty)
     648  		return strdup("none");
     649  
     650  #ifdef __linux__
     651  	/* users assume backing file name rather than /dev/loopN in
     652  	 * output if the device has been initialized by mount(8).
     653  	 */
     654  	if (strncmp(pretty, "/dev/loop", 9) == 0) {
     655  		struct loopdev_cxt lc;
     656  
     657  		if (loopcxt_init(&lc, 0) || loopcxt_set_device(&lc, pretty))
     658  			goto done;
     659  
     660  		if (loopcxt_is_autoclear(&lc)) {
     661  			char *tmp = loopcxt_get_backing_file(&lc);
     662  			if (tmp) {
     663  				loopcxt_deinit(&lc);
     664  				if (!cache)
     665  					free(pretty);	/* not cached, deallocate */
     666  				return tmp;		/* return backing file */
     667  			}
     668  		}
     669  		loopcxt_deinit(&lc);
     670  
     671  	}
     672  #endif
     673  
     674  done:
     675  	/* don't return pointer to the cache, allocate a new string */
     676  	return cache ? strdup(pretty) : pretty;
     677  }
     678  
     679  /**
     680   * mnt_resolve_tag:
     681   * @token: tag name
     682   * @value: tag value
     683   * @cache: for results or NULL
     684   *
     685   * Returns: device name or NULL in case of error. The result has to be
     686   * deallocated by free() if @cache is NULL.
     687   */
     688  char *mnt_resolve_tag(const char *token, const char *value,
     689  		      struct libmnt_cache *cache)
     690  {
     691  	char *p = NULL;
     692  
     693  	/*DBG(CACHE, ul_debugobj(cache, "resolving tag token=%s value=%s",
     694  				token, value));*/
     695  
     696  	if (!token || !value)
     697  		return NULL;
     698  
     699  	if (cache)
     700  		p = (char *) cache_find_tag(cache, token, value);
     701  
     702  	if (!p) {
     703  		/* returns newly allocated string */
     704  		p = blkid_evaluate_tag(token, value, cache ? &cache->bc : NULL);
     705  
     706  		if (p && cache &&
     707  		    cache_add_tag(cache, token, value, p, 0))
     708  				goto error;
     709  	}
     710  
     711  	return p;
     712  error:
     713  	free(p);
     714  	return NULL;
     715  }
     716  
     717  
     718  
     719  /**
     720   * mnt_resolve_spec:
     721   * @spec: path or tag
     722   * @cache: paths cache
     723   *
     724   * Returns: canonicalized path or NULL. The result has to be
     725   * deallocated by free() if @cache is NULL.
     726   */
     727  char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache)
     728  {
     729  	char *cn = NULL;
     730  	char *t = NULL, *v = NULL;
     731  
     732  	if (!spec)
     733  		return NULL;
     734  
     735  	if (blkid_parse_tag_string(spec, &t, &v) == 0 && mnt_valid_tagname(t))
     736  		cn = mnt_resolve_tag(t, v, cache);
     737  	else
     738  		cn = mnt_resolve_path(spec, cache);
     739  
     740  	free(t);
     741  	free(v);
     742  	return cn;
     743  }
     744  
     745  
     746  #ifdef TEST_PROGRAM
     747  
     748  static int test_resolve_path(struct libmnt_test *ts, int argc, char *argv[])
     749  {
     750  	char line[BUFSIZ];
     751  	struct libmnt_cache *cache;
     752  
     753  	cache = mnt_new_cache();
     754  	if (!cache)
     755  		return -ENOMEM;
     756  
     757  	while(fgets(line, sizeof(line), stdin)) {
     758  		size_t sz = strlen(line);
     759  		char *p;
     760  
     761  		if (sz > 0 && line[sz - 1] == '\n')
     762  			line[sz - 1] = '\0';
     763  
     764  		p = mnt_resolve_path(line, cache);
     765  		printf("%s : %s\n", line, p);
     766  	}
     767  	mnt_unref_cache(cache);
     768  	return 0;
     769  }
     770  
     771  static int test_resolve_spec(struct libmnt_test *ts, int argc, char *argv[])
     772  {
     773  	char line[BUFSIZ];
     774  	struct libmnt_cache *cache;
     775  
     776  	cache = mnt_new_cache();
     777  	if (!cache)
     778  		return -ENOMEM;
     779  
     780  	while(fgets(line, sizeof(line), stdin)) {
     781  		size_t sz = strlen(line);
     782  		char *p;
     783  
     784  		if (sz > 0 && line[sz - 1] == '\n')
     785  			line[sz - 1] = '\0';
     786  
     787  		p = mnt_resolve_spec(line, cache);
     788  		printf("%s : %s\n", line, p);
     789  	}
     790  	mnt_unref_cache(cache);
     791  	return 0;
     792  }
     793  
     794  static int test_read_tags(struct libmnt_test *ts, int argc, char *argv[])
     795  {
     796  	char line[BUFSIZ];
     797  	struct libmnt_cache *cache;
     798  	size_t i;
     799  
     800  	cache = mnt_new_cache();
     801  	if (!cache)
     802  		return -ENOMEM;
     803  
     804  	while(fgets(line, sizeof(line), stdin)) {
     805  		size_t sz = strlen(line);
     806  		char *t = NULL, *v = NULL;
     807  
     808  		if (sz > 0 && line[sz - 1] == '\n')
     809  			line[sz - 1] = '\0';
     810  
     811  		if (!strcmp(line, "quit"))
     812  			break;
     813  
     814  		if (*line == '/') {
     815  			if (mnt_cache_read_tags(cache, line) < 0)
     816  				fprintf(stderr, "%s: read tags failed\n", line);
     817  
     818  		} else if (blkid_parse_tag_string(line, &t, &v) == 0) {
     819  			const char *cn = NULL;
     820  
     821  			if (mnt_valid_tagname(t))
     822  				cn = cache_find_tag(cache, t, v);
     823  			free(t);
     824  			free(v);
     825  
     826  			if (cn)
     827  				printf("%s: %s\n", line, cn);
     828  			else
     829  				printf("%s: not cached\n", line);
     830  		}
     831  	}
     832  
     833  	for (i = 0; i < cache->nents; i++) {
     834  		struct mnt_cache_entry *e = &cache->ents[i];
     835  		if (!(e->flag & MNT_CACHE_ISTAG))
     836  			continue;
     837  
     838  		printf("%15s : %5s : %s\n", e->value, e->key,
     839  				e->key + strlen(e->key) + 1);
     840  	}
     841  
     842  	mnt_unref_cache(cache);
     843  	return 0;
     844  
     845  }
     846  
     847  int main(int argc, char *argv[])
     848  {
     849  	struct libmnt_test ts[] = {
     850  		{ "--resolve-path", test_resolve_path, "  resolve paths from stdin" },
     851  		{ "--resolve-spec", test_resolve_spec, "  evaluate specs from stdin" },
     852  		{ "--read-tags", test_read_tags,       "  read devname or TAG from stdin (\"quit\" to exit)" },
     853  		{ NULL }
     854  	};
     855  
     856  	return mnt_run_test(ts, argc, argv);
     857  }
     858  #endif