From 5768fcc4cf74e3da02cc248d331f13bc5a366622 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Mon, 12 Oct 2020 15:19:48 -0600 Subject: [PATCH] stub all fuse/fs methods --- fs.go | 99 ++++++++-------------- handle.go | 124 ++++++++++++++++++++++++++++ node.go | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 400 insertions(+), 64 deletions(-) create mode 100644 handle.go create mode 100644 node.go diff --git a/fs.go b/fs.go index 54fe34b..31edebf 100644 --- a/fs.go +++ b/fs.go @@ -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 {} diff --git a/handle.go b/handle.go new file mode 100644 index 0000000..dcbf20b --- /dev/null +++ b/handle.go @@ -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") +} diff --git a/node.go b/node.go new file mode 100644 index 0000000..18e44c1 --- /dev/null +++ b/node.go @@ -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") +}