/*
 * tag.c - allocation/initialization/free routines for tag structs
 *
 * Copyright (C) 2001 Andreas Dilger
 * Copyright (C) 2003 Theodore Ts'o
 *
 * %Begin-Header%
 * This file may be redistributed under the terms of the
 * GNU Lesser General Public License.
 * %End-Header%
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "blkidP.h"

static blkid_tag blkid_new_tag(void)
{
	blkid_tag tag;

	if (!(tag = (blkid_tag) calloc(1, sizeof(struct blkid_struct_tag))))
		return NULL;

	INIT_LIST_HEAD(&tag->bit_tags);
	INIT_LIST_HEAD(&tag->bit_names);

	return tag;
}

void blkid_free_tag(blkid_tag tag)
{
	if (!tag)
		return;

	DBG(DEBUG_TAG, printf("    freeing tag %s=%s\n", tag->bit_name,
		   tag->bit_val ? tag->bit_val : "(NULL)"));
	DEB_DUMP_TAG(DEBUG_TAG, tag);

	list_del(&tag->bit_tags);	/* list of tags for this device */
	list_del(&tag->bit_names);	/* list of tags with this type */

	if (tag->bit_name)
		free(tag->bit_name);
	if (tag->bit_val)
		free(tag->bit_val);

	free(tag);
}

/*
 * Find the desired tag on a device.  If value is NULL, then the
 * first such tag is returned, otherwise return only exact tag if found.
 */
blkid_tag blkid_find_tag_dev(blkid_dev dev, const char *type)
{
	struct list_head *p;

	if (!dev || !type)
		return NULL;

	list_for_each(p, &dev->bid_tags) {
		blkid_tag tmp = list_entry(p, struct blkid_struct_tag,
					   bit_tags);

		if (!strcmp(tmp->bit_name, type))
			return tmp;
	}
	return NULL;
}

/*
 * Find the desired tag type in the cache.
 * We return the head tag for this tag type.
 */
static blkid_tag blkid_find_head_cache(blkid_cache cache, const char *type)
{
	blkid_tag head = NULL, tmp;
	struct list_head *p;

	if (!cache || !type)
		return NULL;

	list_for_each(p, &cache->bic_tags) {
		tmp = list_entry(p, struct blkid_struct_tag, bit_tags);
		if (!strcmp(tmp->bit_name, type)) {
			DBG(DEBUG_TAG,
			    printf("    found cache tag head %s\n", type));
			head = tmp;
			break;
		}
	}
	return head;
}

/*
 * Set a tag on an existing device.
 * 
 * If value is NULL, then delete the tagsfrom the device.
 */
int blkid_set_tag(blkid_dev dev, const char *name,
		  const char *value, const int vlength)
{
	blkid_tag	t = 0, head = 0;
	char		*val = 0;

	if (!dev || !name)
		return -BLKID_ERR_PARAM;

	if (!(val = blkid_strndup(value, vlength)) && value)
		return -BLKID_ERR_MEM;
	t = blkid_find_tag_dev(dev, name);
	if (!value) {
		if (t)
			blkid_free_tag(t);
	} else if (t) {
		if (!strcmp(t->bit_val, val)) {
			/* Same thing, exit */
			free(val);
			return 0;
		}
		free(t->bit_val);
		t->bit_val = val;
	} else {
		/* Existing tag not present, add to device */
		if (!(t = blkid_new_tag()))
			goto errout;
		t->bit_name = blkid_strdup(name);
		t->bit_val = val;
		t->bit_dev = dev;

		list_add_tail(&t->bit_tags, &dev->bid_tags);
		
		if (dev->bid_cache) {
			head = blkid_find_head_cache(dev->bid_cache,
						     t->bit_name);
			if (!head) {
				head = blkid_new_tag();
				if (!head)
					goto errout;

				DBG(DEBUG_TAG,
				    printf("    creating new cache tag head %s\n", name));
				head->bit_name = blkid_strdup(name);
				if (!head->bit_name)
					goto errout;
				list_add_tail(&head->bit_tags,
					      &dev->bid_cache->bic_tags);
			}
			list_add_tail(&t->bit_names, &head->bit_names);
		}
	}
	
	/* Link common tags directly to the device struct */
	if (!strcmp(name, "TYPE"))
		dev->bid_type = val;
	else if (!strcmp(name, "LABEL"))
		dev->bid_label = val;
	else if (!strcmp(name, "UUID"))
		dev->bid_uuid = val;
		
	if (dev->bid_cache)
		dev->bid_cache->bic_flags |= BLKID_BIC_FL_CHANGED;
	return 0;

errout:
	if (t)
		blkid_free_tag(t);
	else if (val)
		free(val);
	if (head)
		blkid_free_tag(head);
	return -BLKID_ERR_MEM;
}


/*
 * Parse a "NAME=value" string.  This is slightly different than
 * parse_token, because that will end an unquoted value at a space, while
 * this will assume that an unquoted value is the rest of the token (e.g.
 * if we are passed an already quoted string from the command-line we don't
 * have to both quote and escape quote so that the quotes make it to
 * us).
 *
 * Returns 0 on success, and -1 on failure.
 */
int blkid_parse_tag_string(const char *token, char **ret_type, char **ret_val)
{
	char *name, *value, *cp;

	DBG(DEBUG_TAG, printf("trying to parse '%s' as a tag\n", token));

	if (!token || !(cp = strchr(token, '=')))
		return -1;

	name = blkid_strdup(token);
	if (!name)
		return -1;
	value = name + (cp - token);
	*value++ = '\0';
	if (*value == '"' || *value == '\'') {
		char c = *value++;
		if (!(cp = strrchr(value, c)))
			goto errout; /* missing closing quote */
		*cp = '\0';
	}
	value = blkid_strdup(value);
	if (!value)
		goto errout;

	*ret_type = name;
	*ret_val = value;

	return 0;

errout:
	free(name);
	return -1;
}

/*
 * Tag iteration routines for the public libblkid interface.
 *
 * These routines do not expose the list.h implementation, which are a
 * contamination of the namespace, and which force us to reveal far, far
 * too much of our internal implemenation.  I'm not convinced I want
 * to keep list.h in the long term, anyway.  It's fine for kernel
 * programming, but performance is not the #1 priority for this
 * library, and I really don't like the tradeoff of type-safety for
 * performance for this application.  [tytso:20030125.2007EST]
 */

/*
 * This series of functions iterate over all tags in a device
 */
#define TAG_ITERATE_MAGIC	0x01a5284c
	
struct blkid_struct_tag_iterate {
	int			magic;
	blkid_dev		dev;
	struct list_head	*p;
};

extern blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev)
{
	blkid_tag_iterate	iter;

	iter = malloc(sizeof(struct blkid_struct_tag_iterate));
	if (iter) {
		iter->magic = TAG_ITERATE_MAGIC;
		iter->dev = dev;
		iter->p	= dev->bid_tags.next;
	}
	return (iter);
}

/*
 * Return 0 on success, -1 on error
 */
extern int blkid_tag_next(blkid_tag_iterate iter,
			  const char **type, const char **value)
{
	blkid_tag tag;
	
	*type = 0;
	*value = 0;
	if (!iter || iter->magic != TAG_ITERATE_MAGIC ||
	    iter->p == &iter->dev->bid_tags)
		return -1;
	tag = list_entry(iter->p, struct blkid_struct_tag, bit_tags);
	*type = tag->bit_name;
	*value = tag->bit_val;
	iter->p = iter->p->next;
	return 0;
}

extern void blkid_tag_iterate_end(blkid_tag_iterate iter)
{
	if (!iter || iter->magic != TAG_ITERATE_MAGIC)
		return;
	iter->magic = 0;
	free(iter);
}

/*
 * This function returns a device which matches a particular
 * type/value pair.  If there is more than one device that matches the
 * search specification, it returns the one with the highest priority
 * value.  This allows us to give preference to EVMS or LVM devices.
 *
 * XXX there should also be an interface which uses an iterator so we
 * can get all of the devices which match a type/value search parameter.
 */
extern blkid_dev blkid_find_dev_with_tag(blkid_cache cache,
					 const char *type,
					 const char *value)
{
	blkid_tag	head;
	blkid_dev	dev;
	int		pri;
	struct list_head *p;

	if (!cache || !type || !value)
		return NULL;

	blkid_read_cache(cache);
	
	DBG(DEBUG_TAG, printf("looking for %s=%s in cache\n", type, value));
	
try_again:
	pri = -1;
	dev = 0;
	head = blkid_find_head_cache(cache, type);

	if (head) {
		list_for_each(p, &head->bit_names) {
			blkid_tag tmp = list_entry(p, struct blkid_struct_tag, 
						   bit_names);

			if (!strcmp(tmp->bit_val, value) &&
			    tmp->bit_dev->bid_pri > pri) {
				dev = tmp->bit_dev;
				pri = dev->bid_pri;
			}
		}
	}
	if (dev && !(dev->bid_flags & BLKID_BID_FL_VERIFIED)) {
		dev = blkid_verify(cache, dev);
		if (dev && (dev->bid_flags & BLKID_BID_FL_VERIFIED))
			goto try_again;
	}

	if (!dev && !(cache->bic_flags & BLKID_BIC_FL_PROBED)) {
		if (blkid_probe_all(cache) < 0)
			return NULL;
		goto try_again;
	}
	return dev;
}
