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 (8.9 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] [-n 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 "dump.h"
  59
  60/* This is a NOEXEC applet. Be very careful! */
  61
  62#define OPT_l (1 << 0)
  63#define OPT_s (1 << 1)
  64#define OPT_a (1 << 2)
  65#define OPT_p (1 << 3)
  66#define OPT_i (1 << 4)
  67#define OPT_r (1 << 5)
  68#define OPT_g (1 << 6)
  69#define OPT_c (1 << 7)
  70#define OPT_o (1 << 8)
  71
  72static void reverse(unsigned opt, const char *filename)
  73{
  74        FILE *fp;
  75        char *buf;
  76
  77        fp = filename ? xfopen_for_read(filename) : stdin;
  78
  79        while ((buf = xmalloc_fgetline(fp)) != NULL) {
  80                char *p;
  81
  82                p = buf;
  83                if (!(opt & OPT_p)) {
  84                        /* skip address */
  85                        while (isxdigit(*p)) p++;
  86                        /* NB: for xxd -r, first hex portion is address even without colon */
  87                        /* If it's there, skip it: */
  88                        if (*p == ':') p++;
  89
  90//TODO: seek (or zero-pad if unseekable) to the address position
  91//NOTE: -s SEEK value should be added to the address before seeking
  92                }
  93
  94                /* Process hex bytes optionally separated by whitespace */
  95                for (;;) {
  96                        uint8_t val, c;
  97 nibble1:
  98                        p = skip_whitespace(p);
  99
 100                        c = *p++;
 101                        if (isdigit(c))
 102                                val = c - '0';
 103                        else if ((c|0x20) >= 'a' && (c|0x20) <= 'f')
 104                                val = (c|0x20) - ('a' - 10);
 105                        else {
 106                                /* xxd V1.10 is inconsistent here.
 107                                 *  echo -e "31 !3 0a 0a" | xxd -r -p
 108                                 * is "10<a0>" (no <cr>) - "!" is ignored,
 109                                 * but
 110                                 *  echo -e "31 !!343434\n30 0a" | xxd -r -p
 111                                 * is "10<cr>" - "!!" drops rest of the line.
 112                                 * We will ignore all invalid chars:
 113                                 */
 114                                if (c != '\0')
 115                                        goto nibble1;
 116                                break;
 117                        }
 118                        val <<= 4;
 119
 120                        /* Works the same with xxd V1.10:
 121                         *  echo "31 09 32 0a" | xxd -r -p
 122                         *  echo "31 0 9 32 0a" | xxd -r -p
 123                         * thus allow whitespace even within the byte:
 124                         */
 125 nibble2:
 126                        p = skip_whitespace(p);
 127
 128                        c = *p++;
 129                        if (isdigit(c))
 130                                val |= c - '0';
 131                        else if ((c|0x20) >= 'a' && (c|0x20) <= 'f')
 132                                val |= (c|0x20) - ('a' - 10);
 133                        else {
 134                                if (c != '\0') {
 135                                        /* "...3<not_hex_char>..." ignores both chars */
 136                                        goto nibble1;
 137                                }
 138                                /* Nibbles can join even through newline:
 139                                 * echo -e "31 3\n2 0a" | xxd -r -p
 140                                 * is "12<cr>".
 141                                 */
 142                                free(buf);
 143                                p = buf = xmalloc_fgetline(fp);
 144                                if (!buf)
 145                                        break;
 146                                goto nibble2;
 147                        }
 148                        putchar(val);
 149                }
 150                free(buf);
 151        }
 152        //fclose(fp);
 153        fflush_stdout_and_exit(EXIT_SUCCESS);
 154}
 155
 156static void print_C_style(const char *p, const char *hdr)
 157{
 158        printf(hdr, isdigit(p[0]) ? "__" : "");
 159        while (*p) {
 160                bb_putchar(isalnum(*p) ? *p : '_');
 161                p++;
 162        }
 163}
 164
 165int xxd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 166int xxd_main(int argc UNUSED_PARAM, char **argv)
 167{
 168        char buf[80];
 169        dumper_t *dumper;
 170        char *opt_l, *opt_s, *opt_o;
 171        unsigned bytes = 2;
 172        unsigned cols = 0;
 173        unsigned opt;
 174        int r;
 175
 176        dumper = alloc_dumper();
 177
 178        opt = getopt32(argv, "^" "l:s:apirg:+c:+o:" "\0" "?1" /* 1 argument max */,
 179                        &opt_l, &opt_s, &bytes, &cols, &opt_o
 180        );
 181        argv += optind;
 182
 183        dumper->dump_vflag = ALL;
 184//      if (opt & OPT_a)
 185//              dumper->dump_vflag = SKIPNUL; ..does not exist
 186        if (opt & OPT_l) {
 187                dumper->dump_length = xstrtou_range(
 188                                opt_l,
 189                                /*base:*/ 0,
 190                                /*lo:*/ 0, /*hi:*/ INT_MAX
 191                );
 192        }
 193        if (opt & OPT_s) {
 194                dumper->dump_skip = xstrtoull_range(
 195                                opt_s,
 196                                /*base:*/ 0,
 197                                /*lo:*/ 0, /*hi:*/ OFF_T_MAX
 198                );
 199                //BUGGY for /proc/version (unseekable?)
 200        }
 201
 202        if (opt & OPT_r) {
 203                reverse(opt, argv[0]);
 204        }
 205
 206        if (opt & OPT_o) {
 207                /* -o accepts negative numbers too */
 208                dumper->xxd_displayoff = xstrtoll(opt_o, /*base:*/ 0);
 209        }
 210
 211        if (opt & OPT_p) {
 212                if (cols == 0)
 213                        cols = 30;
 214                bytes = cols; /* -p ignores -gN */
 215        } else {
 216                if (cols == 0)
 217                        cols = (opt & OPT_i) ? 12 : 16;
 218                if (opt & OPT_i) {
 219                        bytes = 1; // -i ignores -gN
 220                        // output is "  0xXX, 0xXX, 0xXX...", add leading space
 221                        bb_dump_add(dumper, "\" \"");
 222                } else
 223                        bb_dump_add(dumper, "\"%08.8_ax: \""); // "address: "
 224        }
 225
 226        if (bytes < 1 || bytes >= cols) {
 227                sprintf(buf, "%u/1 \"%%02x\"", cols); // cols * "XX"
 228                bb_dump_add(dumper, buf);
 229        }
 230        else if (bytes == 1) {
 231                if (opt & OPT_i)
 232                        sprintf(buf, "%u/1 \" 0x%%02x,\"", cols); // cols * " 0xXX,"
 233//TODO: compat: omit the last comma after the very last byte
 234                else
 235                        sprintf(buf, "%u/1 \"%%02x \"", cols); // cols * "XX "
 236                bb_dump_add(dumper, buf);
 237        }
 238        else {
 239/* Format "print byte" with and without trailing space */
 240#define BS "/1 \"%02x \""
 241#define B  "/1 \"%02x\""
 242                unsigned i;
 243                char *bigbuf = xmalloc(cols * (sizeof(BS)-1));
 244                char *p = bigbuf;
 245                for (i = 1; i <= cols; i++) {
 246                        if (i == cols || i % bytes)
 247                                p = stpcpy(p, B);
 248                        else
 249                                p = stpcpy(p, BS);
 250                }
 251                // for -g3, this results in B B BS B B BS... B = "xxxxxx xxxxxx .....xx"
 252                // todo: can be more clever and use
 253                // one 'bytes-1/1 "%02x"' format instead of many "B B B..." formats
 254                //bb_error_msg("ADDED:'%s'", bigbuf);
 255                bb_dump_add(dumper, bigbuf);
 256                free(bigbuf);
 257        }
 258
 259        if (!(opt & (OPT_p|OPT_i))) {
 260                sprintf(buf, "\"  \"%u/1 \"%%_p\"\"\n\"", cols); // "  ASCII\n"
 261                bb_dump_add(dumper, buf);
 262        } else {
 263                bb_dump_add(dumper, "\"\n\"");
 264                dumper->xxd_eofstring = "\n";
 265        }
 266
 267        if ((opt & OPT_i) && argv[0]) {
 268                print_C_style(argv[0], "unsigned char %s");
 269                printf("[] = {\n");
 270        }
 271        r = bb_dump_dump(dumper, argv);
 272        if (r == 0 && (opt & OPT_i) && argv[0]) {
 273                print_C_style(argv[0], "};\nunsigned int %s");
 274                printf("_len = %"OFF_FMT"u;\n", dumper->address);
 275        }
 276        return r;
 277}
 278