Subject: bug#45648: closed (Re: bug#45648: `dd` seek/skip which way is up?)

From: [email-masked] (GNU bug Tracking System)
Date: Tue, 22 Feb 2022 17:13:01 +0000

Your bug report

#45648: `dd` seek/skip which way is up?

which was filed against the coreutils package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
45648: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=45648
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#45648: `dd` seek/skip which way is up?
From: Paul Eggert <[email-masked]>
To: Bela Lubkin <[email-masked]>
Cc: [email-masked]
Date: Tue, 22 Feb 2022 09:12:42 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

On 1/4/21 20:08, Paul Eggert wrote:
> On 1/4/21 7:44 PM, Bela Lubkin wrote:
>> TLDR: *huge* existing presence of 'iseek' and 'oseek'; most OSes document
>> them as pure synonyms for 'skip' and 'seek'.
> 
> Thanks for doing all that research. It's compelling, and I think your 
> patch (or something like it) should go in. I'll wait for a bit to hear 
> other opinions.

After thinking about the patch a bit more, let's omit the part about 
adding new conversions iseek_bytes etc., as I think there's a better way 
to address that issue. I proposed something in <https://bugs.gnu.org/54112>.

So instead of your patch, I installed the attached patches. The first 
one adds the iseek and oseek operands that you suggested; the second one 
clarifies dd documentation, as I found several things were confusing 
when rereading it carefully. Something like these patches should appear 
in the next coreutils release.

From 6ad981900cc170258d4914197e2796fc94a37863 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Mon, 21 Feb 2022 11:23:02 -0800
Subject: [PATCH 1/2] dd: support iseek= and oseek=

Alias iseek=N to skip=N, oseek=N to seek=N (Bug#45648).
* src/dd.c (scanargs): Parse iseek= and oseek=.
* tests/dd/skip-seek.pl (sk-seek5): New test case.
---
 NEWS                  |  3 +++
 doc/coreutils.texi    | 16 ++++++++++------
 src/dd.c              |  8 ++++----
 tests/dd/skip-seek.pl | 10 ++++++++++
 4 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/NEWS b/NEWS
index ef65b4ab8..de03f0d47 100644
--- a/NEWS
+++ b/NEWS
@@ -57,6 +57,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   dd conv=fsync now synchronizes output even after a write error,
   and similarly for dd conv=fdatasync.
 
+  dd now supports the aliases iseek=N for skip=N, and oseek=N for seek=N,
+  like FreeBSD and other operating systems.
+
   timeout --foreground --kill-after=... will now exit with status 137
   if the kill signal was sent, which is consistent with the behavior
   when the --foreground option is not specified.  This allows users to
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 8d2974bde..4ec998802 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -9189,8 +9189,7 @@ Read from @var{file} instead of standard input.
 @item of=@var{file}
 @opindex of
 Write to @var{file} instead of standard output.  Unless
-@samp{conv=notrunc} is given, @command{dd} truncates @var{file} to zero
-bytes (or the size specified with @samp{seek=}).
+@samp{conv=notrunc} is given, truncate @var{file} before writing it.
 
 @item ibs=@var{bytes}
 @opindex ibs
@@ -9230,15 +9229,20 @@ When converting variable-length records to fixed-length ones
 use @var{bytes} as the fixed record length.
 
 @item skip=@var{n}
+@itemx iseek=@var{n}
 @opindex skip
+@opindex iseek
 Skip @var{n} @samp{ibs}-byte blocks in the input file before copying.
 If @samp{iflag=skip_bytes} is specified, @var{n} is interpreted
 as a byte count rather than a block count.
 
 @item seek=@var{n}
+@itemx oseek=@var{n}
 @opindex seek
-Skip @var{n} @samp{obs}-byte blocks in the output file before copying.
-if @samp{oflag=seek_bytes} is specified, @var{n} is interpreted
+@opindex oseek
+Skip @var{n} @samp{obs}-byte blocks in the output file before
+truncating or copying.
+If @samp{oflag=seek_bytes} is specified, @var{n} is interpreted
 as a byte count rather than a block count.
 
 @item count=@var{n}
@@ -9588,14 +9592,14 @@ This flag can be used only with @code{iflag}.
 
 @item skip_bytes
 @opindex skip_bytes
-Interpret the @samp{skip=} operand as a byte count,
+Interpret the @samp{skip=} or @samp{iseek=} operand as a byte count,
 rather than a block count, which allows specifying
 an offset that is not a multiple of the I/O block size.
 This flag can be used only with @code{iflag}.
 
 @item seek_bytes
 @opindex seek_bytes
-Interpret the @samp{seek=} operand as a byte count,
+Interpret the @samp{seek=} or @samp{oseek=} operand as a byte count,
 rather than a block count, which allows specifying
 an offset that is not a multiple of the I/O block size.
 This flag can be used only with @code{oflag}.
diff --git a/src/dd.c b/src/dd.c
index 7360a4973..1c30e414d 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -562,8 +562,8 @@ Copy a file, converting and formatting according to the operands.\n\
   obs=BYTES       write BYTES bytes at a time (default: 512)\n\
   of=FILE         write to FILE instead of stdout\n\
   oflag=FLAGS     write as per the comma separated symbol list\n\
-  seek=N          skip N obs-sized blocks at start of output\n\
-  skip=N          skip N ibs-sized blocks at start of input\n\
+  seek=N          (or oseek=N) skip N obs-sized output blocks\n\
+  skip=N          (or iseek=N) skip N ibs-sized input blocks\n\
   status=LEVEL    The LEVEL of information to print to stderr;\n\
                   'none' suppresses everything but error messages,\n\
                   'noxfer' suppresses the final transfer statistics,\n\
@@ -1564,9 +1564,9 @@ scanargs (int argc, char *const *argv)
               n_max = MIN (SIZE_MAX, IDX_MAX);
               converted_idx = &conversion_blocksize;
             }
-          else if (operand_is (name, "skip"))
+          else if (operand_is (name, "skip") || operand_is (name, "iseek"))
             skip = n;
-          else if (operand_is (name, "seek"))
+          else if (operand_is (name + (*name == 'o'), "seek"))
             seek = n;
           else if (operand_is (name, "count"))
             count = n;
diff --git a/tests/dd/skip-seek.pl b/tests/dd/skip-seek.pl
index 41639cc71..0fcb1cf25 100755
--- a/tests/dd/skip-seek.pl
+++ b/tests/dd/skip-seek.pl
@@ -68,6 +68,16 @@ my @Tests =
       {OUT=> "bc\n"},
       {ERR=> "3+0 records in\n3+0 records out\n"},
      ],
+     [
+      # Check that iseek and oseek aliases work too.
+      'sk-seek5',
+      qw (bs=1 iseek=1 oseek=2 conv=notrunc count=3 status=noxfer of=@AUX@ < ),
+      {IN=> '0123456789abcdef'},
+      {AUX=> 'zyxwvutsrqponmlkji'},
+      {OUT=> ''},
+      {ERR=> "3+0 records in\n3+0 records out\n"},
+      {CMP=> ['zy123utsrqponmlkji', {'@AUX@'=> undef}]},
+     ],
     );
 
 my $save_temps = $ENV{DEBUG};
-- 
2.32.0

From a2d0ad6c6de032acadec32532afc22e47da4b617 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Tue, 22 Feb 2022 08:55:53 -0800
Subject: [PATCH 2/2] dd: improve doc relative to POSIX

* doc/coreutils.texi (dd invocation): Improve documentation,
clarifying whether features are extensions to POSIX.
---
 doc/coreutils.texi | 90 ++++++++++++++++++++++++++++++++++++----------
 1 file changed, 72 insertions(+), 18 deletions(-)

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 4ec998802..5419c61ef 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -9166,9 +9166,8 @@ option, and overrides the @option{--preserve=all} and @option{-a} options.
 @pindex dd
 @cindex converting while copying a file
 
-@command{dd} copies a file (from standard input to standard output, by
-default) with a changeable I/O block size, while optionally performing
-conversions on it.  Synopses:
+@command{dd} copies input to output with a changeable I/O block size,
+while optionally performing conversions on the data.  Synopses:
 
 @example
 dd [@var{operand}]@dots{}
@@ -9176,7 +9175,43 @@ dd @var{option}
 @end example
 
 The only options are @option{--help} and @option{--version}.
-@xref{Common options}.  @command{dd} accepts the following operands,
+@xref{Common options}.
+
+By default, @command{dd} copies standard input to standard output.
+To copy, @command{dd} repeatedly does the following steps in order:
+
+@enumerate
+@item
+Read an input block.
+
+@item
+If converting via @samp{sync}, pad as needed to meet the input block size.
+Pad with spaces if converting via @samp{block} or @samp{unblock}, NUL
+bytes otherwise.
+
+@item
+If @samp{bs=} is given and no conversion mentioned in steps (4) or (5)
+is given, output the data as a single block and skip all remaining steps.
+
+@item
+If the @samp{swab} conversion is given, swap each pair of input bytes.
+If the input data length is odd, preserve the last input byte
+(since there is nothing to swap it with).
+
+@item
+If any of the conversions @samp{swab}, @samp{block}, @samp{unblock},
+@samp{lcase}, @samp{ucase}, @samp{ascii}, @samp{ebcdic} and @samp{ibm}
+are given, do these conversions.  These conversions operate
+independently of input blocking, and might deal with records that span
+block boundaries.
+
+@item
+Aggregate the resulting data into output blocks of the specified size,
+and output each output block in turn.  Do not pad the last output block;
+it can be shorter than usual.
+@end enumerate
+
+@command{dd} accepts the following operands,
 whose syntax was inspired by the DD (data definition) statement of
 OS/360 JCL.
 
@@ -9233,8 +9268,9 @@ use @var{bytes} as the fixed record length.
 @opindex skip
 @opindex iseek
 Skip @var{n} @samp{ibs}-byte blocks in the input file before copying.
-If @samp{iflag=skip_bytes} is specified, @var{n} is interpreted
+With @samp{iflag=skip_bytes}, interpret @var{n}
 as a byte count rather than a block count.
+(The @samp{iseek=} spelling is an extension to POSIX.)
 
 @item seek=@var{n}
 @itemx oseek=@var{n}
@@ -9242,20 +9278,22 @@ as a byte count rather than a block count.
 @opindex oseek
 Skip @var{n} @samp{obs}-byte blocks in the output file before
 truncating or copying.
-If @samp{oflag=seek_bytes} is specified, @var{n} is interpreted
+With @samp{oflag=seek_bytes}, interpret @var{n}
 as a byte count rather than a block count.
+(The @samp{oseek=} spelling is an extension to POSIX.)
 
 @item count=@var{n}
 @opindex count
 Copy @var{n} @samp{ibs}-byte blocks from the input file, instead
 of everything until the end of the file.
-if @samp{iflag=count_bytes} is specified, @var{n} is interpreted
+With @samp{iflag=count_bytes}, interpret @var{n}
 as a byte count rather than a block count.
-Note if the input may return short reads as could be the case
+If short reads occur, as could be the case
 when reading from a pipe for example, @samp{iflag=fullblock}
-will ensure that @samp{count=} corresponds to complete input blocks
-rather than the traditional POSIX specified behavior of counting
-input read operations.
+ensures that @samp{count=} counts complete input blocks
+rather than input read operations.
+As an extension to POSIX, @samp{count=0} copies zero blocks
+instead of copying all blocks.
 
 @item status=@var{level}
 @opindex status
@@ -9301,6 +9339,8 @@ An additional line like @samp{1 truncated record} or @samp{10
 truncated records} is output after the @samp{records out} line if
 @samp{conv=block} processing truncated one or more input records.
 
+The @samp{status=} operand is a GNU extension to POSIX.
+
 @item conv=@var{conversion}[,@var{conversion}]@dots{}
 @opindex conv
 Convert the file as specified by the @var{conversion} argument(s).
@@ -9348,6 +9388,8 @@ Remove any trailing spaces in each @samp{cbs}-sized input block,
 and append a newline.
 
 The @samp{block} and @samp{unblock} conversions are mutually exclusive.
+If you use either of these conversions, you should also use the
+@samp{cbs=} operand.
 
 @item lcase
 @opindex lcase@r{, converting to}
@@ -9373,12 +9415,12 @@ Similarly, when the output is a device rather than a file,
 NUL input blocks are not copied, and therefore this conversion
 is most useful with virtual or pre zeroed devices.
 
+The @samp{sparse} conversion is a GNU extension to POSIX.
+
 @item swab
 @opindex swab @r{(byte-swapping)}
 @cindex byte-swapping
-Swap every pair of input bytes.  GNU @command{dd}, unlike others, works
-when an odd number of bytes are read---the last byte is simply copied
-(since there is nothing to swap it with).
+Swap every pair of input bytes.
 
 @item sync
 @opindex sync @r{(padding with ASCII NULs)}
@@ -9403,7 +9445,8 @@ output file itself.
 @cindex creating output file, avoiding
 Do not create the output file; the output file must already exist.
 
-The @samp{excl} and @samp{nocreat} conversions are mutually exclusive.
+The @samp{excl} and @samp{nocreat} conversions are mutually exclusive,
+and are GNU extensions to POSIX.
 
 @item notrunc
 @opindex notrunc
@@ -9421,6 +9464,7 @@ Continue after read errors.
 Synchronize output data just before finishing,
 even if there were write errors.
 This forces a physical write of output data.
+This conversion is a GNU extension to POSIX.
 
 @item fsync
 @opindex fsync
@@ -9428,6 +9472,7 @@ This forces a physical write of output data.
 Synchronize output data and metadata just before finishing,
 even if there were write errors.
 This forces a physical write of output data and metadata.
+This conversion is a GNU extension to POSIX.
 
 @end table
 
@@ -9441,8 +9486,7 @@ argument(s).  (No spaces around any comma(s).)
 Access the output file using the flags specified by the @var{flag}
 argument(s).  (No spaces around any comma(s).)
 
-Here are the flags.  Not every flag is supported on every operating
-system.
+Here are the flags.
 
 @table @samp
 
@@ -9606,7 +9650,8 @@ This flag can be used only with @code{oflag}.
 
 @end table
 
-These flags are not supported on all systems, and @samp{dd} rejects
+These flags are all GNU extensions to POSIX.
+They are not supported on all systems, and @samp{dd} rejects
 attempts to use them when they are not supported.  When reading from
 standard input or writing to standard output, the @samp{nofollow} and
 @samp{noctty} flags should not be specified, and the other flags
@@ -9615,11 +9660,20 @@ affected file descriptors, even after @command{dd} exits.
 
 @end table
 
+The behavior of @command{dd} is unspecified if operands other than
+@samp{conv=}, @samp{iflag=}, @samp{oflag=}, and @samp{status=} are
+specified more than once.
+
 @cindex multipliers after numbers
 The numeric-valued strings above (@var{n} and @var{bytes})
+are unsigned decimal integers that
 can be followed by a multiplier: @samp{b}=512, @samp{c}=1,
 @samp{w}=2, @samp{x@var{m}}=@var{m}, or any of the
 standard block size suffixes like @samp{k}=1024 (@pxref{Block size}).
+These multipliers are GNU extensions to POSIX, except that
+POSIX allows @var{bytes} to be followed by @samp{k}, @samp{b}, and
+@samp{x@var{m}}.
+Block sizes (i.e., specified by @var{bytes} strings) must be nonzero.
 
 Any block size you specify via @samp{bs=}, @samp{ibs=}, @samp{obs=}, @samp{cbs=}
 should not be too large---values larger than a few megabytes
-- 
2.32.0

Subject: `dd` seek/skip which way is up?
From: Bela Lubkin <[email-masked]>
To: [email-masked]
Date: Sun, 3 Jan 2021 19:03:17 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

Hello --

I constantly confuse 'seek=N' and 'skip=N'.  The two words have no natural
affinity to one I/O direction or the other.

I previously encountered a `dd` implementation which also accepted
'oseek=N' and 'iseek=N', which I found far more natural and easy to
remember.

Here is a small patch implementing the same for coreutils `dd`.  Patch is
against just-gotten git tree; `dd --version` reports 'dd (coreutils)
8.32.101-ebf2c-dirty'.  (I probably got the .texi formatting wrong; please
repair as needed.)

While in the area, I slightly improved some of the help (and therefore man
page).

>Bela<

========================================================================

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index e9dd21c4e..417857c5e 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -9100,6 +9100,15 @@ Skip @var{n} @samp{obs}-byte blocks in the output
file before copying.
 if @samp{oflag=seek_bytes} is specified, @var{n} is interpreted
 as a byte count rather than a block count.

+@item oseek
+@item iseek
+@opindex oseek
+@opindex iseek
+As the distinction between @samp{seek} and @samp{skip}
+is easily confused, @samp{oseek} is accepted as an alias
+for @samp{seek}; @samp{iseek} for @samp{skip}.
+Do not use these in scripts, as this reduces compatibility.
+
 @item count=@var{n}
 @opindex count
 Copy @var{n} @samp{ibs}-byte blocks from the input file, instead
@@ -9457,6 +9466,15 @@ rather than a block count, which allows specifying
 an offset that is not a multiple of the I/O block size.
 This flag can be used only with @code{oflag}.

+@item oseek_bytes
+@item iseek_bytes
+@opindex oseek_bytes
+@opindex iseek_bytes
+As the distinction between @samp{seek_bytes} and @samp{skip_bytes}
+is easily confused, @samp{oseek_bytes} is accepted as an alias
+for @samp{seek_bytes}; @samp{iseek_bytes} for @samp{skip_bytes}.
+Do not use these in scripts, as this reduces compatibility.
+
 @end table

 These flags are not supported on all systems, and @samp{dd} rejects
diff --git a/src/dd.c b/src/dd.c
index 9152a2550..a187522c2 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -381,7 +381,9 @@ static struct symbol_value const flags[] =
   {"fullblock",   O_FULLBLOCK}, /* Accumulate full blocks from input.  */
   {"count_bytes", O_COUNT_BYTES},
   {"skip_bytes",  O_SKIP_BYTES},
+  {"iseek_bytes", O_SKIP_BYTES},
   {"seek_bytes",  O_SEEK_BYTES},
+  {"oseek_bytes", O_SEEK_BYTES},
   {"", 0}
 };

@@ -571,7 +573,7 @@ Copy a file, converting and formatting according to the
operands.\n\
                   overrides ibs and obs\n\
   cbs=BYTES       convert BYTES bytes at a time\n\
   conv=CONVS      convert the file as per the comma separated symbol
list\n\
-  count=N         copy only N input blocks\n\
+  count=N         copy only N input blocks (bytes if iflag=count_bytes)\n\
   ibs=BYTES       read up to BYTES bytes at a time (default: 512)\n\
 "), stdout);
       fputs (_("\
@@ -580,8 +582,8 @@ Copy a file, converting and formatting according to the
operands.\n\
   obs=BYTES       write BYTES bytes at a time (default: 512)\n\
   of=FILE         write to FILE instead of stdout\n\
   oflag=FLAGS     write as per the comma separated symbol list\n\
-  seek=N          skip N obs-sized blocks at start of output\n\
-  skip=N          skip N ibs-sized blocks at start of input\n\
+  seek=N (or oseek=N)  skip N obs-sized blocks at start of output (bytes
if oflag=seek_bytes)\n\
+  skip=N (or iseek=N)  skip N ibs-sized blocks at start of input (bytes if
iflag=skip_bytes)\n\
   status=LEVEL    The LEVEL of information to print to stderr;\n\
                   'none' suppresses everything but error messages,\n\
                   'noxfer' suppresses the final transfer statistics,\n\
@@ -660,10 +662,10 @@ Each FLAG symbol may be:\n\
         fputs (_("  count_bytes  treat 'count=N' as a byte count (iflag
only)\n\
 "), stdout);
       if (O_SKIP_BYTES)
-        fputs (_("  skip_bytes  treat 'skip=N' as a byte count (iflag
only)\n\
+        fputs (_("  skip_bytes (or iseek_bytes)  treat 'skip=N' as a byte
count (iflag only)\n\
 "), stdout);
       if (O_SEEK_BYTES)
-        fputs (_("  seek_bytes  treat 'seek=N' as a byte count (oflag
only)\n\
+        fputs (_("  seek_bytes (or oseek_bytes)  treat 'seek=N' as a byte
count (oflag only)\n\
 "), stdout);

       {
@@ -1554,9 +1556,11 @@ scanargs (int argc, char *const *argv)
               n_max = SIZE_MAX;
               conversion_blocksize = n;
             }
-          else if (operand_is (name, "skip"))
+          else if (operand_is (name, "skip") ||
+                    operand_is (name, "iseek"))
             skip = n;
-          else if (operand_is (name, "seek"))
+          else if (operand_is (name, "seek") ||
+                   operand_is (name, "oseek"))
             seek = n;
           else if (operand_is (name, "count"))
             count = n;