mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #9308 from btw616:erofs-initial-support
PiperOrigin-RevId: 571416660
This commit is contained in:
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y curl gnupg2 git \
|
||||
apt-transport-https ca-certificates gnupg-agent \
|
||||
software-properties-common \
|
||||
pkg-config libffi-dev patch diffutils libssl-dev iptables kmod \
|
||||
clang crossbuild-essential-amd64
|
||||
clang crossbuild-essential-amd64 erofs-utils
|
||||
|
||||
# Install Docker client for the website build.
|
||||
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||
|
||||
@@ -193,6 +193,40 @@ var DirentType = abi.ValueSet{
|
||||
DT_WHT: "DT_WHT",
|
||||
}
|
||||
|
||||
// Values for fs on-disk file types.
|
||||
const (
|
||||
FT_UNKNOWN = 0
|
||||
FT_REG_FILE = 1
|
||||
FT_DIR = 2
|
||||
FT_CHRDEV = 3
|
||||
FT_BLKDEV = 4
|
||||
FT_FIFO = 5
|
||||
FT_SOCK = 6
|
||||
FT_SYMLINK = 7
|
||||
FT_MAX = 8
|
||||
)
|
||||
|
||||
// Conversion from fs on-disk file type to dirent type.
|
||||
var direntTypeByFileType = [FT_MAX]uint8{
|
||||
FT_UNKNOWN: DT_UNKNOWN,
|
||||
FT_REG_FILE: DT_REG,
|
||||
FT_DIR: DT_DIR,
|
||||
FT_CHRDEV: DT_CHR,
|
||||
FT_BLKDEV: DT_BLK,
|
||||
FT_FIFO: DT_FIFO,
|
||||
FT_SOCK: DT_SOCK,
|
||||
FT_SYMLINK: DT_LNK,
|
||||
}
|
||||
|
||||
// FileTypeToDirentType converts the on-disk file type (FT_*) to the directory
|
||||
// entry type (DT_*).
|
||||
func FileTypeToDirentType(filetype uint8) uint8 {
|
||||
if filetype >= FT_MAX {
|
||||
return DT_UNKNOWN
|
||||
}
|
||||
return direntTypeByFileType[filetype]
|
||||
}
|
||||
|
||||
// Values for preadv2/pwritev2.
|
||||
const (
|
||||
// NOTE(b/120162627): gVisor does not implement the RWF_HIPRI feature, but
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
load("//tools:defs.bzl", "go_library", "go_test")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "erofs",
|
||||
srcs = ["erofs.go"],
|
||||
marshal = True,
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/cleanup",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/log",
|
||||
"//pkg/marshal",
|
||||
"//pkg/marshal/primitive",
|
||||
"//pkg/safemem",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "erofs_test",
|
||||
size = "small",
|
||||
srcs = ["erofs_test.go"],
|
||||
library = ":erofs",
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
// Copyright 2023 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 erofs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOnDiskStructureSizes(t *testing.T) {
|
||||
if sb := new(SuperBlock); sb.SizeBytes() != 128 {
|
||||
t.Errorf("wrong super block size: want 128, got %d", sb.SizeBytes())
|
||||
}
|
||||
|
||||
if i := new(InodeCompact); i.SizeBytes() != 32 {
|
||||
t.Errorf("wrong compact inode size: want 32, got %d", i.SizeBytes())
|
||||
}
|
||||
|
||||
if i := new(InodeExtended); i.SizeBytes() != 64 {
|
||||
t.Errorf("wrong extended inode size: want 64, got %d", i.SizeBytes())
|
||||
}
|
||||
|
||||
if d := new(Dirent); d.SizeBytes() != 12 {
|
||||
t.Errorf("wrong dirent size: want 12, got %d", d.SizeBytes())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
load("//tools/go_generics:defs.bzl", "go_template_instance")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "fstree",
|
||||
out = "fstree.go",
|
||||
package = "erofs",
|
||||
prefix = "generic",
|
||||
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
|
||||
types = {
|
||||
"Dentry": "dentry",
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "dentry_refs",
|
||||
out = "dentry_refs.go",
|
||||
package = "erofs",
|
||||
prefix = "dentry",
|
||||
template = "//pkg/refs:refs_template",
|
||||
types = {
|
||||
"T": "dentry",
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "inode_refs",
|
||||
out = "inode_refs.go",
|
||||
package = "erofs",
|
||||
prefix = "inode",
|
||||
template = "//pkg/refs:refs_template",
|
||||
types = {
|
||||
"T": "inode",
|
||||
},
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "erofs",
|
||||
srcs = [
|
||||
"dentry_refs.go",
|
||||
"directory.go",
|
||||
"erofs.go",
|
||||
"filesystem.go",
|
||||
"fstree.go",
|
||||
"inode_refs.go",
|
||||
"regular_file.go",
|
||||
"save_restore.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/atomicbitops",
|
||||
"//pkg/cleanup",
|
||||
"//pkg/context",
|
||||
"//pkg/erofs",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/fspath",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/refs",
|
||||
"//pkg/safemem",
|
||||
"//pkg/sentry/fsimpl/lock",
|
||||
"//pkg/sentry/fsutil",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/socket/unix/transport",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/usermem",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright 2023 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 erofs
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
func (i *inode) getDirents() ([]vfs.Dirent, error) {
|
||||
// Fast path.
|
||||
i.dirMu.RLock()
|
||||
dirents := i.dirents
|
||||
i.dirMu.RUnlock()
|
||||
if dirents != nil {
|
||||
return dirents, nil
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
i.dirMu.Lock()
|
||||
defer i.dirMu.Unlock()
|
||||
|
||||
off := int64(1)
|
||||
if err := i.IterDirents(func(name string, typ uint8, nid uint64) error {
|
||||
dirents = append(dirents, vfs.Dirent{
|
||||
Name: name,
|
||||
Type: linux.FileTypeToDirentType(typ),
|
||||
Ino: nid,
|
||||
NextOff: off,
|
||||
})
|
||||
off++
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// "." and ".." should always be present.
|
||||
if len(dirents) < 2 {
|
||||
return nil, linuxerr.EUCLEAN
|
||||
}
|
||||
|
||||
i.dirents = dirents
|
||||
return dirents, nil
|
||||
}
|
||||
|
||||
func (i *inode) lookup(name string) (uint64, error) {
|
||||
// TODO: For simplicity, currently a lookup will cause all dirents to be
|
||||
// read and cached. But it hurts the performance of large directories.
|
||||
// We should do binary search on disk data directly (like Linux does).
|
||||
dirents, err := i.getDirents()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Skip "." and ".."
|
||||
dirents = dirents[2:]
|
||||
|
||||
// The dirents are sorted in alphabetical order. We do binary search
|
||||
// to find the target.
|
||||
idx := sort.Search(len(dirents), func(i int) bool {
|
||||
return dirents[i].Name >= name
|
||||
})
|
||||
if idx >= len(dirents) || dirents[idx].Name != name {
|
||||
return 0, linuxerr.ENOENT
|
||||
}
|
||||
return dirents[idx].Ino, nil
|
||||
}
|
||||
|
||||
func (d *dentry) lookup(ctx context.Context, name string) (*dentry, error) {
|
||||
// Fast path, dentry already exists.
|
||||
d.dirMu.RLock()
|
||||
child, ok := d.childMap[name]
|
||||
d.dirMu.RUnlock()
|
||||
if ok {
|
||||
return child, nil
|
||||
}
|
||||
|
||||
// Slow path, create a new dentry.
|
||||
d.dirMu.Lock()
|
||||
defer d.dirMu.Unlock()
|
||||
if child, ok := d.childMap[name]; ok {
|
||||
return child, nil
|
||||
}
|
||||
|
||||
nid, err := d.inode.lookup(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.childMap == nil {
|
||||
d.childMap = make(map[string]*dentry)
|
||||
}
|
||||
|
||||
child, err = d.inode.fs.newDentry(nid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
child.parent.Store(d)
|
||||
child.name = name
|
||||
d.childMap[name] = child
|
||||
return child, nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type directoryFD struct {
|
||||
fileDescription
|
||||
vfs.DirectoryFileDescriptionDefaultImpl
|
||||
|
||||
// mu protects off.
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
off int64
|
||||
}
|
||||
|
||||
// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
|
||||
func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
|
||||
d := fd.dentry()
|
||||
dirents, err := d.inode.getDirents()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.InotifyWithParent(ctx, linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
|
||||
fd.mu.Lock()
|
||||
defer fd.mu.Unlock()
|
||||
|
||||
for fd.off < int64(len(dirents)) {
|
||||
if err := cb.Handle(dirents[fd.off]); err != nil {
|
||||
return err
|
||||
}
|
||||
fd.off++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
fd.mu.Lock()
|
||||
defer fd.mu.Unlock()
|
||||
|
||||
switch whence {
|
||||
case linux.SEEK_SET:
|
||||
// use offset as specified
|
||||
case linux.SEEK_CUR:
|
||||
offset += fd.off
|
||||
default:
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
fd.off = offset
|
||||
return offset, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,444 @@
|
||||
// Copyright 2023 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 erofs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/erofs"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
// step resolves rp.Component() to an existing file, starting from the given directory.
|
||||
//
|
||||
// step is loosely analogous to fs/namei.c:walk_component().
|
||||
//
|
||||
// Preconditions:
|
||||
// - !rp.Done().
|
||||
func step(ctx context.Context, rp *vfs.ResolvingPath, d *dentry) (*dentry, bool, error) {
|
||||
if !d.inode.IsDir() {
|
||||
return nil, false, linuxerr.ENOTDIR
|
||||
}
|
||||
if err := d.inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
name := rp.Component()
|
||||
if name == "." {
|
||||
rp.Advance()
|
||||
return d, false, nil
|
||||
}
|
||||
if name == ".." {
|
||||
parent := d.parent.Load()
|
||||
if isRoot, err := rp.CheckRoot(ctx, &d.vfsd); err != nil {
|
||||
return nil, false, err
|
||||
} else if isRoot || parent == nil {
|
||||
rp.Advance()
|
||||
return d, false, nil
|
||||
}
|
||||
if err := rp.CheckMount(ctx, &parent.vfsd); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
rp.Advance()
|
||||
return parent, false, nil
|
||||
}
|
||||
if len(name) > erofs.MaxNameLen {
|
||||
return nil, false, linuxerr.ENAMETOOLONG
|
||||
}
|
||||
child, err := d.lookup(ctx, name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err := rp.CheckMount(ctx, &child.vfsd); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if child.inode.IsSymlink() && rp.ShouldFollowSymlink() {
|
||||
target, err := child.inode.Readlink()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
followedSymlink, err := rp.HandleSymlink(target)
|
||||
return d, followedSymlink, err
|
||||
}
|
||||
rp.Advance()
|
||||
return child, false, nil
|
||||
}
|
||||
|
||||
// walkParentDir resolves all but the last path component of rp to an existing
|
||||
// directory, starting from the gvien directory. It does not check that the
|
||||
// returned directory is searchable by the provider of rp.
|
||||
//
|
||||
// walkParentDir is loosely analogous to Linux's fs/namei.c:path_parentat().
|
||||
//
|
||||
// Preconditions:
|
||||
// - !rp.Done().
|
||||
func walkParentDir(ctx context.Context, rp *vfs.ResolvingPath, d *dentry) (*dentry, error) {
|
||||
for !rp.Final() {
|
||||
next, _, err := step(ctx, rp, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d = next
|
||||
}
|
||||
if !d.inode.IsDir() {
|
||||
return nil, linuxerr.ENOTDIR
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// resolve resolves rp to an existing file.
|
||||
//
|
||||
// resolve is loosely analogous to Linux's fs/namei.c:path_lookupat().
|
||||
func resolve(ctx context.Context, rp *vfs.ResolvingPath) (*dentry, error) {
|
||||
d := rp.Start().Impl().(*dentry)
|
||||
for !rp.Done() {
|
||||
next, _, err := step(ctx, rp, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d = next
|
||||
}
|
||||
if rp.MustBeDir() && !d.inode.IsDir() {
|
||||
return nil, linuxerr.ENOTDIR
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// doCreateAt checks that creating a file at rp is permitted.
|
||||
//
|
||||
// doCreateAt is loosely analogous to a conjunction of Linux's
|
||||
// fs/namei.c:filename_create() and done_path_create().
|
||||
//
|
||||
// Preconditions:
|
||||
// - !rp.Done().
|
||||
// - For the final path component in rp, !rp.ShouldFollowSymlink().
|
||||
func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir bool) error {
|
||||
parentDir, err := walkParentDir(ctx, rp, rp.Start().Impl().(*dentry))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Order of checks is important. First check if parent directory can be
|
||||
// executed, then check for existence, and lastly check if mount is writable.
|
||||
if err := parentDir.inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
||||
return err
|
||||
}
|
||||
name := rp.Component()
|
||||
if name == "." || name == ".." {
|
||||
return linuxerr.EEXIST
|
||||
}
|
||||
if len(name) > erofs.MaxNameLen {
|
||||
return linuxerr.ENAMETOOLONG
|
||||
}
|
||||
if _, err := parentDir.lookup(ctx, name); err == nil {
|
||||
return linuxerr.EEXIST
|
||||
} else if !linuxerr.Equals(linuxerr.ENOENT, err) {
|
||||
return err
|
||||
}
|
||||
if !dir && rp.MustBeDir() {
|
||||
return linuxerr.ENOENT
|
||||
}
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
|
||||
// Sync implements vfs.FilesystemImpl.Sync.
|
||||
func (fs *filesystem) Sync(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AccessAt implements vfs.FilesystemImpl.AccessAt.
|
||||
func (fs *filesystem) AccessAt(ctx context.Context, rp *vfs.ResolvingPath, creds *auth.Credentials, ats vfs.AccessTypes) error {
|
||||
d, err := resolve(ctx, rp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ats.MayWrite() {
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
return d.inode.checkPermissions(creds, ats)
|
||||
}
|
||||
|
||||
// GetDentryAt implements vfs.FilesystemImpl.GetDentryAt.
|
||||
func (fs *filesystem) GetDentryAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetDentryOptions) (*vfs.Dentry, error) {
|
||||
d, err := resolve(ctx, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.CheckSearchable {
|
||||
if !d.inode.IsDir() {
|
||||
return nil, linuxerr.ENOTDIR
|
||||
}
|
||||
if err := d.inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d.IncRef()
|
||||
return &d.vfsd, nil
|
||||
}
|
||||
|
||||
// GetParentDentryAt implements vfs.FilesystemImpl.GetParentDentryAt.
|
||||
func (fs *filesystem) GetParentDentryAt(ctx context.Context, rp *vfs.ResolvingPath) (*vfs.Dentry, error) {
|
||||
dir, err := walkParentDir(ctx, rp, rp.Start().Impl().(*dentry))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir.IncRef()
|
||||
return &dir.vfsd, nil
|
||||
}
|
||||
|
||||
// LinkAt implements vfs.FilesystemImpl.LinkAt.
|
||||
func (fs *filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry) error {
|
||||
return fs.doCreateAt(ctx, rp, false /* dir */)
|
||||
}
|
||||
|
||||
// MkdirAt implements vfs.FilesystemImpl.MkdirAt.
|
||||
func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MkdirOptions) error {
|
||||
return fs.doCreateAt(ctx, rp, true /* dir */)
|
||||
}
|
||||
|
||||
// MknodAt implements vfs.FilesystemImpl.MknodAt.
|
||||
func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MknodOptions) error {
|
||||
return fs.doCreateAt(ctx, rp, false /* dir */)
|
||||
}
|
||||
|
||||
// OpenAt implements vfs.FilesystemImpl.OpenAt.
|
||||
func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
if opts.Flags&linux.O_TMPFILE != 0 {
|
||||
return nil, linuxerr.EOPNOTSUPP
|
||||
}
|
||||
|
||||
if opts.Flags&linux.O_CREAT == 0 {
|
||||
d, err := resolve(ctx, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.open(ctx, rp, &opts)
|
||||
}
|
||||
|
||||
mustCreate := opts.Flags&linux.O_EXCL != 0
|
||||
start := rp.Start().Impl().(*dentry)
|
||||
if rp.Done() {
|
||||
// Reject attempts to open mount root directory with O_CREAT.
|
||||
if rp.MustBeDir() {
|
||||
return nil, linuxerr.EISDIR
|
||||
}
|
||||
if mustCreate {
|
||||
return nil, linuxerr.EEXIST
|
||||
}
|
||||
return start.open(ctx, rp, &opts)
|
||||
}
|
||||
afterTrailingSymlink:
|
||||
parentDir, err := walkParentDir(ctx, rp, start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check for search permission in the parent directory.
|
||||
if err := parentDir.inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Reject attempts to open directories with O_CREAT.
|
||||
if rp.MustBeDir() {
|
||||
return nil, linuxerr.EISDIR
|
||||
}
|
||||
child, followedSymlink, err := step(ctx, rp, parentDir)
|
||||
if followedSymlink {
|
||||
if mustCreate {
|
||||
// EEXIST must be returned if an existing symlink is opened with O_EXCL.
|
||||
return nil, linuxerr.EEXIST
|
||||
}
|
||||
if err != nil {
|
||||
// If followedSymlink && err != nil, then this symlink resolution error
|
||||
// must be handled by the VFS layer.
|
||||
return nil, err
|
||||
}
|
||||
start = parentDir
|
||||
goto afterTrailingSymlink
|
||||
}
|
||||
if linuxerr.Equals(linuxerr.ENOENT, err) {
|
||||
return nil, linuxerr.EROFS
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mustCreate {
|
||||
return nil, linuxerr.EEXIST
|
||||
}
|
||||
if rp.MustBeDir() && !child.inode.IsDir() {
|
||||
return nil, linuxerr.ENOTDIR
|
||||
}
|
||||
return child.open(ctx, rp, &opts)
|
||||
}
|
||||
|
||||
// ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt.
|
||||
func (fs *filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (string, error) {
|
||||
d, err := resolve(ctx, rp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.inode.Readlink()
|
||||
}
|
||||
|
||||
// RenameAt implements vfs.FilesystemImpl.RenameAt.
|
||||
func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldParentVD vfs.VirtualDentry, oldName string, opts vfs.RenameOptions) error {
|
||||
// Resolve newParent first to verify that it's on this Mount.
|
||||
newParentDir, err := walkParentDir(ctx, rp, rp.Start().Impl().(*dentry))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newName := rp.Component()
|
||||
if len(newName) > erofs.MaxNameLen {
|
||||
return linuxerr.ENAMETOOLONG
|
||||
}
|
||||
mnt := rp.Mount()
|
||||
if mnt != oldParentVD.Mount() {
|
||||
return linuxerr.EXDEV
|
||||
}
|
||||
if err := newParentDir.inode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil {
|
||||
return err
|
||||
}
|
||||
oldParentDir := oldParentVD.Dentry().Impl().(*dentry)
|
||||
if err := oldParentDir.inode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil {
|
||||
return err
|
||||
}
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
|
||||
// RmdirAt implements vfs.FilesystemImpl.RmdirAt.
|
||||
func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
||||
parentDir, err := walkParentDir(ctx, rp, rp.Start().Impl().(*dentry))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parentDir.inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
||||
return err
|
||||
}
|
||||
name := rp.Component()
|
||||
if name == "." {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
if name == ".." {
|
||||
return linuxerr.ENOTEMPTY
|
||||
}
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
|
||||
// SetStatAt implements vfs.FilesystemImpl.SetStatAt.
|
||||
func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error {
|
||||
if _, err := resolve(ctx, rp); err != nil {
|
||||
return err
|
||||
}
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
|
||||
// StatAt implements vfs.FilesystemImpl.StatAt.
|
||||
func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
d, err := resolve(ctx, rp)
|
||||
if err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
var stat linux.Statx
|
||||
d.inode.statTo(&stat)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
// StatFSAt implements vfs.FilesystemImpl.StatFSAt.
|
||||
func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) {
|
||||
if _, err := resolve(ctx, rp); err != nil {
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
return fs.statFS(), nil
|
||||
}
|
||||
|
||||
// SymlinkAt implements vfs.FilesystemImpl.SymlinkAt.
|
||||
func (fs *filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, target string) error {
|
||||
return fs.doCreateAt(ctx, rp, false /* dir */)
|
||||
}
|
||||
|
||||
// UnlinkAt implements vfs.FilesystemImpl.UnlinkAt.
|
||||
func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
||||
parentDir, err := walkParentDir(ctx, rp, rp.Start().Impl().(*dentry))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parentDir.inode.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
||||
return err
|
||||
}
|
||||
name := rp.Component()
|
||||
if name == "." || name == ".." {
|
||||
return linuxerr.EISDIR
|
||||
}
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
|
||||
// BoundEndpointAt implements vfs.FilesystemImpl.BoundEndpointAt.
|
||||
func (fs *filesystem) BoundEndpointAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.BoundEndpointOptions) (transport.BoundEndpoint, error) {
|
||||
d, err := resolve(ctx, rp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.inode.checkPermissions(rp.Credentials(), vfs.MayWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, linuxerr.ECONNREFUSED
|
||||
}
|
||||
|
||||
// ListXattrAt implements vfs.FilesystemImpl.ListXattrAt.
|
||||
func (fs *filesystem) ListXattrAt(ctx context.Context, rp *vfs.ResolvingPath, size uint64) ([]string, error) {
|
||||
if _, err := resolve(ctx, rp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, linuxerr.ENOTSUP
|
||||
}
|
||||
|
||||
// GetXattrAt implements vfs.FilesystemImpl.GetXattrAt.
|
||||
func (fs *filesystem) GetXattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetXattrOptions) (string, error) {
|
||||
if _, err := resolve(ctx, rp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", linuxerr.ENOTSUP
|
||||
}
|
||||
|
||||
// SetXattrAt implements vfs.FilesystemImpl.SetXattrAt.
|
||||
func (fs *filesystem) SetXattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetXattrOptions) error {
|
||||
if _, err := resolve(ctx, rp); err != nil {
|
||||
return err
|
||||
}
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
|
||||
// RemoveXattrAt implements vfs.FilesystemImpl.RemoveXattrAt.
|
||||
func (fs *filesystem) RemoveXattrAt(ctx context.Context, rp *vfs.ResolvingPath, name string) error {
|
||||
if _, err := resolve(ctx, rp); err != nil {
|
||||
return err
|
||||
}
|
||||
return linuxerr.EROFS
|
||||
}
|
||||
|
||||
// PrependPath implements vfs.FilesystemImpl.PrependPath.
|
||||
func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
|
||||
return genericPrependPath(vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
|
||||
}
|
||||
|
||||
// MountOptions implements vfs.FilesystemImpl.MountOptions.
|
||||
func (fs *filesystem) MountOptions() string {
|
||||
return fs.mopts
|
||||
}
|
||||
|
||||
// IsDescendant implements vfs.FilesystemImpl.IsDescendant.
|
||||
func (fs *filesystem) IsDescendant(vfsroot, vd vfs.VirtualDentry) bool {
|
||||
return genericIsDescendant(vfsroot.Dentry(), vd.Dentry().Impl().(*dentry))
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright 2023 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 erofs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/erofs"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type regularFileFD struct {
|
||||
fileDescription
|
||||
|
||||
// offMu protects off.
|
||||
offMu sync.Mutex `state:"nosave"`
|
||||
|
||||
// off is the file offset.
|
||||
// +checklocks:offMu
|
||||
off int64
|
||||
}
|
||||
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
if offset < 0 {
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
// Check that flags are supported.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/2601): Support select preadv2 flags.
|
||||
if opts.Flags&^linux.RWF_HIPRI != 0 {
|
||||
return 0, linuxerr.EOPNOTSUPP
|
||||
}
|
||||
|
||||
if dst.NumBytes() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
data, err := fd.inode().Data()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r := ®ularFileReader{
|
||||
data: data,
|
||||
off: uint64(offset),
|
||||
}
|
||||
return dst.CopyOutFrom(ctx, r)
|
||||
}
|
||||
|
||||
type regularFileReader struct {
|
||||
data safemem.BlockSeq
|
||||
off uint64
|
||||
}
|
||||
|
||||
// ReadToBlocks implements safemem.Reader.ReadToBlocks.
|
||||
func (r *regularFileReader) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
|
||||
if r.off >= r.data.NumBytes() {
|
||||
return 0, io.EOF
|
||||
}
|
||||
cp, err := safemem.CopySeq(dsts, r.data.DropFirst(int(r.off)))
|
||||
r.off += cp
|
||||
return cp, err
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
n, err := fd.PRead(ctx, dst, fd.off, opts)
|
||||
fd.off += n
|
||||
fd.offMu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, linuxerr.EROFS
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, linuxerr.EROFS
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
defer fd.offMu.Unlock()
|
||||
switch whence {
|
||||
case linux.SEEK_SET:
|
||||
// use offset as specified
|
||||
case linux.SEEK_CUR:
|
||||
offset += fd.off
|
||||
case linux.SEEK_END:
|
||||
offset += int64(fd.inode().Size())
|
||||
default:
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
fd.off = offset
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
|
||||
func (fd *regularFileFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
||||
return vfs.GenericConfigureMMap(&fd.vfsfd, fd.inode(), opts)
|
||||
}
|
||||
|
||||
// AddMapping implements memmap.Mappable.AddMapping.
|
||||
func (i *inode) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveMapping implements memmap.Mappable.RemoveMapping.
|
||||
func (i *inode) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) {
|
||||
}
|
||||
|
||||
// CopyMapping implements memmap.Mappable.CopyMapping.
|
||||
func (i *inode) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Translate implements memmap.Mappable.Translate.
|
||||
func (i *inode) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) {
|
||||
pgend, _ := hostarch.PageRoundUp(i.Size())
|
||||
if required.End > pgend {
|
||||
if required.Start >= pgend {
|
||||
return nil, &memmap.BusError{io.EOF}
|
||||
}
|
||||
required.End = pgend
|
||||
}
|
||||
if optional.End > pgend {
|
||||
optional.End = pgend
|
||||
}
|
||||
if at.Write {
|
||||
return nil, &memmap.BusError{linuxerr.EROFS}
|
||||
}
|
||||
offset, err := i.DataOffset()
|
||||
if err != nil {
|
||||
return nil, &memmap.BusError{err}
|
||||
}
|
||||
mr := optional
|
||||
return []memmap.Translation{
|
||||
{
|
||||
Source: mr,
|
||||
File: &i.fs.mf,
|
||||
Offset: mr.Start + offset,
|
||||
Perms: at,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
|
||||
func (i *inode) InvalidateUnsavable(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type imageMemmapFile struct {
|
||||
image *erofs.Image
|
||||
}
|
||||
|
||||
// IncRef implements memmap.File.IncRef.
|
||||
func (mf *imageMemmapFile) IncRef(fr memmap.FileRange, memCgID uint32) {}
|
||||
|
||||
// DecRef implements memmap.File.DecRef.
|
||||
func (mf *imageMemmapFile) DecRef(fr memmap.FileRange) {}
|
||||
|
||||
// MapInternal implements memmap.File.MapInternal.
|
||||
func (mf *imageMemmapFile) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) {
|
||||
if at.Write {
|
||||
return safemem.BlockSeq{}, &memmap.BusError{linuxerr.EROFS}
|
||||
}
|
||||
bytes, err := mf.image.BytesAt(fr.Start, fr.Length())
|
||||
if err != nil {
|
||||
return safemem.BlockSeq{}, &memmap.BusError{err}
|
||||
}
|
||||
return safemem.BlockSeqOf(safemem.BlockFromSafeSlice(bytes)), nil
|
||||
}
|
||||
|
||||
// FD implements memmap.File.FD.
|
||||
func (mf *imageMemmapFile) FD() int {
|
||||
return mf.image.FD()
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2023 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 erofs
|
||||
|
||||
// TODO: support checkpoint/restore.
|
||||
|
||||
// saveParent is called by stateify.
|
||||
func (d *dentry) saveParent() *dentry {
|
||||
return d.parent.Load()
|
||||
}
|
||||
|
||||
// loadParent is called by stateify.
|
||||
func (d *dentry) loadParent(parent *dentry) {
|
||||
d.parent.Store(parent)
|
||||
}
|
||||
@@ -60,6 +60,7 @@ go_library(
|
||||
"//pkg/sentry/fsimpl/cgroupfs",
|
||||
"//pkg/sentry/fsimpl/devpts",
|
||||
"//pkg/sentry/fsimpl/devtmpfs",
|
||||
"//pkg/sentry/fsimpl/erofs",
|
||||
"//pkg/sentry/fsimpl/fuse",
|
||||
"//pkg/sentry/fsimpl/gofer",
|
||||
"//pkg/sentry/fsimpl/host",
|
||||
|
||||
@@ -18,14 +18,19 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
gtime "time"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/cleanup"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/control/server"
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/control"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/netstack"
|
||||
@@ -95,6 +100,9 @@ const (
|
||||
|
||||
// ContMgrProcfsDump dumps sandbox procfs state.
|
||||
ContMgrProcfsDump = "containerManager.ProcfsDump"
|
||||
|
||||
// ContMgrMount mounts a filesystem in a container.
|
||||
ContMgrMount = "containerManager.Mount"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -673,3 +681,95 @@ func (cm *containerManager) ProcfsDump(_ *struct{}, out *[]procfs.ProcessProcfsD
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountArgs contains arguments to the Mount method.
|
||||
type MountArgs struct {
|
||||
// ContainerID is the container in which we will mount the filesystem.
|
||||
ContainerID string
|
||||
|
||||
// Source is the mount source.
|
||||
Source string
|
||||
|
||||
// Destination is the mount target.
|
||||
Destination string
|
||||
|
||||
// FsType is the filesystem type.
|
||||
FsType string
|
||||
|
||||
// FilePayload contains the source image FD, if required by the filesystem.
|
||||
urpc.FilePayload
|
||||
}
|
||||
|
||||
const initTID kernel.ThreadID = 1
|
||||
|
||||
// Mount mounts a filesystem in a container.
|
||||
func (cm *containerManager) Mount(args *MountArgs, _ *struct{}) error {
|
||||
log.Debugf("containerManager.Mount, cid: %s, args: %+v", args.ContainerID, args)
|
||||
|
||||
var cu cleanup.Cleanup
|
||||
defer cu.Clean()
|
||||
|
||||
eid := execID{cid: args.ContainerID}
|
||||
ep, ok := cm.l.processes[eid]
|
||||
if !ok {
|
||||
return fmt.Errorf("container %v is deleted", args.ContainerID)
|
||||
}
|
||||
if ep.tg == nil {
|
||||
return fmt.Errorf("container %v isn't started", args.ContainerID)
|
||||
}
|
||||
|
||||
t := ep.tg.PIDNamespace().TaskWithID(initTID)
|
||||
if t == nil {
|
||||
return fmt.Errorf("failed to find init process")
|
||||
}
|
||||
|
||||
source := args.Source
|
||||
dest := path.Clean(args.Destination)
|
||||
fstype := args.FsType
|
||||
|
||||
if dest[0] != '/' {
|
||||
return fmt.Errorf("absolute path must be provided for destination")
|
||||
}
|
||||
|
||||
var opts vfs.MountOptions
|
||||
switch fstype {
|
||||
case erofs.Name:
|
||||
if len(args.FilePayload.Files) != 1 {
|
||||
return fmt.Errorf("exactly one image file must be provided")
|
||||
}
|
||||
|
||||
imageFD, err := unix.Dup(int(args.FilePayload.Files[0].Fd()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to dup image FD: %v", err)
|
||||
}
|
||||
cu.Add(func() { unix.Close(imageFD) })
|
||||
|
||||
opts = vfs.MountOptions{
|
||||
ReadOnly: true,
|
||||
GetFilesystemOptions: vfs.GetFilesystemOptions{
|
||||
Data: fmt.Sprintf("ifd=%d", imageFD),
|
||||
},
|
||||
InternalMount: true,
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported filesystem type: %v", fstype)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
root := t.FSContext().RootDirectory()
|
||||
defer root.DecRef(ctx)
|
||||
|
||||
pop := vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(dest),
|
||||
}
|
||||
|
||||
if _, err := t.Kernel().VFS().MountAt(ctx, t.Credentials(), source, &pop, fstype, &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Mounted %q to %q type: %s, internal-options: %q, in container %q", source, dest, fstype, opts.GetFilesystemOptions.Data, args.ContainerID)
|
||||
cu.Release()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/cgroupfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/devpts"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/devtmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/fuse"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/gofer"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/mqfs"
|
||||
@@ -101,6 +102,9 @@ func registerFilesystems(k *kernel.Kernel, info *containerInfo) error {
|
||||
AllowUserMount: true,
|
||||
AllowUserList: true,
|
||||
})
|
||||
vfsObj.MustRegisterFilesystemType(erofs.Name, &erofs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserList: true,
|
||||
})
|
||||
vfsObj.MustRegisterFilesystemType(fuse.Name, &fuse.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserMount: true,
|
||||
AllowUserList: true,
|
||||
|
||||
@@ -49,6 +49,7 @@ type Debug struct {
|
||||
delay time.Duration
|
||||
duration time.Duration
|
||||
ps bool
|
||||
mount string
|
||||
}
|
||||
|
||||
// Name implements subcommands.Command.
|
||||
@@ -82,6 +83,7 @@ func (d *Debug) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&d.logLevel, "log-level", "", "The log level to set: warning (0), info (1), or debug (2).")
|
||||
f.StringVar(&d.logPackets, "log-packets", "", "A boolean value to enable or disable packet logging: true or false.")
|
||||
f.BoolVar(&d.ps, "ps", false, "lists processes")
|
||||
f.StringVar(&d.mount, "mount", "", "Mount a filesystem (-mount fstype:source:destination).")
|
||||
}
|
||||
|
||||
// Execute implements subcommands.Command.Execute.
|
||||
@@ -224,6 +226,18 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomm
|
||||
}
|
||||
util.Infof("%s", o)
|
||||
}
|
||||
if d.mount != "" {
|
||||
opts := strings.Split(d.mount, ":")
|
||||
if len(opts) != 3 {
|
||||
util.Fatalf("Mount failed: invalid option: %v", d.mount)
|
||||
}
|
||||
fstype := opts[0]
|
||||
src := opts[1]
|
||||
dest := opts[2]
|
||||
if err := c.Sandbox.Mount(c.ID, fstype, src, dest); err != nil {
|
||||
util.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Open profiling files.
|
||||
var (
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -3135,3 +3136,135 @@ func TestOverlayByMountAnnotation(t *testing.T) {
|
||||
t.Fatalf("overlay filestore at %q was not deleted after container.Destroy()", filestoreFile)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMountEROFS checks that the checksums from the target directory in container
|
||||
// are identical with the ones from the source directory on host.
|
||||
func TestMountEROFS(t *testing.T) {
|
||||
// Skip this test if mkfs.erofs is not available.
|
||||
mkfs, err := exec.LookPath("mkfs.erofs")
|
||||
if err != nil {
|
||||
t.Skipf("mkfs.erofs is not available: %v", err)
|
||||
}
|
||||
|
||||
// Create a temporary directory to save the test files.
|
||||
assetsDir, err := ioutil.TempDir(testutil.TmpDir(), "erofs-assets")
|
||||
if err != nil {
|
||||
t.Fatalf("ioutil.TempDir() failed: %v", err)
|
||||
}
|
||||
|
||||
// Create a temporary directory with some random files in it, which will
|
||||
// be used as the source directory to create the EROFS images.
|
||||
sourceDir := filepath.Join(assetsDir, "source")
|
||||
if err := os.Mkdir(sourceDir, 0755); err != nil {
|
||||
t.Fatalf("os.Mkdir() failed: %v", err)
|
||||
}
|
||||
testApp, err := testutil.FindFile("test/cmd/test_app/test_app")
|
||||
if err != nil {
|
||||
t.Fatalf("error finding test_app: %v", err)
|
||||
}
|
||||
// Source directory is a small directory. Let's create a big directory in it.
|
||||
// So we can cover both cases.
|
||||
cmd := fmt.Sprintf("%s fsTreeCreate --target-dir=%s --create-symlink --depth=1 --file-per-level=500 --file-size=5000", testApp, filepath.Join(sourceDir, "big-directory"))
|
||||
if out, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput(); err != nil {
|
||||
t.Fatalf("exec: sh -c %q, err: %v, out: %s", cmd, err, out)
|
||||
}
|
||||
|
||||
// Create a test script which can be used to get the checksums
|
||||
// from a specified directory.
|
||||
scriptFile := filepath.Join(assetsDir, "test-script")
|
||||
if err := os.WriteFile(scriptFile, []byte(`#!/bin/bash
|
||||
set -u -e -o pipefail
|
||||
dir=$1
|
||||
find $dir -printf "%P\n" | sort | md5sum
|
||||
find $dir -type l | sort | xargs -L 1 readlink | md5sum
|
||||
find $dir -type l -o -type f | sort | xargs cat | md5sum`), 0755); err != nil {
|
||||
t.Fatalf("os.WriteFile() failed: %v", err)
|
||||
}
|
||||
|
||||
// Get the checksums from the source directory on host.
|
||||
var checksums string
|
||||
if out, err := exec.Command(scriptFile, sourceDir).CombinedOutput(); err != nil {
|
||||
t.Fatalf("exec: %s %s, err: %v, out: %s", scriptFile, sourceDir, err, out)
|
||||
} else {
|
||||
checksums = string(out)
|
||||
}
|
||||
|
||||
images := []struct {
|
||||
name string
|
||||
options string
|
||||
}{
|
||||
{
|
||||
// Generate extended inodes. Inline regular files if possible.
|
||||
name: "image1",
|
||||
options: "-E force-inode-extended",
|
||||
},
|
||||
{
|
||||
// Generate extended inodes. Do not inline regular files.
|
||||
name: "image2",
|
||||
options: "-E force-inode-extended -E noinline_data",
|
||||
},
|
||||
{
|
||||
// Generate compact inodes. Inline regular files if possible.
|
||||
name: "image3",
|
||||
options: "-E force-inode-compact",
|
||||
},
|
||||
{
|
||||
// Generate compact inodes. Do not inline regular files.
|
||||
name: "image4",
|
||||
options: "-E force-inode-compact -E noinline_data",
|
||||
},
|
||||
}
|
||||
|
||||
// Create the EROFS images.
|
||||
for _, i := range images {
|
||||
cmd := fmt.Sprintf("%s %s %s %s", mkfs, i.options, filepath.Join(assetsDir, i.name), sourceDir)
|
||||
if out, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput(); err != nil {
|
||||
t.Fatalf("exec: sh -c %q, err: %v, out: %s", cmd, err, out)
|
||||
}
|
||||
}
|
||||
|
||||
spec, _ := sleepSpecConf(t)
|
||||
conf := testutil.TestConfig(t)
|
||||
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
|
||||
if err != nil {
|
||||
t.Fatalf("error setting up container: %v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Create and start the container.
|
||||
args := Args{
|
||||
ID: testutil.RandomContainerID(),
|
||||
Spec: spec,
|
||||
BundleDir: bundleDir,
|
||||
}
|
||||
c, err := New(conf, args)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating container: %v", err)
|
||||
}
|
||||
defer c.Destroy()
|
||||
if err := c.Start(conf); err != nil {
|
||||
t.Fatalf("error starting container: %v", err)
|
||||
}
|
||||
|
||||
targetDir := "/mnt"
|
||||
for _, i := range images {
|
||||
// Mount the EROFS image in container.
|
||||
imageFile := filepath.Join(assetsDir, i.name)
|
||||
if err := c.Sandbox.Mount(c.ID, "erofs", imageFile, targetDir); err != nil {
|
||||
t.Fatalf("error mounting EROFS image %q to %q, err: %v", imageFile, targetDir, err)
|
||||
}
|
||||
|
||||
// Get the checksums from the target directory in container, and check if they are
|
||||
// identical with the ones got from the source directory on host.
|
||||
if out, err := executeCombinedOutput(conf, c, nil, scriptFile, targetDir); err != nil {
|
||||
t.Fatalf("exec: %s %s, err: %v, out: %s", scriptFile, targetDir, err, out)
|
||||
} else if checksums != string(out) {
|
||||
t.Errorf("checksums do not match, got: %s from %s, expected: %s", out, imageFile, checksums)
|
||||
}
|
||||
|
||||
// Unmount the EROFS image in container.
|
||||
if out, err := executeCombinedOutput(conf, c, nil, "/bin/umount", targetDir); err != nil {
|
||||
t.Fatalf("exec: umount %q, err: %v, out: %s", targetDir, err, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ go_library(
|
||||
"//pkg/metric:metric_go_proto",
|
||||
"//pkg/prometheus",
|
||||
"//pkg/sentry/control",
|
||||
"//pkg/sentry/fsimpl/erofs",
|
||||
"//pkg/sentry/platform",
|
||||
"//pkg/sentry/seccheck",
|
||||
"//pkg/state/statefile",
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
metricpb "gvisor.dev/gvisor/pkg/metric/metric_go_proto"
|
||||
"gvisor.dev/gvisor/pkg/prometheus"
|
||||
"gvisor.dev/gvisor/pkg/sentry/control"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/platform"
|
||||
"gvisor.dev/gvisor/pkg/sentry/seccheck"
|
||||
"gvisor.dev/gvisor/pkg/state/statefile"
|
||||
@@ -1669,3 +1670,28 @@ func SetUserMappings(spec *specs.Spec, pid int) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mount mounts a filesystem in a container.
|
||||
func (s *Sandbox) Mount(cid, fstype, src, dest string) error {
|
||||
var files []*os.File
|
||||
switch fstype {
|
||||
case erofs.Name:
|
||||
if imageFile, err := os.Open(src); err != nil {
|
||||
return fmt.Errorf("opening %s: %v", src, err)
|
||||
} else {
|
||||
files = append(files, imageFile)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported filesystem type: %v", fstype)
|
||||
}
|
||||
|
||||
args := boot.MountArgs{
|
||||
ContainerID: cid,
|
||||
Source: src,
|
||||
Destination: dest,
|
||||
FsType: fstype,
|
||||
FilePayload: urpc.FilePayload{Files: files},
|
||||
}
|
||||
return s.call(boot.ContMgrMount, &args, nil)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ type fsTreeCreator struct {
|
||||
depth uint
|
||||
numFilesPerLevel uint
|
||||
fileSize uint
|
||||
targetDir string
|
||||
createSymlink bool
|
||||
}
|
||||
|
||||
// Name implements subcommands.Command.Name.
|
||||
@@ -73,7 +75,7 @@ func (*fsTreeCreator) Name() string {
|
||||
|
||||
// Synopsis implements subcommands.Command.Synopsys.
|
||||
func (*fsTreeCreator) Synopsis() string {
|
||||
return "creates a filesystem tree of a certain depth, with a certain number of files on each level and each file with a certain size. Some randomization is added on top of this"
|
||||
return "creates a filesystem tree of a certain depth, with a certain number of files on each level and each file with a certain size and type, under a certain directory. Some randomization is added on top of this"
|
||||
}
|
||||
|
||||
// Usage implements subcommands.Command.Usage.
|
||||
@@ -86,6 +88,8 @@ func (c *fsTreeCreator) SetFlags(f *flag.FlagSet) {
|
||||
f.UintVar(&c.depth, "depth", 10, "number of levels to create")
|
||||
f.UintVar(&c.numFilesPerLevel, "file-per-level", 10, "number of files to create per level")
|
||||
f.UintVar(&c.fileSize, "file-size", 4096, "size of each file")
|
||||
f.StringVar(&c.targetDir, "target-dir", "/", "directory under which to create the filesystem tree")
|
||||
f.BoolVar(&c.createSymlink, "create-symlink", false, "create symlinks other than the first file per level")
|
||||
}
|
||||
|
||||
// Execute implements subcommands.Command.Execute.
|
||||
@@ -93,15 +97,26 @@ func (c *fsTreeCreator) Execute(ctx context.Context, f *flag.FlagSet, args ...an
|
||||
depth := c.depth + uint(rand.Uint32())%c.depth
|
||||
numFilesPerLevel := c.numFilesPerLevel + uint(rand.Uint32())%c.numFilesPerLevel
|
||||
fileSize := c.fileSize + uint(rand.Uint32())%c.fileSize
|
||||
curDir := c.targetDir
|
||||
if _, err := os.Stat(curDir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(curDir, 0777); err != nil {
|
||||
log.Fatalf("error creating directory %q: %v", curDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
curDir := "/"
|
||||
data := make([]byte, fileSize)
|
||||
rand.Read(data)
|
||||
for i := uint(0); i < depth; i++ {
|
||||
for j := uint(0); j < numFilesPerLevel; j++ {
|
||||
filePath := filepath.Join(curDir, fmt.Sprintf("file%d", j))
|
||||
if err := os.WriteFile(filePath, data, 0666); err != nil {
|
||||
log.Fatalf("error writing file %q: %v", filePath, err)
|
||||
if c.createSymlink && j > 0 {
|
||||
if err := os.Symlink("file0", filePath); err != nil {
|
||||
log.Fatalf("error creating symlink %q: %v", filePath, err)
|
||||
}
|
||||
} else {
|
||||
if err := os.WriteFile(filePath, data, 0666); err != nil {
|
||||
log.Fatalf("error writing file %q: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
nextDir := filepath.Join(curDir, "dir")
|
||||
|
||||
Reference in New Issue
Block a user