diff options
Diffstat (limited to 'usr/src/cmd/sgs/elfedit/common/elfedit.c')
-rw-r--r-- | usr/src/cmd/sgs/elfedit/common/elfedit.c | 3600 |
1 files changed, 3600 insertions, 0 deletions
diff --git a/usr/src/cmd/sgs/elfedit/common/elfedit.c b/usr/src/cmd/sgs/elfedit/common/elfedit.c new file mode 100644 index 0000000000..c3d4c5f379 --- /dev/null +++ b/usr/src/cmd/sgs/elfedit/common/elfedit.c @@ -0,0 +1,3600 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <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)); + } +} + + +/* + * 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; + } +} + + +#if 0 +/* + * 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: + /* + * 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(); + } +} + + +/* + * 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; + } + + if (*look == '\\') { + look++; + if (*look == '\0') /* Esc applied to NULL term? */ + break; + } + + if (look != str) + *str = *look; + str++; + } + 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; + +#if 0 + printf("GETTOK >%s< len(%d) offset(%d)\n", + gettok_state->gtok_last_token.tok_str, + 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); + +} + + +/* + * 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. + */ +#if 0 + 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; + } + } + } + +#if 0 + 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); +} |