stub all fuse/fs methods

This commit is contained in:
Ben Johnson
2020-10-12 15:19:48 -06:00
parent 0cf8c29e16
commit 5768fcc4cf
3 changed files with 400 additions and 64 deletions

99
fs.go
View File

@@ -2,17 +2,16 @@ package main
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
)
var _ fs.FS = (*FS)(nil)
var _ fs.FSDestroyer = (*FS)(nil)
var _ fs.FSStatfser = (*FS)(nil)
// var _ fs.FSInodeGenerator = (*FS)(nil)
type FS struct {
SourcePath string
@@ -20,68 +19,40 @@ type FS struct {
// Root returns the file system root.
func (f *FS) Root() (fs.Node, error) {
return &File{fs: f}, nil
return &Node{fs: f}, nil
}
var _ fs.Node = (*File)(nil)
type File struct {
fs *FS // base filesystem
path string // path within file system
// Destroy is called when the file system is shutting down.
//
// Linux only sends this request for block device backed (fuseblk)
// filesystems, to allow them to flush writes to disk before the
// unmount completes.
func (f *FS) Destroy() {
// TODO: Flush writes?
}
func (f *File) srcpath() string {
return filepath.Join(f.fs.SourcePath, f.path)
// Statfs is called to obtain file system metadata.
// It should write that data to resp.
func (f *FS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error {
panic("TODO")
}
func (f *File) Attr(ctx context.Context, a *fuse.Attr) error {
fi, err := os.Stat(f.srcpath())
if err != nil {
return err
}
statt := fi.Sys().(*syscall.Stat_t)
// TODO: Cache attr w/ a.Valid?
if f.path == "" {
a.Inode = 1
} else {
a.Inode = statt.Ino
}
a.Size = uint64(fi.Size())
a.Blocks = uint64(statt.Blocks)
a.Atime = time.Unix(statt.Atim.Sec, statt.Atim.Nsec).UTC()
a.Mtime = time.Unix(statt.Mtim.Sec, statt.Mtim.Nsec).UTC()
a.Ctime = time.Unix(statt.Ctim.Sec, statt.Ctim.Nsec).UTC()
a.Mode = fi.Mode()
a.Nlink = uint32(statt.Nlink)
a.Uid = uint32(statt.Uid)
a.Gid = uint32(statt.Gid)
a.Rdev = uint32(statt.Rdev)
a.BlockSize = uint32(statt.Blksize)
return nil
}
func (f *File) Lookup(ctx context.Context, name string) (fs.Node, error) {
path := filepath.Join(f.path, name)
srcpath := filepath.Join(f.fs.SourcePath, path)
if _, err := os.Stat(srcpath); os.IsNotExist(err) {
return nil, syscall.ENOENT
}
return &File{fs: f.fs, path: path}, nil
}
func (f *File) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
fis, err := ioutil.ReadDir(f.srcpath())
if err != nil {
return nil, err
}
ents := make([]fuse.Dirent, len(fis))
for i, fi := range fis {
statt := fi.Sys().(*syscall.Stat_t)
ents[i] = fuse.Dirent{Inode: statt.Ino, Name: fi.Name()}
}
return ents, nil
}
// GenerateInode is called to pick a dynamic inode number when it
// would otherwise be 0.
//
// Not all filesystems bother tracking inodes, but FUSE requires
// the inode to be set, and fewer duplicates in general makes UNIX
// tools work better.
//
// Operations where the nodes may return 0 inodes include Getattr,
// Setattr and ReadDir.
//
// If FS does not implement FSInodeGenerator, GenerateDynamicInode
// is used.
//
// Implementing this is useful to e.g. constrain the range of
// inode values used for dynamic inodes.
//
// Non-zero return values should be greater than 1, as that is
// always used for the root inode.
// func (f *FS) GenerateInode(parentInode uint64, name string) uint64 {}

124
handle.go Normal file
View File

@@ -0,0 +1,124 @@
package main
import (
"context"
"bazil.org/fuse"
"bazil.org/fuse/fs"
)
var _ fs.HandleFlockLocker = (*Handle)(nil)
var _ fs.HandleFlusher = (*Handle)(nil)
var _ fs.HandleLocker = (*Handle)(nil)
var _ fs.HandlePOSIXLocker = (*Handle)(nil)
var _ fs.HandlePoller = (*Handle)(nil)
var _ fs.HandleReadAller = (*Handle)(nil)
var _ fs.HandleReadDirAller = (*Handle)(nil)
var _ fs.HandleReader = (*Handle)(nil)
var _ fs.HandleReleaser = (*Handle)(nil)
var _ fs.HandleWriter = (*Handle)(nil)
type Handle struct{}
// Flush is called each time the file or directory is closed.
// Because there can be multiple file descriptors referring to a
// single opened file, Flush can be called multiple times.
func (h *Handle) Flush(ctx context.Context, req *fuse.FlushRequest) error {
panic("TODO")
}
func (h *Handle) ReadAll(ctx context.Context) ([]byte, error) {
panic("TODO")
}
func (h *Handle) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
panic("TODO")
}
// Read requests to read data from the handle.
//
// There is a page cache in the kernel that normally submits only
// page-aligned reads spanning one or more pages. However, you
// should not rely on this. To see individual requests as
// submitted by the file system clients, set OpenDirectIO.
//
// Note that reads beyond the size of the file as reported by Attr
// are not even attempted (except in OpenDirectIO mode).
func (h *Handle) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
panic("TODO")
}
// Write requests to write data into the handle at the given offset.
// Store the amount of data written in resp.Size.
//
// There is a writeback page cache in the kernel that normally submits
// only page-aligned writes spanning one or more pages. However,
// you should not rely on this. To see individual requests as
// submitted by the file system clients, set OpenDirectIO.
//
// Writes that grow the file are expected to update the file size
// (as seen through Attr). Note that file size changes are
// communicated also through Setattr.
func (h *Handle) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
panic("TODO")
}
func (h *Handle) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
panic("TODO")
}
// Poll checks whether the handle is currently ready for I/O, and
// may request a wakeup when it is.
//
// Poll should always return quickly. Clients waiting for
// readiness can be woken up by passing the return value of
// PollRequest.Wakeup to fs.Server.NotifyPollWakeup or
// fuse.Conn.NotifyPollWakeup.
//
// To allow supporting poll for only some of your Nodes/Handles,
// the default behavior is to report immediate readiness. If your
// FS does not support polling and you want to minimize needless
// requests and log noise, implement NodePoller and return
// syscall.ENOSYS.
//
// The Go runtime uses epoll-based I/O whenever possible, even for
// regular files.
func (h *Handle) Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error {
panic("TODO")
}
// Lock tries to acquire a lock on a byte range of the node. If a
// conflicting lock is already held, returns syscall.EAGAIN.
//
// LockRequest.LockOwner is a file-unique identifier for this
// lock, and will be seen in calls releasing this lock
// (UnlockRequest, ReleaseRequest, FlushRequest) and also
// in e.g. ReadRequest, WriteRequest.
func (h *Handle) Lock(ctx context.Context, req *fuse.LockRequest) error {
panic("TODO")
}
// LockWait acquires a lock on a byte range of the node, waiting
// until the lock can be obtained (or context is canceled).
func (h *Handle) LockWait(ctx context.Context, req *fuse.LockWaitRequest) error {
panic("TODO")
}
// Unlock releases the lock on a byte range of the node. Locks can
// be released also implicitly, see HandleFlockLocker and
// HandlePOSIXLocker.
func (h *Handle) Unlock(ctx context.Context, req *fuse.UnlockRequest) error {
panic("TODO")
}
// QueryLock returns the current state of locks held for the byte
// range of the node.
//
// See QueryLockRequest for details on how to respond.
//
// To simplify implementing this method, resp.Lock is prefilled to
// have Lock.Type F_UNLCK, and the whole struct should be
// overwritten for in case of conflicting locks.
func (h *Handle) QueryLock(ctx context.Context, req *fuse.QueryLockRequest, resp *fuse.QueryLockResponse) error {
panic("TODO")
}

241
node.go Normal file
View File

@@ -0,0 +1,241 @@
package main
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
)
var _ fs.Node = (*Node)(nil)
var _ fs.NodeAccesser = (*Node)(nil)
var _ fs.NodeCreater = (*Node)(nil)
var _ fs.NodeForgetter = (*Node)(nil)
var _ fs.NodeFsyncer = (*Node)(nil)
var _ fs.NodeGetattrer = (*Node)(nil)
var _ fs.NodeGetxattrer = (*Node)(nil)
var _ fs.NodeLinker = (*Node)(nil)
var _ fs.NodeListxattrer = (*Node)(nil)
var _ fs.NodeMkdirer = (*Node)(nil)
var _ fs.NodeMknoder = (*Node)(nil)
var _ fs.NodeOpener = (*Node)(nil)
var _ fs.NodePoller = (*Node)(nil)
var _ fs.NodeReadlinker = (*Node)(nil)
var _ fs.NodeRemover = (*Node)(nil)
var _ fs.NodeRemovexattrer = (*Node)(nil)
var _ fs.NodeRenamer = (*Node)(nil)
var _ fs.NodeSetattrer = (*Node)(nil)
var _ fs.NodeSetxattrer = (*Node)(nil)
var _ fs.NodeStringLookuper = (*Node)(nil)
var _ fs.NodeSymlinker = (*Node)(nil)
// var _ fs.NodeRequestLookuper = (*Node)(nil)
type Node struct {
fs *FS // base filesystem
path string // path within file system
}
func (n *Node) srcpath() string {
return filepath.Join(n.fs.SourcePath, n.path)
}
func (n *Node) Attr(ctx context.Context, a *fuse.Attr) error {
fi, err := os.Stat(n.srcpath())
if err != nil {
return err
}
statt := fi.Sys().(*syscall.Stat_t)
// TODO: Cache attr w/ a.Valid?
if n.path == "" {
a.Inode = 1
} else {
a.Inode = statt.Ino
}
a.Size = uint64(fi.Size())
a.Blocks = uint64(statt.Blocks)
a.Atime = time.Unix(statt.Atim.Sec, statt.Atim.Nsec).UTC()
a.Mtime = time.Unix(statt.Mtim.Sec, statt.Mtim.Nsec).UTC()
a.Ctime = time.Unix(statt.Ctim.Sec, statt.Ctim.Nsec).UTC()
a.Mode = fi.Mode()
a.Nlink = uint32(statt.Nlink)
a.Uid = uint32(statt.Uid)
a.Gid = uint32(statt.Gid)
a.Rdev = uint32(statt.Rdev)
a.BlockSize = uint32(statt.Blksize)
return nil
}
// Lookup looks up a specific entry in the receiver,
// which must be a directory. Lookup should return a Node
// corresponding to the entry. If the name does not exist in
// the directory, Lookup should return ENOENT.
//
// Lookup need not to handle the names "." and "..".
func (n *Node) Lookup(ctx context.Context, name string) (fs.Node, error) {
path := filepath.Join(n.path, name)
srcpath := filepath.Join(n.fs.SourcePath, path)
if _, err := os.Stat(srcpath); os.IsNotExist(err) {
return nil, syscall.ENOENT
}
return &Node{fs: n.fs, path: path}, nil
}
func (n *Node) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
fis, err := ioutil.ReadDir(n.srcpath())
if err != nil {
return nil, err
}
ents := make([]fuse.Dirent, len(fis))
for i, fi := range fis {
statt := fi.Sys().(*syscall.Stat_t)
ents[i] = fuse.Dirent{Inode: statt.Ino, Name: fi.Name()}
}
return ents, nil
}
// Getattr obtains the standard metadata for the receiver.
// It should store that metadata in resp.
//
// If this method is not implemented, the attributes will be
// generated based on Attr(), with zero values filled in.
func (n *Node) Getattr(ctx context.Context, req *fuse.GetattrRequest, resp *fuse.GetattrResponse) error {
panic("TODO")
}
// Setattr sets the standard metadata for the receiver.
//
// Note, this is also used to communicate changes in the size of
// the file, outside of Writes.
//
// req.Valid is a bitmask of what fields are actually being set.
// For example, the method should not change the mode of the file
// unless req.Valid.Mode() is true.
func (n *Node) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
panic("TODO")
}
// Symlink creates a new symbolic link in the receiver, which must be a directory.
//
// TODO is the above true about directories?
func (n *Node) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) { panic("TODO") }
// Readlink reads a symbolic link.
func (n *Node) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
panic("TODO")
}
// Link creates a new directory entry in the receiver based on an
// existing Node. Receiver must be a directory.
func (n *Node) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) {
panic("TODO")
}
// Remove removes the entry with the given name from
// the receiver, which must be a directory. The entry to be removed
// may correspond to a file (unlink) or to a directory (rmdir).
func (n *Node) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
panic("TODO")
}
// Access checks whether the calling context has permission for
// the given operations on the receiver. If so, Access should
// return nil. If not, Access should return EPERM.
//
// Note that this call affects the result of the access(2) system
// call but not the open(2) system call. If Access is not
// implemented, the Node behaves as if it always returns nil
// (permission granted), relying on checks in Open instead.
func (n *Node) Access(ctx context.Context, req *fuse.AccessRequest) error {
panic("TODO")
}
//type NodeRequestLookuper interface {
// // Lookup looks up a specific entry in the receiver.
// // See NodeStringLookuper for more.
// Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (Node, error)
//}
func (n *Node) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
panic("TODO")
}
// Open opens the receiver. After a successful open, a client
// process has a file descriptor referring to this Handle.
//
// Open can also be also called on non-files. For example,
// directories are Opened for ReadDir or fchdir(2).
//
// If this method is not implemented, the open will always
// succeed, and the Node itself will be used as the Handle.
//
// XXX note about access. XXX OpenFlags.
func (n *Node) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
panic("TODO")
}
// Create creates a new directory entry in the receiver, which
// must be a directory.
func (n *Node) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
panic("TODO")
}
// Forget about this node. This node will not receive further
// method calls.
//
// Forget is not necessarily seen on unmount, as all nodes are
// implicitly forgotten as part of the unmount.
func (n *Node) Forget() { panic("TODO") }
func (n *Node) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
panic("TODO")
}
func (n *Node) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, error) {
panic("TODO")
}
func (n *Node) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
panic("TODO")
}
// Getxattr gets an extended attribute by the given name from the
// node.
//
// If there is no xattr by that name, returns fuse.ErrNoXattr.
func (n *Node) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
panic("TODO")
}
// Listxattr lists the extended attributes recorded for the node.
func (n *Node) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
panic("TODO")
}
// Setxattr sets an extended attribute with the given name and
// value for the node.
func (n *Node) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
panic("TODO")
}
// Removexattr removes an extended attribute for the name.
//
// If there is no xattr by that name, returns fuse.ErrNoXattr.
func (n *Node) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
panic("TODO")
}
// Poll checks whether the node is currently ready for I/O, and
// may request a wakeup when it is. See HandlePoller.
func (n *Node) Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error {
panic("TODO")
}