blob: 4ee8699adb76c1c85850301b55b7fec1079c0016 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_LIB_STORAGE_VFS_CPP_PAGED_VNODE_H_
#define SRC_LIB_STORAGE_VFS_CPP_PAGED_VNODE_H_
#include <lib/async/cpp/wait.h>
#include <zircon/compiler.h>
#include "src/lib/storage/vfs/cpp/paged_vfs.h"
#include "src/lib/storage/vfs/cpp/vnode.h"
namespace fs {
// A Vnode that supports paged I/O.
//
// To implement, derive from this class and:
// - Implement Vnode::GetVmo().
// - Use PagedVnode::EnsureCreateVmo() to create the data mapping. This will create it in such a
// way that it's registered with the paging system for callbacks.
// - Do vmo().create_child() to clone the VMO backing this node.
// - Set the rights on the cloned VMO with the rights passed to GetVmo().
// - Populate the GetVmo() out parameter with the child VMO.
// - Implement VmoRead() to fill the VMO data when requested.
class PagedVnode : public Vnode {
public:
// Called by the paging system in response to a kernel request to fill data into this node's VMO.
//
// - On success, calls vfs()->SupplyPages() with the created data range.
// - On failure, calls vfs()->ReportPagerError() with the error information.
//
// The success or failure cases can happen synchronously (from within this call stack) or
// asynchronously in the future. Failure to report success or failure will hang the requesting
// process.
//
// Note that offset + length will be page-aligned so can extend beyond the end of the file.
//
// Since pager registration and unregistration is not synchronized between this class and the
// PagedVfs, this function can be called after the VMO has been unregistered and possibly
// destroyed. The implementation should be prepared to fail in these cases.
virtual void VmoRead(uint64_t offset, uint64_t length) = 0;
protected:
friend fbl::RefPtr<PagedVnode>;
explicit PagedVnode(PagedVfs* vfs);
~PagedVnode() override;
// This will be null if the Vfs has shut down. Since Vnodes are refcounted, it's possible for them
// to outlive their associated Vfs. Always null check before using. If there is no Vfs associated
// with this object, all operations are expected to fail.
PagedVfs* paged_vfs() __TA_REQUIRES_SHARED(mutex_) {
// Since we were constructed with a PagedVfs, we know it's safe to up-cast back to that.
return static_cast<PagedVfs*>(vfs());
}
// Returns the vmo associated with the paging system, if any. This will be a null handle if there
// is no paged vmo associated with this vnode.
//
// Populate with EnsureCreatePagedVmo(), free with FreeVmo().
//
// This vmo must not be mapped and then written to. Doing so will cause the kernel to "page in"
// the vmo which will reenter the filesystem to populate it, which is not what you want when
// writing to it.
//
// It is theoretically possible to read from this vmo (either mapped or using zx::vmo::read()) but
// the caller must be VERY careful and it is strongly recommended that you avoid this. Reading
// will cause the data to be paged in which will reenter the PagedVnode. Therefore, the mutex_
// must NOT be held during the read process. The caller's memory management structure must then
// guarantee that everything remain valid across this unlocked period (the vnode could be closed
// on another thread) or it must be able to handle the ensuing race conditions.
const zx::vmo& paged_vmo() const __TA_REQUIRES_SHARED(mutex_) { return paged_vmo_info_.vmo; }
// Returns true if there are clones of the VMO alive that have been given out.
bool has_clones() const __TA_REQUIRES_SHARED(mutex_) { return has_clones_; }
// Populates the paged_vmo() if necessary. Does nothing if it already exists. Access the created
// vmo with this class' paged_vmo() getter.
//
// When a mapping is requested, the derived class should call this function and then create a
// clone of this VMO with the desired flags. This class registers an observer for when the clone
// count drops to 0 to clean up the VMO. This means that if the caller doesn't create a clone the
// VMO will leak if it's registered as handling paging requests on the Vfs (which will keep this
// object alive).
zx::status<> EnsureCreatePagedVmo(uint64_t size) __TA_REQUIRES(mutex_);
// Releases the vmo_ and unregisters for paging notifications from the PagedVfs.
void FreePagedVmo() __TA_REQUIRES(mutex_);
// Implementors of this class can override this function to response to the event that there
// are no more clones of the vmo_. The default implementation calls FreePagedVmo().
virtual void OnNoPagedVmoClones() __TA_REQUIRES(mutex_);
private:
// Callback handler for the "no clones" message. Due to kernel message delivery race conditions
// there might actually be clones. This checks and calls OnNoPagedVmoClones() when needed.
void OnNoPagedVmoClonesMessage(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal)
__TA_EXCLUDES(mutex_);
// Starts the clone_watcher_ to observe the case of no vmo_ clones. The WaitMethod is called only
// once per "watch" call so this needs to be re-called after triggering.
//
// The vmo_ and paged_vfs() must exist.
void WatchForZeroVmoClones() __TA_REQUIRES(mutex_);
// The root VMO that paging happens out of for this vnode. VMOs that map the data into user
// processes will be children of this VMO.
PagedVfs::VmoCreateInfo paged_vmo_info_ __TA_GUARDED(mutex_);
// Set when there are clones of the vmo_.
bool has_clones_ __TA_GUARDED(mutex_) = false;
// Watches any clones of "paged_vmo()" provided to clients. Observes the ZX_VMO_ZERO_CHILDREN
// signal. See WatchForZeroChildren().
async::WaitMethod<PagedVnode, &PagedVnode::OnNoPagedVmoClonesMessage> clone_watcher_
__TA_GUARDED(mutex_);
};
} // namespace fs
#endif // SRC_LIB_STORAGE_VFS_CPP_PAGED_VNODE_H_