2 * Copyright (C) 2017 Denys Vlasenko <vda.linux@googlemail.com>
4 * Licensed under GPLv2, see file LICENSE in this source tree.
6 //config:config HEXEDIT
7 //config: bool "hexedit (20 kb)"
10 //config: Edit file in hexadecimal.
12 //applet:IF_HEXEDIT(APPLET(hexedit, BB_DIR_USR_BIN, BB_SUID_DROP))
14 //kbuild:lib-$(CONFIG_HEXEDIT) += hexedit.o
21 #define CLEAR_TILL_EOL ESC"[K"
22 #define SET_ALT_SCR ESC"[?1049h"
23 #define POP_ALT_SCR ESC"[?1049l"
26 #define CTRL(c) ((c) & (uint8_t)~0x60)
36 uint8_t *current_byte;
40 /* needs to be zero-inited, thus keeping it in G: */
41 char read_key_buffer[KEYCODE_BUFFER_SIZE];
42 struct termios orig_termios;
44 #define G (*ptr_to_globals)
45 #define INIT_G() do { \
46 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
50 #if defined(__x86_64__) || defined(i386)
51 # define G_pagesize 4096
52 # define INIT_PAGESIZE() ((void)0)
54 # define G_pagesize (G.pagesize)
55 # define INIT_PAGESIZE() ((void)(G.pagesize = getpagesize()))
58 /* hopefully there aren't arches with PAGE_SIZE > 64k */
59 #define G_mapsize (64*1024)
61 /* "12ef5670 (xx )*16 _1_3_5_7_9abcdef\n"NUL */
62 #define LINEBUF_SIZE (8 + 1 + 3*16 + 16 + 1 + 1 /*paranoia:*/ + 13)
64 static void restore_term(void)
66 tcsetattr_stdin_TCSANOW(&G.orig_termios);
71 static void sig_catcher(int sig)
74 /* now it's not safe to do I/O, just inform the main loop */
79 kill_myself_with_sig(sig);
82 static int format_line(char *hex, uint8_t *data, off_t offset)
89 /* Can be more than 4Gb, thus >8 chars, thus use a variable - don't assume 8! */
90 ofs_pos = sprintf(hex, "%08"OFF_FMT"x ", offset);
93 ofs_pos = sprintf(hex, "%04"OFF_FMT"x ", offset);
95 ofs_pos = sprintf(hex, "%08"OFF_FMT"x ", offset);
101 if ((G.size - offset) > 0) {
103 if ((G.size - offset) <= 15)
104 end = data + (G.size - offset) - 1;
105 while (data <= end) {
107 *hex++ = bb_hexdigits_upcase[c >> 4];
108 *hex++ = bb_hexdigits_upcase[c & 0xf];
110 if (c < ' ' || c > 0x7e)
115 while (data <= end1) {
127 static void redraw(unsigned cursor)
135 /* if cursor is past end of screen, how many lines to move down? */
136 i = (cursor / 16) - G.height + 1;
140 data = G.baseaddr + i * 16;
141 offset = G.offset + i * 16;
144 while (i < G.height) {
145 char buf[LINEBUF_SIZE];
146 pos = format_line(buf, data, offset);
148 "\r\n%s" + (!i) * 2, /* print \r\n only on 2nd line and later */
156 printf(ESC"[%u;%uH", 1 + cursor / 16, 1 + pos + (cursor & 0xf) * 3);
159 static void redraw_cur_line(void)
161 char buf[LINEBUF_SIZE];
166 column = (0xf & (uintptr_t)G.current_byte);
167 data = G.current_byte - column;
168 offset = G.offset + (data - G.baseaddr);
170 column = column*3 + G.half;
171 column += format_line(buf, data, offset);
180 /* if remappers return 0, no change was done */
181 static int remap(unsigned cur_pos)
184 munmap(G.baseaddr, G_mapsize);
186 G.baseaddr = mmap(NULL,
188 PROT_READ | PROT_WRITE,
193 if (G.baseaddr == MAP_FAILED) {
195 bb_perror_msg_and_die("mmap");
198 G.current_byte = G.baseaddr + cur_pos;
200 G.eof_byte = G.baseaddr + G_mapsize;
201 if ((G.size - G.offset) < G_mapsize) {
202 /* mapping covers tail of the file */
203 /* we do have a mapped byte which is past eof */
204 G.eof_byte = G.baseaddr + (G.size - G.offset);
208 static int move_mapping_further(void)
213 if ((G.size - G.offset) < G_mapsize)
214 return 0; /* can't move mapping even further, it's at the end already */
216 pagesize = G_pagesize; /* constant on most arches */
217 pos = G.current_byte - G.baseaddr;
218 if (pos >= pagesize) {
219 /* move offset up until current position is in 1st page */
221 G.offset += pagesize;
222 if (G.offset == 0) { /* whoops */
223 G.offset -= pagesize;
227 } while (pos >= pagesize);
232 static int move_mapping_lower(void)
238 return 0; /* we are at 0 already */
240 pagesize = G_pagesize; /* constant on most arches */
241 pos = G.current_byte - G.baseaddr;
243 /* move offset down until current position is in last page */
245 while (pos < G_mapsize) {
247 G.offset -= pagesize;
256 //usage:#define hexedit_trivial_usage
258 //usage:#define hexedit_full_usage "\n\n"
259 //usage: "Edit FILE in hexadecimal"
260 int hexedit_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
261 int hexedit_main(int argc UNUSED_PARAM, char **argv)
266 get_terminal_width_height(-1, NULL, &G.height);
268 /* reduce number of write() syscalls while PgUp/Down: fully buffered output */
269 unsigned sz = (G.height | 0xf) * LINEBUF_SIZE;
270 setvbuf(stdout, xmalloc(sz), _IOFBF, sz);
273 getopt32(argv, "^" "" "\0" "=1"/*one arg*/);
276 G.fd = xopen(*argv, O_RDWR);
277 G.size = xlseek(G.fd, 0, SEEK_END);
279 /* TERMIOS_RAW_CRNL suppresses \n -> \r\n translation, helps with down-arrow */
281 set_termios_to_raw(STDIN_FILENO, &G.orig_termios, TERMIOS_RAW_CRNL);
282 bb_signals(BB_FATAL_SIGS, sig_catcher);
287 //TODO: //Home/End: start/end of line; '<'/'>': start/end of file
291 //'/', Ctrl-S: search
292 //TODO: detect window resize
296 int32_t key = key; /* for compiler */
302 key = read_key(STDIN_FILENO, G.read_key_buffer, -1);
308 if ((unsigned)(key - 'A') <= 'Z' - 'A')
309 key |= 0x20; /* convert A-Z to a-z */
311 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
312 /* convert to '0'+10...15 */
313 key = key - ('a' - '0' - 10);
315 case '0': case '1': case '2': case '3': case '4':
316 case '5': case '6': case '7': case '8': case '9':
317 if (G.current_byte == G.eof_byte) {
318 if (!move_mapping_further()) {
319 /* already at EOF; extend the file */
320 if (++G.size <= 0 /* overflow? */
321 || ftruncate(G.fd, G.size) != 0 /* error extending? (e.g. block dev) */
330 byte = *G.current_byte & 0xf0;
332 byte = *G.current_byte & 0x0f;
335 *G.current_byte = byte + key;
336 /* can't just print one updated hex char: need to update right-hand ASCII too */
340 if (G.current_byte == G.eof_byte)
341 break; /* eof - don't allow going past it */
342 byte = *G.current_byte;
345 putchar(bb_hexdigits_upcase[byte >> 4]);
349 if ((0xf & (uintptr_t)G.current_byte) == 0) {
350 /* rightmost pos, wrap to next line */
351 if (G.current_byte == G.eof_byte)
352 move_mapping_further();
353 printf(ESC"[46D"); /* cursor left 3*15 + 1 chars */
356 putchar(bb_hexdigits_upcase[byte & 0xf]);
360 case KEYCODE_PAGEDOWN:
364 G.current_byte += 16;
365 if (G.current_byte >= G.eof_byte) {
366 move_mapping_further();
367 if (G.current_byte > G.eof_byte) {
368 /* _after_ eof - don't allow this */
369 G.current_byte -= 16;
374 putchar('\n'); /* down one line, possibly scroll screen */
376 if (G.row >= G.height) {
390 if ((0xf & (uintptr_t)G.current_byte) == 0) {
391 /* leftmost pos, wrap to prev line */
392 if (G.current_byte == G.baseaddr) {
393 if (!move_mapping_lower())
394 break; /* first line, don't do anything */
398 printf(ESC"[46C"); /* cursor right 3*15 + 1 chars */
409 if ((G.current_byte - G.baseaddr) < 16) {
410 if (!move_mapping_lower())
411 break; /* already at 0, stop */
413 G.current_byte -= 16;
417 printf(ESC"[A"); /* up (won't scroll) */
419 //printf(ESC"[T"); /* scroll up */ - not implemented on Linux VT!
420 printf(ESC"M"); /* scroll up */
429 /* [Enter]: goto specified position */
431 char buf[sizeof(G.offset)*3 + 4];
432 printf(ESC"[999;1H" CLEAR_TILL_EOL); /* go to last line */
433 if (read_line_input(NULL, "Go to (dec,0Xhex,0oct): ", buf, sizeof(buf)) > 0) {
437 t = bb_strtoull(buf, NULL, 0);
440 cursor = t & (G_pagesize - 1);
444 if (t != 0 && cursor < 0x1ff) {
445 /* very close to end of page, possibly to EOF */
446 /* move one page lower */
448 cursor += G_pagesize;
455 /* ^C/EOF/error: fall through to exiting */