lisafs

package
v0.0.0-...-ba09d25 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2021 License: Apache-2.0, MIT Imports: 23 Imported by: 0

README

Replacing 9P

Background

The Linux filesystem model consists of the following key aspects (modulo mounts, which are outside the scope of this discussion):

  • A struct inode represents a "filesystem object", such as a directory or a regular file. "Filesystem object" is most precisely defined by the practical properties of an inode, such as an immutable type (regular file, directory, symbolic link, etc.) and its independence from the path originally used to obtain it.

  • A struct dentry represents a node in a filesystem tree. Semantically, each dentry is immutably associated with an inode representing the filesystem object at that position. (Linux implements optimizations involving reuse of unreferenced dentries, which allows their associated inodes to change, but this is outside the scope of this discussion.)

  • A struct file represents an open file description (hereafter FD) and is needed to perform I/O. Each FD is immutably associated with the dentry through which it was opened.

The current gVisor virtual filesystem implementation (hereafter VFS1) closely imitates the Linux design:

  • struct inode => fs.Inode

  • struct dentry => fs.Dirent

  • struct file => fs.File

gVisor accesses most external filesystems through a variant of the 9P2000.L protocol, including extensions for performance (walkgetattr) and for features not supported by vanilla 9P2000.L (flushf, lconnect). The 9P protocol family is inode-based; 9P fids represent a file (equivalently "file system object"), and the protocol is structured around alternatively obtaining fids to represent files (with walk and, in gVisor, walkgetattr) and performing operations on those fids.

In the sections below, a shared filesystem is a filesystem that is mutably accessible by multiple concurrent clients, such that a non-shared filesystem is a filesystem that is either read-only or accessible by only a single client.

Problems

Serialization of Path Component RPCs

Broadly speaking, VFS1 traverses each path component in a pathname, alternating between verifying that each traversed dentry represents an inode that represents a searchable directory and moving to the next dentry in the path.

In the context of a remote filesystem, the structure of this traversal means that - modulo caching - a path involving N components requires at least N-1 sequential RPCs to obtain metadata for intermediate directories, incurring significant latency. (In vanilla 9P2000.L, 2(N-1) RPCs are required: N-1 walk and N-1 getattr. We added the walkgetattr RPC to reduce this overhead.) On non-shared filesystems, this overhead is primarily significant during application startup; caching mitigates much of this overhead at steady state. On shared filesystems, where correct caching requires revalidation (requiring RPCs for each revalidated directory anyway), this overhead is consistently ruinous.

Inefficient RPCs

9P is not exceptionally economical with RPCs in general. In addition to the issue described above:

  • Opening an existing file in 9P involves at least 2 RPCs: walk to produce an unopened fid representing the file, and lopen to open the fid.

  • Creating a file also involves at least 2 RPCs: walk to produce an unopened fid representing the parent directory, and lcreate to create the file and convert the fid to an open fid representing the created file. In practice, both the Linux and gVisor 9P clients expect to have an unopened fid for the created file (necessitating an additional walk), as well as attributes for the created file (necessitating an additional getattr), for a total of 4 RPCs. (In a shared filesystem, where whether a file already exists can change between RPCs, a correct implementation of open(O_CREAT) would have to alternate between these two paths (plus clunking the temporary fid between alternations, since the nature of the fid differs between the two paths). Neither Linux nor gVisor implement the required alternation, so open(O_CREAT) without O_EXCL can spuriously fail with EEXIST on both.)

  • Closing (clunking) a fid requires an RPC. VFS1 issues this RPC asynchronously in an attempt to reduce critical path latency, but scheduling overhead makes this not clearly advantageous in practice.

  • read and readdir can return partial reads without a way to indicate EOF, necessitating an additional final read to detect EOF.

  • Operations that affect filesystem state do not consistently return updated filesystem state. In gVisor, the client implementation attempts to handle this by tracking what it thinks updated state "should" be; this is complex, and especially brittle for timestamps (which are often not arbitrarily settable). In Linux, the client implemtation invalidates cached metadata whenever it performs such an operation, and reloads it when a dentry corresponding to an inode with no valid cached metadata is revalidated; this is simple, but necessitates an additional getattr.

Dentry/Inode Ambiguity

As noted above, 9P's documentation tends to imply that unopened fids represent an inode. In practice, most filesystem APIs present very limited interfaces for working with inodes at best, such that the interpretation of unopened fids varies:

  • Linux's 9P client associates unopened fids with (dentry, uid) pairs. When caching is enabled, it also associates each inode with the first fid opened writably that references that inode, in order to support page cache writeback.

  • gVisor's 9P client associates unopened fids with inodes, and also caches opened fids in inodes in a manner similar to Linux.

  • The runsc fsgofer associates unopened fids with both "dentries" (host filesystem paths) and "inodes" (host file descriptors); which is used depends on the operation invoked on the fid.

For non-shared filesystems, this confusion has resulted in correctness issues that are (in gVisor) currently handled by a number of coarse-grained locks that serialize renames with all other filesystem operations. For shared filesystems, this means inconsistent behavior in the presence of concurrent mutation.

Design

Almost all Linux filesystem syscalls describe filesystem resources in one of two ways:

  • Path-based: A filesystem position is described by a combination of a starting position and a sequence of path components relative to that position, where the starting position is one of:

    • The VFS root (defined by mount namespace and chroot), for absolute paths

    • The VFS position of an existing FD, for relative paths passed to *at syscalls (e.g. statat)

    • The current working directory, for relative paths passed to non-*at syscalls and *at syscalls with AT_FDCWD

  • File-description-based: A filesystem object is described by an existing FD, passed to a f* syscall (e.g. fstat).

Many of our issues with 9P arise from its (and VFS') interposition of a model based on inodes between the filesystem syscall API and filesystem implementations. We propose to replace 9P with a protocol that does not feature inodes at all, and instead closely follows the filesystem syscall API by featuring only path-based and FD-based operations, with minimal deviations as necessary to ameliorate deficiencies in the syscall interface (see below). This approach addresses the issues described above:

  • Even on shared filesystems, most application filesystem syscalls are translated to a single RPC (possibly excepting special cases described below), which is a logical lower bound.

  • The behavior of application syscalls on shared filesystems is straightforwardly predictable: path-based syscalls are translated to path-based RPCs, which will re-lookup the file at that path, and FD-based syscalls are translated to FD-based RPCs, which use an existing open file without performing another lookup. (This is at least true on gofers that proxy the host local filesystem; other filesystems that lack support for e.g. certain operations on FDs may have different behavior, but this divergence is at least still predictable and inherent to the underlying filesystem implementation.)

Note that this approach is only feasible in gVisor's next-generation virtual filesystem (VFS2), which does not assume the existence of inodes and allows the remote filesystem client to translate whole path-based syscalls into RPCs. Thus one of the unavoidable tradeoffs associated with such a protocol vs. 9P is the inability to construct a Linux client that is performance-competitive with gVisor.

File Permissions

Many filesystem operations are side-effectual, such that file permissions must be checked before such operations take effect. The simplest approach to file permission checking is for the sentry to obtain permissions from the remote filesystem, then apply permission checks in the sentry before performing the application-requested operation. However, this requires an additional RPC per application syscall (which can't be mitigated by caching on shared filesystems). Alternatively, we may delegate file permission checking to gofers. In general, file permission checks depend on the following properties of the accessor:

  • Filesystem UID/GID

  • Supplementary GIDs

  • Effective capabilities in the accessor's user namespace (i.e. the accessor's effective capability set)

  • All UIDs and GIDs mapped in the accessor's user namespace (which determine if the accessor's capabilities apply to accessed files)

We may choose to delay implementation of file permission checking delegation, although this is potentially costly since it doubles the number of required RPCs for most operations on shared filesystems. We may also consider compromise options, such as only delegating file permission checks for accessors in the root user namespace.

gVisor usually interprets symbolic link targets in its VFS rather than on the filesystem containing the symbolic link; thus e.g. a symlink to "/proc/self/maps" on a remote filesystem resolves to said file in the sentry's procfs rather than the host's. This implies that:

  • Remote filesystem servers that proxy filesystems supporting symlinks must check if each path component is a symlink during path traversal.

  • Absolute symlinks require that the sentry restart the operation at its contextual VFS root (which is task-specific and may not be on a remote filesystem at all), so if a remote filesystem server encounters an absolute symlink during path traversal on behalf of a path-based operation, it must terminate path traversal and return the symlink target.

  • Relative symlinks begin target resolution in the parent directory of the symlink, so in theory most relative symlinks can be handled automatically during the path traversal that encounters the symlink, provided that said traversal is supplied with the number of remaining symlinks before ELOOP. However, the new path traversed by the symlink target may cross VFS mount boundaries, such that it's only safe for remote filesystem servers to speculatively follow relative symlinks for side-effect-free operations such as stat (where the sentry can simply ignore results that are inapplicable due to crossing mount boundaries). We may choose to delay implementation of this feature, at the cost of an additional RPC per relative symlink (note that even if the symlink target crosses a mount boundary, the sentry will need to stat the path to the mount boundary to confirm that each traversed component is an accessible directory); until it is implemented, relative symlinks may be handled like absolute symlinks, by terminating path traversal and returning the symlink target.

The possibility of symlinks (and the possibility of a compromised sentry) means that the sentry may issue RPCs with paths that, in the absence of symlinks, would traverse beyond the root of the remote filesystem. For example, the sentry may issue an RPC with a path like "/foo/../..", on the premise that if "/foo" is a symlink then the resulting path may be elsewhere on the remote filesystem. To handle this, path traversal must also track its current depth below the remote filesystem root, and terminate path traversal if it would ascend beyond this point.

Path Traversal

Since path-based VFS operations will translate to path-based RPCs, filesystem servers will need to handle path traversal. From the perspective of a given filesystem implementation in the server, there are two basic approaches to path traversal:

  • Inode-walk: For each path component, obtain a handle to the underlying filesystem object (e.g. with open(O_PATH)), check if that object is a symlink (as described above) and that that object is accessible by the caller (e.g. with fstat()), then continue to the next path component (e.g. with openat()). This ensures that the checked filesystem object is the one used to obtain the next object in the traversal, which is intuitively appealing. However, while this approach works for host local filesystems, it requires features that are not widely supported by other filesystems.

  • Path-walk: For each path component, use a path-based operation to determine if the filesystem object currently referred to by that path component is a symlink / is accessible. This is highly portable, but suffers from quadratic behavior (at the level of the underlying filesystem implementation, the first path component will be traversed a number of times equal to the number of path components in the path).

The implementation should support either option by delegating path traversal to filesystem implementations within the server (like VFS and the remote filesystem protocol itself), as inode-walking is still safe, efficient, amenable to FD caching, and implementable on non-shared host local filesystems (a sufficiently common case as to be worth considering in the design).

Both approaches are susceptible to race conditions that may permit sandboxed filesystem escapes:

  • Under inode-walk, a malicious application may cause a directory to be moved (with rename) during path traversal, such that the filesystem implementation incorrectly determines whether subsequent inodes are located in paths that should be visible to sandboxed applications.

  • Under path-walk, a malicious application may cause a non-symlink file to be replaced with a symlink during path traversal, such that following path operations will incorrectly follow the symlink.

Both race conditions can, to some extent, be mitigated in filesystem server implementations by synchronizing path traversal with the hazardous operations in question. However, shared filesystems are frequently used to share data between sandboxed and unsandboxed applications in a controlled way, and in some cases a malicious sandboxed application may be able to take advantage of a hazardous filesystem operation performed by an unsandboxed application. In some cases, filesystem features may be available to ensure safety even in such cases (e.g. the new openat2() syscall), but it is not clear how to solve this problem in general. (Note that this issue is not specific to our design; rather, it is a fundamental limitation of filesystem sandboxing.)

Filesystem Multiplexing

A given sentry may need to access multiple distinct remote filesystems (e.g. different volumes for a given container). In many cases, there is no advantage to serving these filesystems from distinct filesystem servers, or accessing them through distinct connections (factors such as maximum RPC concurrency should be based on available host resources). Therefore, the protocol should support multiplexing of distinct filesystem trees within a single session. 9P supports this by allowing multiple calls to the attach RPC to produce fids representing distinct filesystem trees, but this is somewhat clunky; we propose a much simpler mechanism wherein each message that conveys a path also conveys a numeric filesystem ID that identifies a filesystem tree.

Alternatives Considered

Additional Extensions to 9P

There are at least three conceptual aspects to 9P:

  • Wire format: messages with a 4-byte little-endian size prefix, strings with a 2-byte little-endian size prefix, etc. Whether the wire format is worth retaining is unclear; in particular, it's unclear that the 9P wire format has a significant advantage over protobufs, which are substantially easier to extend. Note that the official Go protobuf implementation is widely known to suffer from a significant number of performance deficiencies, so if we choose to switch to protobuf, we may need to use an alternative toolchain such as gogo/protobuf (which is also widely used in the Go ecosystem, e.g. by Kubernetes).

  • Filesystem model: fids, qids, etc. Discarding this is one of the motivations for this proposal.

  • RPCs: Twalk, Tlopen, etc. In addition to previously-described inefficiencies, most of these are dependent on the filesystem model and therefore must be discarded.

FUSE

The FUSE (Filesystem in Userspace) protocol is frequently used to provide arbitrary userspace filesystem implementations to a host Linux kernel. Unfortunately, FUSE is also inode-based, and therefore doesn't address any of the problems we have with 9P.

virtio-fs

virtio-fs is an ongoing project aimed at improving Linux VM filesystem performance when accessing Linux host filesystems (vs. virtio-9p). In brief, it is based on:

  • Using a FUSE client in the guest that communicates over virtio with a FUSE server in the host.

  • Using DAX to map the host page cache into the guest.

  • Using a file metadata table in shared memory to avoid VM exits for metadata updates.

None of these improvements seem applicable to gVisor:

  • As explained above, FUSE is still inode-based, so it is still susceptible to most of the problems we have with 9P.

  • Our use of host file descriptors already allows us to leverage the host page cache for file contents.

  • Our need for shared filesystem coherence is usually based on a user requirement that an out-of-sandbox filesystem mutation is guaranteed to be visible by all subsequent observations from within the sandbox, or vice versa; it's not clear that this can be guaranteed without a synchronous signaling mechanism like an RPC.

Documentation

Overview

Package lisafs (LInux SAndbox FileSystem) defines the protocol for filesystem RPCs between an untrusted Sandbox (client) and a trusted filesystem server.

Index

Constants

View Source
const (
	// NoUID is a sentinel used to indicate no valid UID.
	NoUID UID = math.MaxUint32

	// NoGID is a sentinel used to indicate no valid GID.
	NoGID GID = math.MaxUint32
)

Variables

This section is empty.

Functions

func ChannelHandler

func ChannelHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

ChannelHandler handles the Channel RPC.

func CloseHandler

func CloseHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

CloseHandler handles the Close RPC.

func ConnectHandler

func ConnectHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

ConnectHandler handles the Connect RPC.

func ErrorHandler

func ErrorHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

ErrorHandler handles Error message.

func FAllocateHandler

func FAllocateHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FAllocateHandler handles the FAllocate RPC.

func FGetXattrHandler

func FGetXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FGetXattrHandler handles the FGetXattr RPC.

func FListXattrHandler

func FListXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FListXattrHandler handles the FListXattr RPC.

func FRemoveXattrHandler

func FRemoveXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FRemoveXattrHandler handles the FRemoveXattr RPC.

func FSetXattrHandler

func FSetXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FSetXattrHandler handles the FSetXattr RPC.

func FStatFSHandler

func FStatFSHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FStatFSHandler handles the FStatFS RPC.

func FStatHandler

func FStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FStatHandler handles the FStat RPC.

func FSyncHandler

func FSyncHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FSyncHandler handles the FSync RPC.

func FlushHandler

func FlushHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

FlushHandler handles the Flush RPC.

func Getdents64Handler

func Getdents64Handler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

Getdents64Handler handles the Getdents64 RPC.

func LinkAtHandler

func LinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

LinkAtHandler handles the LinkAt RPC.

func MaxMessageSize

func MaxMessageSize() uint32

MaxMessageSize is the recommended max message size that can be used by connections. Server implementations may choose to use other values.

func MkdirAtHandler

func MkdirAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

MkdirAtHandler handles the MkdirAt RPC.

func MknodAtHandler

func MknodAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

MknodAtHandler handles the MknodAt RPC.

func MountHandler

func MountHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

MountHandler handles the Mount RPC. Note that there can not be concurrent executions of MountHandler on a connection because the connection enforces that Mount is the first message on the connection. Only after the connection has been successfully mounted can other channels be created.

func NewClient

func NewClient(sock *unet.Socket, mountPath string) (*Client, *Inode, error)

NewClient creates a new client for communication with the server. It mounts the server and creates channels for fast IPC. NewClient takes ownership over the passed socket. On success, it returns the initialized client along with the root Inode.

func NoopMarshal

func NoopMarshal(b []byte) []byte

NoopMarshal is a noop implementation of marshal.Marshallable.MarshalBytes.

func NoopUnmarshal

func NoopUnmarshal(b []byte) ([]byte, bool)

NoopUnmarshal is a noop implementation of marshal.Marshallable.UnmarshalBytes.

func OpenAtHandler

func OpenAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

OpenAtHandler handles the OpenAt RPC.

func OpenCreateAtHandler

func OpenCreateAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

OpenCreateAtHandler handles the OpenCreateAt RPC.

func PReadHandler

func PReadHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

PReadHandler handles the PRead RPC.

func PWriteHandler

func PWriteHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

PWriteHandler handles the PWrite RPC.

func ReadLinkAtHandler

func ReadLinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

ReadLinkAtHandler handles the ReadLinkAt RPC.

func RenameAtHandler

func RenameAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

RenameAtHandler handles the RenameAt RPC.

func SetStatHandler

func SetStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

SetStatHandler handles the SetStat RPC.

func SymlinkAtHandler

func SymlinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

SymlinkAtHandler handles the SymlinkAt RPC.

func UnlinkAtHandler

func UnlinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

UnlinkAtHandler handles the UnlinkAt RPC.

func WalkHandler

func WalkHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

WalkHandler handles the Walk RPC.

func WalkStatHandler

func WalkStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

WalkStatHandler handles the WalkStat RPC.

Types

type ChannelResp

type ChannelResp struct {
	// contains filtered or unexported fields
}

ChannelResp is the response to the create channel request.

+marshal boundCheck

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client helps manage a connection to the lisafs server and pass messages efficiently. There is a 1:1 mapping between a Connection and a Client.

func (*Client) Close

func (c *Client) Close()

Close shuts down the main socket and waits for the watchdog to clean up.

func (*Client) CloseFDBatched

func (c *Client) CloseFDBatched(ctx context.Context, fd FDID)

CloseFDBatched either queues the passed FD to be closed or makes a batch RPC to close all the accumulated FDs-to-close.

func (*Client) IsSupported

func (c *Client) IsSupported(m MID) bool

IsSupported returns true if this connection supports the passed message.

func (*Client) NewFD

func (c *Client) NewFD(fd FDID) ClientFD

NewFD initializes a new ClientFD.

func (*Client) SndRcvMessage

func (c *Client) SndRcvMessage(m MID, payloadLen uint32, reqMarshal func(dst []byte) []byte, respUnmarshal func(src []byte) ([]byte, bool), respFDs []int) error

SndRcvMessage invokes reqMarshal to marshal the request onto the payload buffer, wakes up the server to process the request, waits for the response and invokes respUnmarshal with the response payload. respFDs is populated with the received FDs, extra fields are set to -1.

Note that the function arguments intentionally accept marshal.Marshallable functions like Marshal{Bytes/Unsafe} and Unmarshal{Bytes/Unsafe} instead of directly accepting the marshal.Marshallable interface. Even though just accepting marshal.Marshallable is cleaner, it leads to a heap allocation (even if that interface variable itself does not escape). In other words, implicit conversion to an interface leads to an allocation.

Precondition: reqMarshal and respUnmarshal must be non-nil.

func (*Client) SyncFDs

func (c *Client) SyncFDs(ctx context.Context, fds []FDID) error

SyncFDs makes a Fsync RPC to sync multiple FDs.

type ClientFD

type ClientFD struct {
	// contains filtered or unexported fields
}

ClientFD is a wrapper around FDID that provides client-side utilities so that RPC making is easier.

func (*ClientFD) Allocate

func (f *ClientFD) Allocate(ctx context.Context, mode, offset, length uint64) error

Allocate makes the FAllocate RPC.

func (*ClientFD) Client

func (f *ClientFD) Client() *Client

Client returns the backing Client.

func (*ClientFD) Close

func (f *ClientFD) Close(ctx context.Context) error

Close closes this FD immediately (invoking a Close RPC). Consider using CloseBatched if closing this FD on remote right away is not critical.

func (*ClientFD) CloseBatched

func (f *ClientFD) CloseBatched(ctx context.Context)

CloseBatched queues this FD to be closed on the server and resets f.fd. This maybe invoke the Close RPC if the queue is full.

func (*ClientFD) Connect

func (f *ClientFD) Connect(ctx context.Context, sockType linux.SockType) (int, error)

Connect makes the Connect RPC.

func (*ClientFD) Flush

func (f *ClientFD) Flush(ctx context.Context) error

Flush makes the Flush RPC.

func (*ClientFD) GetXattr

func (f *ClientFD) GetXattr(ctx context.Context, name string, size uint64) (string, error)

GetXattr makes the FGetXattr RPC.

func (*ClientFD) Getdents64

func (f *ClientFD) Getdents64(ctx context.Context, count int32) ([]Dirent64, error)

Getdents64 makes the Getdents64 RPC.

func (*ClientFD) ID

func (f *ClientFD) ID() FDID

ID returns the underlying FDID.

func (*ClientFD) LinkAt

func (f *ClientFD) LinkAt(ctx context.Context, targetFD FDID, name string) (*Inode, error)

LinkAt makes the LinkAt RPC.

func (*ClientFD) ListXattr

func (f *ClientFD) ListXattr(ctx context.Context, size uint64) ([]string, error)

ListXattr makes the FListXattr RPC.

func (*ClientFD) MkdirAt

func (f *ClientFD) MkdirAt(ctx context.Context, name string, mode linux.FileMode, uid UID, gid GID) (*Inode, error)

MkdirAt makes the MkdirAt RPC.

func (*ClientFD) MknodAt

func (f *ClientFD) MknodAt(ctx context.Context, name string, mode linux.FileMode, uid UID, gid GID, minor, major uint32) (*Inode, error)

MknodAt makes the MknodAt RPC.

func (*ClientFD) Ok

func (f *ClientFD) Ok() bool

Ok returns true if the underlying FD is ok.

func (*ClientFD) OpenAt

func (f *ClientFD) OpenAt(ctx context.Context, flags uint32) (FDID, int, error)

OpenAt makes the OpenAt RPC.

func (*ClientFD) OpenCreateAt

func (f *ClientFD) OpenCreateAt(ctx context.Context, name string, flags uint32, mode linux.FileMode, uid UID, gid GID) (Inode, FDID, int, error)

OpenCreateAt makes the OpenCreateAt RPC.

func (*ClientFD) Read

func (f *ClientFD) Read(ctx context.Context, dst []byte, offset uint64) (uint64, error)

Read makes the PRead RPC.

func (*ClientFD) ReadLinkAt

func (f *ClientFD) ReadLinkAt(ctx context.Context) (string, error)

ReadLinkAt makes the ReadLinkAt RPC.

func (*ClientFD) RemoveXattr

func (f *ClientFD) RemoveXattr(ctx context.Context, name string) error

RemoveXattr makes the FRemoveXattr RPC.

func (*ClientFD) RenameTo

func (f *ClientFD) RenameTo(ctx context.Context, newDirFD FDID, newName string) error

RenameTo makes the RenameAt RPC which renames f to newDirFD directory with name newName.

func (*ClientFD) SetStat

func (f *ClientFD) SetStat(ctx context.Context, stat *linux.Statx) (uint32, error, error)

SetStat makes the SetStat RPC.

func (*ClientFD) SetXattr

func (f *ClientFD) SetXattr(ctx context.Context, name string, value string, flags uint32) error

SetXattr makes the FSetXattr RPC.

func (*ClientFD) StatFSTo

func (f *ClientFD) StatFSTo(ctx context.Context, statFS *StatFS) error

StatFSTo makes the FStatFS RPC and populates statFS with the result.

func (*ClientFD) StatTo

func (f *ClientFD) StatTo(ctx context.Context, stat *linux.Statx) error

StatTo makes the Fstat RPC and populates stat with the result.

func (*ClientFD) SymlinkAt

func (f *ClientFD) SymlinkAt(ctx context.Context, name, target string, uid UID, gid GID) (*Inode, error)

SymlinkAt makes the SymlinkAt RPC.

func (*ClientFD) Sync

func (f *ClientFD) Sync(ctx context.Context) error

Sync makes the Fsync RPC.

func (*ClientFD) UnlinkAt

func (f *ClientFD) UnlinkAt(ctx context.Context, name string, flags uint32) error

UnlinkAt makes the UnlinkAt RPC.

func (*ClientFD) Walk

func (f *ClientFD) Walk(ctx context.Context, name string) (*Inode, error)

Walk makes the Walk RPC with just one path component to walk.

func (*ClientFD) WalkMultiple

func (f *ClientFD) WalkMultiple(ctx context.Context, names []string) (WalkStatus, []Inode, error)

WalkMultiple makes the Walk RPC with multiple path components.

func (*ClientFD) WalkStat

func (f *ClientFD) WalkStat(ctx context.Context, names []string) ([]linux.Statx, error)

WalkStat makes the WalkStat RPC with multiple path components to walk.

func (*ClientFD) Write

func (f *ClientFD) Write(ctx context.Context, src []byte, offset uint64) (uint64, error)

Write makes the PWrite RPC.

type CloseReq

type CloseReq struct {
	FDs FdArray
}

CloseReq is used to close(2) FDs.

func (*CloseReq) CheckedUnmarshal

func (c *CloseReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*CloseReq) MarshalBytes

func (c *CloseReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*CloseReq) SizeBytes

func (c *CloseReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type Communicator

type Communicator interface {
	// PayloadBuf returns a slice to the payload section of its internal buffer
	// where the message can be marshalled. The handlers should use this to
	// populate the payload buffer with the message.
	//
	// The payload buffer contents *should* be preserved across calls with
	// different sizes. Note that this is not a guarantee, because a compromised
	// owner of a "shared" payload buffer can tamper with its contents anytime,
	// even when it's not its turn to do so.
	PayloadBuf(size uint32) []byte

	// SndRcvMessage sends message m. The caller must have populated PayloadBuf()
	// with payloadLen bytes. The caller expects to receive wantFDs FDs.
	// Any received FDs must be accessible via ReleaseFDs(). It returns the
	// response message along with the response payload length.
	SndRcvMessage(m MID, payloadLen uint32, wantFDs uint8) (MID, uint32, error)

	// DonateFD makes fd non-blocking and starts tracking it. The next call to
	// ReleaseFDs will include fd in the order it was added. Communicator takes
	// ownership of fd. Server side should call this.
	DonateFD(fd int) error

	// Track starts tracking fd. The next call to ReleaseFDs will include fd in
	// the order it was added. Communicator takes ownership of fd. Client side
	// should use this for accumulating received FDs.
	TrackFD(fd int)

	// ReleaseFDs returns the accumulated FDs and stops tracking them. The
	// ownership of the FDs is transferred to the caller.
	ReleaseFDs() []int
}

Communicator is a server side utility which represents exactly how the server is communicating with the client.

type ConnectReq

type ConnectReq struct {
	FD FDID
	// SockType is used to specify the socket type to connect to. As a special
	// case, SockType = 0 means that the socket type does not matter and the
	// requester will accept any socket type.
	SockType uint32
}

ConnectReq is used to make a Connect request.

+marshal boundCheck

type Connection

type Connection struct {
	// contains filtered or unexported fields
}

Connection represents a connection between a mount point in the client and a mount point in the server. It is owned by the server on which it was started and facilitates communication with the client mount.

Each connection is set up using a unix domain socket. One end is owned by the server and the other end is owned by the client. The connection may spawn additional comunicational channels for the same mount for increased RPC concurrency.

func (*Connection) LookupControlFD

func (c *Connection) LookupControlFD(id FDID) (*ControlFD, error)

LookupControlFD retrieves the control FD identified by id on this connection. On success, the caller gains a ref on the FD.

func (*Connection) LookupOpenFD

func (c *Connection) LookupOpenFD(id FDID) (*OpenFD, error)

LookupOpenFD retrieves the open FD identified by id on this connection. On success, the caller gains a ref on the FD.

func (*Connection) RemoveControlFDLocked

func (c *Connection) RemoveControlFDLocked(id FDID)

RemoveControlFDLocked is the same as RemoveFD with added preconditions.

Preconditions: * server's rename mutex must at least be read locked. * id must be pointing to a control FD.

func (*Connection) RemoveFD

func (c *Connection) RemoveFD(id FDID)

RemoveFD makes c stop tracking the passed FDID and drops its ref on it.

func (*Connection) Run

func (c *Connection) Run()

Run defines the lifecycle of a connection.

func (*Connection) Server

func (c *Connection) Server() *Server

Server returns the associated server.

func (*Connection) ServerImpl

func (c *Connection) ServerImpl() ServerImpl

ServerImpl returns the associated server implementation.

type ControlFD

type ControlFD struct {
	// contains filtered or unexported fields
}

A ControlFD is the gateway to the backing filesystem tree node. It is an unusual concept. This exists to provide a safe way to do path-based operations on the file. It performs operations that can modify the filesystem tree and synchronizes these operations. See ControlFDImpl for supported operations.

It is not an inode, because multiple control FDs are allowed to exist on the same file. It is not a file descriptor because it is not tied to any access mode, i.e. a control FD can change its access mode based on the operation being performed.

Reference Model:

  • When a control FD is created, the connection takes a ref on it which represents the client's ref on the FD.
  • The client can drop its ref via the Close RPC which will in turn make the connection drop its ref.
  • Each control FD holds a ref on its parent for its entire life time.

func (*ControlFD) DecRef

func (fd *ControlFD) DecRef(context.Context)

DecRef implements refsvfs2.RefCounter.DecRef. Note that the context parameter should never be used. It exists solely to comply with the refsvfs2.RefCounter interface.

func (*ControlFD) DecRefLocked

func (fd *ControlFD) DecRefLocked()

DecRefLocked is the same as DecRef except the added precondition.

Precondition: server's rename mutex must be at least read locked.

func (*ControlFD) FilePath

func (fd *ControlFD) FilePath() string

FilePath returns the absolute path of the file fd was opened on. This is expensive and must not be called on hot paths. FilePath acquires the rename mutex for reading so callers should not be holding it.

func (*ControlFD) FilePathLocked

func (fd *ControlFD) FilePathLocked() string

FilePathLocked is the same as FilePath with the additional precondition.

Precondition: server's rename mutex must be at least read locked.

func (*ControlFD) FileType

func (fd *ControlFD) FileType() linux.FileMode

FileType returns the file mode only containing the file type bits.

func (*ControlFD) ForEachOpenFD

func (fd *ControlFD) ForEachOpenFD(fn func(ofd OpenFDImpl))

ForEachOpenFD executes fn on each FD opened on fd.

func (*ControlFD) ID

func (fd *ControlFD) ID() FDID

ID returns fd's ID.

func (*ControlFD) Init

func (fd *ControlFD) Init(c *Connection, parent *ControlFD, name string, mode linux.FileMode, impl ControlFDImpl)

Init must be called before first use of fd. It inserts fd into the filesystem tree.

Precondition: server's rename mutex must be at least read locked.

func (*ControlFD) IsDir

func (fd *ControlFD) IsDir() bool

IsDir indicates whether fd represents a directory.

func (*ControlFD) IsRegular

func (fd *ControlFD) IsRegular() bool

IsRegular indicates whether fd represents a regular file.

func (*ControlFD) IsSocket

func (fd *ControlFD) IsSocket() bool

IsSocket indicates whether fd represents a socket.

func (fd *ControlFD) IsSymlink() bool

IsSymlink indicates whether fd represents a symbolic link.

func (*ControlFD) NameLocked

func (fd *ControlFD) NameLocked() string

NameLocked returns the backing file's last component name.

Precondition: server's rename mutex must be at least read locked.

func (*ControlFD) ParentLocked

func (fd *ControlFD) ParentLocked() ControlFDImpl

ParentLocked returns the parent control FD. Note that parent might be a control FD from another connection on this server. So its ID must not returned on this connection because FDIDs are local to their connection.

Precondition: server's rename mutex must be at least read locked.

type ControlFDImpl

type ControlFDImpl interface {
	FD() *ControlFD
	Close(c *Connection)
	Stat(c *Connection) (linux.Statx, error)
	SetStat(c *Connection, stat SetStatReq) (uint32, error)
	Walk(c *Connection, path StringArray, recordInode func(Inode)) (WalkStatus, error)
	WalkStat(c *Connection, path StringArray, recordStat func(linux.Statx)) error
	Open(c *Connection, flags uint32) (FDID, int, error)
	OpenCreate(c *Connection, mode linux.FileMode, uid UID, gid GID, name string, flags uint32) (Inode, FDID, int, error)
	Mkdir(c *Connection, mode linux.FileMode, uid UID, gid GID, name string) (Inode, error)
	Mknod(c *Connection, mode linux.FileMode, uid UID, gid GID, name string, minor uint32, major uint32) (Inode, error)
	Symlink(c *Connection, name string, target string, uid UID, gid GID) (Inode, error)
	Link(c *Connection, dir ControlFDImpl, name string) (Inode, error)
	StatFS(c *Connection) (StatFS, error)
	Readlink(c *Connection, getLinkBuf func(uint32) []byte) (uint32, error)
	Connect(c *Connection, sockType uint32) (int, error)
	Unlink(c *Connection, name string, flags uint32) error
	RenameLocked(c *Connection, newDir ControlFDImpl, newName string) (func(ControlFDImpl), func(), error)
	GetXattr(c *Connection, name string, dataBuf []byte) (uint32, error)
	SetXattr(c *Connection, name string, value string, flags uint32) error
	ListXattr(c *Connection, size uint64) (StringArray, error)
	RemoveXattr(c *Connection, name string) error
}

ControlFDImpl contains implementation details for a ControlFD. Implementations of ControlFDImpl should contain their associated ControlFD by value as their first field.

The operations that perform path traversal or any modification to the filesystem tree must synchronize those modifications with the server's rename mutex.

type Dirent64

type Dirent64 struct {
	Ino      primitive.Uint64
	DevMinor primitive.Uint32
	DevMajor primitive.Uint32
	Off      primitive.Uint64
	Type     primitive.Uint8
	Name     SizedString
}

Dirent64 is analogous to struct linux_dirent64.

func (*Dirent64) CheckedUnmarshal

func (d *Dirent64) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*Dirent64) MarshalBytes

func (d *Dirent64) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*Dirent64) SizeBytes

func (d *Dirent64) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type ErrorResp

type ErrorResp struct {
	// contains filtered or unexported fields
}

ErrorResp is returned to represent an error while handling a request.

+marshal

type FAllocateReq

type FAllocateReq struct {
	FD FDID

	Mode   uint64
	Offset uint64
	Length uint64
	// contains filtered or unexported fields
}

FAllocateReq is used to request to fallocate(2) an FD. This has no response.

+marshal boundCheck

type FDID

type FDID uint32

FDID (file descriptor identifier) is used to identify FDs on a connection. Each connection has its own FDID namespace.

+marshal boundCheck slice:FDIDSlice

const InvalidFDID FDID = 0

InvalidFDID represents an invalid FDID.

func (FDID) Ok

func (f FDID) Ok() bool

Ok returns true if f is a valid FDID.

type FGetXattrReq

type FGetXattrReq struct {
	FD      FDID
	BufSize primitive.Uint32
	Name    SizedString
}

FGetXattrReq is used to make FGetXattr requests. The response to this is just a SizedString containing the xattr value.

func (*FGetXattrReq) CheckedUnmarshal

func (g *FGetXattrReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*FGetXattrReq) MarshalBytes

func (g *FGetXattrReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*FGetXattrReq) SizeBytes

func (g *FGetXattrReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type FGetXattrResp

type FGetXattrResp struct {
	Value SizedString
}

FGetXattrResp is used to respond to FGetXattr request.

func (*FGetXattrResp) CheckedUnmarshal

func (g *FGetXattrResp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*FGetXattrResp) MarshalBytes

func (g *FGetXattrResp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*FGetXattrResp) SizeBytes

func (g *FGetXattrResp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type FListXattrReq

type FListXattrReq struct {
	FD FDID

	Size uint64
	// contains filtered or unexported fields
}

FListXattrReq is used to make FListXattr requests.

+marshal boundCheck

type FListXattrResp

type FListXattrResp struct {
	Xattrs StringArray
}

FListXattrResp is used to respond to FListXattr requests.

func (*FListXattrResp) CheckedUnmarshal

func (l *FListXattrResp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*FListXattrResp) MarshalBytes

func (l *FListXattrResp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*FListXattrResp) SizeBytes

func (l *FListXattrResp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type FRemoveXattrReq

type FRemoveXattrReq struct {
	FD   FDID
	Name SizedString
}

FRemoveXattrReq is used to make FRemoveXattr requests. It has no response.

func (*FRemoveXattrReq) CheckedUnmarshal

func (r *FRemoveXattrReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*FRemoveXattrReq) MarshalBytes

func (r *FRemoveXattrReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*FRemoveXattrReq) SizeBytes

func (r *FRemoveXattrReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type FSetXattrReq

type FSetXattrReq struct {
	FD    FDID
	Flags primitive.Uint32
	Name  SizedString
	Value SizedString
}

FSetXattrReq is used to make FSetXattr requests. It has no response.

func (*FSetXattrReq) CheckedUnmarshal

func (s *FSetXattrReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*FSetXattrReq) MarshalBytes

func (s *FSetXattrReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*FSetXattrReq) SizeBytes

func (s *FSetXattrReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type FStatFSReq

type FStatFSReq struct {
	FD FDID
}

FStatFSReq is used to request StatFS results for the specified FD.

+marshal boundCheck

type FdArray

type FdArray []FDID

FdArray is a utility struct which implements a marshallable type for communicating an array of FDIDs. In memory, the array data is preceded by a uint32 denoting the array length.

func (*FdArray) CheckedUnmarshal

func (f *FdArray) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*FdArray) MarshalBytes

func (f *FdArray) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*FdArray) SizeBytes

func (f *FdArray) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type FlushReq

type FlushReq struct {
	FD FDID
}

FlushReq is used to make Flush requests.

+marshal boundCheck

type FsyncReq

type FsyncReq struct {
	FDs FdArray
}

FsyncReq is used to fsync(2) FDs.

func (*FsyncReq) CheckedUnmarshal

func (f *FsyncReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*FsyncReq) MarshalBytes

func (f *FsyncReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*FsyncReq) SizeBytes

func (f *FsyncReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type GID

type GID uint32

GID represents a group ID.

+marshal

func (GID) Ok

func (gid GID) Ok() bool

Ok returns true if gid is not NoGID.

type Getdents64Req

type Getdents64Req struct {
	DirFD FDID
	// Count is the number of bytes to read. A negative value of Count is used to
	// indicate that the implementation must lseek(0, SEEK_SET) before calling
	// getdents64(2). Implementations must use the absolute value of Count to
	// determine the number of bytes to read.
	Count int32
}

Getdents64Req is used to make Getdents64 requests.

+marshal boundCheck

type Getdents64Resp

type Getdents64Resp struct {
	Dirents []Dirent64
}

Getdents64Resp is used to communicate getdents64 results.

func (*Getdents64Resp) CheckedUnmarshal

func (g *Getdents64Resp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*Getdents64Resp) MarshalBytes

func (g *Getdents64Resp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*Getdents64Resp) SizeBytes

func (g *Getdents64Resp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type Inode

type Inode struct {
	ControlFD FDID

	Stat linux.Statx
	// contains filtered or unexported fields
}

Inode represents an inode on the remote filesystem.

+marshal slice:InodeSlice

type LinkAtReq

type LinkAtReq struct {
	DirFD  FDID
	Target FDID
	Name   SizedString
}

LinkAtReq is used to make LinkAt requests.

func (*LinkAtReq) CheckedUnmarshal

func (l *LinkAtReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*LinkAtReq) MarshalBytes

func (l *LinkAtReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*LinkAtReq) SizeBytes

func (l *LinkAtReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type LinkAtResp

type LinkAtResp struct {
	Link Inode
}

LinkAtResp is used to respond to a successful LinkAt request.

+marshal boundCheck

type MID

type MID uint16

MID (message ID) is used to identify messages to parse from payload.

+marshal slice:MIDSlice

const (
	// Error is only used in responses to pass errors to client.
	Error MID = 0

	// Mount is used to establish connection between the client and server mount
	// point. lisafs requires that the client makes a successful Mount RPC before
	// making other RPCs.
	Mount MID = 1

	// Channel requests to start a new communicational channel.
	Channel MID = 2

	// FStat requests the stat(2) results for a specified file.
	FStat MID = 3

	// SetStat requests to change file attributes. Note that there is no one
	// corresponding Linux syscall. This is a conglomeration of fchmod(2),
	// fchown(2), ftruncate(2) and futimesat(2).
	SetStat MID = 4

	// Walk requests to walk the specified path starting from the specified
	// directory. Server-side path traversal is terminated preemptively on
	// symlinks entries because they can cause non-linear traversal.
	Walk MID = 5

	// WalkStat is the same as Walk, except the following differences:
	//  * If the first path component is "", then it also returns stat results
	//    for the directory where the walk starts.
	//  * Does not return Inode, just the Stat results for each path component.
	WalkStat MID = 6

	// OpenAt is analogous to openat(2). It does not perform any walk. It merely
	// duplicates the control FD with the open flags passed.
	OpenAt MID = 7

	// OpenCreateAt is analogous to openat(2) with O_CREAT|O_EXCL added to flags.
	// It also returns the newly created file inode.
	OpenCreateAt MID = 8

	// Close is analogous to close(2) but can work on multiple FDs.
	Close MID = 9

	// FSync is analogous to fsync(2) but can work on multiple FDs.
	FSync MID = 10

	// PWrite is analogous to pwrite(2).
	PWrite MID = 11

	// PRead is analogous to pread(2).
	PRead MID = 12

	// MkdirAt is analogous to mkdirat(2).
	MkdirAt MID = 13

	// MknodAt is analogous to mknodat(2).
	MknodAt MID = 14

	// SymlinkAt is analogous to symlinkat(2).
	SymlinkAt MID = 15

	// LinkAt is analogous to linkat(2).
	LinkAt MID = 16

	// FStatFS is analogous to fstatfs(2).
	FStatFS MID = 17

	// FAllocate is analogous to fallocate(2).
	FAllocate MID = 18

	// ReadLinkAt is analogous to readlinkat(2).
	ReadLinkAt MID = 19

	// Flush cleans up the file state. Its behavior is implementation
	// dependent and might not even be supported in server implementations.
	Flush MID = 20

	// Connect is loosely analogous to connect(2).
	Connect MID = 21

	// UnlinkAt is analogous to unlinkat(2).
	UnlinkAt MID = 22

	// RenameAt is loosely analogous to renameat(2).
	RenameAt MID = 23

	// Getdents64 is analogous to getdents64(2).
	Getdents64 MID = 24

	// FGetXattr is analogous to fgetxattr(2).
	FGetXattr MID = 25

	// FSetXattr is analogous to fsetxattr(2).
	FSetXattr MID = 26

	// FListXattr is analogous to flistxattr(2).
	FListXattr MID = 27

	// FRemoveXattr is analogous to fremovexattr(2).
	FRemoveXattr MID = 28
)

These constants are used to identify their corresponding message types.

type MkdirAtReq

type MkdirAtReq struct {
	Name SizedString
	// contains filtered or unexported fields
}

MkdirAtReq is used to make MkdirAt requests.

func (*MkdirAtReq) CheckedUnmarshal

func (m *MkdirAtReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*MkdirAtReq) MarshalBytes

func (m *MkdirAtReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*MkdirAtReq) SizeBytes

func (m *MkdirAtReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type MkdirAtResp

type MkdirAtResp struct {
	ChildDir Inode
}

MkdirAtResp is the response to a successful MkdirAt request.

+marshal boundCheck

type MknodAtReq

type MknodAtReq struct {
	Minor primitive.Uint32
	Major primitive.Uint32
	Name  SizedString
	// contains filtered or unexported fields
}

MknodAtReq is used to make MknodAt requests.

func (*MknodAtReq) CheckedUnmarshal

func (m *MknodAtReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*MknodAtReq) MarshalBytes

func (m *MknodAtReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*MknodAtReq) SizeBytes

func (m *MknodAtReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type MknodAtResp

type MknodAtResp struct {
	Child Inode
}

MknodAtResp is the response to a successful MknodAt request.

+marshal boundCheck

type MountReq

type MountReq struct {
	MountPath SizedString
}

MountReq represents a Mount request.

func (*MountReq) CheckedUnmarshal

func (m *MountReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*MountReq) MarshalBytes

func (m *MountReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*MountReq) SizeBytes

func (m *MountReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type MountResp

type MountResp struct {
	Root Inode
	// MaxMessageSize is the maximum size of messages communicated between the
	// client and server in bytes. This includes the communication header.
	MaxMessageSize primitive.Uint32
	// SupportedMs holds all the supported messages.
	SupportedMs []MID
}

MountResp represents a Mount response.

func (*MountResp) CheckedUnmarshal

func (m *MountResp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*MountResp) MarshalBytes

func (m *MountResp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*MountResp) SizeBytes

func (m *MountResp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type MsgDynamic

type MsgDynamic struct {
	N   primitive.Uint32
	Arr []MsgSimple
}

MsgDynamic is a sample dynamic struct which can be used to test message passing.

+marshal dynamic

func (*MsgDynamic) CheckedUnmarshal

func (m *MsgDynamic) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*MsgDynamic) MarshalBytes

func (m *MsgDynamic) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*MsgDynamic) Randomize

func (m *MsgDynamic) Randomize(arrLen int)

Randomize randomizes the contents of m.

func (*MsgDynamic) SizeBytes

func (m *MsgDynamic) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

func (*MsgDynamic) UnmarshalBytes

func (m *MsgDynamic) UnmarshalBytes(src []byte) []byte

UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.

type MsgSimple

type MsgSimple struct {
	A uint16
	B uint16
	C uint32
	D uint64
}

MsgSimple is a sample packed struct which can be used to test message passing.

+marshal slice:Msg1Slice

func (*MsgSimple) Randomize

func (m *MsgSimple) Randomize()

Randomize randomizes the contents of m.

type OpenAtReq

type OpenAtReq struct {
	FD    FDID
	Flags uint32
}

OpenAtReq is used to open existing FDs with the specified flags.

+marshal boundCheck

type OpenAtResp

type OpenAtResp struct {
	OpenFD FDID
}

OpenAtResp is used to communicate the newly created FD.

+marshal boundCheck

type OpenCreateAtReq

type OpenCreateAtReq struct {
	Flags primitive.Uint32
	Name  SizedString
	// contains filtered or unexported fields
}

OpenCreateAtReq is used to make OpenCreateAt requests.

func (*OpenCreateAtReq) CheckedUnmarshal

func (o *OpenCreateAtReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*OpenCreateAtReq) MarshalBytes

func (o *OpenCreateAtReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*OpenCreateAtReq) SizeBytes

func (o *OpenCreateAtReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type OpenCreateAtResp

type OpenCreateAtResp struct {
	Child Inode
	NewFD FDID
	// contains filtered or unexported fields
}

OpenCreateAtResp is used to communicate successful OpenCreateAt results.

+marshal boundCheck

type OpenFD

type OpenFD struct {
	// contains filtered or unexported fields
}

OpenFD represents an open file descriptor on the protocol. It resonates closely with a Linux file descriptor. Its operations are limited to the file. Its operations are not allowed to modify or traverse the filesystem tree. See OpenFDImpl for the supported operations.

Reference Model: * An OpenFD takes a reference on the control FD it was opened on.

func (*OpenFD) ControlFD

func (fd *OpenFD) ControlFD() ControlFDImpl

ControlFD returns the control FD on which this FD was opened.

func (*OpenFD) DecRef

func (fd *OpenFD) DecRef(context.Context)

DecRef implements refsvfs2.RefCounter.DecRef. Note that the context parameter should never be used. It exists solely to comply with the refsvfs2.RefCounter interface.

func (*OpenFD) ID

func (fd *OpenFD) ID() FDID

ID returns fd's ID.

func (*OpenFD) Init

func (fd *OpenFD) Init(cfd *ControlFD, flags uint32, impl OpenFDImpl)

Init must be called before first use of fd.

type OpenFDImpl

type OpenFDImpl interface {
	FD() *OpenFD
	Close(c *Connection)
	Stat(c *Connection) (linux.Statx, error)
	Sync(c *Connection) error
	Write(c *Connection, buf []byte, off uint64) (uint64, error)
	Read(c *Connection, off uint64, dataBuf []byte) (uint32, error)
	Allocate(c *Connection, mode, off, length uint64) error
	Flush(c *Connection) error
	Getdent64(c *Connection, count uint32, seek0 bool, recordDirent func(Dirent64)) error
}

OpenFDImpl contains implementation details for a OpenFD. Implementations of OpenFDImpl should contain their associated OpenFD by value as their first field.

Since these operations do not perform any path traversal or any modification to the filesystem tree, there is no need to synchronize with rename operations.

type P9Version

type P9Version struct {
	MSize   primitive.Uint32
	Version string
}

P9Version mimics p9.TVersion and p9.Rversion.

func (*P9Version) CheckedUnmarshal

func (v *P9Version) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*P9Version) MarshalBytes

func (v *P9Version) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*P9Version) SizeBytes

func (v *P9Version) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type PReadReq

type PReadReq struct {
	Offset uint64
	FD     FDID
	Count  uint32
}

PReadReq is used to pread(2) on an FD.

+marshal boundCheck

type PReadResp

type PReadResp struct {
	NumBytes primitive.Uint32
	Buf      []byte
}

PReadResp is used to return the result of pread(2).

func (*PReadResp) CheckedUnmarshal

func (r *PReadResp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*PReadResp) MarshalBytes

func (r *PReadResp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*PReadResp) SizeBytes

func (r *PReadResp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type PWriteReq

type PWriteReq struct {
	Offset   primitive.Uint64
	FD       FDID
	NumBytes primitive.Uint32
	Buf      []byte
}

PWriteReq is used to pwrite(2) on an FD.

func (*PWriteReq) CheckedUnmarshal

func (w *PWriteReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*PWriteReq) MarshalBytes

func (w *PWriteReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*PWriteReq) SizeBytes

func (w *PWriteReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type PWriteResp

type PWriteResp struct {
	Count uint64
}

PWriteResp is used to return the result of pwrite(2).

+marshal boundCheck

type RPCHandler

type RPCHandler func(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)

RPCHandler defines a handler that is invoked when the associated message is received. The handler is responsible for:

* Unmarshalling the request from the passed payload and interpreting it. * Marshalling the response into the communicator's payload buffer. * Return the number of payload bytes written. * Donate any FDs (if needed) to comm which will in turn donate it to client.

type ReadLinkAtReq

type ReadLinkAtReq struct {
	FD FDID
}

ReadLinkAtReq is used to readlinkat(2) at the specified FD.

+marshal boundCheck

type ReadLinkAtResp

type ReadLinkAtResp struct {
	Target SizedString
}

ReadLinkAtResp is used to communicate ReadLinkAt results.

func (*ReadLinkAtResp) CheckedUnmarshal

func (r *ReadLinkAtResp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*ReadLinkAtResp) MarshalBytes

func (r *ReadLinkAtResp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*ReadLinkAtResp) SizeBytes

func (r *ReadLinkAtResp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type RenameAtReq

type RenameAtReq struct {
	Renamed FDID
	NewDir  FDID
	NewName SizedString
}

RenameAtReq is used to make Rename requests. Note that the request takes in the to-be-renamed file's FD instead of oldDir and oldName like renameat(2).

func (*RenameAtReq) CheckedUnmarshal

func (r *RenameAtReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*RenameAtReq) MarshalBytes

func (r *RenameAtReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*RenameAtReq) SizeBytes

func (r *RenameAtReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type Server

type Server struct {

	// RenameMu synchronizes rename operations within this filesystem tree.
	RenameMu sync.RWMutex
	// contains filtered or unexported fields
}

Server serves a filesystem tree. Multiple connections on different mount points can be started on a server. The server provides utilities to safely modify the filesystem tree across its connections (mount points). Note that it does not support synchronizing filesystem tree mutations across other servers serving the same filesystem subtree. Server also manages the lifecycle of all connections.

func (*Server) CreateConnection

func (s *Server) CreateConnection(sock *unet.Socket, readonly bool) (*Connection, error)

CreateConnection initializes a new connection - creating a server if required. The connection must be started separately.

func (*Server) Init

func (s *Server) Init(impl ServerImpl)

Init must be called before first use of server.

func (*Server) InitTestOnly

func (s *Server) InitTestOnly(impl ServerImpl, handlers []RPCHandler)

InitTestOnly is the same as Init except that it allows to swap out the underlying handlers with something custom. This is for test only.

func (*Server) StartConnection

func (s *Server) StartConnection(c *Connection)

StartConnection starts the connection on a separate goroutine and tracks it.

func (*Server) Wait

func (s *Server) Wait()

Wait waits for all connections started via StartConnection() to terminate.

func (*Server) WithRenameReadLock

func (s *Server) WithRenameReadLock(fn func() error) error

WithRenameReadLock invokes fn with the server's rename mutex locked for reading. This ensures that no rename operations occur concurrently.

type ServerImpl

type ServerImpl interface {
	// Mount is called when a Mount RPC is made. It mounts the connection at
	// mountPath.
	//
	// Precondition: mountPath == path.Clean(mountPath).
	Mount(c *Connection, mountPath string) (ControlFDImpl, Inode, error)

	// SupportedMessages returns a list of messages that the server
	// implementation supports.
	SupportedMessages() []MID

	// MaxMessageSize is the maximum payload length (in bytes) that can be sent
	// to this server implementation.
	MaxMessageSize() uint32
}

ServerImpl contains the implementation details for a Server. Implementations of ServerImpl should contain their associated Server by value as their first field.

type SetStatReq

type SetStatReq struct {
	FD FDID

	Mask  uint32
	Mode  uint32 // Only permissions part is settable.
	UID   UID
	GID   GID
	Size  uint64
	Atime linux.Timespec
	Mtime linux.Timespec
	// contains filtered or unexported fields
}

SetStatReq is used to set attributeds on FDs.

+marshal boundCheck

type SetStatResp

type SetStatResp struct {
	FailureMask  uint32
	FailureErrNo uint32
}

SetStatResp is used to communicate SetStat results. It contains a mask representing the failed changes. It also contains the errno of the failed set attribute operation. If multiple operations failed then any of those errnos can be returned.

+marshal boundCheck

type SizedString

type SizedString string

SizedString represents a string in memory. The marshalled string bytes are preceded by a uint32 signifying the string length.

func (*SizedString) CheckedUnmarshal

func (s *SizedString) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*SizedString) MarshalBytes

func (s *SizedString) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*SizedString) SizeBytes

func (s *SizedString) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type StatFS

type StatFS struct {
	Type            uint64
	BlockSize       int64
	Blocks          uint64
	BlocksFree      uint64
	BlocksAvailable uint64
	Files           uint64
	FilesFree       uint64
	NameLength      uint64
}

StatFS is responded to a successful FStatFS request.

+marshal boundCheck

type StatReq

type StatReq struct {
	FD FDID
}

StatReq requests the stat results for the specified FD.

+marshal boundCheck

type StringArray

type StringArray []string

StringArray represents an array of SizedStrings in memory. The marshalled array data is preceded by a uint32 signifying the array length.

func (*StringArray) CheckedUnmarshal

func (s *StringArray) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*StringArray) MarshalBytes

func (s *StringArray) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*StringArray) SizeBytes

func (s *StringArray) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type SymlinkAtReq

type SymlinkAtReq struct {
	DirFD  FDID
	UID    UID
	GID    GID
	Name   SizedString
	Target SizedString
}

SymlinkAtReq is used to make SymlinkAt request.

func (*SymlinkAtReq) CheckedUnmarshal

func (s *SymlinkAtReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*SymlinkAtReq) MarshalBytes

func (s *SymlinkAtReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*SymlinkAtReq) SizeBytes

func (s *SymlinkAtReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type SymlinkAtResp

type SymlinkAtResp struct {
	Symlink Inode
}

SymlinkAtResp is the response to a successful SymlinkAt request.

+marshal boundCheck

type UID

type UID uint32

UID represents a user ID.

+marshal

func (UID) Ok

func (uid UID) Ok() bool

Ok returns true if uid is not NoUID.

type UnlinkAtReq

type UnlinkAtReq struct {
	DirFD FDID
	Flags primitive.Uint32
	Name  SizedString
}

UnlinkAtReq is used to make UnlinkAt request.

func (*UnlinkAtReq) CheckedUnmarshal

func (u *UnlinkAtReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*UnlinkAtReq) MarshalBytes

func (u *UnlinkAtReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*UnlinkAtReq) SizeBytes

func (u *UnlinkAtReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type WalkReq

type WalkReq struct {
	DirFD FDID
	Path  StringArray
}

WalkReq is used to request to walk multiple path components at once. This is used for both Walk and WalkStat.

func (*WalkReq) CheckedUnmarshal

func (w *WalkReq) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*WalkReq) MarshalBytes

func (w *WalkReq) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*WalkReq) SizeBytes

func (w *WalkReq) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type WalkResp

type WalkResp struct {
	Status WalkStatus
	Inodes []Inode
}

WalkResp is used to communicate the inodes walked by the server.

func (*WalkResp) CheckedUnmarshal

func (w *WalkResp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*WalkResp) MarshalBytes

func (w *WalkResp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*WalkResp) SizeBytes

func (w *WalkResp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type WalkStatResp

type WalkStatResp struct {
	Stats []linux.Statx
}

WalkStatResp is used to communicate stat results for WalkStat.

func (*WalkStatResp) CheckedUnmarshal

func (w *WalkStatResp) CheckedUnmarshal(src []byte) ([]byte, bool)

CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.

func (*WalkStatResp) MarshalBytes

func (w *WalkStatResp) MarshalBytes(dst []byte) []byte

MarshalBytes implements marshal.Marshallable.MarshalBytes.

func (*WalkStatResp) SizeBytes

func (w *WalkStatResp) SizeBytes() int

SizeBytes implements marshal.Marshallable.SizeBytes.

type WalkStatus

type WalkStatus = primitive.Uint8

WalkStatus is used to indicate the reason for partial/unsuccessful server side Walk operations. Please note that partial/unsuccessful walk operations do not necessarily fail the RPC. The RPC is successful with a failure hint which can be used by the client to infer server-side state.

const (
	// WalkSuccess indicates that all path components were successfully walked.
	WalkSuccess WalkStatus = iota

	// WalkComponentDoesNotExist indicates that the walk was prematurely
	// terminated because an intermediate path component does not exist on
	// server. The results of all previous existing path components is returned.
	WalkComponentDoesNotExist

	// WalkComponentSymlink indicates that the walk was prematurely
	// terminated because an intermediate path component was a symlink. It is not
	// safe to resolve symlinks remotely (unaware of mount points).
	WalkComponentSymlink
)

Directories

Path Synopsis
Package testsuite provides a integration testing suite for lisafs.
Package testsuite provides a integration testing suite for lisafs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL