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
This commit is contained in:
Paul Eggert 2022-11-19 19:04:36 -08:00 committed by Pádraig Brady
parent 01755d36e7
commit 6c343a5574
3 changed files with 33 additions and 1 deletions

5
NEWS
View File

@ -13,6 +13,11 @@ GNU coreutils NEWS -*- outline -*-
'cp -rx / /mnt' no longer complains "cannot create directory /mnt/".
[bug introduced in coreutils-9.1]
cp, mv, and install avoid allocating too much memory, and possibly
triggering "memory exhausted" failures, on file systems like ZFS,
which can return varied file system I/O block size values for files.
[bug introduced in coreutils-6.0]
'mv --backup=simple f d/' no longer mistakenly backs up d/f to f~.
[bug introduced in coreutils-9.1]

View File

@ -59,6 +59,7 @@ gnulib_modules="
config-h
configmake
copy-file-range
count-leading-zeros
crypto/md5
crypto/sha1
crypto/sha256

View File

@ -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;
}
}
/* Dont 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);
}