busybox/miscutils/hexedit.c

469 lines
10 KiB
C
Raw Normal View History

/*
* 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();
libbb: reduce the overhead of single parameter bb_error_msg() calls Back in 2007, commit 0c97c9d43707 ("'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>
2019-07-02 15:05:03 +05:30
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;
}