ext: testing environment setup with VFS2 support.

PiperOrigin-RevId: 259835948
This commit is contained in:
Ayush Ranjan
2019-07-24 16:03:30 -07:00
committed by gVisor bot
parent 40e682759f
commit 2ed832ff86
8 changed files with 432 additions and 15 deletions
+19 -1
View File
@@ -15,7 +15,10 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/binary",
"//pkg/sentry/context",
"//pkg/sentry/fs/ext/disklayout",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/vfs",
"//pkg/syserror",
],
)
@@ -23,11 +26,26 @@ go_library(
go_test(
name = "ext_test",
size = "small",
srcs = ["extent_test.go"],
srcs = [
"ext_test.go",
"extent_test.go",
],
data = [
"//pkg/sentry/fs/ext:assets/bigfile.txt",
"//pkg/sentry/fs/ext:assets/file.txt",
"//pkg/sentry/fs/ext:assets/tiny.ext2",
"//pkg/sentry/fs/ext:assets/tiny.ext3",
"//pkg/sentry/fs/ext:assets/tiny.ext4",
],
embed = [":ext"],
deps = [
"//pkg/abi/linux",
"//pkg/binary",
"//pkg/sentry/context",
"//pkg/sentry/context/contexttest",
"//pkg/sentry/fs/ext/disklayout",
"//pkg/sentry/vfs",
"//runsc/test/testutil",
"@com_github_google_go-cmp//cmp:go_default_library",
"@com_github_google_go-cmp//cmp/cmpopts:go_default_library",
],
+24
View File
@@ -14,10 +14,34 @@
package ext
import (
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// dentry implements vfs.DentryImpl.
type dentry struct {
vfsd vfs.Dentry
// inode is the inode represented by this dentry. Multiple Dentries may
// share a single non-directory Inode (with hard links). inode is
// immutable.
inode *inode
}
// Compiles only if dentry implements vfs.DentryImpl.
var _ vfs.DentryImpl = (*dentry)(nil)
// IncRef implements vfs.DentryImpl.IncRef.
func (d *dentry) IncRef(vfsfs *vfs.Filesystem) {
d.inode.incRef()
}
// TryIncRef implements vfs.DentryImpl.TryIncRef.
func (d *dentry) TryIncRef(vfsfs *vfs.Filesystem) bool {
return d.inode.tryIncRef()
}
// DecRef implements vfs.DentryImpl.DecRef.
func (d *dentry) DecRef(vfsfs *vfs.Filesystem) {
d.inode.decRef(vfsfs.Impl().(*filesystem))
}
+1 -2
View File
@@ -17,8 +17,7 @@ package disklayout
import "gvisor.dev/gvisor/pkg/sentry/fs"
// DirentOld represents the old directory entry struct which does not contain
// the file type. This emulates Linux's ext4_dir_entry struct. This is used in
// ext2, ext3 and sometimes in ext4.
// the file type. This emulates Linux's ext4_dir_entry struct.
//
// Note: This struct can be of variable size on disk. The one described below
// is of maximum size and the FileName beyond NameLength bytes might contain
@@ -15,7 +15,8 @@
package disklayout
// SuperBlock32Bit implements SuperBlock and represents the 32-bit version of
// the ext4_super_block struct in fs/ext4/ext4.h.
// the ext4_super_block struct in fs/ext4/ext4.h. Should be used only if
// RevLevel = DynamicRev and 64-bit feature is disabled.
type SuperBlock32Bit struct {
// We embed the old superblock struct here because the 32-bit version is just
// an extension of the old version.
@@ -17,7 +17,8 @@ package disklayout
// SuperBlock64Bit implements SuperBlock and represents the 64-bit version of
// the ext4_super_block struct in fs/ext4/ext4.h. This sums up to be exactly
// 1024 bytes (smallest possible block size) and hence the superblock always
// fits in no more than one data block.
// fits in no more than one data block. Should only be used when the 64-bit
// feature is set.
type SuperBlock64Bit struct {
// We embed the 32-bit struct here because 64-bit version is just an extension
// of the 32-bit version.
@@ -15,7 +15,7 @@
package disklayout
// SuperBlockOld implements SuperBlock and represents the old version of the
// superblock struct in ext2 and ext3 systems.
// superblock struct. Should be used only if RevLevel = OldRev.
type SuperBlockOld struct {
InodesCountRaw uint32
BlocksCountLo uint32
+61 -9
View File
@@ -16,17 +16,34 @@
package ext
import (
"errors"
"fmt"
"io"
"os"
"sync"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fs/ext/disklayout"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
// filesystemType implements vfs.FilesystemType.
type filesystemType struct{}
// Compiles only if filesystemType implements vfs.FilesystemType.
var _ vfs.FilesystemType = (*filesystemType)(nil)
// filesystem implements vfs.FilesystemImpl.
type filesystem struct {
// mu serializes changes to the Dentry tree and the usage of the read seeker.
// TODO(b/134676337): Remove when all methods have been implemented.
vfs.FilesystemImpl
vfsfs vfs.Filesystem
// mu serializes changes to the dentry tree and the usage of the read seeker.
mu sync.Mutex
// dev is the ReadSeeker for the underlying fs device. It is protected by mu.
@@ -58,28 +75,63 @@ type filesystem struct {
bgs []disklayout.BlockGroup
}
// newFilesystem is the filesystem constructor.
func newFilesystem(dev io.ReadSeeker) (*filesystem, error) {
fs := filesystem{dev: dev, inodeCache: make(map[uint32]*inode)}
var err error
// Compiles only if filesystem implements vfs.FilesystemImpl.
var _ vfs.FilesystemImpl = (*filesystem)(nil)
// getDeviceFd returns the read seeker to the underlying device.
// Currently there are two ways of mounting an ext(2/3/4) fs:
// 1. Specify a mount with our internal special MountType in the OCI spec.
// 2. Expose the device to the container and mount it from application layer.
func getDeviceFd(source string, opts vfs.NewFilesystemOptions) (io.ReadSeeker, error) {
if opts.InternalData == nil {
// User mount call.
// TODO(b/134676337): Open the device specified by `source` and return that.
panic("unimplemented")
}
// NewFilesystem call originated from within the sentry.
fd, ok := opts.InternalData.(uintptr)
if !ok {
return nil, errors.New("internal data for ext fs must be a uintptr containing the file descriptor to device")
}
// We do not close this file because that would close the underlying device
// file descriptor (which is required for reading the fs from disk).
// TODO(b/134676337): Use pkg/fd instead.
deviceFile := os.NewFile(fd, source)
if deviceFile == nil {
return nil, fmt.Errorf("ext4 device file descriptor is not valid: %d", fd)
}
return deviceFile, nil
}
// NewFilesystem implements vfs.FilesystemType.NewFilesystem.
func (fstype filesystemType) NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts vfs.NewFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
dev, err := getDeviceFd(source, opts)
if err != nil {
return nil, nil, err
}
fs := filesystem{dev: dev, inodeCache: make(map[uint32]*inode)}
fs.vfsfs.Init(&fs)
fs.sb, err = readSuperBlock(dev)
if err != nil {
return nil, err
return nil, nil, err
}
if fs.sb.Magic() != linux.EXT_SUPER_MAGIC {
// mount(2) specifies that EINVAL should be returned if the superblock is
// invalid.
return nil, syserror.EINVAL
return nil, nil, syserror.EINVAL
}
fs.bgs, err = readBlockGroups(dev, fs.sb)
if err != nil {
return nil, err
return nil, nil, err
}
return &fs, nil
return &fs.vfsfs, nil, nil
}
// getOrCreateInode gets the inode corresponding to the inode number passed in.
+322
View File
@@ -0,0 +1,322 @@
// Copyright 2019 The gVisor Authors.
//
// 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 ext
import (
"fmt"
"os"
"path"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
"gvisor.dev/gvisor/pkg/sentry/fs/ext/disklayout"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/runsc/test/testutil"
)
const (
assetsDir = "pkg/sentry/fs/ext/assets"
)
var (
ext2ImagePath = path.Join(assetsDir, "tiny.ext2")
ext3ImagePath = path.Join(assetsDir, "tiny.ext3")
ext4ImagePath = path.Join(assetsDir, "tiny.ext4")
)
func beginning(_ uint64) uint64 {
return 0
}
func middle(i uint64) uint64 {
return i / 2
}
func end(i uint64) uint64 {
return i
}
// setUp opens imagePath as an ext Filesystem and returns all necessary
// elements required to run tests. If error is non-nil, it also returns a tear
// down function which must be called after the test is run for clean up.
func setUp(t *testing.T, imagePath string) (context.Context, *vfs.Filesystem, *vfs.Dentry, func(), error) {
localImagePath, err := testutil.FindFile(imagePath)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to open local image at path %s: %v", imagePath, err)
}
f, err := os.Open(localImagePath)
if err != nil {
return nil, nil, nil, nil, err
}
// Mount the ext4 fs and retrieve the inode structure for the file.
mockCtx := contexttest.Context(t)
fs, d, err := filesystemType{}.NewFilesystem(mockCtx, nil, localImagePath, vfs.NewFilesystemOptions{InternalData: f.Fd()})
if err != nil {
f.Close()
return nil, nil, nil, nil, err
}
tearDown := func() {
if err := f.Close(); err != nil {
t.Fatalf("tearDown failed: %v", err)
}
}
return mockCtx, fs, d, tearDown, nil
}
// TestFilesystemInit tests that the filesystem superblock and block group
// descriptors are correctly read in and initialized.
func TestFilesystemInit(t *testing.T) {
// sb only contains the immutable properties of the superblock.
type sb struct {
InodesCount uint32
BlocksCount uint64
MaxMountCount uint16
FirstDataBlock uint32
BlockSize uint64
BlocksPerGroup uint32
ClusterSize uint64
ClustersPerGroup uint32
InodeSize uint16
InodesPerGroup uint32
BgDescSize uint16
Magic uint16
Revision disklayout.SbRevision
CompatFeatures disklayout.CompatFeatures
IncompatFeatures disklayout.IncompatFeatures
RoCompatFeatures disklayout.RoCompatFeatures
}
// bg only contains the immutable properties of the block group descriptor.
type bg struct {
InodeTable uint64
BlockBitmap uint64
InodeBitmap uint64
ExclusionBitmap uint64
Flags disklayout.BGFlags
}
type fsInitTest struct {
name string
image string
wantSb sb
wantBgs []bg
}
tests := []fsInitTest{
{
name: "ext4 filesystem init",
image: ext4ImagePath,
wantSb: sb{
InodesCount: 0x10,
BlocksCount: 0x40,
MaxMountCount: 0xffff,
FirstDataBlock: 0x1,
BlockSize: 0x400,
BlocksPerGroup: 0x2000,
ClusterSize: 0x400,
ClustersPerGroup: 0x2000,
InodeSize: 0x80,
InodesPerGroup: 0x10,
BgDescSize: 0x40,
Magic: linux.EXT_SUPER_MAGIC,
Revision: disklayout.DynamicRev,
CompatFeatures: disklayout.CompatFeatures{
ExtAttr: true,
ResizeInode: true,
DirIndex: true,
},
IncompatFeatures: disklayout.IncompatFeatures{
DirentFileType: true,
Extents: true,
Is64Bit: true,
FlexBg: true,
},
RoCompatFeatures: disklayout.RoCompatFeatures{
Sparse: true,
LargeFile: true,
HugeFile: true,
DirNlink: true,
ExtraIsize: true,
MetadataCsum: true,
},
},
wantBgs: []bg{
{
InodeTable: 0x23,
BlockBitmap: 0x3,
InodeBitmap: 0x13,
Flags: disklayout.BGFlags{
InodeZeroed: true,
},
},
},
},
{
name: "ext3 filesystem init",
image: ext3ImagePath,
wantSb: sb{
InodesCount: 0x10,
BlocksCount: 0x40,
MaxMountCount: 0xffff,
FirstDataBlock: 0x1,
BlockSize: 0x400,
BlocksPerGroup: 0x2000,
ClusterSize: 0x400,
ClustersPerGroup: 0x2000,
InodeSize: 0x80,
InodesPerGroup: 0x10,
BgDescSize: 0x20,
Magic: linux.EXT_SUPER_MAGIC,
Revision: disklayout.DynamicRev,
CompatFeatures: disklayout.CompatFeatures{
ExtAttr: true,
ResizeInode: true,
DirIndex: true,
},
IncompatFeatures: disklayout.IncompatFeatures{
DirentFileType: true,
},
RoCompatFeatures: disklayout.RoCompatFeatures{
Sparse: true,
LargeFile: true,
},
},
wantBgs: []bg{
{
InodeTable: 0x5,
BlockBitmap: 0x3,
InodeBitmap: 0x4,
Flags: disklayout.BGFlags{
InodeZeroed: true,
},
},
},
},
{
name: "ext2 filesystem init",
image: ext2ImagePath,
wantSb: sb{
InodesCount: 0x10,
BlocksCount: 0x40,
MaxMountCount: 0xffff,
FirstDataBlock: 0x1,
BlockSize: 0x400,
BlocksPerGroup: 0x2000,
ClusterSize: 0x400,
ClustersPerGroup: 0x2000,
InodeSize: 0x80,
InodesPerGroup: 0x10,
BgDescSize: 0x20,
Magic: linux.EXT_SUPER_MAGIC,
Revision: disklayout.DynamicRev,
CompatFeatures: disklayout.CompatFeatures{
ExtAttr: true,
ResizeInode: true,
DirIndex: true,
},
IncompatFeatures: disklayout.IncompatFeatures{
DirentFileType: true,
},
RoCompatFeatures: disklayout.RoCompatFeatures{
Sparse: true,
LargeFile: true,
},
},
wantBgs: []bg{
{
InodeTable: 0x5,
BlockBitmap: 0x3,
InodeBitmap: 0x4,
Flags: disklayout.BGFlags{
InodeZeroed: true,
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, vfsfs, _, tearDown, err := setUp(t, test.image)
if err != nil {
t.Fatalf("setUp failed: %v", err)
}
defer tearDown()
fs, ok := vfsfs.Impl().(*filesystem)
if !ok {
t.Fatalf("ext filesystem of incorrect type: %T", vfsfs.Impl())
}
// Offload superblock and block group descriptors contents into
// local structs for comparison.
totalFreeInodes := uint32(0)
totalFreeBlocks := uint64(0)
gotSb := sb{
InodesCount: fs.sb.InodesCount(),
BlocksCount: fs.sb.BlocksCount(),
MaxMountCount: fs.sb.MaxMountCount(),
FirstDataBlock: fs.sb.FirstDataBlock(),
BlockSize: fs.sb.BlockSize(),
BlocksPerGroup: fs.sb.BlocksPerGroup(),
ClusterSize: fs.sb.ClusterSize(),
ClustersPerGroup: fs.sb.ClustersPerGroup(),
InodeSize: fs.sb.InodeSize(),
InodesPerGroup: fs.sb.InodesPerGroup(),
BgDescSize: fs.sb.BgDescSize(),
Magic: fs.sb.Magic(),
Revision: fs.sb.Revision(),
CompatFeatures: fs.sb.CompatibleFeatures(),
IncompatFeatures: fs.sb.IncompatibleFeatures(),
RoCompatFeatures: fs.sb.ReadOnlyCompatibleFeatures(),
}
gotNumBgs := len(fs.bgs)
gotBgs := make([]bg, gotNumBgs)
for i := 0; i < gotNumBgs; i++ {
gotBgs[i].InodeTable = fs.bgs[i].InodeTable()
gotBgs[i].BlockBitmap = fs.bgs[i].BlockBitmap()
gotBgs[i].InodeBitmap = fs.bgs[i].InodeBitmap()
gotBgs[i].ExclusionBitmap = fs.bgs[i].ExclusionBitmap()
gotBgs[i].Flags = fs.bgs[i].Flags()
totalFreeInodes += fs.bgs[i].FreeInodesCount()
totalFreeBlocks += uint64(fs.bgs[i].FreeBlocksCount())
}
if diff := cmp.Diff(gotSb, test.wantSb); diff != "" {
t.Errorf("superblock mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(gotBgs, test.wantBgs); diff != "" {
t.Errorf("block group descriptors mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(totalFreeInodes, fs.sb.FreeInodesCount()); diff != "" {
t.Errorf("total free inodes mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(totalFreeBlocks, fs.sb.FreeBlocksCount()); diff != "" {
t.Errorf("total free blocks mismatch (-want +got):\n%s", diff)
}
})
}
}