123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- /*
- * 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;
- IF_VARIABLE_ARCH_PAGESIZE(unsigned pagesize;)
- #define G_pagesize cached_pagesize(G.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)
- /* 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(G.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;
- }
|