From d908031520fb3a1b847dde5c55b1f0d698d49edf Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Thu, 14 Dec 2023 20:53:35 -0800 Subject: [PATCH] Fix macOS posix_fallocate(). The fallback ftruncate() call was wrong, because posix_fallocate() will never shrink a file, but ftruncate() will. Also, if that failed, we didn't turn errno into a return code. Also the fcntl() was wrong. In my defense, here's the documentation: The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as follows: F_PEOFPOSMODE Allocate from the physical end of file. In this case, fst_length indicates the number of newly allocated bytes desired. F_VOLPOSMODE Allocate from the volume offset. I think the new version is right, though it's obviously a lot more conservative than the real posix_fallocate(), but I don't think it's possible to do better? Also add tests for some of the prior failures. Fixes #472. --- lib/portability.c | 11 ++++++----- tests/fallocate.test | 8 ++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100755 tests/fallocate.test diff --git a/lib/portability.c b/lib/portability.c index 5c791596..c747ddf0 100644 --- a/lib/portability.c +++ b/lib/portability.c @@ -380,18 +380,19 @@ int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev) } // As of 10.15, macOS offers an fcntl F_PREALLOCATE rather than fallocate() -// or posix_fallocate() calls. +// or posix_fallocate() calls. The fcntl only (as the name implies) +// pre-allocates, so we also need to ftruncate() afterwards. int posix_fallocate(int fd, off_t offset, off_t length) { - int e = errno, result; + int e = errno, result = 0; fstore_t f; f.fst_flags = F_ALLOCATEALL; f.fst_posmode = F_PEOFPOSMODE; - f.fst_offset = offset; - f.fst_length = length; + f.fst_offset = 0; + f.fst_length = offset + length; if (fcntl(fd, F_PREALLOCATE, &f) == -1) result = errno; - else result = ftruncate(fd, length); + else if (ftruncate(fd, maxof(offset+length, fdlength(fd)))) result = errno; errno = e; return result; } diff --git a/tests/fallocate.test b/tests/fallocate.test new file mode 100755 index 00000000..7f5134f7 --- /dev/null +++ b/tests/fallocate.test @@ -0,0 +1,8 @@ +#!/bin/bash + +#testing "name" "command" "result" "infile" "stdin" + +rm -f foo +testcmd 'simple' '-l 123 foo && stat -c %s foo' '123\n' '' '' +testcmd 'shorter' '-l 12 foo && stat -c %s foo' '123\n' '' '' +testcmd 'longer' '-o 200 -l 12 foo && stat -c %s foo' '212\n' '' '' -- 2.39.2