summaryrefslogtreecommitdiff
path: root/src/ioblksize.h
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2022-11-19 19:04:36 -0800
committerPádraig Brady <P@draigBrady.com>2023-01-02 23:30:07 +0000
commit6c343a55745a37a1d4d20f39a427170c37044a65 (patch)
treec441b1b2725059feb95005eff8468d5c6ac8d320 /src/ioblksize.h
parent01755d36e7389aef48f712193976205e7381115b (diff)
downloadcoreutils-6c343a55745a37a1d4d20f39a427170c37044a65.tar.gz
copy: fix possible over allocation for regular files
* bootstrap.conf (gnulib_modules): Add count-leading-zeros, which was already an indirect dependency, since ioblksize.h now uses it directly. * src/ioblksize.h: Include count-leading-zeros.h. (io_blksize): Treat impossible blocksizes as IO_BUFSIZE. When growing a blocksize to IO_BUFSIZE, keep it a multiple of the stated blocksize. Work around the ZFS performance bug. * NEWS: Mention the bug fix. Problem reported by Korn Andras at https://bugs.gnu.org/59382
Diffstat (limited to 'src/ioblksize.h')
-rw-r--r--src/ioblksize.h28
1 files changed, 27 insertions, 1 deletions
diff --git a/src/ioblksize.h b/src/ioblksize.h
index 7a56c1a51..80d7931ba 100644
--- a/src/ioblksize.h
+++ b/src/ioblksize.h
@@ -18,6 +18,7 @@
/* sys/stat.h and minmax.h will already have been included by system.h. */
#include "idx.h"
+#include "count-leading-zeros.h"
#include "stat-size.h"
@@ -75,8 +76,33 @@ enum { IO_BUFSIZE = 128 * 1024 };
static inline idx_t
io_blksize (struct stat sb)
{
+ /* Treat impossible blocksizes as if they were IO_BUFSIZE. */
+ idx_t blocksize = ST_BLKSIZE (sb) <= 0 ? IO_BUFSIZE : ST_BLKSIZE (sb);
+
+ /* Use a blocksize of at least IO_BUFSIZE bytes, keeping it a
+ multiple of the original blocksize. */
+ blocksize += (IO_BUFSIZE - 1) - (IO_BUFSIZE - 1) % blocksize;
+
+ /* For regular files we can ignore the blocksize if we think we know better.
+ ZFS sometimes understates the blocksize, because it thinks
+ apps stupidly allocate a block that large even for small files.
+ This misinformation can cause coreutils to use wrong-sized blocks.
+ Work around some of the performance bug by substituting the next
+ power of two when the reported blocksize is not a power of two. */
+ if (S_ISREG (sb.st_mode)
+ && blocksize & (blocksize - 1))
+ {
+ int leading_zeros = count_leading_zeros_ll (blocksize);
+ if (IDX_MAX < ULLONG_MAX || leading_zeros)
+ {
+ unsigned long long power = 1ull << (ULLONG_WIDTH - leading_zeros);
+ if (power <= IDX_MAX)
+ blocksize = power;
+ }
+ }
+
/* Don’t go above the largest power of two that fits in idx_t and size_t,
as that is asking for trouble. */
return MIN (MIN (IDX_MAX, SIZE_MAX) / 2 + 1,
- MAX (IO_BUFSIZE, ST_BLKSIZE (sb)));
+ blocksize);
}