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..063a44c --- /dev/null +++ b/samples/errorfs/error_fs_test.go @@ -0,0 +1,102 @@ +// 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) + + _, err := os.Open(path.Join(t.Dir, "foo")) + 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")) + 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) + + _, err := os.Open(t.Dir) + 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) + AssertEq(nil, err) + + // Read + _, err = f.Readdirnames(1) + ExpectThat(err, Error(MatchesRegexp("read.*: .*owner died"))) +}