Handle the case of posix_fadvise() returning ENOENT

This commit is contained in:
Andrea Mazzoleni 2026-04-04 20:02:30 +02:00
parent de598080c4
commit 35b5c46851
4 changed files with 81 additions and 9 deletions

View file

@ -49,7 +49,7 @@ STREAM* sopen_read(const char* file, int flags)
#if HAVE_POSIX_FADVISE
if (s->flags & STREAM_FLAGS_SEQUENTIAL) {
/* advise sequential access */
ret = posix_fadvise(s->handle[0].f, 0, 0, POSIX_FADV_SEQUENTIAL);
ret = posix_fadvise_wrapper(s->handle[0].f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret == ENOSYS) {
log_error(errno, "WARNING! fadvise() is not supported in this platform. Performance may not be optimal!\n");
/* call is not supported, like in armhf, see posix_fadvise manpage */
@ -137,7 +137,7 @@ int sopen_multi_file(STREAM* s, unsigned i, const char* file)
#if HAVE_POSIX_FADVISE
if (s->flags & STREAM_FLAGS_SEQUENTIAL) {
/* advise sequential access */
ret = posix_fadvise(f, 0, 0, POSIX_FADV_SEQUENTIAL);
ret = posix_fadvise_wrapper(f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret == ENOSYS) {
/* call is not supported, like in armhf, see posix_fadvise manpage */
ret = 0;

View file

@ -1357,7 +1357,7 @@ int advise_open(struct advise_struct* advise, int f)
/* advise noreuse access, this avoids to pollute the page cache */
/* supported from Linux Kernel 6.3 with this commit: https://github.com/torvalds/linux/commit/17e810229cb3068b692fa078bd9b3a6527e0866a */
ret = posix_fadvise(f, 0, 0, POSIX_FADV_NOREUSE);
ret = posix_fadvise_wrapper(f, 0, 0, POSIX_FADV_NOREUSE);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
@ -1370,7 +1370,7 @@ int advise_open(struct advise_struct* advise, int f)
}
/* advise sequential access, this doubles the read-ahead window size */
ret = posix_fadvise(f, 0, 0, POSIX_FADV_SEQUENTIAL);
ret = posix_fadvise_wrapper(f, 0, 0, POSIX_FADV_SEQUENTIAL);
if (ret == ENOSYS) {
/* call is not supported, like in armhf, see posix_fadvise manpage */
ret = 0;
@ -1550,8 +1550,11 @@ int advise_write(struct advise_struct* advise, int f, data_off_t offset, data_of
}
/* flush the data from the cache */
ret = posix_fadvise(f, discard_offset, discard_size, POSIX_FADV_DONTNEED);
/* for POSIX_FADV_DONTNEED we don't allow failure with ENOSYS */
ret = posix_fadvise_wrapper(f, discard_offset, discard_size, POSIX_FADV_DONTNEED);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */
@ -1578,8 +1581,11 @@ int advise_read(struct advise_struct* advise, int f, data_off_t offset, data_off
int ret;
/* flush the data from the cache */
ret = posix_fadvise(f, offset, size, POSIX_FADV_DONTNEED);
/* for POSIX_FADV_DONTNEED we don't allow failure with ENOSYS */
ret = posix_fadvise_wrapper(f, offset, size, POSIX_FADV_DONTNEED);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
errno = ret; /* posix_fadvise return the error code */

View file

@ -14,6 +14,63 @@ int exit_success = 0;
int exit_failure = 1;
int exit_sync_needed = 2;
#if HAVE_POSIX_FADVISE
/**
* Wrapper around posix_fadvise() that handles ENOENT by converting it to
* ENOSYS.
*
* Why can posix_fadvise() return ENOENT?
*
* According to POSIX and the Linux kernel's standard fadvise64 implementation
* (mm/fadvise.c), posix_fadvise() should only return EBADF (bad file
* descriptor), ESPIPE (fd refers to a pipe or FIFO), or EINVAL (invalid
* advice value or arguments). ENOENT is never generated by the kernel's
* generic path.
*
* However, the VFS layer allows individual filesystems to provide their own
* .fadvise() method, which is called instead of (or in addition to) the
* generic implementation. Certain filesystem drivers can return ENOENT in
* this method for reasons that are completely unrelated to the file not
* existing (the file is clearly open, we have a valid fd):
*
* - NFS: A stale NFS inode can be revalidated against the server while
* fadvise is being processed. If the server-side file has been removed or
* renamed, the revalidation discovers the inode is gone and returns
* -ENOENT, even though the client still holds an open file descriptor.
*
* - FUSE-based filesystems (mergerfs, sshfs, rclone, etc.): The FUSE daemon
* receives the fadvise request and may forward it to an underlying backend.
* If that backend cannot find the file (e.g., the backing path has changed,
* the FUSE daemon is partially initialised, or the daemon translates the
* request and the target layer returns an error), the daemon may return
* ENOENT to the kernel.
*
* - Overlayfs (used by Docker, Podman, LXC): The overlayfs .fadvise()
* implementation delegates to the lower or upper layer. If the lookup
* through the overlay stack fails to find the real inode (e.g., the upper
* directory entry is absent and the lower copy is in a weird state), the
* delegation can return ENOENT.
*
* - Other network or virtual filesystems: Any out-of-tree or in-tree
* filesystem that implements .fadvise() and performs a path or inode
* lookup as part of processing the hint can propagate ENOENT if that
* internal lookup fails.
*
* Because posix_fadvise() is always an advisory hint, it never affects
* correctness, only performance, it is safe to treat unexpected errors,
* including ENOENT, as "not supported" (ENOSYS) and continue.
*/
int posix_fadvise_wrapper(int fd, off_t offset, off_t len, int advice)
{
int ret = posix_fadvise(fd, offset, len, advice);
if (ret == ENOENT)
return ENOSYS;
return ret;
}
#endif
int open_noatime(const char* file, int flags)
{
#ifdef O_NOATIME
@ -2054,7 +2111,11 @@ static int devup(dev_t device, const char* name)
#if HAVE_POSIX_FADVISE
/* clear cache */
ret = posix_fadvise(f, offset, 4096, POSIX_FADV_DONTNEED);
ret = posix_fadvise_wrapper(f, offset, 4096, POSIX_FADV_DONTNEED);
if (ret == ENOSYS) {
/* call is not supported */
ret = 0;
}
if (ret != 0) {
/* LCOV_EXCL_START */
close(f);

View file

@ -12,6 +12,11 @@
#define O_BINARY 0 /**< Not used in Unix. */
#define O_SEQUENTIAL 0 /**< In Unix posix_fadvise() shall be used. */
#if HAVE_POSIX_FADVISE
int posix_fadvise_wrapper(int fd, off_t offset, off_t len, int advice);
#endif
/**
* If nanoseconds are not supported, we report the special STAT_NSEC_INVALID value,
* to mark that it's undefined.