/*
* Copyright (C) 2009 Karel Zak <kzak@redhat.com>
*
* This file may be redistributed under the terms of the
* GNU Lesser General Public License.
*/
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <blkid.h>
#include "canonicalize.h"
#include "mountP.h"
/*
* Canonicalized (resolved) paths 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 *native; /* the original path */
char *real; /* canonicalized path */
int flag;
};
struct _mnt_cache {
struct mnt_cache_entry *ents;
size_t nents;
size_t nallocs;
/* 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 /etc/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;
};
/**
* mnt_new_cache:
*
* Returns new mnt_cache instance or NULL in case of ENOMEM error.
*/
mnt_cache *mnt_new_cache(void)
{
return calloc(1, sizeof(struct _mnt_cache));
}
/**
* mnt_free_cache:
* @cache: pointer to mnt_cache instance
*
* Deallocates mnt_cache.
*/
void mnt_free_cache(mnt_cache *cache)
{
int i;
if (!cache)
return;
for (i = 0; i < cache->nents; i++) {
struct mnt_cache_entry *e = &cache->ents[i];
if (e->real != e->native)
free(e->real);
free(e->native);
}
free(cache->ents);
if (cache->bc)
blkid_put_cache(cache->bc);
free(cache);
}
/* note that the @native could be tha same pointer as @real */
static int mnt_cache_add_entry(mnt_cache *cache, char *native,
char *real, int flag)
{
struct mnt_cache_entry *e;
assert(cache);
assert(real);
assert(native);
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 -1;
cache->ents = e;
cache->nallocs = sz;
}
e = &cache->ents[cache->nents];
e->native = native;
e->real = real;
e->flag = flag;
cache->nents++;
return 0;
}
/* add tag to the cache, @real has to be allocated string */
static int mnt_cache_add_tag(mnt_cache *cache, const char *token,
const char *value, char *real, int flag)
{
size_t tksz, vlsz;
char *native;
assert(cache);
assert(real);
assert(token);
assert(value);
/* add into cache -- cache format for TAGs is
* native = "NAME\0VALUE\0"
* real = "/dev/foo"
*/
tksz = strlen(token);
vlsz = strlen(value);
native = malloc(tksz + vlsz + 2);
if (!native)
goto error;
memcpy(native, token, tksz + 1); /* include '\0' */
memcpy(native + tksz + 1, value, vlsz + 1);
if (mnt_cache_add_entry(cache, native, real, flag))
goto error;
DBG(DEBUG_CACHE,
printf("cache: added %s: %s=%s\n", real, token, value));
return 0;
error:
free(native);
return -1;
}
/**
* mnt_cache_find_path:
* @cache: pointer to mnt_cache instance
* @path: requested "native" (non-canonicalized) path
*
* Returns cached canonicalized path or NULL.
*/
const char *mnt_cache_find_path(mnt_cache *cache, const char *path)
{
int i;
assert(cache);
assert(path);
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 (strcmp(path, e->native) == 0)
return e->real;
}
return NULL;
}
/**
* mnt_cache_find_tag:
* @cache: pointer to mnt_cache instance
* @token: tag name
* @value: tag value
*
* Returns cached path or NULL.
*/
const char *mnt_cache_find_tag(mnt_cache *cache,
const char *token, const char *value)
{
int i;
size_t tksz;
assert(cache);
assert(token);
assert(value);
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->native) == 0 &&
strcmp(value, e->native + tksz + 1) == 0)
return e->real;
}
return NULL;
}
/**
* mnt_cache_read_tags
* @cache: pointer to mnt_cache instance
* @devname: path device
*
* Reads @devname LABEL and UUID to the @cache.
*
* Returns: 1 if at least on tag was added, 0 no tag was added or
* -1 in case of error.
*/
int mnt_cache_read_tags(mnt_cache *cache, const char *devname)
{
int i, ntags = 0;
int fd;
static blkid_probe pr;
const char *tags[] = { "LABEL", "UUID" };
assert(cache);
assert(devname);
if (!cache || !devname)
return -1;
DBG(DEBUG_CACHE, printf("cache: tags for %s requested\n", devname));
/* check is 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->real, devname) == 0)
/* tags has been already read */
return 0;
}
DBG(DEBUG_CACHE,
printf("cache: reading tags for: %s\n", devname));
fd = mnt_open_device(devname, O_RDONLY);
if (fd < 0)
return -1;
pr = blkid_new_probe();
if (!pr)
goto error;
if (blkid_probe_set_device(pr, fd, 0, 0))
goto error;
blkid_probe_enable_superblocks(pr, 1);
blkid_probe_set_superblocks_flags(pr,
BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID);
if (blkid_do_safeprobe(pr))
goto error;
for (i = 0; i < ARRAY_SIZE(tags); i++) {
const char *data;
char *dev;
if (blkid_probe_lookup_value(pr, tags[i], &data, NULL))
continue;
if (mnt_cache_find_tag(cache, tags[i], data))
continue; /* already cached */
dev = strdup(devname);
if (!dev)
goto error;
if (mnt_cache_add_tag(cache, tags[i], data, dev,
(MNT_CACHE_ISTAG | MNT_CACHE_TAGREAD))) {
free(dev);
goto error;
}
ntags++;
}
return ntags ? 1 : 0;
error:
blkid_free_probe(pr);
close(fd);
return -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 it @tag+@value are associated with @devname.
*
* Returns: 1 on success or 0.
*/
int mnt_cache_device_has_tag(mnt_cache *cache, const char *devname,
const char *token, const char *value)
{
const char *path = mnt_cache_find_tag(cache, token, value);
if (path && strcmp(path, devname) == 0)
return 1;
return 0;
}
/**
* mnt_resolve_path:
* @path: "native" path
* @cache: cache for results or NULL
*
* 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, mnt_cache *cache)
{
char *p = NULL;
char *native = NULL;
char *real = NULL;
assert(path);
if (!path)
return NULL;
if (cache)
p = (char *) mnt_cache_find_path(cache, path);
if (!p) {
p = canonicalize_path(path);
if (p && cache) {
native = strdup(path);
real = strcmp(path, p) == 0 ? native : p;
if (!native || !real)
goto error;
if (mnt_cache_add_entry(cache, native, real,
MNT_CACHE_ISPATH))
goto error;
}
}
DBG(DEBUG_CACHE, printf("cache: added %s: %s\n", path, p));
return p;
error:
if (real != native)
free(real);
free(native);
return NULL;
}
/**
* 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, mnt_cache *cache)
{
char *p = NULL;
assert(token);
assert(value);
if (!token || !value)
return NULL;
if (cache)
p = (char *) mnt_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 &&
mnt_cache_add_tag(cache, token, value, p, MNT_CACHE_ISTAG))
goto error;
}
DBG(DEBUG_CACHE, printf("cache: %s=%s --> %s\n", token, value, p));
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, mnt_cache *cache)
{
char *cn = NULL;
if (!spec)
return NULL;
if (strchr(spec, '=')) {
char *tag, *val;
if (!blkid_parse_tag_string(spec, &tag, &val)) {
cn = mnt_resolve_tag(tag, val, cache);
free(tag);
free(val);
}
} else
cn = mnt_resolve_path(spec, cache);
return cn;
}
#ifdef TEST_PROGRAM
int test_resolve_path(struct mtest *ts, int argc, char *argv[])
{
char line[BUFSIZ];
mnt_cache *cache;
cache = mnt_new_cache();
if (!cache)
return -1;
while(fgets(line, sizeof(line), stdin)) {
size_t sz = strlen(line);
char *p;
if (line[sz - 1] == '\n')
line[sz - 1] = '\0';
p = mnt_resolve_path(line, cache);
printf("%s : %s\n", line, p);
}
mnt_free_cache(cache);
return 0;
}
int test_resolve_spec(struct mtest *ts, int argc, char *argv[])
{
char line[BUFSIZ];
mnt_cache *cache;
cache = mnt_new_cache();
if (!cache)
return -1;
while(fgets(line, sizeof(line), stdin)) {
size_t sz = strlen(line);
char *p;
if (line[sz - 1] == '\n')
line[sz - 1] = '\0';
p = mnt_resolve_spec(line, cache);
printf("%s : %s\n", line, p);
}
mnt_free_cache(cache);
return 0;
}
int test_read_tags(struct mtest *ts, int argc, char *argv[])
{
char line[BUFSIZ];
mnt_cache *cache;
cache = mnt_new_cache();
if (!cache)
return -1;
while(fgets(line, sizeof(line), stdin)) {
size_t sz = strlen(line);
if (line[sz - 1] == '\n')
line[sz - 1] = '\0';
if (*line == '/') {
if (mnt_cache_read_tags(cache, line) < 0)
fprintf(stderr, "%s: read tags faild\n", line);
} else if (strchr(line, '=')) {
char *tag, *val;
const char *cn = NULL;
if (!blkid_parse_tag_string(line, &tag, &val)) {
cn = mnt_cache_find_tag(cache, tag, val);
free(tag);
free(val);
}
if (cn)
printf("%s: %s\n", line, cn);
else
printf("%s: not cached\n", line);
}
}
mnt_free_cache(cache);
return 0;
}
int main(int argc, char *argv[])
{
struct mtest 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 stdin" },
{ NULL }
};
return mnt_run_test(ts, argc, argv);
}
#endif