syslogd: add config option to include milliseconds in timestamps
[oweals/busybox.git] / miscutils / hexedit.c
1 /*
2  * Copyright (C) 2017 Denys Vlasenko <vda.linux@googlemail.com>
3  *
4  * Licensed under GPLv2, see file LICENSE in this source tree.
5  */
6 //config:config HEXEDIT
7 //config:       bool "hexedit (21 kb)"
8 //config:       default y
9 //config:       help
10 //config:       Edit file in hexadecimal.
11
12 //applet:IF_HEXEDIT(APPLET(hexedit, BB_DIR_USR_BIN, BB_SUID_DROP))
13
14 //kbuild:lib-$(CONFIG_HEXEDIT) += hexedit.o
15
16 #include "libbb.h"
17
18 #define ESC             "\033"
19 #define HOME            ESC"[H"
20 #define CLEAR           ESC"[J"
21 #define CLEAR_TILL_EOL  ESC"[K"
22 #define SET_ALT_SCR     ESC"[?1049h"
23 #define POP_ALT_SCR     ESC"[?1049l"
24
25 #undef CTRL
26 #define CTRL(c)  ((c) & (uint8_t)~0x60)
27
28 struct globals {
29         smallint half;
30         smallint in_read_key;
31         int fd;
32         unsigned height;
33         unsigned row;
34         unsigned pagesize;
35         uint8_t *baseaddr;
36         uint8_t *current_byte;
37         uint8_t *eof_byte;
38         off_t size;
39         off_t offset;
40         /* needs to be zero-inited, thus keeping it in G: */
41         char read_key_buffer[KEYCODE_BUFFER_SIZE];
42         struct termios orig_termios;
43 };
44 #define G (*ptr_to_globals)
45 #define INIT_G() do { \
46         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
47 } while (0)
48
49 //TODO: move to libbb
50 #if defined(__x86_64__) || defined(i386)
51 # define G_pagesize 4096
52 # define INIT_PAGESIZE() ((void)0)
53 #else
54 # define G_pagesize (G.pagesize)
55 # define INIT_PAGESIZE() ((void)(G.pagesize = getpagesize()))
56 #endif
57
58 /* hopefully there aren't arches with PAGE_SIZE > 64k */
59 #define G_mapsize  (64*1024)
60
61 /* "12ef5670 (xx )*16 _1_3_5_7_9abcdef\n"NUL */
62 #define LINEBUF_SIZE (8 + 1 + 3*16 + 16 + 1 + 1 /*paranoia:*/ + 13)
63
64 static void restore_term(void)
65 {
66         tcsetattr_stdin_TCSANOW(&G.orig_termios);
67         printf(POP_ALT_SCR);
68         fflush_all();
69 }
70
71 static void sig_catcher(int sig)
72 {
73         if (!G.in_read_key) {
74                 /* now it's not safe to do I/O, just inform the main loop */
75                 bb_got_signal = sig;
76                 return;
77         }
78         restore_term();
79         kill_myself_with_sig(sig);
80 }
81
82 static int format_line(char *hex, uint8_t *data, off_t offset)
83 {
84         int ofs_pos;
85         char *text;
86         uint8_t *end, *end1;
87
88 #if 1
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);
91 #else
92         if (offset <= 0xffff)
93                 ofs_pos = sprintf(hex, "%04"OFF_FMT"x ", offset);
94         else
95                 ofs_pos = sprintf(hex, "%08"OFF_FMT"x ", offset);
96 #endif
97         hex += ofs_pos;
98
99         text = hex + 16 * 3;
100         end1 = data + 15;
101         if ((G.size - offset) > 0) {
102                 end = end1;
103                 if ((G.size - offset) <= 15)
104                         end = data + (G.size - offset) - 1;
105                 while (data <= end) {
106                         uint8_t c = *data++;
107                         *hex++ = bb_hexdigits_upcase[c >> 4];
108                         *hex++ = bb_hexdigits_upcase[c & 0xf];
109                         *hex++ = ' ';
110                         if (c < ' ' || c > 0x7e)
111                                 c = '.';
112                         *text++ = c;
113                 }
114         }
115         while (data <= end1) {
116                 *hex++ = ' ';
117                 *hex++ = ' ';
118                 *hex++ = ' ';
119                 *text++ = ' ';
120                 data++;
121         }
122         *text = '\0';
123
124         return ofs_pos;
125 }
126
127 static void redraw(unsigned cursor)
128 {
129         uint8_t *data;
130         off_t offset;
131         unsigned i, pos;
132
133         printf(HOME CLEAR);
134
135         /* if cursor is past end of screen, how many lines to move down? */
136         i = (cursor / 16) - G.height + 1;
137         if ((int)i < 0)
138                 i = 0;
139
140         data = G.baseaddr + i * 16;
141         offset = G.offset + i * 16;
142         cursor -= i * 16;
143         pos = i = 0;
144         while (i < G.height) {
145                 char buf[LINEBUF_SIZE];
146                 pos = format_line(buf, data, offset);
147                 printf(
148                         "\r\n%s" + (!i) * 2, /* print \r\n only on 2nd line and later */
149                         buf
150                 );
151                 data += 16;
152                 offset += 16;
153                 i++;
154         }
155
156         G.row = cursor / 16;
157         printf(ESC"[%u;%uH", 1 + G.row, 1 + pos + (cursor & 0xf) * 3);
158 }
159
160 static void redraw_cur_line(void)
161 {
162         char buf[LINEBUF_SIZE];
163         uint8_t *data;
164         off_t offset;
165         int column;
166
167         column = (0xf & (uintptr_t)G.current_byte);
168         data = G.current_byte - column;
169         offset = G.offset + (data - G.baseaddr);
170
171         column = column*3 + G.half;
172         column += format_line(buf, data, offset);
173         printf("%s"
174                 "\r"
175                 "%.*s",
176                 buf + column,
177                 column, buf
178         );
179 }
180
181 /* if remappers return 0, no change was done */
182 static int remap(unsigned cur_pos)
183 {
184         if (G.baseaddr)
185                 munmap(G.baseaddr, G_mapsize);
186
187         G.baseaddr = mmap(NULL,
188                 G_mapsize,
189                 PROT_READ | PROT_WRITE,
190                 MAP_SHARED,
191                 G.fd,
192                 G.offset
193         );
194         if (G.baseaddr == MAP_FAILED) {
195                 restore_term();
196                 bb_simple_perror_msg_and_die("mmap");
197         }
198
199         G.current_byte = G.baseaddr + cur_pos;
200
201         G.eof_byte = G.baseaddr + G_mapsize;
202         if ((G.size - G.offset) < G_mapsize) {
203                 /* mapping covers tail of the file */
204                 /* we do have a mapped byte which is past eof */
205                 G.eof_byte = G.baseaddr + (G.size - G.offset);
206         }
207         return 1;
208 }
209 static int move_mapping_further(void)
210 {
211         unsigned pos;
212         unsigned pagesize;
213
214         if ((G.size - G.offset) < G_mapsize)
215                 return 0; /* can't move mapping even further, it's at the end already */
216
217         pagesize = G_pagesize; /* constant on most arches */
218         pos = G.current_byte - G.baseaddr;
219         if (pos >= pagesize) {
220                 /* move offset up until current position is in 1st page */
221                 do {
222                         G.offset += pagesize;
223                         if (G.offset == 0) { /* whoops */
224                                 G.offset -= pagesize;
225                                 break;
226                         }
227                         pos -= pagesize;
228                 } while (pos >= pagesize);
229                 return remap(pos);
230         }
231         return 0;
232 }
233 static int move_mapping_lower(void)
234 {
235         unsigned pos;
236         unsigned pagesize;
237
238         if (G.offset == 0)
239                 return 0; /* we are at 0 already */
240
241         pagesize = G_pagesize; /* constant on most arches */
242         pos = G.current_byte - G.baseaddr;
243
244         /* move offset down until current position is in last page */
245         pos += pagesize;
246         while (pos < G_mapsize) {
247                 pos += pagesize;
248                 G.offset -= pagesize;
249                 if (G.offset == 0)
250                         break;
251         }
252         pos -= pagesize;
253
254         return remap(pos);
255 }
256
257 //usage:#define hexedit_trivial_usage
258 //usage:        "FILE"
259 //usage:#define hexedit_full_usage "\n\n"
260 //usage:        "Edit FILE in hexadecimal"
261 int hexedit_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
262 int hexedit_main(int argc UNUSED_PARAM, char **argv)
263 {
264         INIT_G();
265         INIT_PAGESIZE();
266
267         get_terminal_width_height(-1, NULL, &G.height);
268         if (1) {
269                 /* reduce number of write() syscalls while PgUp/Down: fully buffered output */
270                 unsigned sz = (G.height | 0xf) * LINEBUF_SIZE;
271                 setvbuf(stdout, xmalloc(sz), _IOFBF, sz);
272         }
273
274         getopt32(argv, "^" "" "\0" "=1"/*one arg*/);
275         argv += optind;
276
277         G.fd = xopen(*argv, O_RDWR);
278         G.size = xlseek(G.fd, 0, SEEK_END);
279
280         /* TERMIOS_RAW_CRNL suppresses \n -> \r\n translation, helps with down-arrow */
281         printf(SET_ALT_SCR);
282         set_termios_to_raw(STDIN_FILENO, &G.orig_termios, TERMIOS_RAW_CRNL);
283         bb_signals(BB_FATAL_SIGS, sig_catcher);
284
285         remap(0);
286         redraw(0);
287
288 //TODO: //Home/End: start/end of line; '<'/'>': start/end of file
289         //Backspace: undo
290         //Ctrl-L: redraw
291         //Ctrl-Z: suspend
292         //'/', Ctrl-S: search
293 //TODO: detect window resize
294
295         for (;;) {
296                 unsigned cnt;
297                 int32_t key = key; /* for compiler */
298                 uint8_t byte;
299
300                 fflush_all();
301                 G.in_read_key = 1;
302                 if (!bb_got_signal)
303                         key = read_key(STDIN_FILENO, G.read_key_buffer, -1);
304                 G.in_read_key = 0;
305                 if (bb_got_signal)
306                         key = CTRL('X');
307
308                 cnt = 1;
309                 if ((unsigned)(key - 'A') <= 'Z' - 'A')
310                         key |= 0x20; /* convert A-Z to a-z */
311                 switch (key) {
312                 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
313                         /* convert to '0'+10...15 */
314                         key = key - ('a' - '0' - 10);
315                         /* fall through */
316                 case '0': case '1': case '2': case '3': case '4':
317                 case '5': case '6': case '7': case '8': case '9':
318                         if (G.current_byte == G.eof_byte) {
319                                 if (!move_mapping_further()) {
320                                         /* already at EOF; extend the file */
321                                         if (++G.size <= 0 /* overflow? */
322                                          || ftruncate(G.fd, G.size) != 0 /* error extending? (e.g. block dev) */
323                                         ) {
324                                                 G.size--;
325                                                 break;
326                                         }
327                                         G.eof_byte++;
328                                 }
329                         }
330                         key -= '0';
331                         byte = *G.current_byte & 0xf0;
332                         if (!G.half) {
333                                 byte = *G.current_byte & 0x0f;
334                                 key <<= 4;
335                         }
336                         *G.current_byte = byte + key;
337                         /* can't just print one updated hex char: need to update right-hand ASCII too */
338                         redraw_cur_line();
339                         /* fall through */
340                 case KEYCODE_RIGHT:
341                         if (G.current_byte == G.eof_byte)
342                                 break; /* eof - don't allow going past it */
343                         byte = *G.current_byte;
344                         if (!G.half) {
345                                 G.half = 1;
346                                 putchar(bb_hexdigits_upcase[byte >> 4]);
347                         } else {
348                                 G.half = 0;
349                                 G.current_byte++;
350                                 if ((0xf & (uintptr_t)G.current_byte) == 0) {
351                                         /* rightmost pos, wrap to next line */
352                                         if (G.current_byte == G.eof_byte)
353                                                 move_mapping_further();
354                                         printf(ESC"[46D"); /* cursor left 3*15 + 1 chars */
355                                         goto down;
356                                 }
357                                 putchar(bb_hexdigits_upcase[byte & 0xf]);
358                                 putchar(' ');
359                         }
360                         break;
361                 case KEYCODE_PAGEDOWN:
362                         cnt = G.height;
363                 case KEYCODE_DOWN:
364  k_down:
365                         G.current_byte += 16;
366                         if (G.current_byte >= G.eof_byte) {
367                                 move_mapping_further();
368                                 if (G.current_byte > G.eof_byte) {
369                                         /* _after_ eof - don't allow this */
370                                         G.current_byte -= 16;
371                                         if (G.current_byte < G.baseaddr)
372                                                 move_mapping_lower();
373                                         break;
374                                 }
375                         }
376  down:
377                         putchar('\n'); /* down one line, possibly scroll screen */
378                         G.row++;
379                         if (G.row >= G.height) {
380                                 G.row--;
381                                 redraw_cur_line();
382                         }
383                         if (--cnt)
384                                 goto k_down;
385                         break;
386
387                 case KEYCODE_LEFT:
388                         if (G.half) {
389                                 G.half = 0;
390                                 printf(ESC"[D");
391                                 break;
392                         }
393                         if ((0xf & (uintptr_t)G.current_byte) == 0) {
394                                 /* leftmost pos, wrap to prev line */
395                                 if (G.current_byte == G.baseaddr) {
396                                         if (!move_mapping_lower())
397                                                 break; /* first line, don't do anything */
398                                 }
399                                 G.half = 1;
400                                 G.current_byte--;
401                                 printf(ESC"[46C"); /* cursor right 3*15 + 1 chars */
402                                 goto up;
403                         }
404                         G.half = 1;
405                         G.current_byte--;
406                         printf(ESC"[2D");
407                         break;
408                 case KEYCODE_PAGEUP:
409                         cnt = G.height;
410                 case KEYCODE_UP:
411  k_up:
412                         if ((G.current_byte - G.baseaddr) < 16) {
413                                 if (!move_mapping_lower())
414                                         break; /* already at 0, stop */
415                         }
416                         G.current_byte -= 16;
417  up:
418                         if (G.row != 0) {
419                                 G.row--;
420                                 printf(ESC"[A"); /* up (won't scroll) */
421                         } else {
422                                 //printf(ESC"[T"); /* scroll up */ - not implemented on Linux VT!
423                                 printf(ESC"M"); /* scroll up */
424                                 redraw_cur_line();
425                         }
426                         if (--cnt)
427                                 goto k_up;
428                         break;
429
430                 case '\n':
431                 case '\r':
432                         /* [Enter]: goto specified position */
433                         {
434                                 char buf[sizeof(G.offset)*3 + 4];
435                                 printf(ESC"[999;1H" CLEAR_TILL_EOL); /* go to last line */
436                                 if (read_line_input(NULL, "Go to (dec,0Xhex,0oct): ", buf, sizeof(buf)) > 0) {
437                                         off_t t;
438                                         unsigned cursor;
439
440                                         t = bb_strtoull(buf, NULL, 0);
441                                         if (t >= G.size)
442                                                 t = G.size - 1;
443                                         cursor = t & (G_pagesize - 1);
444                                         t -= cursor;
445                                         if (t < 0)
446                                                 cursor = t = 0;
447                                         if (t != 0 && cursor < 0x1ff) {
448                                                 /* very close to end of page, possibly to EOF */
449                                                 /* move one page lower */
450                                                 t -= G_pagesize;
451                                                 cursor += G_pagesize;
452                                         }
453                                         G.offset = t;
454                                         remap(cursor);
455                                         redraw(cursor);
456                                         break;
457                                 }
458                                 /* ^C/EOF/error: fall through to exiting */
459                         }
460                 case CTRL('X'):
461                         restore_term();
462                         return EXIT_SUCCESS;
463                 } /* switch */
464         } /* for (;;) */
465
466         /* not reached */
467         return EXIT_SUCCESS;
468 }