/*
 * evaluate.c - very high-level API to evaluate LABELs or UUIDs
 *
 * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
 *
 * This file may be redistributed under the terms of the
 * GNU Lesser General Public License.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include <stdint.h>
#include <stdarg.h>

#include "pathnames.h"
#include "canonicalize.h"
#include "closestream.h"

#include "blkidP.h"

/**
 * SECTION:evaluate
 * @title: Tags and Spec evaluation
 * @short_description: top-level API for LABEL and UUID evaluation.
 *
 * This API provides very simple and portable way how evaluate LABEL and UUID
 * tags.  The blkid_evaluate_tag() and blkid_evaluate_spec() work on 2.4 and
 * 2.6 systems and on systems with or without udev. Currently, the libblkid
 * library supports "udev" and "scan" methods. The "udev" method uses udev
 * /dev/disk/by-* symlinks and the "scan" method scans all block devices from
 * the /proc/partitions file. The evaluation could be controlled by the
 * /etc/blkid.conf config file. The default is to try "udev" and then "scan"
 * method.
 *
 * The blkid_evaluate_tag() also automatically informs udevd when an obsolete
 * /dev/disk/by-* symlink is detected.
 *
 * If you are not sure how translate LABEL or UUID to the device name use this
 * API.
 */

#ifdef CONFIG_BLKID_VERIFY_UDEV
/* returns zero when the device has NAME=value (LABEL/UUID) */
static int verify_tag(const char *devname, const char *name, const char *value)
{
	blkid_probe pr;
	int fd = -1, rc = -1;
	size_t len;
	const char *data;
	int errsv = 0;

	if (strcmp(token, "ID") == 0)
		return 0; /* non-content tag */

	pr = blkid_new_probe();
	if (!pr)
		return -1;

	blkid_probe_enable_superblocks(pr, TRUE);
	blkid_probe_set_superblocks_flags(pr,
			BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID);

	blkid_probe_enable_partitions(pr, TRUE);
	blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);

	fd = open(devname, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
	if (fd < 0) {
		errsv = errno;
		goto done;
	}
	if (blkid_probe_set_device(pr, fd, 0, 0))
		goto done;
	rc = blkid_do_safeprobe(pr);
	if (rc)
		goto done;
	rc = blkid_probe_lookup_value(pr, name, &data, &len);
	if (!rc)
		rc = memcmp(value, data, len);
done:
	DBG(EVALUATE, ul_debug("%s: %s verification %s",
			devname, name, rc == 0 ? "PASS" : "FAILED"));
	if (fd >= 0)
		close(fd);
	blkid_free_probe(pr);

	/* for non-root users we use unverified udev links */
	return errsv == EACCES ? 0 : rc;
}
#endif /* CONFIG_BLKID_VERIFY_UDEV*/

/**
 * blkid_send_uevent:
 * @devname: absolute path to the device
 * @action: event string
 *
 * Returns: -1 in case of failure, or 0 on success.
 */
int blkid_send_uevent(const char *devname, const char *action)
{
	char uevent[PATH_MAX];
	struct stat st;
	FILE *f;
	int rc = -1;

	DBG(EVALUATE, ul_debug("%s: uevent '%s' requested", devname, action));

	if (!devname || !action)
		return -1;
	if (stat(devname, &st) || !S_ISBLK(st.st_mode))
		return -1;

	snprintf(uevent, sizeof(uevent), "/sys/dev/block/%d:%d/uevent",
			major(st.st_rdev), minor(st.st_rdev));

	f = fopen(uevent, "w" UL_CLOEXECSTR);
	if (f) {
		rc = 0;
		if (fputs(action, f) >= 0)
			rc = 0;
		if (close_stream(f) != 0)
			DBG(EVALUATE, ul_debug("write failed: %s", uevent));
	}
	DBG(EVALUATE, ul_debug("%s: send uevent %s",
			uevent, rc == 0 ? "SUCCESS" : "FAILED"));
	return rc;
}

static char *evaluate_by_udev(const char *token, const char *value, int uevent)
{
	char dev[PATH_MAX];
	char *path = NULL;
	size_t len;
	struct stat st;

	DBG(EVALUATE, ul_debug("evaluating by udev %s=%s", token, value));

	if (!strcmp(token, "UUID"))
		strcpy(dev, _PATH_DEV_BYUUID "/");
	else if (!strcmp(token, "LABEL"))
		strcpy(dev, _PATH_DEV_BYLABEL "/");
	else if (!strcmp(token, "PARTLABEL"))
		strcpy(dev, _PATH_DEV_BYPARTLABEL "/");
	else if (!strcmp(token, "PARTUUID"))
		strcpy(dev, _PATH_DEV_BYPARTUUID "/");
	else if (!strcmp(token, "ID"))
		strcpy(dev, _PATH_DEV_BYID "/");
	else {
		DBG(EVALUATE, ul_debug("unsupported token %s", token));
		return NULL;	/* unsupported tag */
	}

	len = strlen(dev);
	if (blkid_encode_string(value, &dev[len], sizeof(dev) - len) != 0)
		return NULL;

	DBG(EVALUATE, ul_debug("expected udev link: %s", dev));

	if (stat(dev, &st))
		goto failed;	/* link or device does not exist */

	if (!S_ISBLK(st.st_mode))
		return NULL;

	path = ul_canonicalize_path(dev);
	if (!path)
		return NULL;

#ifdef CONFIG_BLKID_VERIFY_UDEV
	if (verify_tag(path, token, value))
		goto failed;
#endif
	return path;

failed:
	DBG(EVALUATE, ul_debug("failed to evaluate by udev"));

	if (uevent && path)
		blkid_send_uevent(path, "change");
	free(path);
	return NULL;
}

static char *evaluate_by_scan(const char *token, const char *value,
		blkid_cache *cache, struct blkid_config *conf)
{
	blkid_cache c = cache ? *cache : NULL;
	char *res;

	DBG(EVALUATE, ul_debug("evaluating by blkid scan %s=%s", token, value));

	if (!c) {
		char *cachefile = blkid_get_cache_filename(conf);
		int rc = blkid_get_cache(&c, cachefile);
		free(cachefile);
		if (rc < 0)
			return NULL;
	}
	if (!c)
		return NULL;

	res = blkid_get_devname(c, token, value);

	if (cache)
		*cache = c;
	else
		blkid_put_cache(c);

	return res;
}

/**
 * blkid_evaluate_tag:
 * @token: token name (e.g "LABEL" or "UUID") or unparsed tag (e.g. "LABEL=foo")
 * @value: token data (e.g. "foo")
 * @cache: pointer to cache (or NULL when you don't want to re-use the cache)
 *
* If the @value is NULL and @token is not in the NAME=value format, then return
* a copy of the @token.
 *
 * Returns: allocated string with a device name.
 */
char *blkid_evaluate_tag(const char *token, const char *value, blkid_cache *cache)
{
	struct blkid_config *conf = NULL;
	char *t = NULL, *v = NULL;
	char *ret = NULL;
	int i;

	if (!token)
		return NULL;

	DBG(EVALUATE, ul_debug("evaluating  %s%s%s", token, value ? "=" : "",
		   value ? value : ""));

	if (!value) {
		if (!strchr(token, '=')) {
			ret = strdup(token);
			goto out;
		}
		if (blkid_parse_tag_string(token, &t, &v) != 0 || !t || !v)
			goto out;
		token = t;
		value = v;
	}

	conf = blkid_read_config(NULL);
	if (!conf)
		goto out;

	for (i = 0; i < conf->nevals; i++) {
		if (conf->eval[i] == BLKID_EVAL_UDEV)
			ret = evaluate_by_udev(token, value, conf->uevent);
		else if (conf->eval[i] == BLKID_EVAL_SCAN)
			ret = evaluate_by_scan(token, value, cache, conf);
		if (ret)
			break;
	}

	DBG(EVALUATE, ul_debug("%s=%s evaluated as %s", token, value, ret));
out:
	blkid_free_config(conf);
	free(t);
	free(v);
	return ret;
}

/**
 * blkid_evaluate_spec:
 * @spec: unparsed tag (e.g. "LABEL=foo") or path (e.g. /dev/dm-0)
 * @cache: pointer to cache (or NULL when you don't want to re-use the cache)
 *
 * All returned paths are canonicalized, device-mapper paths are converted
 * to the /dev/mapper/name format.
 *
 * Returns: allocated string with a device name.
 */
char *blkid_evaluate_spec(const char *spec, blkid_cache *cache)
{
	char *t = NULL, *v = NULL, *res;

	if (!spec)
		return NULL;

	if (strchr(spec, '=') &&
	    blkid_parse_tag_string(spec, &t, &v) != 0)	/* parse error */
		return NULL;

	if (v)
		res = blkid_evaluate_tag(t, v, cache);
	else
		res = ul_canonicalize_path(spec);

	free(t);
	free(v);
	return res;
}


#ifdef TEST_PROGRAM
int main(int argc, char *argv[])
{
	blkid_cache cache = NULL;
	char *res;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <tag> | <spec>\n", argv[0]);
		return EXIT_FAILURE;
	}

	res = blkid_evaluate_spec(argv[1], &cache);
	if (res)
		printf("%s\n", res);
	if (cache)
		blkid_put_cache(cache);

	return res ? EXIT_SUCCESS : EXIT_FAILURE;
}
#endif
