/* SPDX-License-Identifier: GPL-2.0-only */

#include "common.h"
#include "opts.h"

nvramtool_op_info_t nvramtool_op;

nvramtool_op_modifier_info_t nvramtool_op_modifiers[NVRAMTOOL_NUM_OP_MODIFIERS];

static char *handle_optional_arg(int argc, char *argv[]);
static void register_op(int *op_found, nvramtool_op_t op, char op_param[]);
static void register_op_modifier(nvramtool_op_modifier_t mod, char mod_param[]);
static void resolve_op_modifiers(void);
static void sanity_check_args(void);

static const char getopt_string[] = "-ab:B:c::C:dD:e:hH:iL:l::np:r:tvw:xX:y:Y";

/****************************************************************************
 * parse_nvramtool_args
 *
 * Parse command line arguments.
 ****************************************************************************/
void parse_nvramtool_args(int argc, char *argv[])
{
	nvramtool_op_modifier_info_t *mod_info;
	int i, op_found, c;

	for (i = 0, mod_info = nvramtool_op_modifiers;
	     i < NVRAMTOOL_NUM_OP_MODIFIERS; i++, mod_info++) {
		mod_info->found = FALSE;
		mod_info->found_seq = 0;
		mod_info->param = NULL;
	}

	op_found = FALSE;
	opterr = 0;

	do {
		switch (c = getopt(argc, argv, getopt_string)) {
		case 'a':
			register_op(&op_found,
				    NVRAMTOOL_OP_CMOS_SHOW_ALL_PARAMS, NULL);
			break;
		case 'b':
			register_op(&op_found, NVRAMTOOL_OP_WRITE_CMOS_DUMP,
				    optarg);
			break;
		case 'B':
			register_op(&op_found, NVRAMTOOL_OP_READ_CMOS_DUMP,
				    optarg);
			break;
		case 'c':
			register_op(&op_found, NVRAMTOOL_OP_CMOS_CHECKSUM,
				    handle_optional_arg(argc, argv));
			break;
		case 'C':
			register_op_modifier(NVRAMTOOL_MOD_USE_CBFS_FILE,
					     optarg);
			break;
		case 'd':
			register_op(&op_found, NVRAMTOOL_OP_LBTABLE_DUMP, NULL);
			break;
		case 'D':
			register_op_modifier(NVRAMTOOL_MOD_USE_CMOS_FILE,
					     optarg);
			break;
		case 'e':
			register_op(&op_found, NVRAMTOOL_OP_SHOW_PARAM_VALUES,
				    optarg);
			break;
		case 'h':
			register_op(&op_found, NVRAMTOOL_OP_SHOW_USAGE, NULL);
			break;
		case 'H':
			register_op(&op_found, NVRAMTOOL_OP_WRITE_HEADER_FILE, optarg);
			break;
		case 'i':
			register_op(&op_found,
				    NVRAMTOOL_OP_CMOS_SET_PARAMS_STDIN, NULL);
			break;
		case 'l':
			register_op(&op_found, NVRAMTOOL_OP_LBTABLE_SHOW_INFO,
				    handle_optional_arg(argc, argv));
			break;
		case 'L':
			register_op(&op_found, NVRAMTOOL_OP_WRITE_BINARY_FILE,
					     optarg);
			break;
		case 'n':
			register_op_modifier(NVRAMTOOL_MOD_SHOW_VALUE_ONLY,
					     NULL);
			break;
		case 'p':
			register_op(&op_found,
				    NVRAMTOOL_OP_CMOS_SET_PARAMS_FILE, optarg);
			break;
		case 'r':
			register_op(&op_found, NVRAMTOOL_OP_CMOS_SHOW_ONE_PARAM,
				    optarg);
			break;
		case 't':
			register_op_modifier(NVRAMTOOL_MOD_USE_CMOS_OPT_TABLE,
					     NULL);
			break;
		case 'v':
			register_op(&op_found, NVRAMTOOL_OP_SHOW_VERSION, NULL);
			break;
		case 'w':
			register_op(&op_found, NVRAMTOOL_OP_CMOS_SET_ONE_PARAM,
				    optarg);
			break;
		case 'x':
			register_op(&op_found, NVRAMTOOL_OP_SHOW_CMOS_HEX_DUMP,
				    NULL);
			break;
		case 'X':
			register_op(&op_found, NVRAMTOOL_OP_SHOW_CMOS_DUMPFILE,
				    optarg);
			break;
		case 'y':
			register_op_modifier(NVRAMTOOL_MOD_USE_CMOS_LAYOUT_FILE,
					     optarg);
			break;
		case 'Y':
			register_op(&op_found, NVRAMTOOL_OP_SHOW_LAYOUT, NULL);
			break;
		case -1:	/* no more command line args */
			break;
		case '?':	/* unknown option found */
		case 1:	/* nonoption command line arg found */
		default:
			usage(stderr);
			break;
		}
	} while (c != -1);

	if (!op_found)
		usage(stderr);

	resolve_op_modifiers();
	sanity_check_args();
}

/****************************************************************************
 * handle_optional_arg
 *
 * Handle a command line option with an optional argument.
 ****************************************************************************/
static char *handle_optional_arg(int argc, char *argv[])
{
	char *arg;

	if (optarg != NULL) {
		/* optional arg is present and arg was specified as
		 * "-zarg" (with no whitespace between "z" and "arg"),
		 * where -z is the option and "arg" is the value of the
		 * optional arg
		 */
		return optarg;
	}

	if ((argv[optind] == NULL) || (argv[optind][0] == '-'))
		return NULL;

	arg = argv[optind];	/* optional arg is present */

	/* This call to getopt yields the optional arg we just found,
	 * which we want to skip.
	 */
	getopt(argc, argv, getopt_string);

	return arg;
}

/****************************************************************************
 * register_op
 *
 * Store the user's selection of which operation this program should perform.
 ****************************************************************************/
static void register_op(int *op_found, nvramtool_op_t op, char op_param[])
{
	if (*op_found && (op != nvramtool_op.op))
		usage(stderr);

	*op_found = TRUE;
	nvramtool_op.op = op;
	nvramtool_op.param = op_param;
}

/****************************************************************************
 * register_op_modifier
 *
 * Store information regarding an optional argument specified in addition to
 * the user's selection of which operation this program should perform.
 ****************************************************************************/
static void register_op_modifier(nvramtool_op_modifier_t mod, char mod_param[])
{
	static int found_seq = 0;
	nvramtool_op_modifier_info_t *mod_info;

	mod_info = &nvramtool_op_modifiers[mod];
	mod_info->found = TRUE;
	mod_info->found_seq = ++found_seq;
	mod_info->param = mod_param;
}

/****************************************************************************
 * resolve_op_modifiers
 *
 * If the user specifies multiple arguments that conflict with each other,
 * the last specified argument overrides previous conflicting arguments.
 ****************************************************************************/
static void resolve_op_modifiers(void)
{
	if (nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_LAYOUT_FILE].found &&
	    nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_OPT_TABLE].found) {
		if (nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_LAYOUT_FILE].found_seq >
		    nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_OPT_TABLE].found_seq)
			nvramtool_op_modifiers
			    [NVRAMTOOL_MOD_USE_CMOS_OPT_TABLE].found = FALSE;
		else
			nvramtool_op_modifiers
			    [NVRAMTOOL_MOD_USE_CMOS_LAYOUT_FILE].found = FALSE;
	}
}

/****************************************************************************
 * sanity_check_args
 *
 * Perform sanity checking on command line arguments.
 ****************************************************************************/
static void sanity_check_args(void)
{
	if ((nvramtool_op_modifiers[NVRAMTOOL_MOD_SHOW_VALUE_ONLY].found) &&
	    (nvramtool_op.op != NVRAMTOOL_OP_CMOS_SHOW_ONE_PARAM))
		usage(stderr);
}