busybox/util-linux/hexdump_xxd.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * xxd implementation for busybox
   4 *
   5 * Copyright (c) 2017 Denys Vlasenko <vda.linux@gmail.com>
   6 *
   7 * Licensed under GPLv2, see file LICENSE in this source tree.
   8 */
   9//config:config XXD
  10//config:       bool "xxd (11 kb)"
  11//config:       default y
  12//config:       help
  13//config:       The xxd utility is used to display binary data in a readable
  14//config:       way that is comparable to the output from most hex editors.
  15
  16//applet:IF_XXD(APPLET_NOEXEC(xxd, xxd, BB_DIR_USR_BIN, BB_SUID_DROP, xxd))
  17
  18//kbuild:lib-$(CONFIG_XXD) += hexdump_xxd.o
  19
  20// $ xxd --version
  21// xxd V1.10 27oct98 by Juergen Weigert
  22// $ xxd --help
  23// Usage:
  24//       xxd [options] [infile [outfile]]
  25//    or
  26//       xxd -r [-s [-]offset] [-c cols] [-ps] [infile [outfile]]
  27// Options:
  28//    -a          toggle autoskip: A single '*' replaces nul-lines. Default off.
  29//    -b          binary digit dump (incompatible with -ps,-i,-r). Default hex.
  30//    -c cols     format <cols> octets per line. Default 16 (-i: 12, -ps: 30).
  31//    -E          show characters in EBCDIC. Default ASCII.
  32//    -e          little-endian dump (incompatible with -ps,-i,-r).
  33//    -g          number of octets per group in normal output. Default 2 (-e: 4).
  34//    -i          output in C include file style.
  35//    -l len      stop after <len> octets.
  36//    -o off      add <off> to the displayed file position.
  37//    -ps         output in postscript plain hexdump style.
  38//    -r          reverse operation: convert (or patch) hexdump into binary.
  39//    -r -s off   revert with <off> added to file positions found in hexdump.
  40//    -s [+][-]seek  start at <seek> bytes abs. (or +: rel.) infile offset.
  41//    -u          use upper case hex letters.
  42
  43//usage:#define xxd_trivial_usage
  44//usage:       "[-pri] [-g N] [-c N] [-l LEN] [-s OFS] [-o OFS] [FILE]"
  45//usage:#define xxd_full_usage "\n\n"
  46//usage:       "Hex dump FILE (or stdin)\n"
  47//usage:     "\n        -g N            Bytes per group"
  48//usage:     "\n        -c N            Bytes per line"
  49//usage:     "\n        -p              Show only hex bytes, assumes -c30"
  50//usage:     "\n        -i              C include file style"
  51// exactly the same help text lines in hexdump and xxd:
  52//usage:     "\n        -l LENGTH       Show only first LENGTH bytes"
  53//usage:     "\n        -s OFFSET       Skip OFFSET bytes"
  54//usage:     "\n        -o OFFSET       Add OFFSET to displayed offset"
  55//usage:     "\n        -r              Reverse (with -p, assumes no offsets in input)"
  56
  57#include "libbb.h"
  58#include "common_bufsiz.h"
  59#include "dump.h"
  60
  61/* This is a NOEXEC applet. Be very careful! */
  62
  63#define OPT_l (1 << 0)
  64#define OPT_s (1 << 1)
  65#define OPT_a (1 << 2)
  66#define OPT_p (1 << 3)
  67#define OPT_i (1 << 4)
  68#define OPT_r (1 << 5)
  69#define OPT_g (1 << 6)
  70#define OPT_c (1 << 7)
  71#define OPT_o (1 << 8)
  72
  73#define fillbuf bb_common_bufsiz1
  74
  75static void write_zeros(off_t count)
  76{
  77        errno = 0;
  78        do {
  79                unsigned sz = count < COMMON_BUFSIZE ? (unsigned)count : COMMON_BUFSIZE;
  80                if (fwrite(fillbuf, 1, sz, stdout) != sz)
  81                        bb_simple_perror_msg_and_die("write error");
  82                count -= sz;
  83        } while (count != 0);
  84}
  85
  86static void reverse(unsigned opt, const char *filename, char *opt_s)
  87{
  88        FILE *fp;
  89        char *buf;
  90        off_t cur, opt_s_ofs;
  91
  92        memset(fillbuf, 0, COMMON_BUFSIZE);
  93        opt_s_ofs = cur = 0;
  94        if (opt_s) {
  95                opt_s_ofs = BB_STRTOOFF(opt_s, NULL, 0);
  96                if (errno || opt_s_ofs < 0)
  97                        bb_error_msg_and_die("invalid number '%s'", opt_s);
  98        }
  99
 100        fp = filename ? xfopen_for_read(filename) : stdin;
 101
 102 get_new_line:
 103        while ((buf = xmalloc_fgetline(fp)) != NULL) {
 104                char *p;
 105
 106                p = buf;
 107                if (!(opt & OPT_p)) {
 108                        char *end;
 109                        off_t ofs;
 110 skip_address:
 111                        p = skip_whitespace(p);
 112                        ofs = BB_STRTOOFF(p, &end, 16);
 113                        if ((errno && errno != EINVAL)
 114                         || ofs < 0
 115                        /* -s SEEK value should be added before seeking */
 116                         || (ofs += opt_s_ofs) < 0
 117                        ) {
 118                                bb_error_msg_and_die("invalid number '%s'", p);
 119                        }
 120                        if (ofs != cur) {
 121                                if (fseeko(stdout, ofs, SEEK_SET) != 0) {
 122                                        if (ofs < cur)
 123                                                bb_simple_perror_msg_and_die("cannot seek");
 124                                        write_zeros(ofs - cur);
 125                                }
 126                                cur = ofs;
 127                        }
 128                        p = end;
 129                        /* NB: for xxd -r, first hex portion is address even without colon */
 130                        /* But if colon is there, skip it: */
 131                        if (*p == ':')
 132                                p++;
 133                }
 134
 135                /* Process hex bytes optionally separated by whitespace */
 136                for (;;) {
 137                        uint8_t val, c;
 138                        int badchar = 0;
 139 nibble1:
 140                        if (opt & OPT_p)
 141                                p = skip_whitespace(p);
 142                        c = *p++;
 143                        if (isdigit(c))
 144                                val = c - '0';
 145                        else if ((c|0x20) >= 'a' && (c|0x20) <= 'f')
 146                                val = (c|0x20) - ('a' - 10);
 147                        else {
 148                                /* xxd V1.10 allows one non-hexnum char:
 149                                 *  echo -e "31 !3 0a 0a" | xxd -r -p
 150                                 * is "10<a0>" (no <cr>) - "!" is ignored,
 151                                 * but stops for more than one:
 152                                 *  echo -e "31 !!343434\n30 0a" | xxd -r -p
 153                                 * is "10<cr>" - "!!" drops rest of the line.
 154                                 * Note: this also covers whitespace chars:
 155                                 * xxxxxxxx: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f  0123456789:;<=>?
 156                                 *  detects this ^ - skips this one space
 157                                 * xxxxxxxx: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f  0123456789:;<=>?
 158                                 *                                     detects this ^^ - skips the rest
 159                                 */
 160                                if (c == '\0' || badchar)
 161                                        break;
 162                                badchar++;
 163                                goto nibble1;
 164                        }
 165                        val <<= 4;
 166
 167 nibble2:
 168                        if (opt & OPT_p) {
 169                                /* Works the same with xxd V1.10:
 170                                 *  echo "31 09 32 0a" | xxd -r -p
 171                                 *  echo "31 0 9 32 0a" | xxd -r -p
 172                                 * thus allow whitespace (even multiple chars)
 173                                 * after byte's 1st char:
 174                                 */
 175                                p = skip_whitespace(p);
 176                        }
 177
 178                        c = *p++;
 179                        if (isdigit(c))
 180                                val |= c - '0';
 181                        else if ((c|0x20) >= 'a' && (c|0x20) <= 'f')
 182                                val |= (c|0x20) - ('a' - 10);
 183                        else {
 184                                if (c != '\0') {
 185                                        /* "...3<not_hex_char>...": ignore "3",
 186                                         * skip everything up to next hexchar or newline:
 187                                         */
 188                                        while (!isxdigit(*p)) {
 189                                                if (*p == '\0') {
 190                                                        free(buf);
 191                                                        goto get_new_line;
 192                                                }
 193                                                p++;
 194                                        }
 195                                        goto nibble1;
 196                                }
 197                                /* Nibbles can join even through newline:
 198                                 * echo -e "31 3\n2 0a" | xxd -r -p
 199                                 * is "12<cr>".
 200                                 */
 201                                free(buf);
 202                                p = buf = xmalloc_fgetline(fp);
 203                                if (!buf)
 204                                        break;
 205                                if (!(opt & OPT_p)) /* -p and !-p: different behavior */
 206                                        goto skip_address;
 207                                goto nibble2;
 208                        }
 209                        putchar(val);
 210                        cur++;
 211                } /* for(;;) */
 212                free(buf);
 213        }
 214        //fclose(fp);
 215        fflush_stdout_and_exit_SUCCESS();
 216}
 217
 218static void print_C_style(const char *p, const char *hdr)
 219{
 220        printf(hdr, isdigit(p[0]) ? "__" : "");
 221        while (*p) {
 222                bb_putchar(isalnum(*p) ? *p : '_');
 223                p++;
 224        }
 225}
 226
 227int xxd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 228int xxd_main(int argc UNUSED_PARAM, char **argv)
 229{
 230        char buf[80];
 231        dumper_t *dumper;
 232        char *opt_l, *opt_o;
 233        char *opt_s = NULL;
 234        unsigned bytes = 2;
 235        unsigned cols = 0;
 236        unsigned opt;
 237        int r;
 238
 239        setup_common_bufsiz();
 240
 241        dumper = alloc_dumper();
 242
 243        opt = getopt32(argv, "^" "l:s:apirg:+c:+o:" "\0" "?1" /* 1 argument max */,
 244                        &opt_l, &opt_s, &bytes, &cols, &opt_o
 245        );
 246        argv += optind;
 247
 248        dumper->dump_vflag = ALL;
 249//      if (opt & OPT_a)
 250//              dumper->dump_vflag = SKIPNUL; ..does not exist
 251        if (opt & OPT_l) {
 252                dumper->dump_length = xstrtou_range(
 253                                opt_l,
 254                                /*base:*/ 0,
 255                                /*lo:*/ 0, /*hi:*/ INT_MAX
 256                );
 257        }
 258        if (opt & OPT_s) {
 259                dumper->dump_skip = xstrtoull_range(
 260                                opt_s,
 261                                /*base:*/ 0,
 262                                /*lo:*/ 0, /*hi:*/ OFF_T_MAX
 263                );
 264                //BUGGY for /proc/version (unseekable?)
 265        }
 266
 267        if (opt & OPT_r) {
 268                reverse(opt, argv[0], opt_s);
 269        }
 270
 271        if (opt & OPT_o) {
 272                /* -o accepts negative numbers too */
 273                dumper->xxd_displayoff = xstrtoll(opt_o, /*base:*/ 0);
 274        }
 275
 276        if (opt & OPT_p) {
 277                if (cols == 0)
 278                        cols = 30;
 279                bytes = cols; /* -p ignores -gN */
 280        } else {
 281                if (cols == 0)
 282                        cols = (opt & OPT_i) ? 12 : 16;
 283                if (opt & OPT_i) {
 284                        bytes = 1; // -i ignores -gN
 285                        // output is "  0xXX, 0xXX, 0xXX...", add leading space
 286                        bb_dump_add(dumper, "\" \"");
 287                } else
 288                        bb_dump_add(dumper, "\"%08_ax: \""); // "address: "
 289        }
 290
 291        if (bytes < 1 || bytes >= cols) {
 292                sprintf(buf, "%u/1 \"%%02x\"", cols); // cols * "XX"
 293                bb_dump_add(dumper, buf);
 294        }
 295        else if (bytes == 1) {
 296                if (opt & OPT_i)
 297                        sprintf(buf, "%u/1 \" 0x%%02x,\"", cols); // cols * " 0xXX,"
 298//TODO: compat: omit the last comma after the very last byte
 299                else
 300                        sprintf(buf, "%u/1 \"%%02x \"", cols); // cols * "XX "
 301                bb_dump_add(dumper, buf);
 302        }
 303        else {
 304/* Format "print byte" with and without trailing space */
 305#define BS "/1 \"%02x \""
 306#define B  "/1 \"%02x\""
 307                unsigned i;
 308                char *bigbuf = xmalloc(cols * (sizeof(BS)-1));
 309                char *p = bigbuf;
 310                for (i = 1; i <= cols; i++) {
 311                        if (i == cols || i % bytes)
 312                                p = stpcpy(p, B);
 313                        else
 314                                p = stpcpy(p, BS);
 315                }
 316                // for -g3, this results in B B BS B B BS... B = "xxxxxx xxxxxx .....xx"
 317                // todo: can be more clever and use
 318                // one 'bytes-1/1 "%02x"' format instead of many "B B B..." formats
 319                //bb_error_msg("ADDED:'%s'", bigbuf);
 320                bb_dump_add(dumper, bigbuf);
 321                free(bigbuf);
 322        }
 323
 324        if (!(opt & (OPT_p|OPT_i))) {
 325                sprintf(buf, "\"  \"%u/1 \"%%_p\"\"\n\"", cols); // "  ASCII\n"
 326                bb_dump_add(dumper, buf);
 327        } else {
 328                bb_dump_add(dumper, "\"\n\"");
 329                dumper->xxd_eofstring = "\n";
 330        }
 331
 332        if ((opt & OPT_i) && argv[0]) {
 333                print_C_style(argv[0], "unsigned char %s");
 334                printf("[] = {\n");
 335        }
 336        r = bb_dump_dump(dumper, argv);
 337        if (r == 0 && (opt & OPT_i) && argv[0]) {
 338                print_C_style(argv[0], "};\nunsigned int %s");
 339                printf("_len = %"OFF_FMT"u;\n", dumper->address);
 340        }
 341        return r;
 342}
 343