mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
ext: testing environment setup with VFS2 support.
PiperOrigin-RevId: 259835948
This commit is contained in:
+19
-1
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user