Help us shape the future of Go! Take the 2026 Go Developer Survey

remote

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: BSD-3-Clause Imports: 12 Imported by: 0

README

remote

A go-filesystems filesystem served over a connection: one process opens the files, another serves them. Serve answers requests against any filesystem.Filesystem; New is that filesystem at the other end of the connection, with the same interfaces and the same errors.

It exists so that a file server can keep the process that talks to the network apart from the process that touches the files. go-fileshare/fileshare uses it to run a person's file access as that person: a worker started under their uid serves the directory, and the front that parses SMB, WebDAV or SFTP calls it through remote (fileshare's docs/identity.md).

// in the process that may open the files
go remote.Serve(conn, osfsTree)

// in the process that serves them
fsys, err := remote.New(conn) // a filesystem.Filesystem, an Opener, ...

conn is any io.ReadWriteCloser: a unix socketpair handed to a child process, a pipe, a TCP connection. Pure Go, CGO_ENABLED=0, every platform Go builds for.

What crosses, and what is kept

  • Every method of filesystem.Filesystem, and of the optional interfaces a protocol server looks for: Opener, Symlinker, HardLinker, Truncater, MetadataSetter and LabelReader. The client always has them. Those the server's filesystem lacks answer errors.ErrUnsupported (Label: ""), and Can says which are really there.
  • Errors keep their meaning.
    • An errno comes back as itself, so errors.Is(err, fs.ErrNotExist) answers as it did on the server.
    • Any other error comes back as its text, with the kinds it was (fs.ErrNotExist, ErrExist, ErrPermission, ErrInvalid, ErrClosed, errors.ErrUnsupported).
    • A *fs.PathError, *os.LinkError or *os.SyscallError is rebuilt as itself, with the same text.
  • A Stat keeps the modification time its driver gave, in the form it gave it: ModTime() int64 (as webdav, sftp and nfs ask for it) or ModTime() time.Time (as s3 does). Where the driver gave none, none is invented.
  • An opened file is held by the server. Reads and writes are calls, in pieces of at most 1 MiB. ReadAt keeps io.ReaderAt's contract to the letter, and a file is a filesystem.WritableFile exactly when the server's was. Files the client leaves open are closed by the server when the connection ends.

The peer is not trusted

Each side treats the other's bytes as hostile.

  • Frames: a frame is at most MaxFrame (32 MiB). Its length is checked before anything is allocated for it, and every string and count inside it is checked against what is left.
  • Unknown ops and replies: an unknown op, or a reply to a call never made, ends the connection.
  • Limits: a server works on at most 64 requests at once and holds at most 4096 files per connection.

Each of these is a test. Each was also checked by removing it: ten defences, ten tests that fail without them. One of those tests used to pass for the wrong reason. It checked only that a 4 GiB frame was refused, without checking why: the server believed the length, allocated the 4 GiB, and failed later on the closed pipe. It now checks the reason.

What it costs

Reading 64 MiB in 1 MiB pieces over a unix socketpair, on an Apple M-series laptop under heavy load (load average about 155), gave:

  • through remote: about 0.3 GB/s;
  • through osfs directly: about 10 GB/s, from the page cache.

Those figures are an order of magnitude, not a benchmark. 0.3 GB/s keeps up with a 1 Gb/s network and would limit a 10 Gb/s one. Passing the file descriptor across a unix socket (SCM_RIGHTS) would remove this cost: the server opens the file under its own identity, and the client reads and writes it directly. That is the next step, and it will be measured. The benchmark is go test -bench Read.

Tests

The client is tested against the same operations done directly: every step runs on osfs locally and on osfs through remote. The results, the error types, the errnos, the kinds errors.Is recognises, and the text must all be equal. The first run of that test found that an *os.LinkError (from Rename) had come back as plain text.

Coverage is 100% and enforced, under -race. CI runs the tests on Linux (amd64, arm64), macOS and Windows, and emulated on riscv64, loong64, ppc64le and s390x. It builds for FreeBSD, OpenBSD, NetBSD, Plan 9, js/wasm and wasip1.

Licence

BSD 3-Clause.

Documentation

Overview

Package remote is a go-filesystems filesystem served over a connection: Serve answers requests against any filesystem.Filesystem, and New is that filesystem at the other end, with the same interfaces and the same errors.

It keeps the process that touches the files apart from the process that talks to the network -- for go-fileshare/fileshare, a worker running as the person whose files they are, behind a front that holds no privilege. Neither side trusts the other's bytes: every frame and every field in it is bounded before anything is allocated for it.

Index

Constants

View Source
const (
	CanLabel    = Capability(capLabel)
	CanSymlink  = Capability(capSymlink)
	CanLink     = Capability(capLink)
	CanTruncate = Capability(capTruncate)
	CanMetadata = Capability(capMetadata)
	CanOpen     = Capability(capOpen)
)
View Source
const MaxFrame = 32 << 20

MaxFrame bounds one frame: a ReadFile or WriteFile carries the whole file, so this is also the largest file those two move. Larger ones go through OpenFile, whose reads and writes are split into pieces of maxChunk.

Variables

This section is empty.

Functions

func Serve

func Serve(conn io.ReadWriteCloser, fsys filesystem.Filesystem) error

Serve answers the requests that arrive on conn against fsys, until conn is closed. It sends the hello first, then serves up to maxInFlight requests at once. It never closes fsys -- that is its owner's -- but it closes every file its peer opened and did not close, and conn.

The peer is not trusted: a frame that does not decode, an unknown op, a reply id the server never sent, or a handle that is not open ends the connection or is refused, and nothing a frame says is allocated before it is checked against the bounds above.

Types

type Capability

type Capability uint64

Capability is something a server's filesystem may or may not do.

type Error

type Error struct {
	Msg string
	// contains filtered or unexported fields
}

Error is an error the server's filesystem returned that was not an errno: its text, and the kinds it was (fs.ErrNotExist, fs.ErrPermission, ...), which errors.Is still recognises.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

func (e *Error) Is(target error) bool

Is reports the kinds the original error was.

type FS

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

FS is a filesystem served by Serve at the other end of a connection. It implements filesystem.Filesystem and every optional interface a protocol server looks for -- Opener, Symlinker, HardLinker, Truncater, MetadataSetter, LabelReader. What the server's filesystem does not have answers errors.ErrUnsupported (Label: ""), so a type assertion succeeds where the server's would have failed; Can says what is really there.

Calls may be made from any number of goroutines; each waits for its own reply.

func New

func New(conn io.ReadWriteCloser) (*FS, error)

New is the filesystem served at the other end of conn. It reads the server's hello before returning, so a connection that is not a remote filesystem fails here rather than at the first call.

func (*FS) Can

func (f *FS) Can(c Capability) bool

Can says whether the server's filesystem has c.

func (*FS) Chmod

func (f *FS) Chmod(path string, perm os.FileMode) error

func (*FS) Chown

func (f *FS) Chown(path string, uid, gid uint32) error

func (*FS) Chtimes

func (f *FS) Chtimes(path string, atime, mtime time.Time) error

func (*FS) Close

func (f *FS) Close() error

Close closes the connection. The server's filesystem stays open -- it is its owner's -- and the files opened through f are closed by the server.

func (*FS) DeleteDir

func (f *FS) DeleteDir(path string) error

func (*FS) DeleteFile

func (f *FS) DeleteFile(path string) error

func (*FS) Label

func (f *FS) Label() string

Label is the server's volume label, or "" when its filesystem has none to read (or the call fails: Label has no error to return).

func (f *FS) Link(oldPath, newPath string) error

func (*FS) ListDir

func (f *FS) ListDir(path string) ([]filesystem.DirEntry, error)

func (*FS) MkDir

func (f *FS) MkDir(path string, perm os.FileMode) error

func (*FS) OpenFile

func (f *FS) OpenFile(path string) (filesystem.File, error)

OpenFile opens path on the server, which holds it open until Close. The result is a filesystem.WritableFile exactly when the server's was.

func (*FS) ReadFile

func (f *FS) ReadFile(path string) ([]byte, error)
func (f *FS) ReadLink(path string) (string, error)

func (*FS) Rename

func (f *FS) Rename(oldPath, newPath string) error

func (*FS) Stat

func (f *FS) Stat(path string) (filesystem.Stat, error)
func (f *FS) Symlink(target, linkPath string) error

func (*FS) Truncate

func (f *FS) Truncate(path string, newSize int64) error

func (*FS) WriteFile

func (f *FS) WriteFile(path string, data []byte, perm os.FileMode) error

Jump to

Keyboard shortcuts

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