summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiels Möller <nisse@lysator.liu.se>2014-01-31 17:39:52 +0100
committerPádraig Brady <P@draigBrady.com>2014-02-09 11:29:24 +0000
commitb370924c03adaef222859061c61be06fc30c9a3e (patch)
tree026d54633dc52ac9c7339b2c953f5cfeceaa7426
parentb938b6e289ef78815935ffa705673a6a8b2ee98e (diff)
downloadcoreutils-b370924c03adaef222859061c61be06fc30c9a3e.tar.xz
od: add an --endian option to control byte swapping
* src/od.c (main): Handle the new --endian option, taking "little" and "big" as parameters. (usage): Describe the new option. (PRINT_FIELDS): Adjust to swap bytes if required. * tests/misc/od-endian.sh: A new test to verify the byte swapping operations for hex (ints) and floats for all sizes between 1 and 16 inclusive. * test/local.mk: Reference the new test. * doc/coreutils.texi (od invocation): Describe the new option. * NEWS: Mention the new feature.
-rw-r--r--NEWS5
-rw-r--r--doc/coreutils.texi10
-rw-r--r--src/od.c59
-rw-r--r--tests/local.mk1
-rwxr-xr-xtests/misc/od-endian.sh46
5 files changed, 118 insertions, 3 deletions
diff --git a/NEWS b/NEWS
index e091d1829..0da82accf 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,11 @@ GNU coreutils NEWS -*- outline -*-
it would display an error, requiring --no-dereference to avoid the issue.
[bug introduced in coreutils-5.3.0]
+** New features
+
+ od accepts a new option: --endian=TYPE to handle inputs with different byte
+ orders, or to provide consistent output on systems with disparate endianness.
+
** Improvements
stat and tail work better with HFS+ and HFSX. stat -f --format=%T now reports
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 35e7bd9fd..326929125 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -1868,6 +1868,16 @@ none (do not print offsets).
The default is octal.
+@item --endian=@var{order}
+@opindex --endian
+@cindex byte-swapping
+@cindex endianness
+Reorder input bytes, to handle inputs with differing byte orders,
+or to provide consistent output independent of the endian convention
+of the current system. Swapping is performed according to the
+specified @option{--type} size and endian @var{order}, which can be
+@samp{little} or @samp{big}.
+
@item -j @var{bytes}
@itemx --skip-bytes=@var{bytes}
@opindex -j
diff --git a/src/od.c b/src/od.c
index 514fe5024..7bc0e2a8b 100644
--- a/src/od.c
+++ b/src/od.c
@@ -23,6 +23,7 @@
#include <getopt.h>
#include <sys/types.h>
#include "system.h"
+#include "argmatch.h"
#include "error.h"
#include "ftoastr.h"
#include "quote.h"
@@ -259,13 +260,37 @@ 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];
+#ifndef WORDS_BIGENDIAN
+# define WORDS_BIGENDIAN 0
+#endif
+
+/* Use native endianess by default. */
+static bool input_swap;
+
static char const short_options[] = "A:aBbcDdeFfHhIij:LlN:OoS:st:vw::Xx";
/* For long options that have no equivalent short option, use a
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum
{
- TRADITIONAL_OPTION = CHAR_MAX + 1
+ TRADITIONAL_OPTION = CHAR_MAX + 1,
+ ENDIAN_OPTION,
+};
+
+enum endian_type
+{
+ endian_little,
+ endian_big
+};
+
+static char const *const endian_args[] =
+{
+ "little", "big", NULL
+};
+
+static enum endian_type const endian_types[] =
+{
+ endian_little, endian_big
};
static struct option const long_options[] =
@@ -278,6 +303,7 @@ static struct option const long_options[] =
{"strings", optional_argument, NULL, 'S'},
{"traditional", no_argument, NULL, TRADITIONAL_OPTION},
{"width", optional_argument, NULL, 'w'},
+ {"endian", required_argument, NULL, ENDIAN_OPTION },
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
@@ -318,6 +344,7 @@ suffixes may be . for octal and b for multiply by 512.\n\
fputs (_("\
-A, --address-radix=RADIX output format for file offsets; RADIX is one\n\
of [doxn], for Decimal, Octal, Hex or None\n\
+ --endian={big|little} swap input bytes according the specified order\n\
-j, --skip-bytes=BYTES skip BYTES input bytes first\n\
"), stdout);
fputs (_("\
@@ -400,13 +427,27 @@ N (size_t fields, size_t blank, void const *block, \
char const *FMT_STRING, int width, int pad) \
{ \
T const *p = block; \
- uintmax_t i; \
+ uintmax_t i; \
int pad_remaining = pad; \
for (i = fields; blank < i; i--) \
{ \
int next_pad = pad * (i - 1) / fields; \
int adjusted_width = pad_remaining - next_pad + width; \
- T x = *p++; \
+ T x; \
+ if (input_swap && sizeof (T) > 1) \
+ { \
+ size_t j; \
+ union { \
+ T x; \
+ char b[sizeof (T)]; \
+ } u; \
+ for (j = 0; j < sizeof (T); j++) \
+ u.b[j] = ((const char *) p)[sizeof (T) - 1 - j]; \
+ x = u.x; \
+ } \
+ else \
+ x = *p; \
+ p++; \
ACTION; \
pad_remaining = next_pad; \
} \
@@ -1664,6 +1705,18 @@ main (int argc, char **argv)
traditional = true;
break;
+ case ENDIAN_OPTION:
+ switch (XARGMATCH ("--endian", optarg, endian_args, endian_types))
+ {
+ case endian_big:
+ input_swap = ! WORDS_BIGENDIAN;
+ break;
+ case endian_little:
+ input_swap = WORDS_BIGENDIAN;
+ break;
+ }
+ break;
+
/* The next several cases map the traditional format
specification options to the corresponding modern format
specs. GNU od accepts any combination of old- and
diff --git a/tests/local.mk b/tests/local.mk
index 9d556f6db..815dc6fdd 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -239,6 +239,7 @@ all_tests = \
tests/misc/xstrtol.pl \
tests/tail-2/pid.sh \
tests/misc/od.pl \
+ tests/misc/od-endian.sh \
tests/misc/od-float.sh \
tests/misc/mktemp.pl \
tests/misc/arch.sh \
diff --git a/tests/misc/od-endian.sh b/tests/misc/od-endian.sh
new file mode 100755
index 000000000..5ee17e617
--- /dev/null
+++ b/tests/misc/od-endian.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# verify that od --endian works properly
+
+# Copyright (C) 2014 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ od
+
+in='0123456789abcdef'
+
+# rev(1) is not generally available, so here's a simplistic
+# implementation sufficient for our purposes.
+rev() {
+ while read line; do
+ printf '%s' "$line" | sed 's/./&\n/g' | tac | paste -s -d ''
+ done
+}
+
+in_swapped() { printf '%s' "$in" | sed "s/.\{$1\}/&\\n/g" | rev | tr -d '\n'; }
+
+for e in little big; do
+ test $e = little && eo=big || eo=little
+ for s in 1 2 4 8 16; do
+ for t in x f; do
+ od -t $t$s --endian=$e /dev/null > /dev/null 2>&1 || continue
+ printf '%s' "$in" | od -An -t $t$s --endian=$e > out1
+ in_swapped "$s" | od -An -t $t$s --endian=$eo > out2
+ compare out1 out2 || fail=1
+ done
+ done
+done
+
+Exit $fail