/* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msg.h" #include "_elfedit.h" #include /* liblddb */ /* * Column at which elfedit_format_command_usage() will wrap the * generated usage string if the wrap argument is True (1). */ #define USAGE_WRAP_COL 55 /* * Type used to represent a string buffer that can grow as needed * to hold strings of arbitrary length. The user should declare * variables of this type sa static. The strbuf_ensure_size() function * is used to ensure that it has a minimum desired size. */ typedef struct { char *buf; /* String buffer */ size_t n; /* Size of buffer */ } STRBUF; /* * Types used by tokenize_user_cmd() to represent the result of * spliting a user command into individual tokens. */ typedef struct { char *tok_str; /* Token string */ size_t tok_len; /* strlen(str) */ size_t tok_line_off; /* Token offset in original string */ } TOK_ELT; typedef struct { size_t tokst_cmd_len; /* Length of original user command, without */ /* newline or NULL termination chars */ size_t tokst_str_size; /* Space needed to hold all the resulting */ /* tokens, including terminating NULL */ TOK_ELT *tokst_buf; /* The array of tokens */ size_t tokst_cnt; /* # of tokens in array */ size_t tokst_bufsize; /* capacity of array */ } TOK_STATE; /* State block used by gettok_init() and gettok() */ typedef struct { const char *gtok_buf; /* Addr of buffer containing string */ char *gtok_cur_buf; /* Addr withing buffer for next token */ int gtok_inc_null_final; /* True if final NULL token used */ int gtok_null_seen; /* True when NULL byte seen */ TOK_ELT gtok_last_token; /* Last token parsed */ } GETTOK_STATE; /* * The elfedit_cpl_*() functions are used for command line completion. * Currently this uses the tecla library, but to allow for changing the * library used, we hide all tecla interfaces from our modules. Instead, * cmd_match_fcn() builds an ELFEDIT_CPL_STATE struct, and we pass the * address of that struct as an opaque handle to the modules. Since the * pointer is opaque, the contents of ELFEDIT_CPL_STATE are free to change * as necessary. */ typedef struct { WordCompletion *ecpl_cpl; /* tecla handle */ const char *ecpl_line; /* raw input line */ int ecpl_word_start; /* start offset within line */ int ecpl_word_end; /* offset just past token */ /* * ecpl_add_mod_colon is a secret handshake between * elfedit_cpl_command() and elfedit_cpl_add_match(). It adds * ':' to end of matched modules. */ int ecpl_add_mod_colon; const char *ecpl_token_str; /* token being completed */ size_t ecpl_token_len; /* strlen(ecpl_token_str) */ } ELFEDIT_CPL_STATE; /* This structure maintains elfedit global state */ STATE_T state; /* * Define a pair of static global variables that contain the * ISA strings that correspond to %i and %I tokens in module search * paths. * * isa_i_str - The ISA string for the currently running program * isa_I_str - For 64-bit programs, the same as isa_i_str. For * 32-bit programs, an empty string. */ #ifdef __sparc #ifdef __sparcv9 static const char *isa_i_str = MSG_ORIG(MSG_ISA_SPARC_64); static const char *isa_I_str = MSG_ORIG(MSG_ISA_SPARC_64); #else static const char *isa_i_str = MSG_ORIG(MSG_ISA_SPARC_32); static const char *isa_I_str = MSG_ORIG(MSG_STR_EMPTY); #endif #endif #ifdef __i386 static const char *isa_i_str = MSG_ORIG(MSG_ISA_X86_32); static const char *isa_I_str = MSG_ORIG(MSG_STR_EMPTY); #endif #ifdef __amd64 static const char *isa_i_str = MSG_ORIG(MSG_ISA_X86_64); static const char *isa_I_str = MSG_ORIG(MSG_ISA_X86_64); #endif /* Forward declarations */ static void free_user_cmds(void); static void elfedit_pager_cleanup(void); /* * We supply this function for the msg module */ const char * _elfedit_msg(Msg mid) { return (gettext(MSG_ORIG(mid))); } /* * Copy at most min(cpsize, dstsize-1) bytes from src into dst, * truncating src if necessary. The result is always null-terminated. * * entry: * dst - Destination buffer * src - Source string * dstsize - sizeof(dst) * * note: * This is similar to strncpy(), but with two modifications: * 1) You specify the number of characters to copy, not just * the size of the destination. Hence, you can copy non-NULL * terminated strings. * 2) The destination is guaranteed to be NULL terminated. strncpy() * does not terminate a completely full buffer. */ static void elfedit_strnbcpy(char *dst, const char *src, size_t cpsize, size_t dstsize) { if (cpsize >= dstsize) cpsize = dstsize - 1; if (cpsize > 0) (void) strncpy(dst, src, cpsize + 1); dst[cpsize] = '\0'; } /* * Calls exit() on behalf of elfedit. */ void elfedit_exit(int status) { if (state.file.present) { /* Exiting with unflushed changes pending? Issue debug notice */ if (state.file.dirty) elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_DIRTYEXIT)); /* * If the edit file is marked for unlink on exit, then * take care of it here. */ if (state.file.unlink_on_exit) { elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_UNLINKFILE), state.file.outfile); (void) unlink(state.file.outfile); } } exit(status); } /* * Standard message function for elfedit. All user visible * output, for error or informational reasons, should go through * this function. * * entry: * type - Type of message. One of the ELFEDIT_MSG_* values. * format, ... - As per the printf() family * * exit: * The desired message has been output. For informational * messages, control returns to the caller. For errors, * this routine will terminate execution or strip the execution * stack and return control directly to the outer control loop. * In either case, the caller will not receive control. */ /*PRINTFLIKE2*/ void elfedit_msg(elfedit_msg_t type, const char *format, ...) { typedef enum { /* What to do after finished */ DISP_RET = 0, /* Return to caller */ DISP_JMP = 1, /* if (interactive) longjmp else exit */ DISP_EXIT = 2 /* exit under all circumstances */ } DISP; va_list args; FILE *stream = stderr; DISP disp = DISP_RET; int do_output = 1; int need_prefix = 1; va_start(args, format); switch (type) { case ELFEDIT_MSG_ERR: case ELFEDIT_MSG_CMDUSAGE: disp = DISP_JMP; break; case ELFEDIT_MSG_FATAL: disp = DISP_EXIT; break; case ELFEDIT_MSG_USAGE: need_prefix = 0; break; case ELFEDIT_MSG_DEBUG: if (!(state.flags & ELFEDIT_F_DEBUG)) return; stream = stdout; break; case ELFEDIT_MSG_QUIET: do_output = 0; disp = DISP_JMP; break; } /* * If there is a pager process running, we are returning to the * caller, and the output is going to stdout, then let the * pager handle it instead of writing it directly from this process. * That way, the output gets paged along with everything else. * * If there is a pager process running, and we are not returning * to the caller, then end the pager process now, before we generate * any new output. This allows for any text buffered in the pager * pipe to be output before the new stuff. */ if (state.pager.fptr != NULL) { if (disp == DISP_RET) { if (stream == stdout) stream = state.pager.fptr; } else { elfedit_pager_cleanup(); } } /* * If this message is coming from within the libtecla command * completion code, call gl_normal_io() to give the library notice. * That function sets the tty back to cooked mode and advances * the cursor to the beginning of the next line so that our output * will appear properly. When we return to the command completion code, * tecla will re-enter raw mode and redraw the current command line. */ if (state.input.in_tecla) (void) gl_normal_io(state.input.gl); if (do_output) { if (need_prefix) (void) fprintf(stream, MSG_ORIG(MSG_STR_ELFEDIT)); (void) vfprintf(stream, format, args); (void) fflush(stream); } va_end(args); /* * If this is an error, then we do not return to the caller. * The action taken depends on whether the outer loop has registered * a jump buffer for us or not. */ if (disp != DISP_RET) { if (state.msg_jbuf.active && (disp == DISP_JMP)) { /* Free the user command list */ free_user_cmds(); /* Clean up to reflect effect of non-local goto */ state.input.in_tecla = FALSE; /* Jump to the outer loop to resume */ siglongjmp(state.msg_jbuf.env, 1); } else { elfedit_exit(1); } } } /* * Wrapper on elfedit_msg() that issues an error that results from * a call to libelf. * * entry: * file - Name of ELF object * libelf_rtn_name - Name of routine that was called * * exit: * An error has been issued that shows the routine called * and the libelf error string for it from elf_errmsg(). * This routine does not return to the caller. */ void elfedit_elferr(const char *file, const char *libelf_rtn_name) { const char *errstr = elf_errmsg(elf_errno()); elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_LIBELF), file, libelf_rtn_name, errstr ? errstr : MSG_INTL(MSG_FMT_UNKNOWN)); } /* * Start an output pager process for elfedit_printf()/elfedit_write() to use. * * note: * If this elfedit session is not interactive, then no pager is * started. Paging is only intended for interactive use. The caller * is not supposed to worry about this point, but simply to use * this function to flag situations in which paging might be needed. */ void elfedit_pager_init(void) { const char *errstr; const char *cmd; int err; /* * If there is no pager process running, start one. * Only do this for interactive sessions --- elfedit_pager() * won't use a pager in batch mode. */ if (state.msg_jbuf.active && state.input.full_tty && (state.pager.fptr == NULL)) { /* * If the user has the PAGER environment variable set, * then we will use that program. Otherwise we default * to /bin/more. */ cmd = getenv(MSG_ORIG(MSG_STR_PAGER)); if ((cmd == NULL) || (*cmd == '\0')) cmd = MSG_ORIG(MSG_STR_BINMORE); /* * The popen() manpage says that on failure, it "may set errno", * which is somewhat ambiguous. We explicitly zero it here, and * assume that any change is due to popen() failing. */ errno = 0; state.pager.fptr = popen(cmd, MSG_ORIG(MSG_STR_W)); if (state.pager.fptr == NULL) { err = errno; errstr = (err == 0) ? MSG_INTL(MSG_ERR_UNKNOWNSYSERR) : strerror(err); elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTEXEC), MSG_ORIG(MSG_STR_ELFEDIT), cmd, errstr); } } } /* * If there is a pager process present, close it out. * * note: * This function is called from within elfedit_msg(), and as * such, must not use elfedit_msg() to report errors. Furthermore, * any such errors are not a sufficient reason to terminate the process * or to longjmp(). This is a rare case where errors are written * directly to stderr. */ static void elfedit_pager_cleanup(void) { if (state.pager.fptr != NULL) { if (pclose(state.pager.fptr) == -1) (void) fprintf(stderr, MSG_INTL(MSG_ERR_PAGERFINI)); state.pager.fptr = NULL; } } /* * Print general formtted text for the user, using printf()-style * formatting. Uses the pager process if one has been started, or * stdout otherwise. */ void elfedit_printf(const char *format, ...) { va_list args; int err; FILE *fptr; int pager; int broken_pipe = 0; /* * If there is a pager process, then use it. Otherwise write * directly to stdout. */ pager = (state.pager.fptr != NULL); fptr = pager ? state.pager.fptr : stdout; va_start(args, format); errno = 0; err = vfprintf(fptr, format, args); /* Did we fail because a child pager process has exited? */ broken_pipe = pager && (err < 0) && (errno == EPIPE); va_end(args); /* * On error, we simply issue the error without cleaning up * the pager process. The message code handles that as a standard * part of error processing. * * We handle failure due to an exited pager process differently * than a normal error, because it is usually due to the user * intentionally telling it to. */ if (err < 0) { if (broken_pipe) elfedit_msg(ELFEDIT_MSG_QUIET, MSG_ORIG(MSG_STR_NULL)); else elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_PRINTF)); } } /* * Some our modules use liblddb routines to format ELF output. * In order to ensure that such output is sent to the pager pipe * when there is one, and stdout otherwise, we redefine the dbg_print() * function here. * * This item should be defined NODIRECT. */ /* PRINTFLIKE2 */ void dbg_print(Lm_list *lml, const char *format, ...) { va_list ap; int err; FILE *fptr; int pager; int broken_pipe = 0; #if defined(lint) /* * The lml argument is only meaningful for diagnostics sent to ld.so.1. * Supress the lint error by making a dummy assignment. */ lml = 0; #endif /* * If there is a pager process, then use it. Otherwise write * directly to stdout. */ pager = (state.pager.fptr != NULL); fptr = pager ? state.pager.fptr : stdout; va_start(ap, format); errno = 0; err = vfprintf(fptr, format, ap); if (err >= 0) err = fprintf(fptr, MSG_ORIG(MSG_STR_NL)); /* Did we fail because a child pager process has exited? */ broken_pipe = (err < 0) && pager && (errno == EPIPE); va_end(ap); /* * On error, we simply issue the error without cleaning up * the pager process. The message code handles that as a standard * part of error processing. * * We handle failure due to an exited pager process differently * than a normal error, because it is usually due to the user * intentionally telling it to. */ if (err < 0) { if (broken_pipe) elfedit_msg(ELFEDIT_MSG_QUIET, MSG_ORIG(MSG_STR_NULL)); else elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_PRINTF)); } } /* * Write raw bytes of text in a manner similar to fwrite(). * Uses the pager process if one has been started, or * stdout otherwise. */ void elfedit_write(const void *ptr, size_t size) { FILE *fptr; int err; /* * If there is a pager process, then use it. Otherwise write * directly to stdout. */ fptr = (state.pager.fptr == NULL) ? stdout : state.pager.fptr; if (fwrite(ptr, 1, size, fptr) != size) { err = errno; elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_FWRITE), strerror(err)); } } /* * Convert the NULL terminated string to the form used by the C * language to represent literal strings. See conv_str_to_c_literal() * for details. * * This routine differs from conv_str_to_c_literal() in two ways: * 1) String is NULL terminated instead of counted * 2) Signature of outfunc * * entry: * str - String to be processed * outfunc - Function to be called to move output characters. Note * that this function has the same signature as elfedit_write(), * and that function can be used to write the characters to * the output. * * exit: * The string has been processed, with the resulting data passed * to outfunc for processing. */ static void elfedit_str_to_c_literal_cb(const void *ptr, size_t size, void *uvalue) { elfedit_write_func_t *outfunc = (elfedit_write_func_t *)uvalue; (* outfunc)(ptr, size); } void elfedit_str_to_c_literal(const char *str, elfedit_write_func_t *outfunc) { conv_str_to_c_literal(str, strlen(str), elfedit_str_to_c_literal_cb, (void *) outfunc); } /* * Wrappers on malloc() and realloc() that check the result for success * and issue an error if not. The caller can use the result of these * functions without checking for a NULL pointer, as we do not return to * the caller in the failure case. */ void * elfedit_malloc(const char *item_name, size_t size) { void *m; m = malloc(size); if (m == NULL) { int err = errno; elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_MALLOC), item_name, strerror(err)); } return (m); } void * elfedit_realloc(const char *item_name, void *ptr, size_t size) { void *m; m = realloc(ptr, size); if (m == NULL) { int err = errno; elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_MALLOC), item_name, strerror(err)); } return (m); } /* * Ensure that the given buffer has room for n bytes of data. */ static void strbuf_ensure_size(STRBUF *str, size_t size) { #define INITIAL_STR_ALLOC 128 size_t n; n = (str->n == 0) ? INITIAL_STR_ALLOC : str->n; while (size > n) /* Double buffer until string fits */ n *= 2; if (n != str->n) { /* Alloc new string buffer if needed */ str->buf = elfedit_realloc(MSG_INTL(MSG_ALLOC_UCMDSTR), str->buf, n); str->n = n; } #undef INITIAL_STR_ALLOC } /* * Extract the argument/option information for the next item referenced * by optarg, and advance the pointer to the next item. * * entry: * optarg - Address of pointer to argument or option array * item - Struct to be filled in. * * exit: * The item block has been filled in with the information for * the next item in the optarg array. *optarg has been advanced * to the next item. */ void elfedit_next_optarg(elfedit_cmd_optarg_t **optarg, elfedit_optarg_item_t *item) { /* * Array of inheritable options/arguments. Indexed by one less * than the corresponding ELFEDIT_STDOA_ value. */ static const elfedit_optarg_item_t stdoa[] = { /* ELFEDIT_STDOA_O */ { MSG_ORIG(MSG_STR_MINUS_O), MSG_ORIG(MSG_STR_OUTSTYLE), /* MSG_INTL(MSG_STDOA_OPTDESC_O) */ (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_O, ELFEDIT_CMDOA_F_VALUE }, /* ELFEDIT_STDOA_AND */ { MSG_ORIG(MSG_STR_MINUS_AND), NULL, /* MSG_INTL(MSG_STDOA_OPTDESC_AND) */ (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_AND, 0 }, /* ELFEDIT_STDOA_CMP */ { MSG_ORIG(MSG_STR_MINUS_CMP), NULL, /* MSG_INTL(MSG_STDOA_OPTDESC_CMP) */ (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_CMP, 0 }, /* ELFEDIT_STDOA_OR */ { MSG_ORIG(MSG_STR_MINUS_OR), NULL, /* MSG_INTL(MSG_STDOA_OPTDESC_OR) */ (elfedit_i18nhdl_t)MSG_STDOA_OPTDESC_OR, 0 }, }; elfedit_cmd_optarg_t *oa; /* Grab first item, advance the callers pointer over it */ oa = (*optarg)++; if (oa->oa_flags & ELFEDIT_CMDOA_F_INHERIT) { /* Values are pre-chewed in the stdoa array above */ *item = stdoa[((uintptr_t)oa->oa_name) - 1]; /* * Set the inherited flag so that elfedit_optarg_helpstr() * can tell who is responsible for translating the help string. */ item->oai_flags |= ELFEDIT_CMDOA_F_INHERIT; } else { /* Non-inherited item */ item->oai_name = oa->oa_name; if ((oa->oa_flags & ELFEDIT_CMDOA_F_VALUE) != 0) { item->oai_vname = oa[1].oa_name; /* Advance users pointer past value element */ (*optarg)++; } else { item->oai_vname = NULL; } item->oai_help = oa->oa_help; item->oai_flags = oa->oa_flags; } /* * The module determines the idmask and excmask fields whether * or not inheritance is in play. */ item->oai_idmask = oa->oa_idmask; item->oai_excmask = oa->oa_excmask; } /* * Return the help string for an option/argument item, as returned * by elfedit_next_optarg(). This routine handles the details of * knowing whether the string is provided by elfedit itself (inherited), * or needs to be translated by the module. */ const char * elfedit_optarg_helpstr(elfeditGC_module_t *mod, elfedit_optarg_item_t *item) { /* * The help string from an inherited item comes right out * of the main elfedit string table. */ if (item->oai_flags & ELFEDIT_CMDOA_F_INHERIT) return (MSG_INTL((Msg) item->oai_help)); /* * If the string is defined by the module, then we need to * have the module translate it for us. */ return ((* mod->mod_i18nhdl_to_str)(item->oai_help)); } /* * Used by usage_optarg() to insert a character into the output buffer, * advancing the buffer pointer and current column, and reducing the * amount of remaining space. */ static void usage_optarg_insert_ch(int ch, char **cur, size_t *n, size_t *cur_col) { *(*cur)++ = ch; **cur = '\0'; (*n)--; (*cur_col)++; } /* * Used by usage_optarg() to insert a string into the output * buffer, advancing the buffer pointer and current column, and reducing * the amount of remaining space. */ static void usage_optarg_insert_str(char **cur, size_t *n, size_t *cur_col, const char *format, ...) { size_t len; va_list args; va_start(args, format); len = vsnprintf(*cur, *n, format, args); va_end(args); *cur += len; *n -= len; *cur_col += len; } /* * Used by usage_optarg() to insert an optarg item string into the output * buffer, advancing the buffer pointer and current column, and reducing * the amount of remaining space. */ static void usage_optarg_insert_item(elfedit_optarg_item_t *item, char **cur, size_t *n, size_t *cur_col) { size_t len; if (item->oai_flags & ELFEDIT_CMDOA_F_VALUE) { len = snprintf(*cur, *n, MSG_ORIG(MSG_STR_HLPOPTARG2), item->oai_name, item->oai_vname); } else { len = snprintf(*cur, *n, MSG_ORIG(MSG_STR_HLPOPTARG), item->oai_name); } *cur += len; *n -= len; *cur_col += len; } /* * Write the options/arguments to the usage string. * * entry: * main_buf_n - Size of main buffer from which buf and buf_n are * allocated. * buf - Address of pointer to where next item is to be placed. * buf_n - Address of count of remaining bytes in buffer * buf_cur_col - Address of current output column for current line * of generated string. * optarg - Options list * isopt - True if these are options, false for arguments. * wrap_str - String to indent wrapped lines. If NULL, lines * are not wrapped */ static void usage_optarg(size_t main_buf_n, char **buf, size_t *buf_n, size_t *buf_cur_col, elfedit_cmd_optarg_t *optarg, int isopt, const char *wrap_str) { /* * An option can be combined into a simple format if it lacks * these flags and is only one character in length. */ static const elfedit_cmd_oa_flag_t exflags = (ELFEDIT_CMDOA_F_VALUE | ELFEDIT_CMDOA_F_MULT); /* * A static buffer, which is grown as needed to accomodate * the maximum usage string seen. */ static STRBUF simple_str; char *cur = *buf; size_t n = *buf_n; size_t cur_col = *buf_cur_col; int len; int use_simple = 0; elfedit_optarg_item_t item; elfedit_cmd_oa_mask_t optmask = 0; int use_bkt; /* * If processing options, pull the 1-character ones that don't have * an associated value and don't have any mutual exclusion issues into * a single combination string to go at the beginning of the usage. */ if (isopt) { elfedit_cmd_optarg_t *tmp_optarg = optarg; char *s; /* * The simple string is guaranteed to fit in the same * amount of space reserved for the main buffer. */ strbuf_ensure_size(&simple_str, main_buf_n); s = simple_str.buf; *s++ = ' '; *s++ = '['; *s++ = '-'; while (tmp_optarg->oa_name != NULL) { elfedit_next_optarg(&tmp_optarg, &item); if (((item.oai_flags & exflags) == 0) && (item.oai_name[2] == '\0') && (item.oai_excmask == 0)) { optmask |= item.oai_idmask; *s++ = item.oai_name[1]; } } /* * If we found more than one, then finish the string and * add it. Don't do this for a single option, because * it looks better in that case if the option shows up * in alphabetical order rather than being hoisted. */ use_simple = (s > (simple_str.buf + 4)); if (use_simple) { *s++ = ']'; *s++ = '\0'; usage_optarg_insert_str(&cur, &n, &cur_col, MSG_ORIG(MSG_STR_HLPOPTARG), simple_str.buf); } else { /* Not using it, so reset the cumulative options mask */ optmask = 0; } } while (optarg->oa_name != NULL) { elfedit_next_optarg(&optarg, &item); if (isopt) { /* * If this is an option that was pulled into the * combination string above, then skip over it. */ if (use_simple && ((item.oai_flags & exflags) == 0) && (item.oai_name[2] == '\0') && (item.oai_excmask == 0)) continue; /* * If this is a mutual exclusion option that was * picked up out of order by a previous iteration * of this loop, then skip over it. */ if ((optmask & item.oai_idmask) != 0) continue; /* Add this item to the accumulating options mask */ optmask |= item.oai_idmask; } /* Wrap line, or insert blank separator */ if ((wrap_str != NULL) && (cur_col > USAGE_WRAP_COL)) { len = snprintf(cur, n, MSG_ORIG(MSG_FMT_WRAPUSAGE), wrap_str); cur += len; n -= len; cur_col = len - 1; /* Don't count the newline */ } else { usage_optarg_insert_ch(' ', &cur, &n, &cur_col); } use_bkt = (item.oai_flags & ELFEDIT_CMDOA_F_OPT) || isopt; if (use_bkt) usage_optarg_insert_ch('[', &cur, &n, &cur_col); /* Add the item to the buffer */ usage_optarg_insert_item(&item, &cur, &n, &cur_col); /* * If this item has a non-zero mutual exclusion mask, * then look for the other items and display them all * together with alternation (|). Note that plain arguments * cannot have a non-0 exclusion mask, so this is * effectively options-only (isopt != 0). */ if (item.oai_excmask != 0) { elfedit_cmd_optarg_t *tmp_optarg = optarg; elfedit_optarg_item_t tmp_item; /* * When showing alternation, elipses for multiple * copies need to appear inside the [] brackets. */ if (item.oai_flags & ELFEDIT_CMDOA_F_MULT) usage_optarg_insert_str(&cur, &n, &cur_col, MSG_ORIG(MSG_STR_ELIPSES)); while (tmp_optarg->oa_name != NULL) { elfedit_next_optarg(&tmp_optarg, &tmp_item); if ((item.oai_excmask & tmp_item.oai_idmask) == 0) continue; usage_optarg_insert_str(&cur, &n, &cur_col, MSG_ORIG(MSG_STR_SP_BAR_SP)); usage_optarg_insert_item(&tmp_item, &cur, &n, &cur_col); /* * Add it to the mask of seen options. * This will keep us from showing it twice. */ optmask |= tmp_item.oai_idmask; } } if (use_bkt) usage_optarg_insert_ch(']', &cur, &n, &cur_col); /* * If alternation was not shown above (non-zero exclusion mask) * then the elipses for multiple copies are shown outside * any [] brackets. */ if ((item.oai_excmask == 0) && (item.oai_flags & ELFEDIT_CMDOA_F_MULT)) usage_optarg_insert_str(&cur, &n, &cur_col, MSG_ORIG(MSG_STR_ELIPSES)); } *buf = cur; *buf_n = n; *buf_cur_col = cur_col; } /* * Format the usage string for a command into a static buffer and * return the pointer to the user. The resultant string is valid * until the next call to this routine, and which point it * will be overwritten or the memory is freed. * * entry: * mod, cmd - Module and command definitions for command to be described * wrap_str - NULL, or string to be used to indent when * lines are wrapped. If NULL, no wrapping is done, and * all output is on a single line. * cur_col - Starting column at which the string will be displayed. * Ignored if wrap_str is NULL. */ const char * elfedit_format_command_usage(elfeditGC_module_t *mod, elfeditGC_cmd_t *cmd, const char *wrap_str, size_t cur_col) { /* * A static buffer, which is grown as needed to accomodate * the maximum usage string seen. */ static STRBUF str; elfedit_cmd_optarg_t *optarg; size_t len, n, elipses_len; char *cur; elfedit_optarg_item_t item; /* * Estimate a worst case size for the usage string: * - module name * - lengths of the strings * - every option or argument is enclosed in brackets * - space in between each item, with an alternation (" | ") * - elipses will be displayed with each option and argument */ n = strlen(mod->mod_name) + strlen(cmd->cmd_name[0]) + 6; elipses_len = strlen(MSG_ORIG(MSG_STR_ELIPSES)); if ((optarg = cmd->cmd_opt) != NULL) while (optarg->oa_name != NULL) { elfedit_next_optarg(&optarg, &item); n += strlen(item.oai_name) + 5 + elipses_len; } if ((optarg = cmd->cmd_args) != NULL) while (optarg->oa_name != NULL) { elfedit_next_optarg(&optarg, &item); n += strlen(item.oai_name) + 5 + elipses_len; } n++; /* Null termination */ /* * If wrapping lines, we insert a newline and then wrap_str * every USAGE_WRAP_COL characters. */ if (wrap_str != NULL) n += ((n + USAGE_WRAP_COL) / USAGE_WRAP_COL) * (strlen(wrap_str) + 1); strbuf_ensure_size(&str, n); /* Command name */ cur = str.buf; n = str.n; if (strcmp(mod->mod_name, MSG_ORIG(MSG_MOD_SYS)) == 0) len = snprintf(cur, n, MSG_ORIG(MSG_FMT_SYSCMD), cmd->cmd_name[0]); else len = snprintf(cur, n, MSG_ORIG(MSG_FMT_MODCMD), mod->mod_name, cmd->cmd_name[0]); cur += len; n -= len; cur_col += len; if (cmd->cmd_opt != NULL) usage_optarg(str.n, &cur, &n, &cur_col, cmd->cmd_opt, 1, wrap_str); if (cmd->cmd_args != NULL) usage_optarg(str.n, &cur, &n, &cur_col, cmd->cmd_args, 0, wrap_str); return (str.buf); } /* * Wrapper on elfedit_msg() that issues an ELFEDIT_MSG_USAGE * error giving usage information for the command currently * referenced by state.cur_cmd. */ void elfedit_command_usage(void) { elfedit_msg(ELFEDIT_MSG_CMDUSAGE, MSG_INTL(MSG_USAGE_CMD), elfedit_format_command_usage(state.cur_cmd->ucmd_mod, state.cur_cmd->ucmd_cmd, NULL, 0)); } /* * This function allows the loadable modules to get the command line * flags. */ elfedit_flag_t elfedit_flags(void) { return (state.flags); } /* * This function is used to register a per-command invocation output style * that will momentarily override the global output style for the duration * of the current command. This function must only be called by an * active command. * * entry: * str - One of the valid strings for the output style */ void elfedit_set_cmd_outstyle(const char *str) { if ((state.cur_cmd != NULL) && (str != NULL)) { if (elfedit_atooutstyle(str, &state.cur_cmd->ucmd_ostyle) == 0) elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_BADOSTYLE), str); state.cur_cmd->ucmd_ostyle_set = 1; } } /* * This function allows the loadable modules to get the output style. */ elfedit_outstyle_t elfedit_outstyle(void) { /* * If there is an active per-command output style, * return it. */ if ((state.cur_cmd != NULL) && (state.cur_cmd->ucmd_ostyle_set)) return (state.cur_cmd->ucmd_ostyle); return (state.outstyle); } /* * Return the command descriptor of the currently executing command. * For use only by the modules or code called by the modules. */ elfeditGC_cmd_t * elfedit_curcmd(void) { return (state.cur_cmd->ucmd_cmd); } /* * Build a dynamically allocated elfedit_obj_state_t struct that * contains a cache of the ELF file contents. This pre-chewed form * is fed to each command, reducing the amount of ELF boilerplate * code each command needs to contain. * * entry: * file - Name of file to process * * exit: * Fills state.elf with the necessary information for the open file. * * note: The resulting elfedit_obj_state_t is allocated from a single * piece of memory, such that a single call to free() suffices * to release it as well as any memory it references. */ static void init_obj_state(const char *file) { int fd; Elf *elf; int open_flag; /* * In readonly mode, we open the file readonly so that it is * impossible to modify the file by accident. This also allows * us to access readonly files, perhaps in a case where we don't * intend to change it. * * We always use ELF_C_RDWR with elf_begin(), even in a readonly * session. This allows us to modify the in-memory image, which * can be useful when examining a file, even though we don't intend * to modify the on-disk data. The file is not writable in * this case, and we don't call elf_update(), so it is safe to do so. */ open_flag = ((state.flags & ELFEDIT_F_READONLY) ? O_RDONLY : O_RDWR); if ((fd = open(file, open_flag)) == -1) { int err = errno; elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTOPNFILE), file, strerror(err)); } (void) elf_version(EV_CURRENT); elf = elf_begin(fd, ELF_C_RDWR, NULL); if (elf == NULL) { (void) close(fd); elfedit_elferr(file, MSG_ORIG(MSG_ELF_BEGIN)); /*NOTREACHED*/ } /* We only handle standalone ELF files */ switch (elf_kind(elf)) { case ELF_K_AR: (void) close(fd); elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_NOAR), file); break; case ELF_K_ELF: break; default: (void) close(fd); elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_UNRECELFFILE), file); break; } /* * Tell libelf that we take responsibility for object layout. * Otherwise, it will compute "proper" values for layout and * alignment fields, and these values can overwrite the values * set in the elfedit session. We are modifying existing * objects --- the layout concerns have already been dealt * with when the object was built. */ (void) elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT); /* Fill in state.elf.obj_state */ state.elf.elfclass = gelf_getclass(elf); switch (state.elf.elfclass) { case ELFCLASS32: elfedit32_init_obj_state(file, fd, elf); break; case ELFCLASS64: elfedit64_init_obj_state(file, fd, elf); break; default: (void) close(fd); elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_BADELFCLASS), file); break; } } #ifdef DEBUG_MODULE_LIST /* * Debug routine. Dump the module list to stdout. */ static void dbg_module_list(char *title) { MODLIST_T *m; printf("\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 : ""); } printf("\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(); } } /* * Given the pointer to the character following a '\' character in * a C style literal, return the ASCII character code it represents, * and advance the string pointer to the character following the last * character in the escape sequence. * * entry: * str - Address of string pointer to first character following * the backslash. * * exit: * If the character is not valid, an error is thrown and this routine * does not return to its caller. Otherwise, it returns the ASCII * code for the translated character, and *str has been advanced. */ static int translate_c_esc(char **str) { char *s = *str; int ch; int i; ch = *s++; switch (ch) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* Octal constant: There can be up to 3 digits */ ch -= '0'; for (i = 0; i < 2; i++) { if ((*s < '0') || (*s > '7')) break; ch = (ch << 3) + (*s++ - '0'); } break; /* * There are some cases where ch already has the desired value. * These cases exist simply to remove the special meaning that * character would otherwise have. We need to match them to * prevent them from falling into the default error case. */ case '\\': case '\'': case '"': break; default: elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_BADCESC), ch); break; } *str = s; return (ch); } /* * Prepare a GETTOK_STATE struct for gettok(). * * entry: * gettok_state - gettok state block to use * str - Writable buffer to tokenize. Note that gettok() * is allowed to change the contents of this buffer. * inc_null_final - If the line ends in whitespace instead of * immediately hitting a NULL, and inc_null_final is TRUE, * then a null final token is generated. Otherwise trailing * whitespace is ignored. */ static void gettok_init(GETTOK_STATE *gettok_state, char *buf, int inc_null_final) { gettok_state->gtok_buf = gettok_state->gtok_cur_buf = buf; gettok_state->gtok_inc_null_final = inc_null_final; gettok_state->gtok_null_seen = 0; } /* * Locate the next token from the buffer. * * entry: * gettok_state - State of gettok() operation. Initialized * by gettok_init(), and passed to gettok(). * * exit: * If a token is found, gettok_state->gtok_last_token is filled in * with the details and True (1) is returned. If no token is found, * False (1) is returned, and the contents of * gettok_state->gtok_last_token are undefined. * * note: * - The token returned references the memory in gettok_state->gtok_buf. * The caller should not modify the buffer until all such * pointers have been discarded. * - This routine will modify the contents of gettok_state->gtok_buf * as necessary to remove quotes and eliminate escape * (\)characters. */ static int gettok(GETTOK_STATE *gettok_state) { char *str = gettok_state->gtok_cur_buf; char *look; int quote_ch = '\0'; /* Skip leading whitespace */ while (isspace(*str)) str++; if (*str == '\0') { /* * If user requested it, and there was whitespace at the * end, then generate one last null token. */ if (gettok_state->gtok_inc_null_final && !gettok_state->gtok_null_seen) { gettok_state->gtok_inc_null_final = 0; gettok_state->gtok_null_seen = 1; gettok_state->gtok_last_token.tok_str = str; gettok_state->gtok_last_token.tok_len = 0; gettok_state->gtok_last_token.tok_line_off = str - gettok_state->gtok_buf; return (1); } gettok_state->gtok_null_seen = 1; return (0); } /* * Read token: The standard delimiter is whitespace, but * we honor either single or double quotes. Also, we honor * backslash escapes. */ gettok_state->gtok_last_token.tok_str = look = str; gettok_state->gtok_last_token.tok_line_off = look - gettok_state->gtok_buf; for (; *look; look++) { if (*look == quote_ch) { /* Terminates active quote */ quote_ch = '\0'; continue; } if (quote_ch == '\0') { /* No quote currently active */ if ((*look == '\'') || (*look == '"')) { quote_ch = *look; /* New active quote */ continue; } if (isspace(*look)) break; } /* * The semantics of the backslash character depends on * the quote style in use: * - Within single quotes, backslash is not * an escape character, and is taken literally. * - If outside of quotes, the backslash is an escape * character. The backslash is ignored and the * following character is taken literally, losing * any special properties it normally has. * - Within double quotes, backslash works like a * backslash escape within a C literal. Certain * escapes are recognized and replaced with their * special character. Any others are an error. */ if (*look == '\\') { if (quote_ch == '\'') { *str++ = *look; continue; } look++; if (*look == '\0') { /* Esc applied to NULL term? */ elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_ESCEOL)); /*NOTREACHED*/ } if (quote_ch == '"') { *str++ = translate_c_esc(&look); look--; /* for() will advance by 1 */ continue; } } if (look != str) *str = *look; str++; } /* Don't allow unterminated quoted tokens */ if (quote_ch != '\0') elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_UNTERMQUOTE), quote_ch); gettok_state->gtok_last_token.tok_len = str - gettok_state->gtok_last_token.tok_str; gettok_state->gtok_null_seen = *look == '\0'; if (!gettok_state->gtok_null_seen) look++; *str = '\0'; gettok_state->gtok_cur_buf = look; #ifdef DEBUG_GETTOK printf("GETTOK >"); elfedit_str_to_c_literal(gettok_state->gtok_last_token.tok_str, elfedit_write); printf("< \tlen(%d) offset(%d)\n", gettok_state->gtok_last_token.tok_len, gettok_state->gtok_last_token.tok_line_off); #endif return (1); } /* * Tokenize the user command string, and return a pointer to the * TOK_STATE buffer maintained by this function. That buffer contains * the tokenized strings. * * entry: * user_cmd_str - String to tokenize * len - # of characters in user_cmd_str to examine. If * (len < 0), then the complete string is processed * stopping with the NULL termination. Otherwise, * processing stops after len characters, and any * remaining characters are ignored. * inc_null_final - If True, and if user_cmd_str has whitespace * at the end following the last non-null token, then * a final null token will be included. If False, null * tokens are ignored. * * note: * This routine returns pointers to internally allocated memory. * The caller must not alter anything contained in the TOK_STATE * buffer returned. Furthermore, the the contents of TOK_STATE * are only valid until the next call to tokenize_user_cmd(). */ static TOK_STATE * tokenize_user_cmd(const char *user_cmd_str, size_t len, int inc_null_final) { #define INITIAL_TOK_ALLOC 5 /* * As we parse the user command, we need temporary space to * hold the tokens. We do this by dynamically allocating a string * buffer and a token array, and doubling them as necessary. This * is a single threaded application, so static variables suffice. */ static STRBUF str; static TOK_STATE tokst; GETTOK_STATE gettok_state; size_t n; /* * Make a copy we can modify. If (len == 0), take the entire * string. Otherwise limit it to the specified length. */ tokst.tokst_cmd_len = strlen(user_cmd_str); if ((len > 0) && (len < tokst.tokst_cmd_len)) tokst.tokst_cmd_len = len; tokst.tokst_cmd_len++; /* Room for NULL termination */ strbuf_ensure_size(&str, tokst.tokst_cmd_len); (void) strlcpy(str.buf, user_cmd_str, tokst.tokst_cmd_len); /* Trim off any newline character that might be present */ if ((tokst.tokst_cmd_len > 1) && (str.buf[tokst.tokst_cmd_len - 2] == '\n')) { tokst.tokst_cmd_len--; str.buf[tokst.tokst_cmd_len - 1] = '\0'; } /* Tokenize the user command string into tok struct */ gettok_init(&gettok_state, str.buf, inc_null_final); tokst.tokst_str_size = 0; /* Space needed for token strings */ for (tokst.tokst_cnt = 0; gettok(&gettok_state) != 0; tokst.tokst_cnt++) { /* If we need more room, expand the token buffer */ if (tokst.tokst_cnt >= tokst.tokst_bufsize) { n = (tokst.tokst_bufsize == 0) ? INITIAL_TOK_ALLOC : (tokst.tokst_bufsize * 2); tokst.tokst_buf = elfedit_realloc( MSG_INTL(MSG_ALLOC_TOKBUF), tokst.tokst_buf, n * sizeof (*tokst.tokst_buf)); tokst.tokst_bufsize = n; } tokst.tokst_str_size += gettok_state.gtok_last_token.tok_len + 1; tokst.tokst_buf[tokst.tokst_cnt] = gettok_state.gtok_last_token; } /* fold the command token to lowercase */ if (tokst.tokst_cnt > 0) { char *s; for (s = tokst.tokst_buf[0].tok_str; *s; s++) if (isupper(*s)) *s = tolower(*s); } return (&tokst); #undef INITIAL_TOK_ALLOC } /* * Parse the user command string, and put an entry for it at the end * of state.ucmd. */ static void parse_user_cmd(const char *user_cmd_str) { TOK_STATE *tokst; char *s; size_t n; size_t len; USER_CMD_T *ucmd; elfeditGC_module_t *mod; elfeditGC_cmd_t *cmd; /* * Break it into tokens. If there are none, then it is * an empty command and is ignored. */ tokst = tokenize_user_cmd(user_cmd_str, -1, 0); if (tokst->tokst_cnt == 0) return; /* Find the command. Won't return on error */ cmd = elfedit_find_command(tokst->tokst_buf[0].tok_str, 1, &mod); /* * If there is no ELF file being edited, then only commands * from the sys: module are allowed. */ if ((state.file.present == 0) && (strcmp(mod->mod_name, MSG_ORIG(MSG_MOD_SYS)) != 0)) elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_NOFILSYSONLY), mod->mod_name, cmd->cmd_name[0]); /* Allocate, fill in, and insert a USER_CMD_T block */ n = S_DROUND(sizeof (USER_CMD_T)); ucmd = elfedit_malloc(MSG_INTL(MSG_ALLOC_UCMD), n + (sizeof (char *) * (tokst->tokst_cnt - 1)) + tokst->tokst_cmd_len + tokst->tokst_str_size); ucmd->ucmd_next = NULL; ucmd->ucmd_argc = tokst->tokst_cnt - 1; /*LINTED E_BAD_PTR_CAST_ALIGN*/ ucmd->ucmd_argv = (const char **)(n + (char *)ucmd); ucmd->ucmd_orig_str = (char *)(ucmd->ucmd_argv + ucmd->ucmd_argc); (void) strncpy(ucmd->ucmd_orig_str, user_cmd_str, tokst->tokst_cmd_len); ucmd->ucmd_mod = mod; ucmd->ucmd_cmd = cmd; ucmd->ucmd_ostyle_set = 0; s = ucmd->ucmd_orig_str + tokst->tokst_cmd_len; for (n = 1; n < tokst->tokst_cnt; n++) { len = tokst->tokst_buf[n].tok_len + 1; ucmd->ucmd_argv[n - 1] = s; (void) strncpy(s, tokst->tokst_buf[n].tok_str, len); s += len; } if (state.ucmd.list == NULL) { state.ucmd.list = state.ucmd.tail = ucmd; } else { state.ucmd.tail->ucmd_next = ucmd; state.ucmd.tail = ucmd; } state.ucmd.n++; } /* * Copy infile to a new file with the name given by outfile. */ static void create_outfile(const char *infile, const char *outfile) { pid_t pid; int statloc; struct stat statbuf; pid = fork(); switch (pid) { case -1: /* Unable to create process */ { int err = errno; elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTFORK), strerror(err)); } /*NOTREACHED*/ return; case 0: (void) execl(MSG_ORIG(MSG_STR_BINCP), MSG_ORIG(MSG_STR_BINCP), infile, outfile, NULL); /* * exec() only returns on error. This is the child process, * so we want to stay away from the usual error mechanism * and handle things directly. */ { int err = errno; (void) fprintf(stderr, MSG_INTL(MSG_ERR_CNTEXEC), MSG_ORIG(MSG_STR_ELFEDIT), MSG_ORIG(MSG_STR_BINCP), strerror(err)); } exit(1); /*NOTREACHED*/ } /* This is the parent: Wait for the child to terminate */ if (waitpid(pid, &statloc, 0) != pid) { int err = errno; elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTWAIT), strerror(err)); } /* * If the child failed, then terminate the process. There is no * need for an error message, because the child will have taken * care of that. */ if (!WIFEXITED(statloc) || (WEXITSTATUS(statloc) != 0)) exit(1); /* Make sure the copy allows user write access */ if (stat(outfile, &statbuf) == -1) { int err = errno; (void) unlink(outfile); elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTSTAT), outfile, strerror(err)); } if ((statbuf.st_mode & S_IWUSR) == 0) { /* Only keep permission bits, and add user write */ statbuf.st_mode |= S_IWUSR; statbuf.st_mode &= 07777; /* Only keep the permission bits */ if (chmod(outfile, statbuf.st_mode) == -1) { int err = errno; (void) unlink(outfile); elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTCHMOD), outfile, strerror(err)); } } } /* * Given a module path string, determine how long the resulting path will * be when all % tokens have been expanded. * * entry: * path - Path for which expanded length is desired * origin_root - Root of $ORIGIN tree containing running elfedit program * * exit: * Returns the value strlen() will give for the expanded path. */ static size_t modpath_strlen(const char *path, const char *origin_root) { size_t len = 0; const char *s; s = path; len = 0; for (s = path; *s != '\0'; s++) { if (*s == '%') { s++; switch (*s) { case 'i': /* ISA of running elfedit */ len += strlen(isa_i_str); break; case 'I': /* "" for 32-bit, same as %i for 64 */ len += strlen(isa_I_str); break; case 'o': /* Insert default path */ len += modpath_strlen(MSG_ORIG(MSG_STR_MODPATH), origin_root); break; case 'r': /* root of tree with running elfedit */ len += strlen(origin_root); break; case '%': /* %% is reduced to just '%' */ len++; break; default: /* All other % codes are reserved */ elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_BADPATHCODE), *s); /*NOTREACHED*/ break; } } else { /* Non-% character passes straight through */ len++; } } return (len); } /* * Given a module path string, and a buffer large enough to hold the results, * fill the buffer with the expanded path. * * entry: * path - Path for which expanded length is desired * origin_root - Root of tree containing running elfedit program * buf - Buffer to receive the result. buf must as large or larger * than the value given by modpath_strlen(). * * exit: * Returns pointer to location following the last character * written to buf. A NULL byte is written to that address. */ static char * modpath_expand(const char *path, const char *origin_root, char *buf) { size_t len; const char *cp_str; for (; *path != '\0'; path++) { if (*path == '%') { path++; cp_str = NULL; switch (*path) { case 'i': /* ISA of running elfedit */ cp_str = isa_i_str; break; case 'I': /* "" for 32-bit, same as %i for 64 */ cp_str = isa_I_str; break; case 'o': /* Insert default path */ buf = modpath_expand(MSG_ORIG(MSG_STR_MODPATH), origin_root, buf); break; case 'r': cp_str = origin_root; break; case '%': /* %% is reduced to just '%' */ *buf++ = *path; break; default: /* All other % codes are reserved */ elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_BADPATHCODE), *path); /*NOTREACHED*/ break; } if ((cp_str != NULL) && ((len = strlen(cp_str)) > 0)) { bcopy(cp_str, buf, len); buf += len; } } else { /* Non-% character passes straight through */ *buf++ = *path; } } *buf = '\0'; return (buf); } /* * Establish the module search path: state.modpath * * The path used comes from the following sources, taking the first * one that has a value, and ignoring any others: * * - ELFEDIT_PATH environment variable * - -L command line argument * - Default value * * entry: * path - NULL, or the value of the -L command line argument * * exit: * state.modpath has been filled in */ static void establish_modpath(const char *cmdline_path) { char origin_root[PATH_MAX + 1]; /* Where elfedit binary is */ const char *path; /* Initial path */ char *expath; /* Expanded path */ size_t len; char *src, *dst; path = getenv(MSG_ORIG(MSG_STR_ENVVAR)); if (path == NULL) path = cmdline_path; if (path == NULL) path = MSG_ORIG(MSG_STR_MODPATH); /* * Root of tree containing running for running program. 32-bit elfedit * is installed in /usr/bin, and 64-bit elfedit is one level lower * in an ISA-specific subdirectory. So, we find the root by * getting the $ORGIN of the current running program, and trimming * off the last 2 (32-bit) or 3 (64-bit) directories. * * On a standard system, this will simply yield '/'. However, * doing it this way allows us to run elfedit from a proto area, * and pick up modules from the same proto area instead of those * installed on the system. */ if (dlinfo(RTLD_SELF, RTLD_DI_ORIGIN, &origin_root) == -1) elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_CNTGETORIGIN)); len = (sizeof (char *) == 8) ? 3 : 2; src = origin_root + strlen(origin_root); while ((src > origin_root) && (len > 0)) { if (*(src - 1) == '/') len--; src--; } *src = '\0'; /* * Calculate space needed to hold expanded path. Note that * this assumes that MSG_STR_MODPATH will never contain a '%o' * code, and so, the expansion is not recursive. The codes allowed * are: * %i - ISA of running elfedit (sparc, sparcv9, etc) * %I - 64-bit ISA: Same as %i for 64-bit versions of elfedit, * but yields empty string for 32-bit ISAs. * %o - The original (default) path. * %r - Root of tree holding elfedit program. * %% - A single % * * A % followed by anything else is an error. This allows us to * add new codes in the future without backward compatability issues. */ len = modpath_strlen(path, origin_root); expath = elfedit_malloc(MSG_INTL(MSG_ALLOC_EXPATH), len + 1); (void) modpath_expand(path, origin_root, expath); /* * Count path segments, eliminate extra '/', and replace ':' * with NULL. */ state.modpath.n = 1; for (src = dst = expath; *src; src++) { if (*src == '/') { switch (*(src + 1)) { case '/': case ':': case '\0': continue; } } if (*src == ':') { state.modpath.n++; *dst = '\0'; } else if (src != dst) { *dst = *src; } dst++; } if (src != dst) *dst = '\0'; state.modpath.seg = elfedit_malloc(MSG_INTL(MSG_ALLOC_PATHARR), sizeof (state.modpath.seg[0]) * state.modpath.n); src = expath; for (len = 0; len < state.modpath.n; len++) { if (*src == '\0') { state.modpath.seg[len] = MSG_ORIG(MSG_STR_DOT); src++; } else { state.modpath.seg[len] = src; src += strlen(src) + 1; } } } /* * When interactive (reading commands from a tty), we catch * SIGINT in order to restart the outer command loop. */ /*ARGSUSED*/ static void sigint_handler(int sig, siginfo_t *sip, void *ucp) { /* Jump to the outer loop to resume */ if (state.msg_jbuf.active) { state.msg_jbuf.active = 0; siglongjmp(state.msg_jbuf.env, 1); } } static void usage(int full) { elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_BRIEF)); if (full) { elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL1)); elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL2)); elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL3)); elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL4)); elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL5)); elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL6)); elfedit_msg(ELFEDIT_MSG_USAGE, MSG_INTL(MSG_USAGE_DETAIL_LAST)); } elfedit_exit(2); } /* * In order to complete commands, we need to know about them, * which means that we need to force all the modules to be * loaded. This is a relatively expensive operation, so we use * this function, which avoids doing it more than once in a session. */ static void elfedit_cpl_load_modules(void) { static int loaded; if (!loaded) { elfedit_load_modpath(); loaded = 1; /* Don't do it again */ } } /* * Compare the token to the given string, and if they share a common * initial sequence, add the tail of string to the tecla command completion * buffer: * * entry: * cpldata - Current completion state * str - String to match against token * casefold - True to allow case insensitive completion, False * if case must match exactly. */ void elfedit_cpl_match(void *cpldata, const char *str, int casefold) { ELFEDIT_CPL_STATE *cstate = (ELFEDIT_CPL_STATE *) cpldata; const char *cont_suffix; const char *type_suffix; /* * Reasons to return immediately: * - NULL strings have no completion value * - The string is shorter than the existing item being completed */ if ((str == NULL) || (*str == '\0') || ((cstate->ecpl_token_len != 0) && ((strlen(str) < cstate->ecpl_token_len)))) return; /* If the string does not share the existing prefix, don't use it */ if (casefold) { if (strncasecmp(cstate->ecpl_token_str, str, cstate->ecpl_token_len) != 0) return; } else { if (strncmp(cstate->ecpl_token_str, str, cstate->ecpl_token_len) != 0) return; } if (cstate->ecpl_add_mod_colon) { cont_suffix = type_suffix = MSG_ORIG(MSG_STR_COLON); } else { cont_suffix = MSG_ORIG(MSG_STR_SPACE); type_suffix = NULL; } (void) cpl_add_completion(cstate->ecpl_cpl, cstate->ecpl_line, cstate->ecpl_word_start, cstate->ecpl_word_end, str + cstate->ecpl_token_len, type_suffix, cont_suffix); } /* * Convenience wrapper on elfedit_cpl_match(): Format an unsigned * 32-bit integer as a string and enter the result for command completion. */ void elfedit_cpl_ndx(void *cpldata, uint_t ndx) { Conv_inv_buf_t buf; (void) snprintf(buf.buf, sizeof (buf.buf), MSG_ORIG(MSG_FMT_WORDVAL), ndx); elfedit_cpl_match(cpldata, buf.buf, 0); } /* * Compare the token to the names of the commands from the given module, * and if they share a common initial sequence, add the tail of string * to the tecla command completion buffer: * * entry: * tok_buf - Token user has entered * tok_len - strlen(tok_buf) * mod - Module definition from which commands should be matched * cpl, line, word_start, word_end, cont_suffix - As documented * for gl_get_line() and cpl_add_completion. */ static void match_module_cmds(ELFEDIT_CPL_STATE *cstate, elfeditGC_module_t *mod) { elfeditGC_cmd_t *cmd; const char **cmd_name; for (cmd = mod->mod_cmds; cmd->cmd_func != NULL; cmd++) for (cmd_name = cmd->cmd_name; *cmd_name; cmd_name++) elfedit_cpl_match(cstate, *cmd_name, 1); } /* * Compare the token to the known module names, and add those that * match to the list of alternatives via elfedit_cpl_match(). * * entry: * load_all_modules - If True, causes all modules to be loaded * before processing is done. If False, only the modules * currently seen will be used. */ void elfedit_cpl_module(void *cpldata, int load_all_modules) { ELFEDIT_CPL_STATE *cstate = (ELFEDIT_CPL_STATE *) cpldata; MODLIST_T *modlist; if (load_all_modules) elfedit_cpl_load_modules(); for (modlist = state.modlist; modlist != NULL; modlist = modlist->ml_next) { elfedit_cpl_match(cstate, modlist->ml_mod->mod_name, 1); } } /* * Compare the token to all the known commands, and add those that * match to the list of alternatives. * * note: * This routine will force modules to be loaded as necessary to * obtain the names it needs to match. */ void elfedit_cpl_command(void *cpldata) { ELFEDIT_CPL_STATE *cstate = (ELFEDIT_CPL_STATE *) cpldata; ELFEDIT_CPL_STATE colon_state; const char *colon_pos; MODLIST_T *modlist; MODLIST_T *insdef; char buf[128]; /* * Is there a colon in the command? If so, locate its offset within * the raw input line. */ for (colon_pos = cstate->ecpl_token_str; *colon_pos && (*colon_pos != ':'); colon_pos++) ; /* * If no colon was seen, then we are completing a module name, * or one of the commands from 'sys:' */ if (*colon_pos == '\0') { /* * Setting cstate->add_mod_colon tells elfedit_cpl_match() * to add an implicit ':' to the names it matches. We use it * here so the user doesn't have to enter the ':' manually. * Hiding this in the opaque state instead of making it * an argument to that function gives us the ability to * change it later without breaking the published interface. */ cstate->ecpl_add_mod_colon = 1; elfedit_cpl_module(cpldata, 1); cstate->ecpl_add_mod_colon = 0; /* Add bare (no sys: prefix) commands from the sys: module */ match_module_cmds(cstate, elfedit_load_module(MSG_ORIG(MSG_MOD_SYS), 1, 0)); return; } /* * A colon was seen, so we have a module name. Extract the name, * substituting 'sys' for the case where the given name is empty. */ if (colon_pos == 0) (void) strlcpy(buf, MSG_ORIG(MSG_MOD_SYS), sizeof (buf)); else elfedit_strnbcpy(buf, cstate->ecpl_token_str, colon_pos - cstate->ecpl_token_str, sizeof (buf)); /* * Locate the module. If it isn't already loaded, make an explicit * attempt to load it and try again. If a module definition is * obtained, process the commands it supplies. */ modlist = module_loaded(buf, &insdef); if (modlist == NULL) { (void) elfedit_load_module(buf, 0, 0); modlist = module_loaded(buf, &insdef); } if (modlist != NULL) { /* * Make a copy of the cstate, and adjust the line and * token so that the new one starts just past the colon * character. We know that the colon exists because * of the preceeding test that found it. Therefore, we do * not need to test against running off the end of the * string here. */ colon_state = *cstate; while (colon_state.ecpl_line[colon_state.ecpl_word_start] != ':') colon_state.ecpl_word_start++; while (*colon_state.ecpl_token_str != ':') { colon_state.ecpl_token_str++; colon_state.ecpl_token_len--; } /* Skip past the ':' character */ colon_state.ecpl_word_start++; colon_state.ecpl_token_str++; colon_state.ecpl_token_len--; match_module_cmds(&colon_state, modlist->ml_mod); } } /* * Command completion function for use with libtacla. */ /*ARGSUSED1*/ static int cmd_match_fcn(WordCompletion *cpl, void *data, const char *line, int word_end) { const char *argv[ELFEDIT_MAXCPLARGS]; ELFEDIT_CPL_STATE cstate; TOK_STATE *tokst; int ndx; int i; elfeditGC_module_t *mod; elfeditGC_cmd_t *cmd; int num_opt; int opt_term_seen; int skip_one; elfedit_cmd_optarg_t *optarg; elfedit_optarg_item_t item; int ostyle_ndx = -1; /* * For debugging, enable the following block. It tells the tecla * library that the program using is going to write to stdout. * It will put the tty back into normal mode, and it will cause * tecla to redraw the current input line when it gets control back. */ #ifdef DEBUG_CMD_MATCH gl_normal_io(state.input.gl); #endif /* * Tokenize the line up through word_end. The last token in * the list is the one requiring completion. */ tokst = tokenize_user_cmd(line, word_end, 1); if (tokst->tokst_cnt == 0) return (0); /* Set up the cstate block, containing the completion state */ ndx = tokst->tokst_cnt - 1; /* Index of token to complete */ cstate.ecpl_cpl = cpl; cstate.ecpl_line = line; cstate.ecpl_word_start = tokst->tokst_buf[ndx].tok_line_off; cstate.ecpl_word_end = word_end; cstate.ecpl_add_mod_colon = 0; cstate.ecpl_token_str = tokst->tokst_buf[ndx].tok_str; cstate.ecpl_token_len = tokst->tokst_buf[ndx].tok_len; /* * If there is only one token, then we are completing the * command itself. */ if (ndx == 0) { elfedit_cpl_command(&cstate); return (0); } /* * There is more than one token. Use the first one to * locate the definition for the command. If we don't have * a definition for the command, then there's nothing more * we can do. */ cmd = elfedit_find_command(tokst->tokst_buf[0].tok_str, 0, &mod); if (cmd == NULL) return (0); /* * Since we know the command, give them a quick usage message. * It may be that they just need a quick reminder about the form * of the command and the options. */ (void) gl_normal_io(state.input.gl); elfedit_printf(MSG_INTL(MSG_USAGE_CMD), elfedit_format_command_usage(mod, cmd, NULL, 0)); /* * We have a generous setting for ELFEDIT_MAXCPLARGS, so there * should always be plenty of room. If there's not room, we * can't proceed. */ if (ndx >= ELFEDIT_MAXCPLARGS) return (0); /* * Put pointers to the tokens into argv, and determine how * many of the tokens are optional arguments. * * We consider the final optional argument to be the rightmost * argument that starts with a '-'. If a '--' is seen, then * we stop there, and any argument that follows is a plain argument * (even if it starts with '-'). * * We look for an inherited '-o' option, because we are willing * to supply command completion for these values. */ num_opt = 0; opt_term_seen = 0; skip_one = 0; for (i = 0; i < ndx; i++) { argv[i] = tokst->tokst_buf[i + 1].tok_str; if (opt_term_seen || skip_one) { skip_one = 0; continue; } skip_one = 0; ostyle_ndx = -1; if ((strcmp(argv[i], MSG_ORIG(MSG_STR_MINUS_MINUS)) == NULL) || (*argv[i] != '-')) { opt_term_seen = 1; continue; } num_opt = i + 1; /* * If it is a recognised ELFEDIT_CMDOA_F_VALUE option, * then the item following it is the associated value. * Check for this and skip the value. * * At the same time, look for STDOA_OPT_O inherited * options. We want to identify the index of any such * item. Although the option is simply "-o", we are willing * to treat any option that starts with "-o" as a potential * STDOA_OPT_O. This lets us to command completion for things * like "-onum", and is otherwise harmless, the only cost * being a few additional strcmps by the cpl code. */ if ((optarg = cmd->cmd_opt) == NULL) continue; while (optarg->oa_name != NULL) { int is_ostyle_optarg = (optarg->oa_flags & ELFEDIT_CMDOA_F_INHERIT) && (optarg->oa_name == ELFEDIT_STDOA_OPT_O); elfedit_next_optarg(&optarg, &item); if (item.oai_flags & ELFEDIT_CMDOA_F_VALUE) { if (is_ostyle_optarg && (strncmp(argv[i], MSG_ORIG(MSG_STR_MINUS_O), 2) == 0)) ostyle_ndx = i + 1; if (strcmp(item.oai_name, argv[i]) == 0) { num_opt = i + 2; skip_one = 1; break; } /* * If it didn't match "-o" exactly, but it is * ostyle_ndx, then it is a potential combined * STDOA_OPT_O, as discussed above. It counts * as a single argument. */ if (ostyle_ndx == ndx) break; } } } #ifdef DEBUG_CMD_MATCH (void) printf("NDX(%d) NUM_OPT(%d) ostyle_ndx(%d)\n", ndx, num_opt, ostyle_ndx); #endif if (ostyle_ndx != -1) { /* * If ostyle_ndx is one less than ndx, and ndx is * the same as num_opt, then we have a definitive * STDOA_OPT_O inherited outstyle option. We supply * the value strings, and are done. */ if ((ostyle_ndx == (ndx - 1)) && (ndx == num_opt)) { elfedit_cpl_atoconst(&cstate, ELFEDIT_CONST_OUTSTYLE); return (0); } /* * If ostyle is the same as ndx, then we have an option * staring with "-o" that may end up being a STDOA_OPT_O, * and we are still inside that token. In this case, we * supply completion strings that include the leading * "-o" followed by the values, without a space * (i.e. "-onum"). We then fall through, allowing any * other options starting with "-o" to be added * below. elfedit_cpl_match() will throw out the incorrect * options, so it is harmless to add these extra items in * the worst case, and useful otherwise. */ if (ostyle_ndx == ndx) elfedit_cpl_atoconst(&cstate, ELFEDIT_CONST_OUTSTYLE_MO); } /* * If (ndx <= num_opt), then the token needing completion * is an option. If the leading '-' is there, then we should fill * in all of the option alternatives. If anything follows the '-' * though, we assume that the user has already figured out what * option to use, and we leave well enough alone. * * Note that we are intentionally ignoring a related case * where supplying option strings would be legal: In the case * where we are one past the last option (ndx == (num_opt + 1)), * and the current option is an empty string, the argument can * be either a plain argument or an option --- the user needs to * enter the next character before we can tell. It would be * OK to enter the option strings in this case. However, consider * what happens when the first plain argument to the command does * not provide any command completion (e.g. it is a plain integer). * In this case, tecla will see that all the alternatives start * with '-', and will insert a '-' into the input. If the user * intends the next argument to be plain, they will have to delete * this '-', which is annoying. Worse than that, they may be confused * by it, and think that the plain argument is not allowed there. * The best solution is to not supply option strings unless the * user first enters the '-'. */ if ((ndx <= num_opt) && (argv[ndx - 1][0] == '-')) { if ((optarg = cmd->cmd_opt) != NULL) { while (optarg->oa_name != NULL) { elfedit_next_optarg(&optarg, &item); elfedit_cpl_match(&cstate, item.oai_name, 1); } } return (0); } /* * At this point we know that ndx and num_opt are not equal. * If num_opt is larger than ndx, then we have an ELFEDIT_CMDOA_F_VALUE * argument at the end, and the following value has not been entered. * * If ndx is greater than num_opt, it means that we are looking * at a plain argument (or in the case where (ndx == (num_opt + 1)), * a *potential* plain argument. * * If the command has a completion function registered, then we * hand off the remaining work to it. The cmd_cplfunc field is * the generic definition. We need to cast it to the type that matches * the proper ELFCLASS before calling it. */ if (state.elf.elfclass == ELFCLASS32) { elfedit32_cmdcpl_func_t *cmdcpl_func = (elfedit32_cmdcpl_func_t *)cmd->cmd_cplfunc; if (cmdcpl_func != NULL) (* cmdcpl_func)(state.elf.obj_state.s32, &cstate, ndx, argv, num_opt); } else { elfedit64_cmdcpl_func_t *cmdcpl_func = (elfedit64_cmdcpl_func_t *)cmd->cmd_cplfunc; if (cmdcpl_func != NULL) (* cmdcpl_func)(state.elf.obj_state.s64, &cstate, ndx, argv, num_opt); } return (0); } /* * Read a line of input from stdin, and return pointer to it. * * This routine uses a private buffer, so the contents of the returned * string are only good until the next call. */ static const char * read_cmd(void) { char *s; if (state.input.full_tty) { state.input.in_tecla = TRUE; s = gl_get_line(state.input.gl, MSG_ORIG(MSG_STR_PROMPT), NULL, -1); state.input.in_tecla = FALSE; /* * gl_get_line() returns NULL for EOF or for error. EOF is fine, * but we need to catch and report anything else. Since * reading from stdin is critical to our operation, an * error implies that we cannot recover and must exit. */ if ((s == NULL) && (gl_return_status(state.input.gl) == GLR_ERROR)) { elfedit_msg(ELFEDIT_MSG_FATAL, MSG_INTL(MSG_ERR_GLREAD), gl_error_message(state.input.gl, NULL, 0)); } } else { /* * This should be a dynamically sized buffer, but for now, * I'm going to take a simpler path. */ static char cmd_buf[ELFEDIT_MAXCMD + 1]; s = fgets(cmd_buf, sizeof (cmd_buf), stdin); } /* Return user string, or 'quit' on EOF */ return (s ? s : MSG_ORIG(MSG_SYS_CMD_QUIT)); } int main(int argc, char **argv, char **envp) { /* * Note: This function can use setjmp()/longjmp() which does * not preserve the values of auto/register variables. Hence, * variables that need their values preserved across a jump must * be marked volatile, or must not be auto/register. * * Volatile can be messy, because it requires explictly casting * away the attribute when passing it to functions, or declaring * those functions with the attribute as well. In a single threaded * program like this one, an easier approach is to make things * static. That can be done here, or by putting things in the * 'state' structure. */ int c, i; int num_batch = 0; char **batch_list = NULL; const char *modpath = NULL; /* * Always have liblddb display unclipped section names. * This global is exported by liblddb, and declared in debug.h. */ dbg_desc->d_extra |= DBG_E_LONG; opterr = 0; while ((c = getopt(argc, argv, MSG_ORIG(MSG_STR_OPTIONS))) != EOF) { switch (c) { case 'a': state.flags |= ELFEDIT_F_AUTOPRINT; break; case 'd': state.flags |= ELFEDIT_F_DEBUG; break; case 'e': /* * Delay parsing the -e options until after the call to * conv_check_native() so that we won't bother loading * modules of the wrong class. */ if (batch_list == NULL) batch_list = elfedit_malloc( MSG_INTL(MSG_ALLOC_BATCHLST), sizeof (*batch_list) * (argc - 1)); batch_list[num_batch++] = optarg; break; case 'L': modpath = optarg; break; case 'o': if (elfedit_atooutstyle(optarg, &state.outstyle) == 0) usage(1); break; case 'r': state.flags |= ELFEDIT_F_READONLY; break; case '?': usage(1); } } /* * We allow 0, 1, or 2 files: * * The no-file case is an extremely limited mode, in which the * only commands allowed to execute come from the sys: module. * This mode exists primarily to allow easy access to the help * facility. * * To get full access to elfedit's capablities, there must * be an input file. If this is not a readonly * session, then an optional second output file is allowed. * * In the case where two files are given and the session is * readonly, use a full usage message, because the simple * one isn't enough for the user to understand their error. * Otherwise, the simple usage message suffices. */ argc = argc - optind; if ((argc == 2) && (state.flags & ELFEDIT_F_READONLY)) usage(1); if (argc > 2) usage(0); state.file.present = (argc != 0); /* * If we have a file to edit, and unless told otherwise by the * caller, we try to run the 64-bit version of this program * when the system is capable of it. If that fails, then we * continue on with the currently running version. * * To force 32-bit execution on a 64-bit host, set the * LD_NOEXEC_64 environment variable to a non-empty value. * * There is no reason to bother with this if in "no file" mode. */ if (state.file.present != 0) (void) conv_check_native(argv, envp); elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_VERSION), (sizeof (char *) == 8) ? 64 : 32); /* * Put a module definition for the builtin system module on the * module list. We know it starts out empty, so we do not have * to go through a more general insertion process than this. */ state.modlist = elfedit_sys_init(ELFEDIT_VER_CURRENT); /* Establish the search path for loadable modules */ establish_modpath(modpath); /* * Now that we are running the final version of this program, * deal with the input/output file(s). */ if (state.file.present == 0) { /* * This is arbitrary --- we simply need to be able to * load modules so that we can access their help strings * and command completion functions. Without a file, we * will refuse to call commands from any module other * than sys. Those commands have been written to be aware * of the case where there is no input file, and are * therefore safe to run. */ state.elf.elfclass = ELFCLASS32; elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_NOFILE)); } else { state.file.infile = argv[optind]; if (argc == 1) { state.file.outfile = state.file.infile; if (state.flags & ELFEDIT_F_READONLY) elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_READONLY)); else elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_INPLACEWARN), state.file.infile); } else { state.file.outfile = argv[optind + 1]; create_outfile(state.file.infile, state.file.outfile); elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_CPFILE), state.file.infile, state.file.outfile); /* * We are editing a copy of the original file that we * just created. If we should exit before the edits are * updated, then we want to unlink this copy so that we * don't leave junk lying around. Once an update * succeeds however, we'll leave it in place even * if an error occurs afterwards. */ state.file.unlink_on_exit = 1; optind++; /* Edit copy instead of the original */ } init_obj_state(state.file.outfile); } /* * Process commands. * * If any -e options were used, then do them and * immediately exit. On error, exit immediately without * updating the target ELF file. On success, the 'write' * and 'quit' commands are implicit in this mode. * * If no -e options are used, read commands from stdin. * quit must be explicitly used. Exit is implicit on EOF. * If stdin is a tty, then errors do not cause the editor * to terminate. Rather, the error message is printed, and the * user prompted to continue. */ if (batch_list != NULL) { /* -e was used */ /* Compile the commands */ for (i = 0; i < num_batch; i++) parse_user_cmd(batch_list[i]); free(batch_list); /* * 'write' and 'quit' are implicit in this mode. * Add them as well. */ if ((state.flags & ELFEDIT_F_READONLY) == 0) parse_user_cmd(MSG_ORIG(MSG_SYS_CMD_WRITE)); parse_user_cmd(MSG_ORIG(MSG_SYS_CMD_QUIT)); /* And run them. This won't return, thanks to the 'quit' */ dispatch_user_cmds(); } else { state.input.is_tty = isatty(fileno(stdin)); state.input.full_tty = state.input.is_tty && isatty(fileno(stdout)); if (state.input.full_tty) { struct sigaction act; act.sa_sigaction = sigint_handler; (void) sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(SIGINT, &act, NULL) == -1) { int err = errno; elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_SIGACTION), strerror(err)); } /* * If pager process exits before we are done * writing, we can see SIGPIPE. Prevent it * from killing the process. */ (void) sigignore(SIGPIPE); /* Open tecla handle for command line editing */ state.input.gl = new_GetLine(ELFEDIT_MAXCMD, ELFEDIT_MAXHIST); /* Register our command completion function */ (void) gl_customize_completion(state.input.gl, NULL, cmd_match_fcn); /* * Make autoprint the default for interactive * sessions. */ state.flags |= ELFEDIT_F_AUTOPRINT; } for (;;) { /* * If this is an interactive session, then use * sigsetjmp()/siglongjmp() to recover from bad * commands and keep going. A non-0 return from * sigsetjmp() means that an error just occurred. * In that case, we simply restart this loop. */ if (state.input.is_tty) { if (sigsetjmp(state.msg_jbuf.env, 1) != 0) { if (state.input.full_tty) gl_abandon_line(state.input.gl); continue; } state.msg_jbuf.active = TRUE; } /* * Force all output out before each command. * This is a no-OP when a tty is in use, but * in a pipeline, it ensures that the block * mode buffering doesn't delay output past * the completion of each command. * * If we didn't do this, the output would eventually * arrive at its destination, but the lag can be * annoying when you pipe the output into a tool * that displays the results in real time. */ (void) fflush(stdout); (void) fflush(stderr); parse_user_cmd(read_cmd()); dispatch_user_cmds(); state.msg_jbuf.active = FALSE; } } /*NOTREACHED*/ return (0); }