// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2011, 2016, Intel Corporation.
 */
/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * lustre/utils/lustre_cfg.c
 *
 * Author: Peter J. Braam <braam@clusterfs.com>
 * Author: Phil Schwan <phil@clusterfs.com>
 * Author: Andreas Dilger <adilger@clusterfs.com>
 * Author: Robert Read <rread@clusterfs.com>
 */

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <libcfs/util/ioctl.h>
#include <libcfs/util/string.h>
#include <libcfs/util/param.h>
#include <libcfs/util/parser.h>
#include <lustre/lustreapi.h>
#include <linux/lnet/nidstr.h>
#include <linux/lnet/lnetctl.h>
#include <linux/lustre/lustre_cfg.h>
#include <linux/lustre/lustre_ioctl.h>
#include <linux/lustre/lustre_kernelcomm.h>
#include <linux/lustre/lustre_ver.h>
#include <lnetconfig/liblnetconfig.h>

#include "lctl_thread.h"
#include "lustreapi_internal.h"

#include <sys/un.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>

#include "obdctl.h"
#include <stdio.h>
#include <yaml.h>

static int lcfg_apply_param_yaml(const char *func, const char *filename);

static char *lcfg_devname;

int lcfg_set_devname(char *name)
{
	char *ptr;
	int digit = 1;

	if (name) {
		if (lcfg_devname)
			free(lcfg_devname);
		/* quietly strip the unnecessary '$' */
		if (*name == '$' || *name == '%')
			name++;

		ptr = name;
		while (*ptr != '\0') {
			if (!isdigit(*ptr)) {
				digit = 0;
				break;
			}
			ptr++;
		}

		if (digit) {
			/* We can't translate from dev # to name */
			lcfg_devname = NULL;
		} else {
			lcfg_devname = strdup(name);
		}
	} else {
		lcfg_devname = NULL;
	}
	return 0;
}

char *lcfg_get_devname(void)
{
	return lcfg_devname;
}

int jt_lcfg_device(int argc, char **argv)
{
	return jt_obd_device(argc, argv);
}

static int jt_lcfg_ioctl(struct lustre_cfg_bufs *bufs, char *arg, int cmd)
{
	struct lustre_cfg *lcfg;
	int rc;

	lcfg = malloc(lustre_cfg_len(bufs->lcfg_bufcount, bufs->lcfg_buflen));
	if (!lcfg) {
		rc = -ENOMEM;
	} else {
		lustre_cfg_init(lcfg, cmd, bufs);
		rc = lcfg_ioctl(arg, OBD_DEV_ID, lcfg);
		free(lcfg);
	}
	if (rc < 0)
		fprintf(stderr, "error: %s: %s\n", jt_cmdname(arg),
			strerror(rc = errno));
	return rc;
}

int jt_lcfg_attach(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;
	int rc;

	if (argc != 4)
		return CMD_HELP;

	lustre_cfg_bufs_reset(&bufs, NULL);

	lustre_cfg_bufs_set_string(&bufs, 1, argv[1]);
	lustre_cfg_bufs_set_string(&bufs, 0, argv[2]);
	lustre_cfg_bufs_set_string(&bufs, 2, argv[3]);

	rc = jt_lcfg_ioctl(&bufs, argv[0], LCFG_ATTACH);
	if (rc == 0)
		lcfg_set_devname(argv[2]);

	return rc;
}

int jt_lcfg_setup(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;
	int i;

	if (!lcfg_devname) {
		fprintf(stderr,
			"%s: please use 'device name' to set the device name for config commands.\n",
			jt_cmdname(argv[0]));
		return -EINVAL;
	}

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);

	if (argc > 6)
		return CMD_HELP;

	for (i = 1; i < argc; i++)
		lustre_cfg_bufs_set_string(&bufs, i, argv[i]);

	return jt_lcfg_ioctl(&bufs, argv[0], LCFG_SETUP);
}

int jt_obd_detach(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;

	if (!lcfg_devname) {
		fprintf(stderr,
			"%s: please use 'device name' to set the device name for config commands.\n",
			jt_cmdname(argv[0]));
		return -EINVAL;
	}

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);

	if (argc != 1)
		return CMD_HELP;

	return jt_lcfg_ioctl(&bufs, argv[0], LCFG_DETACH);
}

int jt_obd_cleanup(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;
	char force = 'F';
	char failover = 'A';
	char flags[3] = { 0 };
	int flag_cnt = 0, n;

	if (!lcfg_devname) {
		fprintf(stderr,
			"%s: please use 'device name' to set the device name for config commands.\n",
			jt_cmdname(argv[0]));
		return -EINVAL;
	}

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);

	if (argc < 1 || argc > 3)
		return CMD_HELP;

	/*
	 * we are protected from overflowing our buffer by the argc
	 * check above
	 */
	for (n = 1; n < argc; n++) {
		if (strcmp(argv[n], "force") == 0) {
			flags[flag_cnt++] = force;
		} else if (strcmp(argv[n], "failover") == 0) {
			flags[flag_cnt++] = failover;
		} else {
			fprintf(stderr, "unknown option: %s\n", argv[n]);
			return CMD_HELP;
		}
	}

	if (flag_cnt)
		lustre_cfg_bufs_set_string(&bufs, 1, flags);

	return jt_lcfg_ioctl(&bufs, argv[0], LCFG_CLEANUP);
}

static
int do_add_uuid(char *func, char *uuid, struct lnet_nid *nid)
{
	int rc;
	char nidstr[LNET_NIDSTR_SIZE];
	struct lustre_cfg_bufs bufs;
	struct lustre_cfg *lcfg;

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);
	if (uuid)
		lustre_cfg_bufs_set_string(&bufs, 1, uuid);
	if (!nid_is_nid4(nid)) {
		libcfs_nidstr_r(nid, nidstr, sizeof(nidstr));
		lustre_cfg_bufs_set_string(&bufs, 2, nidstr);
	}

	lcfg = malloc(lustre_cfg_len(bufs.lcfg_bufcount, bufs.lcfg_buflen));
	if (!lcfg) {
		rc = -ENOMEM;
	} else {
		lustre_cfg_init(lcfg, LCFG_ADD_UUID, &bufs);
		if (nid_is_nid4(nid))
			lcfg->lcfg_nid = lnet_nid_to_nid4(nid);
		else
			lcfg->lcfg_nid = 0;

		rc = lcfg_ioctl(func, OBD_DEV_ID, lcfg);
		free(lcfg);
	}
	if (rc) {
		fprintf(stderr, "IOC_PORTAL_ADD_UUID failed: %s\n",
			strerror(errno));
		return -1;
	}

	if (uuid)
		printf("Added uuid %s: %s\n", uuid, libcfs_nidstr(nid));

	return 0;
}

int jt_lcfg_add_uuid(int argc, char **argv)
{
	struct lnet_nid nid;

	if (argc != 3)
		return CMD_HELP;

	if (libcfs_strnid(&nid, argv[2]) < 0) {
		fprintf(stderr, "Can't parse NID %s\n", argv[2]);
		return (-1);
	}

	return do_add_uuid(argv[0], argv[1], &nid);
}

int jt_lcfg_del_uuid(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;

	if (argc != 2) {
		fprintf(stderr, "usage: %s <uuid>\n", argv[0]);
		return 0;
	}

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);
	if (strcmp(argv[1], "_all_"))
		lustre_cfg_bufs_set_string(&bufs, 1, argv[1]);

	return jt_lcfg_ioctl(&bufs, argv[0], LCFG_DEL_UUID);
}

int jt_lcfg_del_mount_option(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;

	if (argc != 2)
		return CMD_HELP;

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);

	/* profile name */
	lustre_cfg_bufs_set_string(&bufs, 1, argv[1]);

	return jt_lcfg_ioctl(&bufs, argv[0], LCFG_DEL_MOUNTOPT);
}

int jt_lcfg_add_conn(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;
	struct lustre_cfg *lcfg;
	int priority;
	int rc;

	if (argc == 2)
		priority = 0;
	else if (argc == 3)
		priority = 1;
	else
		return CMD_HELP;

	if (!lcfg_devname) {
		fprintf(stderr,
			"%s: please use 'device name' to set the device name for config commands.\n",
			jt_cmdname(argv[0]));
		return -EINVAL;
	}

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);

	lustre_cfg_bufs_set_string(&bufs, 1, argv[1]);

	lcfg = malloc(lustre_cfg_len(bufs.lcfg_bufcount, bufs.lcfg_buflen));
	if (!lcfg) {
		rc = -ENOMEM;
	} else {
		lustre_cfg_init(lcfg, LCFG_ADD_CONN, &bufs);
		lcfg->lcfg_num = priority;

		rc = lcfg_ioctl(argv[0], OBD_DEV_ID, lcfg);
		free(lcfg);
	}
	if (rc < 0) {
		fprintf(stderr, "error: %s: %s\n", jt_cmdname(argv[0]),
			strerror(rc = errno));
	}

	return rc;
}

int jt_lcfg_del_conn(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;

	if (argc != 2)
		return CMD_HELP;

	if (!lcfg_devname) {
		fprintf(stderr,
			"%s: please use 'device name' to set the device name for config commands.\n",
			jt_cmdname(argv[0]));
		return -EINVAL;
	}

	lustre_cfg_bufs_reset(&bufs, lcfg_devname);

	/* connection uuid */
	lustre_cfg_bufs_set_string(&bufs, 1, argv[1]);

	return jt_lcfg_ioctl(&bufs, argv[0], LCFG_DEL_MOUNTOPT);
}

/* Param set locally, directly on target */
int jt_lcfg_param(int argc, char **argv)
{
	struct lustre_cfg_bufs bufs;
	int i;

	if (argc >= LUSTRE_CFG_MAX_BUFCOUNT)
		return CMD_HELP;

	lustre_cfg_bufs_reset(&bufs, NULL);

	for (i = 1; i < argc; i++)
		lustre_cfg_bufs_set_string(&bufs, i, argv[i]);

	return jt_lcfg_ioctl(&bufs, argv[0], LCFG_PARAM);
}

static int lcfg_setparam_perm(const char *func, char *buf, bool del)
{
	int rc = 0;
	struct lustre_cfg_bufs bufs;
	struct lustre_cfg *lcfg;

	lustre_cfg_bufs_reset(&bufs, NULL);
	/*
	 * This same command would be executed on all nodes, many
	 * of which should fail (silently) because they don't have
	 * that proc file existing locally. There would be no
	 * preprocessing on the MGS to try to figure out which
	 * parameter files to add this to, there would be nodes
	 * processing on the cluster nodes to try to figure out
	 * if they are the intended targets. They will blindly
	 * try to set the parameter, and ENOTFOUND means it wasn't
	 * for them.
	 * Target name LUSTRE_CFG_ALL_TARGETS means call on all targets. It is
	 * left here in case some filtering will be added in
	 * future.
	 */
	lustre_cfg_bufs_set_string(&bufs, 0, LUSTRE_CFG_ALL_TARGETS);
	lustre_cfg_bufs_set_string(&bufs, 1, buf);
	if (del)
		lustre_cfg_bufs_set_string(&bufs, 2, "del");

	lcfg = malloc(lustre_cfg_len(bufs.lcfg_bufcount,
				     bufs.lcfg_buflen));
	if (!lcfg) {
		rc = -ENOMEM;
		fprintf(stderr, "error: allocating lcfg for %s: %s\n",
			jt_cmdname(func), strerror(rc));

	} else {
		lustre_cfg_init(lcfg, LCFG_SET_PARAM, &bufs);
		rc = lcfg_mgs_ioctl(func, OBD_DEV_ID, lcfg);
		if (rc != 0)
			fprintf(stderr, "error: executing %s: %s\n",
				jt_cmdname(func), strerror(errno));
		free(lcfg);
	}

	return rc;
}

/*
 * Param set to single log file, used by all clients and servers.
 * This should be loaded after the individual config logs.
 * Called from set param with -P option.
 */
int jt_lcfg_setparam_perm(int argc, char **argv, struct param_opts *popt)
{
	int rc;
	int i;
	char *buf = NULL;

	if (optind < 0 || optind >= argc)
		return CMD_HELP;

	if (popt->po_file)
		return lcfg_apply_param_yaml(argv[0], argv[optind]);

	for (i = optind, rc = 0; i < argc; i++) {
		buf = argv[i];
		if (popt->po_delete) {
			char *end_pos;
			size_t len;

			len = strlen(buf);
			/* make sure it always ends with '=' */
			end_pos = memchr(buf, '=', len - 1);
			if (!end_pos) {
				size_t buflen = len + 2;

				buf = malloc(buflen);
				if (buf == NULL)
					return -ENOMEM;
				snprintf(buf, buflen, "%s=", argv[i]);
			}
		}

		rc = lcfg_setparam_perm(argv[0], buf, popt->po_delete);
		if (buf != argv[i])
			free(buf);
	}

	return rc;
}

static int lcfg_conf_param(const char *func, char *buf, bool del)
{
	int rc;
	struct lustre_cfg_bufs bufs;
	struct lustre_cfg *lcfg;

	lustre_cfg_bufs_reset(&bufs, NULL);
	lustre_cfg_bufs_set_string(&bufs, 1, buf);
	if (del)
		lustre_cfg_bufs_set_string(&bufs, 2, "del");

	/* We could put other opcodes here. */
	lcfg = malloc(lustre_cfg_len(bufs.lcfg_bufcount, bufs.lcfg_buflen));
	if (!lcfg) {
		rc = -ENOMEM;
	} else {
		lustre_cfg_init(lcfg, LCFG_PARAM, &bufs);
		rc = lcfg_mgs_ioctl(func, OBD_DEV_ID, lcfg);
		if (rc < 0)
			rc = -errno;
		free(lcfg);
	}

	return rc;
}

/*
 * Param set in config log on MGS
 * conf_param key=value
 *
 * Note we can actually send mgc conf_params from clients, but currently
 * that's only done for default file striping (see ll_send_mgc_param),
 * and not here.
 *
 * After removal of a parameter (-d) Lustre will use the default
 * AT NEXT REBOOT, not immediately.
 */
int jt_lcfg_confparam(int argc, char **argv)
{
	int rc;
	bool del = false;
	char *buf = NULL;

	/* mgs_setparam processes only lctl buf #1 */
	if ((argc > 3) || (argc <= 1))
		return CMD_HELP;

	while ((rc = getopt(argc, argv, "d")) != -1) {
		switch (rc) {
		case 'd':
			del = true;
			break;
		default:
			return CMD_HELP;
		}
	}

	buf = argv[optind];
	if (del) {
		char *end_pos;
		size_t len;

		len = strlen(buf);
		/* make sure it always ends with '=' */
		end_pos = memchr(buf, '=', len - 1);
		if (!end_pos) {
			size_t buflen = len + 2;

			buf = malloc(buflen);
			if (buf == NULL)
				return -ENOMEM;
			snprintf(buf, buflen, "%s=", argv[optind]);
		}
	}

	rc = lcfg_conf_param(argv[0], buf, del);

	if (buf != argv[optind])
		free(buf);

	if (rc < 0) {
		fprintf(stderr, "error: %s: %s\n", jt_cmdname(argv[0]),
			strerror(-rc));
	}

	return rc;
}

static void print_obd_line(char *s)
{
	const char *param = "osc/%s/ost_conn_uuid";
	char obd_name[MAX_OBD_NAME];
	char buf[MAX_OBD_NAME];
	FILE *fp = NULL;
	glob_t path;
	char *ptr;
retry:
	/* obd device type is the first 3 characters of param name */
	snprintf(buf, sizeof(buf), " %%*d %%*s %.3s %%%zus %%*s %%*d ",
		 param, sizeof(obd_name) - 1);
	if (sscanf(s, buf, obd_name) == 0)
		goto try_mdc;
	if (cfs_get_param_paths(&path, param, obd_name) != 0)
		goto try_mdc;
	fp = fopen(path.gl_pathv[0], "r");
	if (!fp) {
		/* need to free path data before retry */
		cfs_free_param_data(&path);
try_mdc:
		if (param[0] == 'o') { /* failed with osc, try mdc */
			param = "mdc/%s/mds_conn_uuid";
			goto retry;
		}
		buf[0] = '\0';
		goto fail_print;
	}

	/* should not ignore fgets(3)'s return value */
	if (!fgets(buf, sizeof(buf), fp)) {
		fprintf(stderr, "reading from %s: %s", buf, strerror(errno));
		goto fail_close;
	}

fail_close:
	fclose(fp);
	cfs_free_param_data(&path);

	/* trim trailing newlines */
	ptr = strrchr(buf, '\n');
	if (ptr)
		*ptr = '\0';
fail_print:
	ptr = strrchr(s, '\n');
	if (ptr)
		*ptr = '\0';
	printf("%s%s%s\n", s, buf[0] ? " " : "", buf);
}

static int yaml_get_device_index(char *source)
{
	yaml_emitter_t request;
	yaml_parser_t reply;
	yaml_event_t event;
	struct nl_sock *sk;
	bool done = false;
	int rc;

	sk = nl_socket_alloc();
	if (!sk)
		return -EOPNOTSUPP;

	/* Setup parser to recieve Netlink packets */
	rc = yaml_parser_initialize(&reply);
	if (rc == 0)
		return -EOPNOTSUPP;

	rc = yaml_parser_set_input_netlink(&reply, sk, false);
	if (rc == 0)
		return -EOPNOTSUPP;

	/* Create Netlink emitter to send request to kernel */
	yaml_emitter_initialize(&request);
	rc = yaml_emitter_set_output_netlink(&request, sk, "lustre",
					     LUSTRE_GENL_VERSION,
					     LUSTRE_CMD_DEVICES, NLM_F_DUMP);
	if (rc == 0)
		goto error;

	yaml_emitter_open(&request);

	yaml_document_start_event_initialize(&event, NULL, NULL, NULL, 0);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_mapping_start_event_initialize(&event, NULL,
					    (yaml_char_t *)YAML_MAP_TAG,
					    1, YAML_ANY_MAPPING_STYLE);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_scalar_event_initialize(&event, NULL,
				     (yaml_char_t *)YAML_STR_TAG,
				     (yaml_char_t *)"devices",
				     strlen("devices"), 1, 0,
				     YAML_PLAIN_SCALAR_STYLE);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_sequence_start_event_initialize(&event, NULL,
					     (yaml_char_t *)YAML_SEQ_TAG,
					     1, YAML_ANY_SEQUENCE_STYLE);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_mapping_start_event_initialize(&event, NULL,
					    (yaml_char_t *)YAML_MAP_TAG,
					    1, YAML_ANY_MAPPING_STYLE);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_scalar_event_initialize(&event, NULL,
				     (yaml_char_t *)YAML_STR_TAG,
				     (yaml_char_t *)"name",
				     strlen("name"),
				     1, 0, YAML_PLAIN_SCALAR_STYLE);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	rc = yaml_scalar_event_initialize(&event, NULL,
				     (yaml_char_t *)YAML_STR_TAG,
				     (yaml_char_t *)source,
				     strlen(source), 1, 0,
				     YAML_PLAIN_SCALAR_STYLE);
	if (rc == 0)
		goto error;

	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_mapping_end_event_initialize(&event);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_sequence_end_event_initialize(&event);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_mapping_end_event_initialize(&event);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_document_end_event_initialize(&event, 0);
	rc = yaml_emitter_emit(&request, &event);
	if (rc == 0)
		goto error;

	yaml_emitter_close(&request);
error:
	if (rc == 0) {
		yaml_emitter_log_error(&request, stderr);
		yaml_emitter_cleanup(&request);
		rc = -EINVAL;
		goto free_reply;
	}
	yaml_emitter_cleanup(&request);

	while (!done) {
		rc = yaml_parser_parse(&reply, &event);
		if (rc == 0) {
			yaml_parser_log_error(&reply, stdout, "lctl: ");
			rc = -EINVAL;
			break;
		}

		if (event.type == YAML_SCALAR_EVENT) {
			char *value = (char *)event.data.scalar.value;

			if (strcmp(value, "index") == 0) {
				yaml_event_delete(&event);
				rc = yaml_parser_parse(&reply, &event);
				if (rc == 1) {
					value = (char *)event.data.scalar.value;
					errno = 0;
					rc = strtoul(value, NULL, 10);
					if (errno) {
						yaml_event_delete(&event);
						rc = -errno;
					}
					goto free_reply;
				}
			}
		}
		done = (event.type == YAML_STREAM_END_EVENT);
		yaml_event_delete(&event);
	}
free_reply:
	yaml_parser_cleanup(&reply);
	nl_socket_free(sk);

	return rc;
}

int yaml_get_limit_uid(const char *config)
{
	yaml_parser_t parser;
	yaml_event_t event;
	bool done = false;
	int rc;

	/* Initialize parser */
	if (!yaml_parser_initialize(&parser))
		return -EOPNOTSUPP;

	/* Set input string */
	yaml_parser_set_input_string(&parser, (const unsigned char *)config,
				     strlen(config));

	while (!done) {
		if (!yaml_parser_parse(&parser, &event)) {
			yaml_parser_log_error(&parser, stderr, "lctl: ");
			rc = -EINVAL;
			goto error;
		}

		if (event.type == YAML_SCALAR_EVENT) {
			char *value = (char *)event.data.scalar.value;

			if (strcmp(value, "limit_uid") == 0) {
				yaml_event_delete(&event);
				if (!yaml_parser_parse(&parser, &event)) {
					rc = -EINVAL;
					goto error;
				}
				value = (char *)event.data.scalar.value;
				errno = 0;
				rc = strtoul(value, NULL, 10);
				if (errno)
					rc = -errno;
				yaml_event_delete(&event);
				goto out;
			}
		}

		done = (event.type == YAML_STREAM_END_EVENT);
		yaml_event_delete(&event);
	}

	rc = -ENOENT; /* Key not found */

out:
	yaml_parser_delete(&parser);
	return rc;

error:
	yaml_parser_delete(&parser);
	return rc;
}

/* get device list by netlink or debugfs */
int jt_device_list(int argc, char **argv)
{
	static const struct option long_opts[] = {
		{ .name = "target",	.has_arg = no_argument,	.val = 't' },
		{ .name = "yaml",	.has_arg = no_argument,	.val = 'y' },
		{ .name = NULL }
	};
	int flags = PARAM_FLAGS_EXTRA_IGNORE_ERROR;
	struct param_opts opts;
	char buf[MAX_OBD_NAME];
	glob_t path;
	int rc, c;
	FILE *fp;

	if (optind + 1 < argc)
		return CMD_HELP;

	memset(&opts, 0, sizeof(opts));

	while ((c = getopt_long(argc, argv, "ty", long_opts, NULL)) != -1) {
		switch (c) {
		case 't':
			flags |= PARAM_FLAGS_EXTRA_DETAILS;
			opts.po_detail = true;
			break;
		case 'y':
			flags |= PARAM_FLAGS_YAML_FORMAT;
			opts.po_yaml = true;
			break;
		default:
			return CMD_HELP;
		}
	}

	if (optind < argc) {
		optind = 1;
		return CMD_HELP;
	}
	optind = 1;

	/* Use YAML to list all devices */
	rc = llapi_param_display_value("devices", LUSTRE_GENL_VERSION, flags,
				       stdout);
	if (rc == 0)
		return 0;

	rc = llapi_param_get_paths("devices", &path, 0);
	if (rc < 0)
		return rc;

	fp = fopen(path.gl_pathv[0], "r");
	if (!fp) {
		cfs_free_param_data(&path);
		return errno;
	}

	while (fgets(buf, sizeof(buf), fp) != NULL)
		if (opts.po_detail)
			print_obd_line(buf);
		else
			printf("%s", buf);

	cfs_free_param_data(&path);
	fclose(fp);
	return 0;
}

static int do_name2dev(char *func, char *name, int dev_id)
{
	struct obd_ioctl_data data;
	char rawbuf[MAX_IOC_BUFLEN], *buf = rawbuf;
	int rc;

	/* Use YAML to find device index */
	rc = yaml_get_device_index(name);
	if (rc >= 0 || rc != -EOPNOTSUPP)
		return rc;

	memset(&data, 0, sizeof(data));
	data.ioc_dev = dev_id;
	data.ioc_inllen1 = strlen(name) + 1;
	data.ioc_inlbuf1 = name;

	memset(buf, 0, sizeof(rawbuf));
	rc = llapi_ioctl_pack(&data, &buf, sizeof(rawbuf));
	if (rc < 0) {
		fprintf(stderr, "error: %s: invalid ioctl\n", jt_cmdname(func));
		return rc;
	}
	rc = l_ioctl(OBD_DEV_ID, OBD_IOC_NAME2DEV, buf);
	if (rc < 0)
		return -errno;
	rc = llapi_ioctl_unpack(&data, buf, sizeof(rawbuf));
	if (rc < 0) {
		fprintf(stderr, "error: %s: invalid reply\n", jt_cmdname(func));
		return rc;
	}

	return data.ioc_dev;
}

/*
 * resolve a device name to a device number.
 * supports a number, $name or %uuid.
 */
int parse_devname(char *func, char *name, int dev_id)
{
	int rc = 0;

	if (!name)
		return -EINVAL;

	/* Test if its a pure number string */
	if (strspn(name, "0123456789") != strlen(name)) {
		if (name[0] == '$' || name[0] == '%')
			name++;

		rc = do_name2dev(func, name, dev_id);
	} else {
		errno = 0;
		rc = strtoul(name, NULL, 10);
		if (errno)
			rc = -errno;
	}

	if (rc < 0)
		fprintf(stderr, "No device found for name %s: %s\n",
			name, strerror(-rc));
	return rc;
}

enum paramtype {
	PT_NONE = 0,
	PT_SETPARAM,
	PT_CONFPARAM
};

#define PS_NONE 0
#define PS_PARAM_FOUND 1
#define PS_PARAM_SET 2
#define PS_VAL_FOUND 4
#define PS_VAL_SET 8
#define PS_DEVICE_FOUND 16
#define PS_DEVICE_SET 32

#define PARAM_SZ 256

static struct cfg_type_data {
	enum paramtype ptype;
	char *type_name;
} cfg_type_table[] = {
	{ PT_SETPARAM, "set_param" },
	{ PT_CONFPARAM, "conf_param" },
	{ PT_NONE, "none" }
};

static struct cfg_stage_data {
	int pstage;
	char *stage_name;
} cfg_stage_table[] = {
	{ PS_PARAM_FOUND, "parameter" },
	{ PS_VAL_FOUND, "value" },
	{ PS_DEVICE_FOUND, "device" },
	{ PS_NONE, "none" }
};

static enum paramtype construct_param(enum paramtype confset, const char *param,
				      const char *device, char *buf,
				      int bufsize, bool convert)
{
	char *tmp;

	if (confset == PT_SETPARAM) {
		strncpy(buf, param, bufsize);
		return confset;
	}
	/*
	 * sys.* params are top level, we just need to trim the sys.
	 */
	tmp = strstr(param, "sys.");
	if (tmp) {
		tmp += 4;
		strncpy(buf, tmp, bufsize);
		return PT_SETPARAM;
	}

	if (convert) {
		/*
		 * parameters look like type.parameter, we need to stick the
		 * device in the middle. Example combine mdt.identity_upcall
		 * with device lustre-MDT0000 for
		 * mdt.lustre-MDT0000.identity_upcall
		 */

		tmp = strchrnul(param, '.');
		snprintf(buf, tmp - param + 1, "%s", param);
		buf += tmp - param;
		bufsize -= tmp - param;
		snprintf(buf, bufsize, ".%s%s", device, tmp);
		return PT_SETPARAM;
	}
	/* create for conf_param */
	if (strlen(device)) {
		int rc;

		rc = snprintf(buf, bufsize, "%s.%s", device, param);
		if (rc < 0)
			return PT_NONE;
		return confset;
	}
	return PT_NONE;
}

static int lcfg_apply_param_yaml(const char *func, const char *filename)
{
	FILE *file;
	yaml_parser_t parser;
	yaml_token_t token;
	int rc = 0, rc1 = 0;

	enum paramtype confset = PT_NONE;
	int param = PS_NONE;
	char *tmp;
	char parameter[PARAM_SZ + 1];
	char value[PARAM_SZ + 1];
	char device[PARAM_SZ + 1];
	bool convert;

	convert = strncmp(func, "set_param", 9) == 0;
	file = fopen(filename, "rb");
	if (!file) {
		rc1 = -errno;
		goto out_open;
	}

	rc = yaml_parser_initialize(&parser);
	if (rc == 0) {
		rc1 = -EOPNOTSUPP;
		goto out_init;
	}
	yaml_parser_set_input_file(&parser, file);

	/*
	 * Search tokens for conf_param or set_param
	 * The token after "parameter" goes into parameter
	 * The token after "value" goes into value
	 * when we have all 3, create param=val and call the
	 * appropriate function for set/conf param
	 */
	do {
		int i;

		if (!yaml_parser_scan(&parser, &token)) {
			rc = 1;
			break;
		}

		if (token.type != YAML_SCALAR_TOKEN)
			continue;

		for (i = 0; cfg_type_table[i].ptype != PT_NONE; i++) {
			if (!strncmp((char *)token.data.alias.value,
				     cfg_type_table[i].type_name,
				     strlen(cfg_type_table[i].type_name))) {
				confset = cfg_type_table[i].ptype;
				break;
			}
		}

		if (confset == PT_NONE)
			continue;

		for (i = 0; cfg_stage_table[i].pstage != PS_NONE; i++) {
			if (!strncmp((char *)token.data.alias.value,
				     cfg_stage_table[i].stage_name,
				     strlen(cfg_stage_table[i].stage_name))) {
				param |= cfg_stage_table[i].pstage;
				break;
			}
		}

		if (cfg_stage_table[i].pstage != PS_NONE)
			continue;

		if (param & PS_PARAM_FOUND) {
			enum paramtype rc;

			rc = construct_param(confset,
					(char *)token.data.alias.value,
					device, parameter, PARAM_SZ, convert);

			if (rc == PT_NONE)
				printf("error: conf_param without device\n");
			confset = rc;
			param |= PS_PARAM_SET;
			param &= ~PS_PARAM_FOUND;

			/*
			 * we're getting parameter: param=val
			 * copy val and mark that we've got it in case
			 * there is no value: tag
			 */
			tmp = strchrnul(parameter, '=');
			if (*tmp == '=') {
				strncpy(value, tmp + 1, sizeof(value) - 1);
				*tmp = '\0';
				param |= PS_VAL_SET;
			} else {
				continue;
			}
		} else if (param & PS_VAL_FOUND) {
			strncpy(value, (char *)token.data.alias.value,
				PARAM_SZ);
			param |= PS_VAL_SET;
			param &= ~PS_VAL_FOUND;
		} else if (param & PS_DEVICE_FOUND) {
			strncpy(device, (char *)token.data.alias.value,
				PARAM_SZ);
			param |= PS_DEVICE_SET;
			param &= ~PS_DEVICE_FOUND;
		}

		if (confset && param & PS_VAL_SET && param & PS_PARAM_SET) {
			int size = strlen(parameter) + strlen(value) + 2;
			char *buf = malloc(size);

			if (!buf) {
				rc = 2;
				break;
			}
			snprintf(buf, size, "%s=%s", parameter, value);

			printf("%s: %s\n", confset == PT_SETPARAM ?
			       "set_param" : "conf_param", buf);

			if (confset == PT_SETPARAM)
				rc = lcfg_setparam_perm(func, buf, 0);
			else
				rc = lcfg_conf_param(func, buf, 0);
			if (rc) {
				printf("error: failed to apply parameter rc = %d, tyring next one\n",
				       rc);
				rc1 = rc;
				rc = 0;
			}
			confset = PT_NONE;
			param = PS_NONE;
			parameter[0] = '\0';
			value[0] = '\0';
			device[0] = '\0';
			free(buf);
		}
	} while (token.type != YAML_STREAM_END_TOKEN);

	yaml_parser_delete(&parser);
out_init:
	fclose(file);
out_open:
	return rc1;
}

int jt_lcfg_applyyaml(int argc, char **argv)
{
	/* the file should be the last argument */
	return lcfg_apply_param_yaml(argv[0], argv[argc - 1]);
}
