/*
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  Support for the verb/device/modifier core logic and API,
 *  command line tool and file parser was kindly sponsored by
 *  Texas Instruments Inc.
 *  Support for multiple active modifiers and devices,
 *  transition sequences, multiple client access and user defined use
 *  cases was kindly sponsored by Wolfson Microelectronics PLC.
 *
 *  Copyright (C) 2008-2010 SlimLogic Ltd
 *  Copyright (C) 2010 Wolfson Microelectronics PLC
 *  Copyright (C) 2010 Texas Instruments Inc.
 *  Copyright (C) 2010 Red Hat Inc.
 *  Authors: Liam Girdwood <lrg@slimlogic.co.uk>
 *	         Stefan Schmidt <stefan@slimlogic.co.uk>
 *	         Justin Xu <justinx@slimlogic.co.uk>
 *               Jaroslav Kysela <perex@perex.cz>
 */

#include "ucm_local.h"
#include <sys/stat.h>
#include <stdbool.h>
#include <dirent.h>
#include <ctype.h>
#include <limits.h>

static int filename_filter(const struct dirent64 *dirent);

static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
			  struct list_head *base,
			  snd_config_t *cfg);

/*
 * compose the absolute ucm filename
 */
static void ucm_filename(char *fn, size_t fn_len, long version,
			  const char *dir, const char *file)
{
	const char *env = getenv(version > 1 ? ALSA_CONFIG_UCM2_VAR : ALSA_CONFIG_UCM_VAR);

	if (file[0] == '/')
		file++;
	if (env == NULL)
		snprintf(fn, fn_len, "%s/%s/%s%s%s",
			 snd_config_topdir(), version > 1 ? "ucm2" : "ucm",
			 dir ?: "", dir ? "/" : "", file);
	else
		snprintf(fn, fn_len, "%s/%s%s%s",
			 env, dir ?: "", dir ? "/" : "", file);
}

/*
 *
 */
int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr,
			     const char *file, snd_config_t **cfg, bool optional)
{
	char filename[PATH_MAX];
	int err;

	ucm_filename(filename, sizeof(filename), uc_mgr->conf_format,
		     file[0] == '/' ? NULL : uc_mgr->conf_dir_name,
		     file);
	err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg, optional);
	if (err < 0) {
		if (!optional || (err != -ENOENT && err != -EACCES))
			snd_error(UCM, "failed to open file %s: %d", filename, err);
		return err;
	}
	return 0;
}

/*
 * Replace mallocated string
 */
static char *replace_string(char **dst, const char *value)
{
	free(*dst);
	*dst = value ? strdup(value) : NULL;
	return *dst;
}

/*
 * Parse string
 */
static int parse_string(snd_config_t *n, char **res)
{
	int err;

	err = snd_config_get_string(n, (const char **)res);
	if (err < 0)
		return err;
	*res = strdup(*res);
	if (*res == NULL)
		return -ENOMEM;
	return 0;
}

/*
 * Parse string and substitute
 */
static int parse_string_substitute(snd_use_case_mgr_t *uc_mgr,
			    snd_config_t *n, char **res)
{
	const char *str;
	char *s;
	int err;

	err = snd_config_get_string(n, &str);
	if (err < 0)
		return err;
	err = uc_mgr_get_substituted_value(uc_mgr, &s, str);
	if (err >= 0)
		*res = s;
	return err;
}

/*
 * Parse string and substitute
 */
static int parse_string_substitute3(snd_use_case_mgr_t *uc_mgr,
			     snd_config_t *n, char **res)
{
	if (uc_mgr->conf_format < 3)
		return parse_string(n, res);
	return parse_string_substitute(uc_mgr, n, res);
}

/*
 * Parse integer with substitution
 */
static int parse_integer_substitute(snd_use_case_mgr_t *uc_mgr,
			     snd_config_t *n, long *res)
{
	char *s1, *s2;
	int err;

	err = snd_config_get_ascii(n, &s1);
	if (err < 0)
		return err;
	err = uc_mgr_get_substituted_value(uc_mgr, &s2, s1);
	if (err >= 0)
		err = safe_strtol(s2, res);
	free(s2);
	free(s1);
	return err;
}

/*
 * Parse integer with substitution
 */
static int parse_integer_substitute3(snd_use_case_mgr_t *uc_mgr,
			      snd_config_t *n, long *res)
{
	char *s1, *s2;
	int err;

	err = snd_config_get_ascii(n, &s1);
	if (err < 0)
		return err;
	if (uc_mgr->conf_format < 3)
		s2 = s1;
	else
		err = uc_mgr_get_substituted_value(uc_mgr, &s2, s1);
	if (err >= 0)
		err = safe_strtol(s2, res);
	if (s1 != s2)
		free(s2);
	free(s1);
	return err;
}

/*
 * Parse safe ID
 */
static int parse_is_name_safe(const char *name)
{
	if (strchr(name, '.')) {
		snd_error(UCM, "char '.' not allowed in '%s'", name);
		return 0;
	}
	return 1;
}

static int get_string3(snd_use_case_mgr_t *uc_mgr, const char *s1, char **s)
{
	if (uc_mgr->conf_format < 3) {
		*s = strdup(s1);
		if (*s == NULL)
			return -ENOMEM;
		return 0;
	}
	return uc_mgr_get_substituted_value(uc_mgr, s, s1);
}

static int parse_get_safe_name(snd_use_case_mgr_t *uc_mgr, snd_config_t *n,
			const char *alt, char **name)
{
	const char *id;
	int err;

	if (alt) {
		id = alt;
	} else {
		err = snd_config_get_id(n, &id);
		if (err < 0)
			return err;
	}
	err = get_string3(uc_mgr, id, name);
	if (err < 0)
		return err;
	if (!parse_is_name_safe(*name)) {
		free(*name);
		return -EINVAL;
	}
	return 0;
}

/*
 * Parse device index from device name
 * According to use-case.h specification, device names can have numeric suffixes
 * like HDMI1, HDMI2, or "Line 1", "Line 2".
 * This function extracts the index and modifies the name to contain only the base.
 */
static int parse_device_index(char **name, int *index)
{
	char *p, *num_start;
	long idx;
	size_t len;
	int err;

	if (!name || !*name || !index)
		return -EINVAL;

	len = strlen(*name);
	if (len == 0)
		return -EINVAL;

	/* Start from the end and find where digits begin */
	p = *name + len - 1;

	/* Skip trailing digits */
	while (p > *name && isdigit(*p))
		p--;

	/* If no digits found at the end, index is 0 (no index) */
	if (p == *name + len - 1) {
		*index = 0;
		return 0;
	}

	/* Move to first digit */
	p++;

	/* Check if there's an optional space before the number */
	if (p > *name && *(p - 1) == ' ')
		p--;

	/* Parse the index */
	num_start = *p == ' ' ? p + 1 : p;
	err = safe_strtol(num_start, &idx);
	if (err < 0) {
		/* No valid number found */
		*index = 0;
		return 0;
	}

	*index = (int)idx;

	/* Truncate the name to remove the index part */
	if (*p == ' ')
		*p = '\0';  /* Remove space and number */
	else if (p > *name)
		*p = '\0';  /* Remove number only */

	return 0;
}

/*
 * Handle 'Error' configuration node.
 */
static int error_node(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	int err;
	char *s;

	err = parse_string_substitute3(uc_mgr, cfg, &s);
	if (err < 0) {
		snd_error(UCM, "failed to get Error string");
		return err;
	}
	if (!uc_mgr->suppress_nodev_errors)
		snd_error(UCM, "%s", s);
	free(s);
	return -ENXIO;
}

/*
 *
 */
static int parse_syntax_field(snd_use_case_mgr_t *uc_mgr,
			      snd_config_t *cfg, const char *filename)
{
	snd_config_t *n;
	long l;
	int err;

	err = snd_config_search(cfg, "Syntax", &n);
	if (err < 0) {
		snd_error(UCM, "Syntax field not found in %s", filename);
		return -EINVAL;
	}
	err = snd_config_get_integer(n, &l);
	if (err < 0) {
		snd_error(UCM, "Syntax field is invalid in %s", filename);
		return err;
	}
	if (l < 2 || l > SYNTAX_VERSION_MAX) {
		snd_error(UCM, "Incompatible syntax %ld in %s", l, filename);
		return -EINVAL;
	}
	/* delete this field to optimize strcmp() call in the parsing loop */
	snd_config_delete(n);
	uc_mgr->conf_format = l;
	return l;
}

/*
 * Evaluate variable regex definitions (in-place delete)
 */
static int evaluate_regex(snd_use_case_mgr_t *uc_mgr,
			  snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *d, *n;
	const char *id;
	int err;

	err = snd_config_search(cfg, "DefineRegex", &d);
	if (err == -ENOENT)
		return 1;
	if (err < 0)
		return err;

	if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for DefineRegex");
		return -EINVAL;
	}

	if (uc_mgr->conf_format < 3) {
		snd_error(UCM, "DefineRegex is supported in v3+ syntax");
		return -EINVAL;
	}

	snd_config_for_each(i, next, d) {
		n = snd_config_iterator_entry(i);
		err = snd_config_get_id(n, &id);
		if (err < 0)
			return err;
		if (id[0] == '@') {
			snd_error(UCM, "value names starting with '@' are reserved for application variables");
			return -EINVAL;
		}
		err = uc_mgr_define_regex(uc_mgr, id, n);
		if (err < 0)
			return err;
	}

	snd_config_delete(d);
	return 0;
}

/*
 * Evaluate variable definitions (in-place delete)
 */
static int evaluate_define(snd_use_case_mgr_t *uc_mgr,
			   snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *d, *n;
	const char *id;
	char *var, *s, *sid;
	int err;

	err = snd_config_search(cfg, "Define", &d);
	if (err == -ENOENT)
		return evaluate_regex(uc_mgr, cfg);
	if (err < 0)
		return err;

	if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for Define");
		return -EINVAL;
	}

	if (uc_mgr->conf_format < 3) {
		snd_error(UCM, "Define is supported in v3+ syntax");
		return -EINVAL;
	}

	snd_config_for_each(i, next, d) {
		n = snd_config_iterator_entry(i);
		err = snd_config_get_id(n, &id);
		if (err < 0)
			return err;
		err = snd_config_get_ascii(n, &var);
		if (err < 0)
			return err;
		err = uc_mgr_get_substituted_value(uc_mgr, &s, var);
		free(var);
		if (err < 0)
			return err;
		if (id[0] == '@') {
			free(s);
			snd_error(UCM, "value names starting with '@' are reserved for application variables");
			return -EINVAL;
		}
		sid = (char *)id;
		if (uc_mgr->conf_format >= 9) {
			err = uc_mgr_get_substituted_value(uc_mgr, &sid, id);
			if (err < 0) {
				free(s);
				return err;
			}
		}
		err = uc_mgr_set_variable(uc_mgr, sid, s);
		free(s);
		if (id != sid)
			free(sid);
		if (err < 0)
			return err;
	}

	snd_config_delete(d);

	return evaluate_regex(uc_mgr, cfg);
}

/*
 * Evaluate macro definitions (in-place delete)
 */
static int evaluate_define_macro(snd_use_case_mgr_t *uc_mgr,
				 snd_config_t *cfg)
{
	snd_config_t *d;
	int err;

	err = snd_config_search(cfg, "DefineMacro", &d);
	if (err == -ENOENT)
		return 1;
	if (err < 0)
		return err;

	if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for DefineMacro");
		return -EINVAL;
	}

	if (uc_mgr->conf_format < 6) {
		snd_error(UCM, "DefineMacro is supported in v6+ syntax");
		return -EINVAL;
	}

	err = snd_config_merge(uc_mgr->macros, d, 0);
	if (err < 0)
		return err;

	return 0;
}

static int evaluate_macro1(snd_use_case_mgr_t *uc_mgr,
			   snd_config_t *dst,
			   snd_config_t *args)
{
	snd_config_iterator_t i, next;
	snd_config_t *m, *mc, *a, *n;
	const char *mid, *id;
	char name[128], *var, *var2;
	const char *s;
	int err;

	err = snd_config_get_id(args, &mid);
	if (err < 0)
		return err;
	err = snd_config_search(uc_mgr->macros, mid, &m);
	if (err < 0) {
		snd_error(UCM, "Macro '%s' is not defined", mid);
		return err;
	}

	a = args;
	if (snd_config_get_type(args) == SND_CONFIG_TYPE_STRING) {
		err = snd_config_get_string(args, &s);
		if (err < 0)
			return err;
		if (uc_mgr->conf_format < 9) {
			err = snd_config_load_string(&a, s, 0);
		} else {
			err = uc_mgr_get_substituted_value(uc_mgr, &var2, s);
			if (err >= 0) {
				err = snd_config_load_string(&a, var2, 0);
				free(var2);
			}
		}
		if (err < 0)
			return err;
	} else if (snd_config_get_type(args) != SND_CONFIG_TYPE_COMPOUND) {
		return -EINVAL;
	}

	/* set arguments */
	snd_config_for_each(i, next, a) {
		n = snd_config_iterator_entry(i);
		err = snd_config_get_id(n, &id);
		if (err < 0)
			goto __err_path;
		snprintf(name, sizeof(name), "__%s", id);
		if (uc_mgr_get_variable(uc_mgr, name, false)) {
			snd_error(UCM, "Macro argument '%s' is already defined", name);
			goto __err_path;
		}
		err = snd_config_get_ascii(n, &var);
		if (err < 0)
			goto __err_path;
		if (uc_mgr->conf_format < 7) {
			err = uc_mgr_set_variable(uc_mgr, name, var);
			free(var);
		} else {
			err = uc_mgr_get_substituted_value(uc_mgr, &var2, var);
			free(var);
			if (err >= 0) {
				err = uc_mgr_set_variable(uc_mgr, name, var2);
				free(var2);
			}
		}
		if (err < 0)
			goto __err_path;
	}

	/* merge + substitute variables */
	err = snd_config_copy(&mc, m);
	if (err < 0)
		goto __err_path;
	err = uc_mgr_evaluate_inplace(uc_mgr, mc);
	if (err < 0) {
		snd_config_delete(mc);
		goto __err_path;
	}
	err = uc_mgr_config_tree_merge(uc_mgr, dst, mc, NULL, NULL);
	snd_config_delete(mc);

	/* delete arguments */
	snd_config_for_each(i, next, a) {
		n = snd_config_iterator_entry(i);
		err = snd_config_get_id(n, &id);
		if (err < 0)
			goto __err_path;
		snprintf(name, sizeof(name), "__%s", id);
		err = uc_mgr_delete_variable(uc_mgr, name);
		if (err < 0)
			goto __err_path;
	}

__err_path:
	if (a != args)
		snd_config_delete(a);
	return err;
}

/*
 * Evaluate macro definitions and instances (in-place delete)
 */
static int evaluate_macro(snd_use_case_mgr_t *uc_mgr,
			  snd_config_t *cfg)
{
	snd_config_iterator_t i, i2, next, next2;
	snd_config_t *d, *n, *n2;
	int err, ret;

	ret = evaluate_define_macro(uc_mgr, cfg);
	if (ret < 0)
		return ret;

	err = snd_config_search(cfg, "Macro", &d);
	if (err == -ENOENT)
		return ret;
	if (err < 0)
		return err;

	if (snd_config_get_type(d) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for DefineMacro");
		return -EINVAL;
	}

	if (uc_mgr->conf_format < 6) {
		snd_error(UCM, "Macro is supported in v6+ syntax");
		return -EINVAL;
	}

	snd_config_for_each(i, next, d) {
		n = snd_config_iterator_entry(i);
		if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
			const char *id;
			if (snd_config_get_id(n, &id))
				id = "";
			snd_error(UCM, "compound type expected for Macro.%s", id);
			return -EINVAL;
		}
		snd_config_for_each(i2, next2, n) {
			n2 = snd_config_iterator_entry(i2);
			err = evaluate_macro1(uc_mgr, cfg, n2);
			if (err < 0)
				return err;
		}
	}

	snd_config_delete(d);

	return 0;
}

/*
 * Evaluate include (in-place)
 */
static int evaluate_include(snd_use_case_mgr_t *uc_mgr,
			    snd_config_t *cfg)
{
	snd_config_t *n;
	int err;

	err = snd_config_search(cfg, "Include", &n);
	if (err == -ENOENT)
		return 1;
	if (err < 0)
		return err;

	err = uc_mgr_evaluate_include(uc_mgr, cfg, n);
	snd_config_delete(n);
	return err;
}

/*
 * Evaluate condition (in-place)
 */
static int evaluate_condition(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	snd_config_t *n;
	int err;

	err = snd_config_search(cfg, "If", &n);
	if (err == -ENOENT)
		return 1;
	if (err < 0)
		return err;

	err = uc_mgr_evaluate_condition(uc_mgr, cfg, n);
	snd_config_delete(n);
	return err;
}

/*
 * Evaluate variant (in-place)
 */
static int evaluate_variant(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *n, *c;
	const char *id;
	int err;

	err = snd_config_search(cfg, "Variant", &c);
	if (err == -ENOENT)
		return 1;
	if (err < 0)
		return err;

	if (uc_mgr->conf_format < 6) {
		snd_error(UCM, "Variant is supported in v6+ syntax");
		return -EINVAL;
	}

	if (uc_mgr->parse_master_section)
		return 1;

	if (uc_mgr->parse_variant == NULL)
		goto __ret;

	snd_config_for_each(i, next, c) {
		n = snd_config_iterator_entry(i);

		if (snd_config_get_id(n, &id) < 0)
			return -EINVAL;

		if (strcmp(id, uc_mgr->parse_variant))
			continue;

		err = uc_mgr_evaluate_inplace(uc_mgr, n);
		if (err < 0)
			return err;

		err = uc_mgr_config_tree_merge(uc_mgr, cfg, n, NULL, NULL);
		if (err < 0)
			return err;
		snd_config_delete(c);
		return 0;
	}

__ret:
	snd_config_delete(c);
	return 1;
}

/*
 * In-place evaluate
 */
int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
			    snd_config_t *cfg)
{
	long iterations = 10000;
	int err1 = 0, err2 = 0, err3 = 0, err4 = 0, err5 = 0, err6 = 0;

	while (err1 == 0 || err2 == 0 || err3 == 0 || err4 == 0 || err5 == 0 || err6 == 0) {
		if (iterations == 0) {
			snd_error(UCM, "Maximal inplace evaluation iterations number reached (recursive references?)");
			return -EINVAL;
		}
		iterations--;
		/* variables at first */
		err1 = evaluate_define(uc_mgr, cfg);
		if (err1 < 0)
			return err1;
		/* include at second */
		err2 = evaluate_include(uc_mgr, cfg);
		if (err2 < 0)
			return err2;
		/* include or macro may define another variables */
		/* conditions may depend on them */
		if (err2 == 0)
			continue;
		err3 = evaluate_variant(uc_mgr, cfg);
		if (err3 < 0)
			return err3;
		if (err3 == 0)
			continue;
		uc_mgr->macro_hops++;
		if (uc_mgr->macro_hops > 100) {
			snd_error(UCM, "Maximal macro hops reached!");
			return -EINVAL;
		}
		err4 = evaluate_macro(uc_mgr, cfg);
		uc_mgr->macro_hops--;
		if (err4 < 0)
			return err4;
		if (err4 == 0)
			continue;
		err5 = uc_mgr_evaluate_repeat(uc_mgr, cfg);
		if (err5 < 0)
			return err5;
		err6 = evaluate_condition(uc_mgr, cfg);
		if (err6 < 0)
			return err6;
	}
	return 0;
}

/*
 * Parse one item for alsa-lib config
 */
static int parse_libconfig1(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *n, *config = NULL;
	const char *id, *file = NULL;
	bool substfile = false, substconfig = false;
	int err;

	if (snd_config_get_id(cfg, &id) < 0)
		return -EINVAL;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for %s", id);
		return -EINVAL;
	}

	snd_config_for_each(i, next, cfg) {
		n = snd_config_iterator_entry(i);

		if (snd_config_get_id(n, &id) < 0)
			return -EINVAL;

		if (strcmp(id, "File") == 0 ||
		    strcmp(id, "SubstiFile") == 0) {
			substfile = id[0] == 'S';
			err = snd_config_get_string(n, &file);
			if (err < 0)
				return err;
			continue;
		}

		if (strcmp(id, "Config") == 0 ||
		    strcmp(id, "SubstiConfig") == 0) {
			substconfig = id[0] == 'S';
			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND)
				return -EINVAL;
			config = n;
			continue;
		}

		snd_error(UCM, "unknown field %s", id);
		return -EINVAL;
	}

	if (file) {
		if (substfile) {
			snd_config_t *cfg;
			err = uc_mgr_config_load_file(uc_mgr, file, &cfg, false);
			if (err < 0)
				return err;
			err = uc_mgr_substitute_tree(uc_mgr, cfg);
			if (err < 0) {
				snd_config_delete(cfg);
				return err;
			}
			err = snd_config_merge(uc_mgr->local_config, cfg, 0);
			if (err < 0) {
				snd_config_delete(cfg);
				return err;
			}
		} else {
			char filename[PATH_MAX];

			ucm_filename(filename, sizeof(filename), uc_mgr->conf_format,
				     file[0] == '/' ? NULL : uc_mgr->conf_dir_name,
				     file);
			err = uc_mgr_config_load_into(uc_mgr->conf_format, filename, uc_mgr->local_config, false);
			if (err < 0)
				return err;
		}
	}

	if (config) {
		if (substconfig) {
			err = uc_mgr_substitute_tree(uc_mgr, config);
			if (err < 0)
				return err;
		}
		err = snd_config_merge(uc_mgr->local_config, config, 0);
		if (err < 0)
			return err;
	}

	return 0;
}

/*
 * Parse alsa-lib config
 */
static int parse_libconfig(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	const char *id;
	int err;

	if (snd_config_get_id(cfg, &id) < 0)
		return -EINVAL;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for %s", id);
		return -EINVAL;
	}

	snd_config_for_each(i, next, cfg) {
		n = snd_config_iterator_entry(i);

		err = parse_libconfig1(uc_mgr, n);
		if (err < 0)
			return err;
	}

	return 0;
}

/*
 * Parse transition
 */
static int parse_transition(snd_use_case_mgr_t *uc_mgr,
			    struct list_head *tlist,
			    snd_config_t *cfg)
{
	struct transition_sequence *tseq;
	const char *id;
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err;

	if (snd_config_get_id(cfg, &id) < 0)
		return -EINVAL;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for %s", id);
		return -EINVAL;
	}

	snd_config_for_each(i, next, cfg) {
		n = snd_config_iterator_entry(i);

		if (snd_config_get_id(n, &id) < 0)
			return -EINVAL;

		tseq = calloc(1, sizeof(*tseq));
		if (tseq == NULL)
			return -ENOMEM;
		INIT_LIST_HEAD(&tseq->transition_list);

		err = get_string3(uc_mgr, id, &tseq->name);
		if (err < 0) {
			free(tseq);
			return err;
		}

		err = parse_sequence(uc_mgr, &tseq->transition_list, n);
		if (err < 0) {
			uc_mgr_free_transition_element(tseq);
			return err;
		}

		list_add(&tseq->list, tlist);
	}
	return 0;
}

/*
 * Parse compound
 */
static int parse_compound(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
	  int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *),
	  void *data1, void *data2)
{
	const char *id;
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err;

	if (snd_config_get_id(cfg, &id) < 0)
		return -EINVAL;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for %s", id);
		return -EINVAL;
	}
	/* parse compound */
	snd_config_for_each(i, next, cfg) {
		n = snd_config_iterator_entry(i);

		if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
			snd_error(UCM, "compound type expected for %s, is %d", id, snd_config_get_type(cfg));
			return -EINVAL;
		}

		err = fcn(uc_mgr, n, data1, data2);
		if (err < 0)
			return err;
	}

	return 0;
}

static int strip_legacy_dev_index(char *name)
{
	char *dot = strchr(name, '.');
	if (!dot)
		return 0;
	if (dot[1] != '0' || dot[2] != '\0') {
		snd_error(UCM, "device name %s contains a '.',"
			       " and is not legacy foo.0 format", name);

		return -EINVAL;
	}
	*dot = '\0';
	return 0;
}

/*
 * Parse device list
 */
static int parse_device_list(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
			     struct dev_list *dev_list,
			     enum dev_list_type type,
			     snd_config_t *cfg)
{
	struct dev_list_node *sdev;
	const char *id;
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err;

	if (dev_list->type != DEVLIST_NONE) {
		snd_error(UCM, "multiple supported or conflicting device lists");

		return -EEXIST;
	}

	if (snd_config_get_id(cfg, &id) < 0)
		return -EINVAL;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for %s", id);
		return -EINVAL;
	}

	snd_config_for_each(i, next, cfg) {
		n = snd_config_iterator_entry(i);

		if (snd_config_get_id(n, &id) < 0)
			return -EINVAL;

		sdev = calloc(1, sizeof(struct dev_list_node));
		if (sdev == NULL)
			return -ENOMEM;
		err = parse_string_substitute3(uc_mgr, n, &sdev->name);
		if (err < 0) {
			free(sdev);
			return err;
		}
		err = strip_legacy_dev_index(sdev->name);
		if (err < 0) {
			free(sdev->name);
			free(sdev);
			return err;
		}
		list_add(&sdev->list, &dev_list->list);
	}

	dev_list->type = type;

	return 0;
}

/* Find a component device by its name, and remove it from machine device
 * list.
 *
 * Component devices are defined by machine components (usually off-soc
 * codes or DSP embeded in SoC). Since alsaconf imports their configuration
 * files automatically, we don't know which devices are component devices
 * until they are referenced by a machine device sequence. So here when we
 * find a referenced device, we move it from the machine device list to the
 * component device list. Component devices will not be exposed to applications
 * by the original API to list devices for backward compatibility. So sound
 * servers can only see the machine devices.
 */
struct use_case_device *find_component_dev(snd_use_case_mgr_t *uc_mgr,
	const char *name)
{
	struct list_head *pos, *posdev, *_posdev;
	struct use_case_verb *verb;
	struct use_case_device *dev;

	list_for_each(pos, &uc_mgr->verb_list) {
		verb = list_entry(pos, struct use_case_verb, list);

		/* search in the component device list */
		list_for_each(posdev, &verb->cmpt_device_list) {
			dev = list_entry(posdev, struct use_case_device, list);
			if (!strcmp(dev->name, name))
				return dev;
		}

		/* search the machine device list */
		list_for_each_safe(posdev, _posdev, &verb->device_list) {
			dev = list_entry(posdev, struct use_case_device, list);
			if (!strcmp(dev->name, name)) {
				/* find the component device, move it from the
				 * machine device list to the component device
				 * list.
				 */
				list_del(&dev->list);
				list_add_tail(&dev->list,
					      &verb->cmpt_device_list);
				return dev;
			}
		}
	}

	return NULL;
}

/* parse sequence of a component device
 *
 * This function will find the component device and mark if its enable or
 * disable sequence is needed by its parenet device.
 */
static int parse_component_seq(snd_use_case_mgr_t *uc_mgr,
			       snd_config_t *n, int enable,
			       struct component_sequence *cmpt_seq)
{
	char *val;
	int err;

	err = parse_string_substitute3(uc_mgr, n, &val);
	if (err < 0)
		return err;

	cmpt_seq->device = find_component_dev(uc_mgr, val);
	if (!cmpt_seq->device) {
		snd_error(UCM, "Cannot find component device %s", val);
		free(val);
		return -EINVAL;
	}
	free(val);

	/* Parent needs its enable or disable sequence */
	cmpt_seq->enable = enable;

	return 0;
}

/*
 * Parse sequences.
 *
 * Sequence controls elements  are in the following form:-
 *
 * cdev "hw:0"
 * cset "element_id_syntax value_syntax"
 * usleep time
 * exec "any unix command with arguments"
 * enadev "component device name"
 * disdev "component device name"
 *
 * e.g.
 *	cset "name='Master Playback Switch' 0,0"
 *      cset "iface=PCM,name='Disable HDMI',index=1 0"
 *	enadev "rt286:Headphones"
 *	disdev "rt286:Speaker"
 */
static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
			  struct list_head *base,
			  snd_config_t *cfg)
{
	struct sequence_element *curr;
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err, idx = 0;
	const char *cmd = NULL;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound is expected for sequence definition");
		return -EINVAL;
	}

	snd_config_for_each(i, next, cfg) {
		const char *id;
		idx ^= 1;
		n = snd_config_iterator_entry(i);
		err = snd_config_get_id(n, &id);
		if (err < 0)
			continue;
		if (idx == 1) {
			if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
				snd_error(UCM, "string type is expected for sequence command");
				return -EINVAL;
			}
			err = snd_config_get_string(n, &cmd);
			if (err < 0)
				return err;
			continue;
		}

		/* alloc new sequence element */
		curr = calloc(1, sizeof(struct sequence_element));
		if (curr == NULL)
			return -ENOMEM;
		list_add_tail(&curr->list, base);

		if (strcmp(cmd, "cdev") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_CDEV;
			err = parse_string_substitute3(uc_mgr, n, &curr->data.cdev);
			if (err < 0) {
				snd_error(UCM, "cdev requires a string!");
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "cset") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_CSET;
cset:
			err = parse_string_substitute3(uc_mgr, n, &curr->data.cset);
			if (err < 0) {
				snd_error(UCM, "%s requires a string!", cmd);
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "enadev") == 0 ||
		    strcmp(cmd, "disdev") == 0) {
			/* need to enable or disable a component device */
			curr->type = SEQUENCE_ELEMENT_TYPE_CMPT_SEQ;
			err = parse_component_seq(uc_mgr, n,
						strcmp(cmd, "enadev") == 0,
						&curr->data.cmpt_seq);
			if (err < 0) {
				snd_error(UCM, "%s requires a valid device!", cmd);
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "enadev2") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_DEV_ENABLE_SEQ;
			goto device;
		}

		if (strcmp(cmd, "disdev2") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_SEQ;
device:
			err = parse_string_substitute3(uc_mgr, n, &curr->data.device);
			if (err < 0) {
				snd_error(UCM, "%s requires a valid device!", cmd);
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "disdevall") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_ALL;
			continue;
		}

		if (strcmp(cmd, "cset-bin-file") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE;
			goto cset;
		}

		if (strcmp(cmd, "cset-tlv") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_CSET_TLV;
			goto cset;
		}

		if (strcmp(cmd, "cset-new") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_CSET_NEW;
			goto cset;
		}

		if (strcmp(cmd, "ctl-remove") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_CTL_REMOVE;
			goto cset;
		}

		if (strcmp(cmd, "sysw") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_SYSSET;
			err = parse_string_substitute3(uc_mgr, n, &curr->data.sysw);
			if (err < 0) {
				snd_error(UCM, "sysw requires a string!");
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "usleep") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
			err = parse_integer_substitute3(uc_mgr, n, &curr->data.sleep);
			if (err < 0) {
				snd_error(UCM, "usleep requires integer!");
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "msleep") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
			err = parse_integer_substitute3(uc_mgr, n, &curr->data.sleep);
			if (err < 0) {
				snd_error(UCM, "msleep requires integer!");
				return err;
			}
			curr->data.sleep *= 1000L;
			continue;
		}

		if (strcmp(cmd, "exec") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_EXEC;
exec:
			err = parse_string_substitute3(uc_mgr, n, &curr->data.exec);
			if (err < 0) {
				snd_error(UCM, "exec requires a string!");
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "shell") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_SHELL;
			goto exec;
		}

		if (strcmp(cmd, "cfg-save") == 0) {
			curr->type = SEQUENCE_ELEMENT_TYPE_CFGSAVE;
			err = parse_string_substitute3(uc_mgr, n, &curr->data.cfgsave);
			if (err < 0) {
				snd_error(UCM, "sysw requires a string!");
				return err;
			}
			continue;
		}

		if (strcmp(cmd, "comment") == 0)
			goto skip;

		snd_error(UCM, "sequence command '%s' is ignored", cmd);

skip:
		list_del(&curr->list);
		uc_mgr_free_sequence_element(curr);
	}

	return 0;
}

/*
 *
 */
int uc_mgr_add_value(struct list_head *base, const char *key, char *val)
{
	struct ucm_value *curr;

	curr = calloc(1, sizeof(struct ucm_value));
	if (curr == NULL)
		return -ENOMEM;
	curr->name = strdup(key);
	if (curr->name == NULL) {
		free(curr);
		return -ENOMEM;
	}
	list_add_tail(&curr->list, base);
	curr->data = val;
	return 0;
}

/*
 * Parse values.
 *
 * Parse values describing PCM, control/mixer settings and stream parameters.
 *
 * Value {
 *   TQ Voice
 *   CapturePCM "hw:1"
 *   PlaybackVolume "name='Master Playback Volume',index=2"
 *   PlaybackSwitch "name='Master Playback Switch',index=2"
 * }
 */
static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
			  struct list_head *base,
			  snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	char *s;
	snd_config_type_t type;
	int err;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound is expected for value definition");
		return -EINVAL;
	}

	/* in-place evaluation */
	err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
	if (err < 0)
		return err;

	snd_config_for_each(i, next, cfg) {
		const char *id;
		n = snd_config_iterator_entry(i);
		err = snd_config_get_id(n, &id);
		if (err < 0)
			continue;

		type = snd_config_get_type(n);
		switch (type) {
		case SND_CONFIG_TYPE_INTEGER:
		case SND_CONFIG_TYPE_INTEGER64:
		case SND_CONFIG_TYPE_REAL:
			err = snd_config_get_ascii(n, &s);
			if (err < 0) {
				snd_error(UCM, "unable to parse value for id '%s': %s!", id, snd_strerror(err));
				return err;
			}
			break;
		case SND_CONFIG_TYPE_STRING:
			err = parse_string_substitute(uc_mgr, n, &s);
			if (err < 0) {
				snd_error(UCM, "unable to parse a string for id '%s'!", id);
				return err;
			}
			break;
		default:
			snd_error(UCM, "invalid type %i in Value compound '%s'", type, id);
			return -EINVAL;
		}
		err = uc_mgr_add_value(base, id, s);
		if (err < 0) {
			free(s);
			return err;
		}
	}

	return 0;
}

/*
 * Parse Modifier Use cases
 *
 * # Each modifier is described in new section. N modifiers are allowed
 * SectionModifier."Capture Voice" {
 *
 *	Comment "Record voice call"
 *
 *	SupportedDevice [
 *		"x"
 *		"y"
 *	]
 *
 *	ConflictingDevice [
 *		"x"
 *		"y"
 *	]
 *
 *	EnableSequence [
 *		....
 *	]
 *
 *	DisableSequence [
 *		...
 *	]
 *
 *      TransitionSequence."ToModifierName" [
 *		...
 *	]
 *
 *	# Optional TQ and ALSA PCMs
 *	Value {
 *		TQ Voice
 *		CapturePCM "hw:1"
 *		PlaybackVolume "name='Master Playback Volume',index=2"
 *		PlaybackSwitch "name='Master Playback Switch',index=2"
 *	}
 * }
 *
 * SupportedDevice and ConflictingDevice cannot be specified together.
 * Both are optional.
 */
static int parse_modifier(snd_use_case_mgr_t *uc_mgr,
			  snd_config_t *cfg,
			  void *data1, void *data2)
{
	struct use_case_verb *verb = data1;
	struct use_case_modifier *modifier;
	char *name;
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err;

	if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
		return -EINVAL;

	/* allocate modifier */
	modifier = calloc(1, sizeof(*modifier));
	if (modifier == NULL) {
		free(name);
		return -ENOMEM;
	}
	INIT_LIST_HEAD(&modifier->enable_list);
	INIT_LIST_HEAD(&modifier->disable_list);
	INIT_LIST_HEAD(&modifier->transition_list);
	INIT_LIST_HEAD(&modifier->dev_list.list);
	INIT_LIST_HEAD(&modifier->value_list);
	list_add_tail(&modifier->list, &verb->modifier_list);
	modifier->name = name;

	/* in-place evaluation */
	err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
	if (err < 0)
		return err;

	snd_config_for_each(i, next, cfg) {
		const char *id;
		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		if (strcmp(id, "Comment") == 0) {
			err = parse_string_substitute3(uc_mgr, n, &modifier->comment);
			if (err < 0) {
				snd_error(UCM, "failed to get modifier comment");
				return err;
			}
			continue;
		}

		if (strcmp(id, "SupportedDevice") == 0) {
			err = parse_device_list(uc_mgr, &modifier->dev_list,
						DEVLIST_SUPPORTED, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse supported device list");
				return err;
			}
		}

		if (strcmp(id, "ConflictingDevice") == 0) {
			err = parse_device_list(uc_mgr, &modifier->dev_list,
						DEVLIST_CONFLICTING, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse conflicting device list");
				return err;
			}
		}

		if (strcmp(id, "EnableSequence") == 0) {
			err = parse_sequence(uc_mgr, &modifier->enable_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse modifier enable sequence");
				return err;
			}
			continue;
		}

		if (strcmp(id, "DisableSequence") == 0) {
			err = parse_sequence(uc_mgr, &modifier->disable_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse modifier disable sequence");
				return err;
			}
			continue;
		}

		if (strcmp(id, "TransitionSequence") == 0) {
			err = parse_transition(uc_mgr, &modifier->transition_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse transition modifier");
				return err;
			}
			continue;
		}

		if (strcmp(id, "Value") == 0) {
			err = parse_value(uc_mgr, &modifier->value_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse Value");
				return err;
			}
			continue;
		}
	}

	return 0;
}

/*
 * Parse device configuration fields
 */
static int parse_device_fields(snd_use_case_mgr_t *uc_mgr,
			       snd_config_t *cfg,
			       struct use_case_device *device)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err;

	snd_config_for_each(i, next, cfg) {
		const char *id;
		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		if (strcmp(id, "Comment") == 0) {
			err = parse_string_substitute3(uc_mgr, n, &device->comment);
			if (err < 0) {
				snd_error(UCM, "failed to get device comment");
				return err;
			}
			continue;
		}

		if (strcmp(id, "SupportedDevice") == 0) {
			err = parse_device_list(uc_mgr, &device->dev_list,
						DEVLIST_SUPPORTED, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse supported device list");

				return err;
			}
		}

		if (strcmp(id, "ConflictingDevice") == 0) {
			err = parse_device_list(uc_mgr, &device->dev_list,
						DEVLIST_CONFLICTING, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse conflicting device list");

				return err;
			}
		}

		if (strcmp(id, "EnableSequence") == 0) {
			err = parse_sequence(uc_mgr, &device->enable_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse device enable sequence");

				return err;
			}
			continue;
		}

		if (strcmp(id, "DisableSequence") == 0) {
			err = parse_sequence(uc_mgr, &device->disable_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse device disable sequence");

				return err;
			}
			continue;
		}

		if (strcmp(id, "TransitionSequence") == 0) {
			err = parse_transition(uc_mgr, &device->transition_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse transition device");

				return err;
			}
			continue;
		}

		if (strcmp(id, "Value") == 0) {
			err = parse_value(uc_mgr, &device->value_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse Value");
				return err;
			}
			continue;
		}
	}
	return 0;
}

/*
 * Helper function to copy, evaluate and optionally merge configuration trees.
 */
static int uc_mgr_config_copy_eval_merge(snd_use_case_mgr_t *uc_mgr,
					 snd_config_t **dst,
					 snd_config_t *src,
					 snd_config_t *merge_from)
{
	snd_config_t *tmp = NULL;
	int err;

	err = snd_config_copy(&tmp, src);
	if (err < 0)
		return err;

	err = uc_mgr_evaluate_inplace(uc_mgr, tmp);
	if (err < 0) {
		snd_config_delete(tmp);
		return err;
	}

	if (merge_from) {
		err = uc_mgr_config_tree_merge(uc_mgr, tmp, merge_from, NULL, NULL);
		if (err < 0) {
			snd_config_delete(tmp);
			return err;
		}
	}

	*dst = tmp;
	return 0;
}

/*
 * Parse Device Use Cases
 *
 * # Each device is described in new section. N devices are allowed
 * SectionDevice."Headphones" {
 *	Comment "Headphones connected to 3.5mm jack"
 *
 *	SupportedDevice [
 *		"x"
 *		"y"
 *	]
 *
 *	ConflictingDevice [
 *		"x"
 *		"y"
 *	]
 *
 *	EnableSequence [
 *		....
 *	]
 *
 *	DisableSequence [
 *		...
 *	]
 *
 *      TransitionSequence."ToDevice" [
 *		...
 *	]
 *
 *	Value {
 *		PlaybackVolume "name='Master Playback Volume',index=2"
 *		PlaybackSwitch "name='Master Playback Switch',index=2"
 *	}
 * }
 */

static int parse_device_by_name(snd_use_case_mgr_t *uc_mgr,
				snd_config_t *cfg,
				struct use_case_verb *verb,
				const char *name,
				struct use_case_device **ret_device)
{
	struct use_case_device *device;
	int err;

	device = calloc(1, sizeof(*device));
	if (device == NULL)
		return -ENOMEM;

	INIT_LIST_HEAD(&device->enable_list);
	INIT_LIST_HEAD(&device->disable_list);
	INIT_LIST_HEAD(&device->transition_list);
	INIT_LIST_HEAD(&device->dev_list.list);
	INIT_LIST_HEAD(&device->value_list);
	INIT_LIST_HEAD(&device->variants);
	INIT_LIST_HEAD(&device->variant_list);
	list_add_tail(&device->list, &verb->device_list);
	device->name = strdup(name);
	if (device->name == NULL) {
		free(device);
		return -ENOMEM;
	}
	device->orig_name = strdup(name);
	if (device->orig_name == NULL)
		return -ENOMEM;

	err = parse_device_fields(uc_mgr, cfg, device);
	if (err < 0)
		return err;

	if (ret_device)
		*ret_device = device;

	return 0;
}

static int parse_device(snd_use_case_mgr_t *uc_mgr,
			snd_config_t *cfg,
			void *data1, void *data2)
{
	struct use_case_verb *verb = data1;
	char *name, *colon;
	const char *variant_label = NULL;
	struct use_case_device *device = NULL;
	snd_config_t *primary_cfg_copy = NULL;
	snd_config_t *device_variant = NULL;
	snd_config_t *merged_cfg = NULL;
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err;

	if (parse_get_safe_name(uc_mgr, cfg, data2, &name) < 0)
		return -EINVAL;

	if (uc_mgr->conf_format >= 8 && (colon = strchr(name, ':'))) {
		variant_label = colon + 1;

		err = snd_config_search(cfg, "DeviceVariant", &device_variant);
		if (err == 0) {
			snd_config_t *variant_cfg = NULL;

			/* Save a copy of the primary config for creating variant devices */
			err = snd_config_copy(&primary_cfg_copy, cfg);
			if (err < 0) {
				free(name);
				return err;
			}

			err = snd_config_search(device_variant, variant_label, &variant_cfg);
			if (err == 0) {
				err = uc_mgr_config_copy_eval_merge(uc_mgr, &merged_cfg, cfg, variant_cfg);
				if (err < 0) {
					free(name);
					return err;
				}
				cfg = merged_cfg;
			}
		}
	}

	/* in-place evaluation */
	if (cfg != merged_cfg) {
		err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
		if (err < 0) {
			free(name);
			goto __error;
		}
	}

	err = parse_device_by_name(uc_mgr, cfg, verb, name, &device);
	free(name);
	if (err < 0)
		goto __error;

	if (merged_cfg) {
		snd_config_delete(merged_cfg);
		merged_cfg = NULL;
	}

	if (device_variant == NULL)
		goto __end;

	if (device->dev_list.type == DEVLIST_SUPPORTED) {
		snd_error(UCM, "DeviceVariant cannot be used with SupportedDevice");
		err = -EINVAL;
		goto __error;
	}

	if (snd_config_get_type(device_variant) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for DeviceVariant");
		err = -EINVAL;
		goto __error;
	}

	colon = strchr(device->name, ':');
	if (!colon) {
		snd_error(UCM, "DeviceVariant requires ':' in device name");
		err = -EINVAL;
		goto __error;
	}

	snd_config_for_each(i, next, device_variant) {
		const char *variant_name;
		char variant_device_name[128];
		struct use_case_device *variant = NULL;

		n = snd_config_iterator_entry(i);

		if (snd_config_get_id(n, &variant_name) < 0)
			continue;

		/* Create variant device name: base:variant_name */
		snprintf(variant_device_name, sizeof(variant_device_name),
			 "%.*s:%s", (int)(colon - device->name),
			 device->name, variant_name);

		err = uc_mgr_config_copy_eval_merge(uc_mgr, &merged_cfg, primary_cfg_copy, n);
		if (err < 0)
			goto __error;

		err = parse_device_by_name(uc_mgr, merged_cfg, verb,
					   variant_device_name, &variant);
		snd_config_delete(merged_cfg);
		merged_cfg = NULL;
		if (err < 0)
			goto __error;

		/* Link variant to primary device */
		list_add(&variant->variant_list, &device->variants);

		err = uc_mgr_put_to_dev_list(&device->dev_list, variant->name);
		if (err < 0)
			goto __error;
		if (device->dev_list.type == DEVLIST_NONE)
			device->dev_list.type = DEVLIST_CONFLICTING;
	}

__end:
	err = 0;
__error:
	if (merged_cfg)
		snd_config_delete(merged_cfg);
	if (primary_cfg_copy)
		snd_config_delete(primary_cfg_copy);
	return err;
}

/*
 * Parse Device Rename/Delete Command
 *
 * # The devices might be renamed to allow the better conditional runtime
 * # evaluation. Bellow example renames Speaker1 device to Speaker and
 * # removes Speaker2 device.
 * RenameDevice."Speaker1" "Speaker"
 * RemoveDevice."Speaker2" "Speaker2"
 */
static int parse_dev_name_list(snd_use_case_mgr_t *uc_mgr,
			       snd_config_t *cfg,
			       struct list_head *list)
{
	snd_config_t *n;
	snd_config_iterator_t i, next;
	const char *id, *name1;
	char *name1s, *name2;
	struct ucm_dev_name *dev;
	snd_config_iterator_t pos;
	int err;

	if (snd_config_get_id(cfg, &id) < 0)
		return -EINVAL;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for %s", id);
		return -EINVAL;
	}

	snd_config_for_each(i, next, cfg) {
		n = snd_config_iterator_entry(i);

		if (snd_config_get_id(n, &name1) < 0)
			return -EINVAL;

		err = get_string3(uc_mgr, name1, &name1s);
		if (err < 0)
			return err;

		err = parse_string_substitute3(uc_mgr, n, &name2);
		if (err < 0) {
			free(name1s);
			snd_error(UCM, "failed to get target device name for '%s'", name1);
			return err;
		}

		/* skip duplicates */
		list_for_each(pos, list) {
			dev = list_entry(pos, struct ucm_dev_name, list);
			if (strcmp(dev->name1, name1s) == 0) {
				free(name2);
				free(name1s);
				return 0;
			}
		}

		free(name1s);

		dev = calloc(1, sizeof(*dev));
		if (dev == NULL) {
			free(name2);
			return -ENOMEM;
		}
		dev->name1 = strdup(name1);
		if (dev->name1 == NULL) {
			free(dev);
			free(name2);
			return -ENOMEM;
		}
		dev->name2 = name2;
		list_add_tail(&dev->list, list);
	}

	return 0;
}

static int parse_compound_check_legacy(snd_use_case_mgr_t *uc_mgr,
	  snd_config_t *cfg,
	  int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *),
	  void *data1)
{
	const char *id, *idchild;
	int child_ctr = 0, legacy_format = 1;
	snd_config_iterator_t i, next;
	snd_config_t *child;
	int err;

	err = snd_config_get_id(cfg, &id);
	if (err < 0)
		return err;

	snd_config_for_each(i, next, cfg) {
		child_ctr++;
		if (child_ctr > 1) {
			break;
		}

		child = snd_config_iterator_entry(i);

		if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
			legacy_format = 0;
			break;
		}

		if (snd_config_get_id(child, &idchild) < 0)
			return -EINVAL;

		if (strcmp(idchild, "0")) {
			legacy_format = 0;
			break;
		}
	}
	if (child_ctr != 1) {
		legacy_format = 0;
	}

	if (legacy_format)
		return parse_compound(uc_mgr, cfg, fcn, data1, (void *)id);
	else
		return fcn(uc_mgr, cfg, data1, NULL);
}

static int parse_device_name(snd_use_case_mgr_t *uc_mgr,
			     snd_config_t *cfg,
			     void *data1,
			     void *data2 ATTRIBUTE_UNUSED)
{
	return parse_compound_check_legacy(uc_mgr, cfg, parse_device, data1);
}

static int parse_modifier_name(snd_use_case_mgr_t *uc_mgr,
			     snd_config_t *cfg,
			     void *data1,
			     void *data2 ATTRIBUTE_UNUSED)
{
	return parse_compound_check_legacy(uc_mgr, cfg, parse_modifier, data1);
}

static int verb_dev_list_add(struct use_case_verb *verb,
			     enum dev_list_type dst_type,
			     const char *dst,
			     const char *src)
{
	struct use_case_device *device;
	struct list_head *pos;

	list_for_each(pos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);

		if (strcmp(device->name, dst) != 0)
			continue;
		if (device->dev_list.type != dst_type) {
			if (list_empty(&device->dev_list.list)) {
				device->dev_list.type = dst_type;
			} else {
				snd_error(UCM, "incompatible device list type ('%s', '%s')", device->orig_name, src);
				return -EINVAL;
			}
		}
		return uc_mgr_put_to_dev_list(&device->dev_list, src);
	}
	snd_error(UCM, "unable to find device '%s'", dst);
	return -ENOENT;
}

static int verb_dev_list_check(struct use_case_verb *verb)
{
	struct list_head *pos, *pos2, *pos3;
	struct use_case_device *device, *target_dev;
	struct dev_list_node *dlist, *dlist2;
	int err, iteration;
	int max_iterations = 100; /* safety limit to prevent infinite loops */
	bool added_something;

	/* First pass: ensure bidirectional relationships */
	list_for_each(pos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);
		list_for_each(pos2, &device->dev_list.list) {
			dlist = list_entry(pos2, struct dev_list_node, list);
			err = verb_dev_list_add(verb, device->dev_list.type,
						dlist->name, device->name);
			if (err < 0)
				return err;
		}
	}

	/* Second pass: complete other relationships for devices in group.
	 * For n devices, at most n-1 iterations are needed.
	 */
	for (iteration = 0; iteration < max_iterations; iteration++) {
		added_something = false;

		list_for_each(pos, &verb->device_list) {
			device = list_entry(pos, struct use_case_device, list);

			if (device->dev_list.type == DEVLIST_NONE)
				continue;

			list_for_each(pos2, &device->dev_list.list) {
				dlist = list_entry(pos2, struct dev_list_node, list);

				target_dev = NULL;
				list_for_each(pos3, &verb->device_list) {
					struct use_case_device *tmp_dev;
					tmp_dev = list_entry(pos3, struct use_case_device, list);
					if (strcmp(tmp_dev->name, dlist->name) == 0) {
						target_dev = tmp_dev;
						break;
					}
				}

				if (!target_dev)
					continue;

				list_for_each(pos3, &device->dev_list.list) {
					dlist2 = list_entry(pos3, struct dev_list_node, list);

					if (strcmp(dlist2->name, target_dev->name) == 0)
						continue;

					/* verb_dev_list_add returns 1 if device was added, 0 if already exists */
					err = verb_dev_list_add(verb, device->dev_list.type,
								target_dev->name, dlist2->name);
					if (err < 0)
						return err;
					if (err > 0)
						added_something = true;
				}
			}
		}

		/* If nothing was added in this iteration, we're done */
		if (!added_something)
			break;
	}

	if (iteration >= max_iterations) {
		snd_error(UCM, "too many device list iterations for verb '%s'", verb->name);
		return -EINVAL;
	}

	return 0;
}

/*
 * Check if a device name is already in use
 */
static int is_device_name_used(struct use_case_verb *verb, const char *name, struct use_case_device *current)
{
	struct list_head *pos;
	struct use_case_device *device;

	list_for_each(pos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);
		if (device != current && strcmp(device->name, name) == 0)
			return 1;
	}
	return 0;
}

/*
 * Update all references to a device name in modifiers and other devices.
 * This helper function is used when renaming devices to ensure all
 * dev_list references are updated accordingly.
 */
static int verb_update_device_references(struct use_case_verb *verb,
					  const char *old_name,
					  const char *new_name)
{
	struct list_head *pos, *pos2;
	struct use_case_device *device;
	struct use_case_modifier *modifier;
	struct dev_list_node *dlist;
	char *name_copy;

	list_for_each(pos, &verb->modifier_list) {
		modifier = list_entry(pos, struct use_case_modifier, list);
		list_for_each(pos2, &modifier->dev_list.list) {
			dlist = list_entry(pos2, struct dev_list_node, list);
			if (strcmp(dlist->name, old_name) == 0) {
				name_copy = strdup(new_name);
				if (name_copy == NULL)
					return -ENOMEM;
				free(dlist->name);
				dlist->name = name_copy;
			}
		}
	}

	list_for_each(pos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);
		list_for_each(pos2, &device->dev_list.list) {
			dlist = list_entry(pos2, struct dev_list_node, list);
			if (strcmp(dlist->name, old_name) == 0) {
				name_copy = strdup(new_name);
				if (name_copy == NULL)
					return -ENOMEM;
				free(dlist->name);
				dlist->name = name_copy;
			}
		}
	}

	return 0;
}

/*
 * Normalize device names according to use-case.h specification.
 * Device names like "HDMI 1" or "Line 1" should be normalized to "HDMI1" and "Line1".
 * When device name contains ':', add index and remove everything after ':' (including).
 * If final name is already used, retry with higher index.
 * Also updates dev_list members in modifiers and devices to reference the normalized names.
 */
static int verb_normalize_device_names(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb)
{
	struct list_head *pos;
	struct use_case_device *device;
	char *orig_name, *norm_name, *colon;
	char temp[80];
	int err, index;

	list_for_each(pos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);

		orig_name = strdup(device->name);
		if (orig_name == NULL)
			return -ENOMEM;

		norm_name = strdup(device->name);
		if (norm_name == NULL) {
			err = -ENOMEM;
			goto __error;
		}

		if (uc_mgr->conf_format < 8)
			goto __no_colon;

		colon = strchr(norm_name, ':');
		if (colon) {
			if (colon[1] == '\0' || strchr(colon + 1, ' ')) {
				snd_error(UCM, "device descriptor cannot be empty or contain spaces '%s'", orig_name);
				err = -EINVAL;
				goto __error;
			}
			*colon = '\0';
			index = 1;
			do {
				snprintf(temp, sizeof(temp), "%s%d", norm_name, index);
				if (!is_device_name_used(verb, temp, device))
					break;
				index++;
			} while (index < 100); /* Safety limit */
			if (index >= 100) {
				snd_error(UCM, "too many device name conflicts for '%s'", orig_name);
				err = -EINVAL;
				goto __error;
			}

		} else {
__no_colon:
			err = parse_device_index(&norm_name, &index);
			if (err < 0) {
				snd_error(UCM, "cannot parse device name '%s'", orig_name);
				goto __error;
			}

			if (index <= 0) {
				free(orig_name);
				free(norm_name);
				continue;
			}
			snprintf(temp, sizeof(temp), "%s%d", norm_name, index);
		}

		free(device->name);
		device->name = strdup(temp);
		if (device->name == NULL) {
			err = -ENOMEM;
			goto __error;
		}

		/* Update all references to the old device name */
		err = verb_update_device_references(verb, orig_name, device->name);
		if (err < 0)
			goto __error;

		free(orig_name);
		free(norm_name);
	}

	return 0;

__error:
	free(orig_name);
	free(norm_name);
	return err;
}

/*
 * Strip index from single device names.
 * According to use-case.h specification, if there is only one device
 * with a given base name (e.g., only "HDMI1" and no "HDMI2"), the index
 * should be stripped to produce the final name (e.g., "HDMI").
 */
static int verb_strip_single_device_index(struct use_case_verb *verb)
{
	struct list_head *pos, *pos2;
	struct use_case_device *device, *device2;
	char *base_name, *test_base;
	char *orig_name;
	int count, index, test_index, err;

	list_for_each(pos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);

		base_name = strdup(device->name);
		if (base_name == NULL)
			return -ENOMEM;

		err = parse_device_index(&base_name, &index);
		if (err < 0) {
			free(base_name);
			continue;
		}

		if (index <= 0) {
			free(base_name);
			continue;
		}

		/* Count how many devices have the same base name */
		count = 0;
		list_for_each(pos2, &verb->device_list) {
			device2 = list_entry(pos2, struct use_case_device, list);
			test_base = strdup(device2->name);
			if (test_base == NULL) {
				free(base_name);
				return -ENOMEM;
			}

			err = parse_device_index(&test_base, &test_index);
			if (err >= 0 && strcmp(test_base, base_name) == 0)
				count++;

			free(test_base);
		}

		if (count == 1) {
			orig_name = device->name;
			device->name = base_name;

			err = verb_update_device_references(verb, orig_name, device->name);
			if (err < 0) {
				device->name = orig_name;
				free(base_name);
				return err;
			}

			free(orig_name);
		} else {
			free(base_name);
		}
	}

	return 0;
}

/*
 * Determine priority for a device.
 * Priority order:
 *   1. If 'Priority' value exists, use it as the sort key
 *   2. If 'PlaybackPriority' value exists, use it as the sort key
 *   3. If 'CapturePriority' value exists, use it as the sort key
 *   4. Fallback: LONG_MIN (no priority)
 */
static long verb_device_get_priority(struct use_case_device *device)
{
	struct list_head *pos;
	struct ucm_value *val;
	const char *priority_str = NULL;
	long priority = LONG_MIN;
	int err;

	list_for_each(pos, &device->value_list) {
		val = list_entry(pos, struct ucm_value, list);
		if (strcmp(val->name, "Priority") == 0) {
			priority_str = val->data;
			break;
		}
	}

	if (!priority_str) {
		list_for_each(pos, &device->value_list) {
			val = list_entry(pos, struct ucm_value, list);
			if (strcmp(val->name, "PlaybackPriority") == 0) {
				priority_str = val->data;
				break;
			}
		}
	}

	if (!priority_str) {
		list_for_each(pos, &device->value_list) {
			val = list_entry(pos, struct ucm_value, list);
			if (strcmp(val->name, "CapturePriority") == 0) {
				priority_str = val->data;
				break;
			}
		}
	}

	if (priority_str) {
		err = safe_strtol(priority_str, &priority);
		if (err < 0)
			priority = LONG_MIN;
	}

	return priority;
}

/*
 * Sort devices based on priority values.
 * Priority order:
 *   1. If 'Priority' value exists, use it as the sort key
 *   2. If 'PlaybackPriority' value exists, use it as the sort key
 *   3. If 'CapturePriority' value exists, use it as the sort key
 *   4. Fallback: use device->name (original) as the sort key
 * Higher priority values are placed first in the list.
 */
static int verb_sort_devices(struct use_case_verb *verb)
{
	struct list_head sorted_list;
	struct list_head *pos, *npos;
	struct use_case_device *device, *insert_dev;

	INIT_LIST_HEAD(&sorted_list);

	/* First pass: determine and cache priority for all devices */
	list_for_each(pos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);
		device->sort_priority = verb_device_get_priority(device);
	}

	/* Move devices from verb->device_list to sorted_list in sorted order */
	list_for_each_safe(pos, npos, &verb->device_list) {
		device = list_entry(pos, struct use_case_device, list);

		/* Remove device from original list */
		list_del(&device->list);

		/* Find the insertion point in sorted_list */
		/* Devices are sorted in descending order of priority (higher priority first) */
		/* If priorities are equal or not defined, use device name as key */
		if (list_empty(&sorted_list)) {
			list_add_tail(&device->list, &sorted_list);
		} else {
			struct list_head *pos2, *insert_pos = &sorted_list;
			list_for_each(pos2, &sorted_list) {
				insert_dev = list_entry(pos2, struct use_case_device, list);

				if (device->sort_priority > insert_dev->sort_priority) {
					insert_pos = pos2;
					break;
				} else if (device->sort_priority == insert_dev->sort_priority) {
					if (strcmp(device->name, insert_dev->name) < 0) {
						insert_pos = pos2;
						break;
					}
				}
			}

			list_add_tail(&device->list, insert_pos);
		}
	}

	/* Move sorted list back to verb->device_list */
	list_splice_init(&sorted_list, &verb->device_list);

	return 0;
}

static int verb_device_management(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb)
{
	struct list_head *pos;
	struct ucm_dev_name *dev;
	int err;

	/* rename devices */
	list_for_each(pos, &verb->rename_list) {
		dev = list_entry(pos, struct ucm_dev_name, list);
		err = uc_mgr_rename_device(verb, dev->name1, dev->name2);
		if (err < 0) {
			snd_error(UCM, "cannot rename device '%s' to '%s'", dev->name1, dev->name2);
			return err;
		}
	}

	/* remove devices */
	list_for_each(pos, &verb->remove_list) {
		dev = list_entry(pos, struct ucm_dev_name, list);
		err = uc_mgr_remove_device(verb, dev->name2);
		if (err < 0) {
			snd_error(UCM, "cannot remove device '%s'", dev->name2);
			return err;
		}
	}

	/* those lists are no longer used */
	uc_mgr_free_dev_name_list(&verb->rename_list);
	uc_mgr_free_dev_name_list(&verb->remove_list);

	/* strip index from single device names */
	if (uc_mgr->conf_format >= 8) {
		/* sort devices by priority */
		err = verb_sort_devices(verb);
		if (err < 0)
			return err;
	}

	/* normalize device names to remove spaces per use-case.h specification */
	err = verb_normalize_device_names(uc_mgr, verb);
	if (err < 0)
		return err;

	/* strip index from single device names */
	if (uc_mgr->conf_format >= 8) {
		err = verb_strip_single_device_index(verb);
		if (err < 0)
			return err;

	}

	/* handle conflicting/supported lists */
	return verb_dev_list_check(verb);
}

/*
 * Parse Verb Section
 *
 * # Example Use case verb section for Voice call blah
 * # By Joe Blogs <joe@blogs.com>
 *
 * SectionVerb {
 *	# enable and disable sequences are compulsory
 *	EnableSequence [
 *		cset "name='Master Playback Switch',index=2 0,0"
 *		cset "name='Master Playback Volume',index=2 25,25"
 *		msleep 50
 *		cset "name='Master Playback Switch',index=2 1,1"
 *		cset "name='Master Playback Volume',index=2 50,50"
 *	]
 *
 *	DisableSequence [
 *		cset "name='Master Playback Switch',index=2 0,0"
 *		cset "name='Master Playback Volume',index=2 25,25"
 *		msleep 50
 *		cset "name='Master Playback Switch',index=2 1,1"
 *		cset "name='Master Playback Volume',index=2 50,50"
 *	]
 *
 *      # Optional transition verb
 *      TransitionSequence."ToCaseName" [
 *		msleep 1
 *      ]
 *
 *	# Optional TQ and ALSA PCMs
 *	Value {
 *		TQ HiFi
 *		CapturePCM "hw:0"
 *		PlaybackPCM "hw:0"
 *	}
 * }
 */
static int parse_verb(snd_use_case_mgr_t *uc_mgr,
		      struct use_case_verb *verb,
		      snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	int err;

	/* in-place evaluation */
	err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
	if (err < 0)
		return err;

	/* parse verb section */
	snd_config_for_each(i, next, cfg) {
		const char *id;
		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		if (strcmp(id, "EnableSequence") == 0) {
			err = parse_sequence(uc_mgr, &verb->enable_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse verb enable sequence");
				return err;
			}
			continue;
		}

		if (strcmp(id, "DisableSequence") == 0) {
			err = parse_sequence(uc_mgr, &verb->disable_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse verb disable sequence");
				return err;
			}
			continue;
		}

		if (strcmp(id, "TransitionSequence") == 0) {
			snd_debug(UCM, "Parse TransitionSequence");
			err = parse_transition(uc_mgr, &verb->transition_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse transition sequence");
				return err;
			}
			continue;
		}

		if (strcmp(id, "Value") == 0) {
			err = parse_value(uc_mgr, &verb->value_list, n);
			if (err < 0)
				return err;
			continue;
		}
	}

	return 0;
}

/*
 * Parse a Use case verb configuration.
 *
 * This configuration contains the following :-
 *  o Verb enable and disable sequences.
 *  o Supported Device enable and disable sequences for verb.
 *  o Supported Modifier enable and disable sequences for verb
 *  o Optional QoS for the verb and modifiers.
 *  o Optional PCM device ID for verb and modifiers
 *  o Alias kcontrols IDs for master and volumes and mutes.
 */
static int parse_verb_config(snd_use_case_mgr_t *uc_mgr,
			     const char *use_case_name,
			     const char *comment,
			     snd_config_t *cfg,
			     const char *what)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	struct use_case_verb *verb;
	int err;

	/* allocate verb */
	verb = calloc(1, sizeof(struct use_case_verb));
	if (verb == NULL)
		return -ENOMEM;
	INIT_LIST_HEAD(&verb->enable_list);
	INIT_LIST_HEAD(&verb->disable_list);
	INIT_LIST_HEAD(&verb->transition_list);
	INIT_LIST_HEAD(&verb->device_list);
	INIT_LIST_HEAD(&verb->cmpt_device_list);
	INIT_LIST_HEAD(&verb->modifier_list);
	INIT_LIST_HEAD(&verb->value_list);
	INIT_LIST_HEAD(&verb->rename_list);
	INIT_LIST_HEAD(&verb->remove_list);
	list_add_tail(&verb->list, &uc_mgr->verb_list);
	if (use_case_name == NULL)
		return -EINVAL;
	verb->name = strdup(use_case_name);
	if (verb->name == NULL)
		return -ENOMEM;

	if (comment != NULL) {
		verb->comment = strdup(comment);
		if (verb->comment == NULL)
			return -ENOMEM;
	}

	/* in-place evaluation */
	err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
	if (err < 0)
		return err;

	/* parse master config sections */
	snd_config_for_each(i, next, cfg) {
		const char *id;
		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		/* find verb section and parse it */
		if (strcmp(id, "SectionVerb") == 0) {
			err = parse_verb(uc_mgr, verb, n);
			if (err < 0) {
				snd_error(UCM, "%s failed to parse verb", what);
				return err;
			}
			continue;
		}

		/* find device sections and parse them */
		if (strcmp(id, "SectionDevice") == 0) {
			err = parse_compound(uc_mgr, n,
						parse_device_name, verb, NULL);
			if (err < 0) {
				snd_error(UCM, "%s failed to parse device", what);
				return err;
			}
			continue;
		}

		/* find modifier sections and parse them */
		if (strcmp(id, "SectionModifier") == 0) {
			err = parse_compound(uc_mgr, n,
					     parse_modifier_name, verb, NULL);
			if (err < 0) {
				snd_error(UCM, "%s failed to parse modifier", what);
				return err;
			}
			continue;
		}

		/* device renames */
		if (strcmp(id, "RenameDevice") == 0) {
			err = parse_dev_name_list(uc_mgr, n, &verb->rename_list);
			if (err < 0) {
				snd_error(UCM, "%s failed to parse device rename", what);
				return err;
			}
			continue;
		}

		/* device remove */
		if (strcmp(id, "RemoveDevice") == 0) {
			err = parse_dev_name_list(uc_mgr, n, &verb->remove_list);
			if (err < 0) {
				snd_error(UCM, "%s failed to parse device remove", what);
				return err;
			}
			continue;
		}

		/* alsa-lib configuration */
		if (uc_mgr->conf_format > 3 && strcmp(id, "LibraryConfig") == 0) {
			err = parse_libconfig(uc_mgr, n);
			if (err < 0) {
				snd_error(UCM, "%s failed to parse LibConfig", what);
				return err;
			}
			continue;
		}
	}

	/* use case verb must have at least 1 device */
	if (list_empty(&verb->device_list)) {
		snd_error(UCM, "no use case device defined");
		return -EINVAL;
	}

	/* do device rename and delete */
	err = verb_device_management(uc_mgr, verb);
	if (err < 0) {
		snd_error(UCM, "device management error in verb '%s'", verb->name);
		return err;
	}

	return 0;
}

/*
 * Parse variant information
 */
static int parse_variant(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
			 char **_vfile, char **_vcomment)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	char *file = NULL, *comment = NULL;
	int err;

	/* parse master config sections */
	snd_config_for_each(i, next, cfg) {
		const char *id;
		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		/* get use case verb file name */
		if (strcmp(id, "File") == 0) {
			if (_vfile) {
				err = parse_string_substitute3(uc_mgr, n, &file);
				if (err < 0) {
					snd_error(UCM, "failed to get File");
					goto __error;
				}
			}
			continue;
		}

		/* get optional use case comment */
		if (strncmp(id, "Comment", 7) == 0) {
			if (_vcomment) {
				err = parse_string_substitute3(uc_mgr, n, &comment);
				if (err < 0) {
					snd_error(UCM, "failed to get Comment");
					goto __error;
				}
			}
			continue;
		}

		snd_error(UCM, "unknown field '%s' in Variant section", id);
		err = -EINVAL;
		goto __error;
	}

	if (_vfile)
		*_vfile = file;
	if (_vcomment)
		*_vcomment = comment;
	return 0;

__error:
	free(file);
	free(comment);
	return err;
}

/*
 * Parse master section for "Use Case" and "File" tags.
 */
static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
				void *data1 ATTRIBUTE_UNUSED,
				void *data2 ATTRIBUTE_UNUSED)
{
	snd_config_iterator_t i, next;
	snd_config_t *n, *variant = NULL, *config = NULL;
	char *use_case_name, *file = NULL, *comment = NULL;
	bool variant_ok = false;
	int err;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for use case section");
		return -EINVAL;
	}

	err = parse_get_safe_name(uc_mgr, cfg, NULL, &use_case_name);
	if (err < 0) {
		snd_error(UCM, "unable to get name for use case section");
		return err;
	}

	/* in-place evaluation */
	uc_mgr->parse_master_section = 1;
	err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
	uc_mgr->parse_master_section = 0;
	if (err < 0)
		goto __error;

	/* parse master config sections */
	snd_config_for_each(i, next, cfg) {
		const char *id;
		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		/* get use case verb file name */
		if (strcmp(id, "File") == 0) {
			err = parse_string_substitute3(uc_mgr, n, &file);
			if (err < 0) {
				snd_error(UCM, "failed to get File");
				goto __error;
			}
			continue;
		}

		/* get use case verb configuration block (syntax version 8+) */
		if (strcmp(id, "Config") == 0) {
			if (uc_mgr->conf_format < 8) {
				snd_error(UCM, "Config is supported in v8+ syntax");
				err = -EINVAL;
				goto __error;
			}
			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
				snd_error(UCM, "compound type expected for Config");
				err = -EINVAL;
				goto __error;
			}
			config = n;
			continue;
		}

		/* get optional use case comment */
		if (strncmp(id, "Comment", 7) == 0) {
			err = parse_string_substitute3(uc_mgr, n, &comment);
			if (err < 0) {
				snd_error(UCM, "failed to get Comment");
				goto __error;
			}
			continue;
		}

		if (uc_mgr->conf_format >= 6 && strcmp(id, "Variant") == 0) {
			snd_config_iterator_t i2, next2;
			variant = n;
			snd_config_for_each(i2, next2, n) {
				const char *id2;
				snd_config_t *n2;
				n2 = snd_config_iterator_entry(i2);
				if (snd_config_get_id(n2, &id2) < 0)
					continue;
				err = uc_mgr_evaluate_inplace(uc_mgr, n2);
				if (err < 0)
					goto __error;
				if (strcmp(use_case_name, id2) == 0)
					variant_ok = true;
			}
			continue;
		}

		snd_error(UCM, "unknown field '%s' in SectionUseCase", id);
	}

	if (variant && !variant_ok) {
		snd_error(UCM, "undefined variant '%s'", use_case_name);
		err = -EINVAL;
		goto __error;
	}

	/* check mutual exclusivity of File and Config */
	if (file && config) {
		snd_error(UCM, "both File and Config specified in SectionUseCase");
		err = -EINVAL;
		goto __error;
	}

	if (!variant) {
		snd_debug(UCM, "use_case_name %s file '%s'", use_case_name, file);

		/* do we have both use case name and (file or config) ? */
		if (!file && !config) {
			snd_error(UCM, "use case missing file or config");
			err = -EINVAL;
			goto __error;
		}

		/* parse verb from file or config */
		if (file) {
			snd_config_t *cfg;
			/* load config from file */
			err = uc_mgr_config_load_file(uc_mgr, file, &cfg, false);
			if (err < 0)
				goto __error;
			/* parse the config */
			err = parse_verb_config(uc_mgr, use_case_name, comment, cfg, file);
			snd_config_delete(cfg);
		} else {
			/* inline config - parse directly */
			err = parse_verb_config(uc_mgr, use_case_name, comment, config,
						comment ? comment : use_case_name);
		}
	} else {
		/* parse variants */
		struct list_head orig_variable_list;
		snd_config_t *orig_macros = NULL;
		int first_iteration = 1;

		/* save original variable list */
		err = uc_mgr_duplicate_variables(&orig_variable_list, &uc_mgr->variable_list);
		if (err < 0)
			goto __error;

		/* save original macros */
		if (uc_mgr->macros) {
			err = snd_config_copy(&orig_macros, uc_mgr->macros);
			if (err < 0)
				goto __variant_error;
		}

		snd_config_for_each(i, next, variant) {
			char *vfile, *vcomment;
			const char *id;

			/* restore variables and macros for second and later iterations */
			if (!first_iteration) {
				uc_mgr_free_value(&uc_mgr->variable_list);

				err = uc_mgr_duplicate_variables(&uc_mgr->variable_list, &orig_variable_list);
				if (err < 0)
					goto __variant_error;

				if (uc_mgr->macros) {
					snd_config_delete(uc_mgr->macros);
					uc_mgr->macros = NULL;
				}
				if (orig_macros) {
					err = snd_config_copy(&uc_mgr->macros, orig_macros);
					if (err < 0)
						goto __variant_error;
				}
			}
			first_iteration = 0;

			n = snd_config_iterator_entry(i);
			if (snd_config_get_id(n, &id) < 0)
				continue;
			if (!parse_is_name_safe(id)) {
				err = -EINVAL;
				goto __variant_error;
			}
			err = parse_variant(uc_mgr, n, &vfile, &vcomment);
			if (err < 0)
				break;
			uc_mgr->parse_variant = id;
			if (vfile || file) {
				snd_config_t *cfg;
				const char *fname = vfile ? vfile : file;
				/* load config from file */
				err = uc_mgr_config_load_file(uc_mgr, fname, &cfg, false);
				if (err >= 0) {
					err = parse_verb_config(uc_mgr, id,
								vcomment ? vcomment : comment,
								cfg, fname);
					snd_config_delete(cfg);
				}
			} else {
				/* inline config from variant */
				err = parse_verb_config(uc_mgr, id,
							vcomment ? vcomment : comment,
							config,
							vcomment ? vcomment : (comment ? comment : id));
			}
			uc_mgr->parse_variant = NULL;
			free(vfile);
			free(vcomment);
			if (err < 0)
				break;
		}

__variant_error:
		uc_mgr_free_value(&orig_variable_list);
		if (orig_macros)
			snd_config_delete(orig_macros);
	}

__error:
	free(use_case_name);
	free(file);
	free(comment);
	return err;
}

/*
 * parse controls which should be run only at initial boot (forcefully)
 */
static int parse_controls_fixedboot(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	int err;

	if (!list_empty(&uc_mgr->fixedboot_list)) {
		snd_error(UCM, "FixedBoot list is not empty");
		return -EINVAL;
	}
	err = parse_sequence(uc_mgr, &uc_mgr->fixedboot_list, cfg);
	if (err < 0) {
		snd_error(UCM, "Unable to parse FixedBootSequence");
		return err;
	}

	return 0;
}

/*
 * parse controls which should be run only at initial boot
 */
static int parse_controls_boot(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	int err;

	if (!list_empty(&uc_mgr->boot_list)) {
		snd_error(UCM, "Boot list is not empty");
		return -EINVAL;
	}
	err = parse_sequence(uc_mgr, &uc_mgr->boot_list, cfg);
	if (err < 0) {
		snd_error(UCM, "Unable to parse BootSequence");
		return err;
	}

	return 0;
}

/*
 * parse controls
 */
static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	int err;

	if (!list_empty(&uc_mgr->default_list)) {
		snd_error(UCM, "Default list is not empty");
		return -EINVAL;
	}
	err = parse_sequence(uc_mgr, &uc_mgr->default_list, cfg);
	if (err < 0) {
		snd_error(UCM, "Unable to parse SectionDefaults");
		return err;
	}

	return 0;
}

/*
 * Each sound card has a master sound card file that lists all the supported
 * use case verbs for that sound card. i.e.
 *
 * #Example master file for blah sound card
 * #By Joe Blogs <joe@bloggs.org>
 *
 * Comment "Nice Abstracted Soundcard"
 *
 * # The file is divided into Use case sections. One section per use case verb.
 *
 * SectionUseCase."Voice Call" {
 *	File "voice_call_blah"
 *	Comment "Make a voice phone call."
 * }
 *
 * SectionUseCase."HiFi" {
 *	File "hifi_blah"
 *	Comment "Play and record HiFi quality Music."
 * }
 *
 * # Define Value defaults
 *
 * ValueDefaults {
 *	PlaybackCTL "hw:CARD=0"
 *	CaptureCTL "hw:CARD=0"
 * }
 *
 * # The initial boot (run once) configuration.
 *
 * BootSequence [
 *      cset "name='Master Playback Switch',index=2 1,1"
 *	cset "name='Master Playback Volume',index=2 25,25"
 * ]
 *
 * # This file also stores the default sound card state.
 *
 * SectionDefaults [
 *	cset "name='Master Mono Playback',index=1 0"
 *	cset "name='Master Mono Playback Volume',index=1 0"
 *	cset "name='PCM Switch',index=2 1,1"
 *      exec "some binary here"
 *      msleep 50
 *	........
 * ]
 *
 * # End of example file.
 */
static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	const char *id;
	int err;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for master file");
		return -EINVAL;
	}

	if (uc_mgr->conf_format >= 2) {
		err = parse_syntax_field(uc_mgr, cfg, uc_mgr->conf_file_name);
		if (err < 0)
			return err;
	}

	/* in-place evaluation */
	err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
	if (err < 0)
		return err;

	/* parse ValueGlobals first */
	err = snd_config_search(cfg, "ValueGlobals", &n);
	if (err == 0) {
		err = parse_value(uc_mgr, &uc_mgr->global_value_list, n);
		if (err < 0) {
			snd_error(UCM, "failed to parse ValueGlobals");
			return err;
		}
	}

	err = uc_mgr_check_value(&uc_mgr->global_value_list, "BootCardGroup");
	if (err == 0) {
		uc_mgr->card_group = true;
		/* if we are in boot, skip the main parsing loop */
		if (uc_mgr->in_boot)
			return 0;
	}

	/* parse master config sections */
	snd_config_for_each(i, next, cfg) {

		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		if (strcmp(id, "Comment") == 0) {
			err = parse_string_substitute3(uc_mgr, n, &uc_mgr->comment);
			if (err < 0) {
				snd_error(UCM, "failed to get master comment");
				return err;
			}
			continue;
		}

		/* find use case section and parse it */
		if (strcmp(id, "SectionUseCase") == 0) {
			err = parse_compound(uc_mgr, n,
					     parse_master_section,
					     NULL, NULL);
			if (err < 0)
				return err;
			continue;
		}

		/* find default control values section (force boot sequence only) */
		if (strcmp(id, "FixedBootSequence") == 0) {
			err = parse_controls_fixedboot(uc_mgr, n);
			if (err < 0)
				return err;
			continue;
		}

		/* find default control values section (first boot only) */
		if (strcmp(id, "BootSequence") == 0) {
			err = parse_controls_boot(uc_mgr, n);
			if (err < 0)
				return err;
			continue;
		}

		/* find default control values section and parse it */
		if (strcmp(id, "SectionDefaults") == 0) {
			err = parse_controls(uc_mgr, n);
			if (err < 0)
				return err;
			continue;
		}

		/* ValueDefaults is now parsed at the top of this function */
		if (strcmp(id, "ValueDefaults") == 0) {
			err = parse_value(uc_mgr, &uc_mgr->value_list, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse ValueDefaults");
				return err;
			}
			continue;
		}

		/* ValueGlobals is parsed at the top of this function */
		if (strcmp(id, "ValueGlobals") == 0)
			continue;

		/* alsa-lib configuration */
		if (uc_mgr->conf_format > 3 && strcmp(id, "LibraryConfig") == 0) {
			err = parse_libconfig(uc_mgr, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse LibraryConfig");
				return err;
			}
			continue;
		}

		/* error */
		if (strcmp(id, "Error") == 0)
			return error_node(uc_mgr, n);

		/* skip further Syntax value updates (Include) */
		if (strcmp(id, "Syntax") == 0)
			continue;

		snd_error(UCM, "unknown master file field %s", id);
	}
	return 0;
}

/* get the card info */
static int get_card_info(snd_use_case_mgr_t *mgr,
			 const char *ctl_name,
			 snd_ctl_card_info_t **info)
{
	struct ctl_list *ctl_list;
	int err;

	err = uc_mgr_open_ctl(mgr, &ctl_list, ctl_name, 0);
	if (err < 0)
		return err;

	if (info)
		*info = ctl_list->ctl_info;
	return err;
}

/* find the card in the local machine */
static int get_by_card_name(snd_use_case_mgr_t *mgr, const char *card_name)
{
	int card, err;
	snd_ctl_card_info_t *info;
	const char *_driver, *_name, *_long_name;

	snd_ctl_card_info_alloca(&info);

	card = -1;
	if (snd_card_next(&card) < 0 || card < 0) {
		snd_error(UCM, "no soundcards found...");
		return -1;
	}

	while (card >= 0) {
		char name[32];

		/* clear the list, keep the only one CTL device */
		uc_mgr_free_ctl_list(mgr);

		sprintf(name, "hw:%d", card);
		err = get_card_info(mgr, name, &info);

		if (err == 0) {
			_driver = snd_ctl_card_info_get_driver(info);
			_name = snd_ctl_card_info_get_name(info);
			_long_name = snd_ctl_card_info_get_longname(info);
			if (!strcmp(card_name, _driver) ||
			    !strcmp(card_name, _name) ||
			    !strcmp(card_name, _long_name))
				return 0;
		}

		if (snd_card_next(&card) < 0) {
			snd_error(UCM, "snd_card_next");
			break;
		}
	}

	uc_mgr_free_ctl_list(mgr);

	return -1;
}

/* set the driver name and long name by the card ctl name */
static inline int get_by_card(snd_use_case_mgr_t *mgr, const char *ctl_name)
{
	return get_card_info(mgr, ctl_name, NULL);
}

static int parse_toplevel_path(snd_use_case_mgr_t *uc_mgr,
			       char *filename,
			       snd_config_t *cfg)
{
	snd_config_iterator_t i, next, i2, next2;
	snd_config_t *n, *n2;
	const char *id;
	char *dir = NULL, *file = NULL, fn[PATH_MAX];
	struct stat64 st;
	long version;
	int err;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for UseCasePath node");
		return -EINVAL;
	}

	/* parse use case path config sections */
	snd_config_for_each(i, next, cfg) {
		n = snd_config_iterator_entry(i);

		if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
			snd_error(UCM, "compound type expected for UseCasePath.something node");
			return -EINVAL;
		}

			if (snd_config_get_id(n, &id) < 0)
				continue;

		version = 2;

		/* parse use case path config sections */
		snd_config_for_each(i2, next2, n) {

			n2 = snd_config_iterator_entry(i2);
			if (snd_config_get_id(n2, &id) < 0)
				continue;

			if (strcmp(id, "Version") == 0) {
				err = parse_integer_substitute(uc_mgr, n2, &version);
				if (err < 0) {
					snd_error(UCM, "unable to parse UcmDirectory");
					goto __error;
				}
				if (version < 1 || version > 2) {
					snd_error(UCM, "Version must be 1 or 2");
					err = -EINVAL;
					goto __error;
				}
				continue;
			}

			if (strcmp(id, "Directory") == 0) {
				err = parse_string_substitute(uc_mgr, n2, &dir);
				if (err < 0) {
					snd_error(UCM, "unable to parse Directory");
					goto __error;
				}
				continue;
			}

			if (strcmp(id, "File") == 0) {
				err = parse_string_substitute(uc_mgr, n2, &file);
				if (err < 0) {
					snd_error(UCM, "unable to parse File");
					goto __error;
				}
				continue;
			}

			snd_error(UCM, "unknown UseCasePath field %s", id);
		}

		if (dir == NULL) {
			snd_error(UCM, "Directory is not defined in %s!", filename);
			goto __next;
		}
		if (file == NULL) {
			snd_error(UCM, "File is not defined in %s!", filename);
			goto __next;
		}

		ucm_filename(fn, sizeof(fn), version, dir, file);
		snd_trace(UCM, "probing configuration file '%s'", fn);
		if (access(fn, R_OK) == 0 && lstat64(fn, &st) == 0) {
			if (S_ISLNK(st.st_mode)) {
				ssize_t r;
				char *link, *dir2, *p;

				link = malloc(PATH_MAX);
				if (link == NULL)
					goto __enomem;
				r = readlink(fn, link, PATH_MAX - 1);
				if (r <= 0) {
					free(link);
					goto __next;
				}
				link[r] = '\0';
				p = strrchr(link, '/');
				if (p) {
					*p = '\0';
					dir2 = malloc(PATH_MAX);
					if (dir2 == NULL) {
						free(link);
						goto __enomem;
					}
					strncpy(dir2, dir, PATH_MAX - 1);
					strncat(dir2, "/", PATH_MAX - 1);
					strncat(dir2, link, PATH_MAX - 1);
					fn[PATH_MAX - 1] = '\0';
					free(dir);
					dir = dir2;
				}
				free(link);
			}
			snd_trace(UCM, "using directory '%s' and file '%s'", dir, file);
			if (replace_string(&uc_mgr->conf_dir_name, dir) == NULL)
				goto __enomem;
			if (replace_string(&uc_mgr->conf_file_name, file) == NULL)
				goto __enomem;
			strncpy(filename, fn, PATH_MAX);
			filename[PATH_MAX - 1] = '\0';
			uc_mgr->conf_format = version;
			goto __ok;
		}

__next:
		free(file);
		if (dir != fn)
			free(dir);
		dir = NULL;
		file = NULL;
	}

	err = -ENOENT;
	goto __error;

__enomem:
	err = -ENOMEM;
	goto __error;

__ok:
	err = 0;
__error:
	free(file);
	if (dir != fn)
		free(dir);
	return err;
}

static int parse_toplevel_config(snd_use_case_mgr_t *uc_mgr,
				 char *filename,
				 snd_config_t *cfg)
{
	snd_config_iterator_t i, next;
	snd_config_t *n;
	const char *id;
	int err;

	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
		snd_error(UCM, "compound type expected for toplevel file");
		return -EINVAL;
	}

	err = parse_syntax_field(uc_mgr, cfg, filename);
	if (err < 0)
		return err;

	/* in-place evaluation */
	err = uc_mgr_evaluate_inplace(uc_mgr, cfg);
	if (err < 0)
		return err;

	/* parse toplevel config sections */
	snd_config_for_each(i, next, cfg) {

		n = snd_config_iterator_entry(i);
		if (snd_config_get_id(n, &id) < 0)
			continue;

		if (strcmp(id, "UseCasePath") == 0) {
			err = parse_toplevel_path(uc_mgr, filename, n);
			if (err == 0)
				return err;
			continue;
		}

		/* alsa-lib configuration */
		if (uc_mgr->conf_format > 3 && strcmp(id, "LibraryConfig") == 0) {
			err = parse_libconfig(uc_mgr, n);
			if (err < 0) {
				snd_error(UCM, "failed to parse LibConfig");
				return err;
			}
			continue;
		}

		/* skip further Syntax value updates (Include) */
		if (strcmp(id, "Syntax") == 0)
			continue;

		snd_error(UCM, "unknown toplevel field %s", id);
	}

	return -ENOENT;
}

static int load_toplevel_config(snd_use_case_mgr_t *uc_mgr,
				snd_config_t **cfg)
{
	char filename[PATH_MAX];
	snd_config_t *tcfg;
	int err;

	ucm_filename(filename, sizeof(filename), 2, NULL, "ucm.conf");

	if (access(filename, R_OK) != 0) {
		snd_error(UCM, "Unable to find the top-level configuration file '%s'.", filename);
		return -ENOENT;
	}

	err = uc_mgr_config_load(2, filename, &tcfg, false);
	if (err < 0)
		goto __error;

	/* filename is shared for function input and output! */
	err = parse_toplevel_config(uc_mgr, filename, tcfg);
	snd_config_delete(tcfg);
	if (err < 0)
		goto __error;

	err = uc_mgr_config_load(uc_mgr->conf_format, filename, cfg, false);
	if (err < 0) {
		snd_error(UCM, "could not parse configuration for card %s", uc_mgr->card_name);
		goto __error;
	}

	return 0;

__error:
	return err;
}

/* load master use case file for sound card based on rules in ucm2/ucm.conf
 */
int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr)
{
	snd_config_t *cfg;
	const char *name;
	int err;

	err = snd_config_top(&uc_mgr->local_config);
	if (err < 0)
		return err;

	err = snd_config_top(&uc_mgr->macros);
	if (err < 0)
		return err;

	name = uc_mgr->card_name;
	if (strncmp(name, "hw:", 3) == 0) {
		err = get_by_card(uc_mgr, name);
		if (err < 0) {
			snd_error(UCM, "card '%s' is not valid", name);
			goto __error;
		}
	} else if (strncmp(name, "strict:", 7)) {
		/* do not handle the error here */
		/* we can refer the virtual UCM config */
		get_by_card_name(uc_mgr, name);
	}

	err = load_toplevel_config(uc_mgr, &cfg);
	if (err < 0)
		goto __error;

	err = parse_master_file(uc_mgr, cfg);
	if (uc_mgr->macros) {
		snd_config_delete(uc_mgr->macros);
		uc_mgr->macros = NULL;
	}
	snd_config_delete(cfg);
	if (err < 0) {
		uc_mgr_free_ctl_list(uc_mgr);
		uc_mgr_free_verb(uc_mgr);
	}

	return err;

__error:
	uc_mgr_free_ctl_list(uc_mgr);
	replace_string(&uc_mgr->conf_dir_name, NULL);
	return err;
}

static int filename_filter(const struct dirent64 *dirent)
{
	if (dirent == NULL)
		return 0;
	if (dirent->d_type == DT_DIR) {
		if (dirent->d_name[0] == '.') {
			if (dirent->d_name[1] == '\0')
				return 0;
			if (dirent->d_name[1] == '.' &&
			    dirent->d_name[2] == '\0')
				return 0;
		}
		return 1;
	}
	return 0;
}

/* scan all cards and comments
 *
 * Cards are defined by machines. Each card/machine installs its UCM
 * configuration files in a subdirectory with the same name as the sound
 * card under /usr/share/alsa/ucm2. This function will scan all the card
 * directories and skip the component directories defined in the array
 * component_dir.
 */
int uc_mgr_scan_master_configs(const char **_list[])
{
	char filename[PATH_MAX], dfl[PATH_MAX], fn[FILENAME_MAX];
	char *env = getenv(ALSA_CONFIG_UCM2_VAR);
	snd_use_case_mgr_t *uc_mgr;
	const char **list, *d_name;
	char *s;
	snd_config_t *cfg, *c;
	int i, j, cnt, err, cards;
	long l;
	ssize_t ss;
	struct dirent64 **namelist;

	i = -1;
	cards = 0;
	while (1) {
		err = snd_card_next(&i);
		if (err < 0)
			return err;
		if (i < 0)
			break;
		cards++;
	}
	cards += 4;	/* plug-and-play */

	if (env)
		snprintf(filename, sizeof(filename), "%s/conf.virt.d", env);
	else
		snprintf(filename, sizeof(filename), "%s/ucm2/conf.virt.d",
			 snd_config_topdir());

#if defined(_GNU_SOURCE) && \
    !defined(__NetBSD__) && \
    !defined(__FreeBSD__) && \
    !defined(__OpenBSD__) && \
    !defined(__DragonFly__) && \
    !defined(__sun) && \
    !defined(__ANDROID__) && \
    !defined(__OHOS__)
#define SORTFUNC	versionsort64
#else
#define SORTFUNC	alphasort64
#endif
	err = scandir64(filename, &namelist, filename_filter, SORTFUNC);
	if (err < 0) {
		err = -errno;
		snd_error(UCM, "could not scan directory %s: %s", filename, strerror(-err));
		return err;
	}
	cnt = err;

	dfl[0] = '\0';
	if (strlen(filename) + 8 < sizeof(filename)) {
		strcat(filename, "/default");
		ss = readlink(filename, dfl, sizeof(dfl)-1);
		if (ss >= 0) {
			dfl[ss] = '\0';
			dfl[sizeof(dfl)-1] = '\0';
			if (dfl[0] && dfl[strlen(dfl)-1] == '/')
				dfl[strlen(dfl)-1] = '\0';
		} else {
			dfl[0] = '\0';
		}
	}

	j = 0;
	list = calloc(1, (cards + cnt) * 2 * sizeof(char *));
	if (list == NULL) {
		err = -ENOMEM;
		goto __err;
	}

	i = -1;
	while (j / 2 < cards) {
		err = snd_card_next(&i);
		if (err < 0)
			goto __err;
		if (i < 0)
			break;
		snprintf(fn, sizeof(fn), "-hw:%d", i);
		err = snd_use_case_mgr_open(&uc_mgr, fn);
		if (err == -ENOENT || err == -ENXIO)
			continue;
		if (err < 0) {
			snd_error(UCM, "Unable to open '%s': %s", fn, snd_strerror(err));
			goto __err;
		}
		err = snd_use_case_get(uc_mgr, "comment", (const char **)&s);
		if (err < 0) {
			err = snd_card_get_longname(i, &s);
			if (err < 0)
				goto __err;
		}
		snd_use_case_mgr_close(uc_mgr);
		list[j] = strdup(fn + 1);
		if (list[j] == NULL) {
			free(s);
			err = -ENOMEM;
			goto __err;
		}
		list[j + 1] = s;
		j += 2;
	}

	for (i = 0; i < cnt; i++) {

		d_name = namelist[i]->d_name;

		snprintf(fn, sizeof(fn), "%s.conf", d_name);
		ucm_filename(filename, sizeof(filename), 2, d_name, fn);
#ifdef HAVE_EACCESS
		if (eaccess(filename, R_OK))
#else
		if (access(filename, R_OK))
#endif
			continue;

		err = uc_mgr_config_load(2, filename, &cfg, false);
		if (err < 0)
			goto __err;
		err = snd_config_search(cfg, "Syntax", &c);
		if (err < 0) {
			snd_error(UCM, "Syntax field not found in %s", d_name);
			snd_config_delete(cfg);
			continue;
		}
		err = snd_config_get_integer(c, &l);
		if (err < 0) {
			snd_error(UCM, "Syntax field is invalid in %s", d_name);
			snd_config_delete(cfg);
			goto __err;
		}
		if (l < 2 || l > SYNTAX_VERSION_MAX) {
			snd_error(UCM, "Incompatible syntax %d in %s", l, d_name);
			snd_config_delete(cfg);
			goto __err;
		}
		err = snd_config_search(cfg, "Comment", &c);
		if (err >= 0) {
			err = parse_string(c, (char **)&list[j+1]);
			if (err < 0) {
				snd_config_delete(cfg);
				goto __err;
			}
		}
		snd_config_delete(cfg);
		list[j] = strdup(d_name);
		if (list[j] == NULL) {
			err = -ENOMEM;
			goto __err;
		}
		if (strcmp(dfl, list[j]) == 0) {
			/* default to top */
			const char *save1 = list[j];
			const char *save2 = list[j + 1];
			memmove(list + 2, list, j * sizeof(char *));
			list[0] = save1;
			list[1] = save2;
		}
		j += 2;
	}
	err = 0;

      __err:
	for (i = 0; i < cnt; i++)
		free(namelist[i]);
	free(namelist);
	if (err < 0) {
		for (i = 0; i < j; i++) {
			free((void *)list[i * 2]);
			free((void *)list[i * 2 + 1]);
		}
		free(list);
		return err;
	}

	*_list = list;
	return j;
}
