1430 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1430 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* od -- dump files in octal and other formats
 | 
						|
   Copyright (C) 92, 1995-2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 | 
						|
 | 
						|
/* Written by Jim Meyering.  */
 | 
						|
 | 
						|
/* Busyboxed by Denys Vlasenko
 | 
						|
 | 
						|
Based on od.c from coreutils-5.2.1
 | 
						|
Top bloat sources:
 | 
						|
 | 
						|
00000073 t parse_old_offset
 | 
						|
0000007b t get_lcm
 | 
						|
00000090 r long_options
 | 
						|
00000092 t print_named_ascii
 | 
						|
000000bf t print_ascii
 | 
						|
00000168 t write_block
 | 
						|
00000366 t decode_format_string
 | 
						|
00000a71 T od_main
 | 
						|
 | 
						|
Tested for compat with coreutils 6.3
 | 
						|
using this script. Minor differences fixed.
 | 
						|
 | 
						|
#!/bin/sh
 | 
						|
echo STD
 | 
						|
time /path/to/coreutils/od \
 | 
						|
...params... \
 | 
						|
>std
 | 
						|
echo Exit code $?
 | 
						|
echo BBOX
 | 
						|
time ./busybox od \
 | 
						|
...params... \
 | 
						|
>bbox
 | 
						|
echo Exit code $?
 | 
						|
diff -u -a std bbox >bbox.diff || { echo Different!; sleep 1; }
 | 
						|
 | 
						|
*/
 | 
						|
 | 
						|
#include "libbb.h"
 | 
						|
 | 
						|
#define assert(a) ((void)0)
 | 
						|
 | 
						|
/* Check for 0x7f is a coreutils 6.3 addition */
 | 
						|
#define ISPRINT(c) (((c)>=' ') && (c) != 0x7f)
 | 
						|
 | 
						|
typedef long double longdouble_t;
 | 
						|
typedef unsigned long long ulonglong_t;
 | 
						|
typedef long long llong;
 | 
						|
 | 
						|
#if ENABLE_LFS
 | 
						|
# define xstrtooff_sfx xstrtoull_sfx
 | 
						|
#else
 | 
						|
# define xstrtooff_sfx xstrtoul_sfx
 | 
						|
#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
 | 
						|
 | 
						|
enum size_spec {
 | 
						|
	NO_SIZE,
 | 
						|
	CHAR,
 | 
						|
	SHORT,
 | 
						|
	INT,
 | 
						|
	LONG,
 | 
						|
	LONG_LONG,
 | 
						|
	FLOAT_SINGLE,
 | 
						|
	FLOAT_DOUBLE,
 | 
						|
	FLOAT_LONG_DOUBLE,
 | 
						|
	N_SIZE_SPECS
 | 
						|
};
 | 
						|
 | 
						|
enum output_format {
 | 
						|
	SIGNED_DECIMAL,
 | 
						|
	UNSIGNED_DECIMAL,
 | 
						|
	OCTAL,
 | 
						|
	HEXADECIMAL,
 | 
						|
	FLOATING_POINT,
 | 
						|
	NAMED_CHARACTER,
 | 
						|
	CHARACTER
 | 
						|
};
 | 
						|
 | 
						|
/* Each output format specification (from '-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) (size_t, const char *, const char *);
 | 
						|
	char *fmt_string;
 | 
						|
	int hexl_mode_trailer;
 | 
						|
	int field_width;
 | 
						|
};
 | 
						|
 | 
						|
/* 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 uint8_t bytes_to_oct_digits[] ALIGN1 =
 | 
						|
{0, 3, 6, 8, 11, 14, 16, 19, 22, 25, 27, 30, 32, 35, 38, 41, 43};
 | 
						|
 | 
						|
static const uint8_t bytes_to_signed_dec_digits[] ALIGN1 =
 | 
						|
{1, 4, 6, 8, 11, 13, 16, 18, 20, 23, 25, 28, 30, 33, 35, 37, 40};
 | 
						|
 | 
						|
static const uint8_t bytes_to_unsigned_dec_digits[] ALIGN1 =
 | 
						|
{0, 3, 5, 8, 10, 13, 15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39};
 | 
						|
 | 
						|
static const uint8_t bytes_to_hex_digits[] ALIGN1 =
 | 
						|
{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 signed char width_bytes[] ALIGN1 = {
 | 
						|
	-1,
 | 
						|
	sizeof(char),
 | 
						|
	sizeof(short),
 | 
						|
	sizeof(int),
 | 
						|
	sizeof(long),
 | 
						|
	sizeof(ulonglong_t),
 | 
						|
	sizeof(float),
 | 
						|
	sizeof(double),
 | 
						|
	sizeof(longdouble_t)
 | 
						|
};
 | 
						|
/* Ensure that for each member of 'enum size_spec' there is an
 | 
						|
   initializer in the width_bytes array.  */
 | 
						|
struct ERR_width_bytes_has_bad_size {
 | 
						|
	char ERR_width_bytes_has_bad_size[ARRAY_SIZE(width_bytes) == N_SIZE_SPECS ? 1 : -1];
 | 
						|
};
 | 
						|
 | 
						|
static smallint flag_dump_strings;
 | 
						|
/* Non-zero if an old-style 'pseudo-address' was specified.  */
 | 
						|
static smallint flag_pseudo_start;
 | 
						|
static smallint limit_bytes_to_format;
 | 
						|
/* When 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 smallint verbose;
 | 
						|
static smallint ioerror;
 | 
						|
 | 
						|
static size_t string_min;
 | 
						|
 | 
						|
/* An array of specs describing how to format each input block.  */
 | 
						|
static size_t n_specs;
 | 
						|
static struct tspec *spec;
 | 
						|
 | 
						|
/* Function that accepts an address and an optional following char,
 | 
						|
   and prints the address and char to stdout.  */
 | 
						|
static void (*format_address)(off_t, char);
 | 
						|
/* The difference between the old-style pseudo starting address and
 | 
						|
   the number of bytes to skip.  */
 | 
						|
static off_t pseudo_offset;
 | 
						|
/* When zero, MAX_BYTES_TO_FORMAT and END_OFFSET are ignored, and all
 | 
						|
   input is formatted.  */
 | 
						|
 | 
						|
/* 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 bytes_per_block = 32; /* have to use unsigned, not size_t */
 | 
						|
 | 
						|
/* A NULL-terminated list of the file-arguments from the command line.  */
 | 
						|
static const char *const *file_list;
 | 
						|
 | 
						|
/* The input stream associated with the current file.  */
 | 
						|
static FILE *in_stream;
 | 
						|
 | 
						|
#define MAX_INTEGRAL_TYPE_SIZE sizeof(ulonglong_t)
 | 
						|
static const unsigned char integral_type_size[MAX_INTEGRAL_TYPE_SIZE + 1] ALIGN1 = {
 | 
						|
	[sizeof(char)] = CHAR,
 | 
						|
#if USHRT_MAX != UCHAR_MAX
 | 
						|
	[sizeof(short)] = SHORT,
 | 
						|
#endif
 | 
						|
#if UINT_MAX != USHRT_MAX
 | 
						|
	[sizeof(int)] = INT,
 | 
						|
#endif
 | 
						|
#if ULONG_MAX != UINT_MAX
 | 
						|
	[sizeof(long)] = LONG,
 | 
						|
#endif
 | 
						|
#if ULLONG_MAX != ULONG_MAX
 | 
						|
	[sizeof(ulonglong_t)] = LONG_LONG,
 | 
						|
#endif
 | 
						|
};
 | 
						|
 | 
						|
#define MAX_FP_TYPE_SIZE sizeof(longdouble_t)
 | 
						|
static const unsigned char fp_type_size[MAX_FP_TYPE_SIZE + 1] ALIGN1 = {
 | 
						|
	/* gcc seems to allow repeated indexes. Last one stays */
 | 
						|
	[sizeof(longdouble_t)] = FLOAT_LONG_DOUBLE,
 | 
						|
	[sizeof(double)] = FLOAT_DOUBLE,
 | 
						|
	[sizeof(float)] = FLOAT_SINGLE
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static unsigned
 | 
						|
gcd(unsigned u, unsigned v)
 | 
						|
{
 | 
						|
	unsigned t;
 | 
						|
	while (v != 0) {
 | 
						|
		t = u % v;
 | 
						|
		u = v;
 | 
						|
		v = t;
 | 
						|
	}
 | 
						|
	return u;
 | 
						|
}
 | 
						|
 | 
						|
/* Compute the least common multiple of U and V.  */
 | 
						|
static unsigned
 | 
						|
lcm(unsigned u, unsigned v) {
 | 
						|
	unsigned t = gcd(u, v);
 | 
						|
	if (t == 0)
 | 
						|
		return 0;
 | 
						|
	return u * v / t;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_s_char(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	while (n_bytes--) {
 | 
						|
		int tmp = *(signed char *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(unsigned char);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_char(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	while (n_bytes--) {
 | 
						|
		unsigned tmp = *(unsigned char *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(unsigned char);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_s_short(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(signed short);
 | 
						|
	while (n_bytes--) {
 | 
						|
		int tmp = *(signed short *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(unsigned short);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_short(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(unsigned short);
 | 
						|
	while (n_bytes--) {
 | 
						|
		unsigned tmp = *(unsigned short *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(unsigned short);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_int(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(unsigned);
 | 
						|
	while (n_bytes--) {
 | 
						|
		unsigned tmp = *(unsigned *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(unsigned);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
#if UINT_MAX == ULONG_MAX
 | 
						|
# define print_long print_int
 | 
						|
#else
 | 
						|
static void
 | 
						|
print_long(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(unsigned long);
 | 
						|
	while (n_bytes--) {
 | 
						|
		unsigned long tmp = *(unsigned long *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(unsigned long);
 | 
						|
	}
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
#if ULONG_MAX == ULLONG_MAX
 | 
						|
# define print_long_long print_long
 | 
						|
#else
 | 
						|
static void
 | 
						|
print_long_long(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(ulonglong_t);
 | 
						|
	while (n_bytes--) {
 | 
						|
		ulonglong_t tmp = *(ulonglong_t *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(ulonglong_t);
 | 
						|
	}
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
static void
 | 
						|
print_float(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(float);
 | 
						|
	while (n_bytes--) {
 | 
						|
		float tmp = *(float *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(float);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_double(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(double);
 | 
						|
	while (n_bytes--) {
 | 
						|
		double tmp = *(double *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(double);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_long_double(size_t n_bytes, const char *block, const char *fmt_string)
 | 
						|
{
 | 
						|
	n_bytes /= sizeof(longdouble_t);
 | 
						|
	while (n_bytes--) {
 | 
						|
		longdouble_t tmp = *(longdouble_t *) block;
 | 
						|
		printf(fmt_string, tmp);
 | 
						|
		block += sizeof(longdouble_t);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* print_[named]_ascii are optimized for speed.
 | 
						|
 * Remember, someday you may want to pump gigabytes through this thing.
 | 
						|
 * Saving a dozen of .text bytes here is counter-productive */
 | 
						|
 | 
						|
static void
 | 
						|
print_named_ascii(size_t n_bytes, const char *block,
 | 
						|
		const char *unused_fmt_string UNUSED_PARAM)
 | 
						|
{
 | 
						|
	/* Names for some non-printing characters.  */
 | 
						|
	static const char charname[33][3] ALIGN1 = {
 | 
						|
		"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"
 | 
						|
	};
 | 
						|
	// buf[N] pos:  01234 56789
 | 
						|
	char buf[12] = "   x\0 0xx\0";
 | 
						|
	// actually "   x\0 xxx\0", but I want to share the string with below.
 | 
						|
	// [12] because we take three 32bit stack slots anyway, and
 | 
						|
	// gcc is too dumb to initialize with constant stores,
 | 
						|
	// it copies initializer from rodata. Oh well.
 | 
						|
 | 
						|
	while (n_bytes--) {
 | 
						|
		unsigned masked_c = *(unsigned char *) block++;
 | 
						|
 | 
						|
		masked_c &= 0x7f;
 | 
						|
		if (masked_c == 0x7f) {
 | 
						|
			fputs(" del", stdout);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		if (masked_c > ' ') {
 | 
						|
			buf[3] = masked_c;
 | 
						|
			fputs(buf, stdout);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		/* Why? Because printf(" %3.3s") is much slower... */
 | 
						|
		buf[6] = charname[masked_c][0];
 | 
						|
		buf[7] = charname[masked_c][1];
 | 
						|
		buf[8] = charname[masked_c][2];
 | 
						|
		fputs(buf+5, stdout);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
print_ascii(size_t n_bytes, const char *block,
 | 
						|
		const char *unused_fmt_string UNUSED_PARAM)
 | 
						|
{
 | 
						|
	// buf[N] pos:  01234 56789
 | 
						|
	char buf[12] = "   x\0 0xx\0";
 | 
						|
 | 
						|
	while (n_bytes--) {
 | 
						|
		const char *s;
 | 
						|
		unsigned c = *(unsigned char *) block++;
 | 
						|
 | 
						|
		if (ISPRINT(c)) {
 | 
						|
			buf[3] = c;
 | 
						|
			fputs(buf, stdout);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		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;
 | 
						|
		case '\x7f':
 | 
						|
			s = " 177";
 | 
						|
			break;
 | 
						|
		default: /* c is never larger than 040 */
 | 
						|
			buf[7] = (c >> 3) + '0';
 | 
						|
			buf[8] = (c & 7) + '0';
 | 
						|
			s = buf + 5;
 | 
						|
		}
 | 
						|
		fputs(s, stdout);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* Given a list of one or more input filenames FILE_LIST, set the global
 | 
						|
   file pointer IN_STREAM and the global string INPUT_FILENAME to the
 | 
						|
   first one that can be successfully opened. Modify FILE_LIST to
 | 
						|
   reference the next filename in the list.  A file name of "-" is
 | 
						|
   interpreted as standard input.  If any file open fails, give an error
 | 
						|
   message and return nonzero.  */
 | 
						|
 | 
						|
static void
 | 
						|
open_next_file(void)
 | 
						|
{
 | 
						|
	while (1) {
 | 
						|
		if (!*file_list)
 | 
						|
			return;
 | 
						|
		in_stream = fopen_or_warn_stdin(*file_list++);
 | 
						|
		if (in_stream) {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		ioerror = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (limit_bytes_to_format && !flag_dump_strings)
 | 
						|
		setbuf(in_stream, NULL);
 | 
						|
}
 | 
						|
 | 
						|
/* Test whether there have been errors on in_stream, and close it if
 | 
						|
   it is not standard input.  Return nonzero if there has been an error
 | 
						|
   on in_stream or stdout; return zero otherwise.  This function will
 | 
						|
   report more than one error only if both a read and a write error
 | 
						|
   have occurred.  IN_ERRNO, if nonzero, is the error number
 | 
						|
   corresponding to the most recent action for IN_STREAM.  */
 | 
						|
 | 
						|
static void
 | 
						|
check_and_close(void)
 | 
						|
{
 | 
						|
	if (in_stream) {
 | 
						|
		if (ferror(in_stream))	{
 | 
						|
			bb_error_msg("%s: read error", (in_stream == stdin)
 | 
						|
					? bb_msg_standard_input
 | 
						|
					: file_list[-1]
 | 
						|
			);
 | 
						|
			ioerror = 1;
 | 
						|
		}
 | 
						|
		fclose_if_not_stdin(in_stream);
 | 
						|
		in_stream = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ferror(stdout)) {
 | 
						|
		bb_error_msg("write error");
 | 
						|
		ioerror = 1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* If S points to a single valid modern od format string, put
 | 
						|
   a description of that format in *TSPEC, return pointer to
 | 
						|
   character following the just-decoded format.
 | 
						|
   For example, if S were "d4afL", we will return a rtp 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";
 | 
						|
	}
 | 
						|
   S_ORIG is solely for reporting errors.  It should be the full format
 | 
						|
   string argument. */
 | 
						|
 | 
						|
static const char *
 | 
						|
decode_one_format(const char *s_orig, const char *s, struct tspec *tspec)
 | 
						|
{
 | 
						|
	enum size_spec size_spec;
 | 
						|
	unsigned size;
 | 
						|
	enum output_format fmt;
 | 
						|
	const char *p;
 | 
						|
	char *end;
 | 
						|
	char *fmt_string = NULL;
 | 
						|
	void (*print_function) (size_t, const char *, const char *);
 | 
						|
	unsigned c;
 | 
						|
	unsigned field_width = 0;
 | 
						|
	int pos;
 | 
						|
 | 
						|
 | 
						|
	switch (*s) {
 | 
						|
	case 'd':
 | 
						|
	case 'o':
 | 
						|
	case 'u':
 | 
						|
	case 'x': {
 | 
						|
		static const char CSIL[] ALIGN1 = "CSIL";
 | 
						|
 | 
						|
		c = *s++;
 | 
						|
		p = strchr(CSIL, *s);
 | 
						|
		/* if *s == NUL, p != NULL! Testcase: "od -tx" */
 | 
						|
		if (!p || *p == '\0') {
 | 
						|
			size = sizeof(int);
 | 
						|
			if (isdigit(s[0])) {
 | 
						|
				size = bb_strtou(s, &end, 0);
 | 
						|
				if (errno == ERANGE
 | 
						|
				 || MAX_INTEGRAL_TYPE_SIZE < size
 | 
						|
				 || integral_type_size[size] == NO_SIZE
 | 
						|
				) {
 | 
						|
					bb_error_msg_and_die("invalid type string '%s'; "
 | 
						|
						"%u-byte %s type is not supported",
 | 
						|
						s_orig, size, "integral");
 | 
						|
				}
 | 
						|
				s = end;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			static const uint8_t CSIL_sizeof[4] = {
 | 
						|
				sizeof(char),
 | 
						|
				sizeof(short),
 | 
						|
				sizeof(int),
 | 
						|
				sizeof(long),
 | 
						|
			};
 | 
						|
			size = CSIL_sizeof[p - CSIL];
 | 
						|
			s++; /* skip C/S/I/L */
 | 
						|
		}
 | 
						|
 | 
						|
#define ISPEC_TO_FORMAT(Spec, Min_format, Long_format, Max_format) \
 | 
						|
	((Spec) == LONG_LONG ? (Max_format) \
 | 
						|
	: ((Spec) == LONG ? (Long_format) : (Min_format)))
 | 
						|
 | 
						|
#define FMT_BYTES_ALLOCATED 9
 | 
						|
		size_spec = integral_type_size[size];
 | 
						|
 | 
						|
		{
 | 
						|
			static const char doux[] ALIGN1 = "doux";
 | 
						|
			static const char doux_fmt_letter[][4] = {
 | 
						|
				"lld", "llo", "llu", "llx"
 | 
						|
			};
 | 
						|
			static const enum output_format doux_fmt[] = {
 | 
						|
				SIGNED_DECIMAL,
 | 
						|
				OCTAL,
 | 
						|
				UNSIGNED_DECIMAL,
 | 
						|
				HEXADECIMAL,
 | 
						|
			};
 | 
						|
			static const uint8_t *const doux_bytes_to_XXX[] = {
 | 
						|
				bytes_to_signed_dec_digits,
 | 
						|
				bytes_to_oct_digits,
 | 
						|
				bytes_to_unsigned_dec_digits,
 | 
						|
				bytes_to_hex_digits,
 | 
						|
			};
 | 
						|
			static const char doux_fmtstring[][sizeof(" %%0%u%s")] = {
 | 
						|
				" %%%u%s",
 | 
						|
				" %%0%u%s",
 | 
						|
				" %%%u%s",
 | 
						|
				" %%0%u%s",
 | 
						|
			};
 | 
						|
 | 
						|
			pos = strchr(doux, c) - doux;
 | 
						|
			fmt = doux_fmt[pos];
 | 
						|
			field_width = doux_bytes_to_XXX[pos][size];
 | 
						|
			p = doux_fmt_letter[pos] + 2;
 | 
						|
			if (size_spec == LONG) p--;
 | 
						|
			if (size_spec == LONG_LONG) p -= 2;
 | 
						|
			fmt_string = xasprintf(doux_fmtstring[pos], field_width, p);
 | 
						|
		}
 | 
						|
 | 
						|
		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: /* case LONG_LONG: */
 | 
						|
			print_function = print_long_long;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	case 'f': {
 | 
						|
		static const char FDL[] ALIGN1 = "FDL";
 | 
						|
 | 
						|
		fmt = FLOATING_POINT;
 | 
						|
		++s;
 | 
						|
		p = strchr(FDL, *s);
 | 
						|
		if (!p) {
 | 
						|
			size = sizeof(double);
 | 
						|
			if (isdigit(s[0])) {
 | 
						|
				size = bb_strtou(s, &end, 0);
 | 
						|
				if (errno == ERANGE || size > MAX_FP_TYPE_SIZE
 | 
						|
				 || fp_type_size[size] == NO_SIZE
 | 
						|
				) {
 | 
						|
					bb_error_msg_and_die("invalid type string '%s'; "
 | 
						|
						"%u-byte %s type is not supported",
 | 
						|
						s_orig, size, "floating point");
 | 
						|
				}
 | 
						|
				s = end;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			static const uint8_t FDL_sizeof[] = {
 | 
						|
				sizeof(float),
 | 
						|
				sizeof(double),
 | 
						|
				sizeof(longdouble_t),
 | 
						|
			};
 | 
						|
 | 
						|
			size = FDL_sizeof[p - FDL];
 | 
						|
		}
 | 
						|
 | 
						|
		size_spec = fp_type_size[size];
 | 
						|
 | 
						|
		switch (size_spec) {
 | 
						|
		case FLOAT_SINGLE:
 | 
						|
			print_function = print_float;
 | 
						|
			field_width = FLT_DIG + 8;
 | 
						|
			/* Don't use %#e; not all systems support it.  */
 | 
						|
			fmt_string = xasprintf(" %%%d.%de", field_width, FLT_DIG);
 | 
						|
			break;
 | 
						|
		case FLOAT_DOUBLE:
 | 
						|
			print_function = print_double;
 | 
						|
			field_width = DBL_DIG + 8;
 | 
						|
			fmt_string = xasprintf(" %%%d.%de", field_width, DBL_DIG);
 | 
						|
			break;
 | 
						|
		default: /* case FLOAT_LONG_DOUBLE: */
 | 
						|
			print_function = print_long_double;
 | 
						|
			field_width = LDBL_DIG + 8;
 | 
						|
			fmt_string = xasprintf(" %%%d.%dLe", field_width, LDBL_DIG);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	case 'a':
 | 
						|
		++s;
 | 
						|
		fmt = NAMED_CHARACTER;
 | 
						|
		size_spec = CHAR;
 | 
						|
		print_function = print_named_ascii;
 | 
						|
		field_width = 3;
 | 
						|
		break;
 | 
						|
	case 'c':
 | 
						|
		++s;
 | 
						|
		fmt = CHARACTER;
 | 
						|
		size_spec = CHAR;
 | 
						|
		print_function = print_ascii;
 | 
						|
		field_width = 3;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		bb_error_msg_and_die("invalid character '%c' "
 | 
						|
				"in type string '%s'", *s, s_orig);
 | 
						|
	}
 | 
						|
 | 
						|
	tspec->size = size_spec;
 | 
						|
	tspec->fmt = fmt;
 | 
						|
	tspec->print_function = print_function;
 | 
						|
	tspec->fmt_string = fmt_string;
 | 
						|
 | 
						|
	tspec->field_width = field_width;
 | 
						|
	tspec->hexl_mode_trailer = (*s == 'z');
 | 
						|
	if (tspec->hexl_mode_trailer)
 | 
						|
		s++;
 | 
						|
 | 
						|
	return s;
 | 
						|
}
 | 
						|
 | 
						|
/* Decode the modern od format string S.  Append the decoded
 | 
						|
   representation to the global array SPEC, reallocating SPEC if
 | 
						|
   necessary.  */
 | 
						|
 | 
						|
static void
 | 
						|
decode_format_string(const char *s)
 | 
						|
{
 | 
						|
	const char *s_orig = s;
 | 
						|
 | 
						|
	while (*s != '\0') {
 | 
						|
		struct tspec tspec;
 | 
						|
		const char *next;
 | 
						|
 | 
						|
		next = decode_one_format(s_orig, s, &tspec);
 | 
						|
 | 
						|
		assert(s != next);
 | 
						|
		s = next;
 | 
						|
		spec = xrealloc_vector(spec, 4, n_specs);
 | 
						|
		memcpy(&spec[n_specs], &tspec, sizeof(spec[0]));
 | 
						|
		n_specs++;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* 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 return
 | 
						|
   nonzero.  When possible, use seek rather than read operations to
 | 
						|
   advance IN_STREAM.  */
 | 
						|
 | 
						|
static void
 | 
						|
skip(off_t n_skip)
 | 
						|
{
 | 
						|
	if (n_skip == 0)
 | 
						|
		return;
 | 
						|
 | 
						|
	while (in_stream) { /* !EOF */
 | 
						|
		struct stat file_stats;
 | 
						|
 | 
						|
		/* First try seeking.  For large offsets, this extra 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 seek:
 | 
						|
			- 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 fstat.
 | 
						|
			  But that will work only for regular files.  */
 | 
						|
 | 
						|
			/* The st_size field is valid only for regular files
 | 
						|
			   (and for symbolic links, which cannot occur here).
 | 
						|
			   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 (fstat(fileno(in_stream), &file_stats) == 0
 | 
						|
		 && S_ISREG(file_stats.st_mode) && file_stats.st_size > 0
 | 
						|
		) {
 | 
						|
			if (file_stats.st_size < n_skip) {
 | 
						|
				n_skip -= file_stats.st_size;
 | 
						|
				/* take "check & close / open_next" route */
 | 
						|
			} else {
 | 
						|
				if (fseeko(in_stream, n_skip, SEEK_CUR) != 0)
 | 
						|
					ioerror = 1;
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			/* If it's not a regular file with positive size,
 | 
						|
			   position the file pointer by reading.  */
 | 
						|
			char buf[1024];
 | 
						|
			size_t n_bytes_to_read = 1024;
 | 
						|
			size_t n_bytes_read;
 | 
						|
 | 
						|
			while (n_skip > 0) {
 | 
						|
				if (n_skip < n_bytes_to_read)
 | 
						|
					n_bytes_to_read = n_skip;
 | 
						|
				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)
 | 
						|
					break; /* EOF on this file or error */
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (n_skip == 0)
 | 
						|
			return;
 | 
						|
 | 
						|
		check_and_close();
 | 
						|
		open_next_file();
 | 
						|
	}
 | 
						|
 | 
						|
	if (n_skip)
 | 
						|
		bb_error_msg_and_die("can't skip past end of combined input");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
typedef void FN_format_address(off_t address, char c);
 | 
						|
 | 
						|
static void
 | 
						|
format_address_none(off_t address UNUSED_PARAM, char c UNUSED_PARAM)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
static char address_fmt[] ALIGN1 = "%0n"OFF_FMT"xc";
 | 
						|
/* Corresponds to 'x' above */
 | 
						|
#define address_base_char address_fmt[sizeof(address_fmt)-3]
 | 
						|
/* Corresponds to 'n' above */
 | 
						|
#define address_pad_len_char address_fmt[2]
 | 
						|
 | 
						|
static void
 | 
						|
format_address_std(off_t address, char c)
 | 
						|
{
 | 
						|
	/* Corresponds to 'c' */
 | 
						|
	address_fmt[sizeof(address_fmt)-2] = c;
 | 
						|
	printf(address_fmt, address);
 | 
						|
}
 | 
						|
 | 
						|
#if ENABLE_LONG_OPTS
 | 
						|
/* only used with --traditional */
 | 
						|
static void
 | 
						|
format_address_paren(off_t address, char c)
 | 
						|
{
 | 
						|
	putchar('(');
 | 
						|
	format_address_std(address, ')');
 | 
						|
	if (c) putchar(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
format_address_label(off_t address, char c)
 | 
						|
{
 | 
						|
	format_address_std(address, ' ');
 | 
						|
	format_address_paren(address + pseudo_offset, c);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
static void
 | 
						|
dump_hexl_mode_trailer(size_t n_bytes, const char *block)
 | 
						|
{
 | 
						|
	fputs("  >", stdout);
 | 
						|
	while (n_bytes--) {
 | 
						|
		unsigned c = *(unsigned char *) block++;
 | 
						|
		c = (ISPRINT(c) ? c : '.');
 | 
						|
		putchar(c);
 | 
						|
	}
 | 
						|
	putchar('<');
 | 
						|
}
 | 
						|
 | 
						|
/* 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(off_t current_offset, size_t n_bytes,
 | 
						|
		const char *prev_block, const char *curr_block)
 | 
						|
{
 | 
						|
	static char first = 1;
 | 
						|
	static char prev_pair_equal = 0;
 | 
						|
	size_t i;
 | 
						|
 | 
						|
	if (!verbose && !first
 | 
						|
	 && n_bytes == bytes_per_block
 | 
						|
	 && memcmp(prev_block, curr_block, bytes_per_block) == 0
 | 
						|
	) {
 | 
						|
		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 {
 | 
						|
			puts("*");
 | 
						|
			prev_pair_equal = 1;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		first = 0;
 | 
						|
		prev_pair_equal = 0;
 | 
						|
		for (i = 0; i < n_specs; i++) {
 | 
						|
			if (i == 0)
 | 
						|
				format_address(current_offset, '\0');
 | 
						|
			else
 | 
						|
				printf("%*s", address_pad_len_char - '0', "");
 | 
						|
			(*spec[i].print_function) (n_bytes, curr_block, spec[i].fmt_string);
 | 
						|
			if (spec[i].hexl_mode_trailer) {
 | 
						|
				/* space-pad out to full line width, then dump the trailer */
 | 
						|
				int datum_width = width_bytes[spec[i].size];
 | 
						|
				int blank_fields = (bytes_per_block - n_bytes) / datum_width;
 | 
						|
				int field_width = spec[i].field_width + 1;
 | 
						|
				printf("%*s", blank_fields * field_width, "");
 | 
						|
				dump_hexl_mode_trailer(n_bytes, curr_block);
 | 
						|
			}
 | 
						|
			putchar('\n');
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
read_block(size_t n, char *block, size_t *n_bytes_in_buffer)
 | 
						|
{
 | 
						|
	assert(0 < n && n <= bytes_per_block);
 | 
						|
 | 
						|
	*n_bytes_in_buffer = 0;
 | 
						|
 | 
						|
	if (n == 0)
 | 
						|
		return;
 | 
						|
 | 
						|
	while (in_stream != NULL) { /* EOF.  */
 | 
						|
		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);
 | 
						|
		*n_bytes_in_buffer += n_read;
 | 
						|
		if (n_read == n_needed)
 | 
						|
			break;
 | 
						|
		/* error check is done in check_and_close */
 | 
						|
		check_and_close();
 | 
						|
		open_next_file();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* Return the least common multiple of the sizes associated
 | 
						|
   with the format specs.  */
 | 
						|
 | 
						|
static int
 | 
						|
get_lcm(void)
 | 
						|
{
 | 
						|
	size_t 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;
 | 
						|
}
 | 
						|
 | 
						|
#if ENABLE_LONG_OPTS
 | 
						|
/* If S is a valid traditional offset specification with an optional
 | 
						|
   leading '+' return nonzero and set *OFFSET to the offset it denotes.  */
 | 
						|
 | 
						|
static int
 | 
						|
parse_old_offset(const char *s, off_t *offset)
 | 
						|
{
 | 
						|
	static const struct suffix_mult Bb[] = {
 | 
						|
		{ "B", 1024 },
 | 
						|
		{ "b", 512 },
 | 
						|
		{ "", 0 }
 | 
						|
	};
 | 
						|
	char *p;
 | 
						|
	int radix;
 | 
						|
 | 
						|
	/* Skip over any leading '+'. */
 | 
						|
	if (s[0] == '+') ++s;
 | 
						|
 | 
						|
	/* Determine the radix we'll use to interpret S.  If there is a '.',
 | 
						|
	 * it's decimal, otherwise, if the string begins with '0X'or '0x',
 | 
						|
	 * it's hexadecimal, else octal.  */
 | 
						|
	p = strchr(s, '.');
 | 
						|
	radix = 8;
 | 
						|
	if (p) {
 | 
						|
		p[0] = '\0'; /* cheating */
 | 
						|
		radix = 10;
 | 
						|
	} else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
 | 
						|
		radix = 16;
 | 
						|
 | 
						|
	*offset = xstrtooff_sfx(s, radix, Bb);
 | 
						|
	if (p) p[0] = '.';
 | 
						|
 | 
						|
	return (*offset >= 0);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/* Read a chunk 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(off_t current_offset, off_t end_offset)
 | 
						|
{
 | 
						|
	char *block[2];
 | 
						|
	int idx;
 | 
						|
	size_t n_bytes_read;
 | 
						|
 | 
						|
	block[0] = xmalloc(2*bytes_per_block);
 | 
						|
	block[1] = block[0] + bytes_per_block;
 | 
						|
 | 
						|
	idx = 0;
 | 
						|
	if (limit_bytes_to_format) {
 | 
						|
		while (1) {
 | 
						|
			size_t n_needed;
 | 
						|
			if (current_offset >= end_offset) {
 | 
						|
				n_bytes_read = 0;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			n_needed = MIN(end_offset - current_offset,
 | 
						|
				(off_t) bytes_per_block);
 | 
						|
			read_block(n_needed, block[idx], &n_bytes_read);
 | 
						|
			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) {
 | 
						|
			read_block(bytes_per_block, block[idx], &n_bytes_read);
 | 
						|
			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 * ((n_bytes_read + l_c_m - 1) / l_c_m);
 | 
						|
 | 
						|
		memset(block[idx] + n_bytes_read, 0, bytes_to_write - n_bytes_read);
 | 
						|
		write_block(current_offset, bytes_to_write,
 | 
						|
				   block[!idx], block[idx]);
 | 
						|
		current_offset += n_bytes_read;
 | 
						|
	}
 | 
						|
 | 
						|
	format_address(current_offset, '\n');
 | 
						|
 | 
						|
	if (limit_bytes_to_format && current_offset >= end_offset)
 | 
						|
		check_and_close();
 | 
						|
 | 
						|
	free(block[0]);
 | 
						|
}
 | 
						|
 | 
						|
/* Read a single byte into *C 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 INPUT_FILENAME.  If IN_STREAM
 | 
						|
   is at end-of-file, close it and update the global variables IN_STREAM
 | 
						|
   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 EOF is reached for the last file in FILE_LIST, then
 | 
						|
   set *C to EOF and return.  Subsequent calls do likewise.  */
 | 
						|
 | 
						|
static void
 | 
						|
read_char(int *c)
 | 
						|
{
 | 
						|
	while (in_stream) { /* !EOF */
 | 
						|
		*c = fgetc(in_stream);
 | 
						|
		if (*c != EOF)
 | 
						|
			return;
 | 
						|
		check_and_close();
 | 
						|
		open_next_file();
 | 
						|
	}
 | 
						|
	*c = EOF;
 | 
						|
}
 | 
						|
 | 
						|
/* 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 INPUT_FILENAME.  If all N
 | 
						|
   bytes cannot be read from IN_STREAM, close IN_STREAM and update
 | 
						|
   the global variables IN_STREAM and INPUT_FILENAME.  Then try to
 | 
						|
   read the remaining bytes from the newly opened file.  Repeat if
 | 
						|
   necessary until EOF is reached for the last file in FILE_LIST.
 | 
						|
   On subsequent calls, don't modify BLOCK and return zero.  Set
 | 
						|
   *N_BYTES_IN_BUFFER to the number of bytes read.  If an error occurs,
 | 
						|
   it will be detected through ferror when the stream is about to be
 | 
						|
   closed.  If there is an error, give a message but continue reading
 | 
						|
   as usual and return nonzero.  Otherwise return zero.  */
 | 
						|
 | 
						|
/* STRINGS mode.  Find each "string constant" in the input.
 | 
						|
   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
 | 
						|
   traditional version of od.  */
 | 
						|
 | 
						|
static void
 | 
						|
dump_strings(off_t address, off_t end_offset)
 | 
						|
{
 | 
						|
	size_t bufsize = MAX(100, string_min);
 | 
						|
	char *buf = xmalloc(bufsize);
 | 
						|
 | 
						|
	while (1) {
 | 
						|
		size_t i;
 | 
						|
		int c;
 | 
						|
 | 
						|
		/* See if the next 'string_min' chars are all printing chars.  */
 | 
						|
 tryline:
 | 
						|
		if (limit_bytes_to_format && (end_offset - string_min <= address))
 | 
						|
			break;
 | 
						|
		i = 0;
 | 
						|
		while (!limit_bytes_to_format || address < end_offset) {
 | 
						|
			if (i == bufsize) {
 | 
						|
				bufsize += bufsize/8;
 | 
						|
				buf = xrealloc(buf, bufsize);
 | 
						|
			}
 | 
						|
			read_char(&c);
 | 
						|
			if (c < 0) { /* EOF */
 | 
						|
				free(buf);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			address++;
 | 
						|
			if (!c)
 | 
						|
				break;
 | 
						|
			if (!ISPRINT(c))
 | 
						|
				goto tryline;	/* It isn't; give up on this string.  */
 | 
						|
			buf[i++] = c;		/* String continues; store it all.  */
 | 
						|
		}
 | 
						|
 | 
						|
		if (i < string_min)		/* Too short! */
 | 
						|
			goto tryline;
 | 
						|
 | 
						|
		/* If we get here, the string is all printable and NUL-terminated,
 | 
						|
		 * so print it.  It is all in 'buf' and 'i' is its length.  */
 | 
						|
		buf[i] = 0;
 | 
						|
		format_address(address - i - 1, ' ');
 | 
						|
 | 
						|
		for (i = 0; (c = buf[i]); i++) {
 | 
						|
			switch (c) {
 | 
						|
			case '\007': fputs("\\a", stdout); break;
 | 
						|
			case '\b': fputs("\\b", stdout); break;
 | 
						|
			case '\f': fputs("\\f", stdout); break;
 | 
						|
			case '\n': fputs("\\n", stdout); break;
 | 
						|
			case '\r': fputs("\\r", stdout); break;
 | 
						|
			case '\t': fputs("\\t", stdout); break;
 | 
						|
			case '\v': fputs("\\v", stdout); break;
 | 
						|
			default: putchar(c);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		putchar('\n');
 | 
						|
	}
 | 
						|
 | 
						|
	/* We reach this point only if we search through
 | 
						|
	   (max_bytes_to_format - string_min) bytes before reaching EOF.  */
 | 
						|
	free(buf);
 | 
						|
 | 
						|
	check_and_close();
 | 
						|
}
 | 
						|
 | 
						|
int od_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 | 
						|
int od_main(int argc, char **argv)
 | 
						|
{
 | 
						|
	static const struct suffix_mult bkm[] = {
 | 
						|
		{ "b", 512 },
 | 
						|
		{ "k", 1024 },
 | 
						|
		{ "m", 1024*1024 },
 | 
						|
		{ "", 0 }
 | 
						|
	};
 | 
						|
	enum {
 | 
						|
		OPT_A = 1 << 0,
 | 
						|
		OPT_N = 1 << 1,
 | 
						|
		OPT_a = 1 << 2,
 | 
						|
		OPT_b = 1 << 3,
 | 
						|
		OPT_c = 1 << 4,
 | 
						|
		OPT_d = 1 << 5,
 | 
						|
		OPT_f = 1 << 6,
 | 
						|
		OPT_h = 1 << 7,
 | 
						|
		OPT_i = 1 << 8,
 | 
						|
		OPT_j = 1 << 9,
 | 
						|
		OPT_l = 1 << 10,
 | 
						|
		OPT_o = 1 << 11,
 | 
						|
		OPT_t = 1 << 12,
 | 
						|
		OPT_v = 1 << 13,
 | 
						|
		OPT_x = 1 << 14,
 | 
						|
		OPT_s = 1 << 15,
 | 
						|
		OPT_S = 1 << 16,
 | 
						|
		OPT_w = 1 << 17,
 | 
						|
		OPT_traditional = (1 << 18) * ENABLE_LONG_OPTS,
 | 
						|
	};
 | 
						|
#if ENABLE_LONG_OPTS
 | 
						|
	static const char od_longopts[] ALIGN1 =
 | 
						|
		"skip-bytes\0"        Required_argument "j"
 | 
						|
		"address-radix\0"     Required_argument "A"
 | 
						|
		"read-bytes\0"        Required_argument "N"
 | 
						|
		"format\0"            Required_argument "t"
 | 
						|
		"output-duplicates\0" No_argument       "v"
 | 
						|
		"strings\0"           Optional_argument "S"
 | 
						|
		"width\0"             Optional_argument "w"
 | 
						|
		"traditional\0"       No_argument       "\xff"
 | 
						|
		;
 | 
						|
#endif
 | 
						|
	char *str_A, *str_N, *str_j, *str_S;
 | 
						|
	llist_t *lst_t = NULL;
 | 
						|
	unsigned opt;
 | 
						|
	int l_c_m;
 | 
						|
	/* The old-style 'pseudo starting address' to be printed in parentheses
 | 
						|
	   after any true address.  */
 | 
						|
	off_t pseudo_start = pseudo_start; // for gcc
 | 
						|
	/* The number of input bytes to skip before formatting and writing.  */
 | 
						|
	off_t n_bytes_to_skip = 0;
 | 
						|
	/* The offset of the first byte after the last byte to be formatted.  */
 | 
						|
	off_t end_offset = 0;
 | 
						|
	/* The maximum number of bytes that will be formatted.  */
 | 
						|
	off_t max_bytes_to_format = 0;
 | 
						|
 | 
						|
	spec = NULL;
 | 
						|
	format_address = format_address_std;
 | 
						|
	address_base_char = 'o';
 | 
						|
	address_pad_len_char = '7';
 | 
						|
	/* flag_dump_strings = 0; - already is */
 | 
						|
 | 
						|
	/* Parse command line */
 | 
						|
	opt_complementary = "w+:t::"; /* -w N, -t is a list */
 | 
						|
#if ENABLE_LONG_OPTS
 | 
						|
	applet_long_options = od_longopts;
 | 
						|
#endif
 | 
						|
	opt = getopt32(argv, "A:N:abcdfhij:lot:vxsS:"
 | 
						|
		"w::", // -w with optional param
 | 
						|
		// -S was -s and also had optional parameter
 | 
						|
		// but in coreutils 6.3 it was renamed and now has
 | 
						|
		// _mandatory_ parameter
 | 
						|
		&str_A, &str_N, &str_j, &lst_t, &str_S, &bytes_per_block);
 | 
						|
	argc -= optind;
 | 
						|
	argv += optind;
 | 
						|
	if (opt & OPT_A) {
 | 
						|
		static const char doxn[] ALIGN1 = "doxn";
 | 
						|
		static const char doxn_address_base_char[] ALIGN1 = {
 | 
						|
			'u', 'o', 'x', /* '?' fourth one is not important */
 | 
						|
		};
 | 
						|
		static const uint8_t doxn_address_pad_len_char[] ALIGN1 = {
 | 
						|
			'7', '7', '6', /* '?' */
 | 
						|
		};
 | 
						|
		char *p;
 | 
						|
		int pos;
 | 
						|
		p = strchr(doxn, str_A[0]);
 | 
						|
		if (!p)
 | 
						|
			bb_error_msg_and_die("bad output address radix "
 | 
						|
				"'%c' (must be [doxn])", str_A[0]);
 | 
						|
		pos = p - doxn;
 | 
						|
		if (pos == 3) format_address = format_address_none;
 | 
						|
		address_base_char = doxn_address_base_char[pos];
 | 
						|
		address_pad_len_char = doxn_address_pad_len_char[pos];
 | 
						|
	}
 | 
						|
	if (opt & OPT_N) {
 | 
						|
		limit_bytes_to_format = 1;
 | 
						|
		max_bytes_to_format = xstrtooff_sfx(str_N, 0, bkm);
 | 
						|
	}
 | 
						|
	if (opt & OPT_a) decode_format_string("a");
 | 
						|
	if (opt & OPT_b) decode_format_string("oC");
 | 
						|
	if (opt & OPT_c) decode_format_string("c");
 | 
						|
	if (opt & OPT_d) decode_format_string("u2");
 | 
						|
	if (opt & OPT_f) decode_format_string("fF");
 | 
						|
	if (opt & OPT_h) decode_format_string("x2");
 | 
						|
	if (opt & OPT_i) decode_format_string("d2");
 | 
						|
	if (opt & OPT_j) n_bytes_to_skip = xstrtooff_sfx(str_j, 0, bkm);
 | 
						|
	if (opt & OPT_l) decode_format_string("d4");
 | 
						|
	if (opt & OPT_o) decode_format_string("o2");
 | 
						|
	//if (opt & OPT_t)...
 | 
						|
	while (lst_t) {
 | 
						|
		decode_format_string(llist_pop(&lst_t));
 | 
						|
	}
 | 
						|
	if (opt & OPT_v) verbose = 1;
 | 
						|
	if (opt & OPT_x) decode_format_string("x2");
 | 
						|
	if (opt & OPT_s) decode_format_string("d2");
 | 
						|
	if (opt & OPT_S) {
 | 
						|
		string_min = 3;
 | 
						|
		string_min = xstrtou_sfx(str_S, 0, bkm);
 | 
						|
		flag_dump_strings = 1;
 | 
						|
	}
 | 
						|
	//if (opt & OPT_w)...
 | 
						|
	//if (opt & OPT_traditional)...
 | 
						|
 | 
						|
	if (flag_dump_strings && n_specs > 0)
 | 
						|
		bb_error_msg_and_die("no type may be specified when dumping strings");
 | 
						|
 | 
						|
	/* If the --traditional option is used, there may be from
 | 
						|
	 * 0 to 3 remaining command line arguments;  handle each case
 | 
						|
	 * separately.
 | 
						|
	 * od [file] [[+]offset[.][b] [[+]label[.][b]]]
 | 
						|
	 * The offset and pseudo_start have the same syntax.
 | 
						|
	 *
 | 
						|
	 * FIXME: POSIX 1003.1-2001 with XSI requires support for the
 | 
						|
	 * traditional syntax even if --traditional is not given.  */
 | 
						|
 | 
						|
#if ENABLE_LONG_OPTS
 | 
						|
	if (opt & OPT_traditional) {
 | 
						|
		off_t o1, o2;
 | 
						|
 | 
						|
		if (argc == 1) {
 | 
						|
			if (parse_old_offset(argv[0], &o1)) {
 | 
						|
				n_bytes_to_skip = o1;
 | 
						|
				--argc;
 | 
						|
				++argv;
 | 
						|
			}
 | 
						|
		} else if (argc == 2) {
 | 
						|
			if (parse_old_offset(argv[0], &o1)
 | 
						|
			 && parse_old_offset(argv[1], &o2)
 | 
						|
			) {
 | 
						|
				n_bytes_to_skip = o1;
 | 
						|
				flag_pseudo_start = 1;
 | 
						|
				pseudo_start = o2;
 | 
						|
				argv += 2;
 | 
						|
				argc -= 2;
 | 
						|
			} else if (parse_old_offset(argv[1], &o2)) {
 | 
						|
				n_bytes_to_skip = o2;
 | 
						|
				--argc;
 | 
						|
				argv[1] = argv[0];
 | 
						|
				++argv;
 | 
						|
			} else {
 | 
						|
				bb_error_msg_and_die("invalid second operand "
 | 
						|
					"in compatibility mode '%s'", argv[1]);
 | 
						|
			}
 | 
						|
		} else if (argc == 3) {
 | 
						|
			if (parse_old_offset(argv[1], &o1)
 | 
						|
			 && parse_old_offset(argv[2], &o2)
 | 
						|
			) {
 | 
						|
				n_bytes_to_skip = o1;
 | 
						|
				flag_pseudo_start = 1;
 | 
						|
				pseudo_start = o2;
 | 
						|
				argv[2] = argv[0];
 | 
						|
				argv += 2;
 | 
						|
				argc -= 2;
 | 
						|
			} else {
 | 
						|
				bb_error_msg_and_die("in compatibility mode "
 | 
						|
					"the last two arguments must be offsets");
 | 
						|
			}
 | 
						|
		} else if (argc > 3)	{
 | 
						|
			bb_error_msg_and_die("compatibility mode supports "
 | 
						|
				"at most three arguments");
 | 
						|
		}
 | 
						|
 | 
						|
		if (flag_pseudo_start) {
 | 
						|
			if (format_address == format_address_none) {
 | 
						|
				address_base_char = 'o';
 | 
						|
				address_pad_len_char = '7';
 | 
						|
				format_address = format_address_paren;
 | 
						|
			} else
 | 
						|
				format_address = format_address_label;
 | 
						|
		}
 | 
						|
	}
 | 
						|
#endif
 | 
						|
 | 
						|
	if (limit_bytes_to_format) {
 | 
						|
		end_offset = n_bytes_to_skip + max_bytes_to_format;
 | 
						|
		if (end_offset < n_bytes_to_skip)
 | 
						|
			bb_error_msg_and_die("skip-bytes + read-bytes is too large");
 | 
						|
	}
 | 
						|
 | 
						|
	if (n_specs == 0) {
 | 
						|
		decode_format_string("o2");
 | 
						|
		n_specs = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	/* If no files were listed on the command line,
 | 
						|
	   set the global pointer FILE_LIST so that it
 | 
						|
	   references the null-terminated list of one name: "-".  */
 | 
						|
	file_list = bb_argv_dash;
 | 
						|
	if (argc > 0) {
 | 
						|
		/* Set the global pointer FILE_LIST so that it
 | 
						|
		   references the first file-argument on the command-line.  */
 | 
						|
		file_list = (char const *const *) argv;
 | 
						|
	}
 | 
						|
 | 
						|
	/* open the first input file */
 | 
						|
	open_next_file();
 | 
						|
	/* skip over any unwanted header bytes */
 | 
						|
	skip(n_bytes_to_skip);
 | 
						|
	if (!in_stream)
 | 
						|
		return EXIT_FAILURE;
 | 
						|
 | 
						|
	pseudo_offset = (flag_pseudo_start ? pseudo_start - n_bytes_to_skip : 0);
 | 
						|
 | 
						|
	/* Compute output block length.  */
 | 
						|
	l_c_m = get_lcm();
 | 
						|
 | 
						|
	if (opt & OPT_w) { /* -w: width */
 | 
						|
		if (!bytes_per_block || bytes_per_block % l_c_m != 0) {
 | 
						|
			bb_error_msg("warning: invalid width %u; using %d instead",
 | 
						|
					(unsigned)bytes_per_block, l_c_m);
 | 
						|
			bytes_per_block = l_c_m;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		bytes_per_block = l_c_m;
 | 
						|
		if (l_c_m < DEFAULT_BYTES_PER_BLOCK)
 | 
						|
			bytes_per_block *= DEFAULT_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(n_bytes_to_skip, end_offset);
 | 
						|
	else
 | 
						|
		dump(n_bytes_to_skip, end_offset);
 | 
						|
 | 
						|
	if (fclose(stdin) == EOF)
 | 
						|
		bb_perror_msg_and_die(bb_msg_standard_input);
 | 
						|
 | 
						|
	return ioerror;
 | 
						|
}
 |