hexedit.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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. 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. G.row = cursor / 16;
  137. printf(ESC"[%u;%uH", 1 + G.row, 1 + pos + (cursor & 0xf) * 3);
  138. }
  139. static void redraw_cur_line(void)
  140. {
  141. char buf[LINEBUF_SIZE];
  142. uint8_t *data;
  143. off_t offset;
  144. int column;
  145. column = (0xf & (uintptr_t)G.current_byte);
  146. data = G.current_byte - column;
  147. offset = G.offset + (data - G.baseaddr);
  148. column = column*3 + G.half;
  149. column += format_line(buf, data, offset);
  150. printf("%s"
  151. "\r"
  152. "%.*s",
  153. buf + column,
  154. column, buf
  155. );
  156. }
  157. /* if remappers return 0, no change was done */
  158. static int remap(unsigned cur_pos)
  159. {
  160. if (G.baseaddr)
  161. munmap(G.baseaddr, G_mapsize);
  162. G.baseaddr = mmap(NULL,
  163. G_mapsize,
  164. PROT_READ | PROT_WRITE,
  165. MAP_SHARED,
  166. G.fd,
  167. G.offset
  168. );
  169. if (G.baseaddr == MAP_FAILED) {
  170. restore_term();
  171. bb_perror_msg_and_die("mmap");
  172. }
  173. G.current_byte = G.baseaddr + cur_pos;
  174. G.eof_byte = G.baseaddr + G_mapsize;
  175. if ((G.size - G.offset) < G_mapsize) {
  176. /* mapping covers tail of the file */
  177. /* we do have a mapped byte which is past eof */
  178. G.eof_byte = G.baseaddr + (G.size - G.offset);
  179. }
  180. return 1;
  181. }
  182. static int move_mapping_further(void)
  183. {
  184. unsigned pos;
  185. unsigned pagesize;
  186. if ((G.size - G.offset) < G_mapsize)
  187. return 0; /* can't move mapping even further, it's at the end already */
  188. pagesize = G_pagesize; /* constant on most arches */
  189. pos = G.current_byte - G.baseaddr;
  190. if (pos >= pagesize) {
  191. /* move offset up until current position is in 1st page */
  192. do {
  193. G.offset += pagesize;
  194. if (G.offset == 0) { /* whoops */
  195. G.offset -= pagesize;
  196. break;
  197. }
  198. pos -= pagesize;
  199. } while (pos >= pagesize);
  200. return remap(pos);
  201. }
  202. return 0;
  203. }
  204. static int move_mapping_lower(void)
  205. {
  206. unsigned pos;
  207. unsigned pagesize;
  208. if (G.offset == 0)
  209. return 0; /* we are at 0 already */
  210. pagesize = G_pagesize; /* constant on most arches */
  211. pos = G.current_byte - G.baseaddr;
  212. /* move offset down until current position is in last page */
  213. pos += pagesize;
  214. while (pos < G_mapsize) {
  215. pos += pagesize;
  216. G.offset -= pagesize;
  217. if (G.offset == 0)
  218. break;
  219. }
  220. pos -= pagesize;
  221. return remap(pos);
  222. }
  223. //usage:#define hexedit_trivial_usage
  224. //usage: "FILE"
  225. //usage:#define hexedit_full_usage "\n\n"
  226. //usage: "Edit FILE in hexadecimal"
  227. int hexedit_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  228. int hexedit_main(int argc UNUSED_PARAM, char **argv)
  229. {
  230. INIT_G();
  231. INIT_PAGESIZE();
  232. get_terminal_width_height(-1, NULL, &G.height);
  233. if (1) {
  234. /* reduce number of write() syscalls while PgUp/Down: fully buffered output */
  235. unsigned sz = (G.height | 0xf) * LINEBUF_SIZE;
  236. setvbuf(stdout, xmalloc(sz), _IOFBF, sz);
  237. }
  238. getopt32(argv, "^" "" "\0" "=1"/*one arg*/);
  239. argv += optind;
  240. G.fd = xopen(*argv, O_RDWR);
  241. G.size = xlseek(G.fd, 0, SEEK_END);
  242. /* TERMIOS_RAW_CRNL suppresses \n -> \r\n translation, helps with down-arrow */
  243. printf(SET_ALT_SCR);
  244. set_termios_to_raw(STDIN_FILENO, &G.orig_termios, TERMIOS_RAW_CRNL);
  245. bb_signals(BB_FATAL_SIGS, sig_catcher);
  246. remap(0);
  247. redraw(0);
  248. //TODO: //Home/End: start/end of line; '<'/'>': start/end of file
  249. //Backspace: undo
  250. //Ctrl-L: redraw
  251. //Ctrl-Z: suspend
  252. //'/', Ctrl-S: search
  253. //TODO: detect window resize
  254. for (;;) {
  255. unsigned cnt;
  256. int32_t key = key; /* for compiler */
  257. uint8_t byte;
  258. fflush_all();
  259. G.in_read_key = 1;
  260. if (!bb_got_signal)
  261. key = read_key(STDIN_FILENO, G.read_key_buffer, -1);
  262. G.in_read_key = 0;
  263. if (bb_got_signal)
  264. key = CTRL('X');
  265. cnt = 1;
  266. if ((unsigned)(key - 'A') <= 'Z' - 'A')
  267. key |= 0x20; /* convert A-Z to a-z */
  268. switch (key) {
  269. case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
  270. /* convert to '0'+10...15 */
  271. key = key - ('a' - '0' - 10);
  272. /* fall through */
  273. case '0': case '1': case '2': case '3': case '4':
  274. case '5': case '6': case '7': case '8': case '9':
  275. if (G.current_byte == G.eof_byte) {
  276. if (!move_mapping_further()) {
  277. /* already at EOF; extend the file */
  278. if (++G.size <= 0 /* overflow? */
  279. || ftruncate(G.fd, G.size) != 0 /* error extending? (e.g. block dev) */
  280. ) {
  281. G.size--;
  282. break;
  283. }
  284. G.eof_byte++;
  285. }
  286. }
  287. key -= '0';
  288. byte = *G.current_byte & 0xf0;
  289. if (!G.half) {
  290. byte = *G.current_byte & 0x0f;
  291. key <<= 4;
  292. }
  293. *G.current_byte = byte + key;
  294. /* can't just print one updated hex char: need to update right-hand ASCII too */
  295. redraw_cur_line();
  296. /* fall through */
  297. case KEYCODE_RIGHT:
  298. if (G.current_byte == G.eof_byte)
  299. break; /* eof - don't allow going past it */
  300. byte = *G.current_byte;
  301. if (!G.half) {
  302. G.half = 1;
  303. putchar(bb_hexdigits_upcase[byte >> 4]);
  304. } else {
  305. G.half = 0;
  306. G.current_byte++;
  307. if ((0xf & (uintptr_t)G.current_byte) == 0) {
  308. /* rightmost pos, wrap to next line */
  309. if (G.current_byte == G.eof_byte)
  310. move_mapping_further();
  311. printf(ESC"[46D"); /* cursor left 3*15 + 1 chars */
  312. goto down;
  313. }
  314. putchar(bb_hexdigits_upcase[byte & 0xf]);
  315. putchar(' ');
  316. }
  317. break;
  318. case KEYCODE_PAGEDOWN:
  319. cnt = G.height;
  320. case KEYCODE_DOWN:
  321. k_down:
  322. G.current_byte += 16;
  323. if (G.current_byte >= G.eof_byte) {
  324. move_mapping_further();
  325. if (G.current_byte > G.eof_byte) {
  326. /* _after_ eof - don't allow this */
  327. G.current_byte -= 16;
  328. if (G.current_byte < G.baseaddr)
  329. move_mapping_lower();
  330. break;
  331. }
  332. }
  333. down:
  334. putchar('\n'); /* down one line, possibly scroll screen */
  335. G.row++;
  336. if (G.row >= G.height) {
  337. G.row--;
  338. redraw_cur_line();
  339. }
  340. if (--cnt)
  341. goto k_down;
  342. break;
  343. case KEYCODE_LEFT:
  344. if (G.half) {
  345. G.half = 0;
  346. printf(ESC"[D");
  347. break;
  348. }
  349. if ((0xf & (uintptr_t)G.current_byte) == 0) {
  350. /* leftmost pos, wrap to prev line */
  351. if (G.current_byte == G.baseaddr) {
  352. if (!move_mapping_lower())
  353. break; /* first line, don't do anything */
  354. }
  355. G.half = 1;
  356. G.current_byte--;
  357. printf(ESC"[46C"); /* cursor right 3*15 + 1 chars */
  358. goto up;
  359. }
  360. G.half = 1;
  361. G.current_byte--;
  362. printf(ESC"[2D");
  363. break;
  364. case KEYCODE_PAGEUP:
  365. cnt = G.height;
  366. case KEYCODE_UP:
  367. k_up:
  368. if ((G.current_byte - G.baseaddr) < 16) {
  369. if (!move_mapping_lower())
  370. break; /* already at 0, stop */
  371. }
  372. G.current_byte -= 16;
  373. up:
  374. if (G.row != 0) {
  375. G.row--;
  376. printf(ESC"[A"); /* up (won't scroll) */
  377. } else {
  378. //printf(ESC"[T"); /* scroll up */ - not implemented on Linux VT!
  379. printf(ESC"M"); /* scroll up */
  380. redraw_cur_line();
  381. }
  382. if (--cnt)
  383. goto k_up;
  384. break;
  385. case '\n':
  386. case '\r':
  387. /* [Enter]: goto specified position */
  388. {
  389. char buf[sizeof(G.offset)*3 + 4];
  390. printf(ESC"[999;1H" CLEAR_TILL_EOL); /* go to last line */
  391. if (read_line_input(NULL, "Go to (dec,0Xhex,0oct): ", buf, sizeof(buf)) > 0) {
  392. off_t t;
  393. unsigned cursor;
  394. t = bb_strtoull(buf, NULL, 0);
  395. if (t >= G.size)
  396. t = G.size - 1;
  397. cursor = t & (G_pagesize - 1);
  398. t -= cursor;
  399. if (t < 0)
  400. cursor = t = 0;
  401. if (t != 0 && cursor < 0x1ff) {
  402. /* very close to end of page, possibly to EOF */
  403. /* move one page lower */
  404. t -= G_pagesize;
  405. cursor += G_pagesize;
  406. }
  407. G.offset = t;
  408. remap(cursor);
  409. redraw(cursor);
  410. break;
  411. }
  412. /* ^C/EOF/error: fall through to exiting */
  413. }
  414. case CTRL('X'):
  415. restore_term();
  416. return EXIT_SUCCESS;
  417. } /* switch */
  418. } /* for (;;) */
  419. /* not reached */
  420. return EXIT_SUCCESS;
  421. }