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