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"
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
20 #define CLEAR ESC"[H"ESC"[J"
21 #define SET_ALT_SCR ESC"[?1049h"
22 #define POP_ALT_SCR ESC"[?1049l"
25 #define CTRL(c) ((c) & (uint8_t)~0x60)
34 uint8_t *current_byte;
38 /* needs to be zero-inited, thus keeping it in G: */
39 char read_key_buffer[KEYCODE_BUFFER_SIZE];
40 struct termios orig_termios;
42 #define G (*ptr_to_globals)
43 #define INIT_G() do { \
44 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
47 /* Hopefully there aren't arches with PAGE_SIZE > 64k */
48 #define G_mapsize (64*1024)
50 /* "12ef5670 (xx )*16 _1_3_5_7_9abcdef\n"NUL */
51 #define LINEBUF_SIZE (8 + 1 + 3*16 + 16 + 1 + 1 /*paranoia:*/ + 13)
53 static void restore_term(void)
55 tcsetattr_stdin_TCSANOW(&G.orig_termios);
60 static void sig_catcher(int sig)
63 /* now it's not safe to do I/O, just inform the main loop */
68 kill_myself_with_sig(sig);
71 static int format_line(char *hex, uint8_t *data, off_t offset)
78 /* Can be more than 4Gb, thus >8 chars, thus use a variable - don't assume 8! */
79 ofs_pos = sprintf(hex, "%08"OFF_FMT"x ", offset);
82 ofs_pos = sprintf(hex, "%04"OFF_FMT"x ", offset);
84 ofs_pos = sprintf(hex, "%08"OFF_FMT"x ", offset);
90 if ((G.size - offset) > 0) {
92 if ((G.size - offset) <= 15)
93 end = data + (G.size - offset) - 1;
96 *hex++ = bb_hexdigits_upcase[c >> 4];
97 *hex++ = bb_hexdigits_upcase[c & 0xf];
99 if (c < ' ' || c > 0x7e)
104 while (data <= end1) {
116 static void redraw(void)
125 while (i < G.height) {
126 char buf[LINEBUF_SIZE];
127 format_line(buf, data, offset);
129 "\r\n%s" + (!i)*2, /* print \r\n only on 2nd line and later */
138 static void redraw_cur_line(void)
140 char buf[LINEBUF_SIZE];
145 column = (0xf & (uintptr_t)G.current_byte);
146 data = G.current_byte - column;
147 offset = G.offset + (data - G.addr);
149 column = column*3 + G.half;
150 column += format_line(buf, data, offset);
159 static void remap(unsigned cur_pos)
162 munmap(G.addr, G_mapsize);
166 PROT_READ | PROT_WRITE,
171 if (G.addr == MAP_FAILED) {
173 bb_perror_msg_and_die("mmap");
176 G.current_byte = G.addr + cur_pos;
178 G.eof_byte = G.addr + G_mapsize;
179 if ((G.size - G.offset) < G_mapsize) {
180 /* mapping covers tail of the file */
181 /* we do have a mapped byte which is past eof */
182 G.eof_byte = G.addr + (G.size - G.offset);
185 static void move_mapping_further(void)
190 if ((G.size - G.offset) < G_mapsize)
191 return; /* can't move mapping even further, it's at the end already */
193 pagesize = getpagesize(); /* constant on most arches */
194 pos = G.current_byte - G.addr;
195 if (pos >= pagesize) {
196 /* move offset up until current position is in 1st page */
198 G.offset += pagesize;
199 if (G.offset == 0) { /* whoops */
200 G.offset -= pagesize;
204 } while (pos >= pagesize);
208 static void move_mapping_lower(void)
214 return; /* we are at 0 already */
216 pagesize = getpagesize(); /* constant on most arches */
217 pos = G.current_byte - G.addr;
219 /* move offset down until current position is in last page */
221 while (pos < G_mapsize) {
223 G.offset -= pagesize;
232 //usage:#define hexedit_trivial_usage
234 //usage:#define hexedit_full_usage "\n\n"
235 //usage: "Edit FILE in hexadecimal"
236 int hexedit_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
237 int hexedit_main(int argc UNUSED_PARAM, char **argv)
241 get_terminal_width_height(-1, NULL, &G.height);
243 /* reduce number of write() syscalls while PgUp/Down: fully buffered output */
244 unsigned sz = (G.height | 0xf) * LINEBUF_SIZE;
245 setvbuf(stdout, xmalloc(sz), _IOFBF, sz);
248 getopt32(argv, "^" "" "\0" "=1"/*one arg*/);
251 G.fd = xopen(*argv, O_RDWR);
252 G.size = xlseek(G.fd, 0, SEEK_END);
254 /* TERMIOS_RAW_CRNL suppresses \n -> \r\n translation, helps with down-arrow */
255 set_termios_to_raw(STDIN_FILENO, &G.orig_termios, TERMIOS_RAW_CRNL);
256 bb_signals(BB_FATAL_SIGS, sig_catcher);
262 printf(ESC"[1;10H"); /* position on 1st hex byte in first line */
264 //TODO: //Home/End: start/end of line; '<'/'>': start/end of file
266 //Enter: goto specified position
269 //'/', Ctrl-S: search
270 //TODO: detect window resize
274 int32_t key = key; // for compiler
280 key = read_key(STDIN_FILENO, G.read_key_buffer, -1);
287 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
288 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
289 /* lowercase, then convert to '0'+10...15 */
290 key = (key | 0x20) - ('a' - '0' - 10);
292 case '0': case '1': case '2': case '3': case '4':
293 case '5': case '6': case '7': case '8': case '9':
294 if (G.current_byte == G.eof_byte) {
295 move_mapping_further();
296 if (G.current_byte == G.eof_byte) {
297 /* extend the file */
298 if (++G.size <= 0 /* overflow? */
299 || ftruncate(G.fd, G.size) != 0 /* error extending? (e.g. block dev) */
308 byte = *G.current_byte & 0xf0;
310 byte = *G.current_byte & 0x0f;
313 *G.current_byte = byte + key;
314 /* can't just print one updated hex char: need to update right-hand ASCII too */
318 if (G.current_byte == G.eof_byte)
319 break; /* eof - don't allow going past it */
320 byte = *G.current_byte;
323 putchar(bb_hexdigits_upcase[byte >> 4]);
327 if ((0xf & (uintptr_t)G.current_byte) == 0) {
328 /* rightmost pos, wrap to next line */
329 if (G.current_byte == G.eof_byte)
330 move_mapping_further();
331 printf(ESC"[46D"); /* cursor left 3*15 + 1 chars */
334 putchar(bb_hexdigits_upcase[byte & 0xf]);
338 case KEYCODE_PAGEDOWN:
342 G.current_byte += 16;
343 if (G.current_byte >= G.eof_byte) {
344 move_mapping_further();
345 if (G.current_byte > G.eof_byte) {
346 /* eof - don't allow going past it */
347 G.current_byte -= 16;
352 putchar('\n'); /* down one line, possibly scroll screen */
354 if (G.row >= G.height) {
368 if ((0xf & (uintptr_t)G.current_byte) == 0) {
369 /* leftmost pos, wrap to prev line */
370 if (G.current_byte == G.addr) {
371 move_mapping_lower();
372 if (G.current_byte == G.addr)
373 break; /* first line, don't do anything */
377 printf(ESC"[46C"); /* cursor right 3*15 + 1 chars */
388 if ((G.current_byte - G.addr) < 16) {
389 move_mapping_lower();
390 if ((G.current_byte - G.addr) < 16)
393 G.current_byte -= 16;
397 printf(ESC"[A"); /* up (won't scroll) */
399 //printf(ESC"[T"); /* scroll up */ - not implemented on Linux VT!
400 printf(ESC"M"); /* scroll up */