On 2026-06-19 00:54, Paul Eggert wrote:
from dd's point of view, it asked for the time and got the wrong time from the kernel.

On looking into this a bit more, my guess is that the underlying kernel and/or firmware bug is not likely to be fixed any time soon, so I installed the attached workaround to coreutils. Please give it a try.

From 2e7707c23e1a05d12dbe41676b3759ef4f74a812 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Sat, 20 Jun 2026 12:14:55 -0700
Subject: [PATCH] dd: work around Linux kernel CLOCK_MONOTONIC bug
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem reported by Sick Pigs (bug#81269).
Ordinarily we’d say “fix the kernel or the hardware”,
but this bug seems widespread and unlikely to be fixed any time soon,
and the workaround is not a lot of trouble.
* src/dd.c (real_start_time): New static var.
(getrealxtime): New static function.
(print_xfer_stats): Use the max of elapsed monotonic and elapsed
real time.
---
 NEWS     |  3 +++
 src/dd.c | 26 ++++++++++++++++++++------
 2 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/NEWS b/NEWS
index 4ffd690de..b639141a9 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   will correctly match the last delimiter specified.
   [bug introduced with multi-byte support in coreutils-9.11]
 
+  'dd conv=fsync' no longer outputs bogus stats if the monotonic clock freezes.
+  [This works around a too-common bug in the Linux kernel.]
+
   'head' and 'tail' now quote names in file headers when needed.
   [This bug was present in "the beginning".]
 
diff --git a/src/dd.c b/src/dd.c
index 26382a233..9512372f6 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -181,8 +181,8 @@ static intmax_t w_bytes = 0;
 /* Last-reported number of bytes written, or negative if never reported.  */
 static intmax_t reported_w_bytes = -1;
 
-/* Time that dd started.  */
-static xtime_t start_time;
+/* Time that dd started, in both monotonic and real time.  */
+static xtime_t start_time, real_start_time;
 
 /* Next time to report periodic progress.  */
 static xtime_t next_time;
@@ -740,12 +740,20 @@ abbreviation_lacks_prefix (char const *message)
   return message[strlen (message) - 2] == ' ';
 }
 
+static xtime_t
+getrealxtime (void)
+{
+  struct timespec now = current_timespec ();
+  return xtime_make (now.tv_sec, now.tv_nsec);
+}
+
 /* Print transfer statistics.  */
 
 static void
 print_xfer_stats (xtime_t progress_time)
 {
   xtime_t now = progress_time ? progress_time : gethrxtime ();
+  xtime_t real_now = getrealxtime ();
   static char const slash_s[] = "/s";
   char hbuf[3][LONGEST_HUMAN_READABLE + sizeof slash_s];
   double delta_s;
@@ -754,14 +762,19 @@ print_xfer_stats (xtime_t progress_time)
   char const *iec = human_readable (w_bytes, hbuf[1],
                                     human_opts | human_base_1024, 1, 1);
 
-  /* Use integer arithmetic to compute the transfer rate,
-     since that makes it easy to use SI abbreviations.  */
+  /* Use integer arithmetic to compute the transfer rate, since that
+     makes it easy to use SI abbreviations.  Although we want the
+     elapsed monotonic time, too often the Linux kernel stops its
+     monotonic clocks during an fsync/fdatasync (coreutils bug#81269),
+     and perhaps other kernels have similar bugs.  So take the maximum
+     of the elapsed monotonic and real times, even though this can
+     overestimate if the real clock was set during our run.  */
   char *bpsbuf = hbuf[2];
   int bpsbufsize = sizeof hbuf[2];
-  if (start_time < now)
+  xtime_t delta_xtime = MAX (now - start_time, real_now - real_start_time);
+  if (0 < delta_xtime)
     {
       double XTIME_PRECISIONe0 = XTIME_PRECISION;
-      xtime_t delta_xtime = now - start_time;
       delta_s = delta_xtime / XTIME_PRECISIONe0;
       bytes_per_second = human_readable (w_bytes, bpsbuf, human_opts,
                                          XTIME_PRECISION, delta_xtime);
@@ -2537,6 +2550,7 @@ main (int argc, char **argv)
     }
 
   start_time = gethrxtime ();
+  real_start_time = getrealxtime ();
   next_time = start_time + XTIME_PRECISION;
 
   int copy_status = dd_copy ();
-- 
2.53.0

Reply via email to