hexedit: new applet
authorDenys Vlasenko <vda.linux@googlemail.com>
Wed, 13 Sep 2017 17:20:27 +0000 (19:20 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Wed, 13 Sep 2017 17:20:27 +0000 (19:20 +0200)
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>
miscutils/hexedit.c [new file with mode: 0644]
miscutils/less.c

diff --git a/miscutils/hexedit.c b/miscutils/hexedit.c
new file mode 100644 (file)
index 0000000..cd4b3b1
--- /dev/null
@@ -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;
+}
index c1d5e1b39204cd78dd627bad7f214c681d581228..f37c80ad82191875ceefb7166075928cda72fa47 100644 (file)
 #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"