hexedit: new applet
function old new delta hexedit_main - 930 +930 format_line - 197 +197 remap - 168 +168 move_mapping_further - 141 +141 move_mapping_lower - 107 +107 redraw_cur_line - 104 +104 packed_usage 31802 31812 +10 applet_names 2688 2696 +8 applet_main 1552 1556 +4 applet_suid 97 98 +1 applet_install_loc 194 195 +1 ------------------------------------------------------------------------------ (add/remove: 7/0 grow/shrink: 5/0 up/down: 1671/0) Total: 1671 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
parent
b5a0396716
commit
73af705628
357
miscutils/hexedit.c
Normal file
357
miscutils/hexedit.c
Normal file
@ -0,0 +1,357 @@
|
||||
/*
|
||||
* 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"
|
||||
//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"[H"ESC"[J"
|
||||
|
||||
struct globals {
|
||||
smallint half;
|
||||
int fd;
|
||||
unsigned height;
|
||||
uint8_t *addr;
|
||||
uint8_t *current_byte;
|
||||
uint8_t *eof_byte;
|
||||
off_t size;
|
||||
off_t offset;
|
||||
struct termios orig_termios;
|
||||
};
|
||||
#define G (*ptr_to_globals)
|
||||
#define INIT_G() do { \
|
||||
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
|
||||
} while (0)
|
||||
|
||||
/* Hopefully there aren't arches with PAGE_SIZE > 64k */
|
||||
#define G_mapsize (64*1024)
|
||||
|
||||
static void format_line(char *hex, uint8_t *data, off_t offset)
|
||||
{
|
||||
char *text = hex + 16*3;
|
||||
uint8_t *end, *end1;
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
static void redraw(void)
|
||||
{
|
||||
uint8_t *data;
|
||||
off_t offset;
|
||||
unsigned i;
|
||||
|
||||
data = G.addr;
|
||||
offset = 0;
|
||||
i = 0;
|
||||
while (i < G.height) {
|
||||
char buf[16*4 + 8];
|
||||
format_line(buf, data, offset);
|
||||
printf(
|
||||
"\r\n%08"OFF_FMT"x %s" + (!i)*2, /* print \r\n only on 2nd line and later */
|
||||
offset, buf
|
||||
);
|
||||
data += 16;
|
||||
offset += 16;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static void redraw_cur_line(void)
|
||||
{
|
||||
off_t offset;
|
||||
int len;
|
||||
char buf[16*4 + 8];
|
||||
uint8_t *data;
|
||||
|
||||
len = (0xf & (uintptr_t)G.current_byte);
|
||||
data = G.current_byte - len;
|
||||
offset = G.offset + (data - G.addr);
|
||||
format_line(buf, data, offset);
|
||||
printf(
|
||||
"\r%08"OFF_FMT"x %s",
|
||||
offset, buf
|
||||
);
|
||||
printf(
|
||||
"\r%08"OFF_FMT"x %.*s",
|
||||
offset, (len*3 + G.half), buf
|
||||
);
|
||||
}
|
||||
|
||||
static void remap(unsigned cur_pos)
|
||||
{
|
||||
if (G.addr)
|
||||
munmap(G.addr, G_mapsize);
|
||||
|
||||
G.addr = mmap(NULL,
|
||||
G_mapsize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
G.fd,
|
||||
G.offset
|
||||
);
|
||||
if (G.addr == MAP_FAILED)
|
||||
//TODO: restore termios?
|
||||
bb_perror_msg_and_die("mmap");
|
||||
|
||||
G.current_byte = G.addr + cur_pos;
|
||||
|
||||
G.eof_byte = G.addr + 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.addr + (G.size - G.offset);
|
||||
}
|
||||
}
|
||||
static void move_mapping_further(void)
|
||||
{
|
||||
unsigned pos;
|
||||
unsigned pagesize;
|
||||
|
||||
if ((G.size - G.offset) < G_mapsize)
|
||||
return; /* can't move mapping even further, it's at the end already */
|
||||
|
||||
pagesize = getpagesize(); /* constant on most arches */
|
||||
pos = G.current_byte - G.addr;
|
||||
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);
|
||||
remap(pos);
|
||||
}
|
||||
}
|
||||
static void move_mapping_lower(void)
|
||||
{
|
||||
unsigned pos;
|
||||
unsigned pagesize;
|
||||
|
||||
if (G.offset == 0)
|
||||
return; /* we are at 0 already */
|
||||
|
||||
pagesize = getpagesize(); /* constant on most arches */
|
||||
pos = G.current_byte - G.addr;
|
||||
|
||||
/* 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;
|
||||
|
||||
remap(pos);
|
||||
}
|
||||
|
||||
static void sig_catcher(int sig)
|
||||
{
|
||||
tcsetattr_stdin_TCSANOW(&G.orig_termios);
|
||||
kill_myself_with_sig(sig);
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
unsigned row = 0;
|
||||
|
||||
INIT_G();
|
||||
|
||||
getopt32(argv, "");
|
||||
argv += optind;
|
||||
|
||||
get_terminal_width_height(-1, NULL, &G.height);
|
||||
|
||||
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 */
|
||||
set_termios_to_raw(STDIN_FILENO, &G.orig_termios, TERMIOS_RAW_CRNL);
|
||||
bb_signals(BB_FATAL_SIGS, sig_catcher);
|
||||
|
||||
remap(0);
|
||||
|
||||
printf(CLEAR);
|
||||
redraw();
|
||||
printf(HOME "%08x ", 0);
|
||||
|
||||
//TODO: //PgUp/PgDown; Home/End: start/end of line; '<'/'>': start/end of file
|
||||
//Backspace: undo
|
||||
//Enter: goto specified position
|
||||
//Ctrl-L: redraw
|
||||
//Ctrl-X: save and exit (maybe also Q?)
|
||||
//Ctrl-Z: suspend
|
||||
//'/', Ctrl-S: search
|
||||
//TODO: go to end-of-screen on exit (for this, sighandler should interrupt read_key())
|
||||
//TODO: detect window resize
|
||||
//TODO: read-only mode if open(O_RDWR) fails? hide cursor in this case?
|
||||
|
||||
//TODO: smarter redraw: if down-arrow is pressed on last visible line,
|
||||
//emit LF, then print the tail of next line, then CR, then beginning -
|
||||
//which makes cursor end up exactly where it should be! Same for up-arrow.
|
||||
|
||||
for (;;) {
|
||||
char read_key_buffer[KEYCODE_BUFFER_SIZE];
|
||||
int32_t key;
|
||||
uint8_t byte;
|
||||
|
||||
fflush_all();
|
||||
key = read_key(STDIN_FILENO, read_key_buffer, -1);
|
||||
|
||||
switch (key) {
|
||||
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
|
||||
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
|
||||
/* lowercase, then convert to '0'+10...15 */
|
||||
key = (key | 0x20) - ('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) {
|
||||
move_mapping_further();
|
||||
if (G.current_byte == G.eof_byte) {
|
||||
/* 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_DOWN:
|
||||
G.current_byte += 16;
|
||||
if (G.current_byte >= G.eof_byte) {
|
||||
move_mapping_further();
|
||||
if (G.current_byte > G.eof_byte) {
|
||||
/* eof - don't allow going past it */
|
||||
G.current_byte -= 16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
down:
|
||||
putchar('\n'); /* down one line, possibly scroll screen */
|
||||
row++;
|
||||
if (row >= G.height) {
|
||||
row--;
|
||||
redraw_cur_line();
|
||||
}
|
||||
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.addr)
|
||||
move_mapping_lower();
|
||||
if ((G.current_byte - G.addr) < 16)
|
||||
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_UP:
|
||||
if ((G.current_byte - G.addr) < 16) {
|
||||
move_mapping_lower();
|
||||
if ((G.current_byte - G.addr) < 16)
|
||||
break;
|
||||
}
|
||||
G.current_byte -= 16;
|
||||
up:
|
||||
if (row != 0) {
|
||||
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();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -139,7 +139,7 @@
|
||||
#define HIGHLIGHT ESC"[7m"
|
||||
#define NORMAL ESC"[0m"
|
||||
/* The escape code to home and clear to the end of screen */
|
||||
#define CLEAR ESC"[H\033[J"
|
||||
#define CLEAR ESC"[H"ESC"[J"
|
||||
/* The escape code to clear to the end of line */
|
||||
#define CLEAR_2_EOL ESC"[K"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user