Subject: bug#13391: dd silently ignores lseek error

From: Pádraig Brady <[email-masked]>
Date: Thu, 17 Jan 2013 11:56:44 +0000

On 01/09/2013 10:08 AM, Pádraig Brady wrote:
> On 01/09/2013 07:36 AM, Bernhard Voelker wrote:
>> why not check input_seekable where it is set - ~60 lines above?
>
> I was trying to keep related code together.
>
>
> Thanks for all the reviews, which I've fixed locally.
>
> To provide an argument for why this shouldn't go in,
> the following is equivalent:
>
>    python -c 'import os; os.lseek(0,0,os.SEEK_CUR)' 2>/dev/null < /the/file &&
>    dd if=/the/file ...
>
> So if the need is esoteric enough the above might suffice?
>
> I'll leave it stew for  a while longer.

I'm still 50:50 give the above,
so I'll not push until I get more positive feedback.
The latest is attached for reference.

thanks,
Pádraig.
>From fbbd753db704357002f5e99b05e47638a0f62a7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Wed, 9 Jan 2013 00:42:38 +0000
Subject: [PATCH] dd: add [io]flag=seekable to verify file support for lseek

* src/dd.c: Add the new O_SEEKABLE flag.
(main): Verify lseek() works if O_SEEKABLE is set.
(usage): Describe the new flag.
* tests/dd/misc.sh: Augment the test for the new options.
* doc/coreutils.texi (dd invocation): Describe the new option.
* cfg.mk (sc_dd_O_FLAGS): Add O_SEEKABLE to the list of private
flags with a single underscore.
* NEWS: Mention the new feature.
Suggested by Paul Eggert in http://bugs.gnu.org/13391
---
 NEWS               |    3 +++
 cfg.mk             |    2 +-
 doc/coreutils.texi |    6 ++++++
 src/dd.c           |   21 ++++++++++++++++++++-
 tests/dd/misc.sh   |    8 ++++++++
 5 files changed, 38 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 754b2cf..9c415ec 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,9 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** New features
 
+  dd now accepts 'iflag=seekable' and 'oflag=seekable' to verify that the
+  corresponding files support the seek operation.
+
   df now accepts the --output[=FIELD_LIST] option to define the list of columns
   to include in the output, or all available columns if the FIELD_LIST is
   omitted.  Note this enables df to output both block and inode fields together.
diff --git a/cfg.mk b/cfg.mk
index fbc64b4..bf43861 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -57,7 +57,7 @@ _hv_file ?= $(srcdir)/tests/misc/help-version
 dd = $(srcdir)/src/dd.c
 sc_dd_O_FLAGS:
 	@rm -f $@.1 $@.2
-	@{ echo O_FULLBLOCK; echo O_NOCACHE;				\
+	@{ echo O_FULLBLOCK; echo O_NOCACHE; echo O_SEEKABLE;		\
 	  perl -nle '/^ +\| (O_\w*)$$/ and print $$1' $(dd); } | sort > $@.1
 	@{ echo O_NOFOLLOW; perl -nle '/{"[a-z]+",\s*(O_\w+)},/ and print $$1' \
 	  $(dd); } | sort > $@.2
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 45a4b3d..9ee9d86 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -8399,6 +8399,12 @@ 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 seekable
+@opindex seekable
+Fail unless the file is seekable.
+Note @samp{skip=} or @samp{seek=} don't suffice for this check,
+as they will resort to reading, to skip over data.
+
 @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 ef5664b..64015b0 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -326,7 +326,10 @@ enum
     O_SKIP_BYTES = FFS_MASK (v4),
     v5 = v4 ^ O_SKIP_BYTES,
 
-    O_SEEK_BYTES = FFS_MASK (v5)
+    O_SEEK_BYTES = FFS_MASK (v5),
+    v6 = v5 ^ O_SEEK_BYTES,
+
+    O_SEEKABLE = FFS_MASK (v6)
   };
 
 /* Ensure that we got something.  */
@@ -335,6 +338,7 @@ verify (O_NOCACHE != 0);
 verify (O_COUNT_BYTES != 0);
 verify (O_SKIP_BYTES != 0);
 verify (O_SEEK_BYTES != 0);
+verify (O_SEEKABLE != 0);
 
 #define MULTIPLE_BITS_SET(i) (((i) & ((i) - 1)) != 0)
 
@@ -344,6 +348,7 @@ verify ( ! MULTIPLE_BITS_SET (O_NOCACHE));
 verify ( ! MULTIPLE_BITS_SET (O_COUNT_BYTES));
 verify ( ! MULTIPLE_BITS_SET (O_SKIP_BYTES));
 verify ( ! MULTIPLE_BITS_SET (O_SEEK_BYTES));
+verify ( ! MULTIPLE_BITS_SET (O_SEEKABLE));
 
 /* Flags, for iflag="..." and oflag="...".  */
 static struct symbol_value const flags[] =
@@ -366,6 +371,7 @@ static struct symbol_value const flags[] =
   {"count_bytes", O_COUNT_BYTES},
   {"skip_bytes",  O_SKIP_BYTES},
   {"seek_bytes",  O_SEEK_BYTES},
+  {"seekable",    O_SEEKABLE},
   {"",		0}
 };
 
@@ -619,6 +625,8 @@ Each FLAG symbol may be:\n\
       if (O_SEEK_BYTES)
         fputs (_("  seek_bytes  treat 'seek=N' as a byte count (oflag only)\n\
 "), stdout);
+      if (O_SEEKABLE)
+        fputs (_("  seekable  fail unless seekable\n"), stdout);
 
       {
         char const *siginfo_name = (SIGINFO == SIGUSR1 ? "USR1" : "INFO");
@@ -2270,6 +2278,17 @@ main (int argc, char **argv)
         }
     }
 
+  if (! input_seekable && (input_flags & O_SEEKABLE))
+    {
+      error (EXIT_FAILURE, input_seek_errno, _("input is not seekable %s"),
+             quote (input_file));
+    }
+  if ((output_flags & O_SEEKABLE) && lseek (STDOUT_FILENO, 0, SEEK_CUR) < 0)
+    {
+      error (EXIT_FAILURE, errno, _("output is not seekable %s"),
+             quote (output_file));
+    }
+
   start_time = gethrxtime ();
 
   exit_status = dd_copy ();
diff --git a/tests/dd/misc.sh b/tests/dd/misc.sh
index b9ad31a..cc84457 100755
--- a/tests/dd/misc.sh
+++ b/tests/dd/misc.sh
@@ -102,4 +102,12 @@ compare err_ok err || fail=1
 
 test $fail -eq 0 && fail=$warn
 
+# check iflag=seekable and oflag=seekable
+dd if=/dev/null of=/dev/null iflag=seekable oflag=seekable count=0 || fail=1
+echo 'pipe' | dd of=/dev/null iflag=seekable count=0 && fail=1
+dd if=/dev/null oflag=seekable count=0 status=none 2>err | cat
+echo "dd: output is not seekable 'standard output': Illegal seek" > err_ok ||
+  framework_failure_
+compare err_ok err || fail=1
+
 Exit $fail
-- 
1.7.6.4