6937487be7
Back in 2007, commit 0c97c9d437
("'simple' error message functions by
Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower
overhead call to bb_perror_msg() when only a string was being printed
with no parameters. This saves space for some CPU architectures because
it avoids the overhead of a call to a variadic function. However there
has never been a simple version of bb_error_msg(), and since 2007 many
new calls to bb_perror_msg() have been added that only take a single
parameter and so could have been using bb_simple_perror_message().
This changeset introduces 'simple' versions of bb_info_msg(),
bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and
bb_herror_msg_and_die(), and replaces all calls that only take a
single parameter, or use something like ("%s", arg), with calls to the
corresponding 'simple' version.
Since it is likely that single parameter calls to the variadic functions
may be accidentally reintroduced in the future a new debugging config
option WARN_SIMPLE_MSG has been introduced. This uses some macro magic
which will cause any such calls to generate a warning, but this is
turned off by default to avoid use of the unpleasant macros in normal
circumstances.
This is a large changeset due to the number of calls that have been
replaced. The only files that contain changes other than simple
substitution of function calls are libbb.h, libbb/herror_msg.c,
libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c,
networking/udhcp/common.h and util-linux/mdev.c additonal macros have
been added for logging so that single parameter and multiple parameter
logging variants exist.
The amount of space saved varies considerably by architecture, and was
found to be as follows (for 'defconfig' using GCC 7.4):
Arm: -92 bytes
MIPS: -52 bytes
PPC: -1836 bytes
x86_64: -938 bytes
Note that for the MIPS architecture only an exception had to be made
disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h)
because it made these files larger on MIPS.
Signed-off-by: James Byrne <james.byrne@origamienergy.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
469 lines
10 KiB
C
469 lines
10 KiB
C
/*
|
|
* Copyright (C) 2017 Denys Vlasenko <vda.linux@googlemail.com>
|
|
*
|
|
* Licensed under GPLv2, see file LICENSE in this source tree.
|
|
*/
|
|
//config:config HEXEDIT
|
|
//config: bool "hexedit (21 kb)"
|
|
//config: default y
|
|
//config: help
|
|
//config: Edit file in hexadecimal.
|
|
|
|
//applet:IF_HEXEDIT(APPLET(hexedit, BB_DIR_USR_BIN, BB_SUID_DROP))
|
|
|
|
//kbuild:lib-$(CONFIG_HEXEDIT) += hexedit.o
|
|
|
|
#include "libbb.h"
|
|
|
|
#define ESC "\033"
|
|
#define HOME ESC"[H"
|
|
#define CLEAR ESC"[J"
|
|
#define CLEAR_TILL_EOL ESC"[K"
|
|
#define SET_ALT_SCR ESC"[?1049h"
|
|
#define POP_ALT_SCR ESC"[?1049l"
|
|
|
|
#undef CTRL
|
|
#define CTRL(c) ((c) & (uint8_t)~0x60)
|
|
|
|
struct globals {
|
|
smallint half;
|
|
smallint in_read_key;
|
|
int fd;
|
|
unsigned height;
|
|
unsigned row;
|
|
unsigned pagesize;
|
|
uint8_t *baseaddr;
|
|
uint8_t *current_byte;
|
|
uint8_t *eof_byte;
|
|
off_t size;
|
|
off_t offset;
|
|
/* needs to be zero-inited, thus keeping it in G: */
|
|
char read_key_buffer[KEYCODE_BUFFER_SIZE];
|
|
struct termios orig_termios;
|
|
};
|
|
#define G (*ptr_to_globals)
|
|
#define INIT_G() do { \
|
|
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
|
|
} while (0)
|
|
|
|
//TODO: move to libbb
|
|
#if defined(__x86_64__) || defined(i386)
|
|
# define G_pagesize 4096
|
|
# define INIT_PAGESIZE() ((void)0)
|
|
#else
|
|
# define G_pagesize (G.pagesize)
|
|
# define INIT_PAGESIZE() ((void)(G.pagesize = getpagesize()))
|
|
#endif
|
|
|
|
/* hopefully there aren't arches with PAGE_SIZE > 64k */
|
|
#define G_mapsize (64*1024)
|
|
|
|
/* "12ef5670 (xx )*16 _1_3_5_7_9abcdef\n"NUL */
|
|
#define LINEBUF_SIZE (8 + 1 + 3*16 + 16 + 1 + 1 /*paranoia:*/ + 13)
|
|
|
|
static void restore_term(void)
|
|
{
|
|
tcsetattr_stdin_TCSANOW(&G.orig_termios);
|
|
printf(POP_ALT_SCR);
|
|
fflush_all();
|
|
}
|
|
|
|
static void sig_catcher(int sig)
|
|
{
|
|
if (!G.in_read_key) {
|
|
/* now it's not safe to do I/O, just inform the main loop */
|
|
bb_got_signal = sig;
|
|
return;
|
|
}
|
|
restore_term();
|
|
kill_myself_with_sig(sig);
|
|
}
|
|
|
|
static int format_line(char *hex, uint8_t *data, off_t offset)
|
|
{
|
|
int ofs_pos;
|
|
char *text;
|
|
uint8_t *end, *end1;
|
|
|
|
#if 1
|
|
/* Can be more than 4Gb, thus >8 chars, thus use a variable - don't assume 8! */
|
|
ofs_pos = sprintf(hex, "%08"OFF_FMT"x ", offset);
|
|
#else
|
|
if (offset <= 0xffff)
|
|
ofs_pos = sprintf(hex, "%04"OFF_FMT"x ", offset);
|
|
else
|
|
ofs_pos = sprintf(hex, "%08"OFF_FMT"x ", offset);
|
|
#endif
|
|
hex += ofs_pos;
|
|
|
|
text = hex + 16 * 3;
|
|
end1 = data + 15;
|
|
if ((G.size - offset) > 0) {
|
|
end = end1;
|
|
if ((G.size - offset) <= 15)
|
|
end = data + (G.size - offset) - 1;
|
|
while (data <= end) {
|
|
uint8_t c = *data++;
|
|
*hex++ = bb_hexdigits_upcase[c >> 4];
|
|
*hex++ = bb_hexdigits_upcase[c & 0xf];
|
|
*hex++ = ' ';
|
|
if (c < ' ' || c > 0x7e)
|
|
c = '.';
|
|
*text++ = c;
|
|
}
|
|
}
|
|
while (data <= end1) {
|
|
*hex++ = ' ';
|
|
*hex++ = ' ';
|
|
*hex++ = ' ';
|
|
*text++ = ' ';
|
|
data++;
|
|
}
|
|
*text = '\0';
|
|
|
|
return ofs_pos;
|
|
}
|
|
|
|
static void redraw(unsigned cursor)
|
|
{
|
|
uint8_t *data;
|
|
off_t offset;
|
|
unsigned i, pos;
|
|
|
|
printf(HOME CLEAR);
|
|
|
|
/* if cursor is past end of screen, how many lines to move down? */
|
|
i = (cursor / 16) - G.height + 1;
|
|
if ((int)i < 0)
|
|
i = 0;
|
|
|
|
data = G.baseaddr + i * 16;
|
|
offset = G.offset + i * 16;
|
|
cursor -= i * 16;
|
|
pos = i = 0;
|
|
while (i < G.height) {
|
|
char buf[LINEBUF_SIZE];
|
|
pos = format_line(buf, data, offset);
|
|
printf(
|
|
"\r\n%s" + (!i) * 2, /* print \r\n only on 2nd line and later */
|
|
buf
|
|
);
|
|
data += 16;
|
|
offset += 16;
|
|
i++;
|
|
}
|
|
|
|
G.row = cursor / 16;
|
|
printf(ESC"[%u;%uH", 1 + G.row, 1 + pos + (cursor & 0xf) * 3);
|
|
}
|
|
|
|
static void redraw_cur_line(void)
|
|
{
|
|
char buf[LINEBUF_SIZE];
|
|
uint8_t *data;
|
|
off_t offset;
|
|
int column;
|
|
|
|
column = (0xf & (uintptr_t)G.current_byte);
|
|
data = G.current_byte - column;
|
|
offset = G.offset + (data - G.baseaddr);
|
|
|
|
column = column*3 + G.half;
|
|
column += format_line(buf, data, offset);
|
|
printf("%s"
|
|
"\r"
|
|
"%.*s",
|
|
buf + column,
|
|
column, buf
|
|
);
|
|
}
|
|
|
|
/* if remappers return 0, no change was done */
|
|
static int remap(unsigned cur_pos)
|
|
{
|
|
if (G.baseaddr)
|
|
munmap(G.baseaddr, G_mapsize);
|
|
|
|
G.baseaddr = mmap(NULL,
|
|
G_mapsize,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_SHARED,
|
|
G.fd,
|
|
G.offset
|
|
);
|
|
if (G.baseaddr == MAP_FAILED) {
|
|
restore_term();
|
|
bb_simple_perror_msg_and_die("mmap");
|
|
}
|
|
|
|
G.current_byte = G.baseaddr + cur_pos;
|
|
|
|
G.eof_byte = G.baseaddr + G_mapsize;
|
|
if ((G.size - G.offset) < G_mapsize) {
|
|
/* mapping covers tail of the file */
|
|
/* we do have a mapped byte which is past eof */
|
|
G.eof_byte = G.baseaddr + (G.size - G.offset);
|
|
}
|
|
return 1;
|
|
}
|
|
static int move_mapping_further(void)
|
|
{
|
|
unsigned pos;
|
|
unsigned pagesize;
|
|
|
|
if ((G.size - G.offset) < G_mapsize)
|
|
return 0; /* can't move mapping even further, it's at the end already */
|
|
|
|
pagesize = G_pagesize; /* constant on most arches */
|
|
pos = G.current_byte - G.baseaddr;
|
|
if (pos >= pagesize) {
|
|
/* move offset up until current position is in 1st page */
|
|
do {
|
|
G.offset += pagesize;
|
|
if (G.offset == 0) { /* whoops */
|
|
G.offset -= pagesize;
|
|
break;
|
|
}
|
|
pos -= pagesize;
|
|
} while (pos >= pagesize);
|
|
return remap(pos);
|
|
}
|
|
return 0;
|
|
}
|
|
static int move_mapping_lower(void)
|
|
{
|
|
unsigned pos;
|
|
unsigned pagesize;
|
|
|
|
if (G.offset == 0)
|
|
return 0; /* we are at 0 already */
|
|
|
|
pagesize = G_pagesize; /* constant on most arches */
|
|
pos = G.current_byte - G.baseaddr;
|
|
|
|
/* move offset down until current position is in last page */
|
|
pos += pagesize;
|
|
while (pos < G_mapsize) {
|
|
pos += pagesize;
|
|
G.offset -= pagesize;
|
|
if (G.offset == 0)
|
|
break;
|
|
}
|
|
pos -= pagesize;
|
|
|
|
return remap(pos);
|
|
}
|
|
|
|
//usage:#define hexedit_trivial_usage
|
|
//usage: "FILE"
|
|
//usage:#define hexedit_full_usage "\n\n"
|
|
//usage: "Edit FILE in hexadecimal"
|
|
int hexedit_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
|
|
int hexedit_main(int argc UNUSED_PARAM, char **argv)
|
|
{
|
|
INIT_G();
|
|
INIT_PAGESIZE();
|
|
|
|
get_terminal_width_height(-1, NULL, &G.height);
|
|
if (1) {
|
|
/* reduce number of write() syscalls while PgUp/Down: fully buffered output */
|
|
unsigned sz = (G.height | 0xf) * LINEBUF_SIZE;
|
|
setvbuf(stdout, xmalloc(sz), _IOFBF, sz);
|
|
}
|
|
|
|
getopt32(argv, "^" "" "\0" "=1"/*one arg*/);
|
|
argv += optind;
|
|
|
|
G.fd = xopen(*argv, O_RDWR);
|
|
G.size = xlseek(G.fd, 0, SEEK_END);
|
|
|
|
/* TERMIOS_RAW_CRNL suppresses \n -> \r\n translation, helps with down-arrow */
|
|
printf(SET_ALT_SCR);
|
|
set_termios_to_raw(STDIN_FILENO, &G.orig_termios, TERMIOS_RAW_CRNL);
|
|
bb_signals(BB_FATAL_SIGS, sig_catcher);
|
|
|
|
remap(0);
|
|
redraw(0);
|
|
|
|
//TODO: //Home/End: start/end of line; '<'/'>': start/end of file
|
|
//Backspace: undo
|
|
//Ctrl-L: redraw
|
|
//Ctrl-Z: suspend
|
|
//'/', Ctrl-S: search
|
|
//TODO: detect window resize
|
|
|
|
for (;;) {
|
|
unsigned cnt;
|
|
int32_t key = key; /* for compiler */
|
|
uint8_t byte;
|
|
|
|
fflush_all();
|
|
G.in_read_key = 1;
|
|
if (!bb_got_signal)
|
|
key = read_key(STDIN_FILENO, G.read_key_buffer, -1);
|
|
G.in_read_key = 0;
|
|
if (bb_got_signal)
|
|
key = CTRL('X');
|
|
|
|
cnt = 1;
|
|
if ((unsigned)(key - 'A') <= 'Z' - 'A')
|
|
key |= 0x20; /* convert A-Z to a-z */
|
|
switch (key) {
|
|
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
|
/* convert to '0'+10...15 */
|
|
key = key - ('a' - '0' - 10);
|
|
/* fall through */
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
if (G.current_byte == G.eof_byte) {
|
|
if (!move_mapping_further()) {
|
|
/* already at EOF; extend the file */
|
|
if (++G.size <= 0 /* overflow? */
|
|
|| ftruncate(G.fd, G.size) != 0 /* error extending? (e.g. block dev) */
|
|
) {
|
|
G.size--;
|
|
break;
|
|
}
|
|
G.eof_byte++;
|
|
}
|
|
}
|
|
key -= '0';
|
|
byte = *G.current_byte & 0xf0;
|
|
if (!G.half) {
|
|
byte = *G.current_byte & 0x0f;
|
|
key <<= 4;
|
|
}
|
|
*G.current_byte = byte + key;
|
|
/* can't just print one updated hex char: need to update right-hand ASCII too */
|
|
redraw_cur_line();
|
|
/* fall through */
|
|
case KEYCODE_RIGHT:
|
|
if (G.current_byte == G.eof_byte)
|
|
break; /* eof - don't allow going past it */
|
|
byte = *G.current_byte;
|
|
if (!G.half) {
|
|
G.half = 1;
|
|
putchar(bb_hexdigits_upcase[byte >> 4]);
|
|
} else {
|
|
G.half = 0;
|
|
G.current_byte++;
|
|
if ((0xf & (uintptr_t)G.current_byte) == 0) {
|
|
/* rightmost pos, wrap to next line */
|
|
if (G.current_byte == G.eof_byte)
|
|
move_mapping_further();
|
|
printf(ESC"[46D"); /* cursor left 3*15 + 1 chars */
|
|
goto down;
|
|
}
|
|
putchar(bb_hexdigits_upcase[byte & 0xf]);
|
|
putchar(' ');
|
|
}
|
|
break;
|
|
case KEYCODE_PAGEDOWN:
|
|
cnt = G.height;
|
|
case KEYCODE_DOWN:
|
|
k_down:
|
|
G.current_byte += 16;
|
|
if (G.current_byte >= G.eof_byte) {
|
|
move_mapping_further();
|
|
if (G.current_byte > G.eof_byte) {
|
|
/* _after_ eof - don't allow this */
|
|
G.current_byte -= 16;
|
|
if (G.current_byte < G.baseaddr)
|
|
move_mapping_lower();
|
|
break;
|
|
}
|
|
}
|
|
down:
|
|
putchar('\n'); /* down one line, possibly scroll screen */
|
|
G.row++;
|
|
if (G.row >= G.height) {
|
|
G.row--;
|
|
redraw_cur_line();
|
|
}
|
|
if (--cnt)
|
|
goto k_down;
|
|
break;
|
|
|
|
case KEYCODE_LEFT:
|
|
if (G.half) {
|
|
G.half = 0;
|
|
printf(ESC"[D");
|
|
break;
|
|
}
|
|
if ((0xf & (uintptr_t)G.current_byte) == 0) {
|
|
/* leftmost pos, wrap to prev line */
|
|
if (G.current_byte == G.baseaddr) {
|
|
if (!move_mapping_lower())
|
|
break; /* first line, don't do anything */
|
|
}
|
|
G.half = 1;
|
|
G.current_byte--;
|
|
printf(ESC"[46C"); /* cursor right 3*15 + 1 chars */
|
|
goto up;
|
|
}
|
|
G.half = 1;
|
|
G.current_byte--;
|
|
printf(ESC"[2D");
|
|
break;
|
|
case KEYCODE_PAGEUP:
|
|
cnt = G.height;
|
|
case KEYCODE_UP:
|
|
k_up:
|
|
if ((G.current_byte - G.baseaddr) < 16) {
|
|
if (!move_mapping_lower())
|
|
break; /* already at 0, stop */
|
|
}
|
|
G.current_byte -= 16;
|
|
up:
|
|
if (G.row != 0) {
|
|
G.row--;
|
|
printf(ESC"[A"); /* up (won't scroll) */
|
|
} else {
|
|
//printf(ESC"[T"); /* scroll up */ - not implemented on Linux VT!
|
|
printf(ESC"M"); /* scroll up */
|
|
redraw_cur_line();
|
|
}
|
|
if (--cnt)
|
|
goto k_up;
|
|
break;
|
|
|
|
case '\n':
|
|
case '\r':
|
|
/* [Enter]: goto specified position */
|
|
{
|
|
char buf[sizeof(G.offset)*3 + 4];
|
|
printf(ESC"[999;1H" CLEAR_TILL_EOL); /* go to last line */
|
|
if (read_line_input(NULL, "Go to (dec,0Xhex,0oct): ", buf, sizeof(buf)) > 0) {
|
|
off_t t;
|
|
unsigned cursor;
|
|
|
|
t = bb_strtoull(buf, NULL, 0);
|
|
if (t >= G.size)
|
|
t = G.size - 1;
|
|
cursor = t & (G_pagesize - 1);
|
|
t -= cursor;
|
|
if (t < 0)
|
|
cursor = t = 0;
|
|
if (t != 0 && cursor < 0x1ff) {
|
|
/* very close to end of page, possibly to EOF */
|
|
/* move one page lower */
|
|
t -= G_pagesize;
|
|
cursor += G_pagesize;
|
|
}
|
|
G.offset = t;
|
|
remap(cursor);
|
|
redraw(cursor);
|
|
break;
|
|
}
|
|
/* ^C/EOF/error: fall through to exiting */
|
|
}
|
|
case CTRL('X'):
|
|
restore_term();
|
|
return EXIT_SUCCESS;
|
|
} /* switch */
|
|
} /* for (;;) */
|
|
|
|
/* not reached */
|
|
return EXIT_SUCCESS;
|
|
}
|