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>
This commit is contained in:
		
							
								
								
									
										357
									
								
								miscutils/hexedit.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								miscutils/hexedit.c
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user