compress: check cursor position in should_sparse()

Otherwise, the result will be broken, as skipped area by lseek() is not
cleared.
This commit is contained in:
Yu Watanabe
2026-08-27 04:36:57 +09:00
parent f577c38a52
commit d9b6e8663c
2 changed files with 39 additions and 5 deletions
+16 -3
View File
@@ -1632,21 +1632,34 @@ int compress_stream(
return 0;
}
/* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on
* regular files without O_APPEND (O_APPEND ignores lseek position, which would collapse holes). */
static int should_sparse(int fd) {
struct stat st;
assert(fd >= 0);
/* Determine whether sparse writes should be used for this fd. Sparse writes are only safe on regular
* files, without O_APPEND (which ignores the lseek position, collapsing any holes we try to create),
* and only when starting from a position at or beyond the current end of file (otherwise the skipped
* ranges would overlap pre-existing data instead of being genuinely unwritten). */
if (fstat(fd, &st) < 0)
return -errno;
if (!S_ISREG(st.st_mode))
return false;
int flags = fcntl(fd, F_GETFL);
if (flags < 0)
return -errno;
return S_ISREG(st.st_mode) && !FLAGS_SET(flags, O_APPEND);
if (FLAGS_SET(flags, O_APPEND))
return false;
off_t pos = lseek(fd, 0, SEEK_CUR);
if (pos < 0)
return -errno;
return pos >= st.st_size;
}
/* After sparse decompression, set the file size to the current position to account for
+23 -2
View File
@@ -433,8 +433,8 @@ static void compare_fd(int fda, int fdb) {
uint8_t bufa[4096], bufb[4096];
size_t to_read = MIN((size_t) (sta.st_size - offset), sizeof(bufa));
ASSERT_OK_EQ(loop_read(fda, bufa, to_read, true), (ssize_t) to_read);
ASSERT_OK_EQ(loop_read(fdb, bufb, to_read, true), (ssize_t) to_read);
ASSERT_OK(loop_read_exact(fda, bufa, to_read, /* do_poll= */ false));
ASSERT_OK(loop_read_exact(fdb, bufb, to_read, /* do_poll= */ false));
ASSERT_EQ(memcmp(bufa, bufb, to_read), 0);
offset += to_read;
}
@@ -499,6 +499,27 @@ TEST(decompress_stream_sparse) {
else
log_debug("Filesystem does not support holes, skipping sparsity check");
/* Check if the decompressor clears previous data.
* The original data is 4K data, 64K zeros, 4K data, 64K zeros.
* Now, fill the file with 4K zeros, 64K data, 4K zeros, 64K data. */
log_debug("/* testing %s sparse decompression to an existing file */", compression_to_string(c));
ASSERT_OK_ERRNO(ftruncate(decompressed, 0));
ASSERT_OK_ERRNO(ftruncate(decompressed, 4096));
ASSERT_OK_ERRNO(lseek(decompressed, 4096, SEEK_SET));
for (unsigned i = 0; i < 16; i++)
ASSERT_OK(loop_write(decompressed, data_block, sizeof(data_block)));
ASSERT_OK_ERRNO(ftruncate(decompressed, 4096 + 16 * sizeof(data_block) + 4096));
ASSERT_OK_ERRNO(lseek(decompressed, 4096 + 16 * sizeof(data_block) + 4096, SEEK_SET));
for (unsigned i = 0; i < 16; i++)
ASSERT_OK(loop_write(decompressed, data_block, sizeof(data_block)));
ASSERT_OK_ERRNO(lseek(compressed, 0, SEEK_SET));
ASSERT_OK_ERRNO(lseek(decompressed, 0, SEEK_SET));
ASSERT_OK_ZERO(decompress_stream(c, compressed, decompressed, st_src.st_size));
compare_fd(src, decompressed);
/* Test all-zeros input: entire output should be a hole */
log_debug("/* testing %s sparse decompression of all-zeros */", compression_to_string(c));
{