diff --git a/conversions.go b/conversions.go index 3c30d71..a180884 100644 --- a/conversions.go +++ b/conversions.go @@ -423,15 +423,37 @@ func (c *Connection) kernelResponse( h := m.OutHeader() h.Unique = fuseID - // Did the user return an error? Otherwise, fill in the rest of the response. + // Special case: handle the ops for which the kernel expects no response. + // interruptOp . + switch op.(type) { + case *fuseops.ForgetInodeOp: + noResponse = true + return + + case *interruptOp: + noResponse = true + return + } + + // If the user returned the error, fill in the error field of the outgoing + // message header. if opErr != nil { + m.OutHeader().Error = -int32(syscall.EIO) if errno, ok := opErr.(syscall.Errno); ok { m.OutHeader().Error = -int32(errno) - } else { - m.OutHeader().Error = -int32(syscall.EIO) } - } else { - noResponse = c.kernelResponseForOp(m, op) + + // Special case: for some types, convertInMessage grew the message in order + // to obtain a destination buffer. Make sure that we shrink back to just + // the header, because on OS X the kernel otherwise returns EINVAL when we + // attempt to write an error response with a length that extends beyond the + // header. + m.Shrink(uintptr(m.Len() - int(buffer.OutMessageInitialSize))) + } + + // Otherwise, fill in the rest of the response. + if opErr == nil { + c.kernelResponseForOp(m, op) } h.Len = uint32(m.Len()) @@ -442,7 +464,7 @@ func (c *Connection) kernelResponse( // op. func (c *Connection) kernelResponseForOp( m *buffer.OutMessage, - op interface{}) (noResponse bool) { + op interface{}) { // Create the appropriate output message switch o := op.(type) { case *fuseops.LookUpInodeOp: @@ -464,9 +486,6 @@ func (c *Connection) kernelResponseForOp( o.AttributesExpiration) convertAttributes(o.Inode, &o.Attributes, &out.Attr) - case *fuseops.ForgetInodeOp: - noResponse = true - case *fuseops.MkDirOp: size := fusekernel.EntryOutSize(c.protocol) out := (*fusekernel.EntryOut)(m.Grow(size)) @@ -541,9 +560,6 @@ func (c *Connection) kernelResponseForOp( case *statFSOp: m.Grow(unsafe.Sizeof(fusekernel.StatfsOut{})) - case *interruptOp: - noResponse = true - case *initOp: out := (*fusekernel.InitOut)(m.Grow(unsafe.Sizeof(fusekernel.InitOut{}))) @@ -554,7 +570,7 @@ func (c *Connection) kernelResponseForOp( out.MaxWrite = o.MaxWrite default: - panic(fmt.Sprintf("Unknown op: %#v", op)) + panic(fmt.Sprintf("Unexpected op: %#v", op)) } return diff --git a/samples/errorfs/error_fs.go b/samples/errorfs/error_fs.go new file mode 100644 index 0000000..359af08 --- /dev/null +++ b/samples/errorfs/error_fs.go @@ -0,0 +1,215 @@ +// 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 errorfs + +import ( + "fmt" + "os" + "reflect" + "sync" + "syscall" + + "golang.org/x/net/context" + + "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/fuse/fuseutil" +) + +const FooContents = "xxxx" + +const fooInodeID = fuseops.RootInodeID + 1 + +var fooAttrs = fuseops.InodeAttributes{ + Nlink: 1, + Size: uint64(len(FooContents)), + Mode: 0444, +} + +// A file system whose sole contents are a file named "foo" containing the +// string defined by FooContents. +// +// The file system can be configured to returned canned errors for particular +// operations using the method SetError. +type FS interface { + fuseutil.FileSystem + + // Cause the file system to return the supplied error for all future + // operations matching the supplied type. + SetError(t reflect.Type, err syscall.Errno) +} + +func New() (fs FS, err error) { + fs = &errorFS{ + errors: make(map[reflect.Type]syscall.Errno), + } + + return +} + +type errorFS struct { + fuseutil.NotImplementedFileSystem + + mu sync.Mutex + + // GUARDED_BY(mu) + errors map[reflect.Type]syscall.Errno +} + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) SetError(t reflect.Type, err syscall.Errno) { + fs.mu.Lock() + defer fs.mu.Unlock() + + fs.errors[t] = err +} + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) transformError(op interface{}, err *error) bool { + fs.mu.Lock() + defer fs.mu.Unlock() + + cannedErr, ok := fs.errors[reflect.TypeOf(op)] + if ok { + *err = cannedErr + return true + } + + return false +} + +//////////////////////////////////////////////////////////////////////// +// File system methods +//////////////////////////////////////////////////////////////////////// + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) GetInodeAttributes( + ctx context.Context, + op *fuseops.GetInodeAttributesOp) (err error) { + if fs.transformError(op, &err) { + return + } + + // Figure out which inode the request is for. + switch { + case op.Inode == fuseops.RootInodeID: + op.Attributes = fuseops.InodeAttributes{ + Mode: os.ModeDir | 0777, + } + + case op.Inode == fooInodeID: + op.Attributes = fooAttrs + + default: + err = fmt.Errorf("Unknown inode: %d", op.Inode) + return + } + + return +} + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) LookUpInode( + ctx context.Context, + op *fuseops.LookUpInodeOp) (err error) { + if fs.transformError(op, &err) { + return + } + + // Is this a known inode? + if !(op.Parent == fuseops.RootInodeID && op.Name == "foo") { + err = syscall.ENOENT + return + } + + op.Entry.Child = fooInodeID + op.Entry.Attributes = fooAttrs + + return +} + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) OpenFile( + ctx context.Context, + op *fuseops.OpenFileOp) (err error) { + if fs.transformError(op, &err) { + return + } + + if op.Inode != fooInodeID { + err = fmt.Errorf("Unsupported inode ID: %d", op.Inode) + return + } + + return +} + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) ReadFile( + ctx context.Context, + op *fuseops.ReadFileOp) (err error) { + if fs.transformError(op, &err) { + return + } + + if op.Inode != fooInodeID || op.Offset != 0 { + err = fmt.Errorf("Unexpected request: %#v", op) + return + } + + op.BytesRead = copy(op.Dst, FooContents) + + return +} + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) OpenDir( + ctx context.Context, + op *fuseops.OpenDirOp) (err error) { + if fs.transformError(op, &err) { + return + } + + if op.Inode != fuseops.RootInodeID { + err = fmt.Errorf("Unsupported inode ID: %d", op.Inode) + return + } + + return +} + +// LOCKS_EXCLUDED(fs.mu) +func (fs *errorFS) ReadDir( + ctx context.Context, + op *fuseops.ReadDirOp) (err error) { + if fs.transformError(op, &err) { + return + } + + if op.Inode != fuseops.RootInodeID || op.Offset != 0 { + err = fmt.Errorf("Unexpected request: %#v", op) + return + } + + op.BytesRead = fuseutil.WriteDirent( + op.Dst, + fuseutil.Dirent{ + Offset: 0, + Inode: fooInodeID, + Name: "foo", + Type: fuseutil.DT_File, + }) + + return +} diff --git a/samples/errorfs/error_fs_test.go b/samples/errorfs/error_fs_test.go new file mode 100644 index 0000000..9061388 --- /dev/null +++ b/samples/errorfs/error_fs_test.go @@ -0,0 +1,106 @@ +// 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 errorfs_test + +import ( + "io/ioutil" + "os" + "path" + "reflect" + "syscall" + "testing" + + "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/fuse/fuseutil" + "github.com/jacobsa/fuse/samples" + "github.com/jacobsa/fuse/samples/errorfs" + . "github.com/jacobsa/oglematchers" + . "github.com/jacobsa/ogletest" +) + +func TestErrorFS(t *testing.T) { RunTests(t) } + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type ErrorFSTest struct { + samples.SampleTest + fs errorfs.FS +} + +func init() { RegisterTestSuite(&ErrorFSTest{}) } + +var _ SetUpInterface = &ErrorFSTest{} +var _ TearDownInterface = &ErrorFSTest{} + +func (t *ErrorFSTest) SetUp(ti *TestInfo) { + var err error + + // Create the file system. + t.fs, err = errorfs.New() + AssertEq(nil, err) + + t.Server = fuseutil.NewFileSystemServer(t.fs) + + // Mount it. + t.SampleTest.SetUp(ti) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *ErrorFSTest) OpenFile() { + t.fs.SetError(reflect.TypeOf(&fuseops.OpenFileOp{}), syscall.EOWNERDEAD) + + f, err := os.Open(path.Join(t.Dir, "foo")) + defer f.Close() + ExpectThat(err, Error(MatchesRegexp("open.*: .*owner died"))) +} + +func (t *ErrorFSTest) ReadFile() { + t.fs.SetError(reflect.TypeOf(&fuseops.ReadFileOp{}), syscall.EOWNERDEAD) + + // Open + f, err := os.Open(path.Join(t.Dir, "foo")) + defer f.Close() + AssertEq(nil, err) + + // Read + _, err = ioutil.ReadAll(f) + ExpectThat(err, Error(MatchesRegexp("read.*: .*owner died"))) +} + +func (t *ErrorFSTest) OpenDir() { + t.fs.SetError(reflect.TypeOf(&fuseops.OpenDirOp{}), syscall.EOWNERDEAD) + + f, err := os.Open(t.Dir) + defer f.Close() + ExpectThat(err, Error(MatchesRegexp("open.*: .*owner died"))) +} + +func (t *ErrorFSTest) ReadDir() { + t.fs.SetError(reflect.TypeOf(&fuseops.ReadDirOp{}), syscall.EOWNERDEAD) + + // Open + f, err := os.Open(t.Dir) + defer f.Close() + AssertEq(nil, err) + + // Read + _, err = f.Readdirnames(1) + ExpectThat(err, Error(MatchesRegexp("read.*: .*owner died"))) +}