mergerfs/src/fuse_release.cpp
Antonio S. Musumeci 98648ae44b FUSE: require valid file/dir handle; drop open_files fallback
Several FUSE ops fell back to looking up a FileInfo via
state.open_files when the kernel-provided fh was zero, returning
-ENOENT if the lookup failed. This path papered over handle-tracking
bugs and added cost on every call. Use the handle directly and
return -EBADF when it is invalid - the kernel should always hand us
a valid fh for an open resource.

Also simplify FileInfo::from_fh / DirInfo::from_fh and their to_fh
helpers: callers now handle the null-pointer case explicitly so the
ASSERT_NOT_NULL checks are redundant.

Covers fallocate, fchmod, fchown, fgetattr, flock, ftruncate,
futimens, read, release, releasedir, write, statx, ioctl (file +
dir), readdir (sequential, concurrent-on-read,
concurrent-on-search), and the passthrough fast paths in
create/open.
2026-04-18 13:47:23 -05:00

128 lines
3.7 KiB
C++

/*
ISC License
Copyright (c) 2026, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
RELEASE OF SHARED AND PER-HANDLE FILEINFO OBJECTS
=================================================
The open_files map stores one canonical FileInfo per nodeid together with a
ref_count and optional passthrough backing_id. Overlapping opens of the same
file increment that ref_count but still allocate their own FileInfo objects
with duplicated file descriptors for the individual FUSE handles.
That means release() must handle two ownership cases:
- the canonical FileInfo stored in open_files
- a per-handle FileInfo that exists only in fuse_file_info_t.fh
On non-final closes, secondary handle FileInfo objects are freed
immediately after the ref_count decrement while the canonical map entry stays
alive.
On the final close, the map entry is erased atomically and cleanup happens
after the erase_if() callback returns so close() and delete do not execute
while the map bucket is locked. If the final handle is different from the
canonical map-owned FileInfo, both objects must be released.
*/
#include "fuse_release.hpp"
#include "state.hpp"
#include "config.hpp"
#include "fileinfo.hpp"
#include "fs_close.hpp"
#include "fs_fadvise.hpp"
#include "fuse_passthrough.hpp"
#include "fuse.h"
static
int
_release(cu64 nodeid_,
FileInfo *fi_,
const bool dropcacheonclose_)
{
// The double fadvise is necessary insofar as according to nocache
// author (https://github.com/Feh/nocache#limitations) the first one
// doesn't always work due to timing issues.
if(dropcacheonclose_)
{
fs::fadvise_dontneed(fi_->fd);
fs::fadvise_dontneed(fi_->fd);
}
FUSE::release(nodeid_,fi_);
return 0;
}
void
FUSE::release(cu64 nodeid_,
FileInfo * const fi_)
{
auto &of = state.open_files;
int backing_id = INVALID_BACKING_ID;
FileInfo *canonical_fi = nullptr;
FileInfo *fh_fi_to_free = nullptr;
of.erase_if(nodeid_,
[&](auto &v_)
{
v_.second.ref_count--;
if(v_.second.ref_count > 0)
{
if(fi_ != v_.second.fi)
fh_fi_to_free = fi_;
return false;
}
canonical_fi = v_.second.fi;
backing_id = v_.second.backing_id;
if(fi_ != canonical_fi)
fh_fi_to_free = fi_;
return true;
});
FUSE::passthrough_close(backing_id);
if(fh_fi_to_free)
{
fs::close(fh_fi_to_free->fd);
delete fh_fi_to_free;
}
if(canonical_fi)
{
fs::close(canonical_fi->fd);
delete canonical_fi;
}
}
int
FUSE::release(const fuse_req_ctx_t *ctx_,
const fuse_file_info_t *ffi_)
{
FileInfo *fi = FileInfo::from_fh(ffi_->fh);
if(not fi)
return -EBADF;
return ::_release(ctx_->nodeid,
fi,
cfg.dropcacheonclose);
}