/*
 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
 * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License v.2.1.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "lib/misc/lib.h"
#include "lib/filters/filter.h"
#include "lib/commands/toolcontext.h"

struct rfilter {
	struct dm_pool *mem;
	dm_bitset_t accept;
	struct dm_regex *engine;
	unsigned config_filter:1;
	unsigned config_global_filter:1;
	unsigned warned_filter:1;
	unsigned warned_global_filter:1;
};

static int _extract_pattern(struct dm_pool *mem, const char *pat,
			    char **regex, dm_bitset_t accept, int ix)
{
	char sep, *r, *ptr;

	/*
	 * is this an accept or reject pattern
	 */
	switch (*pat) {
	case 'a':
		dm_bit_set(accept, ix);
		break;

	case 'r':
		dm_bit_clear(accept, ix);
		break;

	default:
		log_error("Pattern must begin with 'a' or 'r'.");
		return 0;
	}
	pat++;

	/*
	 * get the separator
	 */
	switch (*pat) {
	case '(':
		sep = ')';
		break;

	case '[':
		sep = ']';
		break;

	case '{':
		sep = '}';
		break;

	default:
		sep = *pat;
	}
	pat++;

	/*
	 * copy the regex
	 */
	if (!(r = dm_pool_strdup(mem, pat)))
		return_0;

	/*
	 * trim the trailing character, having checked it's sep.
	 */
	ptr = r + strlen(r) - 1;
	if (*ptr != sep) {
		log_error("Invalid separator at end of regex.");
		return 0;
	}
	*ptr = '\0';

	regex[ix] = r;
	return 1;
}

static int _build_matcher(struct rfilter *rf, const struct dm_config_value *val)
{
	struct dm_pool *scratch;
	const struct dm_config_value *v;
	char **regex;
	unsigned count = 0;
	int i, r = 0;

	if (!(scratch = dm_pool_create("filter dm_regex", 1024)))
		return_0;

	/*
	 * count how many patterns we have.
	 */
	for (v = val; v; v = v->next) {
		if (v->type != DM_CFG_STRING) {
			log_error("Filter patterns must be enclosed in quotes.");
			goto out;
		}

		count++;
	}

	/* Allocate space for them */
	if (!(regex = dm_pool_alloc(scratch, sizeof(*regex) * count))) {
		log_error("Failed to allocate regex.");
		goto out;
	}

	/* Create the accept/reject bitset */
	if (!(rf->accept = dm_bitset_create(rf->mem, count))) {
		log_error("Failed to create bitset.");
		goto out;
	}

	/*
	 * fill the array back to front because we
	 * want the opposite precedence to what
	 * the matcher gives.
	 */
	for (v = val, i = count - 1; v; v = v->next, i--)
		if (!_extract_pattern(scratch, v->v.str, regex, rf->accept, i)) {
			log_error("Invalid filter pattern \"%s\".", v->v.str);
			goto out;
		}

	/*
	 * build the matcher.
	 */
	if (!(rf->engine = dm_regex_create(rf->mem, (const char * const*) regex,
					   count)))
		goto_out;
	r = 1;

      out:
	dm_pool_destroy(scratch);
	return r;
}

static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
{
	int m, first = 1, rejected = 0;
	struct rfilter *rf = (struct rfilter *) f->private;
	struct dm_str_list *sl;

	dev->filtered_flags &= ~DEV_FILTERED_REGEX;

	if (cmd->enable_devices_list)
		return 1;

	if (cmd->enable_devices_file && !cmd->filter_regex_with_devices_file) {
		/* can't warn in create_filter because enable_devices_file is set later */
		if (rf->config_filter && !rf->warned_filter) {
			log_warn("Please remove the lvm.conf filter, it is ignored with the devices file.");
			rf->warned_filter = 1;
		}
		if (rf->config_global_filter && !rf->warned_global_filter) {
			log_warn("Please remove the lvm.conf global_filter, it is ignored with the devices file.");
			rf->warned_global_filter = 1;
		}
		return 1;
	}

	dm_list_iterate_items(sl, &dev->aliases) {
		m = dm_regex_match(rf->engine, sl->str);

		if (m >= 0) {
			if (dm_bit(rf->accept, m)) {
				if (!first)
					dev_set_preferred_name(sl, dev);

				return 1;
			}

			rejected = 1;
		}

		first = 0;
	}

	if (rejected) {
		dev->filtered_flags |= DEV_FILTERED_REGEX;
		log_debug_devs("%s: Skipping (regex)", dev_name(dev));
	}

	/*
	 * pass everything that doesn't match
	 * anything.
	 */
	return !rejected;
}

static void _regex_destroy(struct dev_filter *f)
{
	struct rfilter *rf = (struct rfilter *) f->private;

	if (f->use_count)
		log_error(INTERNAL_ERROR "Destroying regex filter while in use %u times.", f->use_count);

	dm_pool_destroy(rf->mem);
}

struct dev_filter *regex_filter_create(const struct dm_config_value *patterns, int config_filter, int config_global_filter)
{
	struct dm_pool *mem = dm_pool_create("filter regex", 10 * 1024);
	struct rfilter *rf;
	struct dev_filter *f;

	if (!mem)
		return_NULL;

	if (!(rf = dm_pool_alloc(mem, sizeof(*rf))))
		goto_bad;

	rf->mem = mem;

	rf->config_filter = config_filter;
	rf->config_global_filter = config_global_filter;

	if (!_build_matcher(rf, patterns))
		goto_bad;

	if (!(f = dm_pool_zalloc(mem, sizeof(*f))))
		goto_bad;

	f->passes_filter = _accept_p;
	f->destroy = _regex_destroy;
	f->use_count = 0;
	f->private = rf;
	f->name = "regex";

	log_debug_devs("Regex filter initialised.");

	return f;

      bad:
	dm_pool_destroy(mem);
	return NULL;
}
