diff options
Diffstat (limited to 'pkg/sentry/fs/fsutil/handle_test.go')
-rw-r--r-- | pkg/sentry/fs/fsutil/handle_test.go | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/pkg/sentry/fs/fsutil/handle_test.go b/pkg/sentry/fs/fsutil/handle_test.go new file mode 100644 index 000000000..d94c3eb0d --- /dev/null +++ b/pkg/sentry/fs/fsutil/handle_test.go @@ -0,0 +1,227 @@ +// Copyright 2018 Google Inc. +// +// 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 fsutil_test + +import ( + "io" + "syscall" + "testing" + + "gvisor.googlesource.com/gvisor/pkg/sentry/context" + "gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil" + ramfstest "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs/test" + "gvisor.googlesource.com/gvisor/pkg/sentry/usermem" +) + +type testInodeOperations struct { + fs.InodeOperations + fs.InodeType + FileSize int64 + writes uint + reads uint +} + +func (t *testInodeOperations) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { + return fs.UnstableAttr{Size: t.FileSize}, nil +} + +// Check implements InodeOperations.Check. +func (t *testInodeOperations) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { + return fs.ContextCanAccessFile(ctx, inode, p) +} + +func (t *testInodeOperations) DeprecatedPreadv(ctx context.Context, dst usermem.IOSequence, offset int64) (int64, error) { + t.reads++ + return t.InodeOperations.DeprecatedPreadv(ctx, dst, offset) +} + +func (t *testInodeOperations) DeprecatedPwritev(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { + t.writes++ + return t.InodeOperations.DeprecatedPwritev(ctx, src, offset) +} + +// testHandle returns a handle for a test node. +// +// The size of the node is fixed at 20 bytes. +func testHandle(t *testing.T, flags fs.FileFlags, nt fs.InodeType) (*fs.File, *testInodeOperations) { + ctx := contexttest.Context(t) + m := fs.NewNonCachingMountSource(nil, fs.MountSourceFlags{}) + n := &testInodeOperations{ + InodeOperations: ramfstest.NewFile(ctx, fs.FilePermissions{User: fs.PermMask{Read: true, Write: true}}), + FileSize: 20, + } + d := fs.NewDirent(fs.NewInode(n, m, fs.StableAttr{Type: nt}), "test") + return fsutil.NewHandle(ctx, d, flags, d.Inode.HandleOps()), n +} + +func TestHandleOps(t *testing.T) { + h, n := testHandle(t, fs.FileFlags{Read: true, Write: true}, fs.RegularFile) + defer h.DecRef() + + // Make sure a write request works. + if n, err := h.Writev(contexttest.Context(t), usermem.BytesIOSequence([]byte("a"))); n != 1 || err != nil { + t.Fatalf("Writev: got (%d, %v), wanted (1, nil)", n, err) + } + if n.writes != 1 { + t.Errorf("found %d writes, expected 1", n.writes) + } + + // Make sure a read request works. + dst := make([]byte, 1) + if n, err := h.Preadv(contexttest.Context(t), usermem.BytesIOSequence(dst), 0); n != 1 || (err != nil && err != io.EOF) { + t.Errorf("Preadv: got (%d, %v), wanted (1, nil or EOF)", n, err) + } + if dst[0] != 'a' { + t.Errorf("Preadv: read %q, wanted 'a'", dst[0]) + } + if n.reads != 1 { + t.Errorf("found %d reads, expected 1", n.reads) + } +} + +type seekTest struct { + whence fs.SeekWhence + offset int64 + result int64 + err error +} + +type seekSuite struct { + nodeType fs.InodeType + cases []seekTest +} + +// FIXME: This is currently missing fs.SeekEnd tests due to the +// fact that NullInodeOperations returns an error on stat. +func TestHandleSeek(t *testing.T) { + ts := []seekSuite{ + { + nodeType: fs.RegularFile, + cases: []seekTest{ + {fs.SeekSet, 0, 0, nil}, + {fs.SeekSet, 10, 10, nil}, + {fs.SeekSet, -5, 10, syscall.EINVAL}, + {fs.SeekCurrent, -1, 9, nil}, + {fs.SeekCurrent, 2, 11, nil}, + {fs.SeekCurrent, -12, 11, syscall.EINVAL}, + {fs.SeekEnd, -1, 19, nil}, + {fs.SeekEnd, 0, 20, nil}, + {fs.SeekEnd, 2, 22, nil}, + }, + }, + { + nodeType: fs.Directory, + cases: []seekTest{ + {fs.SeekSet, 0, 0, nil}, + {fs.SeekSet, 10, 0, syscall.EINVAL}, + {fs.SeekSet, -5, 0, syscall.EINVAL}, + {fs.SeekCurrent, 0, 0, nil}, + {fs.SeekCurrent, 11, 0, syscall.EINVAL}, + {fs.SeekCurrent, -6, 0, syscall.EINVAL}, + {fs.SeekEnd, 0, 0, syscall.EINVAL}, + {fs.SeekEnd, -1, 0, syscall.EINVAL}, + {fs.SeekEnd, 2, 0, syscall.EINVAL}, + }, + }, + { + nodeType: fs.Symlink, + cases: []seekTest{ + {fs.SeekSet, 5, 0, syscall.EINVAL}, + {fs.SeekSet, -5, 0, syscall.EINVAL}, + {fs.SeekSet, 0, 0, syscall.EINVAL}, + {fs.SeekCurrent, 5, 0, syscall.EINVAL}, + {fs.SeekCurrent, -5, 0, syscall.EINVAL}, + {fs.SeekCurrent, 0, 0, syscall.EINVAL}, + {fs.SeekEnd, 5, 0, syscall.EINVAL}, + {fs.SeekEnd, -5, 0, syscall.EINVAL}, + {fs.SeekEnd, 0, 0, syscall.EINVAL}, + }, + }, + { + nodeType: fs.Pipe, + cases: []seekTest{ + {fs.SeekSet, 5, 0, syscall.ESPIPE}, + {fs.SeekSet, -5, 0, syscall.ESPIPE}, + {fs.SeekSet, 0, 0, syscall.ESPIPE}, + {fs.SeekCurrent, 5, 0, syscall.ESPIPE}, + {fs.SeekCurrent, -5, 0, syscall.ESPIPE}, + {fs.SeekCurrent, 0, 0, syscall.ESPIPE}, + {fs.SeekEnd, 5, 0, syscall.ESPIPE}, + {fs.SeekEnd, -5, 0, syscall.ESPIPE}, + {fs.SeekEnd, 0, 0, syscall.ESPIPE}, + }, + }, + { + nodeType: fs.Socket, + cases: []seekTest{ + {fs.SeekSet, 5, 0, syscall.ESPIPE}, + {fs.SeekSet, -5, 0, syscall.ESPIPE}, + {fs.SeekSet, 0, 0, syscall.ESPIPE}, + {fs.SeekCurrent, 5, 0, syscall.ESPIPE}, + {fs.SeekCurrent, -5, 0, syscall.ESPIPE}, + {fs.SeekCurrent, 0, 0, syscall.ESPIPE}, + {fs.SeekEnd, 5, 0, syscall.ESPIPE}, + {fs.SeekEnd, -5, 0, syscall.ESPIPE}, + {fs.SeekEnd, 0, 0, syscall.ESPIPE}, + }, + }, + { + nodeType: fs.CharacterDevice, + cases: []seekTest{ + {fs.SeekSet, 5, 0, nil}, + {fs.SeekSet, -5, 0, nil}, + {fs.SeekSet, 0, 0, nil}, + {fs.SeekCurrent, 5, 0, nil}, + {fs.SeekCurrent, -5, 0, nil}, + {fs.SeekCurrent, 0, 0, nil}, + {fs.SeekEnd, 5, 0, nil}, + {fs.SeekEnd, -5, 0, nil}, + {fs.SeekEnd, 0, 0, nil}, + }, + }, + { + nodeType: fs.BlockDevice, + cases: []seekTest{ + {fs.SeekSet, 0, 0, nil}, + {fs.SeekSet, 10, 10, nil}, + {fs.SeekSet, -5, 10, syscall.EINVAL}, + {fs.SeekCurrent, -1, 9, nil}, + {fs.SeekCurrent, 2, 11, nil}, + {fs.SeekCurrent, -12, 11, syscall.EINVAL}, + {fs.SeekEnd, -1, 19, nil}, + {fs.SeekEnd, 0, 20, nil}, + {fs.SeekEnd, 2, 22, nil}, + }, + }, + } + + for _, s := range ts { + h, _ := testHandle(t, fs.FileFlags{Read: true, Write: true}, s.nodeType) + defer h.DecRef() + + for _, c := range s.cases { + // Try the given seek. + offset, err := h.Seek(contexttest.Context(t), c.whence, c.offset) + if err != c.err { + t.Errorf("seek(%s, %d) on %s had unexpected error: expected %v, got %v", c.whence, c.offset, s.nodeType, c.err, err) + } + if err == nil && offset != c.result { + t.Errorf("seek(%s, %d) on %s had bad result: expected %v, got %v", c.whence, c.offset, s.nodeType, c.result, offset) + } + } + } +} |