├── .gitignore ├── LICENSE ├── Makefile ├── README ├── xcd.1 └── xcd.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | xcd 3 | *.tar.gz 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Brian Raiter 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | The software is provided "as is", without warranty of any kind, 15 | express or implied, including but not limited to the warranties of 16 | merchantability, fitness for a particular purpose and noninfringement. 17 | In no event shall the authors or copyright holders be liable for any 18 | claim, damages or other liability, whether in an action of contract, 19 | tort or otherwise, arising from, out of or in connection with the 20 | software or the use or other dealings in the software. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -Wextra -O2 3 | LDFLAGS = -Wall -Wextra -O2 -s 4 | LOADLIBES = -ltinfo 5 | PREFIX = /usr/local 6 | 7 | xcd: xcd.o 8 | xcd.o: xcd.c 9 | 10 | clean: 11 | rm -f xcd xcd.o 12 | 13 | install: 14 | cp xcd $(PREFIX)/bin/ 15 | cp xcd.1 $(PREFIX)/share/man/man1/ 16 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NOTE TO GITHUB USERS 2 | 3 | This repository is no longer being updated on github.com, and will 4 | soon be closed. Please visit the project home page to find a link to 5 | its latest home: 6 | 7 | 8 | 9 | 10 | About This Program 11 | 12 | This program provides a colorized version of xxd(1), assigning colors 13 | to byte values found in the input, thus making it easier to see 14 | patterns of repeated values. 15 | 16 | The source code also provides a very simple example of using the 17 | tinfo library to inject control codes into standard output, without 18 | having to conform to curses's presentation-based API. 19 | 20 | In order to colorize its output, this program needs to be run on a 21 | terminal capable of displaying 256 colors. Because there is no 22 | portable way of querying the color palette, the program assumes that 23 | the terminal's palette matches the standard palette of xterm-256color. 24 | 25 | (If xcd claims that your terminal does not have 256 colors, try 26 | setting the TERM environment variable to "xterm-256color". If the 27 | resulting output is garbled, then try using it with a different 28 | terminal.) 29 | 30 | Building and Installing 31 | 32 | There is no configure script. To build, simply run "make". The 33 | existing executable runs standalone. To install, run "make install". 34 | By default the makefile installs xcd under /usr/local, but you can 35 | override this by change the value of the PREFIX variable. 36 | -------------------------------------------------------------------------------- /xcd.1: -------------------------------------------------------------------------------- 1 | .TH XCD 1 "Mar 2019" "colorized hexdump" 2 | .LO 1 3 | .SH NAME 4 | xcd \- produce a hex dump in color 5 | .SH SYNOPSIS 6 | .B xcd 7 | [OPTIONS] [FILE ...] 8 | .SH DESCRIPTION 9 | .B xcd 10 | generates a colorized hex dump of its input file(s). Its output is 11 | similar to 12 | .BR xxd (1), 13 | except that bytes are assigned colors, making it easier to see 14 | patterns of repeated values. 15 | .SH OPTIONS 16 | If no input file is given, or is specified as "\fB-\fR", then 17 | .B xcd 18 | reads from standard input. 19 | .TP 20 | \fB\-a\fR, \fB\-\-autoskip\fR 21 | Enable autoskip: sections of the input consisting entirely of zero 22 | bytes will be omitted from the output, replaced by a line containing a 23 | lone asterisk. 24 | .TP 25 | \fB\-A\fR, \fB\-\-ascii\fR 26 | Force ASCII-only output. By default, 27 | .B xcd 28 | will display Unicode characters in the text column. When this option 29 | is specified, all non-printing non-ASCII byte values are represented 30 | by a dot. 31 | .TP 32 | \fB\-c\fR, \fB\-\-count\fR=\fIN\fR 33 | Set the number of bytes per line of output to 34 | .IR N . 35 | The default is 16. 36 | .TP 37 | \fB\-g\fR, \fB\-\-group\fR=\fIN\fR 38 | Set the number of bytes to output as a group to 39 | .IR N . 40 | Groups are separated from each other by whitespace. Specify a group 41 | size of zero to disable grouping. The default is 2. 42 | .TP 43 | \fB\-l\fR, \fB\-\-limit\fR=\fIN\fR 44 | Stop reading input after 45 | .I N 46 | bytes. 47 | .TP 48 | \fB\-N\fR, \fB--no-color\fR 49 | Suppress all color output. 50 | .TP 51 | \fB\-R\fR, \fB--raw\fR 52 | Suppress the hexadecimal output. Instead, the input is colorized and 53 | dumped directly to the output. 54 | .TP 55 | \fB\-s\fR, \fB\-\-start\fR=\fIN\fR 56 | Start after 57 | .I N 58 | bytes of input, skipping over the previous bytes. 59 | .TP 60 | .B \--help 61 | Display help and exit. 62 | .TP 63 | .B \--version 64 | Display version information and exit. 65 | .SH NOTE ON COLORS 66 | .P 67 | In order to colorize the output, 68 | .B xcd 69 | requires a terminal capable of displaying 256 colors. 70 | .B xcd 71 | will assign colors attempting to maximize contrast, under the 72 | assumption that the terminal's color palette matches that of the 73 | standard "xterm-256color" terminal. 74 | .SH COPYRIGHT 75 | Copyright \(co 2018 Brian Raiter 76 | .IR . 77 | .P 78 | .BR xcd \'s 79 | home page is at 80 | <\fIhttp://www.muppetlabs.com/~breadbox/software/xcd.html\fR>. 81 | .P 82 | This program is free software: you may deal in this software without 83 | restriction, provided that the copyright and permission notices are 84 | included. See the program's license for details. 85 | .SH SEE ALSO 86 | .IR xxd (1). 87 | 88 | -------------------------------------------------------------------------------- /xcd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * xcd.c: A colorizing hexdump utility. 3 | * 4 | * Copyright (C) 2018 Brian Raiter 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation 8 | * files (the "Software"), to deal in the Software without 9 | * restriction, including without limitation the rights to use, copy, 10 | * modify, merge, publish, distribute, sublicense, and/or sell copies 11 | * of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | typedef unsigned char byte; 38 | 39 | /* Online help. 40 | */ 41 | static char const *yowzitch = 42 | "Usage: xcd [OPTIONS] [FILENAME]...\n" 43 | "Output the contents of FILENAME as a hex dump (displaying octets as\n" 44 | "hexadecimal values, and characters when appropriate), using contrasting\n" 45 | "colors to help bring out patterns. With multiple arguments, the files'\n" 46 | "contents are concatenated together. With no arguments, or when FILENAME\n" 47 | "is -, read from standard input.\n" 48 | "\n" 49 | " -c, --count=N Display N bytes per line [default=16]\n" 50 | " -g, --group=N Display N bytes per groups [default=2]\n" 51 | " -s, --start=N Start N bytes after start of input\n" 52 | " -l, --limit=N Stop after N bytes of input\n" 53 | " -a, --autoskip Omit lines of zero bytes with a single \"*\"\n" 54 | " -N, --no-color Suppress color output\n" 55 | " -R, --raw Dump colorized bytes without the hex display\n" 56 | " -A, --ascii Don't use Unicode characters in text column\n" 57 | " --help Display this help and exit\n" 58 | " --version Display version information and exit\n"; 59 | 60 | /* Version information. 61 | */ 62 | static char const *vourzhon = 63 | "xcd: v1.2\n" 64 | "Copyright (C) 2018 by Brian Raiter \n" 65 | "This is free software; you are free to change and redistribute it.\n" 66 | "There is NO WARRANTY, to the extent permitted by law.\n"; 67 | 68 | /* The color index palette. This is the list of color indexes for the 69 | * 256-color xterm, sorted to maximize visual contrast. Colors should 70 | * be assigned preferentially from the start of this list. 71 | * 72 | * To create this index, high-contrast color sets created by design 73 | * researchers were consulted. References to their papers can be found 74 | * at . The Kenneth 75 | * Kelly palette was the main source, with some modifications (such as 76 | * reserving black and other colors often used for normal text). The 77 | * resulting list of colors was then matched with entries the xterm 78 | * palette, as close as possible. Finally, the remaining colors in the 79 | * xterm palette were added, sorted by their color-space distance from 80 | * each other, but skipping over ones that were too dark to read on a 81 | * black background. Finally, the last two colors are repeated to fill 82 | * out the palette to 256 entries. 83 | */ 84 | static byte const colorset[] = { 85 | 8, /* #808080 */ 86 | 11, /* #FFFF00 */ 87 | 53, /* #5F005F */ 88 | 202, /* #FF5F00 */ 89 | 87, /* #5FFFFF */ 90 | 9, /* #FF0000 */ 91 | 41, /* #00D75F */ 92 | 217, /* #FFAFAF */ 93 | 32, /* #0087D7 */ 94 | 222, /* #FFD787 */ 95 | 57, /* #5F00FF */ 96 | 214, /* #FFAF00 */ 97 | 126, /* #AF0087 */ 98 | 191, /* #D7FF5F */ 99 | 88, /* #870000 */ 100 | 148, /* #AFD700 */ 101 | 94, /* #875F00 */ 102 | 219, /* #FFAFFF */ 103 | 22, /* #005F00 */ 104 | 228, /* #FFFF87 */ 105 | 121, /* #87FFAF */ 106 | 4, /* #000080 */ 107 | 3, /* #808000 */ 108 | 23, /* #005F5F */ 109 | 30, /* #008787 */ 110 | 179, /* #D7AF5F */ 111 | 14, /* #00FFFF */ 112 | 13, /* #FF00FF */ 113 | 195, /* #D7FFFF */ 114 | 12, /* #0000FF */ 115 | 225, /* #FFD7FF */ 116 | 123, /* #87FFFF */ 117 | 230, /* #FFFFD7 */ 118 | 27, /* #005FFF */ 119 | 159, /* #AFFFFF */ 120 | 10, /* #00FF00 */ 121 | 207, /* #FF5FFF */ 122 | 165, /* #D700FF */ 123 | 50, /* #00FFD7 */ 124 | 227, /* #FFFF5F */ 125 | 235, /* #262626 */ 126 | 200, /* #FF00D7 */ 127 | 45, /* #00D7FF */ 128 | 82, /* #5FFF00 */ 129 | 213, /* #FF87FF */ 130 | 197, /* #FF005F */ 131 | 47, /* #00FF5F */ 132 | 255, /* #EEEEEE */ 133 | 20, /* #0000D7 */ 134 | 190, /* #D7FF00 */ 135 | 93, /* #8700FF */ 136 | 229, /* #FFFFAF */ 137 | 236, /* #303030 */ 138 | 33, /* #0087FF */ 139 | 220, /* #FFD700 */ 140 | 129, /* #AF00FF */ 141 | 49, /* #00FFAF */ 142 | 160, /* #D70000 */ 143 | 39, /* #00AFFF */ 144 | 198, /* #FF0087 */ 145 | 118, /* #87FF00 */ 146 | 199, /* #FF00AF */ 147 | 48, /* #00FF87 */ 148 | 208, /* #FF8700 */ 149 | 63, /* #5F5FFF */ 150 | 154, /* #AFFF00 */ 151 | 81, /* #5FD7FF */ 152 | 52, /* #5F0000 */ 153 | 171, /* #D75FFF */ 154 | 194, /* #D7FFD7 */ 155 | 17, /* #00005F */ 156 | 224, /* #FFD7D7 */ 157 | 40, /* #00D700 */ 158 | 206, /* #FF5FD7 */ 159 | 86, /* #5FFFD7 */ 160 | 237, /* #3A3A3A */ 161 | 189, /* #D7D7FF */ 162 | 203, /* #FF5F5F */ 163 | 83, /* #5FFF5F */ 164 | 19, /* #0000AF */ 165 | 254, /* #E4E4E4 */ 166 | 1, /* #800000 */ 167 | 221, /* #FFD75F */ 168 | 177, /* #D787FF */ 169 | 2, /* #008000 */ 170 | 117, /* #87D7FF */ 171 | 18, /* #000087 */ 172 | 158, /* #AFFFD7 */ 173 | 212, /* #FF87D7 */ 174 | 124, /* #AF0000 */ 175 | 183, /* #D7AFFF */ 176 | 28, /* #008700 */ 177 | 122, /* #87FFD7 */ 178 | 204, /* #FF5F87 */ 179 | 34, /* #00AF00 */ 180 | 153, /* #AFD7FF */ 181 | 193, /* #D7FFAF */ 182 | 69, /* #5F87FF */ 183 | 205, /* #FF5FAF */ 184 | 84, /* #5FFF87 */ 185 | 238, /* #444444 */ 186 | 218, /* #FFAFD7 */ 187 | 192, /* #D7FF87 */ 188 | 99, /* #875FFF */ 189 | 119, /* #87FF5F */ 190 | 135, /* #AF5FFF */ 191 | 209, /* #FF875F */ 192 | 75, /* #5FAFFF */ 193 | 223, /* #FFD7AF */ 194 | 85, /* #5FFFAF */ 195 | 215, /* #FFAF5F */ 196 | 56, /* #5F00D7 */ 197 | 155, /* #AFFF5F */ 198 | 164, /* #D700D7 */ 199 | 58, /* #5F5F00 */ 200 | 44, /* #00D7D7 */ 201 | 161, /* #D7005F */ 202 | 184, /* #D7D700 */ 203 | 26, /* #005FD7 */ 204 | 76, /* #5FD700 */ 205 | 105, /* #8787FF */ 206 | 166, /* #D75F00 */ 207 | 120, /* #87FF87 */ 208 | 141, /* #AF87FF */ 209 | 210, /* #FF8787 */ 210 | 239, /* #4E4E4E */ 211 | 111, /* #87AFFF */ 212 | 156, /* #AFFF87 */ 213 | 211, /* #FF87AF */ 214 | 147, /* #AFAFFF */ 215 | 216, /* #FFAF87 */ 216 | 157, /* #AFFFAF */ 217 | 92, /* #8700D7 */ 218 | 42, /* #00D787 */ 219 | 162, /* #D70087 */ 220 | 38, /* #00AFD7 */ 221 | 112, /* #87D700 */ 222 | 163, /* #D700AF */ 223 | 43, /* #00D7AF */ 224 | 172, /* #D78700 */ 225 | 128, /* #AF00D7 */ 226 | 29, /* #00875F */ 227 | 253, /* #DADADA */ 228 | 54, /* #5F0087 */ 229 | 178, /* #D7AF00 */ 230 | 24, /* #005F87 */ 231 | 55, /* #5F00AF */ 232 | 64, /* #5F8700 */ 233 | 188, /* #D7D7D7 */ 234 | 89, /* #87005F */ 235 | 35, /* #00AF5F */ 236 | 25, /* #005FAF */ 237 | 130, /* #AF5F00 */ 238 | 80, /* #5FD7D7 */ 239 | 125, /* #AF005F */ 240 | 70, /* #5FAF00 */ 241 | 170, /* #D75FD7 */ 242 | 185, /* #D7D75F */ 243 | 240, /* #585858 */ 244 | 252, /* #D0D0D0 */ 245 | 62, /* #5F5FD7 */ 246 | 77, /* #5FD75F */ 247 | 5, /* #800080 */ 248 | 167, /* #D75F5F */ 249 | 152, /* #AFD7D7 */ 250 | 6, /* #008080 */ 251 | 182, /* #D7AFD7 */ 252 | 37, /* #00AFAF */ 253 | 187, /* #D7D7AF */ 254 | 91, /* #8700AF */ 255 | 142, /* #AFAF00 */ 256 | 116, /* #87D7D7 */ 257 | 136, /* #AF8700 */ 258 | 176, /* #D787D7 */ 259 | 31, /* #0087AF */ 260 | 186, /* #D7D787 */ 261 | 90, /* #870087 */ 262 | 106, /* #87AF00 */ 263 | 127, /* #AF00AF */ 264 | 36, /* #00AF87 */ 265 | 100, /* #878700 */ 266 | 251, /* #C6C6C6 */ 267 | 59, /* #5F5F5F */ 268 | 74, /* #5FAFD7 */ 269 | 134, /* #AF5FD7 */ 270 | 79, /* #5FD7AF */ 271 | 149, /* #AFD75F */ 272 | 169, /* #D75FAF */ 273 | 241, /* #626262 */ 274 | 68, /* #5F87D7 */ 275 | 113, /* #87D75F */ 276 | 168, /* #D75F87 */ 277 | 78, /* #5FD787 */ 278 | 98, /* #875FD7 */ 279 | 173, /* #D7875F */ 280 | 7, /* #C0C0C0 */ 281 | 242, /* #6C6C6C */ 282 | 146, /* #AFAFD7 */ 283 | 61, /* #5F5FAF */ 284 | 151, /* #AFD7AF */ 285 | 71, /* #5FAF5F */ 286 | 131, /* #AF5F5F */ 287 | 181, /* #D7AFAF */ 288 | 60, /* #5F5F87 */ 289 | 110, /* #87AFD7 */ 290 | 150, /* #AFD787 */ 291 | 175, /* #D787AF */ 292 | 65, /* #5F875F */ 293 | 115, /* #87D7AF */ 294 | 140, /* #AF87D7 */ 295 | 180, /* #D7AF87 */ 296 | 95, /* #875F5F */ 297 | 104, /* #8787D7 */ 298 | 114, /* #87D787 */ 299 | 174, /* #D78787 */ 300 | 250, /* #BCBCBC */ 301 | 243, /* #767676 */ 302 | 73, /* #5FAFAF */ 303 | 133, /* #AF5FAF */ 304 | 143, /* #AFAF5F */ 305 | 67, /* #5F87AF */ 306 | 107, /* #87AF5F */ 307 | 132, /* #AF5F87 */ 308 | 72, /* #5FAF87 */ 309 | 97, /* #875FAF */ 310 | 137, /* #AF875F */ 311 | 66, /* #5F8787 */ 312 | 96, /* #875F87 */ 313 | 101, /* #87875F */ 314 | 249, /* #B2B2B2 */ 315 | 145, /* #AFAFAF */ 316 | 248, /* #A8A8A8 */ 317 | 109, /* #87AFAF */ 318 | 139, /* #AF87AF */ 319 | 144, /* #AFAF87 */ 320 | 247, /* #9E9E9E */ 321 | 103, /* #8787AF */ 322 | 108, /* #87AF87 */ 323 | 138, /* #AF8787 */ 324 | 246, /* #949494 */ 325 | 245, /* #8A8A8A */ 326 | 102, /* #878787 */ 327 | 245, /* #8A8A8A */ 328 | 102, /* #878787 */ 329 | 245, /* #8A8A8A */ 330 | 102, /* #878787 */ 331 | 245, /* #8A8A8A */ 332 | 102, /* #878787 */ 333 | 245, /* #8A8A8A */ 334 | 102, /* #878787 */ 335 | 245, /* #8A8A8A */ 336 | 102, /* #878787 */ 337 | 245, /* #8A8A8A */ 338 | 102, /* #878787 */ 339 | 245, /* #8A8A8A */ 340 | 102, /* #878787 */ 341 | }; 342 | 343 | /* The program's input file state and user-controlled settings. 344 | */ 345 | typedef struct state { 346 | int startoffset; /* skip over this many chars of input at start */ 347 | int maxinputlen; /* stop after this many chars of input */ 348 | char **filenames; /* NULL-terminated list of input filenames */ 349 | FILE *currentfile; /* handle to the currently open input file */ 350 | } state; 351 | 352 | /* True if the program should skip repeated lines of zero bytes. 353 | */ 354 | static int autoskip = 0; 355 | 356 | /* Number of bytes to display per line of dump output. (The default 357 | * value is 16, which produces output that fits comfortably on an 358 | * 80-column display.) 359 | */ 360 | static int linesize = 16; 361 | 362 | /* Number of bytes to group together in output. 363 | */ 364 | static int groupsize = 2; 365 | 366 | /* The program's (eventual) exit code. 367 | */ 368 | static int exitcode = 0; 369 | 370 | /* True if the program should display hexadecimal values. 371 | */ 372 | static int hexoutput = 1; 373 | 374 | /* True if the program should add colors to the output. 375 | */ 376 | static int colorize = 1; 377 | 378 | /* True if the program should use extended Unicode glyphs to represent 379 | * non-graphical and non-ASCII characters. 380 | */ 381 | static int useunicode = 1; 382 | 383 | /* The width of the output containing the hex byte values. 384 | */ 385 | static int hexwidth; 386 | 387 | /* Terminal control sequence strings. setaf is used to change the 388 | * color of the following characters, and sgr0 is used to reset 389 | * characters attributes to the default state. 390 | */ 391 | static char const *setaf; 392 | static char const *sgr0; 393 | 394 | /* The palette of colors to use for each byte value. Initially each 395 | * entry is set to zero, meaning unassigned. 396 | */ 397 | static byte palette[256]; 398 | 399 | /* The index of the first color in colorset that has yet to be 400 | * assigned to a byte value. 401 | */ 402 | static int nextcolorfromset = 0; 403 | 404 | /* 405 | * Basic functions. 406 | */ 407 | 408 | /* Display a formatted message on standard error and exit. 409 | */ 410 | void die(char const *fmt, ...) 411 | { 412 | va_list args; 413 | 414 | va_start(args, fmt); 415 | vfprintf(stderr, fmt, args); 416 | fputc('\n', stderr); 417 | va_end(args); 418 | exit(EXIT_FAILURE); 419 | } 420 | 421 | /* Display an error message for the current file and set the exit code. 422 | */ 423 | static void fail(state *s) 424 | { 425 | perror(s->filenames && *s->filenames ? *s->filenames : "xcd"); 426 | exitcode = EXIT_FAILURE; 427 | } 428 | 429 | /* Read a small non-negative integer from a string. Exit with a 430 | * simple error message if the string is not a valid number, or if 431 | * the number hits a given upper limit. 432 | */ 433 | static int getn(char const *str, char const *name, int maxval) 434 | { 435 | char *p; 436 | long n; 437 | 438 | if (!str || !*str) 439 | die("missing argument for %s", name); 440 | n = strtol(str, &p, 0); 441 | if (*p != '\0' || p == str || (n == LONG_MAX && errno == ERANGE) 442 | || n < 0 || n > INT_MAX) 443 | die("invalid argument '%s' for %s", str, name); 444 | if (maxval && n > maxval) 445 | die("value for %s too large (maximum %d)", name, maxval); 446 | return n; 447 | } 448 | 449 | /* 450 | * File I/O. 451 | */ 452 | 453 | /* Prepare the current input file, if necessary. (Does nothing if the 454 | * current input file is already open and is not at the end.) Any 455 | * errors that occur when opening a file are reported to stderr before 456 | * moving on to the next input file. The return value is zero if no 457 | * more input files are available. 458 | */ 459 | static int inputinit(state *s) 460 | { 461 | if (!s->currentfile) { 462 | if (!*s->filenames) 463 | return 0; 464 | if (!strcmp(*s->filenames, "-")) { 465 | s->currentfile = stdin; 466 | *s->filenames = "stdin"; 467 | } else { 468 | s->currentfile = fopen(*s->filenames, "r"); 469 | } 470 | if (!s->currentfile) { 471 | fail(s); 472 | ++s->filenames; 473 | return inputinit(s); 474 | } 475 | } 476 | return 1; 477 | } 478 | 479 | /* Close the current input file, reporting errors if any to stderr. 480 | */ 481 | static void inputupdate(state *s) 482 | { 483 | if (ferror(s->currentfile)) { 484 | fail(s); 485 | fclose(s->currentfile); 486 | } else { 487 | if (s->currentfile != stdin) 488 | if (fclose(s->currentfile)) 489 | fail(s); 490 | } 491 | s->currentfile = 0; 492 | ++s->filenames; 493 | } 494 | 495 | /* Get the next byte from the current file. If there are no more byte 496 | * in the current file, open the next file in the list of filenames 497 | * and read from that. If there are no more filenames, return EOF. 498 | */ 499 | static int nextbyte(state *s) 500 | { 501 | int ch; 502 | 503 | for (;;) { 504 | if (!inputinit(s)) 505 | return EOF; 506 | ch = fgetc(s->currentfile); 507 | if (ch != EOF) 508 | return ch; 509 | inputupdate(s); 510 | } 511 | } 512 | 513 | /* Return a string representing the given character. Latin-1 and 514 | * Unicode control pictures are used to represent non-ASCII and 515 | * control characters. 516 | */ 517 | static char const *getbyterepresentation(byte ch) 518 | { 519 | static char buf[4]; 520 | 521 | if (useunicode) { 522 | if (ch < 32) { 523 | buf[0] = 0xE2; 524 | buf[1] = 0x90; 525 | buf[2] = 0x80 | ch; 526 | buf[3] = '\0'; 527 | } else if (ch == 32) { 528 | buf[0] = 0xE2; 529 | buf[1] = 0x90; 530 | buf[2] = 0xA0; 531 | buf[3] = '\0'; 532 | } else if (ch < 127) { 533 | buf[0] = ch; 534 | buf[1] = '\0'; 535 | } else if (ch == 127) { 536 | buf[0] = 0xE2; 537 | buf[1] = 0x90; 538 | buf[2] = 0xA1; 539 | buf[3] = '\0'; 540 | } else if (ch < 160) { 541 | buf[0] = 0xE2; 542 | buf[1] = 0x90; 543 | buf[2] = 0xA6; 544 | buf[3] = '\0'; 545 | } else if (ch == 160) { 546 | buf[0] = 0xE2; 547 | buf[1] = 0x90; 548 | buf[2] = 0xA3; 549 | buf[3] = '\0'; 550 | } else { 551 | buf[0] = 0xC0 | (ch >> 6); 552 | buf[1] = 0x80 | (ch & 0x3F); 553 | buf[2] = '\0'; 554 | } 555 | } else { 556 | buf[0] = isprint(ch) ? ch : '.'; 557 | buf[1] = '\0'; 558 | } 559 | return buf; 560 | } 561 | 562 | /* 563 | * Terminal handling functions. 564 | */ 565 | 566 | /* Look up the terminal in the terminfo database and initialize it to 567 | * do color output. 568 | */ 569 | static void initoutput(void) 570 | { 571 | char *termname, *seq; 572 | int err; 573 | 574 | if (!colorize) 575 | return; 576 | termname = getenv("TERM"); 577 | if (setupterm(termname, 1, &err) != 0) { 578 | if (err < 0) 579 | fprintf(stderr, "error: cannot find terminfo database.\n"); 580 | else if (err == 0 || !termname || !*termname) 581 | fprintf(stderr, "error: cannot identify terminal type.\n"); 582 | else 583 | fprintf(stderr, "error: terminal \"%s\" lacks color; use xxd.\n", 584 | termname); 585 | exit(EXIT_FAILURE); 586 | } 587 | sgr0 = tigetstr("sgr0"); 588 | setaf = tigetstr("setaf"); 589 | if (!sgr0 || !setaf) { 590 | fprintf(stderr, "error: terminal \"%s\" lacks color; use --no-color" 591 | " (or xxd(1)).\n", 592 | termname); 593 | exit(EXIT_FAILURE); 594 | } 595 | if (tigetnum("colors") < 256) { 596 | fprintf(stderr, "error: colorizing requires 256 colors," 597 | " but only %d are available.\n", 598 | tigetnum("colors")); 599 | exit(EXIT_FAILURE); 600 | } 601 | seq = tigetstr("is1"); 602 | if (seq) 603 | fputs(seq, stdout); 604 | seq = tigetstr("is2"); 605 | if (seq) 606 | fputs(seq, stdout); 607 | seq = tigetstr("is3"); 608 | if (seq) 609 | fputs(seq, stdout); 610 | 611 | palette[0] = colorset[0]; 612 | nextcolorfromset = 1; 613 | } 614 | 615 | /* 616 | * Three different dump format functions. 617 | */ 618 | 619 | /* Output colorized bytes directly. 620 | */ 621 | static void renderbytescolored(byte const *buf, int count) 622 | { 623 | int ch, i; 624 | 625 | for (i = 0 ; i < count ; ++i) { 626 | ch = buf[i]; 627 | if (!isgraph(ch)) { 628 | putchar(ch); 629 | continue; 630 | } 631 | if (!palette[ch]) 632 | palette[ch] = colorset[nextcolorfromset++]; 633 | printf("%s%s", tiparm(setaf, palette[ch]), getbyterepresentation(ch)); 634 | } 635 | printf("%s", sgr0); 636 | } 637 | 638 | /* Output one line of data as a hexdump, containing up to linesize 639 | * bytes. pos supplies the current file position. 640 | */ 641 | static void renderlineuncolored(byte const *buf, int count, int pos) 642 | { 643 | int i, x; 644 | 645 | printf("%08X:", pos); 646 | x = hexwidth - 2 * count; 647 | for (i = 0 ; i < count ; ++i) { 648 | if (i % groupsize == 0) { 649 | putchar(' '); 650 | --x; 651 | } 652 | printf("%02X", buf[i]); 653 | } 654 | printf("%*s ", x, ""); 655 | for (i = 0 ; i < count ; ++i) 656 | fputs(getbyterepresentation(buf[i]), stdout); 657 | putchar('\n'); 658 | } 659 | 660 | /* Output one line of data as a hexdump, containing up to linesize 661 | * bytes. pos supplies the current file position. 662 | */ 663 | static void renderlinecolored(byte const *buf, int count, int pos) 664 | { 665 | int ch, i, x; 666 | 667 | printf("%s%08X:", sgr0, pos); 668 | x = hexwidth - 2 * count; 669 | for (i = 0 ; i < count ; ++i) { 670 | if (i % groupsize == 0) { 671 | putchar(' '); 672 | --x; 673 | } 674 | ch = buf[i]; 675 | if (!palette[ch]) 676 | palette[ch] = colorset[nextcolorfromset++]; 677 | printf("%s%02X", tiparm(setaf, palette[ch]), ch); 678 | } 679 | printf("%s%*s ", sgr0, x, ""); 680 | for (i = 0 ; i < count ; ++i) { 681 | ch = buf[i]; 682 | if (!palette[ch]) 683 | palette[ch] = colorset[nextcolorfromset++]; 684 | printf("%s%s", tiparm(setaf, palette[ch]), getbyterepresentation(ch)); 685 | } 686 | printf("%s\n", sgr0); 687 | } 688 | 689 | /* 690 | * The line output functions. 691 | */ 692 | 693 | /* Display a single line of a hexdump appropriate in the requested 694 | * format. 695 | */ 696 | static void dumpline(byte const *buf, int count, int pos) 697 | { 698 | if (count) { 699 | if (!hexoutput) 700 | renderbytescolored(buf, count); 701 | else if (colorize) 702 | renderlinecolored(buf, count, pos); 703 | else 704 | renderlineuncolored(buf, count, pos); 705 | } 706 | } 707 | 708 | /* Display some hexdump lines consisting entirely of zero bytes. If 709 | * count is three or more, all but the first are elided. 710 | */ 711 | static void dumpzerolines(int count, int pos) 712 | { 713 | static byte zeroline[256]; 714 | int i; 715 | 716 | if (count > 2) { 717 | dumpline(zeroline, linesize, pos); 718 | printf("*\n"); 719 | } else { 720 | for (i = 0 ; i < count ; ++i) 721 | dumpline(zeroline, linesize, pos + i * linesize); 722 | } 723 | } 724 | 725 | /* Display hexdump lines from the given filenames until there's no 726 | * more input. 727 | */ 728 | static void dumpfiles(state *s, int pos) 729 | { 730 | byte line[256]; 731 | int ch = 0, n; 732 | 733 | while (ch != EOF && s->maxinputlen > 0) { 734 | for (n = 0 ; n < linesize && s->maxinputlen > 0 ; ++n) { 735 | ch = nextbyte(s); 736 | if (ch == EOF) 737 | break; 738 | line[n] = ch; 739 | --s->maxinputlen; 740 | } 741 | if (n == 0) 742 | break; 743 | dumpline(line, n, pos); 744 | pos += n; 745 | } 746 | } 747 | 748 | /* Display hexdump lines from the given filenames until there's no 749 | * more input. Lines are checked for the presence of nonzero bytes, 750 | * and lines that are all zeroes are deferred until a nonzero byte is 751 | * found. At that point, if three or more lines of zeroes were found, 752 | * all but the first are omitted. If the end of input is reached while 753 | * searching for a nonzero byte, then all but the first and last line 754 | * are omitted. 755 | */ 756 | static void dumpfileswithautoskip(state *s, int pos) 757 | { 758 | byte line[256]; 759 | int linesheld = 0, lastheldsize = 0, holdpos = 0; 760 | int nonzero, ch = 0, n; 761 | 762 | while (ch != EOF && s->maxinputlen > 0) { 763 | nonzero = 0; 764 | for (n = 0 ; n < linesize && s->maxinputlen > 0 ; ++n) { 765 | ch = nextbyte(s); 766 | if (ch == EOF) 767 | break; 768 | line[n] = ch; 769 | nonzero |= ch; 770 | --s->maxinputlen; 771 | } 772 | if (n == 0) 773 | break; 774 | if (nonzero) { 775 | if (linesheld) { 776 | dumpzerolines(linesheld, holdpos); 777 | linesheld = 0; 778 | } 779 | dumpline(line, n, pos); 780 | } else { 781 | if (linesheld == 0) 782 | holdpos = pos; 783 | ++linesheld; 784 | lastheldsize = n; 785 | } 786 | pos += n; 787 | } 788 | 789 | if (linesheld) { 790 | dumpzerolines(linesheld - 1, holdpos); 791 | dumpline(line, lastheldsize, pos - lastheldsize); 792 | } 793 | } 794 | 795 | /* Move the input stream to the starting point and then 796 | */ 797 | static void dump(state *s) 798 | { 799 | int pos; 800 | 801 | for (pos = 0 ; pos < s->startoffset ; ++pos) 802 | if (nextbyte(s) == EOF) 803 | return; 804 | 805 | if (autoskip) 806 | dumpfileswithautoskip(s, pos); 807 | else 808 | dumpfiles(s, pos); 809 | } 810 | 811 | /* 812 | * The top-level functions. 813 | */ 814 | 815 | /* Parse the command-line arguments and initialize the given state 816 | * appropriately, as well as default values. Invalid arguments (or 817 | * invalid combinations of arguments) will cause the program to exit. 818 | */ 819 | static void parsecommandline(int argc, char *argv[], state *s) 820 | { 821 | static char *defaultargs[] = { "-", NULL }; 822 | static char const *optstring = "Aac:g:l:NRs:"; 823 | static struct option options[] = { 824 | { "count", required_argument, NULL, 'c' }, 825 | { "group", required_argument, NULL, 'g' }, 826 | { "limit", required_argument, NULL, 'l' }, 827 | { "start", required_argument, NULL, 's' }, 828 | { "autoskip", no_argument, NULL, 'a' }, 829 | { "no-color", no_argument, NULL, 'N' }, 830 | { "raw", no_argument, NULL, 'R' }, 831 | { "ascii", no_argument, NULL, 'A' }, 832 | { "help", no_argument, NULL, 'h' }, 833 | { "version", no_argument, NULL, 'v' }, 834 | { 0, 0, 0, 0 } 835 | }; 836 | 837 | int ch; 838 | 839 | s->startoffset = 0; 840 | s->maxinputlen = INT_MAX; 841 | s->filenames = defaultargs; 842 | s->currentfile = NULL; 843 | 844 | while ((ch = getopt_long(argc, argv, optstring, options, NULL)) != EOF) { 845 | switch (ch) { 846 | case 'l': s->maxinputlen = getn(optarg, "limit", 0); break; 847 | case 's': s->startoffset = getn(optarg, "start", 0); break; 848 | case 'c': linesize = getn(optarg, "count", 255); break; 849 | case 'g': groupsize = getn(optarg, "group", 0); break; 850 | case 'a': autoskip = 1; break; 851 | case 'N': colorize = 0; break; 852 | case 'R': hexoutput = 0; break; 853 | case 'A': useunicode = 0; break; 854 | case 'h': fputs(yowzitch, stdout); exit(0); 855 | case 'v': fputs(vourzhon, stdout); exit(0); 856 | default: die("Try --help for more information."); 857 | } 858 | } 859 | if (!hexoutput) { 860 | autoskip = 0; 861 | if (!colorize) 862 | die("cannot use both --raw and --no-color."); 863 | } 864 | 865 | if (linesize == 0) 866 | linesize = 16; 867 | if (groupsize == 0) 868 | groupsize = linesize; 869 | hexwidth = 2 * linesize + (linesize + groupsize - 1) / groupsize; 870 | 871 | if (optind < argc) 872 | s->filenames = argv + optind; 873 | } 874 | 875 | /* Main itself. 876 | */ 877 | int main(int argc, char *argv[]) 878 | { 879 | state s; 880 | 881 | parsecommandline(argc, argv, &s); 882 | initoutput(); 883 | dump(&s); 884 | return exitcode; 885 | } 886 | --------------------------------------------------------------------------------