From 73af705628ddaedc4c6f7f78b9658d6c01310309 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Wed, 13 Sep 2017 19:20:27 +0200
Subject: [PATCH] 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>
---
 miscutils/hexedit.c | 357 ++++++++++++++++++++++++++++++++++++++++++++
 miscutils/less.c    |   2 +-
 2 files changed, 358 insertions(+), 1 deletion(-)
 create mode 100644 miscutils/hexedit.c

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 <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;
+}
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"
 
-- 
2.25.1