/* ISC License Copyright (c) 2026, Antonio SJ Musumeci 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); }