diff --git a/miscutils/hexedit.c b/miscutils/hexedit.c new file mode 100644 index 000000000..cd4b3b1ad --- /dev/null +++ b/miscutils/hexedit.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2017 Denys Vlasenko + * + * 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; +} diff --git a/miscutils/less.c b/miscutils/less.c index c1d5e1b39..f37c80ad8 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -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"