diff --git a/fuseops/convert.go b/fuseops/convert.go new file mode 100644 index 0000000..0b2b17b --- /dev/null +++ b/fuseops/convert.go @@ -0,0 +1,264 @@ +// 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 fuseops contains implementations of the fuse.Op interface that may +// be returned by fuse.Connection.ReadOp. See documentation in that package for +// more. +package fuseops + +import ( + "time" + + "github.com/jacobsa/bazilfuse" + "golang.org/x/net/context" +) + +// Convert the supplied bazilfuse request struct to an Op, returning nil if it +// is unknown. +// +// 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) { + var co *commonOp + + switch typed := r.(type) { + case *bazilfuse.InitRequest: + to := &InitOp{} + o = to + co = &to.commonOp + + case *bazilfuse.LookupRequest: + to := &LookUpInodeOp{ + Parent: InodeID(typed.Header.Node), + Name: typed.Name, + } + o = to + co = &to.commonOp + + case *bazilfuse.GetattrRequest: + to := &GetInodeAttributesOp{ + Inode: InodeID(typed.Header.Node), + } + o = to + co = &to.commonOp + + case *bazilfuse.SetattrRequest: + to := &SetInodeAttributesOp{ + Inode: InodeID(typed.Header.Node), + } + o = to + co = &to.commonOp + + case *bazilfuse.MkdirRequest: + to := &MkDirOp{ + Parent: InodeID(typed.Header.Node), + Name: typed.Name, + Mode: typed.Mode, + } + o = to + co = &to.commonOp + + case *bazilfuse.CreateRequest: + to := &CreateFileOp{ + Parent: InodeID(typed.Header.Node), + Name: typed.Name, + Mode: typed.Mode, + Flags: typed.Flags, + } + o = to + co = &to.commonOp + + case *bazilfuse.RemoveRequest: + if typed.Dir { + to := &RmDirOp{ + Parent: InodeID(typed.Header.Node), + Name: typed.Name, + } + o = to + co = &to.commonOp + } else { + to := &UnlinkOp{ + Parent: InodeID(typed.Header.Node), + Name: typed.Name, + } + o = to + co = &to.commonOp + } + + case *bazilfuse.OpenRequest: + if typed.Dir { + to := &OpenDirOp{ + Inode: InodeID(typed.Header.Node), + Flags: typed.Flags, + } + o = to + co = &to.commonOp + } else { + to := &OpenFileOp{ + Inode: InodeID(typed.Header.Node), + Flags: typed.Flags, + } + o = to + co = &to.commonOp + } + + case *bazilfuse.ReadRequest: + if typed.Dir { + to := &ReadDirOp{ + Inode: InodeID(typed.Header.Node), + Handle: HandleID(typed.Handle), + Offset: DirOffset(typed.Offset), + Size: typed.Size, + } + o = to + co = &to.commonOp + } else { + to := &ReadFileOp{ + Inode: InodeID(typed.Header.Node), + Handle: HandleID(typed.Handle), + Offset: typed.Offset, + Size: typed.Size, + } + o = to + co = &to.commonOp + } + + case *bazilfuse.ReleaseRequest: + if typed.Dir { + to := &ReleaseDirHandleOp{ + Handle: HandleID(typed.Handle), + } + o = to + co = &to.commonOp + } else { + to := &ReadFileOp{ + Handle: HandleID(typed.Handle), + } + o = to + co = &to.commonOp + } + + case *bazilfuse.WriteRequest: + to := &WriteFileOp{ + Inode: InodeID(typed.Header.Node), + Handle: HandleID(typed.Handle), + Data: typed.Data, + Offset: typed.Offset, + } + o = to + co = &to.commonOp + + case *bazilfuse.FsyncRequest: + // We don't currently support this for directories. + if typed.Dir { + return + } + + to := &SyncFileOp{ + Inode: InodeID(typed.Header.Node), + Handle: HandleID(typed.Handle), + } + o = to + co = &to.commonOp + + case *bazilfuse.FlushRequest: + to := &FlushFileOp{ + Inode: InodeID(typed.Header.Node), + Handle: HandleID(typed.Handle), + } + o = to + co = &to.commonOp + + default: + return + } + + co.init(r) + return +} + +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, + } +} + +// 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) +} + +// A helper for embedding common behavior. +type commonOp struct { + ctx context.Context + r bazilfuse.Request +} + +func (o *commonOp) init(r bazilfuse.Request) { + o.ctx = context.Background() + o.r = r +} + +func (o *commonOp) Header() OpHeader { + bh := o.r.Hdr() + return OpHeader{ + Uid: bh.Uid, + Gid: bh.Gid, + } +} + +func (o *commonOp) Context() context.Context { + return o.ctx +} + +func (o *commonOp) respondErr(err error) { + if err != nil { + panic("Expect non-nil here.") + } + + o.r.RespondError(err) +} diff --git a/fuseops/ops.go b/fuseops/ops.go index a337710..8c020e1 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -22,12 +22,38 @@ import ( "time" "github.com/jacobsa/bazilfuse" + "golang.org/x/net/context" ) +type Op interface { + // Return the fields common to all operations. + Header() OpHeader + + // A context that can be used for long-running operations. + Context() context.Context + + // Repond to the operation with the supplied error. If there is no error, set + // any necessary output fields and then call Respond(nil). + Respond(error) +} + +//////////////////////////////////////////////////////////////////////// +// Setup +//////////////////////////////////////////////////////////////////////// + // Sent once when mounting the file system. It must succeed in order for the // mount to succeed. type InitOp struct { - Header OpHeader + commonOp +} + +func (o *InitOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.InitRequest).Respond(&bazilfuse.InitResponse{}) } //////////////////////////////////////////////////////////////////////// @@ -37,7 +63,7 @@ type InitOp struct { // Look up a child by name within a parent directory. The kernel sends this // when resolving user paths to dentry structs, which are then cached. type LookUpInodeOp struct { - Header OpHeader + commonOp // The ID of the directory inode to which the child belongs. Parent InodeID @@ -57,12 +83,23 @@ type LookUpInodeOp struct { Entry ChildInodeEntry } +func (o *LookUpInodeOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.LookupResponse{} + convertChildInodeEntry(&o.Entry, &resp) + o.r.(*bazilfuse.LookupRequest).Respond(&resp) +} + // Refresh the attributes for an inode whose ID was previously returned in a // LookUpInodeOp. The kernel sends this when the FUSE VFS layer's cache of // inode attributes is stale. This is controlled by the AttributesExpiration // field of ChildInodeEntry, etc. type GetInodeAttributesOp struct { - Header OpHeader + commonOp // The inode of interest. Inode InodeID @@ -74,12 +111,26 @@ type GetInodeAttributesOp struct { AttributesExpiration time.Time } +func (o *GetInodeAttributesOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.GetattrResponse{ + Attr: convertAttributes(o.Inode, o.Attributes), + AttrValid: convertExpirationTime(o.AttributesExpiration), + } + + o.r.(*bazilfuse.GetattrRequest).Respond(&resp) +} + // Change attributes for an inode. // // The kernel sends this for obvious cases like chmod(2), and for less obvious // cases like ftrunctate(2). type SetInodeAttributesOp struct { - Header OpHeader + commonOp // The inode of interest. Inode InodeID @@ -97,10 +148,24 @@ type SetInodeAttributesOp struct { AttributesExpiration time.Time } +func (o *SetInodeAttributesOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.SetattrResponse{ + Attr: convertAttributes(o.Inode, o.Attributes), + AttrValid: convertExpirationTime(o.AttributesExpiration), + } + + o.r.(*bazilfuse.SetattrRequest).Respond(&resp) +} + // Forget an inode ID previously issued (e.g. by LookUpInode or MkDir). The // kernel sends this when removing an inode from its internal caches. type ForgetInodeOp struct { - Header OpHeader + commonOp // 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 @@ -108,6 +173,15 @@ type ForgetInodeOp struct { ID InodeID } +func (o *ForgetInodeOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.ForgetRequest).Respond() +} + //////////////////////////////////////////////////////////////////////// // Inode creation //////////////////////////////////////////////////////////////////////// @@ -120,7 +194,7 @@ type ForgetInodeOp struct { // http://goo.gl/FZpLu5). But volatile file systems and paranoid non-volatile // file systems should check for the reasons described below on CreateFile. type MkDirOp struct { - Header OpHeader + commonOp // The ID of parent directory inode within which to create the child. Parent InodeID @@ -133,6 +207,16 @@ type MkDirOp struct { Entry ChildInodeEntry } +func (o *MkDirOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.MkdirResponse{} + o.r.(*bazilfuse.MkdirRequest).Respond(&resp) +} + // Create a file inode and open it. // // The kernel sends this when the user asks to open a file with the O_CREAT @@ -147,7 +231,7 @@ type MkDirOp struct { // course particularly applies to file systems that are volatile from the // kernel's point of view. type CreateFileOp struct { - Header OpHeader + commonOp // The ID of parent directory inode within which to create the child file. Parent InodeID @@ -173,6 +257,22 @@ type CreateFileOp struct { Handle HandleID } +func (o *CreateFileOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.CreateResponse{ + OpenResponse: bazilfuse.OpenResponse{ + Handle: bazilfuse.HandleID(o.Handle), + }, + } + convertChildInodeEntry(&o.Entry, &resp.LookupResponse) + + o.r.(*bazilfuse.CreateRequest).Respond(&resp) +} + //////////////////////////////////////////////////////////////////////// // Unlinking //////////////////////////////////////////////////////////////////////// @@ -185,7 +285,7 @@ type CreateFileOp struct { // // Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf) type RmDirOp struct { - Header OpHeader + commonOp // The ID of parent directory inode, and the name of the directory being // removed within it. @@ -193,13 +293,22 @@ type RmDirOp struct { Name string } +func (o *RmDirOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.RemoveRequest).Respond() +} + // Unlink a file from its parent. If this brings the inode's link count to // zero, the inode should be deleted once the kernel sends ForgetInodeOp. 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) type UnlinkOp struct { - Header OpHeader + commonOp // The ID of parent directory inode, and the name of the file being removed // within it. @@ -207,6 +316,15 @@ type UnlinkOp struct { Name string } +func (o *UnlinkOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.RemoveRequest).Respond() +} + //////////////////////////////////////////////////////////////////////// // Directory handles //////////////////////////////////////////////////////////////////////// @@ -218,7 +336,7 @@ type UnlinkOp struct { // user-space process. On OS X it may not be sent for every open(2) (cf. // https://github.com/osxfuse/osxfuse/issues/199). type OpenDirOp struct { - Header OpHeader + commonOp // The ID of the inode to be opened. Inode InodeID @@ -237,9 +355,22 @@ type OpenDirOp struct { Handle HandleID } +func (o *OpenDirOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.OpenResponse{ + Handle: bazilfuse.HandleID(o.Handle), + } + + o.r.(*bazilfuse.OpenRequest).Respond(&resp) +} + // Read entries from a directory previously opened with OpenDir. type ReadDirOp struct { - Header OpHeader + commonOp // The directory inode that we are reading, and the handle previously // returned by OpenDir when opening that inode. @@ -327,6 +458,19 @@ type ReadDirOp struct { Data []byte } +func (o *ReadDirOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.ReadResponse{ + Data: o.Data, + } + + o.r.(*bazilfuse.ReadRequest).Respond(&resp) +} + // Release a previously-minted directory handle. The kernel sends this when // there are no more references to an open directory: all file descriptors are // closed and all memory mappings are unmapped. @@ -334,7 +478,7 @@ type ReadDirOp struct { // The kernel guarantees that the handle ID will not be used in further ops // sent to the file system (unless it is reissued by the file system). type ReleaseDirHandleOp struct { - Header OpHeader + commonOp // 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 @@ -342,6 +486,15 @@ type ReleaseDirHandleOp struct { Handle HandleID } +func (o *ReleaseDirHandleOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.ReleaseRequest).Respond() +} + //////////////////////////////////////////////////////////////////////// // File handles //////////////////////////////////////////////////////////////////////// @@ -353,7 +506,7 @@ type ReleaseDirHandleOp struct { // process. On OS X it may not be sent for every open(2) // (cf.https://github.com/osxfuse/osxfuse/issues/199). type OpenFileOp struct { - Header OpHeader + commonOp // The ID of the inode to be opened. Inode InodeID @@ -371,13 +524,26 @@ type OpenFileOp struct { Handle HandleID } +func (o *OpenFileOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.OpenResponse{ + Handle: bazilfuse.HandleID(o.Handle), + } + + o.r.(*bazilfuse.OpenRequest).Respond(&resp) +} + // Read data from a file previously opened with CreateFile or OpenFile. // // Note that this op is not sent for every call to read(2) by the end user; // some reads may be served by the page cache. See notes on WriteFileOp for // more. type ReadFileOp struct { - Header OpHeader + commonOp // The file inode that we are reading, and the handle previously returned by // CreateFile or OpenFile when opening that inode. @@ -400,6 +566,19 @@ type ReadFileOp struct { Data []byte } +func (o *ReadFileOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.ReadResponse{ + Data: o.Data, + } + + o.r.(*bazilfuse.ReadRequest).Respond(&resp) +} + // 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 @@ -429,7 +608,7 @@ type ReadFileOp struct { // flush request. // type WriteFileOp struct { - Header OpHeader + commonOp // The file inode that we are modifying, and the handle previously returned // by CreateFile or OpenFile when opening that inode. @@ -467,6 +646,19 @@ type WriteFileOp struct { Data []byte } +func (o *WriteFileOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + resp := bazilfuse.WriteResponse{ + Size: len(o.Data), + } + + o.r.(*bazilfuse.WriteRequest).Respond(&resp) +} + // Synchronize the current contents of an open file to storage. // // vfs.txt documents this as being called for by the fsync(2) system call @@ -484,13 +676,22 @@ type WriteFileOp struct { // See also: FlushFileOp, which may perform a similar function when closing a // file (but which is not used in "real" file systems). type SyncFileOp struct { - Header OpHeader + commonOp // The file and handle being sync'd. Inode InodeID Handle HandleID } +func (o *SyncFileOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.FsyncRequest).Respond() +} + // Flush the current state of an open file to storage upon closing a file // descriptor. // @@ -539,13 +740,22 @@ type SyncFileOp struct { // to at least schedule a real flush, and maybe do it immediately in order to // return any errors that occur. type FlushFileOp struct { - Header OpHeader + commonOp // The file and handle being flushed. Inode InodeID Handle HandleID } +func (o *FlushFileOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.FlushRequest).Respond() +} + // 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. @@ -553,10 +763,19 @@ type FlushFileOp struct { // 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). type ReleaseFileHandleOp struct { - Header OpHeader + commonOp // 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 } + +func (o *ReleaseFileHandleOp) Respond(err error) { + if err != nil { + o.commonOp.respondErr(err) + return + } + + o.r.(*bazilfuse.ReleaseRequest).Respond() +}