diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2022-11-19 19:04:36 -0800 |
---|---|---|
committer | Pádraig Brady <P@draigBrady.com> | 2023-01-02 23:30:07 +0000 |
commit | 6c343a55745a37a1d4d20f39a427170c37044a65 (patch) | |
tree | c441b1b2725059feb95005eff8468d5c6ac8d320 /src/ioblksize.h | |
parent | 01755d36e7389aef48f712193976205e7381115b (diff) | |
download | coreutils-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.h | 28 |
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); } |