toybox/toys/other/hexedit.c
<<
>>
Prefs
   1/* hexedit.c - Hexadecimal file editor
   2 *
   3 * Copyright 2015 Rob Landley <rob@landley.net>
   4 *
   5 * No standard.
   6
   7USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
   8
   9config HEXEDIT
  10  bool "hexedit"
  11  default y
  12  help
  13    usage: hexedit FILE
  14
  15    Hexadecimal file editor/viewer. All changes are written to disk immediately.
  16
  17    -r  Read only (display but don't edit)
  18
  19    Keys:
  20    Arrows         Move left/right/up/down by one line/column
  21    PgUp/PgDn      Move up/down by one page
  22    Home/End       Start/end of line (start/end of file with ctrl)
  23    0-9, a-f       Change current half-byte to hexadecimal value
  24    ^J or :        Jump (+/- for relative offset, otherwise absolute address)
  25    ^F or /        Find string (^G/n: next, ^D/p: previous match)
  26    u              Undo
  27    x              Toggle bw/color display
  28    q/^C/^Q/Esc    Quit
  29*/
  30
  31#define FOR_hexedit
  32#include "toys.h"
  33
  34GLOBALS(
  35  char *data, *search, keybuf[16], input[80];
  36  long long len, base, pos;
  37  int numlen, undo, undolen, mode;
  38  unsigned rows, cols;
  39)
  40
  41#define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1))
  42
  43static void show_error(char *what)
  44{
  45  printf("\e[%dH\e[41m\e[37m\e[K\e[1m%s\e[0m", TT.rows+1, what);
  46  xflush(1);
  47  msleep(500);
  48}
  49
  50// TODO: support arrow keys, insertion, and scrolling (and reuse in vi)
  51static int prompt(char *prompt, char *initial_value)
  52{
  53  int yes = 0, key, len = strlen(initial_value);
  54
  55  strcpy(TT.input, initial_value);
  56  while (1) {
  57    printf("\e[%dH\e[K\e[1m%s: \e[0m%s\e[?25h", TT.rows+1, prompt, TT.input);
  58    xflush(1);
  59
  60    key = scan_key(TT.keybuf, -1);
  61    if (key < 0 || key == 27) break;
  62    if (key == '\r') {
  63      yes = len; // Hitting enter with no input counts as cancellation.
  64      break;
  65    }
  66
  67    if (key == 0x7f && (len > 0)) TT.input[--len] = 0;
  68    else if (key == 'U'-'@') while (len > 0) TT.input[--len] = 0;
  69    else if (key >= ' ' && key < 0x7f && len < sizeof(TT.input))
  70      TT.input[len++] = key;
  71  }
  72  printf("\e[?25l");
  73
  74  return yes;
  75}
  76
  77// Render all characters printable, using color to distinguish.
  78static void draw_char(int ch)
  79{
  80  if (ch >= ' ' && ch < 0x7f) {
  81    putchar(ch);
  82    return;
  83  }
  84
  85  if (TT.mode) {
  86    if (ch>127) {
  87      printf("\e[2m");
  88      ch &= 127;
  89    }
  90    if (ch<32 || ch==127) {
  91      printf("\e[7m");
  92      if (ch==127) ch = 32;
  93      else ch += 64;
  94    }
  95    xputc(ch);
  96  } else {
  97    if (ch < ' ') printf("\e[31m%c", ch + '@');
  98    else printf("\e[35m?");
  99  }
 100  printf("\e[0m");
 101}
 102
 103static void draw_status(void)
 104{
 105  char line[80];
 106
 107  printf("\e[%dH\e[K", TT.rows+1);
 108
 109  snprintf(line, sizeof(line), "\"%s\"%s, %#llx/%#llx", *toys.optargs,
 110    FLAG(r) ? " [readonly]" : "", TT.pos, TT.len);
 111  draw_trim(line, -1, TT.cols);
 112}
 113
 114static void draw_byte(int byte)
 115{
 116  if (byte) printf("%02x", byte);
 117  else printf("\e[2m00\e[0m");
 118}
 119
 120static void draw_line(long long yy)
 121{
 122  int x, xx = 16;
 123
 124  yy = (TT.base+yy)*16;
 125  if (yy+xx>=TT.len) xx = TT.len-yy;
 126
 127  if (yy<TT.len) {
 128    printf("\r\e[%dm%0*llx\e[0m ", 33*!TT.mode, TT.numlen, yy);
 129    for (x=0; x<xx; x++) {
 130      putchar(' ');
 131      draw_byte(TT.data[yy+x]);
 132    }
 133    printf("%*s", 2+3*(16-xx), "");
 134    for (x=0; x<xx; x++) draw_char(TT.data[yy+x]);
 135    printf("%*s", 16-xx, "");
 136  }
 137  printf("\e[K");
 138}
 139
 140static void draw_page(void)
 141{
 142  int y;
 143
 144  for (y = 0; y<TT.rows; y++) {
 145    printf(y ? "\r\n" : "\e[H");
 146    draw_line(y);
 147  }
 148  draw_status();
 149}
 150
 151// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
 152static void highlight(int xx, int yy, int side)
 153{
 154  char cc = TT.data[16*(TT.base+yy)+xx];
 155  int i;
 156
 157  // Display cursor in hex area.
 158  printf("\e[%u;%uH\e[%dm", yy+1, TT.numlen+3*(xx+1), 7*(side!=2));
 159  if (side>1) draw_byte(cc);
 160  else for (i=0; i<2;) {
 161    if (side==i) printf("\e[32m");
 162    printf("%x", (cc>>(4*(1&++i)))&15);
 163  }
 164
 165  // Display cursor in text area.
 166  printf("\e[7m\e[%u;%uH"+4*(side==2), yy+1, 1+TT.numlen+17*3+xx);
 167  draw_char(cc);
 168}
 169
 170static void find_next(int pos)
 171{
 172  char *p;
 173
 174  p = memmem(TT.data+pos, TT.len-pos, TT.search, strlen(TT.search));
 175  if (p) TT.pos = p - TT.data;
 176  else show_error("No match!");
 177}
 178
 179static void find_prev(int pos)
 180{
 181  size_t len = strlen(TT.search);
 182
 183  for (; pos >= 0; pos--) {
 184    if (!memcmp(TT.data+pos, TT.search, len)) {
 185      TT.pos = pos;
 186      return;
 187    }
 188  }
 189  show_error("No match!");
 190}
 191
 192void hexedit_main(void)
 193{
 194  long long y;
 195  int x, i, side = 0, key, fd;
 196
 197  // Terminal setup
 198  TT.cols = 80;
 199  TT.rows = 24;
 200  terminal_size(&TT.cols, &TT.rows);
 201  if (TT.rows) TT.rows--;
 202  xsignal(SIGWINCH, generic_signal);
 203  sigatexit(tty_sigreset);
 204  dprintf(1, "\e[0m\e[?25l");
 205  xset_terminal(1, 1, 0, 0);
 206
 207  if (access(*toys.optargs, W_OK)) toys.optflags |= FLAG_r;
 208  fd = xopen(*toys.optargs, FLAG(r) ? O_RDONLY : O_RDWR);
 209  if ((TT.len = fdlength(fd))<1) error_exit("bad length");
 210  if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
 211  // count file length hex in digits, rounded up to multiple of 4
 212  for (TT.pos = TT.len, TT.numlen = 0; TT.pos; TT.pos >>= 4, TT.numlen++);
 213  TT.numlen += (4-TT.numlen)&3;
 214
 215  TT.data=xmmap(0, TT.len, PROT_READ|(PROT_WRITE*!FLAG(r)), MAP_SHARED, fd, 0);
 216  close(fd);
 217  draw_page();
 218
 219  for (;;) {
 220    // Scroll display if necessary
 221    if (TT.pos<0) TT.pos = 0;
 222    if (TT.pos>=TT.len) TT.pos = TT.len-1;
 223    x = TT.pos&15;
 224    y = TT.pos/16;
 225
 226    // scroll up
 227    while (y<TT.base) {
 228      if (TT.base-y>(TT.rows/2)) {
 229        TT.base = y;
 230        draw_page();
 231      } else {
 232        TT.base--;
 233        printf("\e[H\e[1L");
 234        draw_line(0);
 235      }
 236    }
 237
 238    // scroll down
 239    while (y>=TT.base+TT.rows) {
 240      if (y-(TT.base+TT.rows)>(TT.rows/2)) {
 241        TT.base = y-TT.rows-1;
 242        draw_page();
 243      } else {
 244        TT.base++;
 245        printf("\e[H\e[1M\e[%uH", TT.rows);
 246        draw_line(TT.rows-1);
 247      }
 248    }
 249
 250    draw_status();
 251    y -= TT.base;
 252
 253    // Display cursor and flush output
 254    highlight(x, y, FLAG(r) ? 3 : side);
 255    xflush(1);
 256
 257    // Wait for next key
 258    key = scan_key(TT.keybuf, -1);
 259
 260    // Window resized?
 261    if (key == -3) {
 262      toys.signal = 0;
 263      terminal_size(&TT.cols, &TT.rows);
 264      if (TT.rows) TT.rows--;
 265      draw_page();
 266      continue;
 267    }
 268
 269    if (key == 'x') {
 270      TT.mode = !TT.mode;
 271      printf("\e[0m");
 272      draw_page();
 273      continue;
 274    }
 275
 276    // Various popular ways to quit...
 277    if (key==-1||key==('C'-'@')||key==('Q'-'@')||key==27||key=='q') break;
 278    highlight(x, y, 2);
 279
 280    if (key == ('J'-'@') || key == ':' || key == '-' || key == '+') {
 281      // Jump (relative or absolute)
 282      char initial[2] = {}, *s = 0;
 283      long long val;
 284
 285      if (key == '-' || key == '+') *initial = key;
 286      if (!prompt("Jump to", initial)) continue;
 287
 288      val = estrtol(TT.input, &s, 0);
 289      if (!errno && s && !*s) {
 290        if (*TT.input == '-' || *TT.input == '+') TT.pos += val;
 291        else TT.pos = val;
 292      }
 293      continue;
 294    } else if (key == ('F'-'@') || key == '/') { // Find
 295      if (!prompt("Find", TT.search ? TT.search : "")) continue;
 296
 297      // TODO: parse hex escapes in input, and record length to support \0
 298      free(TT.search);
 299      TT.search = xstrdup(TT.input);
 300      find_next(TT.pos);
 301    } else if (TT.search && (key == ('G'-'@') || key == 'n')) { // Find next
 302      if (TT.pos < TT.len) find_next(TT.pos+1);
 303    } else if (TT.search && (key == ('D'-'@') || key == 'p')) { // Find previous
 304      if (TT.pos > 0) find_prev(TT.pos-1);
 305    }
 306
 307    // Remove cursor
 308    highlight(x, y, 2);
 309
 310    // Hex digit?
 311    if (key>='a' && key<='f') key-=32;
 312    if (!FLAG(r) && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
 313      if (!side) {
 314        long long *ll = (long long *)toybuf;
 315
 316        ll[TT.undo] = TT.pos;
 317        toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[TT.pos];
 318        if (TT.undolen < UNDO_LEN) TT.undolen++;
 319        TT.undo %= UNDO_LEN;
 320      }
 321
 322      i = key - '0';
 323      if (i>9) i -= 7;
 324      TT.data[TT.pos] &= 15<<(4*side);
 325      TT.data[TT.pos] |= i<<(4*!side);
 326
 327      if (++side==2) {
 328        highlight(x, y, side);
 329        side = 0;
 330        ++TT.pos;
 331      }
 332    } else side = 0;
 333    if (key=='u') {
 334      if (TT.undolen) {
 335        long long *ll = (long long *)toybuf;
 336
 337        TT.undolen--;
 338        if (!TT.undo) TT.undo = UNDO_LEN;
 339        TT.pos = ll[--TT.undo];
 340        TT.data[TT.pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo];
 341      }
 342    }
 343    if (key>=256) {
 344      key -= 256;
 345
 346      if (key==KEY_UP) TT.pos -= 16;
 347      else if (key==KEY_DOWN) TT.pos += 16;
 348      else if (key==KEY_RIGHT) {
 349        if (TT.pos<TT.len) TT.pos++;
 350      } else if (key==KEY_LEFT) {
 351        if (TT.pos>0) TT.pos--;
 352      } else if (key==KEY_PGUP) {
 353        TT.pos -= 16*TT.rows;
 354        if (TT.pos < 0) TT.pos = 0;
 355        TT.base = TT.pos/16;
 356        draw_page();
 357      } else if (key==KEY_PGDN) {
 358        TT.pos += 16*TT.rows;
 359        if (TT.pos > TT.len-1) TT.pos = TT.len-1;
 360        TT.base = TT.pos/16;
 361        draw_page();
 362      } else if (key==KEY_HOME) TT.pos &= ~0xf;
 363      else if (key==KEY_END) TT.pos |= 0xf;
 364      else if (key==(KEY_CTRL|KEY_HOME)) TT.pos = 0;
 365      else if (key==(KEY_CTRL|KEY_END)) TT.pos = TT.len-1;
 366    }
 367  }
 368  munmap(TT.data, TT.len);
 369  tty_reset();
 370}
 371