blob: b52ca809801a9ca1f7fe2c485b978b1ea91287b8 [file] [log] [blame]
/*
* Copyright (C) 2009-2011 Karel Zak <kzak@redhat.com>
*
* This file may be redistributed under the terms of the
* GNU Lesser General Public License.
*/
/**
* SECTION: cache
* @title: Cache
* @short_description: paths and tags (UUID/LABEL) caching
*
* The cache is a very simple API for working with tags (LABEL, UUID, ...) and
* paths. The cache uses libblkid as a backend for TAGs resolution.
*
* All returned paths are always canonicalized.
*/
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <blkid.h>
#include "canonicalize.h"
#include "mountP.h"
#include "loopdev.h"
#include "strutils.h"
/*
* Canonicalized (resolved) paths & tags cache
*/
#define MNT_CACHE_CHUNKSZ 128
#define MNT_CACHE_ISTAG (1 << 1) /* entry is TAG */
#define MNT_CACHE_ISPATH (1 << 2) /* entry is path */
#define MNT_CACHE_TAGREAD (1 << 3) /* tag read by mnt_cache_read_tags() */
/* path cache entry */
struct mnt_cache_entry {
char *key; /* search key (e.g. uncanonicalized path) */
char *value; /* value (e.g. canonicalized path) */
int flag;
};
struct libmnt_cache {
struct mnt_cache_entry *ents;
size_t nents;
size_t nallocs;
int refcount;
/* blkid_evaluate_tag() works in two ways:
*
* 1/ all tags are evaluated by udev /dev/disk/by-* symlinks,
* then the blkid_cache is NULL.
*
* 2/ all tags are read from blkid.tab and verified by /dev
* scanning, then the blkid_cache is not NULL and then it's
* better to reuse the blkid_cache.
*/
blkid_cache bc;
struct libmnt_table *mtab;
};
/**
* mnt_new_cache:
*
* Returns: new struct libmnt_cache instance or NULL in case of ENOMEM error.
*/
struct libmnt_cache *mnt_new_cache(void)
{
struct libmnt_cache *cache = calloc(1, sizeof(*cache));
if (!cache)
return NULL;
DBG(CACHE, ul_debugobj(cache, "alloc"));
cache->refcount = 1;
return cache;
}
/**
* mnt_free_cache:
* @cache: pointer to struct libmnt_cache instance
*
* Deallocates the cache. This function does not care about reference count. Don't
* use this function directly -- it's better to use mnt_unref_cache().
*/
void mnt_free_cache(struct libmnt_cache *cache)
{
size_t i;
if (!cache)
return;
DBG(CACHE, ul_debugobj(cache, "free [refcount=%d]", cache->refcount));
for (i = 0; i < cache->nents; i++) {
struct mnt_cache_entry *e = &cache->ents[i];
if (e->value != e->key)
free(e->value);
free(e->key);
}
free(cache->ents);
if (cache->bc)
blkid_put_cache(cache->bc);
free(cache);
}
/**
* mnt_ref_cache:
* @cache: cache pointer
*
* Increments reference counter.
*/
void mnt_ref_cache(struct libmnt_cache *cache)
{
if (cache) {
cache->refcount++;
/*DBG(CACHE, ul_debugobj(cache, "ref=%d", cache->refcount));*/
}
}
/**
* mnt_unref_cache:
* @cache: cache pointer
*
* De-increments reference counter, on zero the cache is automatically
* deallocated by mnt_free_cache().
*/
void mnt_unref_cache(struct libmnt_cache *cache)
{
if (cache) {
cache->refcount--;
/*DBG(CACHE, ul_debugobj(cache, "unref=%d", cache->refcount));*/
if (cache->refcount <= 0) {
mnt_unref_table(cache->mtab);
mnt_free_cache(cache);
}
}
}
/**
* mnt_cache_set_targets:
* @cache: cache pointer
* @mtab: table with already canonicalized mountpoints
*
* Add to @cache reference to @mtab. This allows to avoid unnecessary paths
* canonicalization in mnt_resolve_target().
*
* Returns: negative number in case of error, or 0 o success.
*/
int mnt_cache_set_targets(struct libmnt_cache *cache,
struct libmnt_table *mtab)
{
if (!cache)
return -EINVAL;
mnt_ref_table(mtab);
mnt_unref_table(cache->mtab);
cache->mtab = mtab;
return 0;
}
/* note that the @key could be the same pointer as @value */
static int cache_add_entry(struct libmnt_cache *cache, char *key,
char *value, int flag)
{
struct mnt_cache_entry *e;
assert(cache);
assert(value);
assert(key);
if (cache->nents == cache->nallocs) {
size_t sz = cache->nallocs + MNT_CACHE_CHUNKSZ;
e = realloc(cache->ents, sz * sizeof(struct mnt_cache_entry));
if (!e)
return -ENOMEM;
cache->ents = e;
cache->nallocs = sz;
}
e = &cache->ents[cache->nents];
e->key = key;
e->value = value;
e->flag = flag;
cache->nents++;
DBG(CACHE, ul_debugobj(cache, "add entry [%2zd] (%s): %s: %s",
cache->nents,
(flag & MNT_CACHE_ISPATH) ? "path" : "tag",
value, key));
return 0;
}
/* add tag to the cache, @devname has to be an allocated string */
static int cache_add_tag(struct libmnt_cache *cache, const char *tagname,
const char *tagval, char *devname, int flag)
{
size_t tksz, vlsz;
char *key;
int rc;
assert(cache);
assert(devname);
assert(tagname);
assert(tagval);
/* add into cache -- cache format for TAGs is
* key = "TAG_NAME\0TAG_VALUE\0"
* value = "/dev/foo"
*/
tksz = strlen(tagname);
vlsz = strlen(tagval);
key = malloc(tksz + vlsz + 2);
if (!key)
return -ENOMEM;
memcpy(key, tagname, tksz + 1); /* include '\0' */
memcpy(key + tksz + 1, tagval, vlsz + 1);
rc = cache_add_entry(cache, key, devname, flag | MNT_CACHE_ISTAG);
if (!rc)
return 0;
free(key);
return rc;
}
/*
* Returns cached canonicalized path or NULL.
*/
static const char *cache_find_path(struct libmnt_cache *cache, const char *path)
{
size_t i;
if (!cache || !path)
return NULL;
for (i = 0; i < cache->nents; i++) {
struct mnt_cache_entry *e = &cache->ents[i];
if (!(e->flag & MNT_CACHE_ISPATH))
continue;
if (streq_paths(path, e->key))
return e->value;
}
return NULL;
}
/*
* Returns cached path or NULL.
*/
static const char *cache_find_tag(struct libmnt_cache *cache,
const char *token, const char *value)
{
size_t i;
size_t tksz;
if (!cache || !token || !value)
return NULL;
tksz = strlen(token);
for (i = 0; i < cache->nents; i++) {
struct mnt_cache_entry *e = &cache->ents[i];
if (!(e->flag & MNT_CACHE_ISTAG))
continue;
if (strcmp(token, e->key) == 0 &&
strcmp(value, e->key + tksz + 1) == 0)
return e->value;
}
return NULL;
}
static char *cache_find_tag_value(struct libmnt_cache *cache,
const char *devname, const char *token)
{
size_t i;
assert(cache);
assert(devname);
assert(token);
for (i = 0; i < cache->nents; i++) {
struct mnt_cache_entry *e = &cache->ents[i];
if (!(e->flag & MNT_CACHE_ISTAG))
continue;
if (strcmp(e->value, devname) == 0 && /* dev name */
strcmp(token, e->key) == 0) /* tag name */
return e->key + strlen(token) + 1; /* tag value */
}
return NULL;
}
/**
* mnt_cache_read_tags
* @cache: pointer to struct libmnt_cache instance
* @devname: path device
*
* Reads @devname LABEL and UUID to the @cache.
*
* Returns: 0 if at least one tag was added, 1 if no tag was added or
* negative number in case of error.
*/
int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname)
{
blkid_probe pr;
size_t i, ntags = 0;
int rc;
const char *tags[] = { "LABEL", "UUID", "TYPE", "PARTUUID", "PARTLABEL" };
const char *blktags[] = { "LABEL", "UUID", "TYPE", "PART_ENTRY_UUID", "PART_ENTRY_NAME" };
if (!cache || !devname)
return -EINVAL;
DBG(CACHE, ul_debugobj(cache, "tags for %s requested", devname));
/* check if device is already cached */
for (i = 0; i < cache->nents; i++) {
struct mnt_cache_entry *e = &cache->ents[i];
if (!(e->flag & MNT_CACHE_TAGREAD))
continue;
if (strcmp(e->value, devname) == 0)
/* tags have already been read */
return 0;
}
pr = blkid_new_probe_from_filename(devname);
if (!pr)
return -1;
blkid_probe_enable_superblocks(pr, 1);
blkid_probe_set_superblocks_flags(pr,
BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
BLKID_SUBLKS_TYPE);
blkid_probe_enable_partitions(pr, 1);
blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
rc = blkid_do_safeprobe(pr);
if (rc)
goto error;
DBG(CACHE, ul_debugobj(cache, "reading tags for: %s", devname));
for (i = 0; i < ARRAY_SIZE(tags); i++) {
const char *data;
char *dev;
if (cache_find_tag_value(cache, devname, tags[i])) {
DBG(CACHE, ul_debugobj(cache,
"\ntag %s already cached", tags[i]));
continue;
}
if (blkid_probe_lookup_value(pr, blktags[i], &data, NULL))
continue;
dev = strdup(devname);
if (!dev)
goto error;
if (cache_add_tag(cache, tags[i], data, dev,
MNT_CACHE_TAGREAD)) {
free(dev);
goto error;
}
ntags++;
}
DBG(CACHE, ul_debugobj(cache, "\tread %zd tags", ntags));
blkid_free_probe(pr);
return ntags ? 0 : 1;
error:
blkid_free_probe(pr);
return rc < 0 ? rc : -1;
}
/**
* mnt_cache_device_has_tag:
* @cache: paths cache
* @devname: path to the device
* @token: tag name (e.g "LABEL")
* @value: tag value
*
* Look up @cache to check if @tag+@value are associated with @devname.
*
* Returns: 1 on success or 0.
*/
int mnt_cache_device_has_tag(struct libmnt_cache *cache, const char *devname,
const char *token, const char *value)
{
const char *path = cache_find_tag(cache, token, value);
if (path && devname && strcmp(path, devname) == 0)
return 1;
return 0;
}
static int __mnt_cache_find_tag_value(struct libmnt_cache *cache,
const char *devname, const char *token, char **data)
{
int rc = 0;
if (!cache || !devname || !token || !data)
return -EINVAL;
rc = mnt_cache_read_tags(cache, devname);
if (rc)
return rc;
*data = cache_find_tag_value(cache, devname, token);
return *data ? 0 : -1;
}
/**
* mnt_cache_find_tag_value:
* @cache: cache for results
* @devname: device name
* @token: tag name ("LABEL" or "UUID")
*
* Returns: LABEL or UUID for the @devname or NULL in case of error.
*/
char *mnt_cache_find_tag_value(struct libmnt_cache *cache,
const char *devname, const char *token)
{
char *data = NULL;
if (__mnt_cache_find_tag_value(cache, devname, token, &data) == 0)
return data;
return NULL;
}
/**
* mnt_get_fstype:
* @devname: device name
* @ambi: returns TRUE if probing result is ambivalent (optional argument)
* @cache: cache for results or NULL
*
* Returns: filesystem type or NULL in case of error. The result has to be
* deallocated by free() if @cache is NULL.
*/
char *mnt_get_fstype(const char *devname, int *ambi, struct libmnt_cache *cache)
{
blkid_probe pr;
const char *data;
char *type = NULL;
int rc;
DBG(CACHE, ul_debugobj(cache, "get %s FS type", devname));
if (cache) {
char *val = NULL;
rc = __mnt_cache_find_tag_value(cache, devname, "TYPE", &val);
if (ambi)
*ambi = rc == -2 ? TRUE : FALSE;
return rc ? NULL : val;
}
/*
* no cache, probe directly
*/
pr = blkid_new_probe_from_filename(devname);
if (!pr)
return NULL;
blkid_probe_enable_superblocks(pr, 1);
blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE);
rc = blkid_do_safeprobe(pr);
DBG(CACHE, ul_debugobj(cache, "libblkid rc=%d", rc));
if (!rc && !blkid_probe_lookup_value(pr, "TYPE", &data, NULL))
type = strdup(data);
if (ambi)
*ambi = rc == -2 ? TRUE : FALSE;
blkid_free_probe(pr);
return type;
}
static char *canonicalize_path_and_cache(const char *path,
struct libmnt_cache *cache)
{
char *p;
char *key;
char *value;
DBG(CACHE, ul_debugobj(cache, "canonicalize path %s", path));
p = canonicalize_path(path);
if (p && cache) {
value = p;
key = strcmp(path, p) == 0 ? value : strdup(path);
if (!key || !value)
goto error;
if (cache_add_entry(cache, key, value,
MNT_CACHE_ISPATH))
goto error;
}
return p;
error:
if (value != key)
free(value);
free(key);
return NULL;
}
/**
* mnt_resolve_path:
* @path: "native" path
* @cache: cache for results or NULL
*
* Converts path:
* - to the absolute path
* - /dev/dm-N to /dev/mapper/name
*
* Returns: absolute path or NULL in case of error. The result has to be
* deallocated by free() if @cache is NULL.
*/
char *mnt_resolve_path(const char *path, struct libmnt_cache *cache)
{
char *p = NULL;
/*DBG(CACHE, ul_debugobj(cache, "resolving path %s", path));*/
if (!path)
return NULL;
if (cache)
p = (char *) cache_find_path(cache, path);
if (!p)
p = canonicalize_path_and_cache(path, cache);
return p;
}
/**
* mnt_resolve_target:
* @path: "native" path, a potential mount point
* @cache: cache for results or NULL.
*
* Like mnt_resolve_path(), unless @cache is not NULL and
* mnt_cache_set_targets(cache, mtab) was called: if @path is found in the
* cached @mtab and the matching entry was provided by the kernel, assume that
* @path is already canonicalized. By avoiding a call to realpath(2) on
* known mount points, there is a lower risk of stepping on a stale mount
* point, which can result in an application freeze. This is also faster in
* general, as stat(2) on a mount point is slower than on a regular file.
*
* Returns: absolute path or NULL in case of error. The result has to be
* deallocated by free() if @cache is NULL.
*/
char *mnt_resolve_target(const char *path, struct libmnt_cache *cache)
{
char *p = NULL;
/*DBG(CACHE, ul_debugobj(cache, "resolving target %s", path));*/
if (!cache || !cache->mtab)
return mnt_resolve_path(path, cache);
p = (char *) cache_find_path(cache, path);
if (p)
return p;
else {
struct libmnt_iter itr;
struct libmnt_fs *fs = NULL;
mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
while (mnt_table_next_fs(cache->mtab, &itr, &fs) == 0) {
if (!mnt_fs_is_kernel(fs)
|| mnt_fs_is_swaparea(fs)
|| !mnt_fs_streq_target(fs, path))
continue;
p = strdup(path);
if (!p)
return NULL; /* ENOMEM */
if (cache_add_entry(cache, p, p, MNT_CACHE_ISPATH)) {
free(p);
return NULL; /* ENOMEM */
}
break;
}
}
if (!p)
p = canonicalize_path_and_cache(path, cache);
return p;
}
/**
* mnt_pretty_path:
* @path: any path
* @cache: NULL or pointer to the cache
*
* Converts path:
* - to the absolute path
* - /dev/dm-N to /dev/mapper/name
* - /dev/loopN to the loop backing filename
* - empty path (NULL) to 'none'
*
* Returns: newly allocated string with path, result always has to be deallocated
* by free().
*/
char *mnt_pretty_path(const char *path, struct libmnt_cache *cache)
{
char *pretty = mnt_resolve_path(path, cache);
if (!pretty)
return strdup("none");
#ifdef __linux__
/* users assume backing file name rather than /dev/loopN in
* output if the device has been initialized by mount(8).
*/
if (strncmp(pretty, "/dev/loop", 9) == 0) {
struct loopdev_cxt lc;
if (loopcxt_init(&lc, 0) || loopcxt_set_device(&lc, pretty))
goto done;
if (loopcxt_is_autoclear(&lc)) {
char *tmp = loopcxt_get_backing_file(&lc);
if (tmp) {
loopcxt_deinit(&lc);
if (!cache)
free(pretty); /* not cached, deallocate */
return tmp; /* return backing file */
}
}
loopcxt_deinit(&lc);
}
#endif
done:
/* don't return pointer to the cache, allocate a new string */
return cache ? strdup(pretty) : pretty;
}
/**
* mnt_resolve_tag:
* @token: tag name
* @value: tag value
* @cache: for results or NULL
*
* Returns: device name or NULL in case of error. The result has to be
* deallocated by free() if @cache is NULL.
*/
char *mnt_resolve_tag(const char *token, const char *value,
struct libmnt_cache *cache)
{
char *p = NULL;
/*DBG(CACHE, ul_debugobj(cache, "resolving tag token=%s value=%s",
token, value));*/
if (!token || !value)
return NULL;
if (cache)
p = (char *) cache_find_tag(cache, token, value);
if (!p) {
/* returns newly allocated string */
p = blkid_evaluate_tag(token, value, cache ? &cache->bc : NULL);
if (p && cache &&
cache_add_tag(cache, token, value, p, 0))
goto error;
}
return p;
error:
free(p);
return NULL;
}
/**
* mnt_resolve_spec:
* @spec: path or tag
* @cache: paths cache
*
* Returns: canonicalized path or NULL. The result has to be
* deallocated by free() if @cache is NULL.
*/
char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache)
{
char *cn = NULL;
char *t = NULL, *v = NULL;
if (!spec)
return NULL;
if (blkid_parse_tag_string(spec, &t, &v) == 0 && mnt_valid_tagname(t))
cn = mnt_resolve_tag(t, v, cache);
else
cn = mnt_resolve_path(spec, cache);
free(t);
free(v);
return cn;
}
#ifdef TEST_PROGRAM
static int test_resolve_path(struct libmnt_test *ts, int argc, char *argv[])
{
char line[BUFSIZ];
struct libmnt_cache *cache;
cache = mnt_new_cache();
if (!cache)
return -ENOMEM;
while(fgets(line, sizeof(line), stdin)) {
size_t sz = strlen(line);
char *p;
if (sz > 0 && line[sz - 1] == '\n')
line[sz - 1] = '\0';
p = mnt_resolve_path(line, cache);
printf("%s : %s\n", line, p);
}
mnt_unref_cache(cache);
return 0;
}
static int test_resolve_spec(struct libmnt_test *ts, int argc, char *argv[])
{
char line[BUFSIZ];
struct libmnt_cache *cache;
cache = mnt_new_cache();
if (!cache)
return -ENOMEM;
while(fgets(line, sizeof(line), stdin)) {
size_t sz = strlen(line);
char *p;
if (sz > 0 && line[sz - 1] == '\n')
line[sz - 1] = '\0';
p = mnt_resolve_spec(line, cache);
printf("%s : %s\n", line, p);
}
mnt_unref_cache(cache);
return 0;
}
static int test_read_tags(struct libmnt_test *ts, int argc, char *argv[])
{
char line[BUFSIZ];
struct libmnt_cache *cache;
size_t i;
cache = mnt_new_cache();
if (!cache)
return -ENOMEM;
while(fgets(line, sizeof(line), stdin)) {
size_t sz = strlen(line);
char *t = NULL, *v = NULL;
if (sz > 0 && line[sz - 1] == '\n')
line[sz - 1] = '\0';
if (!strcmp(line, "quit"))
break;
if (*line == '/') {
if (mnt_cache_read_tags(cache, line) < 0)
fprintf(stderr, "%s: read tags failed\n", line);
} else if (blkid_parse_tag_string(line, &t, &v) == 0) {
const char *cn = NULL;
if (mnt_valid_tagname(t))
cn = cache_find_tag(cache, t, v);
free(t);
free(v);
if (cn)
printf("%s: %s\n", line, cn);
else
printf("%s: not cached\n", line);
}
}
for (i = 0; i < cache->nents; i++) {
struct mnt_cache_entry *e = &cache->ents[i];
if (!(e->flag & MNT_CACHE_ISTAG))
continue;
printf("%15s : %5s : %s\n", e->value, e->key,
e->key + strlen(e->key) + 1);
}
mnt_unref_cache(cache);
return 0;
}
int main(int argc, char *argv[])
{
struct libmnt_test ts[] = {
{ "--resolve-path", test_resolve_path, " resolve paths from stdin" },
{ "--resolve-spec", test_resolve_spec, " evaluate specs from stdin" },
{ "--read-tags", test_read_tags, " read devname or TAG from stdin (\"quit\" to exit)" },
{ NULL }
};
return mnt_run_test(ts, argc, argv);
}
#endif