hexedit: new applet
No binary (build failed)
—
(log yok)
| @@ -0,0 +1,357 @@ | |
| + | /* |
| + | * Copyright (C) 2017 Denys Vlasenko <[email protected]> |
| + | * |
| + | * 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" | |