/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/wait.h>
#include	<stdarg.h>
#include	<fcntl.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<signal.h>
#include	<dirent.h>
#include	<libelf.h>
#include	<gelf.h>
#include	<conv.h>
#include	<dlfcn.h>
#include	<link.h>
#include	<stdarg.h>
#include	<libgen.h>
#include	<libintl.h>
#include	<locale.h>
#include	<unistd.h>
#include	<errno.h>
#include	<ctype.h>
#include	<limits.h>
#include	<strings.h>
#include	<sgs.h>
#include	"msg.h"
#include	"_elfedit.h"
#include	<debug.h>	/* liblddb */



/*
 * Column at which elfedit_format_command_usage() will wrap the
 * generated usage string if the wrap argument is True (1).
 */
#define	USAGE_WRAP_COL 55




/*
 * Type used to represent a string buffer that can grow as needed
 * to hold strings of arbitrary length. The user should declare
 * variables of this type sa static. The strbuf_ensure_size() function
 * is used to ensure that it has a minimum desired size.
 */
typedef struct {
	char *buf;		/* String buffer */
	size_t n;		/* Size of buffer */
} STRBUF;




/*
 * Types used by tokenize_user_cmd() to represent the result of
 * spliting a user command into individual tokens.
 */
typedef struct {
	char	*tok_str;	/* Token string */
	size_t	tok_len;	/* strlen(str) */
	size_t	tok_line_off;	/* Token offset in original string */
} TOK_ELT;
typedef struct {
	size_t	tokst_cmd_len;	/* Length of original user command, without */
				/*	newline or NULL termination chars */
	size_t	tokst_str_size;	/* Space needed to hold all the resulting */
				/*	tokens, including terminating NULL */
	TOK_ELT	*tokst_buf;	/* The array of tokens */
	size_t	tokst_cnt;	/* # of tokens in array */
	size_t	tokst_bufsize;	/* capacity of array */
} TOK_STATE;




/* State block used by gettok_init() and gettok() */
typedef struct {
	const char	*gtok_buf;	/* Addr of buffer containing string */
	char		*gtok_cur_buf;	/* Addr withing buffer for next token */
	int		gtok_inc_null_final; /* True if final NULL token used */
	int		gtok_null_seen;	/* True when NULL byte seen */
	TOK_ELT		gtok_last_token; /* Last token parsed */

} GETTOK_STATE;




/*
 * The elfedit_cpl_*() functions are used for command line completion.
 * Currently this uses the tecla library, but to allow for changing the
 * library used, we hide all tecla interfaces from our modules. Instead,
 * cmd_match_fcn() builds an ELFEDIT_CPL_STATE struct, and we pass the
 * address of that struct as an opaque handle to the modules. Since the
 * pointer is opaque, the contents of ELFEDIT_CPL_STATE are free to change
 * as necessary.
 */
typedef struct {
	WordCompletion	*ecpl_cpl;		/* tecla handle */
	const char	*ecpl_line;		/* raw input line */
	int		ecpl_word_start;	/* start offset within line */
	int		ecpl_word_end;		/* offset just past token */
	/*
	 * ecpl_add_mod_colon is a secret handshake between
	 * elfedit_cpl_command() and  elfedit_cpl_add_match(). It adds
	 * ':' to end of matched modules.
	 */
	int		ecpl_add_mod_colon;
	const char	*ecpl_token_str;	/* token being completed */
	size_t		ecpl_token_len;		/* strlen(ecpl_token_str) */
} ELFEDIT_CPL_STATE;




/* This structure maintains elfedit global state */
STATE_T state;



/*
 * Define a pair of static global variables that contain the
 * ISA strings that correspond to %i and %I tokens in module search
 * paths.
 *
 *	isa_i_str - The ISA string for the currently running program
 *	isa_I_str - For 64-bit programs, the same as isa_i_str. For
 *		32-bit programs, an empty string.
 */
#ifdef __sparc
#ifdef __sparcv9
static const char *isa_i_str = MSG_ORIG(MSG_ISA_SPARC_64);
static const char *isa_I_str = MSG_ORIG(MSG_ISA_SPARC_64);
#else
static const char *isa_i_str = MSG_ORIG(MSG_ISA_SPARC_32);
static const char *isa_I_str = MSG_ORIG(MSG_STR_EMPTY);
#endif
#endif

#ifdef __i386
static const char *isa_i_str = MSG_ORIG(MSG_ISA_X86_32);
static const char *isa_I_str = MSG_ORIG(MSG_STR_EMPTY);
#endif
#ifdef __amd64
static const char *isa_i_str = MSG_ORIG(MSG_ISA_X86_64);
static const char *isa_I_str = MSG_ORIG(MSG_ISA_X86_64);
#endif



/* Forward declarations */
static void free_user_cmds(void);
static void elfedit_pager_cleanup(void);



/*
 * We supply this function for the msg module
 */
const char *
_elfedit_msg(Msg mid)
{
	return (gettext(MSG_ORIG(mid)));
}


/*
 * Copy at most min(cpsize, dstsize-1) bytes from src into dst,
 * truncating src if necessary.  The  result is always null-terminated.
 *
 * entry:
 *	dst - Destination buffer
 *	src - Source string
 *	dstsize - sizeof(dst)
 *
 * note:
 *	This is similar to strncpy(), but with two modifications:
 *	1) You specify the number of characters to copy, not just
 *		the size of the destination. Hence, you can copy non-NULL
 *		terminated strings.
 *	2) The destination is guaranteed to be NULL terminated. strncpy()
 *		does not terminate a completely full buffer.
 */
static void
elfedit_strnbcpy(char *dst, const char *src, size_t cpsize, size_t dstsize)
{
	if (cpsize >= dstsize)
		cpsize = dstsize - 1;
	if (cpsize > 0)
		(void) strncpy(dst, src, cpsize + 1);
	dst[cpsize] = '\0';
}


/*
 * Calls exit() on behalf of elfedit.
 */
void
elfedit_exit(int status)
{
	if (state.file.present) {
		/* Exiting with unflushed changes pending? Issue debug notice */
		if (state.file.dirty)
			elfedit_msg(ELFEDIT_MSG_DEBUG,
			    MSG_INTL(MSG_DEBUG_DIRTYEXIT));

		/*
		 * If the edit file is marked for unlink on exit, then
		 * take care of it here.
		 */
		if (state.file.unlink_on_exit) {
			elfedit_msg(ELFEDIT_MSG_DEBUG,
			    MSG_INTL(MSG_DEBUG_UNLINKFILE),
			    state.file.outfile);
			(void) unlink(state.file.outfile);
		}
	}

	exit(status);
}


/*
 * Standard message function for elfedit. All user visible
 * output, for error or informational reasons, should go through
 * this function.
 *
 * entry:
 *	type - Type of message. One of the ELFEDIT_MSG_* values.
 *	format, ... - As per the printf() family
 *
 * exit:
 *	The desired message has been output. For informational
 *	messages, control returns to the caller. For errors,
 *	this routine will terminate execution or strip the execution
 *	stack and return control directly to the outer control loop.
 *	In either case, the caller will not receive control.
 */
/*PRINTFLIKE2*/
void
elfedit_msg(elfedit_msg_t type, const char *format, ...)
{
	typedef enum {			/* What to do after finished */
		DISP_RET = 0,		/* Return to caller */
		DISP_JMP = 1, 		/* if (interactive) longjmp else exit */
		DISP_EXIT = 2		/* exit under all circumstances */
	} DISP;

	va_list args;
	FILE *stream = stderr;
	DISP disp = DISP_RET;
	int do_output = 1;
	int need_prefix = 1;

	va_start(args, format);

	switch (type) {
	case ELFEDIT_MSG_ERR:
	case ELFEDIT_MSG_CMDUSAGE:
		disp = DISP_JMP;
		break;
	case ELFEDIT_MSG_FATAL:
		disp = DISP_EXIT;
		break;
	case ELFEDIT_MSG_USAGE:
		need_prefix = 0;
		break;
	case ELFEDIT_MSG_DEBUG:
		if (!(state.flags & ELFEDIT_F_DEBUG))
			return;
		stream = stdout;
		break;
	case ELFEDIT_MSG_QUIET:
		do_output = 0;
		disp = DISP_JMP;
		break;
	}


	/*
	 * If there is a pager process running, we are returning to the
	 * caller, and the output is going to stdout, then let the
	 * pager handle it instead of writing it directly from this process.
	 * That way, the output gets paged along with everything else.
	 *
	 * If there is a pager process running, and we are not returning
	 * to the caller, then end the pager process now, before we generate
	 * any new output. This allows for any text buffered in the pager
	 * pipe to be output before the new stuff.
	 */
	if (state.pager.fptr != NULL) {
		if (disp == DISP_RET) {
			if (stream == stdout)
				stream = state.pager.fptr;
		} else {
			elfedit_pager_cleanup();
		}
	}

	/*
	 * If this message is coming from within the libtecla command
	 * completion code, call gl_normal_io() to give the library notice.
	 * That function sets the tty back to cooked mode and advances
	 * the cursor to the beginning of the next line so that our output
	 * will appear properly. When we return to the command completion code,
	 * tecla will re-enter raw mode and redraw the current command line.
	 */
	if (state.input.in_tecla)
		(void) gl_normal_io(state.input.gl);

	if (do_output) {
		if (need_prefix)
			(void) fprintf(stream, MSG_ORIG(MSG_STR_ELFEDIT));
		(void) vfprintf(stream, format, args);
		(void) fflush(stream);
	}
	va_end(args);

	/*
	 * If this is an error, then we do not return to the caller.
	 * The action taken depends on whether the outer loop has registered
	 * a jump buffer for us or not.
	 */
	if (disp != DISP_RET) {
		if (state.msg_jbuf.active && (disp == DISP_JMP)) {
			/* Free the user command list */
			free_user_cmds();

			/* Clean up to reflect effect of non-local goto */
			state.input.in_tecla = FALSE;

			/* Jump to the outer loop to resume */
			siglongjmp(state.msg_jbuf.env, 1);
		} else {
			elfedit_exit(1);
		}
	}
}


/*
 * Wrapper on elfedit_msg() that issues an error that results from
 * a call to libelf.
 *
 * entry:
 *	file - Name of ELF object
 *	libelf_rtn_name - Name of routine that was called
 *
 * exit:
 *	An error has been issued that shows the routine called
 *	and the libelf error string for it from elf_errmsg().
 *	This routine does not return to the caller.
 */
void
elfedit_elferr(const char *file, const char *libelf_rtn_name)
{
	const char *errstr = elf_errmsg(elf_errno());

	elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_LIBELF), file,
	    libelf_rtn_name, errstr ? errstr : MSG_INTL(MSG_FMT_UNKNOWN));
}


/*
 * Start an output pager process for elfedit_printf()/elfedit_write() to use.
 *
 * note:
 *	If this elfedit session is not interactive, then no pager is
 *	started. Paging is only intended for interactive use. The caller
 *	is not supposed to worry about this point, but simply to use
 *	this function to flag situations in which paging might be needed.
 */
void
elfedit_pager_init(void)
{
	const char	*errstr;
	const char	*cmd;
	int		err;

	/*
	 * If there is no pager process running, start one.
	 * Only do this for interactive sessions --- elfedit_pager()
	 * won't use a pager in batch mode.
	 */
	if (state.msg_jbuf.active && state.input.full_tty &&
	    (state.pager.fptr == NULL)) {
		/*
		 * If the user has the PAGER environment variable set,
		 * then we will use that program. Otherwise we default
		 * to /bin/more.
		 */
		cmd = getenv(MSG_ORIG(MSG_STR_PAGER));
		if ((cmd == NULL) || (*cmd == '\0'))
			cmd = MSG_ORIG(MSG_STR_BINMORE);

		/*
		 * The popen() manpage says that on failure, it "may set errno",
		 * which is somewhat ambiguous. We explicitly zero it here, and
		 * assume that any change is due to popen() failing.
		 */
		errno = 0;
		state.pager.fptr = popen(cmd, MSG_ORIG(MSG_STR_W));
		if (state.pager.fptr == NULL) {
			err = errno;
			errstr = (err == 0) ? MSG_INTL(MSG_ERR_UNKNOWNSYSERR) :
			    strerror(err);
			elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTEXEC),
			    MSG_ORIG(MSG_STR_ELFEDIT), cmd, errstr);
		}
	}
}


/*
 * If there is a pager process present, close it out.
 *
 * note:
 *	This function is called from within elfedit_msg(), and as
 *	such, must not use elfedit_msg() to report errors. Furthermore,
 *	any such errors are not a sufficient reason to terminate the process
 *	or to longjmp(). This is a rare case where errors are written
 *	directly to stderr.
 */
static void
elfedit_pager_cleanup(void)
{
	if (state.pager.fptr != NULL) {
		if (pclose(state.pager.fptr) == -1)
			(void) fprintf(stderr, MSG_INTL(MSG_ERR_PAGERFINI));

		state.pager.fptr = NULL;
	}
}


/*
 * Print general formtted text for the user, using printf()-style
 * formatting. Uses the pager process if one has been started, or
 * stdout otherwise.
 */
void
elfedit_printf(const char *format, ...)
{
	va_list	args;
	int	err;
	FILE	*fptr;
	int	pager;
	int	broken_pipe = 0;

	/*
	 * If there is a pager process, then use it. Otherwise write
	 * directly to stdout.
	 */
	pager = (state.pager.fptr != NULL);
	fptr = pager ? state.pager.fptr : stdout;

	va_start(args, format);
	errno = 0;
	err = vfprintf(fptr, format, args);

	/* Did we fail because a child pager process has exited? */
	broken_pipe = pager && (err < 0) && (errno == EPIPE);

	va_end(args);

	/*
	 * On error, we simply issue the error without cleaning up
	 * the pager process. The message code handles that as a standard
	 * part of error processing.
	 *
	 * We handle failure due to an exited pager process differently
	 * than a normal error, because it is usually due to the user
	 * intentionally telling it to.
	 */
	if (err < 0) {
		if (broken_pipe)
			elfedit_msg(ELFEDIT_MSG_QUIET, MSG_ORIG(MSG_STR_NULL));
		else
			elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_PRINTF));
	}
}


/*
 * Some our modules use liblddb routines to format ELF output.
 * In order to ensure that such output is sent to the pager pipe
 * when there is one, and stdout otherwise, we redefine the dbg_print()
 * function here.
 *
 * This item should be defined NODIRECT.
 */
/* PRINTFLIKE2 */
void
dbg_print(Lm_list *lml, const char *format, ...)
{
	va_list	ap;
	int	err;
	FILE	*fptr;
	int	pager;
	int	broken_pipe = 0;

#if	defined(lint)
	/*
	 * The lml argument is only meaningful for diagnostics sent to ld.so.1.
	 * Supress the lint error by making a dummy assignment.
	 */
	lml = 0;
#endif

	/*
	 * If there is a pager process, then use it. Otherwise write
	 * directly to stdout.
	 */
	pager = (state.pager.fptr != NULL);
	fptr = pager ? state.pager.fptr : stdout;

	va_start(ap, format);
	errno = 0;
	err = vfprintf(fptr, format, ap);
	if (err >= 0)
		err = fprintf(fptr, MSG_ORIG(MSG_STR_NL));

	/* Did we fail because a child pager process has exited? */
	broken_pipe = (err < 0) && pager && (errno == EPIPE);

	va_end(ap);

	/*
	 * On error, we simply issue the error without cleaning up
	 * the pager process. The message code handles that as a standard
	 * part of error processing.
	 *
	 * We handle failure due to an exited pager process differently
	 * than a normal error, because it is usually due to the user
	 * intentionally telling it to.
	 */
	if (err < 0) {
		if (broken_pipe)
			elfedit_msg(ELFEDIT_MSG_QUIET, MSG_ORIG(MSG_STR_NULL));
		else
			elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_PRINTF));
	}
}


/*
 * Write raw bytes of text in a manner similar to fwrite().
 * Uses the pager process if one has been started, or
 * stdout otherwise.
 */
void
elfedit_write(const void *ptr, size_t size)
{
	FILE	*fptr;
	int	err;

	/*
	 * If there is a pager process, then use it. Otherwise write
	 * directly to stdout.
	 */
	fptr = (state.pager.fptr == NULL) ? stdout : state.pager.fptr;

	if (fwrite(ptr, 1, size, fptr) != size) {
		err = errno;
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_FWRITE),
		    strerror(err));
	}
}


/*
 * Convert the NULL terminated string to the form used by the C
 * language to represent literal strings. See conv_str_to_c_literal()
 * for details.
 *
 * This routine differs from conv_str_to_c_literal() in two ways:
 *	1) String is NULL terminated instead of counted
 *	2) Signature of outfunc
 *
 * entry:
 *	str - String to be processed
 *	outfunc - Function to be called to move output characters. Note
 *		that this function has the same signature as elfedit_write(),
 *		and that function can be used to write the characters to
 *		the output.
 *
 * exit:
 *	The string has been processed, with the resulting data passed
 *	to outfunc for processing.
 */
static void
elfedit_str_to_c_literal_cb(const void *ptr, size_t size, void *uvalue)
{
	elfedit_write_func_t *outfunc = (elfedit_write_func_t *)uvalue;

	(* outfunc)(ptr, size);

}
void
elfedit_str_to_c_literal(const char *str, elfedit_write_func_t *outfunc)
{
	conv_str_to_c_literal(str, strlen(str),
	    elfedit_str_to_c_literal_cb, (void *) outfunc);
}


/*
 * Wrappers on malloc() and realloc() that check the result for success
 * and issue an error if not. The caller can use the result of these
 * functions without checking for a NULL pointer, as we do not return to
 * the caller in the failure case.
 */
void *
elfedit_malloc(const char *item_name, size_t size)
{
	void *m;

	m = malloc(size);
	if (m == NULL) {
		int err = errno;
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_MALLOC),
		    item_name, strerror(err));
	}

	return (m);
}

void *
elfedit_realloc(const char *item_name, void *ptr, size_t size)
{
	void *m;

	m = realloc(ptr, size);
	if (m == NULL) {
		int err = errno;
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_MALLOC),
		    item_name, strerror(err));
	}

	return (m);
}


/*
 * Ensure that the given buffer has room for n bytes of data.
 */
static void
strbuf_ensure_size(STRBUF *str, size_t size)
{
#define	INITIAL_STR_ALLOC 128

	size_t n;

	n = (str->n == 0) ? INITIAL_STR_ALLOC : str->n;
	while (size > n)	/* Double buffer until string fits */
		n *= 2;
	if (n != str->n) {		/* Alloc new string buffer if needed */
		str->buf = elfedit_realloc(MSG_INTL(MSG_ALLOC_UCMDSTR),
		    str->buf, n);
		str->n = n;
	}

#undef	INITIAL_STR_ALLOC
}


/*
 * Extract the argument/option information for the next item referenced
 * by optarg, and advance the pointer to the next item.
 *
 * entry:
 *	optarg - Address of pointer to argument or option array
 *	item - Struct to be filled in.
 *
 * exit:
 *	The item block has been filled in with the information for
 *	the next item in the optarg array. *optarg has been advanced
 *	to the next item.
 */
void
elfedit_next_optarg(elfedit_cmd_optarg_t **optarg, elfedit_optarg_item_t *item)
{
	/*
	 * Array of inheritable options/arguments. Indexed by one less
	 * than the corresponding ELFEDIT_STDOA_ value.
	 */
	static const elfedit_optarg_item_t stdoa[] = {
		/* ELFEDIT_STDOA_O */
		{ MSG_ORIG(MSG_STR_MINUS_O), MSG_ORIG(MSG_STR_OUTSTYLE),
		    /* MSG_INTL(MSG_STDOA_OPTDESC_O) */
		    (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_O,
		    ELFEDIT_CMDOA_F_VALUE },

		/* ELFEDIT_STDOA_AND */
		{ MSG_ORIG(MSG_STR_MINUS_AND), NULL,
		    /* MSG_INTL(MSG_STDOA_OPTDESC_AND) */
		    (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_AND, 0 },

		/* ELFEDIT_STDOA_CMP */
		{ MSG_ORIG(MSG_STR_MINUS_CMP), NULL,
		    /* MSG_INTL(MSG_STDOA_OPTDESC_CMP) */
		    (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_CMP, 0 },

		/* ELFEDIT_STDOA_OR */
		{ MSG_ORIG(MSG_STR_MINUS_OR), NULL,
		    /* MSG_INTL(MSG_STDOA_OPTDESC_OR) */
		    (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_OR, 0 },
	};

	elfedit_cmd_optarg_t *oa;


	/* Grab first item, advance the callers pointer over it */
	oa = (*optarg)++;

	if (oa->oa_flags & ELFEDIT_CMDOA_F_INHERIT) {
		/* Values are pre-chewed in the stdoa array above */
		*item = stdoa[((uintptr_t)oa->oa_name) - 1];

		/*
		 * Set the inherited flag so that elfedit_optarg_helpstr()
		 * can tell who is responsible for translating the help string.
		 */
		item->oai_flags |= ELFEDIT_CMDOA_F_INHERIT;
	} else {	/* Non-inherited item */
		item->oai_name = oa->oa_name;
		if ((oa->oa_flags & ELFEDIT_CMDOA_F_VALUE) != 0) {
			item->oai_vname = oa[1].oa_name;

			/* Advance users pointer past value element */
			(*optarg)++;
		} else {
			item->oai_vname = NULL;
		}
		item->oai_help = oa->oa_help;
		item->oai_flags = oa->oa_flags;
	}

	/*
	 * The module determines the idmask and excmask fields whether
	 * or not inheritance is in play.
	 */
	item->oai_idmask = oa->oa_idmask;
	item->oai_excmask = oa->oa_excmask;
}



/*
 * Return the help string for an option/argument item, as returned
 * by elfedit_next_optarg(). This routine handles the details of
 * knowing whether the string is provided by elfedit itself (inherited),
 * or needs to be translated by the module.
 */
const char *
elfedit_optarg_helpstr(elfeditGC_module_t *mod, elfedit_optarg_item_t *item)
{
	/*
	 * The help string from an inherited item comes right out
	 * of the main elfedit string table.
	 */
	if (item->oai_flags & ELFEDIT_CMDOA_F_INHERIT)
		return (MSG_INTL((Msg) item->oai_help));

	/*
	 * If the string is defined by the module, then we need to
	 * have the module translate it for us.
	 */
	return ((* mod->mod_i18nhdl_to_str)(item->oai_help));
}



/*
 * Used by usage_optarg() to insert a character into the output buffer,
 * advancing the buffer pointer and current column, and reducing the
 * amount of remaining space.
 */
static void
usage_optarg_insert_ch(int ch, char **cur, size_t *n, size_t *cur_col)
{

	*(*cur)++ = ch;
	**cur = '\0';
	(*n)--;
	(*cur_col)++;
}

/*
 * Used by usage_optarg() to insert a string into the output
 * buffer, advancing the buffer pointer and current column, and reducing
 * the amount of remaining space.
 */
static void
usage_optarg_insert_str(char **cur, size_t *n, size_t *cur_col,
    const char *format, ...)
{
	size_t len;
	va_list args;

	va_start(args, format);
	len = vsnprintf(*cur, *n, format, args);
	va_end(args);

	*cur += len;
	*n -= len;
	*cur_col += len;
}
/*
 * Used by usage_optarg() to insert an optarg item string into the output
 * buffer, advancing the buffer pointer and current column, and reducing
 * the amount of remaining space.
 */
static void
usage_optarg_insert_item(elfedit_optarg_item_t *item, char **cur,
    size_t *n, size_t *cur_col)
{
	size_t len;

	if (item->oai_flags & ELFEDIT_CMDOA_F_VALUE) {
		len = snprintf(*cur, *n, MSG_ORIG(MSG_STR_HLPOPTARG2),
		    item->oai_name, item->oai_vname);
	} else {
		len = snprintf(*cur, *n, MSG_ORIG(MSG_STR_HLPOPTARG),
		    item->oai_name);
	}
	*cur += len;
	*n -= len;
	*cur_col += len;
}



/*
 * Write the options/arguments to the usage string.
 *
 * entry:
 *	main_buf_n - Size of main buffer from which buf and buf_n are
 *		allocated.
 *	buf - Address of pointer to where next item is to be placed.
 *	buf_n - Address of count of remaining bytes in buffer
 *	buf_cur_col - Address of current output column for current line
 *		of generated string.
 *	optarg - Options list
 *	isopt - True if these are options, false for arguments.
 *	wrap_str - String to indent wrapped lines. If NULL, lines
 *		are not wrapped
 */
static void
usage_optarg(size_t main_buf_n, char **buf, size_t *buf_n, size_t *buf_cur_col,
    elfedit_cmd_optarg_t *optarg, int isopt, const char *wrap_str)
{
	/*
	 * An option can be combined into a simple format if it lacks
	 * these flags and is only one character in length.
	 */
	static const elfedit_cmd_oa_flag_t exflags =
	    (ELFEDIT_CMDOA_F_VALUE | ELFEDIT_CMDOA_F_MULT);

	/*
	 * A static buffer, which is grown as needed to accomodate
	 * the maximum usage string seen.
	 */
	static STRBUF simple_str;

	char			*cur = *buf;
	size_t			n = *buf_n;
	size_t			cur_col = *buf_cur_col;
	int			len;
	int			use_simple = 0;
	elfedit_optarg_item_t	item;
	elfedit_cmd_oa_mask_t	optmask = 0;
	int			use_bkt;

	/*
	 * If processing options, pull the 1-character ones that don't have
	 * an associated value and don't have any mutual exclusion issues into
	 * a single combination string to go at the beginning of the usage.
	 */
	if (isopt) {
		elfedit_cmd_optarg_t *tmp_optarg = optarg;
		char *s;

		/*
		 * The simple string is guaranteed to fit in the same
		 * amount of space reserved for the main buffer.
		 */
		strbuf_ensure_size(&simple_str, main_buf_n);
		s = simple_str.buf;
		*s++ = ' ';
		*s++ = '[';
		*s++ = '-';
		while (tmp_optarg->oa_name != NULL) {
			elfedit_next_optarg(&tmp_optarg, &item);
			if (((item.oai_flags & exflags) == 0) &&
			    (item.oai_name[2] == '\0') &&
			    (item.oai_excmask == 0)) {
				optmask |= item.oai_idmask;
				*s++ = item.oai_name[1];
			}
		}

		/*
		 * If we found more than one, then finish the string and
		 * add it. Don't do this for a single option, because
		 * it looks better in that case if the option shows up
		 * in alphabetical order rather than being hoisted.
		 */
		use_simple = (s > (simple_str.buf + 4));
		if (use_simple) {
			*s++ = ']';
			*s++ = '\0';
			usage_optarg_insert_str(&cur, &n, &cur_col,
			    MSG_ORIG(MSG_STR_HLPOPTARG), simple_str.buf);
		} else {
			/* Not using it, so reset the cumulative options mask */
			optmask = 0;
		}
	}

	while (optarg->oa_name != NULL) {
		elfedit_next_optarg(&optarg, &item);

		if (isopt) {
			/*
			 * If this is an option that was pulled into the
			 * combination string above, then skip over it.
			 */
			if (use_simple && ((item.oai_flags & exflags) == 0) &&
			    (item.oai_name[2] == '\0') &&
			    (item.oai_excmask == 0))
				continue;

			/*
			 * If this is a mutual exclusion option that was
			 * picked up out of order by a previous iteration
			 * of this loop, then skip over it.
			 */
			if ((optmask & item.oai_idmask) != 0)
				continue;

			/* Add this item to the accumulating options mask */
			optmask |= item.oai_idmask;
		}

		/* Wrap line, or insert blank separator */
		if ((wrap_str != NULL) && (cur_col > USAGE_WRAP_COL)) {
			len = snprintf(cur, n, MSG_ORIG(MSG_FMT_WRAPUSAGE),
			    wrap_str);
			cur += len;
			n -= len;
			cur_col = len - 1;   /* Don't count the newline */
		} else {
			usage_optarg_insert_ch(' ', &cur, &n, &cur_col);
		}

		use_bkt = (item.oai_flags & ELFEDIT_CMDOA_F_OPT) || isopt;
		if (use_bkt)
			usage_optarg_insert_ch('[', &cur, &n, &cur_col);

		/* Add the item to the buffer */
		usage_optarg_insert_item(&item, &cur, &n, &cur_col);

		/*
		 * If this item has a non-zero mutual exclusion mask,
		 * then look for the other items and display them all
		 * together with alternation (|). Note that plain arguments
		 * cannot have a non-0 exclusion mask, so this is
		 * effectively options-only (isopt != 0).
		 */
		if (item.oai_excmask != 0) {
			elfedit_cmd_optarg_t *tmp_optarg = optarg;
			elfedit_optarg_item_t tmp_item;

			/*
			 * When showing alternation, elipses for multiple
			 * copies need to appear inside the [] brackets.
			 */
			if (item.oai_flags & ELFEDIT_CMDOA_F_MULT)
				usage_optarg_insert_str(&cur, &n, &cur_col,
				    MSG_ORIG(MSG_STR_ELIPSES));


			while (tmp_optarg->oa_name != NULL) {
				elfedit_next_optarg(&tmp_optarg, &tmp_item);
				if ((item.oai_excmask & tmp_item.oai_idmask) ==
				    0)
					continue;
				usage_optarg_insert_str(&cur, &n, &cur_col,
				    MSG_ORIG(MSG_STR_SP_BAR_SP));
				usage_optarg_insert_item(&tmp_item,
				    &cur, &n, &cur_col);

				/*
				 * Add it to the mask of seen options.
				 * This will keep us from showing it twice.
				 */
				optmask |= tmp_item.oai_idmask;
			}
		}
		if (use_bkt)
			usage_optarg_insert_ch(']', &cur, &n, &cur_col);

		/*
		 * If alternation was not shown above (non-zero exclusion mask)
		 * then the elipses for multiple copies are shown outside
		 * any [] brackets.
		 */
		if ((item.oai_excmask == 0) &&
		    (item.oai_flags & ELFEDIT_CMDOA_F_MULT))
			usage_optarg_insert_str(&cur, &n, &cur_col,
			    MSG_ORIG(MSG_STR_ELIPSES));

	}

	*buf = cur;
	*buf_n = n;
	*buf_cur_col = cur_col;
}



/*
 * Format the usage string for a command into a static buffer and
 * return the pointer to the user. The resultant string is valid
 * until the next call to this routine, and which point it
 * will be overwritten or the memory is freed.
 *
 * entry:
 *	mod, cmd - Module and command definitions for command to be described
 *	wrap_str - NULL, or string to be used to indent when
 *		lines are wrapped. If NULL, no wrapping is done, and
 *		all output is on a single line.
 *	cur_col - Starting column at which the string will be displayed.
 *		Ignored if wrap_str is NULL.
 */
const char *
elfedit_format_command_usage(elfeditGC_module_t *mod, elfeditGC_cmd_t *cmd,
    const char *wrap_str, size_t cur_col)
{

	/*
	 * A static buffer, which is grown as needed to accomodate
	 * the maximum usage string seen.
	 */
	static STRBUF str;

	elfedit_cmd_optarg_t	*optarg;
	size_t			len, n, elipses_len;
	char			*cur;
	elfedit_optarg_item_t	item;

	/*
	 * Estimate a worst case size for the usage string:
	 *	- module name
	 *	- lengths of the strings
	 *	- every option or argument is enclosed in brackets
	 *	- space in between each item, with an alternation (" | ")
	 *	- elipses will be displayed with each option and argument
	 */
	n = strlen(mod->mod_name) + strlen(cmd->cmd_name[0]) + 6;
	elipses_len = strlen(MSG_ORIG(MSG_STR_ELIPSES));
	if ((optarg = cmd->cmd_opt) != NULL)
		while (optarg->oa_name != NULL) {
			elfedit_next_optarg(&optarg, &item);
			n += strlen(item.oai_name) + 5 + elipses_len;
		}
	if ((optarg = cmd->cmd_args) != NULL)
		while (optarg->oa_name != NULL) {
			elfedit_next_optarg(&optarg, &item);
			n += strlen(item.oai_name) + 5 + elipses_len;
		}
	n++;			/* Null termination */

	/*
	 * If wrapping lines, we insert a newline and then wrap_str
	 * every USAGE_WRAP_COL characters.
	 */
	if (wrap_str != NULL)
		n += ((n + USAGE_WRAP_COL) / USAGE_WRAP_COL) *
		    (strlen(wrap_str) + 1);

	strbuf_ensure_size(&str, n);

	/* Command name */
	cur = str.buf;
	n = str.n;
	if (strcmp(mod->mod_name, MSG_ORIG(MSG_MOD_SYS)) == 0)
		len = snprintf(cur, n, MSG_ORIG(MSG_FMT_SYSCMD),
		    cmd->cmd_name[0]);
	else
		len = snprintf(cur, n, MSG_ORIG(MSG_FMT_MODCMD),
		    mod->mod_name, cmd->cmd_name[0]);
	cur += len;
	n -= len;
	cur_col += len;

	if (cmd->cmd_opt != NULL)
		usage_optarg(str.n, &cur, &n, &cur_col, cmd->cmd_opt,
		    1, wrap_str);
	if (cmd->cmd_args != NULL)
		usage_optarg(str.n, &cur, &n, &cur_col, cmd->cmd_args,
		    0, wrap_str);

	return (str.buf);
}

/*
 * Wrapper on elfedit_msg() that issues an ELFEDIT_MSG_USAGE
 * error giving usage information for the command currently
 * referenced by state.cur_cmd.
 */
void
elfedit_command_usage(void)
{
	elfedit_msg(ELFEDIT_MSG_CMDUSAGE, MSG_INTL(MSG_USAGE_CMD),
	    elfedit_format_command_usage(state.cur_cmd->ucmd_mod,
	    state.cur_cmd->ucmd_cmd, NULL, 0));
}


/*
 * This function allows the loadable modules to get the command line
 * flags.
 */
elfedit_flag_t
elfedit_flags(void)
{
	return (state.flags);
}

/*
 * This function is used to register a per-command invocation output style
 * that will momentarily override the global output style for the duration
 * of the current command. This function must only be called by an
 * active command.
 *
 * entry:
 *	str - One of the valid strings for the output style
 */
void
elfedit_set_cmd_outstyle(const char *str)
{
	if ((state.cur_cmd != NULL) && (str != NULL)) {
		if (elfedit_atooutstyle(str, &state.cur_cmd->ucmd_ostyle) == 0)
			elfedit_msg(ELFEDIT_MSG_ERR,
			    MSG_INTL(MSG_ERR_BADOSTYLE), str);
		state.cur_cmd->ucmd_ostyle_set = 1;
	}
}

/*
 * This function allows the loadable modules to get the output style.
 */
elfedit_outstyle_t
elfedit_outstyle(void)
{
	/*
	 * If there is an active  per-command output style,
	 * return it.
	 */
	if ((state.cur_cmd != NULL) && (state.cur_cmd->ucmd_ostyle_set))
		return (state.cur_cmd->ucmd_ostyle);


	return (state.outstyle);
}

/*
 * Return the command descriptor of the currently executing command.
 * For use only by the modules or code called by the modules.
 */
elfeditGC_cmd_t *
elfedit_curcmd(void)
{
	return (state.cur_cmd->ucmd_cmd);
}

/*
 * Build a dynamically allocated elfedit_obj_state_t struct that
 * contains a cache of the ELF file contents. This pre-chewed form
 * is fed to each command, reducing the amount of ELF boilerplate
 * code each command needs to contain.
 *
 * entry:
 *	file - Name of file to process
 *
 * exit:
 *	Fills state.elf with the necessary information for the open file.
 *
 * note: The resulting elfedit_obj_state_t is allocated from a single
 *	piece of memory, such that a single call to free() suffices
 *	to release it as well as any memory it references.
 */
static void
init_obj_state(const char *file)
{
	int	fd;
	Elf	*elf;
	int	open_flag;

	/*
	 * In readonly mode, we open the file readonly so that it is
	 * impossible to modify the file by accident. This also allows
	 * us to access readonly files, perhaps in a case where we don't
	 * intend to change it.
	 *
	 * We always use ELF_C_RDWR with elf_begin(), even in a readonly
	 * session. This allows us to modify the in-memory image, which
	 * can be useful when examining a file, even though we don't intend
	 * to modify the on-disk data. The file is not writable in
	 * this case, and we don't call elf_update(), so it is safe to do so.
	 */
	open_flag = ((state.flags & ELFEDIT_F_READONLY) ? O_RDONLY : O_RDWR);
	if ((fd = open(file, open_flag)) == -1) {
		int err = errno;
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTOPNFILE),
		    file, strerror(err));
	}
	(void) elf_version(EV_CURRENT);
	elf = elf_begin(fd, ELF_C_RDWR, NULL);
	if (elf == NULL) {
		(void) close(fd);
		elfedit_elferr(file, MSG_ORIG(MSG_ELF_BEGIN));
		/*NOTREACHED*/
	}

	/* We only handle standalone ELF files */
	switch (elf_kind(elf)) {
	case ELF_K_AR:
		(void) close(fd);
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_NOAR), file);
		break;
	case ELF_K_ELF:
		break;
	default:
		(void) close(fd);
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_UNRECELFFILE),
		    file);
		break;
	}

	/*
	 * Tell libelf that we take responsibility for object layout.
	 * Otherwise, it will compute "proper" values for layout and
	 * alignment fields, and these values can overwrite the values
	 * set in the elfedit session. We are modifying existing
	 * objects --- the layout concerns have already been dealt
	 * with when the object was built.
	 */
	(void) elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT);

	/* Fill in state.elf.obj_state */
	state.elf.elfclass = gelf_getclass(elf);
	switch (state.elf.elfclass) {
	case ELFCLASS32:
		elfedit32_init_obj_state(file, fd, elf);
		break;
	case ELFCLASS64:
		elfedit64_init_obj_state(file, fd, elf);
		break;
	default:
		(void) close(fd);
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_BADELFCLASS),
		    file);
		break;
	}
}


#ifdef DEBUG_MODULE_LIST
/*
 * Debug routine. Dump the module list to stdout.
 */
static void
dbg_module_list(char *title)
{
	MODLIST_T *m;

	printf("<MODULE LIST: %s>\n", title);
	for (m = state.modlist; m != NULL; m = m->next) {
		printf("Module: >%s<\n", m->mod->mod_name);
		printf("    hdl:  %llx\n", m->dl_hdl);
		printf("    path: >%s<\n", m->path ? m->path : "<builtin>");
	}
	printf("<END OF MODULE LIST>\n");
}
#endif


/*
 * Search the module list for the named module.
 *
 * entry:
 *	name - Name of module to find
 *	insdef - Address of variable to receive address of predecessor
 *		node to the desired one.
 *
 * exit:
 *	If the module is it is found, this routine returns the pointer to
 *	its MODLIST_T structure. *insdef references the predecessor node, or
 *	is NULL if the found item is at the head of the list.
 *
 *	If the module is not found, NULL is returned. *insdef references
 *	the predecessor node of the position where an entry for this module
 *	would be placed, or NULL if it would go at the beginning.
 */
static MODLIST_T *
module_loaded(const char *name, MODLIST_T **insdef)
{
	MODLIST_T	*moddef;
	int		cmp;

	*insdef = NULL;
	moddef = state.modlist;
	if (moddef != NULL) {
		cmp = strcasecmp(name, moddef->ml_mod->mod_name);
		if (cmp == 0) {		/* Desired module is first in list */
			return (moddef);
		} else if (cmp > 0) {	/* cmp > 0: Insert in middle/end */
			*insdef = moddef;
			moddef = moddef->ml_next;
			cmp = -1;
			while (moddef && (cmp < 0)) {
				cmp = strcasecmp(moddef->ml_mod->mod_name,
				    name);
				if (cmp == 0)
					return (moddef);
				if (cmp < 0) {
					*insdef = moddef;
					moddef = (*insdef)->ml_next;
				}
			}
		}
	}

	return (NULL);
}


/*
 * Determine if a file is a sharable object based on its file path.
 * If path ends in a .so, followed optionally by a period and 1 or more
 * digits, we say that it is and return a pointer to the first character
 * of the suffix. Otherwise NULL is returned.
 */
static const char *
path_is_so(const char *path)
{
	int		dotso_len;
	const char	*tail;
	size_t		len;

	len = strlen(path);
	if (len == 0)
		return (NULL);
	tail = path + len;
	if (isdigit(*(tail - 1))) {
		while ((tail > path) && isdigit(*(tail - 1)))
			tail--;
		if ((tail <= path) || (*tail != '.'))
			return (NULL);
	}
	dotso_len = strlen(MSG_ORIG(MSG_STR_DOTSO));
	if ((tail - path) < dotso_len)
		return (NULL);
	tail -= dotso_len;
	if (strncmp(tail, MSG_ORIG(MSG_STR_DOTSO), dotso_len) == 0)
		return (tail);

	return (NULL);
}


/*
 * Locate the start of the unsuffixed file name within path. Returns pointer
 * to first character of that name in path.
 *
 * entry:
 *	path - Path to be examined.
 *	tail - NULL, or pointer to position at tail of path from which
 *		the search for '/' characters should start. If NULL,
 *		strlen() is used to locate the end of the string.
 *	buf - NULL, or buffer to receive a copy of the characters that
 *		lie between the start of the filename and tail.
 *	bufsize - sizeof(buf)
 *
 * exit:
 *	The pointer to the first character of the unsuffixed file name
 *	within path is returned. If buf is non-NULL, the characters
 *	lying between that point and tail (or the end of path if tail
 *	is NULL) are copied into buf.
 */
static const char *
elfedit_basename(const char *path, const char *tail, char *buf, size_t bufsiz)
{
	const char 	*s;

	if (tail == NULL)
		tail = path + strlen(path);
	s = tail;
	while ((s > path) && (*(s - 1) != '/'))
		s--;
	if (buf != NULL)
		elfedit_strnbcpy(buf, s, tail - s, bufsiz);
	return (s);
}


/*
 * Issue an error on behalf of load_module(), taking care to release
 * resources that routine may have aquired:
 *
 * entry:
 *	moddef - NULL, or a module definition to be released via free()
 *	dl_hdl - NULL, or a handle to a sharable object to release via
 *		dlclose().
 *	dl_path - If dl_hdl is non-NULL, the path to the sharable object
 *		file that was loaded.
 *	format - A format string to pass to elfedit_msg(), containing
 *		no more than (3) %s format codes, and no other format codes.
 *	[s1-s4] - Strings to pass to elfedit_msg() to satisfy the four
 *		allowed %s codes in format. Should be set to NULL if the
 *		format string does not need them.
 *
 * note:
 *	This routine makes a copy of the s1-s4 strings before freeing any
 *	memory or unmapping the sharable library. It is therefore safe to
 *	use strings from moddef, or from the sharable library (which will
 *	be unmapped) to satisfy the other arguments s1-s4.
 */
static void
load_module_err(MODLIST_T *moddef, void *dl_hdl, const char *dl_path,
    const char *format, const char *s1, const char *s2, const char *s3,
    const char *s4)
{
#define	SCRBUFSIZE (PATH_MAX + 256)   /* A path, plus some extra */

	char s1_buf[SCRBUFSIZE];
	char s2_buf[SCRBUFSIZE];
	char s3_buf[SCRBUFSIZE];
	char s4_buf[SCRBUFSIZE];

	/*
	 * The caller may provide strings for s1-s3 that are from
	 * moddef. If we free moddef, the printf() will die on access
	 * to free memory. We could push back on the user and force
	 * each call to carefully make copies of such data. However, this
	 * is an easy case to miss. Furthermore, this is an error case,
	 * and machine efficiency is not the main issue. We therefore make
	 * copies of the s1-s3 strings here into auto variables, and then
	 * use those copies. The user is freed from worrying about it.
	 *
	 * We use oversized stack based buffers instead of malloc() to
	 * reduce the number of ways that things can go wrong while
	 * reporting the error.
	 */
	if (s1 != NULL)
		(void) strlcpy(s1_buf, s1, sizeof (s1_buf));
	if (s2 != NULL)
		(void) strlcpy(s2_buf, s2, sizeof (s2_buf));
	if (s3 != NULL)
		(void) strlcpy(s3_buf, s3, sizeof (s3_buf));
	if (s4 != NULL)
		(void) strlcpy(s4_buf, s4, sizeof (s4_buf));


	if (moddef != NULL)
		free(moddef);

	if ((dl_hdl != NULL) && (dlclose(dl_hdl) != 0))
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTDLCLOSE),
		    dl_path, dlerror());

	elfedit_msg(ELFEDIT_MSG_ERR, format, s1_buf, s2_buf, s3_buf, s4_buf);
#undef	SCRBUFSIZE
}


/*
 * Load a module sharable object for load_module().
 *
 * entry:
 *	path - Path of file to open
 *	moddef - If this function issues a non-returning error, it will
 *		first return the memory referenced by moddef. This argument
 *		is not used otherwise.
 *	must_exist - If True, we consider it to be an error if the file given
 *		by path does not exist. If False, no error is issued
 *		and a NULL value is quietly returned.
 *
 * exit:
 *	Returns a handle to the loaded object on success, or NULL if no
 *	file was loaded.
 */
static void *
load_module_dlopen(const char *path, MODLIST_T *moddef, int must_exist)
{
	int	fd;
	void	*hdl;

	/*
	 * If the file is not required to exist, and it doesn't, then
	 * we want to quietly return without an error.
	 */
	if (!must_exist) {
		fd = open(path, O_RDONLY);
		if (fd >= 0) {
			(void) close(fd);
		} else if (errno == ENOENT) {
			return (NULL);
		}
	}

	if ((hdl = dlopen(path, RTLD_LAZY|RTLD_FIRST)) == NULL)
		load_module_err(moddef, NULL, NULL,
		    MSG_INTL(MSG_ERR_CNTDLOPEN), path, dlerror(), NULL, NULL);

	return (hdl);
}


/*
 * Sanity check option arguments to prevent common errors. The rest of
 * elfedit assumes these tests have been done, and does not check
 * again.
 */
static void
validate_optarg(elfedit_cmd_optarg_t *optarg, int isopt, MODLIST_T *moddef,
    const char *mod_name, const char *cmd_name,
    void *dl_hdl, const char *dl_path)
{
#define	FAIL(_msg) errmsg = _msg; goto fail

	Msg errmsg;
	elfedit_cmd_oa_mask_t	optmask = 0;

	for (; optarg->oa_name != NULL; optarg++) {
		/*
		 * If ELFEDIT_CMDOA_F_INHERIT is set:
		 *	- oa_name must be a value in the range of
		 *		known ELFEDIT_STDOA_ values.
		 *	- oa_help must be NULL
		 *	- ELFEDIT_CMDOA_F_INHERIT must be the only flag set
		 */
		if (optarg->oa_flags & ELFEDIT_CMDOA_F_INHERIT) {
			if ((((uintptr_t)optarg->oa_name) >
			    ELFEDIT_NUM_STDOA) ||
			    (optarg->oa_help != 0) ||
			    (optarg->oa_flags != ELFEDIT_CMDOA_F_INHERIT))
				/*
				 * Can't use FAIL --- oa_name is not a valid
				 * string, and load_module_err() looks at args.
				 */
				load_module_err(moddef, dl_hdl, dl_path,
				    MSG_INTL(MSG_ERR_BADSTDOA), dl_path,
				    mod_name, cmd_name, NULL);
			continue;
		}

		if (isopt) {
			/*
			 * Option name must start with a '-', and must
			 * have at one following character.
			 */
			if (optarg->oa_name[0] != '-') {
				/* MSG_INTL(MSG_ERR_OPT_MODPRE) */
				FAIL(MSG_ERR_OPT_MODPRE);
			}
			if (optarg->oa_name[1] == '\0') {
				/* MSG_INTL(MSG_ERR_OPT_MODLEN) */
				FAIL(MSG_ERR_OPT_MODLEN);
			}

			/*
			 * oa_idmask must be 0, or it must have a single
			 * bit set (a power of 2).oa_excmask must be 0
			 * if oa_idmask is 0
			 */
			if (optarg->oa_idmask == 0) {
				if (optarg->oa_excmask != 0) {
					/* MSG_INTL(MSG_ERR_OPT_EXCMASKN0) */
					FAIL(MSG_ERR_OPT_EXCMASKN0);
				}
			} else {
				if (elfedit_bits_set(optarg->oa_idmask,
				    sizeof (optarg->oa_idmask)) != 1) {
					/* MSG_INTL(MSG_ERR_OPT_IDMASKPOW2) */
					FAIL(MSG_ERR_OPT_IDMASKPOW2);
				}

				/* Non-zero idmask must be unique */
				if ((optarg->oa_idmask & optmask) != 0) {
					/* MSG_INTL(MSG_ERR_OPT_IDMASKUNIQ) */
					FAIL(MSG_ERR_OPT_IDMASKUNIQ);
				}

				/* Add this one to the overall mask */
				optmask |= optarg->oa_idmask;
			}
		} else {
			/*
			 * Argument name cannot start with a'-', and must
			 * not be a null string.
			 */
			if (optarg->oa_name[0] == '-') {
				/* MSG_INTL(MSG_ERR_ARG_MODPRE) */
				FAIL(MSG_ERR_ARG_MODPRE);
			}
			if (optarg->oa_name[1] == '\0') {
				/* MSG_INTL(MSG_ERR_ARG_MODLEN) */
				FAIL(MSG_ERR_ARG_MODLEN);
			}


			/* oa_idmask and oa_excmask must both be 0 */
			if ((optarg->oa_idmask != 0) ||
			    (optarg->oa_excmask != 0)) {
				/* MSG_INTL(MSG_ERR_ARG_MASKNOT0) */
				FAIL(MSG_ERR_ARG_MASKNOT0);
			}

		}

		/*
		 * If it takes a value, make sure that we are
		 * processing options, because CMDOA_F_VALUE is not
		 * allowed for plain arguments. Then check the following
		 * item in the list:
		 *	- There must be a following item.
		 *	- oa_name must be non-NULL. This is the only field
		 *		that is used by elfedit.
		 *	- oa_help, oa_flags, oa_idmask, and oa_excmask
		 *		must be 0.
		 */
		if (optarg->oa_flags & ELFEDIT_CMDOA_F_VALUE) {
			elfedit_cmd_optarg_t *oa1 = optarg + 1;

			if (!isopt) {
				/* MSG_INTL(MSG_ERR_ARG_CMDOA_VAL) */
				FAIL(MSG_ERR_ARG_CMDOA_VAL);
			}

			if ((optarg + 1)->oa_name == NULL) {
				/* MSG_INTL(MSG_ERR_BADMODOPTVAL) */
				FAIL(MSG_ERR_BADMODOPTVAL);
			}

			if (oa1->oa_name == NULL) {
				/* MSG_INTL(MSG_ERR_CMDOA_VALNAM) */
				FAIL(MSG_ERR_CMDOA_VALNAM);
			}
			if ((oa1->oa_help != NULL) || (oa1->oa_flags != 0) ||
			    (oa1->oa_idmask != 0) || (oa1->oa_excmask != 0)) {
				/* MSG_INTL(MSG_ERR_CMDOA_VALNOT0) */
				FAIL(MSG_ERR_CMDOA_VALNOT0);
			}
			optarg++;
		}
	}


	return;

fail:
	load_module_err(moddef, dl_hdl, dl_path, MSG_INTL(errmsg),
	    dl_path, mod_name, cmd_name, optarg->oa_name);
}

/*
 * Look up the specified module, loading the module if necessary,
 * and return its definition, or NULL on failure.
 *
 * entry:
 *	name - Name of module to load. If name contains a '/' character or has
 *		a ".so" suffix, then it is taken to be an absolute file path,
 *		and is used directly as is. If name does not contain a '/'
 *		character, then we look for it against the locations in
 *		the module path, addint the '.so' suffix, and taking the first
 *		one we find.
 *	must_exist - If True, we consider it to be an error if we are unable
 *		to locate a file to load and the module does not already exist.
 *		If False, NULL is returned quietly in this case.
 *	allow_abs - True if absolute paths are allowed. False to disallow
 *		them.
 *
 * note:
 *	If the path is absolute, then we load the file and take the module
 *	name from the data returned by its elfedit_init() function. If a
 *	module of that name is already loaded, it is unloaded and replaced
 *	with the new one.
 *
 *	If the path is non absolute, then we check to see if the module has
 *	already been loaded, and if so, we return that module definition.
 *	In this case, nothing new is loaded. If the module has not been loaded,
 *	we search the path for it and load it. If the module name provided
 *	by the elfedit_init() function does not match the name of the file,
 *	an error results.
 */
elfeditGC_module_t *
elfedit_load_module(const char *name, int must_exist, int allow_abs)
{
	elfedit_init_func_t	*init_func;
	elfeditGC_module_t	*mod;
	MODLIST_T		*moddef, *insdef;
	const char		*path;
	char			path_buf[PATH_MAX + 1];
	void			*hdl;
	size_t			i;
	int			is_abs_path;
	elfeditGC_cmd_t		*cmd;

	/*
	 * If the name includes a .so suffix, or has any '/' characters,
	 * then it is an absolute path that we use as is to load the named
	 * file. Otherwise, we iterate over the path, adding the .so suffix
	 * and load the first file that matches.
	 */
	is_abs_path = (path_is_so(name) != NULL) ||
	    (name != elfedit_basename(name, NULL, NULL, 0));

	if (is_abs_path && !allow_abs)
		load_module_err(NULL, NULL, NULL,
		    MSG_INTL(MSG_ERR_UNRECMOD), name, NULL, NULL, NULL);

	/*
	 * If this is a non-absolute path, search for the module already
	 * having been loaded, and return it if so.
	 */
	if (!is_abs_path) {
		moddef = module_loaded(name, &insdef);
		if (moddef != NULL)
			return (moddef->ml_mod);
		/*
		 * As a result of module_loaded(), insdef now contains the
		 * immediate predecessor node for the new one, or NULL if
		 * it goes at the front. In the absolute-path case, we take
		 * care of this below, after the sharable object is loaded.
		 */
	}

	/*
	 * malloc() a module definition block before trying to dlopen().
	 * Doing things in the other order can cause the dlopen()'d object
	 * to leak: If elfedit_malloc() fails, it can cause a jump to the
	 * outer command loop without returning to the caller. Hence,
	 * there will be no opportunity to clean up. Allocaing the module
	 * first allows us to free it if necessary.
	 */
	moddef = elfedit_malloc(MSG_INTL(MSG_ALLOC_MODDEF),
	    sizeof (*moddef) + PATH_MAX + 1);
	moddef->ml_path = ((char *)moddef) + sizeof (*moddef);

	if (is_abs_path) {
		path = name;
		hdl = load_module_dlopen(name, moddef, must_exist);
	} else {
		hdl = NULL;
		path = path_buf;
		for (i = 0; i < state.modpath.n; i++) {
			if (snprintf(path_buf, sizeof (path_buf),
			    MSG_ORIG(MSG_FMT_BLDSOPATH), state.modpath.seg[i],
			    name) > sizeof (path_buf))
				load_module_err(moddef, NULL, NULL,
				    MSG_INTL(MSG_ERR_PATHTOOLONG),
				    state.modpath.seg[i], name, NULL, NULL);
			hdl = load_module_dlopen(path, moddef, 0);
		}
		if (must_exist && (hdl == NULL))
			load_module_err(moddef, NULL, NULL,
			    MSG_INTL(MSG_ERR_UNRECMOD), name, NULL, NULL, NULL);
	}

	if (hdl == NULL) {
		free(moddef);
		return (NULL);
	}

	if (state.elf.elfclass == ELFCLASS32) {
		init_func = (elfedit_init_func_t *)
		    dlsym(hdl, MSG_ORIG(MSG_STR_ELFEDITINIT32));
	} else {
		init_func = (elfedit_init_func_t *)
		    dlsym(hdl, MSG_ORIG(MSG_STR_ELFEDITINIT64));
	}
	if (init_func == NULL)
		load_module_err(moddef, hdl, path,
		    MSG_INTL(MSG_ERR_SONOTMOD), path, NULL, NULL, NULL);

	/*
	 * Note that the init function will be passing us an
	 * elfedit[32|64]_module_t pointer, which we cast to the
	 * generic module pointer type in order to be able to manage
	 * either type with one set of code.
	 */
	if (!(mod = (elfeditGC_module_t *)(* init_func)(ELFEDIT_VER_CURRENT)))
		load_module_err(moddef, hdl, path,
		    MSG_INTL(MSG_ERR_BADMODLOAD), path, NULL, NULL, NULL);

	/*
	 * Enforce some rules, to help module developers:
	 *	- The primary name of a command must not be
	 *		the empty string ("").
	 *	- Options must start with a '-' followed by at least
	 *		one character.
	 *	- Arguments and options must be well formed.
	 */
	for (cmd = mod->mod_cmds; cmd->cmd_func != NULL; cmd++) {
		if (**cmd->cmd_name == '\0')
			load_module_err(moddef, hdl, path,
			    MSG_INTL(MSG_ERR_NULLPRICMDNAM), mod->mod_name,
			    NULL, NULL, NULL);

		if (cmd->cmd_args != NULL)
			validate_optarg(cmd->cmd_args, 0, moddef, mod->mod_name,
			    cmd->cmd_name[0], hdl, path);
		if (cmd->cmd_opt != NULL)
			validate_optarg(cmd->cmd_opt, 1, moddef, mod->mod_name,
			    cmd->cmd_name[0], hdl, path);
	}

	/*
	 * Check the name the module provides. How we handle this depends
	 * on whether the path is absolute or the result of a path search.
	 */
	if (is_abs_path) {
		MODLIST_T *old_moddef = module_loaded(mod->mod_name, &insdef);

		if (old_moddef != NULL) {	/* Replace existing */
			free(moddef);		/* Rare case: Don't need it */
			/*
			 * Be sure we don't unload builtin modules!
			 * These have a NULL dl_hdl field.
			 */
			if (old_moddef->ml_dl_hdl == NULL)
				load_module_err(NULL, hdl, path,
				    MSG_INTL(MSG_ERR_CNTULSMOD),
				    old_moddef->ml_mod->mod_name, NULL,
				    NULL, NULL);

			/* Unload existing */
			if (dlclose(old_moddef->ml_dl_hdl) != 0)
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_CNTDLCLOSE),
				    old_moddef->ml_path, dlerror());
			elfedit_msg(ELFEDIT_MSG_DEBUG,
			    MSG_INTL(MSG_DEBUG_MODUNLOAD),
			    old_moddef->ml_mod->mod_name, old_moddef->ml_path);
			old_moddef->ml_mod = mod;
			old_moddef->ml_dl_hdl = hdl;
			(void) strlcpy((char *)old_moddef->ml_path, path,
			    PATH_MAX + 1);
			elfedit_msg(ELFEDIT_MSG_DEBUG,
			    MSG_INTL(MSG_DEBUG_MODLOAD),
			    old_moddef->ml_mod->mod_name, path);
			return (old_moddef->ml_mod);
		}
		/*
		 * insdef now contains the insertion point for the absolute
		 * path case.
		 */
	} else {
		/* If the names don't match, then error */
		if (strcasecmp(name, mod->mod_name) != 0)
			load_module_err(moddef, hdl, path,
			    MSG_INTL(MSG_ERR_BADMODNAME),
			    mod->mod_name, name, path, NULL);
	}

	/*
	 * Link module into the module list. If insdef is NULL,
	 * it goes at the head. If insdef is non-NULL, it goes immediately
	 * after
	 */
	if (insdef == NULL) {
		moddef->ml_next = state.modlist;
		state.modlist = moddef;
	} else {
		moddef->ml_next = insdef->ml_next;
		insdef->ml_next = moddef;
	}
	moddef->ml_mod = mod;
	moddef->ml_dl_hdl = hdl;
	(void) strlcpy((char *)moddef->ml_path, path, PATH_MAX + 1);

	elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_MODLOAD),
	    moddef->ml_mod->mod_name, path);

	return (moddef->ml_mod);
}


/*
 * Unload the specified module
 */
void
elfedit_unload_module(const char *name)
{
	MODLIST_T	*moddef, *insdef;

	moddef = module_loaded(name, &insdef);
	if (moddef == NULL)
		return;

	/* Built in modules cannot be unloaded. They have a NULL dl_hdl field */
	if (moddef->ml_dl_hdl == NULL)
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTULSMOD),
		    moddef->ml_mod->mod_name);

	/*
	 * When we unload it, the name string goes with it. So
	 * announce it while we still can without having to make a copy.
	 */
	elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_MODUNLOAD),
	    moddef->ml_mod->mod_name, moddef->ml_path);

	/*
	 * Close it before going further. On failure, we'll jump, and the
	 * record will remain in the module list. On success,
	 * we'll retain control, and can safely remove it.
	 */
	if (dlclose(moddef->ml_dl_hdl) != 0)
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTDLCLOSE),
		    moddef->ml_path, dlerror());

	/* Unlink the record from the module list */
	if (insdef == NULL)
		state.modlist = moddef->ml_next;
	else
		insdef->ml_next = moddef->ml_next;

	/* Release the memory */
	free(moddef);
}


/*
 * Load all sharable objects found in the specified directory.
 *
 * entry:
 *	dirpath - Path of directory to process.
 *	must_exist - If True, it is an error if diropen() fails to open
 *		the given directory. Of False, we quietly ignore it and return.
 *	abs_path - If True, files are loaded using their literal paths.
 *		If False, their module name is extracted from the dirpath
 *		and a path based search is used to locate it.
 */
void
elfedit_load_moddir(const char *dirpath, int must_exist, int abs_path)
{
	char		path[PATH_MAX + 1];
	DIR		*dir;
	struct dirent	*dp;
	const char 	*tail;

	dir = opendir(dirpath);
	if (dir == NULL) {
		int err = errno;

		if (!must_exist && (err == ENOENT))
			return;
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTOPNDIR),
		    dirpath, strerror(err));
		/*NOTREACHED*/
	}

	while (dp = readdir(dir)) {
		if ((tail = path_is_so(dp->d_name)) != NULL) {
			if (abs_path) {
				(void) snprintf(path, sizeof (path),
				    MSG_ORIG(MSG_FMT_BLDPATH), dirpath,
				    dp->d_name);
			} else {
				(void) elfedit_basename(dp->d_name, tail,
				    path, sizeof (path));
			}
			(void) elfedit_load_module(path, must_exist, 1);
		}
	}
	(void) closedir(dir);
}


/*
 * Follow the module load path, and load the first module found for each
 * given name.
 */
void
elfedit_load_modpath(void)
{
	size_t		i;

	for (i = 0; i < state.modpath.n; i++)
		elfedit_load_moddir(state.modpath.seg[i], 0, 0);
}

/*
 * Given a module definition, look for the specified command.
 * Returns the command if found, and NULL otherwise.
 */
static elfeditGC_cmd_t *
find_cmd(elfeditGC_module_t *mod, const char *name)
{
	elfeditGC_cmd_t *cmd;
	const char **cmd_name;

	for (cmd = mod->mod_cmds; cmd->cmd_func != NULL; cmd++)
		for (cmd_name = cmd->cmd_name; *cmd_name; cmd_name++)
			if (strcasecmp(name, *cmd_name) == 0) {
				if (cmd_name != cmd->cmd_name)
					elfedit_msg(ELFEDIT_MSG_DEBUG,
					    MSG_INTL(MSG_DEBUG_CMDALIAS),
					    mod->mod_name, *cmd_name,
					    mod->mod_name, *cmd->cmd_name);
				return (cmd);
			}

	return (NULL);
}


/*
 * Given a command name, return its command definition.
 *
 * entry:
 *	name - Command to be looked up
 *	must_exist - If True, we consider it to be an error if the command
 *		does not exist. If False, NULL is returned quietly in
 *		this case.
 *	mod_ret - NULL, or address of a variable to receive the
 *		module definition block of the module containing
 *		the command.
 *
 * exit:
 *	On success, returns a pointer to the command definition, and
 *	if mod_ret is non-NULL, *mod_ret receives a pointer to the
 *	module definition. On failure, must_exist determines the
 *	action taken: If must_exist is True, an error is issued and
 *	control does not return to the caller. If must_exist is False,
 *	NULL is quietly returned.
 *
 * note:
 *	A ':' in name is used to delimit the module and command names.
 *	If it is omitted, or if it is the first non-whitespace character
 *	in the name, then the built in sys: module is implied.
 */
elfeditGC_cmd_t *
elfedit_find_command(const char *name, int must_exist,
    elfeditGC_module_t **mod_ret)
{
	elfeditGC_module_t	*mod;
	const char		*mod_str;
	const char		*cmd_str;
	char			mod_buf[ELFEDIT_MAXMODNAM + 1];
	size_t			n;
	elfeditGC_cmd_t		*cmd;


	cmd_str = strstr(name, MSG_ORIG(MSG_STR_COLON));
	if (cmd_str == NULL) {		/* No module name -> sys: */
		mod_str = MSG_ORIG(MSG_MOD_SYS);
		cmd_str = name;
	} else if (cmd_str == name) {	/* Empty module name -> sys: */
		mod_str = MSG_ORIG(MSG_MOD_SYS);
		cmd_str++;		/* Skip the colon */
	} else {			/* Have both module and command */
		n = cmd_str - name;
		if (n >= sizeof (mod_buf)) {
			if (must_exist)
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_MODNAMTOOLONG), name);
			return (NULL);
		}
		(void) strlcpy(mod_buf, name, n + 1);
		mod_str = mod_buf;
		cmd_str++;
	}

	/* Lookup/load module. Won't return on error */
	mod = elfedit_load_module(mod_str, must_exist, 0);
	if (mod == NULL)
		return (NULL);

	/* Locate the command */
	cmd = find_cmd(mod, cmd_str);
	if (cmd == NULL) {
		if (must_exist) {
			/*
			 * Catch empty command in order to provide
			 * a better error message.
			 */
			if (*cmd_str == '\0') {
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_MODNOCMD), mod_str);
			} else {
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_UNRECCMD),
				    mod_str, cmd_str);
			}
		}
	} else {
		if (mod_ret != NULL)
			*mod_ret = mod;
	}
	return (cmd);
}


/*
 * Release all user command blocks found on state.ucmd
 */
static void
free_user_cmds(void)
{
	USER_CMD_T *next;

	while (state.ucmd.list) {
		next = state.ucmd.list->ucmd_next;
		free(state.ucmd.list);
		state.ucmd.list = next;
	}
	state.ucmd.tail = NULL;
	state.ucmd.n = 0;
	state.cur_cmd = NULL;
}


/*
 * Process all user command blocks found on state.ucmd, and then
 * remove them from the list.
 */
static void
dispatch_user_cmds()
{
	USER_CMD_T		*ucmd;
	elfedit_cmdret_t	cmd_ret;

	ucmd = state.ucmd.list;
	if (ucmd) {
		/* Do them, in order */
		for (; ucmd; ucmd = ucmd->ucmd_next) {
			state.cur_cmd = ucmd;
			if (!state.msg_jbuf.active)
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_EXECCMD),
				    ucmd->ucmd_orig_str);
			/*
			 * The cmd_func field is the generic definition.
			 * We need to cast it to the type that matches
			 * the proper ELFCLASS before calling it.
			 */
			if (state.elf.elfclass == ELFCLASS32) {
				elfedit32_cmd_func_t *cmd_func =
				    (elfedit32_cmd_func_t *)
				    ucmd->ucmd_cmd->cmd_func;

				cmd_ret = (* cmd_func)(state.elf.obj_state.s32,
				    ucmd->ucmd_argc, ucmd->ucmd_argv);
			} else {
				elfedit64_cmd_func_t *cmd_func =
				    (elfedit64_cmd_func_t *)
				    ucmd->ucmd_cmd->cmd_func;

				cmd_ret = (* cmd_func)(state.elf.obj_state.s64,
				    ucmd->ucmd_argc, ucmd->ucmd_argv);
			}
			state.cur_cmd = NULL;
			/* If a pager was started, wrap it up */
			elfedit_pager_cleanup();

			switch (cmd_ret) {
			case ELFEDIT_CMDRET_MOD_OS_MACH:
				/*
				 * Inform the elfconst module that the machine
				 * or osabi has has changed. It may be necessary
				 * to fetch new strings from libconv.
				 */
				state.elf.elfconst_ehdr_change = 1;
				/*FALLTHROUGH*/
			case ELFEDIT_CMDRET_MOD:
				/*
				 * Command modified the output ELF image,
				 * mark the file as needing a flush to disk.
				 */
				state.file.dirty = 1;
				break;
			case ELFEDIT_CMDRET_FLUSH:
				/*
				 * Command flushed the output file,
				 * clear the dirty bit.
				 */
				state.file.dirty = 0;
			}
		}
		free_user_cmds();
	}
}


/*
 * Given the pointer to the character following a '\' character in
 * a C style literal, return the ASCII character code it represents,
 * and advance the string pointer to the character following the last
 * character in the escape sequence.
 *
 * entry:
 *	str - Address of string pointer to first character following
 *		the backslash.
 *
 * exit:
 *	If the character is not valid, an error is thrown and this routine
 *	does not return to its caller. Otherwise, it returns the ASCII
 *	code for the translated character, and *str has been advanced.
 */
static int
translate_c_esc(char **str)
{
	char *s = *str;
	int	ch;
	int	i;

	ch = *s++;
	switch (ch) {
	case 'a':
		ch = '\a';
		break;
	case 'b':
		ch = '\b';
		break;
	case 'f':
		ch = '\f';
		break;
	case 'n':
		ch = '\n';
		break;
	case 'r':
		ch = '\r';
		break;
	case 't':
		ch = '\t';
		break;
	case 'v':
		ch = '\v';
		break;

	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
		/* Octal constant: There can be up to 3 digits */
		ch -= '0';
		for (i = 0; i < 2; i++) {
			if ((*s < '0') || (*s > '7'))
				break;
			ch = (ch << 3) + (*s++ - '0');
		}
		break;

	/*
	 * There are some cases where ch already has the desired value.
	 * These cases exist simply to remove the special meaning that
	 * character would otherwise have. We need to match them to
	 * prevent them from falling into the default error case.
	 */
	case '\\':
	case '\'':
	case '"':
		break;

	default:
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_BADCESC), ch);
		break;
	}

	*str = s;
	return (ch);
}


/*
 * Prepare a GETTOK_STATE struct for gettok().
 *
 * entry:
 *	gettok_state - gettok state block to use
 *	str - Writable buffer to tokenize. Note that gettok()
 *		is allowed to change the contents of this buffer.
 *	inc_null_final - If the line ends in whitespace instead of
 *		immediately hitting a NULL, and inc_null_final is TRUE,
 *		then a null final token is generated. Otherwise trailing
 *		whitespace is ignored.
 */
static void
gettok_init(GETTOK_STATE *gettok_state, char *buf, int inc_null_final)
{
	gettok_state->gtok_buf = gettok_state->gtok_cur_buf = buf;
	gettok_state->gtok_inc_null_final = inc_null_final;
	gettok_state->gtok_null_seen = 0;
}


/*
 * Locate the next token from the buffer.
 *
 * entry:
 *	gettok_state - State of gettok() operation. Initialized
 *		by gettok_init(), and passed to gettok().
 *
 * exit:
 *	If a token is found, gettok_state->gtok_last_token is filled in
 *	with the details and True (1) is returned. If no token is found,
 *	False (1) is returned, and the contents of
 *	gettok_state->gtok_last_token are undefined.
 *
 * note:
 *	- The token returned references the memory in gettok_state->gtok_buf.
 *		The caller should not modify the buffer until all such
 *		pointers have been discarded.
 *	- This routine will modify the contents of gettok_state->gtok_buf
 *		as necessary to remove quotes and eliminate escape
 *		(\)characters.
 */
static int
gettok(GETTOK_STATE *gettok_state)
{
	char	*str = gettok_state->gtok_cur_buf;
	char	*look;
	int	quote_ch = '\0';

	/* Skip leading whitespace */
	while (isspace(*str))
		str++;

	if (*str == '\0') {
		/*
		 * If user requested it, and there was whitespace at the
		 * end, then generate one last null token.
		 */
		if (gettok_state->gtok_inc_null_final &&
		    !gettok_state->gtok_null_seen) {
			gettok_state->gtok_inc_null_final = 0;
			gettok_state->gtok_null_seen = 1;
			gettok_state->gtok_last_token.tok_str = str;
			gettok_state->gtok_last_token.tok_len = 0;
			gettok_state->gtok_last_token.tok_line_off =
			    str - gettok_state->gtok_buf;
			return (1);
		}
		gettok_state->gtok_null_seen = 1;
		return (0);
	}

	/*
	 * Read token: The standard delimiter is whitespace, but
	 * we honor either single or double quotes. Also, we honor
	 * backslash escapes.
	 */
	gettok_state->gtok_last_token.tok_str = look = str;
	gettok_state->gtok_last_token.tok_line_off =
	    look - gettok_state->gtok_buf;
	for (; *look; look++) {
		if (*look == quote_ch) {	/* Terminates active quote */
			quote_ch = '\0';
			continue;
		}

		if (quote_ch == '\0') {		/* No quote currently active */
			if ((*look == '\'') || (*look == '"')) {
				quote_ch = *look;	/* New active quote */
				continue;
			}
			if (isspace(*look))
				break;
		}

		/*
		 * The semantics of the backslash character depends on
		 * the quote style in use:
		 *	- Within single quotes, backslash is not
		 *		an escape character, and is taken literally.
		 *	- If outside of quotes, the backslash is an escape
		 *		character. The backslash is ignored and the
		 *		following character is taken literally, losing
		 *		any special properties it normally has.
		 *	- Within double quotes, backslash works like a
		 *		backslash escape within a C literal. Certain
		 *		escapes are recognized and replaced with their
		 *		special character. Any others are an error.
		 */
		if (*look == '\\') {
			if (quote_ch == '\'') {
				*str++ = *look;
				continue;
			}

			look++;
			if (*look == '\0') {	/* Esc applied to NULL term? */
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_ESCEOL));
				/*NOTREACHED*/
			}

			if (quote_ch == '"') {
				*str++ = translate_c_esc(&look);
				look--;		/* for() will advance by 1 */
				continue;
			}
		}

		if (look != str)
			*str = *look;
		str++;
	}

	/* Don't allow unterminated quoted tokens */
	if (quote_ch != '\0')
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_UNTERMQUOTE),
		    quote_ch);

	gettok_state->gtok_last_token.tok_len = str -
	    gettok_state->gtok_last_token.tok_str;
	gettok_state->gtok_null_seen = *look == '\0';
	if (!gettok_state->gtok_null_seen)
		look++;
	*str = '\0';
	gettok_state->gtok_cur_buf = look;

#ifdef DEBUG_GETTOK
	printf("GETTOK >");
	elfedit_str_to_c_literal(gettok_state->gtok_last_token.tok_str,
	    elfedit_write);
	printf("< \tlen(%d) offset(%d)\n",
	    gettok_state->gtok_last_token.tok_len,
	    gettok_state->gtok_last_token.tok_line_off);
#endif

	return (1);
}


/*
 * Tokenize the user command string, and return a pointer to the
 * TOK_STATE buffer maintained by this function. That buffer contains
 * the tokenized strings.
 *
 * entry:
 *	user_cmd_str - String to tokenize
 *	len - # of characters in user_cmd_str to examine. If
 *		(len < 0), then the complete string is processed
 *		stopping with the NULL termination. Otherwise,
 *		processing stops after len characters, and any
 *		remaining characters are ignored.
 *	inc_null_final - If True, and if user_cmd_str has whitespace
 *		at the end following the last non-null token, then
 *		a final null token will be included. If False, null
 *		tokens are ignored.
 *
 * note:
 *	This routine returns pointers to internally allocated memory.
 *	The caller must not alter anything contained in the TOK_STATE
 *	buffer returned. Furthermore, the the contents of TOK_STATE
 *	are only valid until the next call to tokenize_user_cmd().
 */
static TOK_STATE *
tokenize_user_cmd(const char *user_cmd_str, size_t len, int inc_null_final)
{
#define	INITIAL_TOK_ALLOC 5

	/*
	 * As we parse the user command, we need temporary space to
	 * hold the tokens. We do this by dynamically allocating a string
	 * buffer and a token array, and doubling them as necessary. This
	 * is a single threaded application, so static variables suffice.
	 */
	static STRBUF str;
	static TOK_STATE tokst;

	GETTOK_STATE	gettok_state;
	size_t		n;

	/*
	 * Make a copy we can modify. If (len == 0), take the entire
	 * string. Otherwise limit it to the specified length.
	 */
	tokst.tokst_cmd_len = strlen(user_cmd_str);
	if ((len > 0) && (len < tokst.tokst_cmd_len))
		tokst.tokst_cmd_len = len;
	tokst.tokst_cmd_len++;	/* Room for NULL termination */
	strbuf_ensure_size(&str, tokst.tokst_cmd_len);
	(void) strlcpy(str.buf, user_cmd_str, tokst.tokst_cmd_len);

	/* Trim off any newline character that might be present */
	if ((tokst.tokst_cmd_len > 1) &&
	    (str.buf[tokst.tokst_cmd_len - 2] == '\n')) {
		tokst.tokst_cmd_len--;
		str.buf[tokst.tokst_cmd_len - 1] = '\0';
	}

	/* Tokenize the user command string into tok struct */
	gettok_init(&gettok_state, str.buf, inc_null_final);
	tokst.tokst_str_size = 0;	/* Space needed for token strings */
	for (tokst.tokst_cnt = 0; gettok(&gettok_state) != 0;
	    tokst.tokst_cnt++) {
		/* If we need more room, expand the token buffer */
		if (tokst.tokst_cnt >= tokst.tokst_bufsize) {
			n = (tokst.tokst_bufsize == 0) ?
			    INITIAL_TOK_ALLOC : (tokst.tokst_bufsize * 2);
			tokst.tokst_buf = elfedit_realloc(
			    MSG_INTL(MSG_ALLOC_TOKBUF), tokst.tokst_buf,
			    n * sizeof (*tokst.tokst_buf));
			tokst.tokst_bufsize = n;
		}
		tokst.tokst_str_size +=
		    gettok_state.gtok_last_token.tok_len + 1;
		tokst.tokst_buf[tokst.tokst_cnt] = gettok_state.gtok_last_token;
	}
	/* fold the command token to lowercase */
	if (tokst.tokst_cnt > 0) {
		char *s;

		for (s = tokst.tokst_buf[0].tok_str; *s; s++)
			if (isupper(*s))
				*s = tolower(*s);
	}

	return (&tokst);

#undef	INITIAL_TOK_ALLOC
}


/*
 * Parse the user command string, and put an entry for it at the end
 * of state.ucmd.
 */
static void
parse_user_cmd(const char *user_cmd_str)
{
	TOK_STATE	*tokst;
	char		*s;
	size_t		n;
	size_t		len;
	USER_CMD_T	*ucmd;
	elfeditGC_module_t *mod;
	elfeditGC_cmd_t	*cmd;

	/*
	 * Break it into tokens. If there are none, then it is
	 * an empty command and is ignored.
	 */
	tokst = tokenize_user_cmd(user_cmd_str, -1, 0);
	if (tokst->tokst_cnt == 0)
		return;

	/* Find the command. Won't return on error */
	cmd = elfedit_find_command(tokst->tokst_buf[0].tok_str, 1, &mod);

	/*
	 * If there is no ELF file being edited, then only commands
	 * from the sys: module are allowed.
	 */
	if ((state.file.present == 0) &&
	    (strcmp(mod->mod_name, MSG_ORIG(MSG_MOD_SYS)) != 0))
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_NOFILSYSONLY),
		    mod->mod_name, cmd->cmd_name[0]);


	/* Allocate, fill in, and insert a USER_CMD_T block */
	n = S_DROUND(sizeof (USER_CMD_T));
	ucmd = elfedit_malloc(MSG_INTL(MSG_ALLOC_UCMD),
	    n + (sizeof (char *) * (tokst->tokst_cnt - 1)) +
	    tokst->tokst_cmd_len + tokst->tokst_str_size);
	ucmd->ucmd_next = NULL;
	ucmd->ucmd_argc = tokst->tokst_cnt - 1;
	/*LINTED E_BAD_PTR_CAST_ALIGN*/
	ucmd->ucmd_argv = (const char **)(n + (char *)ucmd);
	ucmd->ucmd_orig_str = (char *)(ucmd->ucmd_argv + ucmd->ucmd_argc);
	(void) strncpy(ucmd->ucmd_orig_str, user_cmd_str, tokst->tokst_cmd_len);
	ucmd->ucmd_mod = mod;
	ucmd->ucmd_cmd = cmd;
	ucmd->ucmd_ostyle_set = 0;
	s = ucmd->ucmd_orig_str + tokst->tokst_cmd_len;
	for (n = 1; n < tokst->tokst_cnt; n++) {
		len = tokst->tokst_buf[n].tok_len + 1;
		ucmd->ucmd_argv[n - 1] = s;
		(void) strncpy(s, tokst->tokst_buf[n].tok_str, len);
		s += len;
	}
	if (state.ucmd.list == NULL) {
		state.ucmd.list = state.ucmd.tail = ucmd;
	} else {
		state.ucmd.tail->ucmd_next = ucmd;
		state.ucmd.tail = ucmd;
	}
	state.ucmd.n++;
}


/*
 * Copy infile to a new file with the name given by outfile.
 */
static void
create_outfile(const char *infile, const char *outfile)
{
	pid_t pid;
	int statloc;
	struct stat statbuf;


	pid = fork();
	switch (pid) {
	case -1:			/* Unable to create process */
		{
			int err = errno;
			elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTFORK),
			    strerror(err));
		}
		/*NOTREACHED*/
		return;

	case 0:
		(void) execl(MSG_ORIG(MSG_STR_BINCP),
		    MSG_ORIG(MSG_STR_BINCP), infile, outfile, NULL);
		/*
		 * exec() only returns on error. This is the child process,
		 * so we want to stay away from the usual error mechanism
		 * and handle things directly.
		 */
		{
			int err = errno;
			(void) fprintf(stderr, MSG_INTL(MSG_ERR_CNTEXEC),
			    MSG_ORIG(MSG_STR_ELFEDIT),
			    MSG_ORIG(MSG_STR_BINCP), strerror(err));
		}
		exit(1);
		/*NOTREACHED*/
	}

	/* This is the parent: Wait for the child to terminate */
	if (waitpid(pid, &statloc,  0) != pid) {
		int err = errno;
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTWAIT),
		    strerror(err));
	}
	/*
	 * If the child failed, then terminate the process. There is no
	 * need for an error message, because the child will have taken
	 * care of that.
	 */
	if (!WIFEXITED(statloc) || (WEXITSTATUS(statloc) != 0))
		exit(1);

	/* Make sure the copy allows user write access */
	if (stat(outfile, &statbuf) == -1) {
		int err = errno;
		(void) unlink(outfile);
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTSTAT),
		    outfile, strerror(err));
	}
	if ((statbuf.st_mode & S_IWUSR) == 0) {
		/* Only keep permission bits, and add user write */
		statbuf.st_mode |= S_IWUSR;
		statbuf.st_mode &= 07777;   /* Only keep the permission bits */
		if (chmod(outfile, statbuf.st_mode) == -1) {
			int err = errno;
			(void) unlink(outfile);
			elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTCHMOD),
			    outfile, strerror(err));
		}
	}
}

/*
 * Given a module path string, determine how long the resulting path will
 * be when all % tokens have been expanded.
 *
 * entry:
 *	path - Path for which expanded length is desired
 *	origin_root - Root of $ORIGIN  tree containing running elfedit program
 *
 * exit:
 *	Returns the value strlen() will give for the expanded path.
 */
static size_t
modpath_strlen(const char *path, const char *origin_root)
{
	size_t len = 0;
	const char *s;

	s = path;
	len = 0;
	for (s = path; *s != '\0'; s++) {
		if (*s == '%') {
			s++;
			switch (*s) {
			case 'i':	/* ISA of running elfedit */
				len += strlen(isa_i_str);
				break;
			case 'I':	/* "" for 32-bit, same as %i for 64 */
				len += strlen(isa_I_str);
				break;
			case 'o':	/* Insert default path */
				len +=
				    modpath_strlen(MSG_ORIG(MSG_STR_MODPATH),
				    origin_root);
				break;
			case 'r':	/* root of tree with running elfedit */
				len += strlen(origin_root);
				break;

			case '%':	/* %% is reduced to just '%' */
				len++;
				break;
			default:	/* All other % codes are reserved */
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_BADPATHCODE), *s);
				/*NOTREACHED*/
				break;
			}
		} else {	/* Non-% character passes straight through */
			len++;
		}
	}

	return (len);
}


/*
 * Given a module path string, and a buffer large enough to hold the results,
 * fill the buffer with the expanded path.
 *
 * entry:
 *	path - Path for which expanded length is desired
 *	origin_root - Root of tree containing running elfedit program
 *	buf - Buffer to receive the result. buf must as large or larger
 *		than the value given by modpath_strlen().
 *
 * exit:
 *	Returns pointer to location following the last character
 *	written to buf. A NULL byte is written to that address.
 */
static char *
modpath_expand(const char *path, const char *origin_root, char *buf)
{
	size_t len;
	const char *cp_str;

	for (; *path != '\0'; path++) {
		if (*path == '%') {
			path++;
			cp_str = NULL;
			switch (*path) {
			case 'i':	/* ISA of running elfedit */
				cp_str = isa_i_str;
				break;
			case 'I':	/* "" for 32-bit, same as %i for 64 */
				cp_str = isa_I_str;
				break;
			case 'o':	/* Insert default path */
				buf = modpath_expand(MSG_ORIG(MSG_STR_MODPATH),
				    origin_root, buf);
				break;
			case 'r':
				cp_str = origin_root;
				break;
			case '%':	/* %% is reduced to just '%' */
				*buf++ = *path;
				break;
			default:	/* All other % codes are reserved */
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_BADPATHCODE), *path);
				/*NOTREACHED*/
				break;
			}
			if ((cp_str != NULL) && ((len = strlen(cp_str)) > 0)) {
				bcopy(cp_str, buf, len);
				buf += len;
			}
		} else {	/* Non-% character passes straight through */
			*buf++ = *path;
		}
	}

	*buf = '\0';
	return (buf);
}


/*
 * Establish the module search path: state.modpath
 *
 * The path used comes from the following sources, taking the first
 * one that has a value, and ignoring any others:
 *
 *	- ELFEDIT_PATH environment variable
 *	- -L command line argument
 *	- Default value
 *
 * entry:
 *	path - NULL, or the value of the -L command line argument
 *
 * exit:
 *	state.modpath has been filled in
 */
static void
establish_modpath(const char *cmdline_path)
{
	char origin_root[PATH_MAX + 1];	/* Where elfedit binary is */
	const char	*path;		/* Initial path */
	char		*expath;	/* Expanded path */
	size_t		len;
	char		*src, *dst;

	path = getenv(MSG_ORIG(MSG_STR_ENVVAR));
	if (path == NULL)
		path = cmdline_path;
	if (path == NULL)
		path = MSG_ORIG(MSG_STR_MODPATH);


	/*
	 * Root of tree containing running for running program. 32-bit elfedit
	 * is installed in /usr/bin, and 64-bit elfedit is one level lower
	 * in an ISA-specific subdirectory. So, we find the root by
	 * getting the $ORGIN of the current running program, and trimming
	 * off the last 2 (32-bit) or 3 (64-bit) directories.
	 *
	 * On a standard system, this will simply yield '/'. However,
	 * doing it this way allows us to run elfedit from a proto area,
	 * and pick up modules from the same proto area instead of those
	 * installed on the system.
	 */
	if (dlinfo(RTLD_SELF, RTLD_DI_ORIGIN, &origin_root) == -1)
		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTGETORIGIN));
	len = (sizeof (char *) == 8) ? 3 : 2;
	src = origin_root + strlen(origin_root);
	while ((src > origin_root) && (len > 0)) {
		if (*(src - 1) == '/')
			len--;
		src--;
	}
	*src = '\0';


	/*
	 * Calculate space needed to hold expanded path. Note that
	 * this assumes that MSG_STR_MODPATH will never contain a '%o'
	 * code, and so, the expansion is not recursive. The codes allowed
	 * are:
	 *	%i - ISA of running elfedit (sparc, sparcv9, etc)
	 *	%I - 64-bit ISA: Same as %i for 64-bit versions of elfedit,
	 *		but yields empty string for 32-bit ISAs.
	 *	%o - The original (default) path.
	 *	%r - Root of tree holding elfedit program.
	 *	%% - A single %
	 *
	 * A % followed by anything else is an error. This allows us to
	 * add new codes in the future without backward compatability issues.
	 */
	len = modpath_strlen(path, origin_root);

	expath = elfedit_malloc(MSG_INTL(MSG_ALLOC_EXPATH), len + 1);
	(void) modpath_expand(path, origin_root, expath);

	/*
	 * Count path segments, eliminate extra '/', and replace ':'
	 * with NULL.
	 */
	state.modpath.n = 1;
	for (src = dst = expath; *src; src++) {
		if (*src == '/') {
			switch (*(src + 1)) {
			case '/':
			case ':':
			case '\0':
				continue;
			}
		}
		if (*src == ':') {
			state.modpath.n++;
			*dst = '\0';
		} else if (src != dst) {
			*dst = *src;
		}
		dst++;
	}
	if (src != dst)
		*dst = '\0';

	state.modpath.seg = elfedit_malloc(MSG_INTL(MSG_ALLOC_PATHARR),
	    sizeof (state.modpath.seg[0]) * state.modpath.n);

	src = expath;
	for (len = 0; len < state.modpath.n; len++) {
		if (*src == '\0') {
			state.modpath.seg[len] = MSG_ORIG(MSG_STR_DOT);
			src++;
		} else {
			state.modpath.seg[len] = src;
			src += strlen(src) + 1;
		}
	}
}

/*
 * When interactive (reading commands from a tty), we catch
 * SIGINT in order to restart the outer command loop.
 */
/*ARGSUSED*/
static void
sigint_handler(int sig, siginfo_t *sip, void *ucp)
{
	/* Jump to the outer loop to resume */
	if (state.msg_jbuf.active) {
		state.msg_jbuf.active = 0;
		siglongjmp(state.msg_jbuf.env, 1);
	}
}


static void
usage(int full)
{
	elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_BRIEF));
	if (full) {
		elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL1));
		elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL2));
		elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL3));
		elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL4));
		elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL5));
		elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL6));
		elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL_LAST));
	}
	elfedit_exit(2);
}


/*
 * In order to complete commands, we need to know about them,
 * which means that we need to force all the modules to be
 * loaded. This is a relatively expensive operation, so we use
 * this function, which avoids doing it more than once in a session.
 */
static void
elfedit_cpl_load_modules(void)
{
	static int loaded;

	if (!loaded) {
		elfedit_load_modpath();
		loaded = 1;	/* Don't do it again */
	}
}

/*
 * Compare the token to the given string, and if they share a common
 * initial sequence, add the tail of string to the tecla command completion
 * buffer:
 *
 * entry:
 *	cpldata - Current completion state
 *	str - String to match against token
 *	casefold - True to allow case insensitive completion, False
 *		if case must match exactly.
 */
void
elfedit_cpl_match(void *cpldata, const char *str, int casefold)
{
	ELFEDIT_CPL_STATE *cstate = (ELFEDIT_CPL_STATE *) cpldata;
	const char	*cont_suffix;
	const char	*type_suffix;

	/*
	 * Reasons to return immediately:
	 *	- NULL strings have no completion value
	 *	- The string is shorter than the existing item being completed
	 */
	if ((str == NULL) || (*str == '\0') ||
	    ((cstate->ecpl_token_len != 0) &&
	    ((strlen(str) < cstate->ecpl_token_len))))
		return;

	/* If the string does not share the existing prefix, don't use it */
	if (casefold) {
		if (strncasecmp(cstate->ecpl_token_str, str,
		    cstate->ecpl_token_len) != 0)
			return;
	} else {
		if (strncmp(cstate->ecpl_token_str, str,
		    cstate->ecpl_token_len) != 0)
			return;
	}

	if (cstate->ecpl_add_mod_colon) {
		cont_suffix = type_suffix = MSG_ORIG(MSG_STR_COLON);
	} else {
		cont_suffix = MSG_ORIG(MSG_STR_SPACE);
		type_suffix = NULL;
	}
	(void) cpl_add_completion(cstate->ecpl_cpl, cstate->ecpl_line,
	    cstate->ecpl_word_start, cstate->ecpl_word_end,
	    str + cstate->ecpl_token_len, type_suffix, cont_suffix);

}


/*
 * Convenience wrapper on elfedit_cpl_match(): Format an unsigned
 * 32-bit integer as a string and enter the result for command completion.
 */
void
elfedit_cpl_ndx(void *cpldata, uint_t ndx)
{
	Conv_inv_buf_t	buf;

	(void) snprintf(buf.buf, sizeof (buf.buf),
	    MSG_ORIG(MSG_FMT_WORDVAL), ndx);
	elfedit_cpl_match(cpldata, buf.buf, 0);
}


/*
 * Compare the token to the names of the commands from the given module,
 * and if they share a common initial sequence, add the tail of string
 * to the tecla command completion buffer:
 *
 * entry:
 *	tok_buf - Token user has entered
 *	tok_len - strlen(tok_buf)
 *	mod - Module definition from which commands should be matched
 *	cpl, line, word_start, word_end, cont_suffix - As documented
 *		for gl_get_line() and cpl_add_completion.
 */
static void
match_module_cmds(ELFEDIT_CPL_STATE *cstate, elfeditGC_module_t *mod)
{
	elfeditGC_cmd_t *cmd;
	const char **cmd_name;

	for (cmd = mod->mod_cmds; cmd->cmd_func != NULL; cmd++)
		for (cmd_name = cmd->cmd_name; *cmd_name; cmd_name++)
			elfedit_cpl_match(cstate, *cmd_name, 1);
}


/*
 * Compare the token to the known module names, and add those that
 * match to the list of alternatives via elfedit_cpl_match().
 *
 * entry:
 *	load_all_modules - If True, causes all modules to be loaded
 *		before processing is done. If False, only the modules
 *		currently seen will be used.
 */
void
elfedit_cpl_module(void *cpldata, int load_all_modules)
{
	ELFEDIT_CPL_STATE	*cstate = (ELFEDIT_CPL_STATE *) cpldata;
	MODLIST_T		*modlist;

	if (load_all_modules)
		elfedit_cpl_load_modules();

	for (modlist = state.modlist; modlist != NULL;
	    modlist = modlist->ml_next) {
		elfedit_cpl_match(cstate, modlist->ml_mod->mod_name, 1);
	}
}


/*
 * Compare the token to all the known commands, and add those that
 * match to the list of alternatives.
 *
 * note:
 *	This routine will force modules to be loaded as necessary to
 *	obtain the names it needs to match.
 */
void
elfedit_cpl_command(void *cpldata)
{
	ELFEDIT_CPL_STATE	*cstate = (ELFEDIT_CPL_STATE *) cpldata;
	ELFEDIT_CPL_STATE	colon_state;
	const char		*colon_pos;
	MODLIST_T		*modlist;
	MODLIST_T		*insdef;
	char			buf[128];

	/*
	 * Is there a colon in the command? If so, locate its offset within
	 * the raw input line.
	 */
	for (colon_pos = cstate->ecpl_token_str;
	    *colon_pos && (*colon_pos != ':'); colon_pos++)
		;

	/*
	 * If no colon was seen, then we are completing a module name,
	 * or one of the commands from 'sys:'
	 */
	if (*colon_pos == '\0') {
		/*
		 * Setting cstate->add_mod_colon tells elfedit_cpl_match()
		 * to add an implicit ':' to the names it matches. We use it
		 * here so the user doesn't have to enter the ':' manually.
		 * Hiding this in the opaque state instead of making it
		 * an argument to that function gives us the ability to
		 * change it later without breaking the published interface.
		 */
		cstate->ecpl_add_mod_colon = 1;
		elfedit_cpl_module(cpldata, 1);
		cstate->ecpl_add_mod_colon = 0;

		/* Add bare (no sys: prefix) commands from the sys: module */
		match_module_cmds(cstate,
		    elfedit_load_module(MSG_ORIG(MSG_MOD_SYS), 1, 0));

		return;
	}

	/*
	 * A colon was seen, so we have a module name. Extract the name,
	 * substituting 'sys' for the case where the given name is empty.
	 */
	if (colon_pos == 0)
		(void) strlcpy(buf, MSG_ORIG(MSG_MOD_SYS), sizeof (buf));
	else
		elfedit_strnbcpy(buf, cstate->ecpl_token_str,
		    colon_pos - cstate->ecpl_token_str, sizeof (buf));

	/*
	 * Locate the module. If it isn't already loaded, make an explicit
	 * attempt to load it and try again. If a module definition is
	 * obtained, process the commands it supplies.
	 */
	modlist = module_loaded(buf, &insdef);
	if (modlist == NULL) {
		(void) elfedit_load_module(buf, 0, 0);
		modlist = module_loaded(buf, &insdef);
	}
	if (modlist != NULL) {
		/*
		 * Make a copy of the cstate, and adjust the line and
		 * token so that the new one starts just past the colon
		 * character. We know that the colon exists because
		 * of the preceeding test that found it. Therefore, we do
		 * not need to test against running off the end of the
		 * string here.
		 */
		colon_state = *cstate;
		while (colon_state.ecpl_line[colon_state.ecpl_word_start] !=
		    ':')
			colon_state.ecpl_word_start++;
		while (*colon_state.ecpl_token_str != ':') {
			colon_state.ecpl_token_str++;
			colon_state.ecpl_token_len--;
		}
		/* Skip past the ':' character */
		colon_state.ecpl_word_start++;
		colon_state.ecpl_token_str++;
		colon_state.ecpl_token_len--;

		match_module_cmds(&colon_state, modlist->ml_mod);
	}
}


/*
 * Command completion function for use with libtacla.
 */
/*ARGSUSED1*/
static int
cmd_match_fcn(WordCompletion *cpl, void *data, const char *line, int word_end)
{
	const char		*argv[ELFEDIT_MAXCPLARGS];
	ELFEDIT_CPL_STATE	cstate;
	TOK_STATE		*tokst;
	int			ndx;
	int			i;
	elfeditGC_module_t	*mod;
	elfeditGC_cmd_t		*cmd;
	int			num_opt;
	int			opt_term_seen;
	int			skip_one;
	elfedit_cmd_optarg_t	*optarg;
	elfedit_optarg_item_t	item;
	int			ostyle_ndx = -1;

	/*
	 * For debugging, enable the following block. It tells the tecla
	 * library that the program using is going to write to stdout.
	 * It will put the tty back into normal mode, and it will cause
	 * tecla to redraw the current input line when it gets control back.
	 */
#ifdef DEBUG_CMD_MATCH
	gl_normal_io(state.input.gl);
#endif

	/*
	 * Tokenize the line up through word_end. The last token in
	 * the list is the one requiring completion.
	 */
	tokst = tokenize_user_cmd(line, word_end, 1);
	if (tokst->tokst_cnt == 0)
		return (0);

	/* Set up the cstate block, containing the completion state */
	ndx = tokst->tokst_cnt - 1;	/* Index of token to complete */
	cstate.ecpl_cpl = cpl;
	cstate.ecpl_line = line;
	cstate.ecpl_word_start = tokst->tokst_buf[ndx].tok_line_off;
	cstate.ecpl_word_end = word_end;
	cstate.ecpl_add_mod_colon = 0;
	cstate.ecpl_token_str = tokst->tokst_buf[ndx].tok_str;
	cstate.ecpl_token_len = tokst->tokst_buf[ndx].tok_len;

	/*
	 * If there is only one token, then we are completing the
	 * command itself.
	 */
	if (ndx == 0) {
		elfedit_cpl_command(&cstate);
		return (0);
	}

	/*
	 * There is more than one token. Use the first one to
	 * locate the definition for the command. If we don't have
	 * a definition for the command, then there's nothing more
	 * we can do.
	 */
	cmd = elfedit_find_command(tokst->tokst_buf[0].tok_str, 0, &mod);
	if (cmd == NULL)
		return (0);

	/*
	 * Since we know the command, give them a quick usage message.
	 * It may be that they just need a quick reminder about the form
	 * of the command and the options.
	 */
	(void) gl_normal_io(state.input.gl);
	elfedit_printf(MSG_INTL(MSG_USAGE_CMD),
	    elfedit_format_command_usage(mod, cmd, NULL, 0));


	/*
	 * We have a generous setting for ELFEDIT_MAXCPLARGS, so there
	 * should always be plenty of room. If there's not room, we
	 * can't proceed.
	 */
	if (ndx >= ELFEDIT_MAXCPLARGS)
		return (0);

	/*
	 * Put pointers to the tokens into argv, and determine how
	 * many of the tokens are optional arguments.
	 *
	 * We consider the final optional argument to be the rightmost
	 * argument that starts with a '-'. If a '--' is seen, then
	 * we stop there, and any argument that follows is a plain argument
	 * (even if it starts with '-').
	 *
	 * We look for an inherited '-o' option, because we are willing
	 * to supply command completion for these values.
	 */
	num_opt = 0;
	opt_term_seen = 0;
	skip_one = 0;
	for (i = 0; i < ndx; i++) {
		argv[i] = tokst->tokst_buf[i + 1].tok_str;
		if (opt_term_seen || skip_one) {
			skip_one = 0;
			continue;
		}
		skip_one = 0;
		ostyle_ndx = -1;
		if ((strcmp(argv[i], MSG_ORIG(MSG_STR_MINUS_MINUS)) == NULL) ||
		    (*argv[i] != '-')) {
			opt_term_seen = 1;
			continue;
		}
		num_opt = i + 1;
		/*
		 * If it is a recognised ELFEDIT_CMDOA_F_VALUE option,
		 * then the item following it is the associated value.
		 * Check for this and skip the value.
		 *
		 * At the same time, look for STDOA_OPT_O inherited
		 * options. We want to identify the index of any such
		 * item. Although the option is simply "-o", we are willing
		 * to treat any option that starts with "-o" as a potential
		 * STDOA_OPT_O. This lets us to command completion for things
		 * like "-onum", and is otherwise harmless, the only cost
		 * being a few additional strcmps by the cpl code.
		 */
		if ((optarg = cmd->cmd_opt) == NULL)
			continue;
		while (optarg->oa_name != NULL) {
			int is_ostyle_optarg =
			    (optarg->oa_flags & ELFEDIT_CMDOA_F_INHERIT) &&
			    (optarg->oa_name == ELFEDIT_STDOA_OPT_O);

			elfedit_next_optarg(&optarg, &item);
			if (item.oai_flags & ELFEDIT_CMDOA_F_VALUE) {
				if (is_ostyle_optarg && (strncmp(argv[i],
				    MSG_ORIG(MSG_STR_MINUS_O), 2) == 0))
					ostyle_ndx = i + 1;

				if (strcmp(item.oai_name, argv[i]) == 0) {
					num_opt = i + 2;
					skip_one = 1;
					break;
				}
				/*
				 * If it didn't match "-o" exactly, but it is
				 * ostyle_ndx, then it is a potential combined
				 * STDOA_OPT_O, as discussed above. It counts
				 * as a single argument.
				 */
				if (ostyle_ndx == ndx)
					break;
			}
		}
	}

#ifdef DEBUG_CMD_MATCH
	(void) printf("NDX(%d) NUM_OPT(%d) ostyle_ndx(%d)\n", ndx, num_opt,
	    ostyle_ndx);
#endif

	if (ostyle_ndx != -1) {
		/*
		 * If ostyle_ndx is one less than ndx, and ndx is
		 * the same as num_opt, then we have a definitive
		 * STDOA_OPT_O inherited outstyle option. We supply
		 * the value strings, and are done.
		 */
		if ((ostyle_ndx == (ndx - 1)) && (ndx == num_opt)) {
			elfedit_cpl_atoconst(&cstate, ELFEDIT_CONST_OUTSTYLE);
			return (0);
		}

		/*
		 * If ostyle is the same as ndx, then we have an option
		 * staring with "-o" that may end up being a STDOA_OPT_O,
		 * and we are still inside that token. In this case, we
		 * supply completion strings that include the leading
		 * "-o" followed by the values, without a space
		 * (i.e. "-onum"). We then fall through, allowing any
		 * other options starting with "-o" to be added
		 * below. elfedit_cpl_match() will throw out the incorrect
		 * options, so it is harmless to add these extra items in
		 * the worst case, and useful otherwise.
		 */
		if (ostyle_ndx == ndx)
			elfedit_cpl_atoconst(&cstate,
			    ELFEDIT_CONST_OUTSTYLE_MO);
	}

	/*
	 * If (ndx <= num_opt), then the token needing completion
	 * is an option. If the leading '-' is there, then we should fill
	 * in all of the option alternatives. If anything follows the '-'
	 * though, we assume that the user has already figured out what
	 * option to use, and we leave well enough alone.
	 *
	 * Note that we are intentionally ignoring a related case
	 * where supplying option strings would be legal: In the case
	 * where we are one past the last option (ndx == (num_opt + 1)),
	 * and the current option is an empty string, the argument can
	 * be either a plain argument or an option --- the user needs to
	 * enter the next character before we can tell. It would be
	 * OK to enter the option strings in this case. However, consider
	 * what happens when the first plain argument to the command does
	 * not provide any command completion (e.g. it is a plain integer).
	 * In this case, tecla will see that all the alternatives start
	 * with '-', and will insert a '-' into the input. If the user
	 * intends the next argument to be plain, they will have to delete
	 * this '-', which is annoying. Worse than that, they may be confused
	 * by it, and think that the plain argument is not allowed there.
	 * The best solution is to not supply option strings unless the
	 * user first enters the '-'.
	 */
	if ((ndx <= num_opt) && (argv[ndx - 1][0] == '-')) {
		if ((optarg = cmd->cmd_opt) != NULL) {
			while (optarg->oa_name != NULL) {
				elfedit_next_optarg(&optarg, &item);
				elfedit_cpl_match(&cstate, item.oai_name, 1);
			}
		}
		return (0);
	}

	/*
	 * At this point we know that ndx and num_opt are not equal.
	 * If num_opt is larger than ndx, then we have an ELFEDIT_CMDOA_F_VALUE
	 * argument at the end, and the following value has not been entered.
	 *
	 * If ndx is greater than num_opt, it means that we are looking
	 * at a plain argument (or in the case where (ndx == (num_opt + 1)),
	 * a *potential* plain argument.
	 *
	 * If the command has a completion function registered, then we
	 * hand off the remaining work to it. The cmd_cplfunc field is
	 * the generic definition. We need to cast it to the type that matches
	 * the proper ELFCLASS before calling it.
	 */
	if (state.elf.elfclass == ELFCLASS32) {
		elfedit32_cmdcpl_func_t *cmdcpl_func =
		    (elfedit32_cmdcpl_func_t *)cmd->cmd_cplfunc;

		if (cmdcpl_func != NULL)
			(* cmdcpl_func)(state.elf.obj_state.s32,
			    &cstate, ndx, argv, num_opt);
	} else {
		elfedit64_cmdcpl_func_t *cmdcpl_func =
		    (elfedit64_cmdcpl_func_t *)cmd->cmd_cplfunc;

		if (cmdcpl_func != NULL)
			(* cmdcpl_func)(state.elf.obj_state.s64,
			    &cstate, ndx, argv, num_opt);
	}

	return (0);
}


/*
 * Read a line of input from stdin, and return pointer to it.
 *
 * This routine uses a private buffer, so the contents of the returned
 * string are only good until the next call.
 */
static const char *
read_cmd(void)
{
	char *s;

	if (state.input.full_tty) {
		state.input.in_tecla = TRUE;
		s = gl_get_line(state.input.gl,
		    MSG_ORIG(MSG_STR_PROMPT), NULL, -1);
		state.input.in_tecla = FALSE;
		/*
		 * gl_get_line() returns NULL for EOF or for error. EOF is fine,
		 * but we need to catch and report anything else. Since
		 * reading from stdin is critical to our operation, an
		 * error implies that we cannot recover and must exit.
		 */
		if ((s == NULL) &&
		    (gl_return_status(state.input.gl) == GLR_ERROR)) {
			elfedit_msg(ELFEDIT_MSG_FATAL, MSG_INTL(MSG_ERR_GLREAD),
			    gl_error_message(state.input.gl, NULL, 0));
		}
	} else {
		/*
		 * This should be a dynamically sized buffer, but for now,
		 * I'm going to take a simpler path.
		 */
		static char cmd_buf[ELFEDIT_MAXCMD + 1];

		s = fgets(cmd_buf, sizeof (cmd_buf), stdin);
	}

	/* Return user string, or 'quit' on EOF */
	return (s ? s : MSG_ORIG(MSG_SYS_CMD_QUIT));
}

int
main(int argc, char **argv, char **envp)
{
	/*
	 * Note: This function can use setjmp()/longjmp() which does
	 * not preserve the values of auto/register variables. Hence,
	 * variables that need their values preserved across a jump must
	 * be marked volatile, or must not be auto/register.
	 *
	 * Volatile can be messy, because it requires explictly casting
	 * away the attribute when passing it to functions, or declaring
	 * those functions with the attribute as well. In a single threaded
	 * program like this one, an easier approach is to make things
	 * static. That can be done here, or by putting things in the
	 * 'state' structure.
	 */

	int		c, i;
	int		num_batch = 0;
	char		**batch_list = NULL;
	const char	*modpath = NULL;

	/*
	 * Always have liblddb display unclipped section names.
	 * This global is exported by liblddb, and declared in debug.h.
	 */
	dbg_desc->d_extra |= DBG_E_LONG;

	opterr = 0;
	while ((c = getopt(argc, argv, MSG_ORIG(MSG_STR_OPTIONS))) != EOF) {
		switch (c) {
		case 'a':
			state.flags |= ELFEDIT_F_AUTOPRINT;
			break;

		case 'd':
			state.flags |= ELFEDIT_F_DEBUG;
			break;

		case 'e':
			/*
			 * Delay parsing the -e options until after the call to
			 * conv_check_native() so that we won't bother loading
			 * modules of the wrong class.
			 */
			if (batch_list == NULL)
				batch_list = elfedit_malloc(
				    MSG_INTL(MSG_ALLOC_BATCHLST),
				    sizeof (*batch_list) * (argc - 1));
			batch_list[num_batch++] = optarg;
			break;

		case 'L':
			modpath = optarg;
			break;

		case 'o':
			if (elfedit_atooutstyle(optarg, &state.outstyle) == 0)
				usage(1);
			break;

		case 'r':
			state.flags |= ELFEDIT_F_READONLY;
			break;

		case '?':
			usage(1);
		}
	}

	/*
	 * We allow 0, 1, or 2 files:
	 *
	 * The no-file case is an extremely limited mode, in which the
	 * only commands allowed to execute come from the sys: module.
	 * This mode exists primarily to allow easy access to the help
	 * facility.
	 *
	 * To get full access to elfedit's capablities, there must
	 * be an input file. If this is not a readonly
	 * session, then an optional second output file is allowed.
	 *
	 * In the case where two files are given and the session is
	 * readonly, use a full usage message, because the simple
	 * one isn't enough for the user to understand their error.
	 * Otherwise, the simple usage message suffices.
	 */
	argc = argc - optind;
	if ((argc == 2) && (state.flags & ELFEDIT_F_READONLY))
		usage(1);
	if (argc > 2)
		usage(0);

	state.file.present = (argc != 0);

	/*
	 * If we have a file to edit, and unless told otherwise by the
	 * caller, we try to run the 64-bit version of this program
	 * when the system is capable of it. If that fails, then we
	 * continue on with the currently running version.
	 *
	 * To force 32-bit execution on a 64-bit host, set the
	 * LD_NOEXEC_64 environment variable to a non-empty value.
	 *
	 * There is no reason to bother with this if in "no file" mode.
	 */
	if (state.file.present != 0)
		(void) conv_check_native(argv, envp);

	elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_VERSION),
	    (sizeof (char *) == 8) ? 64 : 32);

	/*
	 * Put a module definition for the builtin system module on the
	 * module list. We know it starts out empty, so we do not have
	 * to go through a more general insertion process than this.
	 */
	state.modlist = elfedit_sys_init(ELFEDIT_VER_CURRENT);

	/* Establish the search path for loadable modules */
	establish_modpath(modpath);

	/*
	 * Now that we are running the final version of this program,
	 * deal with the input/output file(s).
	 */
	if (state.file.present == 0) {
		/*
		 * This is arbitrary --- we simply need to be able to
		 * load modules so that we can access their help strings
		 * and command completion functions. Without a file, we
		 * will refuse to call commands from any module other
		 * than sys. Those commands have been written to be aware
		 * of the case where there is no input file, and are
		 * therefore safe to run.
		 */
		state.elf.elfclass = ELFCLASS32;
		elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_NOFILE));

	} else {
		state.file.infile = argv[optind];
		if (argc == 1) {
			state.file.outfile = state.file.infile;
			if (state.flags & ELFEDIT_F_READONLY)
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_READONLY));
			else
				elfedit_msg(ELFEDIT_MSG_DEBUG,
				    MSG_INTL(MSG_DEBUG_INPLACEWARN),
				    state.file.infile);
		} else {
			state.file.outfile = argv[optind + 1];
			create_outfile(state.file.infile, state.file.outfile);
			elfedit_msg(ELFEDIT_MSG_DEBUG,
			    MSG_INTL(MSG_DEBUG_CPFILE),
			    state.file.infile, state.file.outfile);
			/*
			 * We are editing a copy of the original file that we
			 * just created. If we should exit before the edits are
			 * updated, then we want to unlink this copy so that we
			 * don't leave junk lying around. Once an update
			 * succeeds however, we'll leave it in place even
			 * if an error occurs afterwards.
			 */
			state.file.unlink_on_exit = 1;
			optind++;	/* Edit copy instead of the original */
		}

		init_obj_state(state.file.outfile);
	}


	/*
	 * Process commands.
	 *
	 * If any -e options were used, then do them and
	 * immediately exit. On error, exit immediately without
	 * updating the target ELF file. On success, the 'write'
	 * and 'quit' commands are implicit in this mode.
	 *
	 * If no -e options are used, read commands from stdin.
	 * quit must be explicitly used. Exit is implicit on EOF.
	 * If stdin is a tty, then errors do not cause the editor
	 * to terminate. Rather, the error message is printed, and the
	 * user prompted to continue.
	 */
	if (batch_list != NULL) {	/* -e was used */
		/* Compile the commands */
		for (i = 0; i < num_batch; i++)
			parse_user_cmd(batch_list[i]);
		free(batch_list);

		/*
		 * 'write' and 'quit' are implicit in this mode.
		 * Add them as well.
		 */
		if ((state.flags & ELFEDIT_F_READONLY) == 0)
			parse_user_cmd(MSG_ORIG(MSG_SYS_CMD_WRITE));
		parse_user_cmd(MSG_ORIG(MSG_SYS_CMD_QUIT));

		/* And run them. This won't return, thanks to the 'quit' */
		dispatch_user_cmds();
	} else {
		state.input.is_tty = isatty(fileno(stdin));
		state.input.full_tty = state.input.is_tty &&
		    isatty(fileno(stdout));

		if (state.input.full_tty) {
			struct sigaction act;

			act.sa_sigaction = sigint_handler;
			(void) sigemptyset(&act.sa_mask);
			act.sa_flags = 0;
			if (sigaction(SIGINT, &act, NULL) == -1) {
				int err = errno;
				elfedit_msg(ELFEDIT_MSG_ERR,
				    MSG_INTL(MSG_ERR_SIGACTION), strerror(err));
			}
			/*
			 * If pager process exits before we are done
			 * writing, we can see SIGPIPE. Prevent it
			 * from killing the process.
			 */
			(void) sigignore(SIGPIPE);

			/* Open tecla handle for command line editing */
			state.input.gl = new_GetLine(ELFEDIT_MAXCMD,
			    ELFEDIT_MAXHIST);
			/* Register our command completion function */
			(void) gl_customize_completion(state.input.gl,
			    NULL, cmd_match_fcn);

			/*
			 * Make autoprint the default for interactive
			 * sessions.
			 */
			state.flags |= ELFEDIT_F_AUTOPRINT;
		}
		for (;;) {
			/*
			 * If this is an interactive session, then use
			 * sigsetjmp()/siglongjmp() to recover from bad
			 * commands and keep going. A non-0 return from
			 * sigsetjmp() means that an error just occurred.
			 * In that case, we simply restart this loop.
			 */
			if (state.input.is_tty) {
				if (sigsetjmp(state.msg_jbuf.env, 1) != 0) {
					if (state.input.full_tty)
						gl_abandon_line(state.input.gl);
					continue;
				}
				state.msg_jbuf.active = TRUE;
			}

			/*
			 * Force all output out before each command.
			 * This is a no-OP when a tty is in use, but
			 * in a pipeline, it ensures that the block
			 * mode buffering doesn't delay output past
			 * the completion of each command.
			 *
			 * If we didn't do this, the output would eventually
			 * arrive at its destination, but the lag can be
			 * annoying when you pipe the output into a tool
			 * that displays the results in real time.
			 */
			(void) fflush(stdout);
			(void) fflush(stderr);

			parse_user_cmd(read_cmd());
			dispatch_user_cmds();
			state.msg_jbuf.active = FALSE;
		}
	}


	/*NOTREACHED*/
	return (0);
}