mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Directfs implementation.
Directfs accesses host filesystem directly, without going through the gofer server. This change basically adds runsc fsgofer functionality into the gofer client. We use the gofer dentry impl abstraction to make direct host syscalls instead of making LISAFS RPCs. It works on a donated FD to the root of the mount. All other relevant mount options supported in gofer client are also supported here. Directfs is also a lisafs user. Directfs client makes a Mount RPC. The server is expected to donate a host FD to the mount point that the client can use to perform all necessary filesystem operations. Directfs client does not set up any flipcall channels, because it (mostly) doesn't make any RPCs. Note however that we can not avoid LISAFS RPCs in certain cases. Certain operations like bind(2) and connect(2) require host paths. But the container filesystem is not mounted inside the sandbox's mount namespace. The sandbox just has an FD to the filesystem root. Furthermore, procfs is also not mounted in the sandbox process for security reasons. Otherwise we could have used host paths like `/proc/self/fd/<sockFD>`. The only viable option is to use fchdir(2) to change working directory to the socket's parent directory and use a relative host path from there. But in certain situations, we don't even have access to a socket's parent directory (in case of a socket mount point). The parent lives in a different gofer mount in the sentry. There is no clean way of fetching that. In these extreme corner cases, directfs falls back to using LISAFS RPCs. These corner cases are: - chmod(2) on mount point socket. - utimensat(2) on mount point symlink. - connect(2) on mount point socket. PiperOrigin-RevId: 510465837
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
go_library(
|
||||
name = "fsutil",
|
||||
srcs = [
|
||||
"chdir.go",
|
||||
"fsutil.go",
|
||||
"fsutil_amd64_unsafe.go",
|
||||
"fsutil_arm64_unsafe.go",
|
||||
"fsutil_unsafe.go",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/sync",
|
||||
"//pkg/syserr",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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 fsutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
)
|
||||
|
||||
// chdirMu is the global mutex that synchronizes host chdir operations.
|
||||
var chdirMu sync.Mutex
|
||||
|
||||
// DoInDir performs fn after chdir-ing to dirFD and then reverts back to the
|
||||
// original CWD.
|
||||
func DoInDir(dirFD int, fn func() error) error {
|
||||
chdirMu.Lock()
|
||||
defer chdirMu.Unlock()
|
||||
|
||||
oldCWD, err := unix.Openat(unix.AT_FDCWD, ".", unix.O_PATH, 0 /* mode */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := unix.Fchdir(oldCWD); err != nil {
|
||||
panic(fmt.Errorf("restoring orginial CWD failed: %v", err))
|
||||
}
|
||||
}()
|
||||
|
||||
if err := unix.Fchdir(dirFD); err != nil {
|
||||
return err
|
||||
}
|
||||
return fn()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 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 fsutil contains filesystem utilities that can be shared between the
|
||||
// sentry and other sandbox components.
|
||||
package fsutil
|
||||
@@ -15,7 +15,7 @@
|
||||
//go:build amd64
|
||||
// +build amd64
|
||||
|
||||
package fsgofer
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
@@ -24,7 +24,8 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
)
|
||||
|
||||
func statAt(dirFd int, name string) (unix.Stat_t, error) {
|
||||
// StatAt is a convenience wrapper around newfstatat(2).
|
||||
func StatAt(dirFd int, name string) (unix.Stat_t, error) {
|
||||
nameBytes, err := unix.BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return unix.Stat_t{}, err
|
||||
@@ -15,7 +15,7 @@
|
||||
//go:build arm64
|
||||
// +build arm64
|
||||
|
||||
package fsgofer
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
@@ -24,7 +24,8 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
)
|
||||
|
||||
func statAt(dirFd int, name string) (unix.Stat_t, error) {
|
||||
// StatAt is a convenience wrapper around fstatat(2).
|
||||
func StatAt(dirFd int, name string) (unix.Stat_t, error) {
|
||||
nameBytes, err := unix.BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return unix.Stat_t{}, err
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fsgofer
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
@@ -21,9 +21,12 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
)
|
||||
|
||||
var unixDirentMaxSize = int(unsafe.Sizeof(unix.Dirent{}))
|
||||
// UnixDirentMaxSize is the maximum size of unix.Dirent in bytes.
|
||||
var UnixDirentMaxSize = int(unsafe.Sizeof(unix.Dirent{}))
|
||||
|
||||
func utimensat(dirFd int, name string, times [2]unix.Timespec, flags int) error {
|
||||
// Utimensat is a convenience wrapper to make the utimensat(2) syscall. It
|
||||
// additionally handles empty name.
|
||||
func Utimensat(dirFd int, name string, times [2]unix.Timespec, flags int) error {
|
||||
// utimensat(2) doesn't accept empty name, instead name must be nil to make it
|
||||
// operate directly on 'dirFd' unlike other *at syscalls.
|
||||
var namePtr unsafe.Pointer
|
||||
@@ -51,7 +54,9 @@ func utimensat(dirFd int, name string, times [2]unix.Timespec, flags int) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameat(oldDirFD int, oldName string, newDirFD int, newName string) error {
|
||||
// RenameAt is a convenience wrapper to make the renameat(2) syscall. It
|
||||
// additionally handles empty names.
|
||||
func RenameAt(oldDirFD int, oldName string, newDirFD int, newName string) error {
|
||||
var oldNamePtr unsafe.Pointer
|
||||
if oldName != "" {
|
||||
nameBytes, err := unix.BytePtrFromString(oldName)
|
||||
@@ -83,7 +88,9 @@ func renameat(oldDirFD int, oldName string, newDirFD int, newName string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDirents(buf []byte, handleDirent func(ino uint64, off int64, ftype uint8, name string, reclen uint16) bool) {
|
||||
// ParseDirents parses dirents from buf. buf must have been populated by
|
||||
// getdents64(2) syscall. It calls the handleDirent callback for each dirent.
|
||||
func ParseDirents(buf []byte, handleDirent func(ino uint64, off int64, ftype uint8, name string, reclen uint16) bool) {
|
||||
for len(buf) > 0 {
|
||||
// Interpret the buf populated by unix.Getdents as unix.Dirent.
|
||||
dirent := *(*unix.Dirent)(unsafe.Pointer(&buf[0]))
|
||||
@@ -55,6 +55,7 @@ go_library(
|
||||
srcs = [
|
||||
"dentry_impl.go",
|
||||
"dentry_list.go",
|
||||
"directfs_dentry.go",
|
||||
"directory.go",
|
||||
"filesystem.go",
|
||||
"fstree.go",
|
||||
@@ -76,11 +77,13 @@ go_library(
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/atomicbitops",
|
||||
"//pkg/cleanup",
|
||||
"//pkg/context",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/fd",
|
||||
"//pkg/fdnotifier",
|
||||
"//pkg/fspath",
|
||||
"//pkg/fsutil",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/lisafs",
|
||||
"//pkg/log",
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
@@ -46,6 +48,8 @@ func (d *dentry) isReadHandleOk() bool {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.readFDLisa.Ok()
|
||||
case *directfsDentry:
|
||||
return d.readFD.RacyLoad() >= 0
|
||||
case nil: // synthetic dentry
|
||||
return false
|
||||
default:
|
||||
@@ -58,6 +62,8 @@ func (d *dentry) isWriteHandleOk() bool {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.writeFDLisa.Ok()
|
||||
case *directfsDentry:
|
||||
return d.writeFD.RacyLoad() >= 0
|
||||
case nil: // synthetic dentry
|
||||
return false
|
||||
default:
|
||||
@@ -73,6 +79,8 @@ func (d *dentry) readHandle() handle {
|
||||
fdLisa: dt.readFDLisa,
|
||||
fd: d.readFD.RacyLoad(),
|
||||
}
|
||||
case *directfsDentry:
|
||||
return handle{fd: d.readFD.RacyLoad()}
|
||||
case nil: // synthetic dentry
|
||||
return noHandle
|
||||
default:
|
||||
@@ -88,6 +96,8 @@ func (d *dentry) writeHandle() handle {
|
||||
fdLisa: dt.writeFDLisa,
|
||||
fd: d.writeFD.RacyLoad(),
|
||||
}
|
||||
case *directfsDentry:
|
||||
return handle{fd: d.writeFD.RacyLoad()}
|
||||
case nil: // synthetic dentry
|
||||
return noHandle
|
||||
default:
|
||||
@@ -95,7 +105,9 @@ func (d *dentry) writeHandle() handle {
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: !d.isSynthetic().
|
||||
// Preconditions:
|
||||
// - !d.isSynthetic().
|
||||
// - fs.renameMu is locked.
|
||||
func (d *dentry) openHandle(ctx context.Context, read, write, trunc bool) (handle, error) {
|
||||
flags := uint32(unix.O_RDONLY)
|
||||
switch {
|
||||
@@ -113,14 +125,9 @@ func (d *dentry) openHandle(ctx context.Context, read, write, trunc bool) (handl
|
||||
}
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
openFD, hostFD, err := dt.controlFD.OpenAt(ctx, flags)
|
||||
if err != nil {
|
||||
return noHandle, err
|
||||
}
|
||||
return handle{
|
||||
fdLisa: dt.controlFD.Client().NewFD(openFD),
|
||||
fd: int32(hostFD),
|
||||
}, nil
|
||||
return dt.openHandle(ctx, flags)
|
||||
case *directfsDentry:
|
||||
return dt.openHandle(ctx, flags)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -133,6 +140,8 @@ func (d *dentry) updateHandles(ctx context.Context, h handle, readable, writable
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
dt.updateHandles(ctx, h, readable, writable)
|
||||
case *directfsDentry:
|
||||
// No update needed.
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -148,18 +157,41 @@ func (d *dentry) updateHandles(ctx context.Context, h handle, readable, writable
|
||||
//
|
||||
// +checklocks:d.metadataMu
|
||||
func (d *dentry) updateMetadataLocked(ctx context.Context, h handle) error {
|
||||
// Need checklocksforce below because checklocks has no way of knowing that
|
||||
// d.impl.(*dentryImpl).dentry == d. It can't know that the right metadataMu
|
||||
// is already locked.
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.updateMetadataLocked(ctx, h) // +checklocksforce: acquired by precondition.
|
||||
case *directfsDentry:
|
||||
return dt.updateMetadataLocked(h) // +checklocksforce: acquired by precondition.
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
}
|
||||
|
||||
// Preconditions:
|
||||
// - !d.isSynthetic().
|
||||
// - fs.renameMu is locked.
|
||||
func (d *dentry) prepareSetStat(ctx context.Context, stat *linux.Statx) error {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
// Nothing to be done.
|
||||
return nil
|
||||
case *directfsDentry:
|
||||
return dt.prepareSetStat(ctx, stat)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: fs.renameMu is locked if d is a socket.
|
||||
func (d *dentry) chmod(ctx context.Context, mode uint16) error {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return chmod(ctx, dt.controlFD, mode)
|
||||
case *directfsDentry:
|
||||
return dt.chmod(ctx, mode)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -168,10 +200,14 @@ func (d *dentry) chmod(ctx context.Context, mode uint16) error {
|
||||
// Preconditions:
|
||||
// - !d.isSynthetic().
|
||||
// - d.handleMu is locked.
|
||||
// - fs.renameMu is locked.
|
||||
func (d *dentry) setStatLocked(ctx context.Context, stat *linux.Statx) (uint32, error, error) {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.SetStat(ctx, stat)
|
||||
case *directfsDentry:
|
||||
failureMask, failureErr := dt.setStatLocked(ctx, stat)
|
||||
return failureMask, failureErr, nil
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -181,6 +217,8 @@ func (d *dentry) destroyImpl(ctx context.Context) {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
dt.destroy(ctx)
|
||||
case *directfsDentry:
|
||||
dt.destroy(ctx)
|
||||
case nil: // synthetic dentry
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
@@ -194,6 +232,8 @@ func (d *dentry) getRemoteChild(ctx context.Context, name string) (*dentry, erro
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.getRemoteChild(ctx, name)
|
||||
case *directfsDentry:
|
||||
return dt.getHostChild(name)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -204,7 +244,6 @@ func (d *dentry) getRemoteChild(ctx context.Context, name string) (*dentry, erro
|
||||
// - parent.opMu must be locked for reading.
|
||||
// - parent.isDir().
|
||||
// - !rp.Done() && rp.Component() is not "." or "..".
|
||||
// - dentry at name must not already exist in dentry tree.
|
||||
//
|
||||
// Postcondition: The returned dentry is already cached appropriately.
|
||||
//
|
||||
@@ -213,8 +252,10 @@ func (d *dentry) getRemoteChildAndWalkPathLocked(ctx context.Context, rp *vfs.Re
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.getRemoteChildAndWalkPathLocked(ctx, rp, ds)
|
||||
// TODO(b/258687694): For directfs, remember to use fs.getRemoteChildLocked
|
||||
// so that dentry caching is done properly.
|
||||
case *directfsDentry:
|
||||
// We need to check for races because opMu is read locked which allows
|
||||
// concurrent walks to occur.
|
||||
return d.fs.getRemoteChildLocked(ctx, d, rp.Component(), true /* checkForRace */, ds)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -225,6 +266,9 @@ func (d *dentry) listXattrImpl(ctx context.Context, size uint64) ([]string, erro
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.ListXattr(ctx, size)
|
||||
case *directfsDentry:
|
||||
// Consistent with runsc/fsgofer.
|
||||
return nil, linuxerr.EOPNOTSUPP
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -235,6 +279,9 @@ func (d *dentry) getXattrImpl(ctx context.Context, opts *vfs.GetXattrOptions) (s
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.GetXattr(ctx, opts.Name, opts.Size)
|
||||
case *directfsDentry:
|
||||
// Consistent with runsc/fsgofer.
|
||||
return "", linuxerr.EOPNOTSUPP
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -245,6 +292,9 @@ func (d *dentry) setXattrImpl(ctx context.Context, opts *vfs.SetXattrOptions) er
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.SetXattr(ctx, opts.Name, opts.Value, opts.Flags)
|
||||
case *directfsDentry:
|
||||
// Consistent with runsc/fsgofer.
|
||||
return linuxerr.EOPNOTSUPP
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -255,6 +305,9 @@ func (d *dentry) removeXattrImpl(ctx context.Context, name string) error {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.RemoveXattr(ctx, name)
|
||||
case *directfsDentry:
|
||||
// Consistent with runsc/fsgofer.
|
||||
return linuxerr.EOPNOTSUPP
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -265,6 +318,8 @@ func (d *dentry) mknod(ctx context.Context, name string, creds *auth.Credentials
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.mknod(ctx, name, creds, opts)
|
||||
case *directfsDentry:
|
||||
return dt.mknod(ctx, name, creds, opts)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -275,6 +330,8 @@ func (d *dentry) link(ctx context.Context, target *dentry, name string) (*dentry
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.link(ctx, target.impl.(*lisafsDentry), name)
|
||||
case *directfsDentry:
|
||||
return dt.link(target.impl.(*directfsDentry), name)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -285,6 +342,8 @@ func (d *dentry) mkdir(ctx context.Context, name string, mode linux.FileMode, ui
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.mkdir(ctx, name, mode, uid, gid)
|
||||
case *directfsDentry:
|
||||
return dt.mkdir(name, mode, uid, gid)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -295,6 +354,8 @@ func (d *dentry) symlink(ctx context.Context, name, target string, creds *auth.C
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.symlink(ctx, name, target, creds)
|
||||
case *directfsDentry:
|
||||
return dt.symlink(name, target, creds)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -305,6 +366,8 @@ func (d *dentry) openCreate(ctx context.Context, name string, accessFlags uint32
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.openCreate(ctx, name, accessFlags, mode, uid, gid)
|
||||
case *directfsDentry:
|
||||
return dt.openCreate(name, accessFlags, mode, uid, gid)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -318,6 +381,8 @@ func (d *dentry) getDirentsLocked(ctx context.Context, count int, recordDirent f
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.getDirentsLocked(ctx, count, recordDirent)
|
||||
case *directfsDentry:
|
||||
return dt.getDirentsLocked(count, recordDirent)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -330,6 +395,9 @@ func (d *dentry) flush(ctx context.Context) error {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return flush(ctx, dt.writeFDLisa)
|
||||
case *directfsDentry:
|
||||
// Nothing to do here.
|
||||
return nil
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -342,16 +410,22 @@ func (d *dentry) allocate(ctx context.Context, mode, offset, length uint64) erro
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.writeFDLisa.Allocate(ctx, mode, offset, length)
|
||||
case *directfsDentry:
|
||||
return unix.Fallocate(int(d.writeFD.RacyLoad()), uint32(mode), int64(offset), int64(length))
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: !d.isSynthetic().
|
||||
// Preconditions:
|
||||
// - !d.isSynthetic().
|
||||
// - fs.renameMu is locked.
|
||||
func (d *dentry) connect(ctx context.Context, sockType linux.SockType) (int, error) {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.Connect(ctx, sockType)
|
||||
case *directfsDentry:
|
||||
return dt.connect(ctx, sockType)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -362,6 +436,8 @@ func (d *dentry) readlinkImpl(ctx context.Context) (string, error) {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.ReadLinkAt(ctx)
|
||||
case *directfsDentry:
|
||||
return dt.readlink()
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -372,6 +448,8 @@ func (d *dentry) unlink(ctx context.Context, name string, flags uint32) error {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.UnlinkAt(ctx, name, flags)
|
||||
case *directfsDentry:
|
||||
return unix.Unlinkat(dt.controlFD, name, int(flags))
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -382,6 +460,8 @@ func (d *dentry) rename(ctx context.Context, oldName string, newParent *dentry,
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.controlFD.RenameAt(ctx, oldName, newParent.impl.(*lisafsDentry).controlFD.ID(), newName)
|
||||
case *directfsDentry:
|
||||
return fsutil.RenameAt(dt.controlFD, oldName, newParent.impl.(*directfsDentry).controlFD, newName)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -392,20 +472,26 @@ func (d *dentry) statfs(ctx context.Context) (linux.Statfs, error) {
|
||||
switch dt := d.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return dt.statfs(ctx)
|
||||
case *directfsDentry:
|
||||
return dt.statfs()
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *filesystem) restoreRoot(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
|
||||
rootInode, rootHostFD, err := fs.initClientAndGetRoot(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The root is always non-synthetic.
|
||||
switch dt := fs.root.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
rootInode, err := fs.initClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dt.restoreFile(ctx, &rootInode, opts)
|
||||
case *directfsDentry:
|
||||
dt.rootControlFDLisa = fs.client.NewFD(rootInode.ControlFD)
|
||||
return dt.restoreFile(ctx, rootHostFD, opts)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -422,6 +508,14 @@ func (d *dentry) restoreFile(ctx context.Context, opts *vfs.CompleteRestoreOptio
|
||||
return err
|
||||
}
|
||||
return dt.restoreFile(ctx, &inode, opts)
|
||||
case *directfsDentry:
|
||||
childFD, err := tryOpen(func(flags int) (int, error) {
|
||||
return unix.Openat(d.parent.impl.(*directfsDentry).controlFD, d.name, flags, 0)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dt.restoreFile(ctx, childFD, opts)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
@@ -442,6 +536,8 @@ func (r *revalidateState) doRevalidation(ctx context.Context, vfsObj *vfs.Virtua
|
||||
switch r.start.impl.(type) {
|
||||
case *lisafsDentry:
|
||||
return doRevalidationLisafs(ctx, vfsObj, r, ds)
|
||||
case *directfsDentry:
|
||||
return doRevalidationDirectfs(ctx, vfsObj, r, ds)
|
||||
default:
|
||||
panic("unknown dentry implementation")
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -252,6 +252,8 @@ func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name s
|
||||
// - If checkForRace is false, then parent.opMu must be held for writing.
|
||||
// - Otherwise, parent.opMu must be held for reading.
|
||||
//
|
||||
// Postcondition: The returned dentry is already cached appropriately.
|
||||
//
|
||||
// +checklocksread:parent.opMu
|
||||
func (fs *filesystem) getRemoteChildLocked(ctx context.Context, parent *dentry, name string, checkForRace bool, ds **[]*dentry) (*dentry, error) {
|
||||
child, err := parent.getRemoteChild(ctx, name)
|
||||
@@ -1038,6 +1040,16 @@ func (d *dentry) open(ctx context.Context, rp *vfs.ResolvingPath, opts *vfs.Open
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !d.isSynthetic() {
|
||||
// renameMu is locked here because it is required by d.openHandle(), which
|
||||
// is called by d.ensureSharedHandle() and d.openSpecialFile() below. It is
|
||||
// also required by d.connect() which is called by
|
||||
// d.openSocketByConnecting(). Note that opening non-synthetic pipes may
|
||||
// block, renameMu is unlocked separately in d.openSpecialFile() for pipes.
|
||||
d.fs.renameMu.RLock()
|
||||
defer d.fs.renameMu.RUnlock()
|
||||
}
|
||||
|
||||
trunc := opts.Flags&linux.O_TRUNC != 0 && d.fileType() == linux.S_IFREG
|
||||
if trunc {
|
||||
// Lock metadataMu *while* we open a regular file with O_TRUNC because
|
||||
@@ -1125,6 +1137,7 @@ func (d *dentry) open(ctx context.Context, rp *vfs.ResolvingPath, opts *vfs.Open
|
||||
return vfd, err
|
||||
}
|
||||
|
||||
// Precondition: fs.renameMu is locked.
|
||||
func (d *dentry) openSocketByConnecting(ctx context.Context, opts *vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
if opts.Flags&linux.O_DIRECT != 0 {
|
||||
return nil, linuxerr.EINVAL
|
||||
@@ -1146,6 +1159,10 @@ func (d *dentry) openSocketByConnecting(ctx context.Context, opts *vfs.OpenOptio
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// Preconditions:
|
||||
// - !d.isSynthetic().
|
||||
// - fs.renameMu is locked. It may be released temporarily while pipe blocks.
|
||||
// - If d is a pipe, no other locks (other than fs.renameMu) should be held.
|
||||
func (d *dentry) openSpecialFile(ctx context.Context, mnt *vfs.Mount, opts *vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
ats := vfs.AccessTypesForOpenFlags(opts)
|
||||
if opts.Flags&linux.O_DIRECT != 0 && !d.isRegularFile() {
|
||||
@@ -1166,8 +1183,12 @@ retry:
|
||||
if isBlockingOpenOfNamedPipe && ats == vfs.MayWrite && linuxerr.Equals(linuxerr.ENXIO, err) {
|
||||
// An attempt to open a named pipe with O_WRONLY|O_NONBLOCK fails
|
||||
// with ENXIO if opening the same named pipe with O_WRONLY would
|
||||
// block because there are no readers of the pipe.
|
||||
if err := sleepBetweenNamedPipeOpenChecks(ctx); err != nil {
|
||||
// block because there are no readers of the pipe. Release renameMu
|
||||
// while blocking.
|
||||
d.fs.renameMu.RUnlock()
|
||||
err := sleepBetweenNamedPipeOpenChecks(ctx)
|
||||
d.fs.renameMu.RLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
goto retry
|
||||
@@ -1175,7 +1196,11 @@ retry:
|
||||
return nil, err
|
||||
}
|
||||
if isBlockingOpenOfNamedPipe && ats == vfs.MayRead && h.fd >= 0 {
|
||||
if err := blockUntilNonblockingPipeHasWriter(ctx, h.fd); err != nil {
|
||||
// Release renameMu while blocking.
|
||||
d.fs.renameMu.RUnlock()
|
||||
err := blockUntilNonblockingPipeHasWriter(ctx, h.fd)
|
||||
d.fs.renameMu.RLock()
|
||||
if err != nil {
|
||||
h.close(ctx)
|
||||
return nil, err
|
||||
}
|
||||
@@ -1726,6 +1751,15 @@ func (fs *filesystem) MountOptions() string {
|
||||
if fs.opts.overlayfsStaleRead {
|
||||
optsKV = append(optsKV, mopt{moptOverlayfsStaleRead, nil})
|
||||
}
|
||||
if fs.opts.directfs.enabled {
|
||||
optsKV = append(optsKV, mopt{moptDirectfs, nil})
|
||||
if fs.opts.directfs.hostUDSBind {
|
||||
optsKV = append(optsKV, mopt{moptHostUDSBind, nil})
|
||||
}
|
||||
if fs.opts.directfs.hostUDSConnect {
|
||||
optsKV = append(optsKV, mopt{moptHostUDSConnect, nil})
|
||||
}
|
||||
}
|
||||
|
||||
opts := make([]string, 0, len(optsKV))
|
||||
for _, opt := range optsKV {
|
||||
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/atomicbitops"
|
||||
"gvisor.dev/gvisor/pkg/cleanup"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
@@ -82,6 +83,11 @@ const (
|
||||
moptForcePageCache = "force_page_cache"
|
||||
moptLimitHostFDTranslation = "limit_host_fd_translation"
|
||||
moptOverlayfsStaleRead = "overlayfs_stale_read"
|
||||
|
||||
// Directfs options.
|
||||
moptDirectfs = "directfs"
|
||||
moptHostUDSConnect = "host_uds_connect"
|
||||
moptHostUDSBind = "host_uds_bind"
|
||||
)
|
||||
|
||||
// Valid values for the "cache" mount option.
|
||||
@@ -271,6 +277,24 @@ type filesystemOptions struct {
|
||||
// may regress performance due to excessive Open RPCs. This option is not
|
||||
// supported with overlayfsStaleRead for now.
|
||||
regularFilesUseSpecialFileFD bool
|
||||
|
||||
// directfs holds options for directfs mode.
|
||||
directfs directfsOpts
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type directfsOpts struct {
|
||||
// If directfs is enabled, the gofer client does not make RPCs to the gofer
|
||||
// process. Instead, it makes host syscalls to perform file operations.
|
||||
enabled bool
|
||||
|
||||
// hostUDSBind dictates whether this mount can create host unix domain
|
||||
// sockets.
|
||||
hostUDSBind bool
|
||||
|
||||
// hostUDSConnect dictates whether this mount can connect to host unix domain
|
||||
// sockets.
|
||||
hostUDSConnect bool
|
||||
}
|
||||
|
||||
// InteropMode controls the client's interaction with other remote filesystem
|
||||
@@ -457,6 +481,18 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
delete(mopts, moptOverlayfsStaleRead)
|
||||
fsopts.overlayfsStaleRead = true
|
||||
}
|
||||
if _, ok := mopts[moptDirectfs]; ok {
|
||||
delete(mopts, moptDirectfs)
|
||||
fsopts.directfs.enabled = true
|
||||
}
|
||||
if _, ok := mopts[moptHostUDSBind]; ok {
|
||||
delete(mopts, moptHostUDSBind)
|
||||
fsopts.directfs.hostUDSBind = true
|
||||
}
|
||||
if _, ok := mopts[moptHostUDSConnect]; ok {
|
||||
delete(mopts, moptHostUDSConnect)
|
||||
fsopts.directfs.hostUDSConnect = true
|
||||
}
|
||||
// fsopts.regularFilesUseSpecialFileFD can only be enabled by specifying
|
||||
// "cache=none".
|
||||
|
||||
@@ -507,81 +543,82 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
|
||||
fs.vfsfs.Init(vfsObj, &fstype, fs)
|
||||
|
||||
// TODO(b/258687694): Handle directfs.
|
||||
if err := fs.initClientAndRoot(ctx); err != nil {
|
||||
rootInode, rootHostFD, err := fs.initClientAndGetRoot(ctx)
|
||||
if err != nil {
|
||||
fs.vfsfs.DecRef(ctx)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &fs.vfsfs, &fs.root.vfsd, nil
|
||||
}
|
||||
|
||||
func (fs *filesystem) initClientAndRoot(ctx context.Context) error {
|
||||
rootInode, err := fs.initClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
if fs.opts.directfs.enabled {
|
||||
fs.root, err = fs.getDirectfsRootDentry(ctx, rootHostFD, fs.client.NewFD(rootInode.ControlFD))
|
||||
} else {
|
||||
fs.root, err = fs.newLisafsDentry(ctx, &rootInode)
|
||||
}
|
||||
fs.root, err = fs.newLisafsDentry(ctx, &rootInode)
|
||||
if err != nil {
|
||||
return err
|
||||
fs.vfsfs.DecRef(ctx)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Set the root's reference count to 2. One reference is returned to the
|
||||
// caller, and the other is held by fs to prevent the root from being "cached"
|
||||
// and subsequently evicted.
|
||||
fs.root.refs = atomicbitops.FromInt64(2)
|
||||
return nil
|
||||
return &fs.vfsfs, &fs.root.vfsd, nil
|
||||
}
|
||||
|
||||
func (fs *filesystem) initClient(ctx context.Context) (lisafs.Inode, error) {
|
||||
// initClientAndGetRoot initializes fs.client and returns the root inode for
|
||||
// this mount point. It handles the attach point (fs.opts.aname) resolution.
|
||||
func (fs *filesystem) initClientAndGetRoot(ctx context.Context) (lisafs.Inode, int, error) {
|
||||
sock, err := unet.NewSocket(fs.opts.fd)
|
||||
if err != nil {
|
||||
return lisafs.Inode{}, err
|
||||
return lisafs.Inode{}, -1, err
|
||||
}
|
||||
|
||||
var rootInode lisafs.Inode
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
fs.client, rootInode, _, err = lisafs.NewClient(sock)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
defer ctx.UninterruptibleSleepFinish(false)
|
||||
|
||||
var (
|
||||
rootInode lisafs.Inode
|
||||
rootHostFD int
|
||||
)
|
||||
fs.client, rootInode, rootHostFD, err = lisafs.NewClient(sock)
|
||||
if err != nil {
|
||||
return lisafs.Inode{}, err
|
||||
}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err = fs.client.StartChannels()
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
return lisafs.Inode{}, err
|
||||
}
|
||||
if fs.opts.aname == "/" {
|
||||
return rootInode, nil
|
||||
return lisafs.Inode{}, -1, err
|
||||
}
|
||||
|
||||
// Walk to the attach point from root inode. aname is always absolute.
|
||||
rootFD := fs.client.NewFD(rootInode.ControlFD)
|
||||
status, inodes, err := rootFD.WalkMultiple(ctx, strings.Split(fs.opts.aname, "/")[1:])
|
||||
rootFD.Close(ctx, false /* flush */)
|
||||
if err != nil {
|
||||
return lisafs.Inode{}, err
|
||||
}
|
||||
cu := cleanup.Make(func() {
|
||||
if rootHostFD >= 0 {
|
||||
_ = unix.Close(rootHostFD)
|
||||
}
|
||||
rootControlFD := fs.client.NewFD(rootInode.ControlFD)
|
||||
rootControlFD.Close(ctx, false /* flush */)
|
||||
})
|
||||
defer cu.Clean()
|
||||
|
||||
// Close all intermediate FDs to the attach point.
|
||||
numInodes := len(inodes)
|
||||
for i := 0; i < numInodes-1; i++ {
|
||||
curFD := fs.client.NewFD(inodes[i].ControlFD)
|
||||
curFD.Close(ctx, false /* flush */)
|
||||
}
|
||||
|
||||
switch status {
|
||||
case lisafs.WalkSuccess:
|
||||
return inodes[numInodes-1], nil
|
||||
default:
|
||||
if numInodes > 0 {
|
||||
last := fs.client.NewFD(inodes[numInodes-1].ControlFD)
|
||||
last.Close(ctx, false /* flush */)
|
||||
if fs.opts.directfs.enabled {
|
||||
if fs.opts.aname != "/" {
|
||||
log.Warningf("directfs does not support aname filesystem option: aname=%q", fs.opts.aname)
|
||||
return lisafs.Inode{}, -1, unix.EINVAL
|
||||
}
|
||||
if rootHostFD < 0 {
|
||||
log.Warningf("Mount RPC did not return host FD to mount point with directfs enabled")
|
||||
return lisafs.Inode{}, -1, unix.EINVAL
|
||||
}
|
||||
} else {
|
||||
if rootHostFD >= 0 {
|
||||
log.Warningf("Mount RPC returned a host FD to mount point without directfs, we didn't ask for it")
|
||||
_ = unix.Close(rootHostFD)
|
||||
rootHostFD = -1
|
||||
}
|
||||
// Use flipcall channels with lisafs because it makes a lot of RPCs.
|
||||
if err := fs.client.StartChannels(); err != nil {
|
||||
return lisafs.Inode{}, -1, err
|
||||
}
|
||||
rootInode, err = fs.handleAnameLisafs(ctx, rootInode)
|
||||
if err != nil {
|
||||
return lisafs.Inode{}, -1, err
|
||||
}
|
||||
log.Warningf("initClient failed because walk to attach point %q failed: lisafs.WalkStatus = %v", fs.opts.aname, status)
|
||||
return lisafs.Inode{}, unix.ENOENT
|
||||
}
|
||||
cu.Release()
|
||||
return rootInode, rootHostFD, nil
|
||||
}
|
||||
|
||||
func getFDFromMountOptionsMap(ctx context.Context, mopts map[string]string) (int, error) {
|
||||
@@ -731,6 +768,14 @@ func inoKeyFromStatx(stat *linux.Statx) inoKey {
|
||||
}
|
||||
}
|
||||
|
||||
func inoKeyFromStat(stat *unix.Stat_t) inoKey {
|
||||
return inoKey{
|
||||
ino: stat.Ino,
|
||||
devMinor: unix.Minor(stat.Dev),
|
||||
devMajor: unix.Major(stat.Dev),
|
||||
}
|
||||
}
|
||||
|
||||
// dentry implements vfs.DentryImpl.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -1049,6 +1094,32 @@ func (d *lisafsDentry) updateMetadataFromStatxLocked(stat *linux.Statx) {
|
||||
}
|
||||
}
|
||||
|
||||
// updateMetadataFromStatLocked is similar to updateMetadataFromStatxLocked,
|
||||
// except that it takes a unix.Stat_t argument.
|
||||
// Precondition: d.metadataMu must be locked.
|
||||
// +checklocks:d.metadataMu
|
||||
func (d *directfsDentry) updateMetadataFromStatLocked(stat *unix.Stat_t) error {
|
||||
if got, want := stat.Mode&unix.S_IFMT, d.fileType(); got != want {
|
||||
panic(fmt.Sprintf("direct.dentry file type changed from %#o to %#o", want, got))
|
||||
}
|
||||
d.mode.Store(stat.Mode)
|
||||
d.uid.Store(stat.Uid)
|
||||
d.gid.Store(stat.Gid)
|
||||
d.blockSize.Store(uint32(stat.Blksize))
|
||||
// Don't override newer client-defined timestamps with old host-defined
|
||||
// ones.
|
||||
if d.atimeDirty.Load() == 0 {
|
||||
d.atime.Store(dentryTimestampFromUnix(stat.Atim))
|
||||
}
|
||||
if d.mtimeDirty.Load() == 0 {
|
||||
d.mtime.Store(dentryTimestampFromUnix(stat.Mtim))
|
||||
}
|
||||
d.ctime.Store(dentryTimestampFromUnix(stat.Ctim))
|
||||
d.nlink.Store(uint32(stat.Nlink))
|
||||
d.updateSizeLocked(uint64(stat.Size))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preconditions: !d.isSynthetic().
|
||||
// Preconditions: d.metadataMu is locked.
|
||||
// +checklocks:d.metadataMu
|
||||
@@ -1115,6 +1186,7 @@ func (d *dentry) statTo(stat *linux.Statx) {
|
||||
stat.DevMinor = d.fs.devMinor
|
||||
}
|
||||
|
||||
// Precondition: fs.renameMu is locked.
|
||||
func (d *dentry) setStat(ctx context.Context, creds *auth.Credentials, opts *vfs.SetStatOptions, mnt *vfs.Mount) error {
|
||||
stat := &opts.Stat
|
||||
if stat.Mask == 0 {
|
||||
@@ -1195,6 +1267,9 @@ func (d *dentry) setStat(ctx context.Context, creds *auth.Credentials, opts *vfs
|
||||
var failureErr error
|
||||
if !d.isSynthetic() {
|
||||
if stat.Mask != 0 {
|
||||
if err := d.prepareSetStat(ctx, stat); err != nil {
|
||||
return err
|
||||
}
|
||||
d.handleMu.RLock()
|
||||
if stat.Mask&linux.STATX_SIZE != 0 {
|
||||
// d.dataMu must be held around the update to both the remote
|
||||
@@ -1832,6 +1907,7 @@ func (d *dentry) removeXattr(ctx context.Context, creds *auth.Credentials, name
|
||||
// Preconditions:
|
||||
// - !d.isSynthetic().
|
||||
// - d.isRegularFile() || d.isDir().
|
||||
// - fs.renameMu is locked.
|
||||
func (d *dentry) ensureSharedHandle(ctx context.Context, read, write, trunc bool) error {
|
||||
// O_TRUNC unconditionally requires us to obtain a new handle (opened with
|
||||
// O_TRUNC).
|
||||
@@ -2093,6 +2169,9 @@ func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linu
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
fs := fd.filesystem()
|
||||
fs.renameMu.RLock()
|
||||
defer fs.renameMu.RUnlock()
|
||||
return fd.dentry().setStat(ctx, auth.CredentialsFromContext(ctx), &opts, fd.vfsfd.Mount())
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ package gofer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/atomicbitops"
|
||||
@@ -29,6 +30,39 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
func (fs *filesystem) handleAnameLisafs(ctx context.Context, rootInode lisafs.Inode) (lisafs.Inode, error) {
|
||||
if fs.opts.aname == "/" {
|
||||
return rootInode, nil
|
||||
}
|
||||
|
||||
// Walk to the attach point from root inode. aname is always absolute.
|
||||
rootFD := fs.client.NewFD(rootInode.ControlFD)
|
||||
status, inodes, err := rootFD.WalkMultiple(ctx, strings.Split(fs.opts.aname, "/")[1:])
|
||||
if err != nil {
|
||||
return lisafs.Inode{}, err
|
||||
}
|
||||
|
||||
// Close all intermediate FDs to the attach point.
|
||||
rootFD.Close(ctx, false /* flush */)
|
||||
numInodes := len(inodes)
|
||||
for i := 0; i < numInodes-1; i++ {
|
||||
curFD := fs.client.NewFD(inodes[i].ControlFD)
|
||||
curFD.Close(ctx, false /* flush */)
|
||||
}
|
||||
|
||||
switch status {
|
||||
case lisafs.WalkSuccess:
|
||||
return inodes[numInodes-1], nil
|
||||
default:
|
||||
if numInodes > 0 {
|
||||
last := fs.client.NewFD(inodes[numInodes-1].ControlFD)
|
||||
last.Close(ctx, false /* flush */)
|
||||
}
|
||||
log.Warningf("initClient failed because walk to attach point %q failed: lisafs.WalkStatus = %v", fs.opts.aname, status)
|
||||
return lisafs.Inode{}, linuxerr.ENOENT
|
||||
}
|
||||
}
|
||||
|
||||
// lisafsDentry is a gofer dentry implementation. It represents a dentry backed
|
||||
// by a lisafs connection.
|
||||
//
|
||||
@@ -139,6 +173,17 @@ func (fs *filesystem) newLisafsDentry(ctx context.Context, ino *lisafs.Inode) (*
|
||||
return &d.dentry, nil
|
||||
}
|
||||
|
||||
func (d *lisafsDentry) openHandle(ctx context.Context, flags uint32) (handle, error) {
|
||||
openFD, hostFD, err := d.controlFD.OpenAt(ctx, flags)
|
||||
if err != nil {
|
||||
return noHandle, err
|
||||
}
|
||||
return handle{
|
||||
fdLisa: d.controlFD.Client().NewFD(openFD),
|
||||
fd: int32(hostFD),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *lisafsDentry) updateHandles(ctx context.Context, h handle, readable, writable bool) {
|
||||
// Switch to new LISAFS FDs. Note that the read, write and mmap host FDs are
|
||||
// updated separately.
|
||||
@@ -242,7 +287,8 @@ func (d *lisafsDentry) getRemoteChild(ctx context.Context, name string) (*dentry
|
||||
// - parent.opMu must be locked.
|
||||
// - parent.isDir().
|
||||
// - !rp.Done().
|
||||
// - dentry at name must not already exist in dentry tree.
|
||||
//
|
||||
// Postcondition: The returned dentry is already cached appropriately.
|
||||
func (d *lisafsDentry) getRemoteChildAndWalkPathLocked(ctx context.Context, rp *vfs.ResolvingPath, ds **[]*dentry) (*dentry, error) {
|
||||
// Walk as much of the path as possible in 1 RPC.
|
||||
// Note that pit is a copy of the iterator that does not affect rp.
|
||||
|
||||
@@ -146,6 +146,11 @@ func (d *dentry) afterLoad() {
|
||||
}
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (d *directfsDentry) afterLoad() {
|
||||
d.controlFD = -1
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (d *dentryPlatformFile) afterLoad() {
|
||||
if d.hostFileMapper.IsInited() {
|
||||
|
||||
@@ -28,6 +28,15 @@ func (d *dentry) isSocket() bool {
|
||||
return d.fileType() == linux.S_IFSOCK
|
||||
}
|
||||
|
||||
func isSocketTypeSupported(sockType linux.SockType) bool {
|
||||
switch sockType {
|
||||
case unix.SOCK_STREAM, unix.SOCK_DGRAM, unix.SOCK_SEQPACKET:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// endpoint is a Gofer-backed transport.BoundEndpoint.
|
||||
//
|
||||
// An endpoint's lifetime is the time between when filesystem.BoundEndpointAt()
|
||||
@@ -94,7 +103,9 @@ func (e *endpoint) UnidirectionalConnect(ctx context.Context) (transport.Connect
|
||||
}
|
||||
|
||||
func (e *endpoint) newConnectedEndpoint(ctx context.Context, sockType linux.SockType, queue *waiter.Queue) (*transport.SCMConnectedEndpoint, *syserr.Error) {
|
||||
e.dentry.fs.renameMu.RLock()
|
||||
hostSockFD, err := e.dentry.connect(ctx, sockType)
|
||||
e.dentry.fs.renameMu.RUnlock()
|
||||
if err != nil {
|
||||
return nil, syserr.ErrConnectionRefused
|
||||
}
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
package gofer
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
func dentryTimestamp(t linux.StatxTimestamp) int64 {
|
||||
return t.Sec*1e9 + int64(t.Nsec)
|
||||
return t.ToNsec()
|
||||
}
|
||||
|
||||
func dentryTimestampFromUnix(t unix.Timespec) int64 {
|
||||
return dentryTimestamp(linux.StatxTimestamp{Sec: t.Sec, Nsec: uint32(t.Nsec)})
|
||||
}
|
||||
|
||||
// Preconditions: d.cachedMetadataAuthoritative() == true.
|
||||
|
||||
+1
-4
@@ -5,9 +5,6 @@ package(licenses = ["notice"])
|
||||
go_library(
|
||||
name = "fsgofer",
|
||||
srcs = [
|
||||
"fsgofer_amd64_unsafe.go",
|
||||
"fsgofer_arm64_unsafe.go",
|
||||
"fsgofer_unsafe.go",
|
||||
"lisafs.go",
|
||||
],
|
||||
visibility = ["//runsc:__subpackages__"],
|
||||
@@ -16,10 +13,10 @@ go_library(
|
||||
"//pkg/atomicbitops",
|
||||
"//pkg/cleanup",
|
||||
"//pkg/fd",
|
||||
"//pkg/fsutil",
|
||||
"//pkg/lisafs",
|
||||
"//pkg/log",
|
||||
"//pkg/marshal/primitive",
|
||||
"//pkg/syserr",
|
||||
"//runsc/config",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
|
||||
+15
-9
@@ -30,12 +30,15 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/atomicbitops"
|
||||
"gvisor.dev/gvisor/pkg/cleanup"
|
||||
rwfd "gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/lisafs"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
||||
"gvisor.dev/gvisor/runsc/config"
|
||||
)
|
||||
|
||||
// LINT.IfChange
|
||||
|
||||
const (
|
||||
openFlags = unix.O_NOFOLLOW | unix.O_CLOEXEC
|
||||
|
||||
@@ -332,7 +335,7 @@ func (fd *controlFDLisa) SetStat(stat lisafs.SetStatReq) (failureMask uint32, fa
|
||||
symlinkPath := fd.Node().FilePath()
|
||||
parent, err := unix.Open(path.Dir(symlinkPath), openFlags|unix.O_PATH, 0)
|
||||
if err == nil {
|
||||
err = utimensat(parent, path.Base(symlinkPath), utimes, unix.AT_SYMLINK_NOFOLLOW)
|
||||
err = fsutil.Utimensat(parent, path.Base(symlinkPath), utimes, unix.AT_SYMLINK_NOFOLLOW)
|
||||
unix.Close(parent)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -352,7 +355,7 @@ func (fd *controlFDLisa) SetStat(stat lisafs.SetStatReq) (failureMask uint32, fa
|
||||
}
|
||||
// Directories and regular files can operate directly on the fd
|
||||
// using empty name.
|
||||
err := utimensat(hostFD, "", utimes, 0)
|
||||
err := fsutil.Utimensat(hostFD, "", utimes, 0)
|
||||
if err != nil {
|
||||
log.Warningf("SetStat utimens failed %q, err: %v", fd.Node().FilePath(), err)
|
||||
failureMask |= (stat.Mask & (unix.STATX_ATIME | unix.STATX_MTIME))
|
||||
@@ -749,7 +752,7 @@ func (fd *controlFDLisa) Connect(sockType uint32) (int, error) {
|
||||
// hostPath in our sockaddr. We'd need to redirect through a shorter path
|
||||
// in order to actually connect to this socket.
|
||||
hostPath := fd.Node().FilePath()
|
||||
if len(hostPath) >= unixPathMax {
|
||||
if len(hostPath) >= linux.UnixPathMax {
|
||||
return -1, unix.EINVAL
|
||||
}
|
||||
|
||||
@@ -784,7 +787,7 @@ func (fd *controlFDLisa) BindAt(name string, sockType uint32, mode linux.FileMod
|
||||
// mappings, the app path may have fit in the sockaddr, but we can't fit
|
||||
// hostPath in our sockaddr. We'd need to redirect through a shorter path
|
||||
// in order to actually connect to this socket.
|
||||
if len(socketPath) >= unixPathMax {
|
||||
if len(socketPath) >= linux.UnixPathMax {
|
||||
log.Warningf("BindAt called with name too long: %q (len=%d)", socketPath, len(socketPath))
|
||||
return nil, linux.Statx{}, nil, -1, unix.EINVAL
|
||||
}
|
||||
@@ -861,7 +864,7 @@ func (fd *controlFDLisa) Unlink(name string, flags uint32) error {
|
||||
|
||||
// RenameAt implements lisafs.ControlFDImpl.RenameAt.
|
||||
func (fd *controlFDLisa) RenameAt(oldName string, newDir lisafs.ControlFDImpl, newName string) error {
|
||||
return renameat(fd.hostFD, oldName, newDir.(*controlFDLisa).hostFD, newName)
|
||||
return fsutil.RenameAt(fd.hostFD, oldName, newDir.(*controlFDLisa).hostFD, newName)
|
||||
}
|
||||
|
||||
// Renamed implements lisafs.ControlFDImpl.Renamed.
|
||||
@@ -977,7 +980,7 @@ func (fd *openFDLisa) Getdent64(count uint32, seek0 bool, recordDirent func(lisa
|
||||
}
|
||||
n, err := unix.Getdents(fd.hostFD, direntsBuf[:bufEnd])
|
||||
if err != nil {
|
||||
if err == unix.EINVAL && bufEnd < unixDirentMaxSize {
|
||||
if err == unix.EINVAL && bufEnd < fsutil.UnixDirentMaxSize {
|
||||
// getdents64(2) returns EINVAL is returned when the result
|
||||
// buffer is too small. If bufEnd is smaller than the max
|
||||
// size of unix.Dirent, then just break here to return all
|
||||
@@ -990,7 +993,7 @@ func (fd *openFDLisa) Getdent64(count uint32, seek0 bool, recordDirent func(lisa
|
||||
break
|
||||
}
|
||||
|
||||
parseDirents(direntsBuf[:n], func(ino uint64, off int64, ftype uint8, name string, reclen uint16) bool {
|
||||
fsutil.ParseDirents(direntsBuf[:n], func(ino uint64, off int64, ftype uint8, name string, reclen uint16) bool {
|
||||
dirent := lisafs.Dirent64{
|
||||
Ino: primitive.Uint64(ino),
|
||||
Off: primitive.Uint64(off),
|
||||
@@ -999,8 +1002,9 @@ func (fd *openFDLisa) Getdent64(count uint32, seek0 bool, recordDirent func(lisa
|
||||
}
|
||||
|
||||
// The client also wants the device ID, which annoyingly incurs an
|
||||
// additional syscall per dirent. Live with it.
|
||||
stat, err := statAt(fd.hostFD, name)
|
||||
// additional syscall per dirent.
|
||||
// TODO(gvisor.dev/issue/6665): Get rid of per-dirent stat.
|
||||
stat, err := fsutil.StatAt(fd.hostFD, name)
|
||||
if err != nil {
|
||||
log.Warningf("Getdent64: skipping file %q with failed stat, err: %v", path.Join(fd.ControlFD().FD().Node().FilePath(), name), err)
|
||||
return true
|
||||
@@ -1177,3 +1181,5 @@ func extractErrno(err error) unix.Errno {
|
||||
log.Debugf("Unknown error: %v, defaulting to EIO", err)
|
||||
return unix.EIO
|
||||
}
|
||||
|
||||
// LINT.ThenChange(../../pkg/sentry/fsimpl/gofer/directfs_dentry.go)
|
||||
|
||||
Reference in New Issue
Block a user