function old new delta lineedit_read_key 160 237 +77 __pgetc 522 589 +67 fgetc_interactive 244 309 +65 safe_read_key - 39 +39 read_key 588 607 +19 record_pending_signo 23 32 +9 signal_handler 75 81 +6 .rodata 104312 104309 -3 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 6/1 up/down: 282/-3) Total: 279 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
		
			
				
	
	
		
			461 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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 = safe_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;
 | 
						|
}
 |