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