hexedit.c 10 KB

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