diff options
Diffstat (limited to 'src/od.c')
-rw-r--r-- | src/od.c | 1697 |
1 files changed, 1697 insertions, 0 deletions
diff --git a/src/od.c b/src/od.c new file mode 100644 index 000000000..f13c6b7bc --- /dev/null +++ b/src/od.c @@ -0,0 +1,1697 @@ +/* od -- dump in octal (and other formats) the contents of files + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Written by Jim Meyering. */ + +/* AIX requires this to be the first thing in the file. */ +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not __GNUC__ */ +#if HAVE_ALLOCA_H +#include <alloca.h> +#else /* not HAVE_ALLOCA_H */ +#ifdef _AIX + #pragma alloca +#else /* not _AIX */ +char *alloca (); +#endif /* not _AIX */ +#endif /* not HAVE_ALLOCA_H */ +#endif /* not __GNUC__ */ + +#include <stdio.h> +#include <ctype.h> +#include <assert.h> +#include <getopt.h> +#include <sys/types.h> +#include "system.h" + +#if defined(__GNUC__) || defined(STDC_HEADERS) +#include <float.h> +#endif + +#ifdef __GNUC__ +typedef long double LONG_DOUBLE; +#else +typedef double LONG_DOUBLE; +#endif + +#if HAVE_LIMITS_H +#include <limits.h> +#endif +#ifndef SCHAR_MAX +#define SCHAR_MAX 127 +#endif +#ifndef SHRT_MAX +#define SHRT_MAX 32767 +#endif +#ifndef ULONG_MAX +#define ULONG_MAX ((unsigned long) ~(unsigned long) 0) +#endif + +#define STREQ(a,b) (strcmp((a), (b)) == 0) + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +/* The default number of input bytes per output line. */ +#define DEFAULT_BYTES_PER_BLOCK 16 + +/* The number of decimal digits of precision in a float. */ +#ifndef FLT_DIG +#define FLT_DIG 7 +#endif + +/* The number of decimal digits of precision in a double. */ +#ifndef DBL_DIG +#define DBL_DIG 15 +#endif + +/* The number of decimal digits of precision in a long double. */ +#ifndef LDBL_DIG +#define LDBL_DIG DBL_DIG +#endif + +char *xmalloc (); +char *xrealloc (); +void error (); + +enum size_spec +{ + NO_SIZE, + CHAR, + SHORT, + INT, + LONG, + FP_SINGLE, + FP_DOUBLE, + FP_LONG_DOUBLE +}; + +enum output_format +{ + SIGNED_DECIMAL, + UNSIGNED_DECIMAL, + OCTAL, + HEXADECIMAL, + FLOATING_POINT, + NAMED_CHARACTER, + CHARACTER +}; + +enum strtoul_error +{ + UINT_OK, UINT_INVALID, UINT_INVALID_SUFFIX_CHAR, UINT_OVERFLOW +}; +typedef enum strtoul_error strtoul_error; + +/* Each output format specification (from POSIX `-t spec' or from + old-style options) is represented by one of these structures. */ +struct tspec +{ + enum output_format fmt; + enum size_spec size; + void (*print_function) (); + char *fmt_string; +}; + +/* Convert the number of 8-bit bytes of a binary representation to + the number of characters (digits + sign if the type is signed) + required to represent the same quantity in the specified base/type. + For example, a 32-bit (4-byte) quantity may require a field width + as wide as the following for these types: + 11 unsigned octal + 11 signed decimal + 10 unsigned decimal + 8 unsigned hexadecimal */ + +static const unsigned int bytes_to_oct_digits[] = +{0, 3, 6, 8, 11, 14, 16, 19, 22, 25, 27, 30, 32, 35, 38, 41, 43}; + +static const unsigned int bytes_to_signed_dec_digits[] = +{1, 4, 6, 8, 11, 13, 16, 18, 20, 23, 25, 28, 30, 33, 35, 37, 40}; + +static const unsigned int bytes_to_unsigned_dec_digits[] = +{0, 3, 5, 8, 10, 13, 15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39}; + +static const unsigned int bytes_to_hex_digits[] = +{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; + +/* Convert enum size_spec to the size of the named type. */ +static const int width_bytes[] = +{ + -1, + sizeof (char), + sizeof (short int), + sizeof (int), + sizeof (long int), + sizeof (float), + sizeof (double), + sizeof (LONG_DOUBLE) +}; + +/* Names for some non-printing characters. */ +static const char *const charname[33] = +{ + "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", + "bs", "ht", "nl", "vt", "ff", "cr", "so", "si", + "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", + "can", "em", "sub", "esc", "fs", "gs", "rs", "us", + "sp" +}; + +/* A printf control string for printing a file offset. */ +static const char *output_address_fmt_string; + +/* FIXME: make this the number of octal digits in an unsigned long. */ +#define MAX_ADDRESS_LENGTH 13 +static char address_fmt_buffer[MAX_ADDRESS_LENGTH + 1]; +static char address_pad[MAX_ADDRESS_LENGTH + 1]; + +static unsigned long int string_min; +static unsigned long int flag_dump_strings; + +/* The number of input bytes to skip before formatting and writing. */ +static unsigned long int n_bytes_to_skip = 0; + +/* When non-zero, MAX_BYTES_TO_FORMAT is the maximum number of bytes + to be read and formatted. Otherwise all input is formatted. */ +static int limit_bytes_to_format = 0; + +/* The maximum number of bytes that will be formatted. This + value is used only when LIMIT_BYTES_TO_FORMAT is non-zero. */ +static unsigned long int max_bytes_to_format; + +/* When non-zero and two or more consecutive blocks are equal, format + only the first block and output an asterisk alone on the following + line to indicate that identical blocks have been elided. */ +static int abbreviate_duplicate_blocks = 1; + +/* An array of specs describing how to format each input block. */ +static struct tspec *spec; + +/* The number of format specs. */ +static unsigned int n_specs; + +/* The allocated length of SPEC. */ +static unsigned int n_specs_allocated; + +/* The number of input bytes formatted per output line. It must be + a multiple of the least common multiple of the sizes associated with + the specified output types. It should be as large as possible, but + no larger than 16 -- unless specified with the -w option. */ +static unsigned int bytes_per_block; + +/* Human-readable representation of *file_list (for error messages). + It differs from *file_list only when *file_list is "-". */ +static char const *input_filename; + +/* A NULL-terminated list of the file-arguments from the command line. + If no file-arguments were specified, this variable is initialized + to { "-", NULL }. */ +static char const *const *file_list; + +/* The input stream associated with the current file. */ +static FILE *in_stream; + +#define LONGEST_INTEGRAL_TYPE long int + +#define MAX_INTEGRAL_TYPE_SIZE sizeof(LONGEST_INTEGRAL_TYPE) +static enum size_spec integral_type_size[MAX_INTEGRAL_TYPE_SIZE + 1]; + +#define MAX_FP_TYPE_SIZE sizeof(LONG_DOUBLE) +static enum size_spec fp_type_size[MAX_FP_TYPE_SIZE + 1]; + +static struct option long_options[] = +{ + /* POSIX options. */ + {"skip-bytes", 1, NULL, 'j'}, + {"address-radix", 1, NULL, 'A'}, + {"read-bytes", 1, NULL, 'N'}, + {"format", 1, NULL, 't'}, + {"output-duplicates", 0, NULL, 'v'}, + + /* non-POSIX options. */ + {"strings", 2, NULL, 's'}, + {"width", 2, NULL, 'w'}, + {NULL, 0, NULL, 0} +}; + +/* The name this program was run with. */ +char *program_name; + +static void +usage () +{ + fprintf (stderr, "\ +Usage: %s [-abcdfhiloxv] [-s[bytes]] [-w[bytes]] [-A radix] [-j bytes]\n\ + [-N bytes] [-t type] [--skip-bytes=bytes] [--address-radix=radix]\n\ + [--read-bytes=bytes] [--format=type] [--output-duplicates]\n\ + [--strings[=bytes]] [--width[=bytes]] [file...]\n", + program_name); + exit (1); +} + +/* Compute the greatest common denominator of U and V + using Euclid's algorithm. */ + +static unsigned int +gcd (u, v) + unsigned int u; + unsigned int v; +{ + unsigned int t; + while (v != 0) + { + t = u % v; + u = v; + v = t; + } + return u; +} + +/* Compute the least common multiple of U and V. */ + +static unsigned int +lcm (u, v) + unsigned int u; + unsigned int v; +{ + unsigned int t = gcd (u, v); + if (t == 0) + return 0; + return u * v / t; +} + +static strtoul_error +my_strtoul (s, base, val, allow_bkm_suffix) + const char *s; + int base; + long unsigned int *val; + int allow_bkm_suffix; +{ + char *p; + unsigned long int tmp; + + assert (0 <= base && base <= 36); + + tmp = strtoul (s, &p, base); + if (errno != 0) + return UINT_OVERFLOW; + if (p == s) + return UINT_INVALID; + if (!allow_bkm_suffix) + { + if (*p == '\0') + { + *val = tmp; + return UINT_OK; + } + else + return UINT_INVALID_SUFFIX_CHAR; + } + + switch (*p) + { + case '\0': + break; + +#define BKM_SCALE(x,scale_factor) \ + do \ + { \ + if (x > (double) ULONG_MAX / scale_factor) \ + return UINT_OVERFLOW; \ + x *= scale_factor; \ + } \ + while (0) + + case 'b': + BKM_SCALE (tmp, 512); + break; + + case 'k': + BKM_SCALE (tmp, 1024); + break; + + case 'm': + BKM_SCALE (tmp, 1024 * 1024); + break; + + default: + return UINT_INVALID_SUFFIX_CHAR; + break; + } + + *val = tmp; + return UINT_OK; +} + +static void +uint_fatal_error (str, argument_type_string, err) + const char *str; + const char *argument_type_string; + strtoul_error err; +{ + switch (err) + { + case UINT_OK: + abort (); + + case UINT_INVALID: + error (2, 0, "invalid %s `%s'", argument_type_string, str); + break; + + case UINT_INVALID_SUFFIX_CHAR: + error (2, 0, "invalid character following %s `%s'", + argument_type_string, str); + break; + + case UINT_OVERFLOW: + error (2, 0, "%s `%s' larger than maximum unsigned long", + argument_type_string, str); + break; + } +} + +static void +print_s_char (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes; i > 0; i--) + { + int tmp = (unsigned) *(unsigned char *) block; + if (tmp > SCHAR_MAX) + tmp = (SCHAR_MAX - tmp); + assert (tmp <= SCHAR_MAX); + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (unsigned char); + } + if (err) + error (2, errno, "standard output"); +} + +static void +print_char (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes; i > 0; i--) + { + unsigned int tmp = *(unsigned char *) block; + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (unsigned char); + } + if (err) + error (2, errno, "standard output"); +} + +static void +print_s_short (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes / sizeof (unsigned short); i > 0; i--) + { + int tmp = (unsigned) *(unsigned short *) block; + if (tmp > SHRT_MAX) + tmp = (SHRT_MAX - tmp); + assert (tmp <= SHRT_MAX); + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (unsigned short); + } + if (err) + error (2, errno, "standard output"); +} + +static void +print_short (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes / sizeof (unsigned short); i > 0; i--) + { + unsigned int tmp = *(unsigned short *) block; + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (unsigned short); + } + if (err) + error (2, errno, "standard output"); +} + +static void +print_int (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes / sizeof (unsigned int); i > 0; i--) + { + unsigned int tmp = *(unsigned int *) block; + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (unsigned int); + } + if (err) + error (2, errno, "standard output"); +} + +static void +print_long (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes / sizeof (unsigned long); i > 0; i--) + { + unsigned long tmp = *(unsigned long *) block; + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (unsigned long); + } + if (err) + error (2, errno, "standard output"); +} + +static void +print_float (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes / sizeof (float); i > 0; i--) + { + float tmp = *(float *) block; + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (float); + } + if (err) + error (2, errno, "standard output"); +} + +static void +print_double (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes / sizeof (double); i > 0; i--) + { + double tmp = *(double *) block; + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (double); + } + if (err) + error (2, errno, "standard output"); +} + +#ifdef __GNUC__ +static void +print_long_double (n_bytes, block, fmt_string) + long unsigned int n_bytes; + const char *block; + const char *fmt_string; +{ + int i, err; + err = 0; + for (i = n_bytes / sizeof (LONG_DOUBLE); i > 0; i--) + { + LONG_DOUBLE tmp = *(LONG_DOUBLE *) block; + err |= (printf (fmt_string, tmp, (i == 1 ? '\n' : ' ')) == EOF); + block += sizeof (LONG_DOUBLE); + } + if (err) + error (2, errno, "standard output"); +} + +#endif + +static void +print_named_ascii (n_bytes, block, unused_fmt_string) + long unsigned int n_bytes; + const char *block; + const char *unused_fmt_string; +{ + int i; + for (i = n_bytes; i > 0; i--) + { + unsigned int c = *(unsigned char *) block; + unsigned int masked_c = (0x7f & c); + const char *s; + char buf[5]; + + if (masked_c == 127) + s = "del"; + else if (masked_c <= 040) + s = charname[masked_c]; + else + { + sprintf (buf, " %c", masked_c); + s = buf; + } + + if (printf ("%3s%c", s, (i == 1 ? '\n' : ' ')) == EOF) + error (2, errno, "standard output"); + block += sizeof (unsigned char); + } +} + +static void +print_ascii (n_bytes, block, unused_fmt_string) + long unsigned int n_bytes; + const char *block; + const char *unused_fmt_string; +{ + int i; + for (i = n_bytes; i > 0; i--) + { + unsigned int c = *(unsigned char *) block; + const char *s; + char buf[5]; + + switch (c) + { + case '\0': + s = " \\0"; + break; + + case '\007': + s = " \\a"; + break; + + case '\b': + s = " \\b"; + break; + + case '\f': + s = " \\f"; + break; + + case '\n': + s = " \\n"; + break; + + case '\r': + s = " \\r"; + break; + + case '\t': + s = " \\t"; + break; + + case '\v': + s = " \\v"; + break; + + default: + sprintf (buf, (isprint (c) ? " %c" : "%03o"), c); + s = (const char *) buf; + } + + if (printf ("%3s%c", s, (i == 1 ? '\n' : ' ')) == EOF) + error (2, errno, "standard output"); + block += sizeof (unsigned char); + } +} + +/* Convert a null-terminated (possibly zero-length) string S to an + unsigned long integer value. If S points to a non-digit set *P to S, + *VAL to 0, and return 0. Otherwise, accumulate the integer value of + the string of digits. If the string of digits represents a value + larger than ULONG_MAX, don't modify *VAL or *P and return non-zero. + Otherwise, advance *P to the first non-digit after S, set *VAL to + the result of the conversion and return zero. */ + +static int +simple_strtoul (s, p, val) + const char *s; + const char **p; + long unsigned int *val; +{ + unsigned long int sum; + + sum = 0; + while (isdigit (*s)) + { + unsigned int c = *s++ - '0'; + if (sum > (ULONG_MAX - c) / 10) + return 1; + sum = sum * 10 + c; + } + *p = s; + *val = sum; + return 0; +} + +/* If S points to a single valid POSIX-style od format string, put a + description of that format in *TSPEC, make *NEXT point at the character + following the just-decoded format (if *NEXT is non-NULL), and return + zero. If S is not valid, don't modify *NEXT or *TSPEC and return + non-zero. For example, if S were "d4afL" *NEXT would be set to "afL" + and *TSPEC would be + { + fmt = SIGNED_DECIMAL; + size = INT or LONG; (whichever integral_type_size[4] resolves to) + print_function = print_int; (assuming size == INT) + fmt_string = "%011d%c"; + } +*/ + +static int +decode_one_format (s, next, tspec) + const char *s; + const char **next; + struct tspec *tspec; +{ + enum size_spec size_spec; + unsigned long int size; + enum output_format fmt; + const char *pre_fmt_string; + char *fmt_string; + void (*print_function) (); + const char *p; + unsigned int c; + + assert (tspec != NULL); + + switch (*s) + { + case 'd': + case 'o': + case 'u': + case 'x': + c = *s; + ++s; + switch (*s) + { + case 'C': + ++s; + size = sizeof (char); + break; + + case 'S': + ++s; + size = sizeof (short); + break; + + case 'I': + ++s; + size = sizeof (int); + break; + + case 'L': + ++s; + size = sizeof (long int); + break; + + default: + if (simple_strtoul (s, &p, &size) != 0) + return 1; + if (p == s) + size = sizeof (int); + else + { + if (size > MAX_INTEGRAL_TYPE_SIZE + || integral_type_size[size] == NO_SIZE) + return 1; + s = p; + } + break; + } + +#define FMT_BYTES_ALLOCATED 9 + fmt_string = xmalloc (FMT_BYTES_ALLOCATED); + + size_spec = integral_type_size[size]; + + switch (c) + { + case 'd': + fmt = SIGNED_DECIMAL; + sprintf (fmt_string, "%%0%u%sd%%c", + bytes_to_signed_dec_digits[size], + (size_spec == LONG ? "l" : "")); + break; + + case 'o': + fmt = OCTAL; + sprintf (fmt_string, "%%0%u%so%%c", + bytes_to_oct_digits[size], + (size_spec == LONG ? "l" : "")); + break; + + case 'u': + fmt = UNSIGNED_DECIMAL; + sprintf (fmt_string, "%%0%u%su%%c", + bytes_to_unsigned_dec_digits[size], + (size_spec == LONG ? "l" : "")); + break; + + case 'x': + fmt = HEXADECIMAL; + sprintf (fmt_string, "%%0%u%sx%%c", + bytes_to_hex_digits[size], + (size_spec == LONG ? "l" : "")); + break; + + default: + abort (); + } + + assert (strlen (fmt_string) < FMT_BYTES_ALLOCATED); + + switch (size_spec) + { + case CHAR: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_char + : print_char); + break; + + case SHORT: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_short + : print_short);; + break; + + case INT: + print_function = print_int; + break; + + case LONG: + print_function = print_long; + break; + + default: + abort (); + } + break; + + case 'f': + fmt = FLOATING_POINT; + ++s; + switch (*s) + { + case 'F': + ++s; + size = sizeof (float); + break; + + case 'D': + ++s; + size = sizeof (double); + break; + + case 'L': + ++s; + size = sizeof (LONG_DOUBLE); + break; + + default: + if (simple_strtoul (s, &p, &size) != 0) + return 1; + if (p == s) + size = sizeof (double); + else + { + if (size > MAX_FP_TYPE_SIZE + || fp_type_size[size] == NO_SIZE) + return 1; + s = p; + } + break; + } + size_spec = fp_type_size[size]; + + switch (size_spec) + { + case FP_SINGLE: + print_function = print_float; + pre_fmt_string = "%%%d.%d#e%%c"; + fmt_string = xmalloc (strlen (pre_fmt_string)); + sprintf (fmt_string, pre_fmt_string, + FLT_DIG + 8, FLT_DIG); + break; + + case FP_DOUBLE: + print_function = print_double; + pre_fmt_string = "%%%d.%d#e%%c"; + fmt_string = xmalloc (strlen (pre_fmt_string)); + sprintf (fmt_string, pre_fmt_string, + DBL_DIG + 8, DBL_DIG); + break; + +#ifdef __GNUC__ + case FP_LONG_DOUBLE: + print_function = print_long_double; + pre_fmt_string = "%%%d.%d#le%%c"; + fmt_string = xmalloc (strlen (pre_fmt_string)); + sprintf (fmt_string, pre_fmt_string, + LDBL_DIG + 8, LDBL_DIG); + break; +#endif + + default: + abort (); + } + break; + + case 'a': + ++s; + fmt = NAMED_CHARACTER; + size_spec = CHAR; + fmt_string = NULL; + print_function = print_named_ascii; + break; + + case 'c': + ++s; + fmt = CHARACTER; + size_spec = CHAR; + fmt_string = NULL; + print_function = print_ascii; + break; + + default: + return 1; + } + + tspec->size = size_spec; + tspec->fmt = fmt; + tspec->print_function = print_function; + tspec->fmt_string = fmt_string; + + if (next != NULL) + *next = s; + + return 0; +} + +/* Decode the POSIX-style od format string S. Append the decoded + representation to the global array SPEC, reallocating SPEC if + necessary. Return zero if S is valid, non-zero otherwise. */ + +static int +decode_format_string (s) + const char *s; +{ + assert (s != NULL); + + while (*s != '\0') + { + struct tspec tspec; + const char *next; + + if (decode_one_format (s, &next, &tspec)) + return 1; + + assert (s != next); + s = next; + + if (n_specs >= n_specs_allocated) + { + n_specs_allocated = 1 + (3 * n_specs_allocated) / 2; + spec = (struct tspec *) xrealloc (spec, (n_specs_allocated + * sizeof (struct tspec))); + } + + bcopy ((char *) &tspec, (char *) &spec[n_specs], sizeof (struct tspec)); + ++n_specs; + } + + return 0; +} + +/* Given a list of one or more input filenames FILE_LIST, set the global + file pointer IN_STREAM to position N_SKIP in the concatenation of + those files. If any file operation fails or if there are fewer than + N_SKIP bytes in the combined input, give an error message and exit. + When possible, use seek- rather than read operations to advance + IN_STREAM. A file name of "-" is interpreted as standard input. */ + +static void +skip (n_skip) + long unsigned int n_skip; +{ + for ( /*empty */ ; *file_list != NULL; ++file_list) + { + struct stat file_stats; + int j; + + if (STREQ (*file_list, "-")) + { + input_filename = "standard input"; + in_stream = stdin; + } + else + { + input_filename = *file_list; + in_stream = fopen (input_filename, "r"); + if (in_stream == NULL) + error (2, errno, "%s", input_filename); + } + + if (n_skip == 0) + break; + + /* First try using fseek. For large offsets, all this work is + worthwhile. If the offset is below some threshold it may be + more efficient to move the pointer by reading. There are two + issues when trying to use fseek: + - the file must be seekable. + - before seeking to the specified position, make sure + that the new position is in the current file. + Try to do that by getting file's size using stat(). + But that will work only for regular files and dirs. */ + + if (fstat (fileno (in_stream), &file_stats)) + error (2, errno, "%s", input_filename); + + /* The st_size field is valid only for regular files and + directories. FIXME: is the preceding true? + If the number of bytes left to skip is at least as large as + the size of the current file, we can decrement + n_skip and go on to the next file. */ + if (S_ISREG (file_stats.st_mode) || S_ISDIR (file_stats.st_mode)) + { + if (n_skip >= file_stats.st_size) + { + n_skip -= file_stats.st_size; + if (in_stream != stdin) + { + if (fclose (in_stream)) + error (2, errno, "%s", input_filename); + } + continue; + } + else + { + if (fseek (in_stream, n_skip, SEEK_SET) == 0) + { + n_skip = 0; + break; + } + } + } + + /* fseek didn't work or wasn't attempted; do it the slow way. */ + + for (j = n_skip / BUFSIZ; j >= 0; j--) + { + char buf[BUFSIZ]; + size_t n_bytes_to_read = (j > 0 + ? BUFSIZ + : n_skip % BUFSIZ); + size_t n_bytes_read; + n_bytes_read = fread (buf, 1, n_bytes_to_read, in_stream); + n_skip -= n_bytes_read; + if (n_bytes_read != n_bytes_to_read) + { + if (ferror (in_stream)) + error (2, errno, "%s", input_filename); + else + break; + } + } + + if (n_skip == 0) + break; + } + + if (n_skip != 0) + error (2, 0, "cannot skip past end of combined input"); +} + +static const char * +format_address (address) + long unsigned int address; +{ + const char *address_string; + + if (output_address_fmt_string == NULL) + address_string = ""; + else + { + sprintf (address_fmt_buffer, output_address_fmt_string, address); + address_string = address_fmt_buffer; + } + return address_string; +} + +/* Write N_BYTES bytes from CURR_BLOCK to standard output once for each + of the N_SPEC format specs. CURRENT_OFFSET is the byte address of + CURR_BLOCK in the concatenation of input files, and it is printed + (optionally) only before the output line associated with the first + format spec. When duplicate blocks are being abbreviated, the output + for a sequence of identical input blocks is the output for the first + block followed by an asterisk alone on a line. It is valid to compare + the blocks PREV_BLOCK and CURR_BLOCK only when N_BYTES == BYTES_PER_BLOCK. + That condition may be false only for the last input block -- and then + only when it has not been padded to length BYTES_PER_BLOCK. */ + +static void +write_block (current_offset, n_bytes, prev_block, curr_block) + long unsigned int current_offset; + long unsigned int n_bytes; + const char *prev_block; + const char *curr_block; +{ + static int first = 1; + static int prev_pair_equal = 0; + +#define EQUAL_BLOCKS(b1, b2) (bcmp ((b1), (b2), bytes_per_block) == 0) + + if (abbreviate_duplicate_blocks + && !first && n_bytes == bytes_per_block + && EQUAL_BLOCKS (prev_block, curr_block)) + { + if (prev_pair_equal) + { + /* The two preceding blocks were equal, and the current + block is the same as the last one, so print nothing. */ + } + else + { + printf ("*\n"); + prev_pair_equal = 1; + } + } + else + { + int i; + + prev_pair_equal = 0; + for (i = 0; i < n_specs; i++) + { + if (printf ("%s ", (i == 0 + ? format_address (current_offset) + : address_pad)) + == EOF) + error (2, errno, "standard output"); + (*spec[i].print_function) (n_bytes, curr_block, spec[i].fmt_string); + } + } + first = 0; +} + +/* Read and return a single byte from the concatenation of the input + files named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file *FILE_LIST. If IN_STREAM is + at end-of-file, close it and update the global variables IN_STREAM, + FILE_LIST, and INPUT_FILENAME so they correspond to the next file in + the list. Then try to read a byte from the newly opened file. + Repeat if necessary until *FILE_LIST is NULL. Upon any read-, open-, + or close error give a message and exit. When EOF is reached for the + last file in FILE_LIST, return EOF. Any subsequent calls return EOF. */ + +static int +read_char () +{ + if (*file_list == NULL) + return EOF; + + while (1) + { + int c; + + c = fgetc (in_stream); + + if (c != EOF) + return c; + + if (errno != 0) + error (2, errno, "%s", input_filename); + + if (in_stream != stdin) + if (fclose (in_stream) == EOF) + error (2, errno, "%s", input_filename); + + ++file_list; + if (*file_list == NULL) + return EOF; + + if (STREQ (*file_list, "-")) + { + input_filename = "standard input"; + in_stream = stdin; + } + else + { + input_filename = *file_list; + in_stream = fopen (input_filename, "r"); + if (in_stream == NULL) + error (2, errno, "%s", input_filename); + } + } +} + +/* Read N bytes into BLOCK from the concatenation of the input files + named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file *FILE_LIST. On subsequent + calls, if *FILE_LIST is NULL, don't modify BLOCK and return zero. + If all N bytes cannot be read from IN_STREAM, close IN_STREAM and + update the global variables IN_STREAM, FILE_LIST, and INPUT_FILENAME. + Then try to read the remaining bytes from the newly opened file. + Repeat if necessary until *FILE_LIST is NULL. Upon any read-, open-, + or close error give a message and exit. Otherwise, return the number + of bytes read. */ + +static unsigned long int +read_block (n, block) + size_t n; + char *block; +{ + unsigned long int n_bytes_in_buffer; + + assert (n > 0 && n <= bytes_per_block); + if (n == 0) + return 0; + + n_bytes_in_buffer = 0; + + if (*file_list == NULL) + return 0; /* EOF. */ + + while (1) + { + size_t n_needed; + size_t n_read; + + n_needed = n - n_bytes_in_buffer; + n_read = fread (block + n_bytes_in_buffer, 1, n_needed, in_stream); + + if (ferror (in_stream)) + error (2, errno, "%s", input_filename); + + if (n_read == n_needed) + return n; + + n_bytes_in_buffer += n_read; + + if (in_stream != stdin) + if (fclose (in_stream) == EOF) + error (2, errno, "%s", input_filename); + + ++file_list; + if (*file_list == NULL) + return n_bytes_in_buffer; + + if (STREQ (*file_list, "-")) + { + input_filename = "standard input"; + in_stream = stdin; + } + else + { + input_filename = *file_list; + in_stream = fopen (input_filename, "r"); + if (in_stream == NULL) + error (2, errno, "%s", input_filename); + } + } +} + +/* Return the least common multiple of the sizes associated + with the format specs. */ + +static int +get_lcm () +{ + int i; + int l_c_m = 1; + + for (i = 0; i < n_specs; i++) + l_c_m = lcm (l_c_m, width_bytes[(int) spec[i].size]); + return l_c_m; +} + +/* Read chunks of size BYTES_PER_BLOCK from the input files, write the + formatted block to standard output, and repeat until the specified + maximum number of bytes has been read or until all input has been + processed. If the last block read is smaller than BYTES_PER_BLOCK + and its size is not a multiple of the size associated with a format + spec, extend the input block with zero bytes until its length is a + multiple of all format spec sizes. Write the final block. Finally, + write on a line by itself the offset of the byte after the last byte + read. */ + +static void +dump () +{ + char *block[2]; + unsigned long int current_offset; + int idx = 0; + size_t n_bytes_read; + + block[0] = (char *) alloca (bytes_per_block); + block[1] = (char *) alloca (bytes_per_block); + + current_offset = n_bytes_to_skip; + + if (limit_bytes_to_format) + { + size_t end_offset = n_bytes_to_skip + max_bytes_to_format; + + n_bytes_read = 0; + while (current_offset < end_offset) + { + size_t n_needed; + n_needed = MIN (end_offset - current_offset, bytes_per_block); + n_bytes_read = read_block (n_needed, block[idx]); + if (n_bytes_read < bytes_per_block) + break; + assert (n_bytes_read == bytes_per_block); + write_block (current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } + else + { + while (1) + { + n_bytes_read = read_block (bytes_per_block, block[idx]); + if (n_bytes_read < bytes_per_block) + break; + assert (n_bytes_read == bytes_per_block); + write_block (current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } + + if (n_bytes_read > 0) + { + int l_c_m; + size_t bytes_to_write; + + l_c_m = get_lcm (); + + /* Make bytes_to_write the smallest multiple of l_c_m that + is at least as large as n_bytes_read. */ + bytes_to_write = l_c_m * (int) ((n_bytes_read + l_c_m - 1) / l_c_m); + + bzero (block[idx] + n_bytes_read, bytes_to_write - n_bytes_read); + write_block (current_offset, bytes_to_write, + block[!idx], block[idx]); + current_offset += n_bytes_read; + } + + if (output_address_fmt_string != NULL) + { + if (printf ("%s\n", format_address (current_offset)) == EOF) + error (2, errno, "standard output"); + } +} + +/* STRINGS mode. Find each "string constant" in the file. + A string constant is a run of at least `string_min' ASCII graphic + (or formatting) characters terminated by a null. Based on a + function written by Richard Stallman for a pre-POSIX + version of od. */ + +static void +dump_strings () +{ + int bufsize = MAX (100, string_min); + char *buf = xmalloc (bufsize); + unsigned long address = n_bytes_to_skip; + + while (1) + { + int i; + int c; + + /* See if the next `string_min' chars are all printing chars. */ + tryline: + + if (limit_bytes_to_format + && address >= (n_bytes_to_skip + max_bytes_to_format - string_min)) + break; + + for (i = 0; i < string_min; i++) + { + c = read_char (); + address++; + if (c < 0) + return; + if (!isprint (c)) + /* Found a non-printing. Try again starting with next char. */ + goto tryline; + buf[i] = c; + } + + /* We found a run of `string_min' printable characters. + Now see if it is terminated with a null byte. */ + while (!limit_bytes_to_format + || address < n_bytes_to_skip + max_bytes_to_format) + { + if (i == bufsize) + { + bufsize = 1 + 3 * bufsize / 2; + buf = xrealloc (buf, bufsize); + } + c = read_char (); + address++; + if (c < 0) + return; + if (c == '\0') + break; /* It is; print this string. */ + if (!isprint (c)) + goto tryline; /* It isn't; give up on this string. */ + buf[i++] = c; /* String continues; store it all. */ + } + + /* If we get here, the string is all printable and null-terminated, + so print it. It is all in `buf' and `i' is its length. */ + buf[i] = 0; + if (output_address_fmt_string != NULL) + { + if (printf ("%s ", format_address (address - i - 1)) == EOF) + error (2, errno, "standard output"); + } + for (i = 0; (c = buf[i]); i++) + { + int err; + switch (c) + { + case '\007': + err = fputs ("\\a", stdout); + break; + + case '\b': + err = fputs ("\\b", stdout); + break; + + case '\f': + err = fputs ("\\f", stdout); + break; + + case '\n': + err = fputs ("\\n", stdout); + break; + + case '\r': + err = fputs ("\\r", stdout); + break; + + case '\t': + err = fputs ("\\t", stdout); + break; + + case '\v': + err = fputs ("\\v", stdout); + break; + + default: + err = putchar (c); + } + if (err == EOF) + error (2, errno, "standard output"); + } + if (putchar ('\n') == EOF) + error (2, errno, "standard output"); + } + free (buf); +} + +void +main (argc, argv) + int argc; + char **argv; +{ + int c; + int n_files; + int i; + unsigned int l_c_m; + unsigned int address_pad_len; + unsigned long int desired_width; + int width_specified = 0; + + program_name = argv[0]; + + for (i = 0; i <= MAX_INTEGRAL_TYPE_SIZE; i++) + integral_type_size[i] = NO_SIZE; + + integral_type_size[sizeof (char)] = CHAR; + integral_type_size[sizeof (short int)] = SHORT; + integral_type_size[sizeof (int)] = INT; + integral_type_size[sizeof (long int)] = LONG; + + for (i = 0; i <= MAX_FP_TYPE_SIZE; i++) + fp_type_size[i] = NO_SIZE; + + fp_type_size[sizeof (float)] = FP_SINGLE; + /* The array entry for `double' is filled in after that for LONG_DOUBLE + so that if `long double' is the same type or if long double isn't + supported FP_LONG_DOUBLE will never be used. */ + fp_type_size[sizeof (LONG_DOUBLE)] = FP_LONG_DOUBLE; + fp_type_size[sizeof (double)] = FP_DOUBLE; + + n_specs = 0; + n_specs_allocated = 5; + spec = (struct tspec *) xmalloc (n_specs_allocated * sizeof (struct tspec)); + + output_address_fmt_string = "%07o"; + address_pad_len = 7; + flag_dump_strings = 0; + + while ((c = getopt_long (argc, argv, "abcdfhilos::xw::A:j:N:t:v", + long_options, (int *) 0)) + != EOF) + { + strtoul_error err; + + switch (c) + { + case 'A': + switch (optarg[0]) + { + case 'd': + output_address_fmt_string = "%07d"; + address_pad_len = 7; + break; + case 'o': + output_address_fmt_string = "%07o"; + address_pad_len = 7; + break; + case 'x': + output_address_fmt_string = "%06x"; + address_pad_len = 6; + break; + case 'n': + output_address_fmt_string = NULL; + address_pad_len = 0; + break; + default: + error (2, 0, + "invalid output address radix `%c'; it must be one character from [doxn]", + optarg[0]); + break; + } + break; + + case 'j': + err = my_strtoul (optarg, 0, &n_bytes_to_skip, 1); + if (err != UINT_OK) + uint_fatal_error (optarg, "skip argument", err); + break; + + case 'N': + limit_bytes_to_format = 1; + + err = my_strtoul (optarg, 0, &max_bytes_to_format, 1); + if (err != UINT_OK) + uint_fatal_error (optarg, "limit argument", err); + break; + + case 's': + if (optarg == NULL) + string_min = 3; + else + { + err = my_strtoul (optarg, 0, &string_min, 1); + if (err != UINT_OK) + uint_fatal_error (optarg, "minimum string length", err); + } + ++flag_dump_strings; + break; + + case 't': + if (decode_format_string (optarg)) + error (2, 0, "invalid type string `%s'", optarg); + break; + + case 'v': + abbreviate_duplicate_blocks = 0; + break; + + /* The next several cases map the old, pre-POSIX format + specification options to the corresponding POSIX format + specs. GNU od accepts any combination of old- and + new-style options. If only POSIX format specs are used + and more than one is used, they are accumulated. If only + old-style options are used, all but the last are ignored. + If both types of specs are used in the same command, the + last old-style option and any POSIX specs following it + are accumulated. To illustrate, `od -c -t a' is the same + as `od -t ca', but `od -t a -c' is the same as `od -c'. */ + +#define CASE_OLD_ARG(old_char,new_string) \ + case old_char: \ + { \ + const char *next; \ + int tmp; \ + assert (n_specs_allocated >= 1); \ + tmp = decode_one_format (new_string, &next, &(spec[0])); \ + n_specs = 1; \ + assert (tmp == 0); \ + assert (*next == '\0'); \ + } \ + break + + CASE_OLD_ARG ('a', "a"); + CASE_OLD_ARG ('b', "oC"); + CASE_OLD_ARG ('c', "c"); + CASE_OLD_ARG ('d', "u2"); + CASE_OLD_ARG ('f', "fF"); + CASE_OLD_ARG ('h', "x2"); + CASE_OLD_ARG ('i', "d2"); + CASE_OLD_ARG ('l', "d4"); + CASE_OLD_ARG ('o', "o2"); + CASE_OLD_ARG ('x', "x2"); + +#undef CASE_OLD_ARG + + case 'w': + width_specified = 1; + if (optarg == NULL) + { + desired_width = 32; + } + else + { + err = my_strtoul (optarg, 10, &desired_width, 0); + if (err != UINT_OK) + error (2, 0, "invalid width specification `%s'", optarg); + } + break; + + default: + usage (); + break; + } + } + + if (flag_dump_strings && n_specs > 0) + error (2, 0, "no type may be specified when dumping strings"); + + assert (address_pad_len <= MAX_ADDRESS_LENGTH); + for (i = 0; i < address_pad_len; i++) + address_pad[i] = ' '; + address_pad[address_pad_len] = '\0'; + + if (n_specs == 0) + { + int err = decode_one_format ("o2", NULL, &(spec[0])); + + assert (err == 0); + n_specs = 1; + } + + n_files = argc - optind; + if (n_files > 0) + file_list = (char const *const *) &argv[optind]; + else + { + /* If no files were listed on the command line, set up the + global array FILE_LIST so that it contains the null-terminated + list of one name: "-". */ + static char const * const default_file_list[] = {"-", NULL}; + + file_list = default_file_list; + } + + skip (n_bytes_to_skip); + + /* Compute output block length. */ + l_c_m = get_lcm (); + + if (width_specified) + { + if (desired_width != 0 && desired_width % l_c_m == 0) + bytes_per_block = desired_width; + else + { + error (0, 0, "warning: invalid width %d; using %d instead", + desired_width, l_c_m); + bytes_per_block = l_c_m; + } + } + else + { + if (l_c_m < DEFAULT_BYTES_PER_BLOCK) + bytes_per_block = l_c_m * (int) (DEFAULT_BYTES_PER_BLOCK / l_c_m); + else + bytes_per_block = l_c_m; + } + +#ifdef DEBUG + for (i = 0; i < n_specs; i++) + { + printf ("%d: fmt=\"%s\" width=%d\n", + i, spec[i].fmt_string, width_bytes[spec[i].size]); + } +#endif + + if (flag_dump_strings) + { + dump_strings (); + } + else + { + dump (); + } + + exit (0); +} |