Remade the package fuse interface around reading a sequence of op structs.
This aovids the fundamental race in #3.geesefs-0-30-9
commit
336525bfec
|
@ -8,9 +8,6 @@ and contains a decent amount of canned behavior.
|
|||
|
||||
The chief improvements and/or differences from the bazil.org packages are:
|
||||
|
||||
* A single interface (`fuse.FileSystem`) for all of the methods that you might
|
||||
care about.
|
||||
|
||||
* No surprises in the form of magic/default behaviors. You must provide an
|
||||
implementation for every method in the interface. Embed a
|
||||
`fuseutil.NotImplementedFileSystem` struct to have default implementations
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
)
|
||||
|
||||
// A connection to the fuse kernel process.
|
||||
type Connection struct {
|
||||
logger *log.Logger
|
||||
wrapped *bazilfuse.Conn
|
||||
}
|
||||
|
||||
// Responsibility for closing the wrapped connection is transferred to the
|
||||
// result. You must call c.close() eventually.
|
||||
func newConnection(
|
||||
logger *log.Logger,
|
||||
wrapped *bazilfuse.Conn) (c *Connection, err error) {
|
||||
c = &Connection{
|
||||
logger: logger,
|
||||
wrapped: wrapped,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Read the next op from the kernel process. Return io.EOF if the kernel has
|
||||
// closed the connection.
|
||||
//
|
||||
// This function delivers ops in exactly the order they are received from
|
||||
// /dev/fuse. Be wary of naively calling it concurrently: you probably want
|
||||
// ordering guarantees between e.g. write ops and flush ops. For example,
|
||||
// close(2) causes WriteFileOps to be issued before a FlushFileOp, but doesn't
|
||||
// wait for their response before issuing the latter (cf.
|
||||
// https://github.com/jacobsa/fuse/issues/3).
|
||||
func (c *Connection) ReadOp() (op fuseops.Op, err error) {
|
||||
var bfReq bazilfuse.Request
|
||||
|
||||
// Keep going until we find a request we know how to convert.
|
||||
for {
|
||||
// Read a bazilfuse request.
|
||||
bfReq, err = c.wrapped.ReadRequest()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.logger.Printf("Received: %v", bfReq)
|
||||
|
||||
// Special case: responding to this is required to make mounting work on OS
|
||||
// X. We don't currently expose the capability for the file system to
|
||||
// intercept this.
|
||||
if statfsReq, ok := bfReq.(*bazilfuse.StatfsRequest); ok {
|
||||
c.logger.Println("Responding OK to Statfs.")
|
||||
statfsReq.Respond(&bazilfuse.StatfsResponse{})
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert it, if possible.
|
||||
if op = fuseops.Convert(bfReq, c.logger); op == nil {
|
||||
c.logger.Printf("Returning ENOSYS for unknown bazilfuse request: %v", bfReq)
|
||||
bfReq.RespondError(ENOSYS)
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) waitForReady() (err error) {
|
||||
<-c.wrapped.Ready
|
||||
err = c.wrapped.MountError
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Connection) close() (err error) {
|
||||
err = c.wrapped.Close()
|
||||
return
|
||||
}
|
12
doc.go
12
doc.go
|
@ -16,15 +16,13 @@
|
|||
//
|
||||
// The primary elements of interest are:
|
||||
//
|
||||
// * The FileSystem interface, which defines the methods a file system must
|
||||
// implement.
|
||||
//
|
||||
// * fuseutil.NotImplementedFileSystem, which may be embedded to obtain
|
||||
// default implementations for all methods that are not of interest to a
|
||||
// particular file system.
|
||||
// * The fuseops package, which defines the operations that fuse might send
|
||||
// to your userspace daemon.
|
||||
//
|
||||
// * Mount, a function that allows for mounting a file system.
|
||||
//
|
||||
// In order to use this package to mount file systems on OS X, the system must
|
||||
// have FUSE for OS X installed: http://osxfuse.github.io/
|
||||
// have FUSE for OS X installed (see http://osxfuse.github.io/). Do note that
|
||||
// there are several OS X-specific oddities; grep through the documentation for
|
||||
// more info.
|
||||
package fuse
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
|
||||
const (
|
||||
// Errors corresponding to kernel error numbers. These may be treated
|
||||
// specially when returned by a FileSystem method.
|
||||
// specially by fuseops.Op.Respond methods.
|
||||
EINVAL = bazilfuse.Errno(syscall.EINVAL)
|
||||
EIO = bazilfuse.EIO
|
||||
ENOENT = bazilfuse.ENOENT
|
||||
|
|
888
file_system.go
888
file_system.go
|
@ -1,888 +0,0 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// An interface that must be implemented by file systems to be mounted with
|
||||
// FUSE. See also the comments on request and response structs.
|
||||
//
|
||||
// Not all methods need to have interesting implementations. Embed a field of
|
||||
// type fuseutil.NotImplementedFileSystem to inherit defaults that return
|
||||
// ENOSYS to the kernel.
|
||||
//
|
||||
// Must be safe for concurrent access via all methods.
|
||||
type FileSystem interface {
|
||||
// This method is called once when mounting the file system. It must succeed
|
||||
// in order for the mount to succeed.
|
||||
Init(
|
||||
ctx context.Context,
|
||||
req *InitRequest) (*InitResponse, error)
|
||||
|
||||
///////////////////////////////////
|
||||
// Inodes
|
||||
///////////////////////////////////
|
||||
|
||||
// Look up a child by name within a parent directory. The kernel calls this
|
||||
// when resolving user paths to dentry structs, which are then cached.
|
||||
LookUpInode(
|
||||
ctx context.Context,
|
||||
req *LookUpInodeRequest) (*LookUpInodeResponse, error)
|
||||
|
||||
// Refresh the attributes for an inode whose ID was previously returned by
|
||||
// LookUpInode. The kernel calls this when the FUSE VFS layer's cache of
|
||||
// inode attributes is stale. This is controlled by the AttributesExpiration
|
||||
// field of responses to LookUp, etc.
|
||||
GetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *GetInodeAttributesRequest) (*GetInodeAttributesResponse, error)
|
||||
|
||||
// Change attributes for an inode.
|
||||
//
|
||||
// The kernel calls this for obvious cases like chmod(2), and for less
|
||||
// obvious cases like ftrunctate(2).
|
||||
SetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *SetInodeAttributesRequest) (*SetInodeAttributesResponse, error)
|
||||
|
||||
// Forget an inode ID previously issued (e.g. by LookUpInode or MkDir). The
|
||||
// kernel calls this when removing an inode from its internal caches.
|
||||
ForgetInode(
|
||||
ctx context.Context,
|
||||
req *ForgetInodeRequest) (*ForgetInodeResponse, error)
|
||||
|
||||
///////////////////////////////////
|
||||
// Inode creation
|
||||
///////////////////////////////////
|
||||
|
||||
// Create a directory inode as a child of an existing directory inode. The
|
||||
// kernel sends this in response to a mkdir(2) call.
|
||||
//
|
||||
// The kernel appears to verify the name doesn't already exist (mkdir calls
|
||||
// mkdirat calls user_path_create calls filename_create, which verifies:
|
||||
// http://goo.gl/FZpLu5). But volatile file systems and paranoid non-volatile
|
||||
// file systems should check for the reasons described below on CreateFile.
|
||||
MkDir(
|
||||
ctx context.Context,
|
||||
req *MkDirRequest) (*MkDirResponse, error)
|
||||
|
||||
// Create a file inode and open it.
|
||||
//
|
||||
// The kernel calls this method when the user asks to open a file with the
|
||||
// O_CREAT flag and the kernel has observed that the file doesn't exist. (See
|
||||
// for example lookup_open, http://goo.gl/PlqE9d).
|
||||
//
|
||||
// However it's impossible to tell for sure that all kernels make this check
|
||||
// in all cases and the official fuse documentation is less than encouraging
|
||||
// (" the file does not exist, first create it with the specified mode, and
|
||||
// then open it"). Therefore file systems would be smart to be paranoid and
|
||||
// check themselves, returning EEXIST when the file already exists. This of
|
||||
// course particularly applies to file systems that are volatile from the
|
||||
// kernel's point of view.
|
||||
CreateFile(
|
||||
ctx context.Context,
|
||||
req *CreateFileRequest) (*CreateFileResponse, error)
|
||||
|
||||
///////////////////////////////////
|
||||
// Inode destruction
|
||||
///////////////////////////////////
|
||||
|
||||
// Unlink a directory from its parent. Because directories cannot have a link
|
||||
// count above one, this means the directory inode should be deleted as well
|
||||
// once the kernel calls ForgetInode.
|
||||
//
|
||||
// The file system is responsible for checking that the directory is empty.
|
||||
//
|
||||
// Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf)
|
||||
RmDir(
|
||||
ctx context.Context,
|
||||
req *RmDirRequest) (*RmDirResponse, error)
|
||||
|
||||
// Unlink a file from its parent. If this brings the inode's link count to
|
||||
// zero, the inode should be deleted once the kernel calls ForgetInode. It
|
||||
// may still be referenced before then if a user still has the file open.
|
||||
//
|
||||
// Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C)
|
||||
Unlink(
|
||||
ctx context.Context,
|
||||
req *UnlinkRequest) (*UnlinkResponse, error)
|
||||
|
||||
///////////////////////////////////
|
||||
// Directory handles
|
||||
///////////////////////////////////
|
||||
|
||||
// Open a directory inode.
|
||||
//
|
||||
// On Linux the kernel calls this method when setting up a struct file for a
|
||||
// particular inode with type directory, usually in response to an open(2)
|
||||
// call from a user-space process. On OS X it may not be called for every
|
||||
// open(2) (cf. https://github.com/osxfuse/osxfuse/issues/199).
|
||||
OpenDir(
|
||||
ctx context.Context,
|
||||
req *OpenDirRequest) (*OpenDirResponse, error)
|
||||
|
||||
// Read entries from a directory previously opened with OpenDir.
|
||||
ReadDir(
|
||||
ctx context.Context,
|
||||
req *ReadDirRequest) (*ReadDirResponse, error)
|
||||
|
||||
// Release a previously-minted directory handle. The kernel calls this when
|
||||
// there are no more references to an open directory: all file descriptors
|
||||
// are closed and all memory mappings are unmapped.
|
||||
//
|
||||
// The kernel guarantees that the handle ID will not be used in further calls
|
||||
// to the file system (unless it is reissued by the file system).
|
||||
ReleaseDirHandle(
|
||||
ctx context.Context,
|
||||
req *ReleaseDirHandleRequest) (*ReleaseDirHandleResponse, error)
|
||||
|
||||
///////////////////////////////////
|
||||
// File handles
|
||||
///////////////////////////////////
|
||||
|
||||
// Open a file inode.
|
||||
//
|
||||
// On Linux the kernel calls this method when setting up a struct file for a
|
||||
// particular inode with type file, usually in response to an open(2) call
|
||||
// from a user-space process. On OS X it may not be called for every open(2)
|
||||
// (cf.https://github.com/osxfuse/osxfuse/issues/199).
|
||||
OpenFile(
|
||||
ctx context.Context,
|
||||
req *OpenFileRequest) (*OpenFileResponse, error)
|
||||
|
||||
// Read data from a file previously opened with CreateFile or OpenFile.
|
||||
//
|
||||
// Note that this method is not called for every call to read(2) by the end
|
||||
// user; some reads may be served by the page cache. See notes on Write for
|
||||
// more.
|
||||
ReadFile(
|
||||
ctx context.Context,
|
||||
req *ReadFileRequest) (*ReadFileResponse, error)
|
||||
|
||||
// Write data to a file previously opened with CreateFile or OpenFile.
|
||||
//
|
||||
// When the user writes data using write(2), the write goes into the page
|
||||
// cache and the page is marked dirty. Later the kernel may write back the
|
||||
// page via the FUSE VFS layer, causing this method to be called:
|
||||
//
|
||||
// * The kernel calls address_space_operations::writepage when a dirty page
|
||||
// needs to be written to backing store (cf. http://goo.gl/Ezbewg). Fuse
|
||||
// sets this to fuse_writepage (cf. http://goo.gl/IeNvLT).
|
||||
//
|
||||
// * (http://goo.gl/Eestuy) fuse_writepage calls fuse_writepage_locked.
|
||||
//
|
||||
// * (http://goo.gl/RqYIxY) fuse_writepage_locked makes a write request to
|
||||
// the userspace server.
|
||||
//
|
||||
// Note that writes *will* be received before a call to Flush when closing
|
||||
// the file descriptor to which they were written:
|
||||
//
|
||||
// * (http://goo.gl/PheZjf) fuse_flush calls write_inode_now, which appears
|
||||
// to start a writeback in the background (it talks about a "flusher
|
||||
// thread").
|
||||
//
|
||||
// * (http://goo.gl/1IiepM) fuse_flush then calls fuse_sync_writes, which
|
||||
// "[waits] for all pending writepages on the inode to finish".
|
||||
//
|
||||
// * (http://goo.gl/zzvxWv) Only then does fuse_flush finally send the
|
||||
// flush request.
|
||||
//
|
||||
WriteFile(
|
||||
ctx context.Context,
|
||||
req *WriteFileRequest) (*WriteFileResponse, error)
|
||||
|
||||
// Synchronize the current contents of an open file to storage.
|
||||
//
|
||||
// vfs.txt documents this as being called for by the fsync(2) system call
|
||||
// (cf. http://goo.gl/j9X8nB). Code walk for that case:
|
||||
//
|
||||
// * (http://goo.gl/IQkWZa) sys_fsync calls do_fsync, calls vfs_fsync, calls
|
||||
// vfs_fsync_range.
|
||||
//
|
||||
// * (http://goo.gl/5L2SMy) vfs_fsync_range calls f_op->fsync.
|
||||
//
|
||||
// Note that this is also called by fdatasync(2) (cf. http://goo.gl/01R7rF),
|
||||
// and may be called for msync(2) with the MS_SYNC flag (see the notes on
|
||||
// FlushFile).
|
||||
//
|
||||
// See also: FlushFile, which may perform a similar purpose when closing a
|
||||
// file (but which is not used in "real" file systems).
|
||||
SyncFile(
|
||||
ctx context.Context,
|
||||
req *SyncFileRequest) (*SyncFileResponse, error)
|
||||
|
||||
// Flush the current state of an open file to storage upon closing a file
|
||||
// descriptor.
|
||||
//
|
||||
// vfs.txt documents this as being called for each close(2) system call (cf.
|
||||
// http://goo.gl/FSkbrq). Code walk for that case:
|
||||
//
|
||||
// * (http://goo.gl/e3lv0e) sys_close calls __close_fd, calls filp_close.
|
||||
// * (http://goo.gl/nI8fxD) filp_close calls f_op->flush (fuse_flush).
|
||||
//
|
||||
// But note that this is also called in other contexts where a file
|
||||
// descriptor is closed, such as dup2(2) (cf. http://goo.gl/NQDvFS). In the
|
||||
// case of close(2), a flush error is returned to the user. For dup2(2), it
|
||||
// is not.
|
||||
//
|
||||
// One potentially significant case where this may not be called is mmap'd
|
||||
// files, where the behavior is complicated:
|
||||
//
|
||||
// * munmap(2) does not cause flushes (cf. http://goo.gl/j8B9g0).
|
||||
//
|
||||
// * On OS X, if a user modifies a mapped file via the mapping before
|
||||
// closing the file with close(2), the WriteFile calls for the
|
||||
// modifications may not be received before the FlushFile request for the
|
||||
// close(2) (cf. http://goo.gl/kVmNcx).
|
||||
//
|
||||
// * However, even on OS X you can arrange for writes via a mapping to be
|
||||
// flushed by calling msync(2) followed by close(2). On OS X msync(2)
|
||||
// will cause a WriteFile to go through and close(2) will cause a
|
||||
// FlushFile as usual (cf. http://goo.gl/kVmNcx). On Linux, msync(2) does
|
||||
// nothing unless you set the MS_SYNC flag, in which case it causes a
|
||||
// SyncFile (cf. http://goo.gl/P3mErk).
|
||||
//
|
||||
// In summary: if you make data durable in both FlushFile and SyncFile, then
|
||||
// your users can get safe behavior from mapped files by calling msync(2)
|
||||
// with MS_SYNC, followed by munmap(2), followed by close(2). On Linux, the
|
||||
// msync(2) appears to be optional because close(2) implies dirty page
|
||||
// writeback (cf. http://goo.gl/HyzLTT).
|
||||
//
|
||||
// Because of cases like dup2(2), calls to FlushFile are not necessarily one
|
||||
// to one with calls to OpenFile. They should not be used for reference
|
||||
// counting, and the handle must remain valid even after the method is called
|
||||
// (use ReleaseFileHandle to dispose of it).
|
||||
//
|
||||
// Typical "real" file systems do not implement this, presumably relying on
|
||||
// the kernel to write out the page cache to the block device eventually.
|
||||
// They can get away with this because a later open(2) will see the same
|
||||
// data. A file system that writes to remote storage however probably wants
|
||||
// to at least schedule a real flush, and maybe do it immediately in order to
|
||||
// return any errors that occur.
|
||||
FlushFile(
|
||||
ctx context.Context,
|
||||
req *FlushFileRequest) (*FlushFileResponse, error)
|
||||
|
||||
// Release a previously-minted file handle. The kernel calls this when there
|
||||
// are no more references to an open file: all file descriptors are closed
|
||||
// and all memory mappings are unmapped.
|
||||
//
|
||||
// The kernel guarantees that the handle ID will not be used in further calls
|
||||
// to the file system (unless it is reissued by the file system).
|
||||
ReleaseFileHandle(
|
||||
ctx context.Context,
|
||||
req *ReleaseFileHandleRequest) (*ReleaseFileHandleResponse, error)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Simple types
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// A 64-bit number used to uniquely identify a file or directory in the file
|
||||
// system. File systems may mint inode IDs with any value except for
|
||||
// RootInodeID.
|
||||
//
|
||||
// This corresponds to struct inode::i_no in the VFS layer.
|
||||
// (Cf. http://goo.gl/tvYyQt)
|
||||
type InodeID uint64
|
||||
|
||||
// A distinguished inode ID that identifies the root of the file system, e.g.
|
||||
// in a request to OpenDir or LookUpInode. Unlike all other inode IDs, which
|
||||
// are minted by the file system, the FUSE VFS layer may send a request for
|
||||
// this ID without the file system ever having referenced it in a previous
|
||||
// response.
|
||||
const RootInodeID = 1
|
||||
|
||||
func init() {
|
||||
// Make sure the constant above is correct. We do this at runtime rather than
|
||||
// defining the constant in terms of bazilfuse.RootID for two reasons:
|
||||
//
|
||||
// 1. Users can more clearly see that the root ID is low and can therefore
|
||||
// be used as e.g. an array index, with space reserved up to the root.
|
||||
//
|
||||
// 2. The constant can be untyped and can therefore more easily be used as
|
||||
// an array index.
|
||||
//
|
||||
if RootInodeID != bazilfuse.RootID {
|
||||
panic(
|
||||
fmt.Sprintf(
|
||||
"Oops, RootInodeID is wrong: %v vs. %v",
|
||||
RootInodeID,
|
||||
bazilfuse.RootID))
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes for a file or directory inode. Corresponds to struct inode (cf.
|
||||
// http://goo.gl/tvYyQt).
|
||||
type InodeAttributes struct {
|
||||
Size uint64
|
||||
|
||||
// The number of incoming hard links to this inode.
|
||||
Nlink uint64
|
||||
|
||||
// The mode of the inode. This is exposed to the user in e.g. the result of
|
||||
// fstat(2).
|
||||
//
|
||||
// Note that in contrast to the defaults for FUSE, this package mounts file
|
||||
// systems in a manner such that the kernel checks inode permissions in the
|
||||
// standard posix way. This is implemented by setting the default_permissions
|
||||
// mount option (cf. http://goo.gl/1LxOop and http://goo.gl/1pTjuk).
|
||||
//
|
||||
// For example, in the case of mkdir:
|
||||
//
|
||||
// * (http://goo.gl/JkdxDI) sys_mkdirat calls inode_permission.
|
||||
//
|
||||
// * (...) inode_permission eventually calls do_inode_permission.
|
||||
//
|
||||
// * (http://goo.gl/aGCsmZ) calls i_op->permission, which is
|
||||
// fuse_permission (cf. http://goo.gl/VZ9beH).
|
||||
//
|
||||
// * (http://goo.gl/5kqUKO) fuse_permission doesn't do anything at all for
|
||||
// several code paths if FUSE_DEFAULT_PERMISSIONS is unset. In contrast,
|
||||
// if that flag *is* set, then it calls generic_permission.
|
||||
//
|
||||
Mode os.FileMode
|
||||
|
||||
// Time information. See `man 2 stat` for full details.
|
||||
Atime time.Time // Time of last access
|
||||
Mtime time.Time // Time of last modification
|
||||
Ctime time.Time // Time of last modification to inode
|
||||
Crtime time.Time // Time of creation (OS X only)
|
||||
|
||||
// Ownership information
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
// A generation number for an inode. Irrelevant for file systems that won't be
|
||||
// exported over NFS. For those that will and that reuse inode IDs when they
|
||||
// become free, the generation number must change when an ID is reused.
|
||||
//
|
||||
// This corresponds to struct inode::i_generation in the VFS layer.
|
||||
// (Cf. http://goo.gl/tvYyQt)
|
||||
//
|
||||
// Some related reading:
|
||||
//
|
||||
// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
|
||||
// http://stackoverflow.com/q/11071996/1505451
|
||||
// http://goo.gl/CqvwyX
|
||||
// http://julipedia.meroh.net/2005/09/nfs-file-handles.html
|
||||
// http://goo.gl/wvo3MB
|
||||
//
|
||||
type GenerationNumber uint64
|
||||
|
||||
// An opaque 64-bit number used to identify a particular open handle to a file
|
||||
// or directory.
|
||||
//
|
||||
// This corresponds to fuse_file_info::fh.
|
||||
type HandleID uint64
|
||||
|
||||
// An offset into an open directory handle. This is opaque to FUSE, and can be
|
||||
// used for whatever purpose the file system desires. See notes on
|
||||
// ReadDirRequest.Offset for details.
|
||||
type DirOffset uint64
|
||||
|
||||
// A header that is included with every request.
|
||||
type RequestHeader struct {
|
||||
// Credentials information for the process making the request.
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
// Information about a child inode within its parent directory. Shared by the
|
||||
// responses for LookUpInode, MkDir, CreateFile, etc. Consumed by the kernel in
|
||||
// order to set up a dcache entry.
|
||||
type ChildInodeEntry struct {
|
||||
// The ID of the child inode. The file system must ensure that the returned
|
||||
// inode ID remains valid until a later call to ForgetInode.
|
||||
Child InodeID
|
||||
|
||||
// A generation number for this incarnation of the inode with the given ID.
|
||||
// See comments on type GenerationNumber for more.
|
||||
Generation GenerationNumber
|
||||
|
||||
// Current attributes for the child inode.
|
||||
//
|
||||
// When creating a new inode, the file system is responsible for initializing
|
||||
// and recording (where supported) attributes like time information,
|
||||
// ownership information, etc.
|
||||
//
|
||||
// Ownership information in particular must be set to something reasonable or
|
||||
// by default root will own everything and unprivileged users won't be able
|
||||
// to do anything useful. In traditional file systems in the kernel, the
|
||||
// function inode_init_owner (http://goo.gl/5qavg8) contains the
|
||||
// standards-compliant logic for this.
|
||||
Attributes InodeAttributes
|
||||
|
||||
// The FUSE VFS layer in the kernel maintains a cache of file attributes,
|
||||
// used whenever up to date information about size, mode, etc. is needed.
|
||||
//
|
||||
// For example, this is the abridged call chain for fstat(2):
|
||||
//
|
||||
// * (http://goo.gl/tKBH1p) fstat calls vfs_fstat.
|
||||
// * (http://goo.gl/3HeITq) vfs_fstat eventuall calls vfs_getattr_nosec.
|
||||
// * (http://goo.gl/DccFQr) vfs_getattr_nosec calls i_op->getattr.
|
||||
// * (http://goo.gl/dpKkst) fuse_getattr calls fuse_update_attributes.
|
||||
// * (http://goo.gl/yNlqPw) fuse_update_attributes uses the values in the
|
||||
// struct inode if allowed, otherwise calling out to the user-space code.
|
||||
//
|
||||
// In addition to obvious cases like fstat, this is also used in more subtle
|
||||
// cases like updating size information before seeking (http://goo.gl/2nnMFa)
|
||||
// or reading (http://goo.gl/FQSWs8).
|
||||
//
|
||||
// Most 'real' file systems do not set inode_operations::getattr, and
|
||||
// therefore vfs_getattr_nosec calls generic_fillattr which simply grabs the
|
||||
// information from the inode struct. This makes sense because these file
|
||||
// systems cannot spontaneously change; all modifications go through the
|
||||
// kernel which can update the inode struct as appropriate.
|
||||
//
|
||||
// In contrast, a FUSE file system may have spontaneous changes, so it calls
|
||||
// out to user space to fetch attributes. However this is expensive, so the
|
||||
// FUSE layer in the kernel caches the attributes if requested.
|
||||
//
|
||||
// This field controls when the attributes returned in this response and
|
||||
// stashed in the struct inode should be re-queried. Leave at the zero value
|
||||
// to disable caching.
|
||||
//
|
||||
// More reading:
|
||||
// http://stackoverflow.com/q/21540315/1505451
|
||||
AttributesExpiration time.Time
|
||||
|
||||
// The time until which the kernel may maintain an entry for this name to
|
||||
// inode mapping in its dentry cache. After this time, it will revalidate the
|
||||
// dentry.
|
||||
//
|
||||
// As in the discussion of attribute caching above, unlike real file systems,
|
||||
// FUSE file systems may spontaneously change their name -> inode mapping.
|
||||
// Therefore the FUSE VFS layer uses dentry_operations::d_revalidate
|
||||
// (http://goo.gl/dVea0h) to intercept lookups and revalidate by calling the
|
||||
// user-space LookUpInode method. However the latter may be slow, so it
|
||||
// caches the entries until the time defined by this field.
|
||||
//
|
||||
// Example code walk:
|
||||
//
|
||||
// * (http://goo.gl/M2G3tO) lookup_dcache calls d_revalidate if enabled.
|
||||
// * (http://goo.gl/ef0Elu) fuse_dentry_revalidate just uses the dentry's
|
||||
// inode if fuse_dentry_time(entry) hasn't passed. Otherwise it sends a
|
||||
// lookup request.
|
||||
//
|
||||
// Leave at the zero value to disable caching.
|
||||
//
|
||||
// Beware: this value is ignored on OS X, where entry caching is disabled by
|
||||
// default. See notes on MountConfig.EnableVnodeCaching for more.
|
||||
EntryExpiration time.Time
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Requests and responses
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type InitRequest struct {
|
||||
Header RequestHeader
|
||||
}
|
||||
|
||||
type InitResponse struct {
|
||||
}
|
||||
|
||||
type LookUpInodeRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The ID of the directory inode to which the child belongs.
|
||||
Parent InodeID
|
||||
|
||||
// The name of the child of interest, relative to the parent. For example, in
|
||||
// this directory structure:
|
||||
//
|
||||
// foo/
|
||||
// bar/
|
||||
// baz
|
||||
//
|
||||
// the file system may receive a request to look up the child named "bar" for
|
||||
// the parent foo/.
|
||||
Name string
|
||||
}
|
||||
|
||||
type LookUpInodeResponse struct {
|
||||
Entry ChildInodeEntry
|
||||
}
|
||||
|
||||
type GetInodeAttributesRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The inode of interest.
|
||||
Inode InodeID
|
||||
}
|
||||
|
||||
type GetInodeAttributesResponse struct {
|
||||
// Attributes for the inode, and the time at which they should expire. See
|
||||
// notes on ChildInodeEntry.AttributesExpiration for more.
|
||||
Attributes InodeAttributes
|
||||
AttributesExpiration time.Time
|
||||
}
|
||||
|
||||
type SetInodeAttributesRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The inode of interest.
|
||||
Inode InodeID
|
||||
|
||||
// The attributes to modify, or nil for attributes that don't need a change.
|
||||
Size *uint64
|
||||
Mode *os.FileMode
|
||||
Atime *time.Time
|
||||
Mtime *time.Time
|
||||
}
|
||||
|
||||
type SetInodeAttributesResponse struct {
|
||||
// The new attributes for the inode, and the time at which they should
|
||||
// expire. See notes on ChildInodeEntry.AttributesExpiration for more.
|
||||
Attributes InodeAttributes
|
||||
AttributesExpiration time.Time
|
||||
}
|
||||
|
||||
type ForgetInodeRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The inode to be forgotten. The kernel guarantees that the node ID will not
|
||||
// be used in further calls to the file system (unless it is reissued by the
|
||||
// file system).
|
||||
ID InodeID
|
||||
}
|
||||
|
||||
type ForgetInodeResponse struct {
|
||||
}
|
||||
|
||||
type MkDirRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The ID of parent directory inode within which to create the child.
|
||||
Parent InodeID
|
||||
|
||||
// The name of the child to create, and the mode with which to create it.
|
||||
Name string
|
||||
Mode os.FileMode
|
||||
}
|
||||
|
||||
type MkDirResponse struct {
|
||||
// Information about the inode that was created.
|
||||
Entry ChildInodeEntry
|
||||
}
|
||||
|
||||
type CreateFileRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The ID of parent directory inode within which to create the child file.
|
||||
Parent InodeID
|
||||
|
||||
// The name of the child to create, and the mode with which to create it.
|
||||
Name string
|
||||
Mode os.FileMode
|
||||
|
||||
// Flags for the open operation.
|
||||
Flags bazilfuse.OpenFlags
|
||||
}
|
||||
|
||||
type CreateFileResponse struct {
|
||||
// Information about the inode that was created.
|
||||
Entry ChildInodeEntry
|
||||
|
||||
// An opaque ID that will be echoed in follow-up calls for this file using
|
||||
// the same struct file in the kernel. In practice this usually means
|
||||
// follow-up calls using the file descriptor returned by open(2).
|
||||
//
|
||||
// The handle may be supplied in future calls to methods like ReadFile that
|
||||
// accept a file handle. The file system must ensure this ID remains valid
|
||||
// until a later call to ReleaseFileHandle.
|
||||
Handle HandleID
|
||||
}
|
||||
|
||||
type RmDirRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The ID of parent directory inode, and the name of the directory being
|
||||
// removed within it.
|
||||
Parent InodeID
|
||||
Name string
|
||||
}
|
||||
|
||||
type UnlinkResponse struct {
|
||||
}
|
||||
|
||||
type UnlinkRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The ID of parent directory inode, and the name of the file being removed
|
||||
// within it.
|
||||
Parent InodeID
|
||||
Name string
|
||||
}
|
||||
|
||||
type RmDirResponse struct {
|
||||
}
|
||||
|
||||
type OpenDirRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The ID of the inode to be opened.
|
||||
Inode InodeID
|
||||
|
||||
// Mode and options flags.
|
||||
Flags bazilfuse.OpenFlags
|
||||
}
|
||||
|
||||
type OpenDirResponse struct {
|
||||
// An opaque ID that will be echoed in follow-up calls for this directory
|
||||
// using the same struct file in the kernel. In practice this usually means
|
||||
// follow-up calls using the file descriptor returned by open(2).
|
||||
//
|
||||
// The handle may be supplied in future calls to methods like ReadDir that
|
||||
// accept a directory handle. The file system must ensure this ID remains
|
||||
// valid until a later call to ReleaseDirHandle.
|
||||
Handle HandleID
|
||||
}
|
||||
|
||||
type ReadDirRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The directory inode that we are reading, and the handle previously
|
||||
// returned by OpenDir when opening that inode.
|
||||
Inode InodeID
|
||||
Handle HandleID
|
||||
|
||||
// The offset within the directory at which to read.
|
||||
//
|
||||
// Warning: this field is not necessarily a count of bytes. Its legal values
|
||||
// are defined by the results returned in ReadDirResponse. See the notes
|
||||
// below and the notes on that struct.
|
||||
//
|
||||
// In the Linux kernel this ultimately comes from file::f_pos, which starts
|
||||
// at zero and is set by llseek and by the final consumed result returned by
|
||||
// each call to ReadDir:
|
||||
//
|
||||
// * (http://goo.gl/2nWJPL) iterate_dir, which is called by getdents(2) and
|
||||
// readdir(2), sets dir_context::pos to file::f_pos before calling
|
||||
// f_op->iterate, and then does the opposite assignment afterward.
|
||||
//
|
||||
// * (http://goo.gl/rTQVSL) fuse_readdir, which implements iterate for fuse
|
||||
// directories, passes dir_context::pos as the offset to fuse_read_fill,
|
||||
// which passes it on to user-space. fuse_readdir later calls
|
||||
// parse_dirfile with the same context.
|
||||
//
|
||||
// * (http://goo.gl/vU5ukv) For each returned result (except perhaps the
|
||||
// last, which may be truncated by the page boundary), parse_dirfile
|
||||
// updates dir_context::pos with fuse_dirent::off.
|
||||
//
|
||||
// It is affected by the Posix directory stream interfaces in the following
|
||||
// manner:
|
||||
//
|
||||
// * (http://goo.gl/fQhbyn, http://goo.gl/ns1kDF) opendir initially causes
|
||||
// filepos to be set to zero.
|
||||
//
|
||||
// * (http://goo.gl/ezNKyR, http://goo.gl/xOmDv0) readdir allows the user
|
||||
// to iterate through the directory one entry at a time. As each entry is
|
||||
// consumed, its d_off field is stored in __dirstream::filepos.
|
||||
//
|
||||
// * (http://goo.gl/WEOXG8, http://goo.gl/rjSXl3) telldir allows the user
|
||||
// to obtain the d_off field from the most recently returned entry.
|
||||
//
|
||||
// * (http://goo.gl/WG3nDZ, http://goo.gl/Lp0U6W) seekdir allows the user
|
||||
// to seek backward to an offset previously returned by telldir. It
|
||||
// stores the new offset in filepos, and calls llseek to update the
|
||||
// kernel's struct file.
|
||||
//
|
||||
// * (http://goo.gl/gONQhz, http://goo.gl/VlrQkc) rewinddir allows the user
|
||||
// to go back to the beginning of the directory, obtaining a fresh view.
|
||||
// It updates filepos and calls llseek to update the kernel's struct
|
||||
// file.
|
||||
//
|
||||
// Unfortunately, FUSE offers no way to intercept seeks
|
||||
// (http://goo.gl/H6gEXa), so there is no way to cause seekdir or rewinddir
|
||||
// to fail. Additionally, there is no way to distinguish an explicit
|
||||
// rewinddir followed by readdir from the initial readdir, or a rewinddir
|
||||
// from a seekdir to the value returned by telldir just after opendir.
|
||||
//
|
||||
// Luckily, Posix is vague about what the user will see if they seek
|
||||
// backwards, and requires the user not to seek to an old offset after a
|
||||
// rewind. The only requirement on freshness is that rewinddir results in
|
||||
// something that looks like a newly-opened directory. So FUSE file systems
|
||||
// may e.g. cache an entire fresh listing for each ReadDir with a zero
|
||||
// offset, and return array offsets into that cached listing.
|
||||
Offset DirOffset
|
||||
|
||||
// The maximum number of bytes to return in ReadDirResponse.Data. A smaller
|
||||
// number is acceptable.
|
||||
Size int
|
||||
}
|
||||
|
||||
type ReadDirResponse struct {
|
||||
// A buffer consisting of a sequence of FUSE directory entries in the format
|
||||
// generated by fuse_add_direntry (http://goo.gl/qCcHCV), which is consumed
|
||||
// by parse_dirfile (http://goo.gl/2WUmD2). Use fuseutil.AppendDirent to
|
||||
// generate this data.
|
||||
//
|
||||
// The buffer must not exceed the length specified in ReadDirRequest.Size. It
|
||||
// is okay for the final entry to be truncated; parse_dirfile copes with this
|
||||
// by ignoring the partial record.
|
||||
//
|
||||
// Each entry returned exposes a directory offset to the user that may later
|
||||
// show up in ReadDirRequest.Offset. See notes on that field for more
|
||||
// information.
|
||||
//
|
||||
// An empty buffer indicates the end of the directory has been reached.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type ReleaseDirHandleRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The handle ID to be released. The kernel guarantees that this ID will not
|
||||
// be used in further calls to the file system (unless it is reissued by the
|
||||
// file system).
|
||||
Handle HandleID
|
||||
}
|
||||
|
||||
type ReleaseDirHandleResponse struct {
|
||||
}
|
||||
|
||||
type OpenFileRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The ID of the inode to be opened.
|
||||
Inode InodeID
|
||||
|
||||
// Mode and options flags.
|
||||
Flags bazilfuse.OpenFlags
|
||||
}
|
||||
|
||||
type OpenFileResponse struct {
|
||||
// An opaque ID that will be echoed in follow-up calls for this file using
|
||||
// the same struct file in the kernel. In practice this usually means
|
||||
// follow-up calls using the file descriptor returned by open(2).
|
||||
//
|
||||
// The handle may be supplied in future calls to methods like ReadFile that
|
||||
// accept a file handle. The file system must ensure this ID remains valid
|
||||
// until a later call to ReleaseFileHandle.
|
||||
Handle HandleID
|
||||
}
|
||||
|
||||
type ReadFileRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The file inode that we are reading, and the handle previously returned by
|
||||
// CreateFile or OpenFile when opening that inode.
|
||||
Inode InodeID
|
||||
Handle HandleID
|
||||
|
||||
// The range of the file to read.
|
||||
//
|
||||
// The FUSE documentation requires that exactly the number of bytes be
|
||||
// returned, except in the case of EOF or error (http://goo.gl/ZgfBkF). This
|
||||
// appears to be because it uses file mmapping machinery
|
||||
// (http://goo.gl/SGxnaN) to read a page at a time. It appears to understand
|
||||
// where EOF is by checking the inode size (http://goo.gl/0BkqKD), returned
|
||||
// by a previous call to LookUpInode, GetInodeAttributes, etc.
|
||||
Offset int64
|
||||
Size int
|
||||
}
|
||||
|
||||
type ReadFileResponse struct {
|
||||
// The data read. If this is less than the requested size, it indicates EOF.
|
||||
// An error should not be returned in this case.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type WriteFileRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The file inode that we are modifying, and the handle previously returned
|
||||
// by CreateFile or OpenFile when opening that inode.
|
||||
Inode InodeID
|
||||
Handle HandleID
|
||||
|
||||
// The offset at which to write the data below.
|
||||
//
|
||||
// The man page for pwrite(2) implies that aside from changing the file
|
||||
// handle's offset, using pwrite is equivalent to using lseek(2) and then
|
||||
// write(2). The man page for lseek(2) says the following:
|
||||
//
|
||||
// "The lseek() function allows the file offset to be set beyond the end of
|
||||
// the file (but this does not change the size of the file). If data is later
|
||||
// written at this point, subsequent reads of the data in the gap (a "hole")
|
||||
// return null bytes (aq\0aq) until data is actually written into the gap."
|
||||
//
|
||||
// It is therefore reasonable to assume that the kernel is looking for
|
||||
// the following semantics:
|
||||
//
|
||||
// * If the offset is less than or equal to the current size, extend the
|
||||
// file as necessary to fit any data that goes past the end of the file.
|
||||
//
|
||||
// * If the offset is greater than the current size, extend the file
|
||||
// with null bytes until it is not, then do the above.
|
||||
//
|
||||
Offset int64
|
||||
|
||||
// The data to write.
|
||||
//
|
||||
// The FUSE documentation requires that exactly the number of bytes supplied
|
||||
// be written, except on error (http://goo.gl/KUpwwn). This appears to be
|
||||
// because it uses file mmapping machinery (http://goo.gl/SGxnaN) to write a
|
||||
// page at a time.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type WriteFileResponse struct {
|
||||
}
|
||||
|
||||
type SyncFileRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The file and handle being sync'd.
|
||||
Inode InodeID
|
||||
Handle HandleID
|
||||
}
|
||||
|
||||
type SyncFileResponse struct {
|
||||
}
|
||||
|
||||
type FlushFileRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The file and handle being flushed.
|
||||
Inode InodeID
|
||||
Handle HandleID
|
||||
}
|
||||
|
||||
type FlushFileResponse struct {
|
||||
}
|
||||
|
||||
type ReleaseFileHandleRequest struct {
|
||||
Header RequestHeader
|
||||
|
||||
// The handle ID to be released. The kernel guarantees that this ID will not
|
||||
// be used in further calls to the file system (unless it is reissued by the
|
||||
// file system).
|
||||
Handle HandleID
|
||||
}
|
||||
|
||||
type ReleaseFileHandleResponse struct {
|
||||
}
|
|
@ -18,6 +18,8 @@
|
|||
package fuseops
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
|
@ -29,7 +31,7 @@ import (
|
|||
//
|
||||
// This function is an implementation detail of the fuse package, and must not
|
||||
// be called by anyone else.
|
||||
func Convert(r bazilfuse.Request) (o Op) {
|
||||
func Convert(r bazilfuse.Request, logger *log.Logger) (o Op) {
|
||||
var co *commonOp
|
||||
|
||||
switch typed := r.(type) {
|
||||
|
@ -57,6 +59,23 @@ func Convert(r bazilfuse.Request) (o Op) {
|
|||
to := &SetInodeAttributesOp{
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrSize != 0 {
|
||||
to.Size = &typed.Size
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrMode != 0 {
|
||||
to.Mode = &typed.Mode
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrAtime != 0 {
|
||||
to.Atime = &typed.Atime
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrMtime != 0 {
|
||||
to.Mtime = &typed.Mtime
|
||||
}
|
||||
|
||||
o = to
|
||||
co = &to.commonOp
|
||||
|
||||
|
@ -142,7 +161,7 @@ func Convert(r bazilfuse.Request) (o Op) {
|
|||
o = to
|
||||
co = &to.commonOp
|
||||
} else {
|
||||
to := &ReadFileOp{
|
||||
to := &ReleaseFileHandleOp{
|
||||
Handle: HandleID(typed.Handle),
|
||||
}
|
||||
o = to
|
||||
|
@ -184,7 +203,7 @@ func Convert(r bazilfuse.Request) (o Op) {
|
|||
return
|
||||
}
|
||||
|
||||
co.init(r)
|
||||
co.init(reflect.TypeOf(o).String(), r, logger)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -234,13 +253,20 @@ func convertChildInodeEntry(
|
|||
|
||||
// A helper for embedding common behavior.
|
||||
type commonOp struct {
|
||||
ctx context.Context
|
||||
r bazilfuse.Request
|
||||
opType string
|
||||
ctx context.Context
|
||||
r bazilfuse.Request
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (o *commonOp) init(r bazilfuse.Request) {
|
||||
func (o *commonOp) init(
|
||||
opType string,
|
||||
r bazilfuse.Request,
|
||||
logger *log.Logger) {
|
||||
o.opType = opType
|
||||
o.ctx = context.Background()
|
||||
o.r = r
|
||||
o.logger = logger
|
||||
}
|
||||
|
||||
func (o *commonOp) Header() OpHeader {
|
||||
|
@ -256,9 +282,14 @@ func (o *commonOp) Context() context.Context {
|
|||
}
|
||||
|
||||
func (o *commonOp) respondErr(err error) {
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
panic("Expect non-nil here.")
|
||||
}
|
||||
|
||||
o.logger.Printf(
|
||||
"Responding with error to %s: %v",
|
||||
o.opType,
|
||||
err)
|
||||
|
||||
o.r.RespondError(err)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// A common interface implemented by all ops in this package. Use a type switch
|
||||
// to find particular concrete types, responding with fuse.ENOSYS if a type is
|
||||
// not supported.
|
||||
type Op interface {
|
||||
// Return the fields common to all operations.
|
||||
Header() OpHeader
|
||||
|
@ -53,7 +56,10 @@ func (o *InitOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.r.(*bazilfuse.InitRequest).Respond(&bazilfuse.InitResponse{})
|
||||
resp := &bazilfuse.InitResponse{}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.InitRequest).Respond(resp)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -91,6 +97,8 @@ func (o *LookUpInodeOp) Respond(err error) {
|
|||
|
||||
resp := bazilfuse.LookupResponse{}
|
||||
convertChildInodeEntry(&o.Entry, &resp)
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.LookupRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -122,6 +130,7 @@ func (o *GetInodeAttributesOp) Respond(err error) {
|
|||
AttrValid: convertExpirationTime(o.AttributesExpiration),
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.GetattrRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -159,6 +168,7 @@ func (o *SetInodeAttributesOp) Respond(err error) {
|
|||
AttrValid: convertExpirationTime(o.AttributesExpiration),
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.SetattrRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -179,6 +189,7 @@ func (o *ForgetInodeOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding OK to ForgetInodeOp")
|
||||
o.r.(*bazilfuse.ForgetRequest).Respond()
|
||||
}
|
||||
|
||||
|
@ -214,6 +225,9 @@ func (o *MkDirOp) Respond(err error) {
|
|||
}
|
||||
|
||||
resp := bazilfuse.MkdirResponse{}
|
||||
convertChildInodeEntry(&o.Entry, &resp.LookupResponse)
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.MkdirRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -270,6 +284,7 @@ func (o *CreateFileOp) Respond(err error) {
|
|||
}
|
||||
convertChildInodeEntry(&o.Entry, &resp.LookupResponse)
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.CreateRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -299,6 +314,7 @@ func (o *RmDirOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding OK to RmDirOp")
|
||||
o.r.(*bazilfuse.RemoveRequest).Respond()
|
||||
}
|
||||
|
||||
|
@ -322,6 +338,7 @@ func (o *UnlinkOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding OK to UnlinkOp")
|
||||
o.r.(*bazilfuse.RemoveRequest).Respond()
|
||||
}
|
||||
|
||||
|
@ -365,6 +382,7 @@ func (o *OpenDirOp) Respond(err error) {
|
|||
Handle: bazilfuse.HandleID(o.Handle),
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.OpenRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -468,6 +486,7 @@ func (o *ReadDirOp) Respond(err error) {
|
|||
Data: o.Data,
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.ReadRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -492,6 +511,7 @@ func (o *ReleaseDirHandleOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding OK to ReleaseDirHandleOp")
|
||||
o.r.(*bazilfuse.ReleaseRequest).Respond()
|
||||
}
|
||||
|
||||
|
@ -534,6 +554,7 @@ func (o *OpenFileOp) Respond(err error) {
|
|||
Handle: bazilfuse.HandleID(o.Handle),
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.OpenRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -576,6 +597,7 @@ func (o *ReadFileOp) Respond(err error) {
|
|||
Data: o.Data,
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.ReadRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -656,6 +678,7 @@ func (o *WriteFileOp) Respond(err error) {
|
|||
Size: len(o.Data),
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding: %v", &resp)
|
||||
o.r.(*bazilfuse.WriteRequest).Respond(&resp)
|
||||
}
|
||||
|
||||
|
@ -689,6 +712,7 @@ func (o *SyncFileOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding OK to SyncFileOp")
|
||||
o.r.(*bazilfuse.FsyncRequest).Respond()
|
||||
}
|
||||
|
||||
|
@ -753,6 +777,7 @@ func (o *FlushFileOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding OK to FlushFileOp")
|
||||
o.r.(*bazilfuse.FlushRequest).Respond()
|
||||
}
|
||||
|
||||
|
@ -777,5 +802,6 @@ func (o *ReleaseFileHandleOp) Respond(err error) {
|
|||
return
|
||||
}
|
||||
|
||||
o.commonOp.logger.Printf("Responding OK to ReleaseFileHandleOp")
|
||||
o.r.(*bazilfuse.ReleaseRequest).Respond()
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
)
|
||||
|
||||
type DirentType uint32
|
||||
|
@ -35,14 +35,14 @@ const (
|
|||
)
|
||||
|
||||
// A struct representing an entry within a directory file, describing a child.
|
||||
// See notes on fuse.ReadDirResponse and on AppendDirent for details.
|
||||
// See notes on fuseops.ReadDirOp and on AppendDirent for details.
|
||||
type Dirent struct {
|
||||
// The (opaque) offset within the directory file of the entry following this
|
||||
// one. See notes on fuse.ReadDirRequest.Offset for details.
|
||||
Offset fuse.DirOffset
|
||||
// one. See notes on fuseops.ReadDirOp.Offset for details.
|
||||
Offset fuseops.DirOffset
|
||||
|
||||
// The inode of the child file or directory, and its name within the parent.
|
||||
Inode fuse.InodeID
|
||||
Inode fuseops.InodeID
|
||||
Name string
|
||||
|
||||
// The type of the child. The zero value (DT_Unknown) is legal, but means
|
||||
|
@ -51,7 +51,7 @@ type Dirent struct {
|
|||
}
|
||||
|
||||
// Append the supplied directory entry to the given buffer in the format
|
||||
// expected in fuse.ReadResponse.Data, returning the resulting buffer.
|
||||
// expected in fuseops.ReadFileOp.Data, returning the resulting buffer.
|
||||
func AppendDirent(input []byte, d Dirent) (output []byte) {
|
||||
// We want to append bytes with the layout of fuse_dirent
|
||||
// (http://goo.gl/BmFxob) in host order. The struct must be aligned according
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fuseutil
|
||||
|
||||
import (
|
||||
"github.com/jacobsa/fuse"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Embed this within your file system type to inherit default implementations
|
||||
// of all methods that return fuse.ENOSYS.
|
||||
type NotImplementedFileSystem struct {
|
||||
}
|
||||
|
||||
var _ fuse.FileSystem = &NotImplementedFileSystem{}
|
||||
|
||||
func (fs *NotImplementedFileSystem) Init(
|
||||
ctx context.Context,
|
||||
req *fuse.InitRequest) (*fuse.InitResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) LookUpInode(
|
||||
ctx context.Context,
|
||||
req *fuse.LookUpInodeRequest) (*fuse.LookUpInodeResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) GetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.GetInodeAttributesRequest) (
|
||||
*fuse.GetInodeAttributesResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) SetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.SetInodeAttributesRequest) (
|
||||
*fuse.SetInodeAttributesResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) ForgetInode(
|
||||
ctx context.Context,
|
||||
req *fuse.ForgetInodeRequest) (*fuse.ForgetInodeResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) MkDir(
|
||||
ctx context.Context,
|
||||
req *fuse.MkDirRequest) (*fuse.MkDirResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) CreateFile(
|
||||
ctx context.Context,
|
||||
req *fuse.CreateFileRequest) (*fuse.CreateFileResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) RmDir(
|
||||
ctx context.Context,
|
||||
req *fuse.RmDirRequest) (*fuse.RmDirResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) Unlink(
|
||||
ctx context.Context,
|
||||
req *fuse.UnlinkRequest) (*fuse.UnlinkResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) OpenDir(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenDirRequest) (*fuse.OpenDirResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) ReadDir(
|
||||
ctx context.Context,
|
||||
req *fuse.ReadDirRequest) (*fuse.ReadDirResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) ReleaseDirHandle(
|
||||
ctx context.Context,
|
||||
req *fuse.ReleaseDirHandleRequest) (*fuse.ReleaseDirHandleResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) OpenFile(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenFileRequest) (*fuse.OpenFileResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) ReadFile(
|
||||
ctx context.Context,
|
||||
req *fuse.ReadFileRequest) (*fuse.ReadFileResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) WriteFile(
|
||||
ctx context.Context,
|
||||
req *fuse.WriteFileRequest) (*fuse.WriteFileResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) SyncFile(
|
||||
ctx context.Context,
|
||||
req *fuse.SyncFileRequest) (*fuse.SyncFileResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) FlushFile(
|
||||
ctx context.Context,
|
||||
req *fuse.FlushFileRequest) (*fuse.FlushFileResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *NotImplementedFileSystem) ReleaseFileHandle(
|
||||
ctx context.Context,
|
||||
req *fuse.ReleaseFileHandleRequest) (*fuse.ReleaseFileHandleResponse, error) {
|
||||
return nil, fuse.ENOSYS
|
||||
}
|
|
@ -15,24 +15,24 @@
|
|||
package fuse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// A struct representing the status of a mount operation, with methods for
|
||||
// waiting on the mount to complete, waiting for unmounting, and causing
|
||||
// unmounting.
|
||||
// A type that knows how to serve ops read from a connection.
|
||||
type Server interface {
|
||||
// Read and serve ops from the supplied connection until EOF.
|
||||
ServeOps(*Connection)
|
||||
}
|
||||
|
||||
// A struct representing the status of a mount operation, with a method that
|
||||
// waits for unmounting.
|
||||
type MountedFileSystem struct {
|
||||
dir string
|
||||
|
||||
// The result to return from WaitForReady. Not valid until the channel is
|
||||
// closed.
|
||||
readyStatus error
|
||||
readyStatusAvailable chan struct{}
|
||||
|
||||
// The result to return from Join. Not valid until the channel is closed.
|
||||
joinStatus error
|
||||
joinStatusAvailable chan struct{}
|
||||
|
@ -44,22 +44,9 @@ func (mfs *MountedFileSystem) Dir() string {
|
|||
return mfs.dir
|
||||
}
|
||||
|
||||
// Wait until the mount point is ready to be used. After a successful return
|
||||
// from this function, the contents of the mounted file system should be
|
||||
// visible in the directory supplied to NewMountPoint. May be called multiple
|
||||
// times.
|
||||
func (mfs *MountedFileSystem) WaitForReady(ctx context.Context) error {
|
||||
select {
|
||||
case <-mfs.readyStatusAvailable:
|
||||
return mfs.readyStatus
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// Block until a mounted file system has been unmounted. The return value will
|
||||
// be non-nil if anything unexpected happened while serving. May be called
|
||||
// multiple times. Must not be called unless WaitForReady has returned nil.
|
||||
// multiple times.
|
||||
func (mfs *MountedFileSystem) Join(ctx context.Context) error {
|
||||
select {
|
||||
case <-mfs.joinStatusAvailable:
|
||||
|
@ -70,52 +57,14 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// Attempt to unmount the file system. Use Join to wait for it to actually be
|
||||
// unmounted. You must first call WaitForReady to ensure there is no race with
|
||||
// mounting.
|
||||
// unmounted.
|
||||
//
|
||||
// TODO(jacobsa): Kill this in favor of an Unmount free function that can be
|
||||
// used even from outside of the daemon process.
|
||||
func (mfs *MountedFileSystem) Unmount() error {
|
||||
return bazilfuse.Unmount(mfs.dir)
|
||||
}
|
||||
|
||||
// Runs in the background.
|
||||
func (mfs *MountedFileSystem) mountAndServe(
|
||||
server *server,
|
||||
options []bazilfuse.MountOption) {
|
||||
logger := getLogger()
|
||||
|
||||
// Open a FUSE connection.
|
||||
logger.Println("Opening a FUSE connection.")
|
||||
c, err := bazilfuse.Mount(mfs.dir, options...)
|
||||
if err != nil {
|
||||
mfs.readyStatus = errors.New("bazilfuse.Mount: " + err.Error())
|
||||
close(mfs.readyStatusAvailable)
|
||||
return
|
||||
}
|
||||
|
||||
defer c.Close()
|
||||
|
||||
// Start a goroutine that will notify the MountedFileSystem object when the
|
||||
// connection says it is ready (or it fails to become ready).
|
||||
go func() {
|
||||
logger.Println("Waiting for the FUSE connection to be ready.")
|
||||
<-c.Ready
|
||||
logger.Println("The FUSE connection is ready.")
|
||||
|
||||
mfs.readyStatus = c.MountError
|
||||
close(mfs.readyStatusAvailable)
|
||||
}()
|
||||
|
||||
// Serve the connection using the file system object.
|
||||
logger.Println("Serving the FUSE connection.")
|
||||
if err := server.Serve(c); err != nil {
|
||||
mfs.joinStatus = errors.New("Serve: " + err.Error())
|
||||
close(mfs.joinStatusAvailable)
|
||||
return
|
||||
}
|
||||
|
||||
// Signal that everything is okay.
|
||||
close(mfs.joinStatusAvailable)
|
||||
}
|
||||
|
||||
// Optional configuration accepted by Mount.
|
||||
type MountConfig struct {
|
||||
// OS X only.
|
||||
|
@ -148,28 +97,51 @@ func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
|
|||
return
|
||||
}
|
||||
|
||||
// Attempt to mount the supplied file system on the given directory.
|
||||
// mfs.WaitForReady() must be called to find out whether the mount was
|
||||
// successful.
|
||||
// Attempt to mount a file system on the given directory, using the supplied
|
||||
// Server to serve connection requests. This function blocks until the file
|
||||
// system is successfully mounted. On some systems, this requires the supplied
|
||||
// Server to make forward progress (in particular, to respond to
|
||||
// fuseops.InitOp).
|
||||
func Mount(
|
||||
dir string,
|
||||
fs FileSystem,
|
||||
server Server,
|
||||
config *MountConfig) (mfs *MountedFileSystem, err error) {
|
||||
// Create a server object.
|
||||
server, err := newServer(fs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logger := getLogger()
|
||||
|
||||
// Initialize the struct.
|
||||
mfs = &MountedFileSystem{
|
||||
dir: dir,
|
||||
readyStatusAvailable: make(chan struct{}),
|
||||
joinStatusAvailable: make(chan struct{}),
|
||||
dir: dir,
|
||||
joinStatusAvailable: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Mount in the background.
|
||||
go mfs.mountAndServe(server, config.bazilfuseOptions())
|
||||
// Open a bazilfuse connection.
|
||||
logger.Println("Opening a bazilfuse connection.")
|
||||
bfConn, err := bazilfuse.Mount(mfs.dir, config.bazilfuseOptions()...)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bazilfuse.Mount: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create our own Connection object wrapping it.
|
||||
connection, err := newConnection(logger, bfConn)
|
||||
if err != nil {
|
||||
bfConn.Close()
|
||||
err = fmt.Errorf("newConnection: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Serve the connection in the background. When done, set the join status.
|
||||
go func() {
|
||||
server.ServeOps(connection)
|
||||
mfs.joinStatus = connection.close()
|
||||
close(mfs.joinStatusAvailable)
|
||||
}()
|
||||
|
||||
// Wait for the connection to say it is ready.
|
||||
if err = connection.waitForReady(); err != nil {
|
||||
err = fmt.Errorf("WaitForReady: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -16,13 +16,13 @@ package cachingfs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
"github.com/jacobsa/gcloud/syncutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -42,12 +42,12 @@ const (
|
|||
// requests. It also exposes methods for renumbering inodes and updating mtimes
|
||||
// that are useful in testing that these durations are honored.
|
||||
type CachingFS interface {
|
||||
fuse.FileSystem
|
||||
fuse.Server
|
||||
|
||||
// Return the current inode ID of the file/directory with the given name.
|
||||
FooID() fuse.InodeID
|
||||
DirID() fuse.InodeID
|
||||
BarID() fuse.InodeID
|
||||
FooID() fuseops.InodeID
|
||||
DirID() fuseops.InodeID
|
||||
BarID() fuseops.InodeID
|
||||
|
||||
// Cause the inode IDs to change to values that have never before been used.
|
||||
RenumberInodes()
|
||||
|
@ -72,14 +72,14 @@ type CachingFS interface {
|
|||
func NewCachingFS(
|
||||
lookupEntryTimeout time.Duration,
|
||||
getattrTimeout time.Duration) (fs CachingFS, err error) {
|
||||
roundUp := func(n fuse.InodeID) fuse.InodeID {
|
||||
roundUp := func(n fuseops.InodeID) fuseops.InodeID {
|
||||
return numInodes * ((n + numInodes - 1) / numInodes)
|
||||
}
|
||||
|
||||
cfs := &cachingFS{
|
||||
lookupEntryTimeout: lookupEntryTimeout,
|
||||
getattrTimeout: getattrTimeout,
|
||||
baseID: roundUp(fuse.RootInodeID + 1),
|
||||
baseID: roundUp(fuseops.RootInodeID + 1),
|
||||
mtime: time.Now(),
|
||||
}
|
||||
|
||||
|
@ -99,8 +99,6 @@ const (
|
|||
)
|
||||
|
||||
type cachingFS struct {
|
||||
fuseutil.NotImplementedFileSystem
|
||||
|
||||
/////////////////////////
|
||||
// Constant data
|
||||
/////////////////////////
|
||||
|
@ -116,11 +114,11 @@ type cachingFS struct {
|
|||
|
||||
// The current ID of the lowest numbered non-root inode.
|
||||
//
|
||||
// INVARIANT: baseID > fuse.RootInodeID
|
||||
// INVARIANT: baseID > fuseops.RootInodeID
|
||||
// INVARIANT: baseID % numInodes == 0
|
||||
//
|
||||
// GUARDED_BY(mu)
|
||||
baseID fuse.InodeID
|
||||
baseID fuseops.InodeID
|
||||
|
||||
// GUARDED_BY(mu)
|
||||
mtime time.Time
|
||||
|
@ -131,39 +129,39 @@ type cachingFS struct {
|
|||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (fs *cachingFS) checkInvariants() {
|
||||
// INVARIANT: baseID > fuse.RootInodeID
|
||||
// INVARIANT: baseID > fuseops.RootInodeID
|
||||
// INVARIANT: baseID % numInodes == 0
|
||||
if fs.baseID <= fuse.RootInodeID || fs.baseID%numInodes != 0 {
|
||||
if fs.baseID <= fuseops.RootInodeID || fs.baseID%numInodes != 0 {
|
||||
panic(fmt.Sprintf("Bad baseID: %v", fs.baseID))
|
||||
}
|
||||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *cachingFS) fooID() fuse.InodeID {
|
||||
func (fs *cachingFS) fooID() fuseops.InodeID {
|
||||
return fs.baseID + fooOffset
|
||||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *cachingFS) dirID() fuse.InodeID {
|
||||
func (fs *cachingFS) dirID() fuseops.InodeID {
|
||||
return fs.baseID + dirOffset
|
||||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *cachingFS) barID() fuse.InodeID {
|
||||
func (fs *cachingFS) barID() fuseops.InodeID {
|
||||
return fs.baseID + barOffset
|
||||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *cachingFS) rootAttrs() fuse.InodeAttributes {
|
||||
return fuse.InodeAttributes{
|
||||
func (fs *cachingFS) rootAttrs() fuseops.InodeAttributes {
|
||||
return fuseops.InodeAttributes{
|
||||
Mode: os.ModeDir | 0777,
|
||||
Mtime: fs.mtime,
|
||||
}
|
||||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *cachingFS) fooAttrs() fuse.InodeAttributes {
|
||||
return fuse.InodeAttributes{
|
||||
func (fs *cachingFS) fooAttrs() fuseops.InodeAttributes {
|
||||
return fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Size: FooSize,
|
||||
Mode: 0777,
|
||||
|
@ -172,8 +170,8 @@ func (fs *cachingFS) fooAttrs() fuse.InodeAttributes {
|
|||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *cachingFS) dirAttrs() fuse.InodeAttributes {
|
||||
return fuse.InodeAttributes{
|
||||
func (fs *cachingFS) dirAttrs() fuseops.InodeAttributes {
|
||||
return fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: os.ModeDir | 0777,
|
||||
Mtime: fs.mtime,
|
||||
|
@ -181,8 +179,8 @@ func (fs *cachingFS) dirAttrs() fuse.InodeAttributes {
|
|||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *cachingFS) barAttrs() fuse.InodeAttributes {
|
||||
return fuse.InodeAttributes{
|
||||
func (fs *cachingFS) barAttrs() fuseops.InodeAttributes {
|
||||
return fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Size: BarSize,
|
||||
Mode: 0777,
|
||||
|
@ -195,7 +193,7 @@ func (fs *cachingFS) barAttrs() fuse.InodeAttributes {
|
|||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *cachingFS) FooID() fuse.InodeID {
|
||||
func (fs *cachingFS) FooID() fuseops.InodeID {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
|
@ -203,7 +201,7 @@ func (fs *cachingFS) FooID() fuse.InodeID {
|
|||
}
|
||||
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *cachingFS) DirID() fuse.InodeID {
|
||||
func (fs *cachingFS) DirID() fuseops.InodeID {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
|
@ -211,7 +209,7 @@ func (fs *cachingFS) DirID() fuse.InodeID {
|
|||
}
|
||||
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *cachingFS) BarID() fuse.InodeID {
|
||||
func (fs *cachingFS) BarID() fuseops.InodeID {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
|
@ -234,34 +232,64 @@ func (fs *cachingFS) SetMtime(mtime time.Time) {
|
|||
fs.mtime = mtime
|
||||
}
|
||||
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *cachingFS) ServeOps(c *fuse.Connection) {
|
||||
for {
|
||||
op, err := c.ReadOp()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch typed := op.(type) {
|
||||
case *fuseops.InitOp:
|
||||
fs.init(typed)
|
||||
|
||||
case *fuseops.LookUpInodeOp:
|
||||
fs.lookUpInode(typed)
|
||||
|
||||
case *fuseops.GetInodeAttributesOp:
|
||||
fs.getInodeAttributes(typed)
|
||||
|
||||
case *fuseops.OpenDirOp:
|
||||
fs.openDir(typed)
|
||||
|
||||
case *fuseops.OpenFileOp:
|
||||
fs.openFile(typed)
|
||||
|
||||
default:
|
||||
typed.Respond(fuse.ENOSYS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// FileSystem methods
|
||||
// Op methods
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (fs *cachingFS) Init(
|
||||
ctx context.Context,
|
||||
req *fuse.InitRequest) (resp *fuse.InitResponse, err error) {
|
||||
resp = &fuse.InitResponse{}
|
||||
return
|
||||
func (fs *cachingFS) init(op *fuseops.InitOp) {
|
||||
op.Respond(nil)
|
||||
}
|
||||
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *cachingFS) LookUpInode(
|
||||
ctx context.Context,
|
||||
req *fuse.LookUpInodeRequest) (resp *fuse.LookUpInodeResponse, err error) {
|
||||
resp = &fuse.LookUpInodeResponse{}
|
||||
func (fs *cachingFS) lookUpInode(op *fuseops.LookUpInodeOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Find the ID and attributes.
|
||||
var id fuse.InodeID
|
||||
var attrs fuse.InodeAttributes
|
||||
var id fuseops.InodeID
|
||||
var attrs fuseops.InodeAttributes
|
||||
|
||||
switch req.Name {
|
||||
switch op.Name {
|
||||
case "foo":
|
||||
// Parent must be the root.
|
||||
if req.Parent != fuse.RootInodeID {
|
||||
if op.Parent != fuseops.RootInodeID {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
}
|
||||
|
@ -271,7 +299,7 @@ func (fs *cachingFS) LookUpInode(
|
|||
|
||||
case "dir":
|
||||
// Parent must be the root.
|
||||
if req.Parent != fuse.RootInodeID {
|
||||
if op.Parent != fuseops.RootInodeID {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
}
|
||||
|
@ -281,7 +309,7 @@ func (fs *cachingFS) LookUpInode(
|
|||
|
||||
case "bar":
|
||||
// Parent must be dir.
|
||||
if req.Parent == fuse.RootInodeID || req.Parent%numInodes != dirOffset {
|
||||
if op.Parent == fuseops.RootInodeID || op.Parent%numInodes != dirOffset {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
}
|
||||
|
@ -295,59 +323,55 @@ func (fs *cachingFS) LookUpInode(
|
|||
}
|
||||
|
||||
// Fill in the response.
|
||||
resp.Entry.Child = id
|
||||
resp.Entry.Attributes = attrs
|
||||
resp.Entry.EntryExpiration = time.Now().Add(fs.lookupEntryTimeout)
|
||||
op.Entry.Child = id
|
||||
op.Entry.Attributes = attrs
|
||||
op.Entry.EntryExpiration = time.Now().Add(fs.lookupEntryTimeout)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LOCKS_EXCLUDED(fs.mu)
|
||||
func (fs *cachingFS) GetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.GetInodeAttributesRequest) (
|
||||
resp *fuse.GetInodeAttributesResponse, err error) {
|
||||
resp = &fuse.GetInodeAttributesResponse{}
|
||||
func (fs *cachingFS) getInodeAttributes(op *fuseops.GetInodeAttributesOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Figure out which inode the request is for.
|
||||
var attrs fuse.InodeAttributes
|
||||
var attrs fuseops.InodeAttributes
|
||||
|
||||
switch {
|
||||
case req.Inode == fuse.RootInodeID:
|
||||
case op.Inode == fuseops.RootInodeID:
|
||||
attrs = fs.rootAttrs()
|
||||
|
||||
case req.Inode%numInodes == fooOffset:
|
||||
case op.Inode%numInodes == fooOffset:
|
||||
attrs = fs.fooAttrs()
|
||||
|
||||
case req.Inode%numInodes == dirOffset:
|
||||
case op.Inode%numInodes == dirOffset:
|
||||
attrs = fs.dirAttrs()
|
||||
|
||||
case req.Inode%numInodes == barOffset:
|
||||
case op.Inode%numInodes == barOffset:
|
||||
attrs = fs.barAttrs()
|
||||
}
|
||||
|
||||
// Fill in the response.
|
||||
resp.Attributes = attrs
|
||||
resp.AttributesExpiration = time.Now().Add(fs.getattrTimeout)
|
||||
op.Attributes = attrs
|
||||
op.AttributesExpiration = time.Now().Add(fs.getattrTimeout)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *cachingFS) OpenDir(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenDirRequest) (
|
||||
resp *fuse.OpenDirResponse, err error) {
|
||||
resp = &fuse.OpenDirResponse{}
|
||||
func (fs *cachingFS) openDir(op *fuseops.OpenDirOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *cachingFS) OpenFile(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenFileRequest) (
|
||||
resp *fuse.OpenFileResponse, err error) {
|
||||
resp = &fuse.OpenFileResponse{}
|
||||
func (fs *cachingFS) openFile(op *fuseops.OpenFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (t *cachingFSTest) setUp(
|
|||
t.fs, err = cachingfs.NewCachingFS(lookupEntryTimeout, getattrTimeout)
|
||||
AssertEq(nil, err)
|
||||
|
||||
t.FileSystem = t.fs
|
||||
t.Server = t.fs
|
||||
|
||||
// Mount it.
|
||||
t.SampleTest.SetUp(ti)
|
||||
|
|
|
@ -16,12 +16,12 @@ package flushfs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
"golang.org/x/net/context"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
)
|
||||
|
||||
// Create a file system whose sole contents are a file named "foo" and a
|
||||
|
@ -34,8 +34,8 @@ import (
|
|||
// The directory cannot be modified.
|
||||
func NewFileSystem(
|
||||
reportFlush func(string) error,
|
||||
reportFsync func(string) error) (fs fuse.FileSystem, err error) {
|
||||
fs = &flushFS{
|
||||
reportFsync func(string) error) (server fuse.Server, err error) {
|
||||
server = &flushFS{
|
||||
reportFlush: reportFlush,
|
||||
reportFsync: reportFsync,
|
||||
}
|
||||
|
@ -44,12 +44,11 @@ func NewFileSystem(
|
|||
}
|
||||
|
||||
const (
|
||||
fooID = fuse.RootInodeID + 1 + iota
|
||||
fooID = fuseops.RootInodeID + 1 + iota
|
||||
barID
|
||||
)
|
||||
|
||||
type flushFS struct {
|
||||
fuseutil.NotImplementedFileSystem
|
||||
reportFlush func(string) error
|
||||
reportFsync func(string) error
|
||||
|
||||
|
@ -62,16 +61,16 @@ type flushFS struct {
|
|||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *flushFS) rootAttributes() fuse.InodeAttributes {
|
||||
return fuse.InodeAttributes{
|
||||
func (fs *flushFS) rootAttributes() fuseops.InodeAttributes {
|
||||
return fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: 0777 | os.ModeDir,
|
||||
}
|
||||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *flushFS) fooAttributes() fuse.InodeAttributes {
|
||||
return fuse.InodeAttributes{
|
||||
func (fs *flushFS) fooAttributes() fuseops.InodeAttributes {
|
||||
return fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: 0777,
|
||||
Size: uint64(len(fs.fooContents)),
|
||||
|
@ -79,50 +78,93 @@ func (fs *flushFS) fooAttributes() fuse.InodeAttributes {
|
|||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *flushFS) barAttributes() fuse.InodeAttributes {
|
||||
return fuse.InodeAttributes{
|
||||
func (fs *flushFS) barAttributes() fuseops.InodeAttributes {
|
||||
return fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: 0777 | os.ModeDir,
|
||||
}
|
||||
}
|
||||
|
||||
// LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *flushFS) ServeOps(c *fuse.Connection) {
|
||||
for {
|
||||
op, err := c.ReadOp()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch typed := op.(type) {
|
||||
case *fuseops.InitOp:
|
||||
fs.init(typed)
|
||||
|
||||
case *fuseops.LookUpInodeOp:
|
||||
fs.lookUpInode(typed)
|
||||
|
||||
case *fuseops.GetInodeAttributesOp:
|
||||
fs.getInodeAttributes(typed)
|
||||
|
||||
case *fuseops.OpenFileOp:
|
||||
fs.openFile(typed)
|
||||
|
||||
case *fuseops.ReadFileOp:
|
||||
fs.readFile(typed)
|
||||
|
||||
case *fuseops.WriteFileOp:
|
||||
fs.writeFile(typed)
|
||||
|
||||
case *fuseops.SyncFileOp:
|
||||
fs.syncFile(typed)
|
||||
|
||||
case *fuseops.FlushFileOp:
|
||||
fs.flushFile(typed)
|
||||
|
||||
case *fuseops.OpenDirOp:
|
||||
fs.openDir(typed)
|
||||
|
||||
default:
|
||||
typed.Respond(fuse.ENOSYS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// File system methods
|
||||
// Op methods
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (fs *flushFS) Init(
|
||||
ctx context.Context,
|
||||
req *fuse.InitRequest) (
|
||||
resp *fuse.InitResponse, err error) {
|
||||
resp = &fuse.InitResponse{}
|
||||
func (fs *flushFS) init(op *fuseops.InitOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *flushFS) LookUpInode(
|
||||
ctx context.Context,
|
||||
req *fuse.LookUpInodeRequest) (
|
||||
resp *fuse.LookUpInodeResponse, err error) {
|
||||
resp = &fuse.LookUpInodeResponse{}
|
||||
func (fs *flushFS) lookUpInode(op *fuseops.LookUpInodeOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Sanity check.
|
||||
if req.Parent != fuse.RootInodeID {
|
||||
if op.Parent != fuseops.RootInodeID {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
}
|
||||
|
||||
// Set up the entry.
|
||||
switch req.Name {
|
||||
switch op.Name {
|
||||
case "foo":
|
||||
resp.Entry = fuse.ChildInodeEntry{
|
||||
op.Entry = fuseops.ChildInodeEntry{
|
||||
Child: fooID,
|
||||
Attributes: fs.fooAttributes(),
|
||||
}
|
||||
|
||||
case "bar":
|
||||
resp.Entry = fuse.ChildInodeEntry{
|
||||
op.Entry = fuseops.ChildInodeEntry{
|
||||
Child: barID,
|
||||
Attributes: fs.barAttributes(),
|
||||
}
|
||||
|
@ -135,26 +177,24 @@ func (fs *flushFS) LookUpInode(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *flushFS) GetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.GetInodeAttributesRequest) (
|
||||
resp *fuse.GetInodeAttributesResponse, err error) {
|
||||
resp = &fuse.GetInodeAttributesResponse{}
|
||||
func (fs *flushFS) getInodeAttributes(op *fuseops.GetInodeAttributesOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
switch req.Inode {
|
||||
case fuse.RootInodeID:
|
||||
resp.Attributes = fs.rootAttributes()
|
||||
switch op.Inode {
|
||||
case fuseops.RootInodeID:
|
||||
op.Attributes = fs.rootAttributes()
|
||||
return
|
||||
|
||||
case fooID:
|
||||
resp.Attributes = fs.fooAttributes()
|
||||
op.Attributes = fs.fooAttributes()
|
||||
return
|
||||
|
||||
case barID:
|
||||
resp.Attributes = fs.barAttributes()
|
||||
op.Attributes = fs.barAttributes()
|
||||
return
|
||||
|
||||
default:
|
||||
|
@ -163,17 +203,15 @@ func (fs *flushFS) GetInodeAttributes(
|
|||
}
|
||||
}
|
||||
|
||||
func (fs *flushFS) OpenFile(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenFileRequest) (
|
||||
resp *fuse.OpenFileResponse, err error) {
|
||||
resp = &fuse.OpenFileResponse{}
|
||||
func (fs *flushFS) openFile(op *fuseops.OpenFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Sanity check.
|
||||
if req.Inode != fooID {
|
||||
if op.Inode != fooID {
|
||||
err = fuse.ENOSYS
|
||||
return
|
||||
}
|
||||
|
@ -181,59 +219,53 @@ func (fs *flushFS) OpenFile(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *flushFS) ReadFile(
|
||||
ctx context.Context,
|
||||
req *fuse.ReadFileRequest) (
|
||||
resp *fuse.ReadFileResponse, err error) {
|
||||
resp = &fuse.ReadFileResponse{}
|
||||
func (fs *flushFS) readFile(op *fuseops.ReadFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Ensure the offset is in range.
|
||||
if req.Offset > int64(len(fs.fooContents)) {
|
||||
if op.Offset > int64(len(fs.fooContents)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Read what we can.
|
||||
resp.Data = make([]byte, req.Size)
|
||||
copy(resp.Data, fs.fooContents[req.Offset:])
|
||||
op.Data = make([]byte, op.Size)
|
||||
copy(op.Data, fs.fooContents[op.Offset:])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *flushFS) WriteFile(
|
||||
ctx context.Context,
|
||||
req *fuse.WriteFileRequest) (
|
||||
resp *fuse.WriteFileResponse, err error) {
|
||||
resp = &fuse.WriteFileResponse{}
|
||||
func (fs *flushFS) writeFile(op *fuseops.WriteFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Ensure that the contents slice is long enough.
|
||||
newLen := int(req.Offset) + len(req.Data)
|
||||
newLen := int(op.Offset) + len(op.Data)
|
||||
if len(fs.fooContents) < newLen {
|
||||
padding := make([]byte, newLen-len(fs.fooContents))
|
||||
fs.fooContents = append(fs.fooContents, padding...)
|
||||
}
|
||||
|
||||
// Copy in the data.
|
||||
n := copy(fs.fooContents[req.Offset:], req.Data)
|
||||
n := copy(fs.fooContents[op.Offset:], op.Data)
|
||||
|
||||
// Sanity check.
|
||||
if n != len(req.Data) {
|
||||
if n != len(op.Data) {
|
||||
panic(fmt.Sprintf("Unexpected short copy: %v", n))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *flushFS) SyncFile(
|
||||
ctx context.Context,
|
||||
req *fuse.SyncFileRequest) (
|
||||
resp *fuse.SyncFileResponse, err error) {
|
||||
resp = &fuse.SyncFileResponse{}
|
||||
func (fs *flushFS) syncFile(op *fuseops.SyncFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
@ -242,11 +274,9 @@ func (fs *flushFS) SyncFile(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *flushFS) FlushFile(
|
||||
ctx context.Context,
|
||||
req *fuse.FlushFileRequest) (
|
||||
resp *fuse.FlushFileResponse, err error) {
|
||||
resp = &fuse.FlushFileResponse{}
|
||||
func (fs *flushFS) flushFile(op *fuseops.FlushFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
@ -255,17 +285,15 @@ func (fs *flushFS) FlushFile(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *flushFS) OpenDir(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenDirRequest) (
|
||||
resp *fuse.OpenDirResponse, err error) {
|
||||
resp = &fuse.OpenDirResponse{}
|
||||
func (fs *flushFS) openDir(op *fuseops.OpenDirOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Sanity check.
|
||||
if req.Inode != barID {
|
||||
if op.Inode != barID {
|
||||
err = fuse.ENOSYS
|
||||
return
|
||||
}
|
||||
|
|
|
@ -19,35 +19,79 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
||||
"golang.org/x/net/context"
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
)
|
||||
|
||||
// A file system with a fixed structure that looks like this:
|
||||
// Create a file system with a fixed structure that looks like this:
|
||||
//
|
||||
// hello
|
||||
// dir/
|
||||
// world
|
||||
//
|
||||
// Each file contains the string "Hello, world!".
|
||||
type HelloFS struct {
|
||||
fuseutil.NotImplementedFileSystem
|
||||
func NewHelloFS(clock timeutil.Clock) (server fuse.Server, err error) {
|
||||
server = &helloFS{
|
||||
Clock: clock,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type helloFS struct {
|
||||
Clock timeutil.Clock
|
||||
}
|
||||
|
||||
var _ fuse.FileSystem = &HelloFS{}
|
||||
func (fs *helloFS) ServeOps(c *fuse.Connection) {
|
||||
for {
|
||||
op, err := c.ReadOp()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch typed := op.(type) {
|
||||
case *fuseops.InitOp:
|
||||
fs.init(typed)
|
||||
|
||||
case *fuseops.LookUpInodeOp:
|
||||
fs.lookUpInode(typed)
|
||||
|
||||
case *fuseops.GetInodeAttributesOp:
|
||||
fs.getInodeAttributes(typed)
|
||||
|
||||
case *fuseops.OpenDirOp:
|
||||
fs.openDir(typed)
|
||||
|
||||
case *fuseops.ReadDirOp:
|
||||
fs.readDir(typed)
|
||||
|
||||
case *fuseops.OpenFileOp:
|
||||
fs.openFile(typed)
|
||||
|
||||
case *fuseops.ReadFileOp:
|
||||
fs.readFile(typed)
|
||||
|
||||
default:
|
||||
typed.Respond(fuse.ENOSYS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
rootInode fuse.InodeID = fuse.RootInodeID + iota
|
||||
rootInode fuseops.InodeID = fuseops.RootInodeID + iota
|
||||
helloInode
|
||||
dirInode
|
||||
worldInode
|
||||
)
|
||||
|
||||
type inodeInfo struct {
|
||||
attributes fuse.InodeAttributes
|
||||
attributes fuseops.InodeAttributes
|
||||
|
||||
// File or directory?
|
||||
dir bool
|
||||
|
@ -57,10 +101,10 @@ type inodeInfo struct {
|
|||
}
|
||||
|
||||
// We have a fixed directory structure.
|
||||
var gInodeInfo = map[fuse.InodeID]inodeInfo{
|
||||
var gInodeInfo = map[fuseops.InodeID]inodeInfo{
|
||||
// root
|
||||
rootInode: inodeInfo{
|
||||
attributes: fuse.InodeAttributes{
|
||||
attributes: fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: 0555 | os.ModeDir,
|
||||
},
|
||||
|
@ -83,7 +127,7 @@ var gInodeInfo = map[fuse.InodeID]inodeInfo{
|
|||
|
||||
// hello
|
||||
helloInode: inodeInfo{
|
||||
attributes: fuse.InodeAttributes{
|
||||
attributes: fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: 0444,
|
||||
Size: uint64(len("Hello, world!")),
|
||||
|
@ -92,7 +136,7 @@ var gInodeInfo = map[fuse.InodeID]inodeInfo{
|
|||
|
||||
// dir
|
||||
dirInode: inodeInfo{
|
||||
attributes: fuse.InodeAttributes{
|
||||
attributes: fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: 0555 | os.ModeDir,
|
||||
},
|
||||
|
@ -109,7 +153,7 @@ var gInodeInfo = map[fuse.InodeID]inodeInfo{
|
|||
|
||||
// world
|
||||
worldInode: inodeInfo{
|
||||
attributes: fuse.InodeAttributes{
|
||||
attributes: fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: 0444,
|
||||
Size: uint64(len("Hello, world!")),
|
||||
|
@ -119,7 +163,7 @@ var gInodeInfo = map[fuse.InodeID]inodeInfo{
|
|||
|
||||
func findChildInode(
|
||||
name string,
|
||||
children []fuseutil.Dirent) (inode fuse.InodeID, err error) {
|
||||
children []fuseutil.Dirent) (inode fuseops.InodeID, err error) {
|
||||
for _, e := range children {
|
||||
if e.Name == name {
|
||||
inode = e.Inode
|
||||
|
@ -131,88 +175,76 @@ func findChildInode(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *HelloFS) patchAttributes(
|
||||
attr *fuse.InodeAttributes) {
|
||||
func (fs *helloFS) patchAttributes(
|
||||
attr *fuseops.InodeAttributes) {
|
||||
now := fs.Clock.Now()
|
||||
attr.Atime = now
|
||||
attr.Mtime = now
|
||||
attr.Crtime = now
|
||||
}
|
||||
|
||||
func (fs *HelloFS) Init(
|
||||
ctx context.Context,
|
||||
req *fuse.InitRequest) (
|
||||
resp *fuse.InitResponse, err error) {
|
||||
resp = &fuse.InitResponse{}
|
||||
return
|
||||
func (fs *helloFS) init(op *fuseops.InitOp) {
|
||||
op.Respond(nil)
|
||||
}
|
||||
|
||||
func (fs *HelloFS) LookUpInode(
|
||||
ctx context.Context,
|
||||
req *fuse.LookUpInodeRequest) (
|
||||
resp *fuse.LookUpInodeResponse, err error) {
|
||||
resp = &fuse.LookUpInodeResponse{}
|
||||
func (fs *helloFS) lookUpInode(op *fuseops.LookUpInodeOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
// Find the info for the parent.
|
||||
parentInfo, ok := gInodeInfo[req.Parent]
|
||||
parentInfo, ok := gInodeInfo[op.Parent]
|
||||
if !ok {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
}
|
||||
|
||||
// Find the child within the parent.
|
||||
childInode, err := findChildInode(req.Name, parentInfo.children)
|
||||
childInode, err := findChildInode(op.Name, parentInfo.children)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Copy over information.
|
||||
resp.Entry.Child = childInode
|
||||
resp.Entry.Attributes = gInodeInfo[childInode].attributes
|
||||
op.Entry.Child = childInode
|
||||
op.Entry.Attributes = gInodeInfo[childInode].attributes
|
||||
|
||||
// Patch attributes.
|
||||
fs.patchAttributes(&resp.Entry.Attributes)
|
||||
fs.patchAttributes(&op.Entry.Attributes)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *HelloFS) GetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.GetInodeAttributesRequest) (
|
||||
resp *fuse.GetInodeAttributesResponse, err error) {
|
||||
resp = &fuse.GetInodeAttributesResponse{}
|
||||
func (fs *helloFS) getInodeAttributes(op *fuseops.GetInodeAttributesOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
// Find the info for this inode.
|
||||
info, ok := gInodeInfo[req.Inode]
|
||||
info, ok := gInodeInfo[op.Inode]
|
||||
if !ok {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
}
|
||||
|
||||
// Copy over its attributes.
|
||||
resp.Attributes = info.attributes
|
||||
op.Attributes = info.attributes
|
||||
|
||||
// Patch attributes.
|
||||
fs.patchAttributes(&resp.Attributes)
|
||||
fs.patchAttributes(&op.Attributes)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *HelloFS) OpenDir(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenDirRequest) (resp *fuse.OpenDirResponse, err error) {
|
||||
func (fs *helloFS) openDir(op *fuseops.OpenDirOp) {
|
||||
// Allow opening any directory.
|
||||
resp = &fuse.OpenDirResponse{}
|
||||
return
|
||||
op.Respond(nil)
|
||||
}
|
||||
|
||||
func (fs *HelloFS) ReadDir(
|
||||
ctx context.Context,
|
||||
req *fuse.ReadDirRequest) (resp *fuse.ReadDirResponse, err error) {
|
||||
resp = &fuse.ReadDirResponse{}
|
||||
func (fs *helloFS) readDir(op *fuseops.ReadDirOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
// Find the info for this inode.
|
||||
info, ok := gInodeInfo[req.Inode]
|
||||
info, ok := gInodeInfo[op.Inode]
|
||||
if !ok {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
|
@ -226,18 +258,18 @@ func (fs *HelloFS) ReadDir(
|
|||
entries := info.children
|
||||
|
||||
// Grab the range of interest.
|
||||
if req.Offset > fuse.DirOffset(len(entries)) {
|
||||
if op.Offset > fuseops.DirOffset(len(entries)) {
|
||||
err = fuse.EIO
|
||||
return
|
||||
}
|
||||
|
||||
entries = entries[req.Offset:]
|
||||
entries = entries[op.Offset:]
|
||||
|
||||
// Resume at the specified offset into the array.
|
||||
for _, e := range entries {
|
||||
resp.Data = fuseutil.AppendDirent(resp.Data, e)
|
||||
if len(resp.Data) > req.Size {
|
||||
resp.Data = resp.Data[:req.Size]
|
||||
op.Data = fuseutil.AppendDirent(op.Data, e)
|
||||
if len(op.Data) > op.Size {
|
||||
op.Data = op.Data[:op.Size]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -245,25 +277,21 @@ func (fs *HelloFS) ReadDir(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *HelloFS) OpenFile(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenFileRequest) (resp *fuse.OpenFileResponse, err error) {
|
||||
func (fs *helloFS) openFile(op *fuseops.OpenFileOp) {
|
||||
// Allow opening any file.
|
||||
resp = &fuse.OpenFileResponse{}
|
||||
return
|
||||
op.Respond(nil)
|
||||
}
|
||||
|
||||
func (fs *HelloFS) ReadFile(
|
||||
ctx context.Context,
|
||||
req *fuse.ReadFileRequest) (resp *fuse.ReadFileResponse, err error) {
|
||||
resp = &fuse.ReadFileResponse{}
|
||||
func (fs *helloFS) readFile(op *fuseops.ReadFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
// Let io.ReaderAt deal with the semantics.
|
||||
reader := strings.NewReader("Hello, world!")
|
||||
|
||||
resp.Data = make([]byte, req.Size)
|
||||
n, err := reader.ReadAt(resp.Data, req.Offset)
|
||||
resp.Data = resp.Data[:n]
|
||||
op.Data = make([]byte, op.Size)
|
||||
n, err := reader.ReadAt(op.Data, op.Offset)
|
||||
op.Data = op.Data[:n]
|
||||
|
||||
// Special case: FUSE doesn't expect us to return io.EOF.
|
||||
if err == io.EOF {
|
||||
|
|
|
@ -41,9 +41,10 @@ type HelloFSTest struct {
|
|||
func init() { RegisterTestSuite(&HelloFSTest{}) }
|
||||
|
||||
func (t *HelloFSTest) SetUp(ti *TestInfo) {
|
||||
t.FileSystem = &hellofs.HelloFS{
|
||||
Clock: &t.Clock,
|
||||
}
|
||||
var err error
|
||||
|
||||
t.Server, err = hellofs.NewHelloFS(&t.Clock)
|
||||
AssertEq(nil, err)
|
||||
|
||||
t.SampleTest.SetUp(ti)
|
||||
}
|
||||
|
|
|
@ -29,19 +29,19 @@ import (
|
|||
|
||||
// A struct that implements common behavior needed by tests in the samples/
|
||||
// directory. Use it as an embedded field in your test fixture, calling its
|
||||
// SetUp method from your SetUp method after setting the FileSystem field.
|
||||
// SetUp method from your SetUp method after setting the Server field.
|
||||
type SampleTest struct {
|
||||
// The file system under test and the configuration with which it should be
|
||||
// The server under test and the configuration with which it should be
|
||||
// mounted. These must be set by the user of this type before calling SetUp;
|
||||
// all the other fields below are set by SetUp itself.
|
||||
FileSystem fuse.FileSystem
|
||||
Server fuse.Server
|
||||
MountConfig fuse.MountConfig
|
||||
|
||||
// A context object that can be used for long-running operations.
|
||||
Ctx context.Context
|
||||
|
||||
// A clock with a fixed initial time. The test's set up method may use this
|
||||
// to wire the file system with a clock, if desired.
|
||||
// to wire the server with a clock, if desired.
|
||||
Clock timeutil.SimulatedClock
|
||||
|
||||
// The directory at which the file system is mounted.
|
||||
|
@ -54,12 +54,12 @@ type SampleTest struct {
|
|||
mfs *fuse.MountedFileSystem
|
||||
}
|
||||
|
||||
// Mount t.FileSystem and initialize the other exported fields of the struct.
|
||||
// Mount t.Server and initialize the other exported fields of the struct.
|
||||
// Panics on error.
|
||||
//
|
||||
// REQUIRES: t.FileSystem has been set.
|
||||
// REQUIRES: t.Server has been set.
|
||||
func (t *SampleTest) SetUp(ti *ogletest.TestInfo) {
|
||||
err := t.initialize(t.FileSystem, &t.MountConfig)
|
||||
err := t.initialize(t.Server, &t.MountConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ func (t *SampleTest) SetUp(ti *ogletest.TestInfo) {
|
|||
|
||||
// Like SetUp, but doens't panic.
|
||||
func (t *SampleTest) initialize(
|
||||
fs fuse.FileSystem,
|
||||
server fuse.Server,
|
||||
config *fuse.MountConfig) (err error) {
|
||||
// Initialize the context.
|
||||
t.Ctx = context.Background()
|
||||
|
@ -83,19 +83,12 @@ func (t *SampleTest) initialize(
|
|||
}
|
||||
|
||||
// Mount the file system.
|
||||
t.mfs, err = fuse.Mount(t.Dir, fs, config)
|
||||
t.mfs, err = fuse.Mount(t.Dir, server, config)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Mount: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for it to be ready.
|
||||
err = t.mfs.WaitForReady(t.Ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("WaitForReady: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -20,16 +20,14 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
"github.com/jacobsa/gcloud/syncutil"
|
||||
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type memFS struct {
|
||||
fuseutil.NotImplementedFileSystem
|
||||
|
||||
/////////////////////////
|
||||
// Dependencies
|
||||
/////////////////////////
|
||||
|
@ -44,20 +42,21 @@ type memFS struct {
|
|||
mu syncutil.InvariantMutex
|
||||
|
||||
// The collection of live inodes, indexed by ID. IDs of free inodes that may
|
||||
// be re-used have nil entries. No ID less than fuse.RootInodeID is ever used.
|
||||
// be re-used have nil entries. No ID less than fuseops.RootInodeID is ever
|
||||
// used.
|
||||
//
|
||||
// INVARIANT: len(inodes) > fuse.RootInodeID
|
||||
// INVARIANT: For all i < fuse.RootInodeID, inodes[i] == nil
|
||||
// INVARIANT: inodes[fuse.RootInodeID] != nil
|
||||
// INVARIANT: inodes[fuse.RootInodeID].dir is true
|
||||
// INVARIANT: len(inodes) > fuseops.RootInodeID
|
||||
// INVARIANT: For all i < fuseops.RootInodeID, inodes[i] == nil
|
||||
// INVARIANT: inodes[fuseops.RootInodeID] != nil
|
||||
// INVARIANT: inodes[fuseops.RootInodeID].dir is true
|
||||
inodes []*inode // GUARDED_BY(mu)
|
||||
|
||||
// A list of inode IDs within inodes available for reuse, not including the
|
||||
// reserved IDs less than fuse.RootInodeID.
|
||||
// reserved IDs less than fuseops.RootInodeID.
|
||||
//
|
||||
// INVARIANT: This is all and only indices i of 'inodes' such that i >
|
||||
// fuse.RootInodeID and inodes[i] == nil
|
||||
freeInodes []fuse.InodeID // GUARDED_BY(mu)
|
||||
// fuseops.RootInodeID and inodes[i] == nil
|
||||
freeInodes []fuseops.InodeID // GUARDED_BY(mu)
|
||||
}
|
||||
|
||||
// Create a file system that stores data and metadata in memory.
|
||||
|
@ -68,21 +67,21 @@ type memFS struct {
|
|||
func NewMemFS(
|
||||
uid uint32,
|
||||
gid uint32,
|
||||
clock timeutil.Clock) fuse.FileSystem {
|
||||
clock timeutil.Clock) fuse.Server {
|
||||
// Set up the basic struct.
|
||||
fs := &memFS{
|
||||
clock: clock,
|
||||
inodes: make([]*inode, fuse.RootInodeID+1),
|
||||
inodes: make([]*inode, fuseops.RootInodeID+1),
|
||||
}
|
||||
|
||||
// Set up the root inode.
|
||||
rootAttrs := fuse.InodeAttributes{
|
||||
rootAttrs := fuseops.InodeAttributes{
|
||||
Mode: 0700 | os.ModeDir,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
}
|
||||
|
||||
fs.inodes[fuse.RootInodeID] = newInode(clock, rootAttrs)
|
||||
fs.inodes[fuseops.RootInodeID] = newInode(clock, rootAttrs)
|
||||
|
||||
// Set up invariant checking.
|
||||
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
||||
|
@ -94,25 +93,82 @@ func NewMemFS(
|
|||
// Helpers
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (fs *memFS) ServeOps(c *fuse.Connection) {
|
||||
for {
|
||||
op, err := c.ReadOp()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch typed := op.(type) {
|
||||
case *fuseops.InitOp:
|
||||
fs.init(typed)
|
||||
|
||||
case *fuseops.LookUpInodeOp:
|
||||
fs.lookUpInode(typed)
|
||||
|
||||
case *fuseops.GetInodeAttributesOp:
|
||||
fs.getInodeAttributes(typed)
|
||||
|
||||
case *fuseops.SetInodeAttributesOp:
|
||||
fs.setInodeAttributes(typed)
|
||||
|
||||
case *fuseops.MkDirOp:
|
||||
fs.mkDir(typed)
|
||||
|
||||
case *fuseops.CreateFileOp:
|
||||
fs.createFile(typed)
|
||||
|
||||
case *fuseops.RmDirOp:
|
||||
fs.rmDir(typed)
|
||||
|
||||
case *fuseops.UnlinkOp:
|
||||
fs.unlink(typed)
|
||||
|
||||
case *fuseops.OpenDirOp:
|
||||
fs.openDir(typed)
|
||||
|
||||
case *fuseops.ReadDirOp:
|
||||
fs.readDir(typed)
|
||||
|
||||
case *fuseops.OpenFileOp:
|
||||
fs.openFile(typed)
|
||||
|
||||
case *fuseops.ReadFileOp:
|
||||
fs.readFile(typed)
|
||||
|
||||
case *fuseops.WriteFileOp:
|
||||
fs.writeFile(typed)
|
||||
|
||||
default:
|
||||
typed.Respond(fuse.ENOSYS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *memFS) checkInvariants() {
|
||||
// Check reserved inodes.
|
||||
for i := 0; i < fuse.RootInodeID; i++ {
|
||||
for i := 0; i < fuseops.RootInodeID; i++ {
|
||||
if fs.inodes[i] != nil {
|
||||
panic(fmt.Sprintf("Non-nil inode for ID: %v", i))
|
||||
}
|
||||
}
|
||||
|
||||
// Check the root inode.
|
||||
if !fs.inodes[fuse.RootInodeID].dir {
|
||||
if !fs.inodes[fuseops.RootInodeID].dir {
|
||||
panic("Expected root to be a directory.")
|
||||
}
|
||||
|
||||
// Build our own list of free IDs.
|
||||
freeIDsEncountered := make(map[fuse.InodeID]struct{})
|
||||
for i := fuse.RootInodeID + 1; i < len(fs.inodes); i++ {
|
||||
freeIDsEncountered := make(map[fuseops.InodeID]struct{})
|
||||
for i := fuseops.RootInodeID + 1; i < len(fs.inodes); i++ {
|
||||
inode := fs.inodes[i]
|
||||
if inode == nil {
|
||||
freeIDsEncountered[fuse.InodeID(i)] = struct{}{}
|
||||
freeIDsEncountered[fuseops.InodeID(i)] = struct{}{}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +194,7 @@ func (fs *memFS) checkInvariants() {
|
|||
//
|
||||
// SHARED_LOCKS_REQUIRED(fs.mu)
|
||||
// EXCLUSIVE_LOCK_FUNCTION(inode.mu)
|
||||
func (fs *memFS) getInodeForModifyingOrDie(id fuse.InodeID) (inode *inode) {
|
||||
func (fs *memFS) getInodeForModifyingOrDie(id fuseops.InodeID) (inode *inode) {
|
||||
inode = fs.inodes[id]
|
||||
if inode == nil {
|
||||
panic(fmt.Sprintf("Unknown inode: %v", id))
|
||||
|
@ -153,7 +209,7 @@ func (fs *memFS) getInodeForModifyingOrDie(id fuse.InodeID) (inode *inode) {
|
|||
//
|
||||
// SHARED_LOCKS_REQUIRED(fs.mu)
|
||||
// SHARED_LOCK_FUNCTION(inode.mu)
|
||||
func (fs *memFS) getInodeForReadingOrDie(id fuse.InodeID) (inode *inode) {
|
||||
func (fs *memFS) getInodeForReadingOrDie(id fuseops.InodeID) (inode *inode) {
|
||||
inode = fs.inodes[id]
|
||||
if inode == nil {
|
||||
panic(fmt.Sprintf("Unknown inode: %v", id))
|
||||
|
@ -169,7 +225,7 @@ func (fs *memFS) getInodeForReadingOrDie(id fuse.InodeID) (inode *inode) {
|
|||
// EXCLUSIVE_LOCKS_REQUIRED(fs.mu)
|
||||
// EXCLUSIVE_LOCK_FUNCTION(inode.mu)
|
||||
func (fs *memFS) allocateInode(
|
||||
attrs fuse.InodeAttributes) (id fuse.InodeID, inode *inode) {
|
||||
attrs fuseops.InodeAttributes) (id fuseops.InodeID, inode *inode) {
|
||||
// Create and lock the inode.
|
||||
inode = newInode(fs.clock, attrs)
|
||||
inode.mu.Lock()
|
||||
|
@ -181,7 +237,7 @@ func (fs *memFS) allocateInode(
|
|||
fs.freeInodes = fs.freeInodes[:numFree-1]
|
||||
fs.inodes[id] = inode
|
||||
} else {
|
||||
id = fuse.InodeID(len(fs.inodes))
|
||||
id = fuseops.InodeID(len(fs.inodes))
|
||||
fs.inodes = append(fs.inodes, inode)
|
||||
}
|
||||
|
||||
|
@ -189,37 +245,35 @@ func (fs *memFS) allocateInode(
|
|||
}
|
||||
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(fs.mu)
|
||||
func (fs *memFS) deallocateInode(id fuse.InodeID) {
|
||||
func (fs *memFS) deallocateInode(id fuseops.InodeID) {
|
||||
fs.freeInodes = append(fs.freeInodes, id)
|
||||
fs.inodes[id] = nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// FileSystem methods
|
||||
// Op methods
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (fs *memFS) Init(
|
||||
ctx context.Context,
|
||||
req *fuse.InitRequest) (resp *fuse.InitResponse, err error) {
|
||||
resp = &fuse.InitResponse{}
|
||||
func (fs *memFS) init(op *fuseops.InitOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) LookUpInode(
|
||||
ctx context.Context,
|
||||
req *fuse.LookUpInodeRequest) (resp *fuse.LookUpInodeResponse, err error) {
|
||||
resp = &fuse.LookUpInodeResponse{}
|
||||
func (fs *memFS) lookUpInode(op *fuseops.LookUpInodeOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Grab the parent directory.
|
||||
inode := fs.getInodeForReadingOrDie(req.Parent)
|
||||
inode := fs.getInodeForReadingOrDie(op.Parent)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
// Does the directory have an entry with the given name?
|
||||
childID, ok := inode.LookUpChild(req.Name)
|
||||
childID, ok := inode.LookUpChild(op.Name)
|
||||
if !ok {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
|
@ -230,85 +284,80 @@ func (fs *memFS) LookUpInode(
|
|||
defer child.mu.RUnlock()
|
||||
|
||||
// Fill in the response.
|
||||
resp.Entry.Child = childID
|
||||
resp.Entry.Attributes = child.attributes
|
||||
op.Entry.Child = childID
|
||||
op.Entry.Attributes = child.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
resp.Entry.EntryExpiration = resp.Entry.EntryExpiration
|
||||
op.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) GetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.GetInodeAttributesRequest) (
|
||||
resp *fuse.GetInodeAttributesResponse, err error) {
|
||||
resp = &fuse.GetInodeAttributesResponse{}
|
||||
func (fs *memFS) getInodeAttributes(op *fuseops.GetInodeAttributesOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Grab the inode.
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
inode := fs.getInodeForReadingOrDie(op.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
// Fill in the response.
|
||||
resp.Attributes = inode.attributes
|
||||
op.Attributes = inode.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
op.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) SetInodeAttributes(
|
||||
ctx context.Context,
|
||||
req *fuse.SetInodeAttributesRequest) (
|
||||
resp *fuse.SetInodeAttributesResponse, err error) {
|
||||
resp = &fuse.SetInodeAttributesResponse{}
|
||||
func (fs *memFS) setInodeAttributes(op *fuseops.SetInodeAttributesOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Grab the inode.
|
||||
inode := fs.getInodeForModifyingOrDie(req.Inode)
|
||||
inode := fs.getInodeForModifyingOrDie(op.Inode)
|
||||
defer inode.mu.Unlock()
|
||||
|
||||
// Handle the request.
|
||||
inode.SetAttributes(req.Size, req.Mode, req.Mtime)
|
||||
inode.SetAttributes(op.Size, op.Mode, op.Mtime)
|
||||
|
||||
// Fill in the response.
|
||||
resp.Attributes = inode.attributes
|
||||
op.Attributes = inode.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
op.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) MkDir(
|
||||
ctx context.Context,
|
||||
req *fuse.MkDirRequest) (resp *fuse.MkDirResponse, err error) {
|
||||
resp = &fuse.MkDirResponse{}
|
||||
func (fs *memFS) mkDir(op *fuseops.MkDirOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Grab the parent, which we will update shortly.
|
||||
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
||||
parent := fs.getInodeForModifyingOrDie(op.Parent)
|
||||
defer parent.mu.Unlock()
|
||||
|
||||
// Set up attributes from the child, using the credentials of the calling
|
||||
// process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8).
|
||||
childAttrs := fuse.InodeAttributes{
|
||||
childAttrs := fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: req.Mode,
|
||||
Uid: req.Header.Uid,
|
||||
Gid: req.Header.Gid,
|
||||
Mode: op.Mode,
|
||||
Uid: op.Header().Uid,
|
||||
Gid: op.Header().Gid,
|
||||
}
|
||||
|
||||
// Allocate a child.
|
||||
|
@ -316,44 +365,43 @@ func (fs *memFS) MkDir(
|
|||
defer child.mu.Unlock()
|
||||
|
||||
// Add an entry in the parent.
|
||||
parent.AddChild(childID, req.Name, fuseutil.DT_Directory)
|
||||
parent.AddChild(childID, op.Name, fuseutil.DT_Directory)
|
||||
|
||||
// Fill in the response.
|
||||
resp.Entry.Child = childID
|
||||
resp.Entry.Attributes = child.attributes
|
||||
op.Entry.Child = childID
|
||||
op.Entry.Attributes = child.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
resp.Entry.EntryExpiration = resp.Entry.EntryExpiration
|
||||
op.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) CreateFile(
|
||||
ctx context.Context,
|
||||
req *fuse.CreateFileRequest) (resp *fuse.CreateFileResponse, err error) {
|
||||
resp = &fuse.CreateFileResponse{}
|
||||
func (fs *memFS) createFile(op *fuseops.CreateFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Grab the parent, which we will update shortly.
|
||||
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
||||
parent := fs.getInodeForModifyingOrDie(op.Parent)
|
||||
defer parent.mu.Unlock()
|
||||
|
||||
// Set up attributes from the child, using the credentials of the calling
|
||||
// process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8).
|
||||
now := fs.clock.Now()
|
||||
childAttrs := fuse.InodeAttributes{
|
||||
childAttrs := fuseops.InodeAttributes{
|
||||
Nlink: 1,
|
||||
Mode: req.Mode,
|
||||
Mode: op.Mode,
|
||||
Atime: now,
|
||||
Mtime: now,
|
||||
Ctime: now,
|
||||
Crtime: now,
|
||||
Uid: req.Header.Uid,
|
||||
Gid: req.Header.Gid,
|
||||
Uid: op.Header().Uid,
|
||||
Gid: op.Header().Gid,
|
||||
}
|
||||
|
||||
// Allocate a child.
|
||||
|
@ -361,36 +409,35 @@ func (fs *memFS) CreateFile(
|
|||
defer child.mu.Unlock()
|
||||
|
||||
// Add an entry in the parent.
|
||||
parent.AddChild(childID, req.Name, fuseutil.DT_File)
|
||||
parent.AddChild(childID, op.Name, fuseutil.DT_File)
|
||||
|
||||
// Fill in the response entry.
|
||||
resp.Entry.Child = childID
|
||||
resp.Entry.Attributes = child.attributes
|
||||
op.Entry.Child = childID
|
||||
op.Entry.Attributes = child.attributes
|
||||
|
||||
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||
// (since it also handles invalidation).
|
||||
resp.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
resp.Entry.EntryExpiration = resp.Entry.EntryExpiration
|
||||
op.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||
op.Entry.EntryExpiration = op.Entry.EntryExpiration
|
||||
|
||||
// We have nothing interesting to put in the Handle field.
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) RmDir(
|
||||
ctx context.Context,
|
||||
req *fuse.RmDirRequest) (resp *fuse.RmDirResponse, err error) {
|
||||
resp = &fuse.RmDirResponse{}
|
||||
func (fs *memFS) rmDir(op *fuseops.RmDirOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Grab the parent, which we will update shortly.
|
||||
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
||||
parent := fs.getInodeForModifyingOrDie(op.Parent)
|
||||
defer parent.mu.Unlock()
|
||||
|
||||
// Find the child within the parent.
|
||||
childID, ok := parent.LookUpChild(req.Name)
|
||||
childID, ok := parent.LookUpChild(op.Name)
|
||||
if !ok {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
|
@ -407,7 +454,7 @@ func (fs *memFS) RmDir(
|
|||
}
|
||||
|
||||
// Remove the entry within the parent.
|
||||
parent.RemoveChild(req.Name)
|
||||
parent.RemoveChild(op.Name)
|
||||
|
||||
// Mark the child as unlinked.
|
||||
child.attributes.Nlink--
|
||||
|
@ -415,20 +462,19 @@ func (fs *memFS) RmDir(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) Unlink(
|
||||
ctx context.Context,
|
||||
req *fuse.UnlinkRequest) (resp *fuse.UnlinkResponse, err error) {
|
||||
resp = &fuse.UnlinkResponse{}
|
||||
func (fs *memFS) unlink(op *fuseops.UnlinkOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Grab the parent, which we will update shortly.
|
||||
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
||||
parent := fs.getInodeForModifyingOrDie(op.Parent)
|
||||
defer parent.mu.Unlock()
|
||||
|
||||
// Find the child within the parent.
|
||||
childID, ok := parent.LookUpChild(req.Name)
|
||||
childID, ok := parent.LookUpChild(op.Name)
|
||||
if !ok {
|
||||
err = fuse.ENOENT
|
||||
return
|
||||
|
@ -439,7 +485,7 @@ func (fs *memFS) Unlink(
|
|||
defer child.mu.Unlock()
|
||||
|
||||
// Remove the entry within the parent.
|
||||
parent.RemoveChild(req.Name)
|
||||
parent.RemoveChild(op.Name)
|
||||
|
||||
// Mark the child as unlinked.
|
||||
child.attributes.Nlink--
|
||||
|
@ -447,10 +493,9 @@ func (fs *memFS) Unlink(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) OpenDir(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenDirRequest) (resp *fuse.OpenDirResponse, err error) {
|
||||
resp = &fuse.OpenDirResponse{}
|
||||
func (fs *memFS) openDir(op *fuseops.OpenDirOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
@ -458,7 +503,7 @@ func (fs *memFS) OpenDir(
|
|||
// We don't mutate spontaneosuly, so if the VFS layer has asked for an
|
||||
// inode that doesn't exist, something screwed up earlier (a lookup, a
|
||||
// cache invalidation, etc.).
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
inode := fs.getInodeForReadingOrDie(op.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
if !inode.dir {
|
||||
|
@ -468,20 +513,19 @@ func (fs *memFS) OpenDir(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) ReadDir(
|
||||
ctx context.Context,
|
||||
req *fuse.ReadDirRequest) (resp *fuse.ReadDirResponse, err error) {
|
||||
resp = &fuse.ReadDirResponse{}
|
||||
func (fs *memFS) readDir(op *fuseops.ReadDirOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Grab the directory.
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
inode := fs.getInodeForReadingOrDie(op.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
// Serve the request.
|
||||
resp.Data, err = inode.ReadDir(int(req.Offset), req.Size)
|
||||
op.Data, err = inode.ReadDir(int(op.Offset), op.Size)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("inode.ReadDir: %v", err)
|
||||
return
|
||||
|
@ -490,10 +534,9 @@ func (fs *memFS) ReadDir(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) OpenFile(
|
||||
ctx context.Context,
|
||||
req *fuse.OpenFileRequest) (resp *fuse.OpenFileResponse, err error) {
|
||||
resp = &fuse.OpenFileResponse{}
|
||||
func (fs *memFS) openFile(op *fuseops.OpenFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
@ -501,7 +544,7 @@ func (fs *memFS) OpenFile(
|
|||
// We don't mutate spontaneosuly, so if the VFS layer has asked for an
|
||||
// inode that doesn't exist, something screwed up earlier (a lookup, a
|
||||
// cache invalidation, etc.).
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
inode := fs.getInodeForReadingOrDie(op.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
if inode.dir {
|
||||
|
@ -511,22 +554,21 @@ func (fs *memFS) OpenFile(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) ReadFile(
|
||||
ctx context.Context,
|
||||
req *fuse.ReadFileRequest) (resp *fuse.ReadFileResponse, err error) {
|
||||
resp = &fuse.ReadFileResponse{}
|
||||
func (fs *memFS) readFile(op *fuseops.ReadFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Find the inode in question.
|
||||
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||
inode := fs.getInodeForReadingOrDie(op.Inode)
|
||||
defer inode.mu.RUnlock()
|
||||
|
||||
// Serve the request.
|
||||
resp.Data = make([]byte, req.Size)
|
||||
n, err := inode.ReadAt(resp.Data, req.Offset)
|
||||
resp.Data = resp.Data[:n]
|
||||
op.Data = make([]byte, op.Size)
|
||||
n, err := inode.ReadAt(op.Data, op.Offset)
|
||||
op.Data = op.Data[:n]
|
||||
|
||||
// Don't return EOF errors; we just indicate EOF to fuse using a short read.
|
||||
if err == io.EOF {
|
||||
|
@ -536,20 +578,19 @@ func (fs *memFS) ReadFile(
|
|||
return
|
||||
}
|
||||
|
||||
func (fs *memFS) WriteFile(
|
||||
ctx context.Context,
|
||||
req *fuse.WriteFileRequest) (resp *fuse.WriteFileResponse, err error) {
|
||||
resp = &fuse.WriteFileResponse{}
|
||||
func (fs *memFS) writeFile(op *fuseops.WriteFileOp) {
|
||||
var err error
|
||||
defer func() { op.Respond(err) }()
|
||||
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
|
||||
// Find the inode in question.
|
||||
inode := fs.getInodeForModifyingOrDie(req.Inode)
|
||||
inode := fs.getInodeForModifyingOrDie(op.Inode)
|
||||
defer inode.mu.Unlock()
|
||||
|
||||
// Serve the request.
|
||||
_, err = inode.WriteAt(req.Data, req.Offset)
|
||||
_, err = inode.WriteAt(op.Data, op.Offset)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -20,10 +20,10 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
"github.com/jacobsa/fuse/fuseutil"
|
||||
"github.com/jacobsa/gcloud/syncutil"
|
||||
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
||||
)
|
||||
|
||||
// Common attributes for files and directories.
|
||||
|
@ -53,7 +53,7 @@ type inode struct {
|
|||
// INVARIANT: If dir, then os.ModeDir is set
|
||||
// INVARIANT: If !dir, then os.ModeDir is not set
|
||||
// INVARIANT: attributes.Size == len(contents)
|
||||
attributes fuse.InodeAttributes // GUARDED_BY(mu)
|
||||
attributes fuseops.InodeAttributes // GUARDED_BY(mu)
|
||||
|
||||
// For directories, entries describing the children of the directory. Unused
|
||||
// entries are of type DT_Unknown.
|
||||
|
@ -82,7 +82,7 @@ type inode struct {
|
|||
// time-related information (the inode object will take care of that).
|
||||
func newInode(
|
||||
clock timeutil.Clock,
|
||||
attrs fuse.InodeAttributes) (in *inode) {
|
||||
attrs fuseops.InodeAttributes) (in *inode) {
|
||||
// Update time info.
|
||||
now := clock.Now()
|
||||
attrs.Mtime = now
|
||||
|
@ -122,7 +122,7 @@ func (inode *inode) checkInvariants() {
|
|||
|
||||
childNames := make(map[string]struct{})
|
||||
for i, e := range inode.entries {
|
||||
if e.Offset != fuse.DirOffset(i+1) {
|
||||
if e.Offset != fuseops.DirOffset(i+1) {
|
||||
panic(fmt.Sprintf("Unexpected offset: %v", e.Offset))
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ func (inode *inode) Len() (n int) {
|
|||
//
|
||||
// REQUIRES: inode.dir
|
||||
// SHARED_LOCKS_REQUIRED(inode.mu)
|
||||
func (inode *inode) LookUpChild(name string) (id fuse.InodeID, ok bool) {
|
||||
func (inode *inode) LookUpChild(name string) (id fuseops.InodeID, ok bool) {
|
||||
index, ok := inode.findChild(name)
|
||||
if ok {
|
||||
id = inode.entries[index].Inode
|
||||
|
@ -210,7 +210,7 @@ func (inode *inode) LookUpChild(name string) (id fuse.InodeID, ok bool) {
|
|||
// REQUIRES: dt != fuseutil.DT_Unknown
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
|
||||
func (inode *inode) AddChild(
|
||||
id fuse.InodeID,
|
||||
id fuseops.InodeID,
|
||||
name string,
|
||||
dt fuseutil.DirentType) {
|
||||
var index int
|
||||
|
@ -221,7 +221,7 @@ func (inode *inode) AddChild(
|
|||
// No matter where we place the entry, make sure it has the correct Offset
|
||||
// field.
|
||||
defer func() {
|
||||
inode.entries[index].Offset = fuse.DirOffset(index + 1)
|
||||
inode.entries[index].Offset = fuseops.DirOffset(index + 1)
|
||||
}()
|
||||
|
||||
// Set up the entry.
|
||||
|
@ -262,7 +262,7 @@ func (inode *inode) RemoveChild(name string) {
|
|||
// Mark it as unused.
|
||||
inode.entries[i] = fuseutil.Dirent{
|
||||
Type: fuseutil.DT_Unknown,
|
||||
Offset: fuse.DirOffset(i + 1),
|
||||
Offset: fuseops.DirOffset(i + 1),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ type MemFSTest struct {
|
|||
func init() { RegisterTestSuite(&MemFSTest{}) }
|
||||
|
||||
func (t *MemFSTest) SetUp(ti *TestInfo) {
|
||||
t.FileSystem = memfs.NewMemFS(currentUid(), currentGid(), &t.Clock)
|
||||
t.Server = memfs.NewMemFS(currentUid(), currentGid(), &t.Clock)
|
||||
t.SampleTest.SetUp(ti)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ var fFsyncsFile = flag.Uint64("flushfs.fsyncs_file", 0, "")
|
|||
var fFlushError = flag.Int("flushfs.flush_error", 0, "")
|
||||
var fFsyncError = flag.Int("flushfs.fsync_error", 0, "")
|
||||
|
||||
func makeFlushFS() (fs fuse.FileSystem, err error) {
|
||||
func makeFlushFS() (server fuse.Server, err error) {
|
||||
// Check the flags.
|
||||
if *fFlushesFile == 0 || *fFsyncsFile == 0 {
|
||||
err = fmt.Errorf("You must set the flushfs flags.")
|
||||
|
@ -83,18 +83,18 @@ func makeFlushFS() (fs fuse.FileSystem, err error) {
|
|||
reportFsync := report(fsyncs, fsyncErr)
|
||||
|
||||
// Create the file system.
|
||||
fs, err = flushfs.NewFileSystem(reportFlush, reportFsync)
|
||||
server, err = flushfs.NewFileSystem(reportFlush, reportFsync)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func makeFS() (fs fuse.FileSystem, err error) {
|
||||
func makeFS() (server fuse.Server, err error) {
|
||||
switch *fType {
|
||||
default:
|
||||
err = fmt.Errorf("Unknown FS type: %v", *fType)
|
||||
|
||||
case "flushfs":
|
||||
fs, err = makeFlushFS()
|
||||
server, err = makeFlushFS()
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -124,7 +124,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Create an appropriate file system.
|
||||
fs, err := makeFS()
|
||||
server, err := makeFS()
|
||||
if err != nil {
|
||||
log.Fatalf("makeFS: %v", err)
|
||||
}
|
||||
|
@ -134,16 +134,11 @@ func main() {
|
|||
log.Fatalf("You must set --mount_point.")
|
||||
}
|
||||
|
||||
mfs, err := fuse.Mount(*fMountPoint, fs, &fuse.MountConfig{})
|
||||
mfs, err := fuse.Mount(*fMountPoint, server, &fuse.MountConfig{})
|
||||
if err != nil {
|
||||
log.Fatalf("Mount: %v", err)
|
||||
}
|
||||
|
||||
// Wait for it to be ready.
|
||||
if err = mfs.WaitForReady(context.Background()); err != nil {
|
||||
log.Fatalf("WaitForReady: %v", err)
|
||||
}
|
||||
|
||||
// Signal that it is ready.
|
||||
_, err = readyFile.Write([]byte("x"))
|
||||
if err != nil {
|
||||
|
|
|
@ -67,8 +67,6 @@ type SubprocessTest struct {
|
|||
|
||||
// Mount the file system and initialize the other exported fields of the
|
||||
// struct. Panics on error.
|
||||
//
|
||||
// REQUIRES: t.FileSystem has been set.
|
||||
func (t *SubprocessTest) SetUp(ti *ogletest.TestInfo) {
|
||||
err := t.initialize()
|
||||
if err != nil {
|
||||
|
|
560
server.go
560
server.go
|
@ -1,560 +0,0 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
)
|
||||
|
||||
// An object that terminates one end of the userspace <-> FUSE VFS connection.
|
||||
type server struct {
|
||||
logger *log.Logger
|
||||
fs FileSystem
|
||||
}
|
||||
|
||||
// Create a server that relays requests to the supplied file system.
|
||||
func newServer(fs FileSystem) (s *server, err error) {
|
||||
s = &server{
|
||||
logger: getLogger(),
|
||||
fs: fs,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Convert an absolute cache expiration time to a relative time from now for
|
||||
// consumption by fuse.
|
||||
func convertExpirationTime(t time.Time) (d time.Duration) {
|
||||
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
|
||||
// counts of nanoseconds (cf. http://goo.gl/EJupJV). The bazil.org/fuse
|
||||
// package converts time.Duration values to this form in a straightforward
|
||||
// way (cf. http://goo.gl/FJhV8j).
|
||||
//
|
||||
// So negative durations are right out. There is no need to cap the positive
|
||||
// magnitude, because 2^64 seconds is well longer than the 2^63 ns range of
|
||||
// time.Duration.
|
||||
d = t.Sub(time.Now())
|
||||
if d < 0 {
|
||||
d = 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func convertChildInodeEntry(
|
||||
in *ChildInodeEntry,
|
||||
out *bazilfuse.LookupResponse) {
|
||||
out.Node = bazilfuse.NodeID(in.Child)
|
||||
out.Generation = uint64(in.Generation)
|
||||
out.Attr = convertAttributes(in.Child, in.Attributes)
|
||||
out.AttrValid = convertExpirationTime(in.AttributesExpiration)
|
||||
out.EntryValid = convertExpirationTime(in.EntryExpiration)
|
||||
}
|
||||
|
||||
func convertHeader(
|
||||
in bazilfuse.Header) (out RequestHeader) {
|
||||
out.Uid = in.Uid
|
||||
out.Gid = in.Gid
|
||||
return
|
||||
}
|
||||
|
||||
// Serve the fuse connection by repeatedly reading requests from the supplied
|
||||
// FUSE connection, responding as dictated by the file system. Return when the
|
||||
// connection is closed or an unexpected error occurs.
|
||||
func (s *server) Serve(c *bazilfuse.Conn) (err error) {
|
||||
// Read a message at a time, dispatching to goroutines doing the actual
|
||||
// processing.
|
||||
for {
|
||||
var fuseReq bazilfuse.Request
|
||||
fuseReq, err = c.ReadRequest()
|
||||
|
||||
// ReadRequest returns EOF when the connection has been closed.
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, forward on errors.
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Conn.ReadRequest: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
go s.handleFuseRequest(fuseReq)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
||||
// Log the request.
|
||||
s.logger.Println("Received:", fuseReq)
|
||||
|
||||
// If it becomes important, we can support cancellation when interrupted (cf.
|
||||
// http://goo.gl/C08eAv). If we want to do this, we should find some way to
|
||||
// coax the system into reproducing such requests so we can test the
|
||||
// behavior.
|
||||
ctx := context.Background()
|
||||
|
||||
// Attempt to handle it.
|
||||
switch typed := fuseReq.(type) {
|
||||
case *bazilfuse.InitRequest:
|
||||
// Convert the request.
|
||||
req := &InitRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.Init(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.InitResponse{}
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.StatfsRequest:
|
||||
// Responding to this is required to make mounting work, at least on OS X.
|
||||
// We don't currently expose the capability for the file system to
|
||||
// intercept this.
|
||||
fuseResp := &bazilfuse.StatfsResponse{}
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.LookupRequest:
|
||||
// Convert the request.
|
||||
req := &LookUpInodeRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Parent: InodeID(typed.Header.Node),
|
||||
Name: typed.Name,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.LookUpInode(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.LookupResponse{}
|
||||
convertChildInodeEntry(&resp.Entry, fuseResp)
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.GetattrRequest:
|
||||
// Convert the request.
|
||||
req := &GetInodeAttributesRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.GetInodeAttributes(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.GetattrResponse{
|
||||
Attr: convertAttributes(req.Inode, resp.Attributes),
|
||||
AttrValid: convertExpirationTime(resp.AttributesExpiration),
|
||||
}
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.SetattrRequest:
|
||||
// Convert the request.
|
||||
req := &SetInodeAttributesRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrSize != 0 {
|
||||
req.Size = &typed.Size
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrMode != 0 {
|
||||
req.Mode = &typed.Mode
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrAtime != 0 {
|
||||
req.Atime = &typed.Atime
|
||||
}
|
||||
|
||||
if typed.Valid&bazilfuse.SetattrMtime != 0 {
|
||||
req.Mtime = &typed.Mtime
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.SetInodeAttributes(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.SetattrResponse{
|
||||
Attr: convertAttributes(req.Inode, resp.Attributes),
|
||||
AttrValid: convertExpirationTime(resp.AttributesExpiration),
|
||||
}
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.MkdirRequest:
|
||||
// Convert the request.
|
||||
req := &MkDirRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Parent: InodeID(typed.Header.Node),
|
||||
Name: typed.Name,
|
||||
Mode: typed.Mode,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.MkDir(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.MkdirResponse{}
|
||||
convertChildInodeEntry(&resp.Entry, &fuseResp.LookupResponse)
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.CreateRequest:
|
||||
// Convert the request.
|
||||
req := &CreateFileRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Parent: InodeID(typed.Header.Node),
|
||||
Name: typed.Name,
|
||||
Mode: typed.Mode,
|
||||
Flags: typed.Flags,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.CreateFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.CreateResponse{
|
||||
OpenResponse: bazilfuse.OpenResponse{
|
||||
Handle: bazilfuse.HandleID(resp.Handle),
|
||||
},
|
||||
}
|
||||
convertChildInodeEntry(&resp.Entry, &fuseResp.LookupResponse)
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
case *bazilfuse.RemoveRequest:
|
||||
if typed.Dir {
|
||||
// Convert the request.
|
||||
req := &RmDirRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Parent: InodeID(typed.Header.Node),
|
||||
Name: typed.Name,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.RmDir(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Respond successfully.
|
||||
s.logger.Println("Responding OK.")
|
||||
typed.Respond()
|
||||
} else {
|
||||
// Convert the request.
|
||||
req := &UnlinkRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Parent: InodeID(typed.Header.Node),
|
||||
Name: typed.Name,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.Unlink(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Respond successfully.
|
||||
s.logger.Println("Responding OK.")
|
||||
typed.Respond()
|
||||
}
|
||||
|
||||
case *bazilfuse.OpenRequest:
|
||||
// Directory or file?
|
||||
if typed.Dir {
|
||||
// Convert the request.
|
||||
req := &OpenDirRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Flags: typed.Flags,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.OpenDir(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.OpenResponse{
|
||||
Handle: bazilfuse.HandleID(resp.Handle),
|
||||
}
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
} else {
|
||||
// Convert the request.
|
||||
req := &OpenFileRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Flags: typed.Flags,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.OpenFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.OpenResponse{
|
||||
Handle: bazilfuse.HandleID(resp.Handle),
|
||||
}
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
}
|
||||
|
||||
case *bazilfuse.ReadRequest:
|
||||
// Directory or file?
|
||||
if typed.Dir {
|
||||
// Convert the request.
|
||||
req := &ReadDirRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Handle: HandleID(typed.Handle),
|
||||
Offset: DirOffset(typed.Offset),
|
||||
Size: typed.Size,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.ReadDir(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.ReadResponse{
|
||||
Data: resp.Data,
|
||||
}
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
} else {
|
||||
// Convert the request.
|
||||
req := &ReadFileRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Handle: HandleID(typed.Handle),
|
||||
Offset: typed.Offset,
|
||||
Size: typed.Size,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
resp, err := s.fs.ReadFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.ReadResponse{
|
||||
Data: resp.Data,
|
||||
}
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
}
|
||||
|
||||
case *bazilfuse.FsyncRequest:
|
||||
// We don't currently support this for directories.
|
||||
if typed.Dir {
|
||||
s.logger.Println("fsyncdir not supported. Returning ENOSYS.")
|
||||
typed.RespondError(ENOSYS)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the request.
|
||||
req := &SyncFileRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Handle: HandleID(typed.Handle),
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.SyncFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Println("Responding OK.")
|
||||
typed.Respond()
|
||||
|
||||
case *bazilfuse.FlushRequest:
|
||||
// Convert the request.
|
||||
req := &FlushFileRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Handle: HandleID(typed.Handle),
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.FlushFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Println("Responding OK.")
|
||||
typed.Respond()
|
||||
|
||||
case *bazilfuse.ReleaseRequest:
|
||||
// Directory or file?
|
||||
if typed.Dir {
|
||||
// Convert the request.
|
||||
req := &ReleaseDirHandleRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Handle: HandleID(typed.Handle),
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.ReleaseDirHandle(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Respond successfully.
|
||||
s.logger.Println("Responding OK.")
|
||||
typed.Respond()
|
||||
} else {
|
||||
// Convert the request.
|
||||
req := &ReleaseFileHandleRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Handle: HandleID(typed.Handle),
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.ReleaseFileHandle(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Respond successfully.
|
||||
s.logger.Println("Responding OK.")
|
||||
typed.Respond()
|
||||
}
|
||||
|
||||
case *bazilfuse.WriteRequest:
|
||||
// Convert the request.
|
||||
req := &WriteFileRequest{
|
||||
Header: convertHeader(typed.Header),
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Handle: HandleID(typed.Handle),
|
||||
Data: typed.Data,
|
||||
Offset: typed.Offset,
|
||||
}
|
||||
|
||||
// Call the file system.
|
||||
_, err := s.fs.WriteFile(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Println("Responding:", err)
|
||||
typed.RespondError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the response.
|
||||
fuseResp := &bazilfuse.WriteResponse{
|
||||
Size: len(typed.Data),
|
||||
}
|
||||
|
||||
s.logger.Println("Responding:", fuseResp)
|
||||
typed.Respond(fuseResp)
|
||||
|
||||
default:
|
||||
s.logger.Println("Unhandled type. Returning ENOSYS.")
|
||||
typed.RespondError(ENOSYS)
|
||||
}
|
||||
}
|
||||
|
||||
func convertAttributes(inode InodeID, attr InodeAttributes) bazilfuse.Attr {
|
||||
return bazilfuse.Attr{
|
||||
Inode: uint64(inode),
|
||||
Size: attr.Size,
|
||||
Mode: attr.Mode,
|
||||
Nlink: uint32(attr.Nlink),
|
||||
Atime: attr.Atime,
|
||||
Mtime: attr.Mtime,
|
||||
Ctime: attr.Ctime,
|
||||
Crtime: attr.Crtime,
|
||||
Uid: attr.Uid,
|
||||
Gid: attr.Gid,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue