hexedit.c 10 KB

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