diff --git a/README.md b/README.md index fcc6d66..36349e3 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/connection.go b/connection.go new file mode 100644 index 0000000..1c92dfb --- /dev/null +++ b/connection.go @@ -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 +} diff --git a/doc.go b/doc.go index 1e60be1..8bbdaf4 100644 --- a/doc.go +++ b/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 diff --git a/errors.go b/errors.go index 8bbd7ca..316234f 100644 --- a/errors.go +++ b/errors.go @@ -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 diff --git a/file_system.go b/file_system.go deleted file mode 100644 index 52f8998..0000000 --- a/file_system.go +++ /dev/null @@ -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 { -} diff --git a/fuseops/convert.go b/fuseops/convert.go index 0b2b17b..4191af4 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -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) } diff --git a/fuseops/ops.go b/fuseops/ops.go index 8c020e1..70d40c6 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -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() } diff --git a/fuseutil/dirent.go b/fuseutil/dirent.go index 5e81a1e..fcd5bdd 100644 --- a/fuseutil/dirent.go +++ b/fuseutil/dirent.go @@ -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 diff --git a/fuseutil/not_implemented_file_system.go b/fuseutil/not_implemented_file_system.go deleted file mode 100644 index 9b21d95..0000000 --- a/fuseutil/not_implemented_file_system.go +++ /dev/null @@ -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 -} diff --git a/mounted_file_system.go b/mounted_file_system.go index d396e1f..ec98517 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -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 } diff --git a/samples/cachingfs/caching_fs.go b/samples/cachingfs/caching_fs.go index bf3fd8b..9fcd2c3 100644 --- a/samples/cachingfs/caching_fs.go +++ b/samples/cachingfs/caching_fs.go @@ -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 } diff --git a/samples/cachingfs/caching_fs_test.go b/samples/cachingfs/caching_fs_test.go index 2daf46c..1819981 100644 --- a/samples/cachingfs/caching_fs_test.go +++ b/samples/cachingfs/caching_fs_test.go @@ -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) diff --git a/samples/flushfs/flush_fs.go b/samples/flushfs/flush_fs.go index 8984972..639c73d 100644 --- a/samples/flushfs/flush_fs.go +++ b/samples/flushfs/flush_fs.go @@ -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 } diff --git a/samples/hellofs/hello_fs.go b/samples/hellofs/hello_fs.go index a1887c1..11104c2 100644 --- a/samples/hellofs/hello_fs.go +++ b/samples/hellofs/hello_fs.go @@ -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 { diff --git a/samples/hellofs/hello_fs_test.go b/samples/hellofs/hello_fs_test.go index c336029..4fa042f 100644 --- a/samples/hellofs/hello_fs_test.go +++ b/samples/hellofs/hello_fs_test.go @@ -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) } diff --git a/samples/in_process.go b/samples/in_process.go index c0fb1da..2fc2ecf 100644 --- a/samples/in_process.go +++ b/samples/in_process.go @@ -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 } diff --git a/samples/memfs/fs.go b/samples/memfs/fs.go index cd3fe3d..882bf84 100644 --- a/samples/memfs/fs.go +++ b/samples/memfs/fs.go @@ -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 } diff --git a/samples/memfs/inode.go b/samples/memfs/inode.go index a78e007..9005f26 100644 --- a/samples/memfs/inode.go +++ b/samples/memfs/inode.go @@ -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), } } diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index f51648d..a5d9fd9 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -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) } diff --git a/samples/mount_sample/mount.go b/samples/mount_sample/mount.go index 5559b7e..4577c23 100644 --- a/samples/mount_sample/mount.go +++ b/samples/mount_sample/mount.go @@ -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 { diff --git a/samples/subprocess.go b/samples/subprocess.go index ccd2ae2..510c0ab 100644 --- a/samples/subprocess.go +++ b/samples/subprocess.go @@ -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 { diff --git a/server.go b/server.go deleted file mode 100644 index 91c05ca..0000000 --- a/server.go +++ /dev/null @@ -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, - } -}