xref: /aosp_15_r20/external/toybox/toys/other/hexedit.c (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
1 /* hexedit.c - Hexadecimal file editor
2  *
3  * Copyright 2015 Rob Landley <[email protected]>
4  *
5  * No standard.
6 
7 USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN))
8 
9 config HEXEDIT
10   bool "hexedit"
11   default y
12   help
13     usage: hexedit [-r] FILE
14 
15     Hexadecimal file editor/viewer. All changes are written to disk immediately.
16 
17     -r	Read only (display but don't edit)
18 
19     Keys:
20     Arrows         Move left/right/up/down by one line/column
21     PgUp/PgDn      Move up/down by one page
22     Home/End       Start/end of line (start/end of file with ctrl)
23     0-9, a-f       Change current half-byte to hexadecimal value
24     ^J or :        Jump (+/- for relative offset, otherwise absolute address)
25     ^F or /        Find string (^G/n: next, ^D/p: previous match)
26     u              Undo
27     x              Toggle bw/color display
28     q/^C/^Q/Esc    Quit
29 */
30 
31 #define FOR_hexedit
32 #include "toys.h"
33 
34 GLOBALS(
35   char *data, *search, keybuf[16], input[80];
36   long long len, base, pos;
37   int numlen, undo, undolen, mode;
38   unsigned rows, cols;
39 )
40 
41 #define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
42 
show_error(char * what)43 static void show_error(char *what)
44 {
45   printf("\e[%dH\e[41m\e[37m\e[K\e[1m%s\e[0m", TT.rows+1, what);
46   fflush(0);
47   msleep(500);
48 }
49 
50 // TODO: support arrow keys, insertion, and scrolling (and reuse in vi)
prompt(char * prompt,char * initial_value)51 static int prompt(char *prompt, char *initial_value)
52 {
53   int yes = 0, key, len = strlen(initial_value);
54 
55   strcpy(TT.input, initial_value);
56   while (1) {
57     printf("\e[%dH\e[K\e[1m%s: \e[0m%s\e[?25h", TT.rows+1, prompt, TT.input);
58     fflush(0);
59 
60     key = scan_key(TT.keybuf, -1);
61     if (key < 0 || key == 27) break;
62     if (key == '\r') {
63       yes = len; // Hitting enter with no input counts as cancellation.
64       break;
65     }
66 
67     if (key == 0x7f && (len > 0)) TT.input[--len] = 0;
68     else if (key == 'U'-'@') while (len > 0) TT.input[--len] = 0;
69     else if (key >= ' ' && key < 0x7f && len < sizeof(TT.input))
70       TT.input[len++] = key;
71   }
72   printf("\e[?25l");
73 
74   return yes;
75 }
76 
77 // Render all characters printable, using color to distinguish.
draw_char(int ch)78 static void draw_char(int ch)
79 {
80   if (ch >= ' ' && ch < 0x7f) {
81     putchar(ch);
82     return;
83   }
84 
85   if (TT.mode) {
86     if (ch>127) {
87       printf("\e[2m");
88       ch &= 127;
89     }
90     if (ch<32 || ch==127) {
91       printf("\e[7m");
92       if (ch==127) ch = 32;
93       else ch += 64;
94     }
95     xputc(ch);
96   } else {
97     if (ch < ' ') printf("\e[31m%c", ch + '@');
98     else printf("\e[35m?");
99   }
100   printf("\e[0m");
101 }
102 
draw_status(void)103 static void draw_status(void)
104 {
105   char line[80];
106 
107   printf("\e[%dH\e[K", TT.rows+1);
108 
109   snprintf(line, sizeof(line), "\"%s\"%s, %#llx/%#llx", *toys.optargs,
110     FLAG(r) ? " [readonly]" : "", TT.pos, TT.len);
111   draw_trim(line, -1, TT.cols);
112 }
113 
draw_byte(int byte)114 static void draw_byte(int byte)
115 {
116   if (byte) printf("%02x", byte);
117   else printf("\e[2m00\e[0m");
118 }
119 
draw_line(long long yy)120 static void draw_line(long long yy)
121 {
122   int x, xx = 16;
123 
124   yy = (TT.base+yy)*16;
125   if (yy+xx>=TT.len) xx = TT.len-yy;
126 
127   if (yy<TT.len) {
128     printf("\r\e[%dm%0*llx\e[0m ", 33*!TT.mode, TT.numlen, yy);
129     for (x=0; x<xx; x++) {
130       putchar(' ');
131       draw_byte(TT.data[yy+x]);
132     }
133     printf("%*s", 2+3*(16-xx), "");
134     for (x=0; x<xx; x++) draw_char(TT.data[yy+x]);
135     printf("%*s", 16-xx, "");
136   }
137   printf("\e[K");
138 }
139 
draw_page(void)140 static void draw_page(void)
141 {
142   int y;
143 
144   for (y = 0; y<TT.rows; y++) {
145     printf(y ? "\r\n" : "\e[H");
146     draw_line(y);
147   }
148   draw_status();
149 }
150 
151 // side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
highlight(int xx,int yy,int side)152 static void highlight(int xx, int yy, int side)
153 {
154   char cc = TT.data[16*(TT.base+yy)+xx];
155   int i;
156 
157   // Display cursor in hex area.
158   printf("\e[%u;%uH\e[%dm", yy+1, TT.numlen+3*(xx+1), 7*(side!=2));
159   if (side>1) draw_byte(cc);
160   else for (i=0; i<2;) {
161     if (side==i) printf("\e[32m");
162     printf("%x", (cc>>(4*(1&++i)))&15);
163   }
164 
165   // Display cursor in text area.
166   printf("\e[7m\e[%u;%uH"+4*(side==2), yy+1, 1+TT.numlen+17*3+xx);
167   draw_char(cc);
168 }
169 
find_next(int pos)170 static void find_next(int pos)
171 {
172   char *p;
173 
174   p = memmem(TT.data+pos, TT.len-pos, TT.search, strlen(TT.search));
175   if (p) TT.pos = p - TT.data;
176   else show_error("No match!");
177 }
178 
find_prev(int pos)179 static void find_prev(int pos)
180 {
181   size_t len = strlen(TT.search);
182 
183   for (; pos >= 0; pos--) {
184     if (!smemcmp(TT.data+pos, TT.search, len)) {
185       TT.pos = pos;
186       return;
187     }
188   }
189   show_error("No match!");
190 }
191 
hexedit_main(void)192 void hexedit_main(void)
193 {
194   long long y;
195   int x, i, side = 0, key, fd;
196 
197   // Terminal setup
198   TT.cols = 80;
199   TT.rows = 24;
200   terminal_size(&TT.cols, &TT.rows);
201   if (TT.rows) TT.rows--;
202   xsignal(SIGWINCH, generic_signal);
203   sigatexit(tty_sigreset);
204   dprintf(1, "\e[0m\e[?25l");
205   xset_terminal(1, 1, 0, 0);
206 
207   if (access(*toys.optargs, W_OK)) toys.optflags |= FLAG_r;
208   fd = xopen(*toys.optargs, FLAG(r) ? O_RDONLY : O_RDWR);
209   if ((TT.len = fdlength(fd))<1) error_exit("bad length");
210   if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
211   // count file length hex in digits, rounded up to multiple of 4
212   for (TT.pos = TT.len, TT.numlen = 0; TT.pos; TT.pos >>= 4, TT.numlen++);
213   TT.numlen += (4-TT.numlen)&3;
214 
215   TT.data=xmmap(0, TT.len, PROT_READ|(PROT_WRITE*!FLAG(r)), MAP_SHARED, fd, 0);
216   close(fd);
217   draw_page();
218 
219   for (;;) {
220     // Scroll display if necessary
221     if (TT.pos<0) TT.pos = 0;
222     if (TT.pos>=TT.len) TT.pos = TT.len-1;
223     x = TT.pos&15;
224     y = TT.pos/16;
225 
226     // scroll up
227     while (y<TT.base) {
228       if (TT.base-y>(TT.rows/2)) {
229         TT.base = y;
230         draw_page();
231       } else {
232         TT.base--;
233         printf("\e[H\e[1L");
234         draw_line(0);
235       }
236     }
237 
238     // scroll down
239     while (y>=TT.base+TT.rows) {
240       if (y-(TT.base+TT.rows)>(TT.rows/2)) {
241         TT.base = y-TT.rows-1;
242         draw_page();
243       } else {
244         TT.base++;
245         printf("\e[H\e[1M\e[%uH", TT.rows);
246         draw_line(TT.rows-1);
247       }
248     }
249 
250     draw_status();
251     y -= TT.base;
252 
253     // Display cursor and flush output
254     highlight(x, y, FLAG(r) ? 3 : side);
255     fflush(0);
256 
257     // Wait for next key
258     key = scan_key(TT.keybuf, -1);
259 
260     // Window resized?
261     if (key == -3) {
262       toys.signal = 0;
263       terminal_size(&TT.cols, &TT.rows);
264       if (TT.rows) TT.rows--;
265       draw_page();
266       continue;
267     }
268 
269     if (key == 'x') {
270       TT.mode = !TT.mode;
271       printf("\e[0m");
272       draw_page();
273       continue;
274     }
275 
276     // Various popular ways to quit...
277     if (key==-1||key==('C'-'@')||key==('Q'-'@')||key==27||key=='q') break;
278     highlight(x, y, 2);
279 
280     if (key == ('J'-'@') || key == ':' || key == '-' || key == '+') {
281       // Jump (relative or absolute)
282       char initial[2] = {}, *s = 0;
283       long long val;
284 
285       if (key == '-' || key == '+') *initial = key;
286       if (!prompt("Jump to", initial)) continue;
287 
288       val = estrtol(TT.input, &s, 0);
289       if (!errno && s && !*s) {
290         if (*TT.input == '-' || *TT.input == '+') TT.pos += val;
291         else TT.pos = val;
292       }
293       continue;
294     } else if (key == ('F'-'@') || key == '/') { // Find
295       if (!prompt("Find", TT.search ? TT.search : "")) continue;
296 
297       // TODO: parse hex escapes in input, and record length to support \0
298       free(TT.search);
299       TT.search = xstrdup(TT.input);
300       find_next(TT.pos);
301     } else if (TT.search && (key == ('G'-'@') || key == 'n')) { // Find next
302       if (TT.pos < TT.len) find_next(TT.pos+1);
303     } else if (TT.search && (key == ('D'-'@') || key == 'p')) { // Find previous
304       if (TT.pos > 0) find_prev(TT.pos-1);
305     }
306 
307     // Remove cursor
308     highlight(x, y, 2);
309 
310     // Hex digit?
311     if (key>='a' && key<='f') key-=32;
312     if (!FLAG(r) && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
313       if (!side) {
314         long long *ll = (long long *)toybuf;
315 
316         ll[TT.undo] = TT.pos;
317         toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[TT.pos];
318         if (TT.undolen < UNDO_LEN) TT.undolen++;
319         TT.undo %= UNDO_LEN;
320       }
321 
322       i = key - '0';
323       if (i>9) i -= 7;
324       TT.data[TT.pos] &= 15<<(4*side);
325       TT.data[TT.pos] |= i<<(4*!side);
326 
327       if (++side==2) {
328         highlight(x, y, side);
329         side = 0;
330         ++TT.pos;
331       }
332     } else side = 0;
333     if (key=='u') {
334       if (TT.undolen) {
335         long long *ll = (long long *)toybuf;
336 
337         TT.undolen--;
338         if (!TT.undo) TT.undo = UNDO_LEN;
339         TT.pos = ll[--TT.undo];
340         TT.data[TT.pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
341       }
342     }
343     if (key>=256) {
344       key -= 256;
345 
346       if (key==KEY_UP) TT.pos -= 16;
347       else if (key==KEY_DOWN) TT.pos += 16;
348       else if (key==KEY_RIGHT) {
349         if (TT.pos<TT.len) TT.pos++;
350       } else if (key==KEY_LEFT) {
351         if (TT.pos>0) TT.pos--;
352       } else if (key==KEY_PGUP) {
353         TT.pos -= 16*TT.rows;
354         if (TT.pos < 0) TT.pos = 0;
355         TT.base = TT.pos/16;
356         draw_page();
357       } else if (key==KEY_PGDN) {
358         TT.pos += 16*TT.rows;
359         if (TT.pos > TT.len-1) TT.pos = TT.len-1;
360         TT.base = TT.pos/16;
361         draw_page();
362       } else if (key==KEY_HOME) TT.pos &= ~0xf;
363       else if (key==KEY_END) TT.pos |= 0xf;
364       else if (key==(KEY_CTRL|KEY_HOME)) TT.pos = 0;
365       else if (key==(KEY_CTRL|KEY_END)) TT.pos = TT.len-1;
366     }
367   }
368   munmap(TT.data, TT.len);
369   tty_reset();
370 }
371