Add initial support for EROFS

This patch adds initial support for EROFS [1]. Below is a brief
summary of the supported features.

Both inode formats are supported:

- compact format (32 bytes);
- extended format (64 bytes);

Below data layouts are supported:

- flat file data without data inline (no extent);
- flat file data with tail packing data inline (no extent);

Below file types are supported:

- directory;
- regular file;
- symlink;

Special files (e.g. fifo) can be listed, but cannot be accessed.

With this patch, sentry will be able to mount the EROFS image
created with below command and access the files on it.

 mkfs.erofs -E noinline_data <IMAGE FILE> <SOURCE DIRECTORY>

[1] https://docs.kernel.org/filesystems/erofs.html

Updates #8956

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
This commit is contained in:
Tiwei Bie
2023-10-04 18:15:30 +08:00
parent e16f83a45c
commit eca83ac68c
12 changed files with 2284 additions and 0 deletions
+34
View File
@@ -193,6 +193,40 @@ var DirentType = abi.ValueSet{
DT_WHT: "DT_WHT", 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. // Values for preadv2/pwritev2.
const ( const (
// NOTE(b/120162627): gVisor does not implement the RWF_HIPRI feature, but // NOTE(b/120162627): gVisor does not implement the RWF_HIPRI feature, but
+31
View File
@@ -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",
)
+730
View File
File diff suppressed because it is too large Load Diff
+37
View File
@@ -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())
}
}
+74
View File
@@ -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",
],
)
+172
View File
@@ -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
+444
View File
@@ -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))
}
+207
View File
@@ -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 := &regularFileReader{
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()
}
+27
View File
@@ -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)
}
+1
View File
@@ -60,6 +60,7 @@ go_library(
"//pkg/sentry/fsimpl/cgroupfs", "//pkg/sentry/fsimpl/cgroupfs",
"//pkg/sentry/fsimpl/devpts", "//pkg/sentry/fsimpl/devpts",
"//pkg/sentry/fsimpl/devtmpfs", "//pkg/sentry/fsimpl/devtmpfs",
"//pkg/sentry/fsimpl/erofs",
"//pkg/sentry/fsimpl/fuse", "//pkg/sentry/fsimpl/fuse",
"//pkg/sentry/fsimpl/gofer", "//pkg/sentry/fsimpl/gofer",
"//pkg/sentry/fsimpl/host", "//pkg/sentry/fsimpl/host",
+4
View File
@@ -39,6 +39,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/fsimpl/cgroupfs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/cgroupfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/devpts" "gvisor.dev/gvisor/pkg/sentry/fsimpl/devpts"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/devtmpfs" "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/fuse"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/gofer" "gvisor.dev/gvisor/pkg/sentry/fsimpl/gofer"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/mqfs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/mqfs"
@@ -101,6 +102,9 @@ func registerFilesystems(k *kernel.Kernel, info *containerInfo) error {
AllowUserMount: true, AllowUserMount: true,
AllowUserList: true, AllowUserList: true,
}) })
vfsObj.MustRegisterFilesystemType(erofs.Name, &erofs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserList: true,
})
vfsObj.MustRegisterFilesystemType(fuse.Name, &fuse.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ vfsObj.MustRegisterFilesystemType(fuse.Name, &fuse.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true, AllowUserMount: true,
AllowUserList: true, AllowUserList: true,