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 (15 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)
34 IF_VARIABLE_ARCH_PAGESIZE(unsigned pagesize
;)
35 #define G_pagesize cached_pagesize(G.pagesize)
37 uint8_t *current_byte
;
41 /* needs to be zero-inited, thus keeping it in G: */
42 char read_key_buffer
[KEYCODE_BUFFER_SIZE
];
43 struct termios orig_termios
;
45 #define G (*ptr_to_globals)
46 #define INIT_G() do { \
47 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
50 /* hopefully there aren't arches with PAGE_SIZE > 64k */
51 #define G_mapsize (64*1024)
53 /* "12ef5670 (xx )*16 _1_3_5_7_9abcdef\n"NUL */
54 #define LINEBUF_SIZE (8 + 1 + 3*16 + 16 + 1 + 1 /*paranoia:*/ + 13)
56 static void restore_term(void)
58 tcsetattr_stdin_TCSANOW(&G
.orig_termios
);
63 static void sig_catcher(int sig
)
66 /* now it's not safe to do I/O, just inform the main loop */
71 kill_myself_with_sig(sig
);
74 static int format_line(char *hex
, uint8_t *data
, off_t offset
)
81 /* Can be more than 4Gb, thus >8 chars, thus use a variable - don't assume 8! */
82 ofs_pos
= sprintf(hex
, "%08"OFF_FMT
"x ", offset
);
85 ofs_pos
= sprintf(hex
, "%04"OFF_FMT
"x ", offset
);
87 ofs_pos
= sprintf(hex
, "%08"OFF_FMT
"x ", offset
);
93 if ((G
.size
- offset
) > 0) {
95 if ((G
.size
- offset
) <= 15)
96 end
= data
+ (G
.size
- offset
) - 1;
99 *hex
++ = bb_hexdigits_upcase
[c
>> 4];
100 *hex
++ = bb_hexdigits_upcase
[c
& 0xf];
102 if (c
< ' ' || c
> 0x7e)
107 while (data
<= end1
) {
119 static void redraw(unsigned cursor
)
127 /* if cursor is past end of screen, how many lines to move down? */
128 i
= (cursor
/ 16) - G
.height
+ 1;
132 data
= G
.baseaddr
+ i
* 16;
133 offset
= G
.offset
+ i
* 16;
136 while (i
< G
.height
) {
137 char buf
[LINEBUF_SIZE
];
138 pos
= format_line(buf
, data
, offset
);
140 "\r\n%s" + (!i
) * 2, /* print \r\n only on 2nd line and later */
149 printf(ESC
"[%u;%uH", 1 + G
.row
, 1 + pos
+ (cursor
& 0xf) * 3);
152 static void redraw_cur_line(void)
154 char buf
[LINEBUF_SIZE
];
159 column
= (0xf & (uintptr_t)G
.current_byte
);
160 data
= G
.current_byte
- column
;
161 offset
= G
.offset
+ (data
- G
.baseaddr
);
163 column
= column
*3 + G
.half
;
164 column
+= format_line(buf
, data
, offset
);
173 /* if remappers return 0, no change was done */
174 static int remap(unsigned cur_pos
)
177 munmap(G
.baseaddr
, G_mapsize
);
179 G
.baseaddr
= mmap(NULL
,
181 PROT_READ
| PROT_WRITE
,
186 if (G
.baseaddr
== MAP_FAILED
) {
188 bb_simple_perror_msg_and_die("mmap");
191 G
.current_byte
= G
.baseaddr
+ cur_pos
;
193 G
.eof_byte
= G
.baseaddr
+ G_mapsize
;
194 if ((G
.size
- G
.offset
) < G_mapsize
) {
195 /* mapping covers tail of the file */
196 /* we do have a mapped byte which is past eof */
197 G
.eof_byte
= G
.baseaddr
+ (G
.size
- G
.offset
);
201 static int move_mapping_further(void)
206 if ((G
.size
- G
.offset
) < G_mapsize
)
207 return 0; /* can't move mapping even further, it's at the end already */
209 pagesize
= G_pagesize
; /* constant on most arches */
210 pos
= G
.current_byte
- G
.baseaddr
;
211 if (pos
>= pagesize
) {
212 /* move offset up until current position is in 1st page */
214 G
.offset
+= pagesize
;
215 if (G
.offset
== 0) { /* whoops */
216 G
.offset
-= pagesize
;
220 } while (pos
>= pagesize
);
225 static int move_mapping_lower(void)
231 return 0; /* we are at 0 already */
233 pagesize
= G_pagesize
; /* constant on most arches */
234 pos
= G
.current_byte
- G
.baseaddr
;
236 /* move offset down until current position is in last page */
238 while (pos
< G_mapsize
) {
240 G
.offset
-= pagesize
;
249 //usage:#define hexedit_trivial_usage
251 //usage:#define hexedit_full_usage "\n\n"
252 //usage: "Edit FILE in hexadecimal"
253 int hexedit_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
254 int hexedit_main(int argc UNUSED_PARAM
, char **argv
)
257 INIT_PAGESIZE(G
.pagesize
);
259 get_terminal_width_height(-1, NULL
, &G
.height
);
261 /* reduce number of write() syscalls while PgUp/Down: fully buffered output */
262 unsigned sz
= (G
.height
| 0xf) * LINEBUF_SIZE
;
263 setvbuf(stdout
, xmalloc(sz
), _IOFBF
, sz
);
266 getopt32(argv
, "^" "" "\0" "=1"/*one arg*/);
269 G
.fd
= xopen(*argv
, O_RDWR
);
270 G
.size
= xlseek(G
.fd
, 0, SEEK_END
);
272 /* TERMIOS_RAW_CRNL suppresses \n -> \r\n translation, helps with down-arrow */
274 set_termios_to_raw(STDIN_FILENO
, &G
.orig_termios
, TERMIOS_RAW_CRNL
);
275 bb_signals(BB_FATAL_SIGS
, sig_catcher
);
280 //TODO: //Home/End: start/end of line; '<'/'>': start/end of file
284 //'/', Ctrl-S: search
285 //TODO: detect window resize
289 int32_t key
= key
; /* for compiler */
295 key
= safe_read_key(STDIN_FILENO
, G
.read_key_buffer
, -1);
301 if ((unsigned)(key
- 'A') <= 'Z' - 'A')
302 key
|= 0x20; /* convert A-Z to a-z */
304 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
305 /* convert to '0'+10...15 */
306 key
= key
- ('a' - '0' - 10);
308 case '0': case '1': case '2': case '3': case '4':
309 case '5': case '6': case '7': case '8': case '9':
310 if (G
.current_byte
== G
.eof_byte
) {
311 if (!move_mapping_further()) {
312 /* already at EOF; extend the file */
313 if (++G
.size
<= 0 /* overflow? */
314 || ftruncate(G
.fd
, G
.size
) != 0 /* error extending? (e.g. block dev) */
323 byte
= *G
.current_byte
& 0xf0;
325 byte
= *G
.current_byte
& 0x0f;
328 *G
.current_byte
= byte
+ key
;
329 /* can't just print one updated hex char: need to update right-hand ASCII too */
333 if (G
.current_byte
== G
.eof_byte
)
334 break; /* eof - don't allow going past it */
335 byte
= *G
.current_byte
;
338 putchar(bb_hexdigits_upcase
[byte
>> 4]);
342 if ((0xf & (uintptr_t)G
.current_byte
) == 0) {
343 /* rightmost pos, wrap to next line */
344 if (G
.current_byte
== G
.eof_byte
)
345 move_mapping_further();
346 printf(ESC
"[46D"); /* cursor left 3*15 + 1 chars */
349 putchar(bb_hexdigits_upcase
[byte
& 0xf]);
353 case KEYCODE_PAGEDOWN
:
357 G
.current_byte
+= 16;
358 if (G
.current_byte
>= G
.eof_byte
) {
359 move_mapping_further();
360 if (G
.current_byte
> G
.eof_byte
) {
361 /* _after_ eof - don't allow this */
362 G
.current_byte
-= 16;
363 if (G
.current_byte
< G
.baseaddr
)
364 move_mapping_lower();
369 putchar('\n'); /* down one line, possibly scroll screen */
371 if (G
.row
>= G
.height
) {
385 if ((0xf & (uintptr_t)G
.current_byte
) == 0) {
386 /* leftmost pos, wrap to prev line */
387 if (G
.current_byte
== G
.baseaddr
) {
388 if (!move_mapping_lower())
389 break; /* first line, don't do anything */
393 printf(ESC
"[46C"); /* cursor right 3*15 + 1 chars */
404 if ((G
.current_byte
- G
.baseaddr
) < 16) {
405 if (!move_mapping_lower())
406 break; /* already at 0, stop */
408 G
.current_byte
-= 16;
412 printf(ESC
"[A"); /* up (won't scroll) */
414 //printf(ESC"[T"); /* scroll up */ - not implemented on Linux VT!
415 printf(ESC
"M"); /* scroll up */
424 /* [Enter]: goto specified position */
426 char buf
[sizeof(G
.offset
)*3 + 4];
427 printf(ESC
"[999;1H" CLEAR_TILL_EOL
); /* go to last line */
428 if (read_line_input(NULL
, "Go to (dec,0Xhex,0oct): ", buf
, sizeof(buf
)) > 0) {
432 t
= bb_strtoull(buf
, NULL
, 0);
435 cursor
= t
& (G_pagesize
- 1);
439 if (t
!= 0 && cursor
< 0x1ff) {
440 /* very close to end of page, possibly to EOF */
441 /* move one page lower */
443 cursor
+= G_pagesize
;
450 /* ^C/EOF/error: fall through to exiting */