On 09/03/2026 21:53, Laszlo Ersek wrote:
Hello,

with "dd" built from coreutils @ c5fb1c26de05 ("build: update gnulib
submodule to latest", 2026-03-09), with the gnulib submodule advanced to
d5f683434d1a ("doc: Fix documentation that was added today.",
2026-03-09):

    (
      set -e
      ulimit -S -f 1024
      trap '' XFSZ
      rm -f f
      src/dd if=/dev/urandom of=f bs=768K
    )

The above command produces the regular file "f" with 1024*1024 bytes in
it (as expected); however, "dd" prints the following to stderr:

    dd: error writing 'f': File too large
    2+0 records in
    1+0 records out
    1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.00625178 s, 168 MB/s

The third line ("1+0 records out") is incorrect. It should be "1+1
records out", because the second (final) write outputs 256*1024 bytes.
According to POSIX
<https://pubs.opengroup.org/onlinepubs/9799919799/utilities/dd.html#tag_20_31_11>,
that counts as a partial output block:

    On completion, /dd/ shall write the number of input and output blocks
    to standard error. In the POSIX locale the following formats shall be
    used:

    [...]

    "%u+%u records out\n", </number of whole output blocks/>, </number of
        partial output blocks/>

    [...] A partial output block is one that was written with fewer bytes
    than specified by the output block size. [...]

In dd_copy(), we have

    2321        if (ibuf == obuf)         /* If not C_TWOBUFS. */
    2322          {
    2323            size_t nwritten = iwrite (STDOUT_FILENO, obuf, 
n_bytes_read);
    2324            w_bytes += nwritten;
    2325            if (nwritten != n_bytes_read)
    2326              {
    2327                error (0, errno, _("error writing %s"), quoteaf 
(output_file));
    2328                return EXIT_FAILURE;
    2329              }
    2330            else if (n_bytes_read == input_blocksize)
    2331              w_full++;
    2332            else
    2333              w_partial++;
    2334            continue;
    2335          }

The first execution of this code outputs a full block ("nwritten" ==
768*1024 bytes). The second execution outputs "nwritten" == 256*1024
bytes, with "n_bytes_read" == 768*1024 bytes; yet "w_partial" is not
incremented.

The condition for reaching "w_partial++" is

    nwritten == n_bytes_read &&
    n_bytes_read != input_blocksize

which seems correct to me (it means we managed to output everything we
just read, but we couldn't read a full block -- and therefore we also
couldn't write a (-n identically sized) full block). However, a partial
write can also occur when the read was complete.

The write_output() function (which is not used in this reproducer)
counts partial output records differently:

    1275  static void
    1276  write_output (void)
    1277  {
    1278    size_t nwritten = iwrite (STDOUT_FILENO, obuf, output_blocksize);
    1279    w_bytes += nwritten;
    1280    if (nwritten != output_blocksize)
    1281      {
    1282        error (0, errno, _("writing to %s"), quoteaf (output_file));
    1283        if (nwritten != 0)
    1284          w_partial++;
    1285        quit (EXIT_FAILURE);
    1286      }
    1287    else
    1288      w_full++;
    1289    oc = 0;
    1290  }

If we detect a short -- but not entirely fruitless -- write in this
function, then we bump "w_partial" between error() and quit().


The symptom is also reproducible by populating a block device with "dd"
such that the last successful write has no room for a full (output)
block. (In that case, ENOSPC is reported, rather than EFBIG.) That's in
fact how I first encountered the problem; the "ignored SIGXFSZ + EFBIG
errno" method is just a more convenient reproducer.

Thanks for the excellent report and reproducer.
This looks to be a bug since the original implementation.
Attached is a fix and test.

Marking this as done.

thanks!
Padraig
From 1c2518816333d408eae11460ff577e78dc8ebf67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Wed, 11 Mar 2026 15:39:20 +0000
Subject: [PATCH] dd: always diagnose partial writes on write failure

* src/dd.c (dd_copy): Increment the partial write count upon failure.
* tests/dd/partial-write.sh: Add a new test.
* tests/local.mk: Reference the new test.
* NEWS: Mention the bug fix.
Fixes https://bugs.gnu.org/80583
---
 NEWS                      |  4 ++++
 src/dd.c                  |  2 ++
 tests/dd/partial-write.sh | 33 +++++++++++++++++++++++++++++++++
 tests/local.mk            |  1 +
 4 files changed, 40 insertions(+)
 create mode 100755 tests/dd/partial-write.sh

diff --git a/NEWS b/NEWS
index 97ed7e721..17532a0e5 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Bug fixes
 
+  'dd' now always diagnoses partial writes correctly upon write failure.
+  Previously it may have indicated that only full writes were performed.
+  [This bug was present in "the beginning".]
+
   'fold' will no longer truncate output when encountering 0xFF bytes.
   [bug introduced in coreutils-9.8]
 
diff --git a/src/dd.c b/src/dd.c
index 07b1c6445..5451b0ac9 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -2264,6 +2264,8 @@ dd_copy (void)
           if (nwritten != n_bytes_read)
             {
               diagnose (errno, _("error writing %s"), quoteaf (output_file));
+              if (nwritten != 0)
+                w_partial++;
               return EXIT_FAILURE;
             }
           else if (n_bytes_read == input_blocksize)
diff --git a/tests/dd/partial-write.sh b/tests/dd/partial-write.sh
new file mode 100755
index 000000000..7b2859472
--- /dev/null
+++ b/tests/dd/partial-write.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Ensure partial writes are properly diagnosed
+
+# Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ dd
+
+(
+  ulimit -S -f 1024 || skip_ 'unable to set file size ulimit'
+  trap '' XFSZ || skip_ 'unable to ignore SIGXFSZ'
+  dd if=/dev/zero of=f bs=768K count=2 2>err
+  echo $? > dd.ret
+)
+
+if test "$(cat dd.ret)" = 1; then
+  grep -F '+1 records out' err || { cat err; fail=1; }
+fi
+
+Exit $fail
diff --git a/tests/local.mk b/tests/local.mk
index 15766df16..f9cbf9a5d 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -612,6 +612,7 @@ all_tests =					\
   tests/dd/nocache_eof.sh			\
   tests/dd/nocache_fail.sh			\
   tests/dd/not-rewound.sh			\
+  tests/dd/partial-write.sh			\
   tests/dd/reblock.sh				\
   tests/dd/skip-seek.pl				\
   tests/dd/skip-seek2.sh			\
-- 
2.53.0

Reply via email to