/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <strings.h>
#include <elfedit.h>
#include "_elfedit.h"
#include "msg.h"




/*
 * This file provides the builtin sys module. It is similar to the
 * other modules, but differs in several important ways:
 *
 *	- It is built as a static part of elfedit, and not
 *		as a sharable object.
 *	- It must be avaialble before the ELFCLASS of the object
 *		is known, so it is not ELFCLASS specific. We don't build
 *		it twice with machdep.h, as we do for the loadable modules.
 *		This means that commands need to test for the type
 *		of their obj_state argument at runtime.
 *	- The init function signature is different. We build an entire
 *		module definition statically.
 */



/*
 * This function is supplied to elfedit through our elfedit_module_t
 * definition. It translates the opaque elfedit_i18nhdl_t handles
 * in our module interface into the actual strings for elfedit to
 * use.
 *
 * note:
 *	This module uses Msg codes for its i18n handle type.
 *	So the translation is simply to use MSG_INTL() to turn
 *	it into a string and return it.
 */
static const char *
mod_i18nhdl_to_str(elfedit_i18nhdl_t hdl)
{
	Msg msg = (Msg)hdl;

	return (MSG_INTL(msg));
}



/*
 * The sys_opt_t enum specifies a bit value for every optional argument
 * allowed by a command in this module.
 */
typedef enum {
	SYS_OPT_F_ALL =		1,	/* -a */
	SYS_OPT_F_FORCE =	2,	/* -f */
	SYS_OPT_F_SYNOPSIS =	4,	/* -s */
} dyn_opt_t;


/*
 * Given a generic (void *) pointer to an obj_state argument, determine
 * which type it is, and return the st_file, st_fd and st_elf fields.
 */
static void
get_obj_state_info(void *obj_state, const char **file, int *fd, Elf **elf)
{
	if (state.elf.elfclass == ELFCLASS32) {
		elfedit32_obj_state_t *s = (elfedit32_obj_state_t *)obj_state;

		*file = s->os_file;
		*fd = s->os_fd;
		*elf = s->os_elf;
	} else {
		elfedit64_obj_state_t *s = (elfedit64_obj_state_t *)obj_state;

		*file = s->os_file;
		*fd = s->os_fd;
		*elf = s->os_elf;
	}
}



/*
 * Helper for cmd_help(). Displays synopsis information for one command.
 */
static void
cmd_help_synopsis(elfeditGC_module_t *mod, elfeditGC_cmd_t *cmd)
{
	char		name_buf[128];
	const char	*name;
	const char	**cmd_name;

	if (cmd->cmd_name[1] == NULL) {   /* One name */
		name = *cmd->cmd_name;
	} else {
		const char *cname;
		int need_comma = 0;

		name = name_buf;
		(void) snprintf(name_buf, sizeof (name_buf),
		    MSG_ORIG(MSG_HLPFMT_MULTNAM), cmd->cmd_name[0]);
		for (cmd_name = cmd->cmd_name + 1;
		    *cmd_name; cmd_name++) {
			if (need_comma)
				(void) strlcat(name_buf,
				    MSG_ORIG(MSG_STR_COMMA_SP),
				    sizeof (name_buf));
			need_comma = 1;
			cname = (cmd_name[0][0] == '\0') ?
			    MSG_INTL(MSG_HLPFMT_MODDEFCMD) : *cmd_name;
			(void) strlcat(name_buf, cname,
			    sizeof (name_buf));
		}
		(void) strlcat(name_buf, MSG_ORIG(MSG_STR_CPAREN),
		    sizeof (name_buf));
	}
	elfedit_printf(MSG_ORIG(MSG_HLPFMT_NAMSUMHDR), name,
	    (* mod->mod_i18nhdl_to_str)(cmd->cmd_desc));
	elfedit_printf(MSG_INTL(MSG_HLPFMT_SUMSYNOPSIS),
	    elfedit_format_command_usage(mod, cmd,
	    MSG_ORIG(MSG_STR_HLPSUMINDENT),
	    strlen(MSG_ORIG(MSG_STR_HLPSUMINDENT))));
}


/*
 * Helper for cmd_help(). Displays synopsis information for one module.
 */
static void
cmd_help_showmod(elfeditGC_module_t *mod)
{
	elfeditGC_cmd_t	*cmd;

	elfedit_printf(MSG_ORIG(MSG_HLPFMT_NAMDSCHDR),
	    mod->mod_name, (* mod->mod_i18nhdl_to_str)(mod->mod_desc));
	for (cmd = mod->mod_cmds; cmd->cmd_func != NULL; cmd++) {
		if (cmd != mod->mod_cmds)
			elfedit_printf(MSG_ORIG(MSG_STR_NL));
		elfedit_printf(MSG_ORIG(MSG_STR_NL));
		cmd_help_synopsis(mod, cmd);
	}
}


/*
 * Given a string containing newline characters, break it into
 * individual lines, and output each line with the given
 * prefix string in front.
 */
static void
write_help_str(const char *str, const char *prefix)
{
	size_t i;

	if (str == NULL)
		return;
	while (*str) {
		i = strcspn(str, MSG_ORIG(MSG_STR_NL));
		if (*(str + i) != '\0')
			i++;
		elfedit_printf(prefix);
		elfedit_write(str, i);
		str += i;
	}
}


/*
 * Given a title, and a NULL terminated list of option/argument
 * descriptors, output the list contents.
 */
static void
write_optarg(elfeditGC_module_t *mod, const char *title,
    elfedit_cmd_optarg_t *optarg)
{
	int			cnt;
	int			len;
	const char		*help;
	elfedit_optarg_item_t	item;

	elfedit_printf(title);
	for (cnt = 0; optarg->oa_name != NULL; cnt++) {
		elfedit_next_optarg(&optarg, &item);

		/* Insert a blank line between items */
		if (cnt > 0)
			elfedit_printf(MSG_ORIG(MSG_STR_NL));

		/* Indentation */
		elfedit_printf(MSG_ORIG(MSG_STR_HLPINDENT));
		len = strlen(item.oai_name);
		help = elfedit_optarg_helpstr(mod, &item);
		if (item.oai_flags & ELFEDIT_CMDOA_F_VALUE) {
			len += 1 + strlen(item.oai_vname);
			elfedit_printf(MSG_ORIG(MSG_STR_HLPOPTARG2),
			    item.oai_name, item.oai_vname);
		} else {
			elfedit_printf(MSG_ORIG(MSG_STR_HLPOPTARG),
			    item.oai_name);
		}

		/*
		 * If name is too long, inject a newline to avoid
		 * crowding the help text.
		 */
		if (len > 3)
			elfedit_printf(MSG_ORIG(MSG_STR_NL));

		/* Output the help text with a tab prefix */
		write_help_str(help, MSG_ORIG(MSG_STR_TAB));
	}
}


/*
 * Implementation of sys:help
 */
/*ARGSUSED*/
static elfedit_cmdret_t
cmd_help(void *obj_state, int argc, const char *argv[])
{
#define	INITIAL_ITEM_ALLOC 4


	/*
	 * An array of this type is used to collect the data needed to
	 * generate help output.
	 */
	typedef struct {
		elfeditGC_cmd_t		*cmd;
		elfeditGC_module_t	*cmd_mod;	/* Used with cmd */
		elfeditGC_module_t	*mod;
	} ITEM;

	static ITEM	*item;
	static int	item_cnt;

	MODLIST_T		*modlist;
	int			dispcnt;
	size_t			i;
	elfeditGC_module_t	*mod;
	elfeditGC_cmd_t		*cmd;
	int			minus_s = 0;
	elfedit_getopt_state_t	getopt_state;
	ITEM			*cur_item;

	/*
	 * Process options. The only option accepted is -s, so we
	 * don't even have to check the idmask to know.
	 */
	elfedit_getopt_init(&getopt_state, &argc, &argv);
	while (elfedit_getopt(&getopt_state) != NULL)
		minus_s = 1;

	/*
	 * This command can produce an arbitrary amount of output, so
	 * run a pager.
	 */
	elfedit_pager_init();

	if (argc == 0) {
		if (minus_s) {
			/* Force all modules to load so we have data */
			elfedit_load_modpath();
			for (modlist = state.modlist; modlist;
			    modlist = modlist->ml_next) {
				cmd_help_showmod(modlist->ml_mod);
				if (modlist->ml_next != NULL) {
					elfedit_printf(MSG_ORIG(MSG_STR_NL));
					elfedit_printf(MSG_ORIG(MSG_STR_NL));
				}
			}
			return (ELFEDIT_CMDRET_NONE);
		}

		/*
		 * If no arguments are present, we display a simple
		 * "how to use help" tutorial, which will hopefully
		 * bootstrap the user into a position where they
		 * know how to run the help command, and then find
		 * what they're really after.
		 */
		elfedit_printf(MSG_INTL(MSG_SYS_HELP_HELP_NOARG));
		return (ELFEDIT_CMDRET_NONE);
	}


	/*
	 * As we process the arguments, we are willing to treat each
	 * one as either a module or a command:
	 *	1) An item without a colon can be a module,
	 *		or a command from the sys: module.
	 *	2) An item with a colon, and no command part is
	 *		a module, and it can also be the default
	 *		command for the module, if it has one. We choose
	 *		to only display the module info in this case, since
	 *		the use of "" to represent the default command is
	 *		an implementation detail, not a user-facing concept.
	 *	3) An item with a colon and a command part can only be
	 *		a command.
	 *
	 * Note that there are cases where one argument can have two
	 * valid interpretations. In this case, we display them both.
	 *
	 * Pass over the arguments and determine how many distinct
	 * "things" we need to display. At the same time, force any
	 * needed modules to load so that the debug load messages won't
	 * show up in between the displayed items, and save the command
	 * and module definitions we will need to generate the output.
	 */
	if (argc > item_cnt) {
		int n = (item_cnt == 0) ? INITIAL_ITEM_ALLOC : item_cnt;

		while (n < argc)
			n *= 2;

		item = elfedit_realloc(MSG_INTL(MSG_ALLOC_HELPITEM), item,
		    n * sizeof (*item));
		item_cnt = n;
	}

	dispcnt = 0;
	for (i = 0; i < argc; i++) {
		const char *colon = strchr(argv[i], ':');

		if (colon == NULL) {	/* No colon: sys: cmd or module */
			item[i].cmd =
			    elfedit_find_command(argv[i], 0, &item[i].cmd_mod);
			if (item[i].cmd != NULL)
				dispcnt++;

			/*
			 * Also try to load it as a module. If a command
			 * was found, then this need not succeed. Otherwise,
			 * it has to be a module, and we cause an error
			 * to be issued if not.
			 */
			item[i].mod = elfedit_load_module(argv[i],
			    item[i].cmd == NULL, 0);
			if (item[i].mod != NULL)
				dispcnt++;
		} else if (*(colon + 1) == '\0') {
			/* Just colon: Module (and maybe default command) */
			char buf[ELFEDIT_MAXMODNAM + 1];
			const char *str = argv[i];
			int len = colon - str;

			item[i].cmd = NULL;
			/* Strip off the colon */
			if (len < sizeof (buf)) {
				(void) strncpy(buf, str, len);
				buf[len] = '\0';
				str = buf;
			}
			item[i].mod = elfedit_load_module(str, 1, 0);
			dispcnt++;
		} else {	/* A command */
			item[i].cmd =
			    elfedit_find_command(argv[i], 1, &item[i].cmd_mod);
			dispcnt++;
			item[i].mod = NULL;
		}
	}

	/*
	 * Having validated the items, loop over them again and produce
	 * the required help output.
	 */
	for (cur_item = item; argc--; argv++, cur_item++) {


		/* Help for a module? */
		if (cur_item->mod != NULL) {
			if (dispcnt > 1)
				elfedit_printf(MSG_ORIG(MSG_HLPFMT_MULTIHDR),
				    *argv);
			cmd_help_showmod(cur_item->mod);
			if ((dispcnt > 1) && (argc > 0))
				elfedit_printf(MSG_INTL(MSG_HLPFMT_MULTIEND),
				    argv[0], argv[1]);
			/* An empty line after the last line of output */
			elfedit_printf(MSG_ORIG(MSG_STR_NL));
		}

		/* Help for a command? */
		if (cur_item->cmd == NULL)
			continue;
		cmd = cur_item->cmd;
		mod = cur_item->cmd_mod;
		if (dispcnt > 1)
			elfedit_printf(MSG_ORIG(MSG_HLPFMT_MULTIHDR), *argv);

		/* If -s, display quick synopsis rather than the whole thing */
		if (minus_s) {
			cmd_help_synopsis(mod, cmd);
			continue;
		}

		elfedit_printf(MSG_INTL(MSG_HLPFMT_MOD), mod->mod_name,
		    (* mod->mod_i18nhdl_to_str)(mod->mod_desc));
		elfedit_printf(MSG_INTL(MSG_HLPFMT_NAME),
		    *cmd->cmd_name,
		    (* mod->mod_i18nhdl_to_str)(cmd->cmd_desc));
		elfedit_printf(MSG_INTL(MSG_HLPFMT_SYNOPSIS),
		    elfedit_format_command_usage(mod, cmd,
		    MSG_ORIG(MSG_STR_HLPUSEINDENT),
		    strlen(MSG_ORIG(MSG_STR_HLPINDENT))));
		/* If there are alias names, show them */
		if (cmd->cmd_name[1] != NULL) {
			const char **alias = cmd->cmd_name + 1;

			elfedit_printf(MSG_INTL(MSG_HLPFMT_ALIASES));
			do {
				elfedit_printf(
				    MSG_ORIG(MSG_STR_HLPINDENT));
				elfedit_printf(
				    MSG_ORIG(MSG_FMT_MODCMD),
				    mod->mod_name, *alias);
				if (**alias == '\0')
					elfedit_printf(
					    MSG_INTL(MSG_HLPFMT_DEFCMD));
				elfedit_printf(MSG_ORIG(MSG_STR_NL));
				alias++;
			} while (*alias);
		}
		elfedit_printf(MSG_INTL(MSG_HLPFMT_DESC));
		write_help_str(
		    (* mod->mod_i18nhdl_to_str)(cmd->cmd_help),
		    MSG_ORIG(MSG_STR_HLPINDENT));
		if (cmd->cmd_args != NULL)
			write_optarg(mod, MSG_INTL(MSG_HLPFMT_ARGS),
			    cmd->cmd_args);
		if (cmd->cmd_opt != NULL)
			write_optarg(mod, MSG_INTL(MSG_HLPFMT_OPT),
			    cmd->cmd_opt);
		if ((dispcnt > 1) && (argc > 0))
			elfedit_printf(MSG_INTL(MSG_HLPFMT_MULTIEND),
			    argv[0], argv[1]);
		/* An empty line after the last line of output */
		elfedit_printf(MSG_ORIG(MSG_STR_NL));
	}

	return (ELFEDIT_CMDRET_NONE);

#undef	INITIAL_ITEM_ALLOC
}


/*
 * Command completion function for sys:help
 */
/*ARGSUSED*/
static void
cpl_help(void *obj_state, void *cpldata, int argc, const char *argv[],
    int num_opt)
{
	/*
	 * The arguments can be any module or command. Supplying the
	 * commands implicitly supplies the modules too.
	 */
	elfedit_cpl_command(cpldata);
}


/*
 * Implementation of sys:load
 */
/*ARGSUSED*/
static elfedit_cmdret_t
cmd_load(void *obj_state, int argc, const char *argv[])
{
	elfedit_getopt_state_t	getopt_state;
	elfedit_getopt_ret_t	*getopt_ret;
	struct stat		statbuf;

	elfedit_getopt_init(&getopt_state, &argc, &argv);
	while ((getopt_ret = elfedit_getopt(&getopt_state)) != NULL) {
		switch (getopt_ret->gor_idmask) {
		case SYS_OPT_F_ALL:
			elfedit_load_modpath();
			break;
		}
	}

	/* For each remaining argument, load them individually */
	for (; argc-- > 0; argv++) {
		/* Is it a directory? Load everything in it */
		if ((stat(*argv, &statbuf) == 0) &&
		    (statbuf.st_mode & S_IFDIR)) {
			elfedit_load_moddir(*argv, 1, 1);
		} else {	/* Not a directory. Normal load */
			(void) elfedit_load_module(*argv, 1, 1);
		}
	}

	return (0);
}


/*
 * Command completion function for sys:load
 */
/*ARGSUSED*/
static void
cpl_load(void *obj_state, void *cpldata, int argc, const char *argv[],
    int num_opt)
{
	/*
	 * Module names. Note that this causes elfedit to load all
	 * of the modules, which probably makes the current load
	 * operation unnecessary. This could be improved, but I don't
	 * see it as worth the complexity. Explicit load calls are
	 * rare, and the user will usually not use command completion.
	 */
	elfedit_cpl_module(cpldata, 1);
}


/*
 * Implementation of sys:quit
 */
/*ARGSUSED*/
static elfedit_cmdret_t
cmd_quit(void *obj_state, int argc, const char *argv[])
{
	elfedit_getopt_state_t	getopt_state;
	elfedit_getopt_ret_t	*getopt_ret;
	int			force = 0;
	const char		*file;
	int			fd;
	Elf			*elf;

	elfedit_getopt_init(&getopt_state, &argc, &argv);
	while ((getopt_ret = elfedit_getopt(&getopt_state)) != NULL) {
		switch (getopt_ret->gor_idmask) {
		case SYS_OPT_F_FORCE:
			force = 1;
			break;
		}
	}
	if (argc != 0)
		elfedit_command_usage();

	if (state.file.present) {
		/*
		 * If session is not READONLY, then refuse to quit if file
		 * needs flushing and -f option was not used.
		 */
		if (!(state.flags & ELFEDIT_F_READONLY) && state.file.dirty &&
		    !force)
			elfedit_msg(ELFEDIT_MSG_ERR,
			    MSG_INTL(MSG_ERR_NODIRTYQUIT));

		get_obj_state_info(obj_state, &file, &fd, &elf);
		(void) close(fd);
		(void) elf_end(elf);
		free(obj_state);
	}

	elfedit_exit(0);
	/*NOTREACHED*/
	return (0);
}


/*
 * Implementation of sys:status
 */
/*ARGSUSED*/
static elfedit_cmdret_t
cmd_status(void *obj_state, int argc, const char *argv[])
{
	MODLIST_T	*modlist;
	const char	*s;
	size_t		i;

	if (argc > 0)
		elfedit_command_usage();

	/*
	 * This command can produce an arbitrary amount of output, so
	 * run a pager.
	 */
	elfedit_pager_init();

	/* Files */
	if (state.file.present == 0) {
		elfedit_printf(MSG_INTL(MSG_HLPFMT_INFILENONE));
	} else if (state.flags & ELFEDIT_F_READONLY) {
		elfedit_printf(MSG_INTL(MSG_HLPFMT_INFILERO),
		    state.file.infile);
	} else {
		elfedit_printf(MSG_INTL(MSG_HLPFMT_INFILE), state.file.infile);
		elfedit_printf(MSG_INTL(MSG_HLPFMT_OUTFILE),
		    state.file.outfile);
	}
	if (state.file.dirty)
		elfedit_printf(MSG_INTL(MSG_HLPFMT_CNGPENDING));

	/* Option Variables */
	elfedit_printf(MSG_INTL(MSG_HLPFMT_VARHDR));
	elfedit_printf(MSG_INTL(MSG_HLPFMT_AFLG),
	    (state.flags & ELFEDIT_F_AUTOPRINT) ? MSG_ORIG(MSG_STR_ON) :
	    MSG_ORIG(MSG_STR_OFF));
	elfedit_printf(MSG_INTL(MSG_HLPFMT_DFLG),
	    (state.flags & ELFEDIT_F_DEBUG) ? MSG_ORIG(MSG_STR_ON) :
	    MSG_ORIG(MSG_STR_OFF));
	elfedit_printf(MSG_INTL(MSG_HLPFMT_OFLG),
	    elfedit_atoconst_value_to_str(ELFEDIT_CONST_OUTSTYLE,
	    state.outstyle, 1));

	/* Module Load Path */
	elfedit_printf(MSG_INTL(MSG_HLPFMT_PATHHDR));
	for (i = 0; i < state.modpath.n; i++)
		elfedit_printf(MSG_ORIG(MSG_HLPFMT_PATHELT),
		    state.modpath.seg[i]);

	/* Currently Loaded Modules */
	elfedit_printf(MSG_INTL(MSG_HLPFMT_MODHDR));
	for (modlist = state.modlist; modlist;
	    modlist = modlist->ml_next) {
		s = modlist->ml_path ? modlist->ml_path :
		    MSG_INTL(MSG_FMT_BUILTIN);
		elfedit_printf(MSG_ORIG(MSG_HLPFMT_NAMDSCCOL),
		    modlist->ml_mod->mod_name, s);
	}

	return (ELFEDIT_CMDRET_NONE);
}

/*
 * Implementation of sys:set
 */
/*ARGSUSED*/
static elfedit_cmdret_t
cmd_set(void *obj_state, int argc, const char *argv[])
{
	if ((argc != 2) || (strlen(argv[0]) > 1))
		elfedit_command_usage();

	switch (**argv) {
	case 'a':
	case 'A':
		if (elfedit_atobool(argv[1], MSG_INTL(MSG_SYSSET_A)))
			state.flags |= ELFEDIT_F_AUTOPRINT;
		else
			state.flags &= ~ELFEDIT_F_AUTOPRINT;
		break;

	case 'd':
	case 'D':
		if (elfedit_atobool(argv[1], MSG_INTL(MSG_SYSSET_D)))
			state.flags |= ELFEDIT_F_DEBUG;
		else
			state.flags &= ~ELFEDIT_F_DEBUG;
		break;

	case 'o':
	case 'O':
		if (elfedit_atooutstyle(argv[1], &state.outstyle) == 0)
			elfedit_msg(ELFEDIT_MSG_ERR,
			    MSG_INTL(MSG_ERR_BADOSTYLE), argv[1]);
		break;

	default:
		elfedit_command_usage();
	}

	return (0);
}


/*
 * Command completion function for sys:set
 */
/*ARGSUSED*/
static void
cpl_set(void *obj_state, void *cpldata, int argc, const char *argv[],
    int num_opt)
{
	const char *s;

	/*
	 * This command doesn't accept options, so num_opt should be
	 * 0. This is a defensive measure, in case that should change.
	 */
	argc -= num_opt;
	argv += num_opt;

	if ((argc < 1) || (argc > 2))
		return;

	if (argc == 1) {	/* The first argument is a variable letter */
		elfedit_cpl_match(cpldata, MSG_ORIG(MSG_STR_A), 1);
		elfedit_cpl_match(cpldata, MSG_ORIG(MSG_STR_D), 1);
		elfedit_cpl_match(cpldata, MSG_ORIG(MSG_STR_O), 1);
		elfedit_cpl_match(cpldata, MSG_ORIG(MSG_STR_W), 1);
		return;
	}

	/* We're dealing with the second argument, the value */
	s = argv[0];
	if (strlen(s) > 1)	/* One letter variables */
		return;
	switch (*s) {
	case 'a':		/* Booleans */
	case 'A':
	case 'd':
	case 'D':
	case 'w':
	case 'W':
		/* The second argument is a boolean */
		elfedit_cpl_atoconst(cpldata, ELFEDIT_CONST_BOOL);

		/* The numbers are not symbolic, but we want them in the list */
		elfedit_cpl_match(cpldata, MSG_ORIG(MSG_STR_0), 1);
		elfedit_cpl_match(cpldata, MSG_ORIG(MSG_STR_1), 1);
		break;

	case 'o':		/* Output style */
	case 'O':
		elfedit_cpl_atoconst(cpldata, ELFEDIT_CONST_OUTSTYLE);
		break;
	}
}


/*
 * Implementation of sys:unload
 */
/*ARGSUSED*/
static elfedit_cmdret_t
cmd_unload(void *obj_state, int argc, const char *argv[])
{
	elfedit_getopt_state_t	getopt_state;
	elfedit_getopt_ret_t	*getopt_ret;
	MODLIST_T		*moddef;
	int			do_all = 0;

	elfedit_getopt_init(&getopt_state, &argc, &argv);
	while ((getopt_ret = elfedit_getopt(&getopt_state)) != NULL) {
		switch (getopt_ret->gor_idmask) {
		case SYS_OPT_F_ALL:
			do_all = 1;
			break;
		}
	}

	/*
	 * If -a is specified, unload everything except builtins. Don't
	 * allow plain arguments in this case because there is nothing
	 * left to unload after -a.
	 */
	if (do_all) {
		if (argc > 0)
			elfedit_command_usage();
		/*
		 * Until we run out of non-builtin modules, take the first
		 * one from the list and unload it. Each removal alters
		 * the list, so we always start at the beginning, but this
		 * is efficient since we always remove the first available item
		 */
		while (state.modlist != NULL) {
			for (moddef = state.modlist; moddef != NULL;
			    moddef = moddef->ml_next)
				if (moddef->ml_dl_hdl != NULL) break;

			/* If we made it to the end, then the list is empty */
			if (moddef == NULL)
				break;

			elfedit_unload_module(moddef->ml_mod->mod_name);
		}
		return (0);
	}

	/* Unload each module individually */
	for (; argc-- > 0; argv++)
		elfedit_unload_module(*argv);

	return (0);
}


/*
 * Command completion function for sys:unload
 */
/*ARGSUSED*/
static void
cpl_unload(void *obj_state, void *cpldata, int argc, const char *argv[],
    int num_opt)
{
	/*
	 * Module names. Don't allow elfedit to load all the modules,
	 * as the only modules we want to unload are those already
	 * in memory.
	 */
	elfedit_cpl_module(cpldata, 0);
}


/*
 * Implementation of sys:write
 */
/*ARGSUSED2*/
static elfedit_cmdret_t
cmd_write(void *obj_state, int argc, const char *argv[])
{
	const char	*file;
	int		fd;
	Elf		*elf;

	if (argc != 0)
		elfedit_command_usage();

	if (state.file.present != 0) {
		if (state.flags & ELFEDIT_F_READONLY)
			elfedit_msg(ELFEDIT_MSG_ERR,
			    MSG_INTL(MSG_ERR_READONLY));

		get_obj_state_info(obj_state, &file, &fd, &elf);
		if (elf_update(elf, ELF_C_WRITE) == -1)
			elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_LIBELF),
			    file, MSG_ORIG(MSG_ELF_UPDATE),
			    elf_errmsg(elf_errno()));

		/*
		 * An update has succeeded for this file, so revoke the need
		 * to unlink it on exit.
		 */
		state.file.unlink_on_exit = 0;
	}

	return (ELFEDIT_CMDRET_FLUSH);
}





/*ARGSUSED*/
MODLIST_T *
elfedit_sys_init(elfedit_module_version_t version)
{
	/* sys:help */
	static const char *name_help[] = { MSG_ORIG(MSG_SYS_CMD_HELP),
	    MSG_ORIG(MSG_SYS_CMD_HELP_A1), MSG_ORIG(MSG_SYS_CMD_HELP_A2),
	    NULL };
	static elfedit_cmd_optarg_t opt_help[] = {
		{ MSG_ORIG(MSG_STR_MINUS_S),
		    /* MSG_INTL(MSG_SYS_OPTDESC_HELP_S) */
		    ELFEDIT_I18NHDL(MSG_SYS_OPTDESC_HELP_S), 0,
		    SYS_OPT_F_SYNOPSIS, 0 },
		{ NULL }
	};
	static elfedit_cmd_optarg_t arg_help[] = {
		{ MSG_ORIG(MSG_STR_ARG),
		    /* MSG_INTL(MSG_ARGDESC_HELP_ARG) */
		    ELFEDIT_I18NHDL(MSG_ARGDESC_HELP_ARG),
		    ELFEDIT_CMDOA_F_OPT | ELFEDIT_CMDOA_F_MULT },
		{ NULL }
	};

	/* sys:load */
	static const char *name_load[] = {
	    MSG_ORIG(MSG_SYS_CMD_LOAD), NULL };
	static elfedit_cmd_optarg_t opt_load[] = {
		{ MSG_ORIG(MSG_STR_MINUS_A),
		    /* MSG_INTL(MSG_SYS_OPTDESC_LOAD_A) */
		    ELFEDIT_I18NHDL(MSG_SYS_OPTDESC_LOAD_A), 0,
		    SYS_OPT_F_ALL, 0 },
		{ NULL }
	};
	static elfedit_cmd_optarg_t arg_load[] = {
		{ MSG_ORIG(MSG_STR_MODNAME),
		    /* MSG_INTL(MSG_ARGDESC_LOAD_MODNAME) */
		    ELFEDIT_I18NHDL(MSG_ARGDESC_LOAD_MODNAME),
		    ELFEDIT_CMDOA_F_OPT | ELFEDIT_CMDOA_F_MULT },
		{ NULL }
	};

	/* sys:quit */
	static const char *name_quit[] = { MSG_ORIG(MSG_SYS_CMD_QUIT),
	    MSG_ORIG(MSG_SYS_CMD_QUIT_A1), MSG_ORIG(MSG_SYS_CMD_QUIT_A2),
	    NULL };
	static elfedit_cmd_optarg_t opt_quit[] = {
		{ MSG_ORIG(MSG_STR_MINUS_F),
		    /* MSG_INTL(MSG_SYS_OPTDESC_QUIT_F) */
		    ELFEDIT_I18NHDL(MSG_SYS_OPTDESC_QUIT_F), 0,
		    SYS_OPT_F_FORCE, 0 },
		{ NULL }
	};

	/* sys:status */
	static const char *name_status[] = {
	    MSG_ORIG(MSG_SYS_CMD_STATUS), NULL };

	/* sys:set */
	static const char *name_set[] = {
	    MSG_ORIG(MSG_SYS_CMD_SET), NULL };
	static elfedit_cmd_optarg_t arg_set[] = {
		{ MSG_ORIG(MSG_STR_OPTION),
		    /* MSG_INTL(MSG_ARGDESC_SET_OPTION) */
		    ELFEDIT_I18NHDL(MSG_ARGDESC_SET_OPTION), 0 },
		{ MSG_ORIG(MSG_STR_VALUE),
		    /* MSG_INTL(MSG_ARGDESC_SET_VALUE) */
		    ELFEDIT_I18NHDL(MSG_ARGDESC_SET_VALUE), 0 },
		{ NULL }
	};

	/* sys:unload */
	static const char *name_unload[] = {
	    MSG_ORIG(MSG_SYS_CMD_UNLOAD), NULL };
	static elfedit_cmd_optarg_t opt_unload[] = {
		{ MSG_ORIG(MSG_STR_MINUS_A),
		    /* MSG_INTL(MSG_SYS_OPTDESC_UNLOAD_A) */
		    ELFEDIT_I18NHDL(MSG_SYS_OPTDESC_UNLOAD_A), 0,
		    SYS_OPT_F_ALL, 0},
		{ NULL }
	};
	static elfedit_cmd_optarg_t arg_unload[] = {
		{ MSG_ORIG(MSG_STR_MODNAME),
		    /* MSG_INTL(MSG_ARGDESC_UNLOAD_MODNAME) */
		    ELFEDIT_I18NHDL(MSG_ARGDESC_UNLOAD_MODNAME),
		    ELFEDIT_CMDOA_F_OPT | ELFEDIT_CMDOA_F_MULT },
		{ NULL }
	};

	/* sys:write */
	static const char *name_write[] = { MSG_ORIG(MSG_SYS_CMD_WRITE),
	    MSG_ORIG(MSG_SYS_CMD_WRITE_A1), MSG_ORIG(MSG_SYS_CMD_WRITE_A2),
	    NULL };

	static elfedit_cmd_t cmds[] = {
		/* sym:help */
		{ (elfedit_cmd_func_t *)cmd_help,
		    (elfedit_cmdcpl_func_t *)cpl_help, name_help,
		    /* MSG_INTL(MSG_SYS_DESC_HELP) */
		    ELFEDIT_I18NHDL(MSG_SYS_DESC_HELP),
		    /* MSG_INTL(MSG_SYS_HELP_HELP) */
		    ELFEDIT_I18NHDL(MSG_SYS_HELP_HELP),
		    opt_help, arg_help },

		/* sym:load */
		{ (elfedit_cmd_func_t *)cmd_load,
		    (elfedit_cmdcpl_func_t *)cpl_load, name_load,
		    /* MSG_INTL(MSG_SYS_DESC_LOAD) */
		    ELFEDIT_I18NHDL(MSG_SYS_DESC_LOAD),
		    /* MSG_INTL(MSG_SYS_HELP_LOAD) */
		    ELFEDIT_I18NHDL(MSG_SYS_HELP_LOAD),
		    opt_load, arg_load },

		/* sym:quit */
		{ (elfedit_cmd_func_t *)cmd_quit, NULL, name_quit,
		    /* MSG_INTL(MSG_SYS_DESC_QUIT) */
		    ELFEDIT_I18NHDL(MSG_SYS_DESC_QUIT),
		    /* MSG_INTL(MSG_SYS_HELP_QUIT) */
		    ELFEDIT_I18NHDL(MSG_SYS_HELP_QUIT),
		    opt_quit, NULL },

		/* sym:status */
		{ (elfedit_cmd_func_t *)cmd_status, NULL, name_status,
		    /* MSG_INTL(MSG_SYS_DESC_STATUS) */
		    ELFEDIT_I18NHDL(MSG_SYS_DESC_STATUS),
		    /* MSG_INTL(MSG_SYS_HELP_STATUS) */
		    ELFEDIT_I18NHDL(MSG_SYS_HELP_STATUS),
		    NULL, NULL },

		/* sym:set */
		{ (elfedit_cmd_func_t *)cmd_set,
		    (elfedit_cmdcpl_func_t *)cpl_set, name_set,
		    /* MSG_INTL(MSG_SYS_DESC_SET) */
		    ELFEDIT_I18NHDL(MSG_SYS_DESC_SET),
		    /* MSG_INTL(MSG_SYS_HELP_SET) */
		    ELFEDIT_I18NHDL(MSG_SYS_HELP_SET),
		    NULL, arg_set },

		/* sym:unload */
		{ (elfedit_cmd_func_t *)cmd_unload,
		    (elfedit_cmdcpl_func_t *)cpl_unload, name_unload,
		    /* MSG_INTL(MSG_SYS_DESC_UNLOAD) */
		    ELFEDIT_I18NHDL(MSG_SYS_DESC_UNLOAD),
		    /* MSG_INTL(MSG_SYS_HELP_UNLOAD) */
		    ELFEDIT_I18NHDL(MSG_SYS_HELP_UNLOAD),
		    opt_unload, arg_unload },

		/* sym:write */
		{ (elfedit_cmd_func_t *)cmd_write, NULL, name_write,
		    /* MSG_INTL(MSG_SYS_DESC_WRITE) */
		    ELFEDIT_I18NHDL(MSG_SYS_DESC_WRITE),
		    /* MSG_INTL(MSG_SYS_HELP_WRITE) */
		    ELFEDIT_I18NHDL(MSG_SYS_HELP_WRITE),
		    NULL, NULL},

		{ NULL }
	};

	static elfedit_module_t module = {
	    ELFEDIT_VER_CURRENT, MSG_ORIG(MSG_MOD_SYS),
	    /* MSG_INTL(MSG_MOD_SYS_DESC) */
	    ELFEDIT_I18NHDL(MSG_MOD_SYS_DESC),
	    cmds, mod_i18nhdl_to_str };

	static MODLIST_T moddef = {
		NULL,		/* next */
		(elfeditGC_module_t *)&module,	/* Module definition */
		NULL,		/* Didn't dlopen() it, so NULL handle */
		NULL		/* Didn't dlopen() it, so no file path */
	};

	return (&moddef);
}