writable

package
v0.41.0-rc1 Latest Latest
Warning

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

Go to latest
Published: Apr 13, 2026 License: Apache-2.0, MIT, Apache-2.0, + 1 more Imports: 16 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SymlinkTarget

func SymlinkTarget(f *mfs.File) string

SymlinkTarget extracts the symlink target from an MFS file, or returns "" if the file is not a TSymlink node. MFS represents symlinks as *mfs.File, so the DAG node's UnixFS type must be checked.

Types

type Config

type Config struct {
	StoreMtime bool            // persist mtime on create and open-for-write
	StoreMode  bool            // persist mode on chmod
	DAG        ipld.DAGService // required: read-only opens use it to bypass MFS desclock
	// RepoPath is the on-disk path of the IPFS repo (e.g. ~/.ipfs).
	// Statfs calls syscall.Statfs on this path so that the FUSE mount
	// reports how much free space is left on the volume that stores
	// MFS data. Without it tools like macOS Finder see zero free space
	// and refuse to copy files.
	RepoPath string
	// Blksize is the preferred I/O size advertised via st_blksize on
	// every stat. Callers should derive it from Import.UnixFSChunker via
	// fusemnt.BlksizeFromChunker so the hint matches the chunker MFS
	// will use for writes. If zero, NewDir writes fusemnt.DefaultBlksize
	// into this field in place, so fillAttr on every inode can read
	// cfg.Blksize without a nil-check on each stat.
	Blksize uint32
}

Config controls write-side behavior for writable mounts.

type Dir

type Dir struct {
	fs.Inode
	MFSDir *mfs.Directory
	Cfg    *Config
}

Dir is the FUSE adapter for MFS directories.

func NewDir

func NewDir(d *mfs.Directory, cfg *Config) *Dir

NewDir creates a Dir node backed by the given MFS directory. cfg.DAG is required: read-only file opens build a DagReader directly from it to avoid MFS's desclock (see FileInode.Open). Passing a nil DAG would silently re-introduce the rsync --inplace deadlock, so we fail loudly at construction time instead.

func (*Dir) Create

func (d *Dir) Create(ctx context.Context, name string, flags uint32, _ uint32, out *fuse.EntryOut) (*fs.Inode, fs.FileHandle, uint32, syscall.Errno)

func (*Dir) Getattr

func (d *Dir) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno

func (*Dir) Getxattr

func (d *Dir) Getxattr(_ context.Context, attr string, dest []byte) (uint32, syscall.Errno)

func (*Dir) Listxattr

func (d *Dir) Listxattr(_ context.Context, dest []byte) (uint32, syscall.Errno)

func (*Dir) Lookup

func (d *Dir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno)

func (*Dir) Mkdir

func (d *Dir) Mkdir(ctx context.Context, name string, _ uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno)

Mkdir creates a new directory under d.

TODO: boxo's mfs.Directory.Mkdir(name string) accepts no mode argument, so the caller's mode is silently dropped here. Tools that mkdir then chown without a follow-up chmod (some tar/rsync flows) see the default 0755 instead of the requested mode. Fixing this requires a boxo MFS API change.

func (*Dir) Readdir

func (d *Dir) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno)

func (*Dir) Rename

func (d *Dir) Rename(_ context.Context, oldName string, newParent fs.InodeEmbedder, newName string, _ uint32) syscall.Errno

Rename moves an entry across MFS directories.

TODO: this is not atomic. The source is unlinked before the destination is added, so any failure between the two steps loses the source entry. Making it atomic requires changes to MFS rename semantics (boxo/mfs does not currently expose an atomic rename).

func (*Dir) Rmdir

func (d *Dir) Rmdir(ctx context.Context, name string) syscall.Errno

func (*Dir) Setattr

func (d *Dir) Setattr(_ context.Context, _ fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno

Setattr handles chmod and mtime changes on directories. Tools like tar and rsync set directory timestamps after extraction.

Mode and mtime are stored as UnixFS optional metadata. The UnixFS spec supports all 12 permission bits, but boxo's MFS layer exposes only the lower 9 (ugo-rwx); setuid/setgid/sticky are silently dropped. FUSE mounts are always nosuid so these bits would have no execution effect anyway. See https://specs.ipfs.tech/unixfs/#dag-pb-optional-metadata

func (*Dir) Statfs

func (d *Dir) Statfs(_ context.Context, out *fuse.StatfsOut) syscall.Errno

Statfs reports disk-space statistics for the underlying filesystem. macOS Finder checks free space before copying; without this it reports "not enough free space" because go-fuse returns zeroed stats.

func (d *Dir) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno)

Symlink creates a new symlink in this directory.

func (d *Dir) Unlink(_ context.Context, name string) syscall.Errno

type FileHandle

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

FileHandle wraps an MFS file descriptor for FUSE operations. All methods are serialized by mu because the FUSE server dispatches each request in its own goroutine and the underlying DagModifier is not safe for concurrent use.

func (*FileHandle) Flush

func (fh *FileHandle) Flush(_ context.Context) syscall.Errno

Flush persists buffered writes to the DAG and invalidates the kernel's cached attrs so the next stat sees the updated size.

We intentionally ignore ctx: the underlying MFS flush cannot be safely canceled mid-operation, and abandoning it would leak a background goroutine that races with the subsequent Release.

Cache invalidation happens here (in addition to Release) because the kernel calls Flush synchronously inside close() but sends Release asynchronously after close() returns. Without this, a stat() immediately after close() could see stale cached attrs.

func (*FileHandle) Fsync

func (fh *FileHandle) Fsync(_ context.Context, _ uint32) syscall.Errno

Fsync flushes the write buffer through the open file descriptor and invalidates the kernel's cached attrs and content for this inode. Editors (vim, emacs) and databases call fsync after writing to ensure data reaches persistent storage; a fresh reader on the same path must see the synced bytes immediately, not the size the kernel cached from the initial Create response.

func (*FileHandle) Read

func (fh *FileHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)

func (*FileHandle) Release

func (fh *FileHandle) Release(_ context.Context) syscall.Errno

Release closes the descriptor and invalidates the kernel's cached content and attrs so readers opening the same path see the new data. Invalidation happens here (not in Flush) because fd.Close commits the final DAG node; Flush alone may not have the final size yet.

func (*FileHandle) Write

func (fh *FileHandle) Write(_ context.Context, data []byte, off int64) (uint32, syscall.Errno)

type FileInode

type FileInode struct {
	fs.Inode
	MFSFile *mfs.File
	Cfg     *Config
}

FileInode is the FUSE adapter for MFS file inodes.

func (*FileInode) Getattr

func (fi *FileInode) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno

func (*FileInode) Getxattr

func (fi *FileInode) Getxattr(_ context.Context, attr string, dest []byte) (uint32, syscall.Errno)

func (*FileInode) Listxattr

func (fi *FileInode) Listxattr(_ context.Context, dest []byte) (uint32, syscall.Errno)

func (*FileInode) Open

func (fi *FileInode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno)

func (*FileInode) Setattr

func (fi *FileInode) Setattr(_ context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno

Setattr handles chmod, mtime changes (touch), and ftruncate.

Mode and mtime are stored as UnixFS optional metadata. The UnixFS spec supports all 12 permission bits, but boxo's MFS layer exposes only the lower 9 (ugo-rwx); setuid/setgid/sticky are silently dropped. FUSE mounts are always nosuid so these bits would have no execution effect anyway. See https://specs.ipfs.tech/unixfs/#dag-pb-optional-metadata

With hanwen/go-fuse, the kernel passes the open file handle (fh) when the caller uses ftruncate(fd, size). This lets us truncate through the existing write descriptor without opening a second one. For truncate(path, size) without a handle, a temporary descriptor is opened; this may block if another writer holds MFS's desclock.

type Symlink struct {
	fs.Inode
	Target  string
	MFSFile *mfs.File // backing MFS node for mtime persistence
	Cfg     *Config
}

Symlink is the FUSE adapter for UnixFS TSymlink nodes on writable mounts. Target is resolved once at Lookup/Create time and never changes (POSIX symlinks are immutable; changing the target requires unlink + symlink).

func (*Symlink) Getattr

func (s *Symlink) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno
func (s *Symlink) Readlink(_ context.Context) ([]byte, syscall.Errno)

func (*Symlink) Setattr

func (s *Symlink) Setattr(_ context.Context, _ fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno

Setattr handles mtime changes on symlinks. Tools like rsync call lutimes on symlinks after creating them and treat ENOTSUP as an error. Every major FUSE filesystem (gocryptfs, rclone, sshfs, s3fs) implements Setattr on symlinks for this reason.

Mode is always 0777 per POSIX convention (access control uses the target's mode), so chmod requests are silently accepted but not stored.

Jump to

Keyboard shortcuts

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