mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
- gofer client: Added an xattr cache on the inode struct. In case the gofer mount being used is exclusive (cachedMetadataAuthoritative() == true), we cache positive and negative xattr queries. - Subsequently, we now hold metadataMu for the entire duration of xattr operations. This is important to avoid stale updates in the xattr cache due to concurrent xattr operations. This is the same approach we take for maintaining other inode metadata in the inode struct. - Added support for setxattr/removexattr/listxattr in runsc/fsgofer and gofer client (when using directfs). Consequently the seccomp filters for sandbox and gofer processes were expanded to allow these syscalls. - Added xattr syscall tests to //test/syscalls/BUILD so they are run on BuildKite on presubmits. (Also fixed up some tests.) A note about performance: The default rootfs configuration for runsc enables rootfs overlay. In that, the gofer mount is an exclusive mount on the lower layer. fsimpl/overlay issues a lot of getxattr calls on the lower layer to check for opaque directory xattr. However, as before, the "trusted" xattr namespace is not supported. So all those calls continue to fail with EOPNOTSUPP. So this should not regress rootfs overlay performance. When using an overlay where gofer is the upper layer or when "userxattr" mount option is in effect, fsimpl/overlay will similarly issue a lot of getxattr calls using the "user" xattr namespace. This will be allowed by checkXattrPermissions(). To optimize the common case where directories are not opaque, we specially track `overlayOpaqueNegative` in `xattrCache`. This avoids a map allocation during xattrCache.addNegative(). Updates #12475 Updates #12828 PiperOrigin-RevId: 915097116
658 lines
21 KiB
Go
658 lines
21 KiB
Go
// 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 gofer
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/atomicbitops"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/fsutil"
|
|
"gvisor.dev/gvisor/pkg/lisafs"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
)
|
|
|
|
// We do *not* define an interface for dentry.impl because making interface
|
|
// method calls is almost 2.5x slower than calling the same method on a
|
|
// concrete type. Instead, we use type assertions in switch statements. The
|
|
// asserted type is a concrete dentry implementation and methods are called
|
|
// directly on the concrete type. This helps in the following ways:
|
|
//
|
|
// 1. This is faster because concrete type assertion just needs to compare the
|
|
// itab pointer in the interface value to a constant which is relatively
|
|
// cheap. Benchmarking showed that such type switches don't add almost any
|
|
// overhead.
|
|
// 2. Passing any pointer to an interface method immediately causes the pointed
|
|
// object to escape to heap. Making concrete method calls allows escape
|
|
// analysis to proceed as usual and avoids heap allocations.
|
|
//
|
|
// Also note that the default case in these type switch statements panics. We
|
|
// do not do panic(fmt.Sprintf("... %T", i.impl)) because somehow it adds a lot
|
|
// of overhead to the type switch. So instead we panic with a constant string.
|
|
|
|
// Precondition: i.handleMu must be locked.
|
|
func (i *inode) isReadHandleOk() bool {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.readFDLisa.Ok()
|
|
case *directfsInode:
|
|
return it.readFD.RacyLoad() >= 0
|
|
case nil: // synthetic inode
|
|
return false
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: i.handleMu must be locked.
|
|
func (i *inode) isWriteHandleOk() bool {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.writeFDLisa.Ok()
|
|
case *directfsInode:
|
|
return i.writeFD.RacyLoad() >= 0
|
|
case nil: // synthetic inode
|
|
return false
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: i.handleMu must be locked.
|
|
func (i *inode) readHandle() handle {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return handle{
|
|
fdLisa: it.readFDLisa,
|
|
fd: i.readFD.RacyLoad(),
|
|
}
|
|
case *directfsInode:
|
|
return handle{fd: i.readFD.RacyLoad()}
|
|
case nil: // synthetic dentry
|
|
return noHandle
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: i.handleMu must be locked.
|
|
func (i *inode) writeHandle() handle {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return handle{
|
|
fdLisa: it.writeFDLisa,
|
|
fd: i.writeFD.RacyLoad(),
|
|
}
|
|
case *directfsInode:
|
|
return handle{fd: i.writeFD.RacyLoad()}
|
|
case nil: // synthetic inode
|
|
return noHandle
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
case read && write:
|
|
flags = unix.O_RDWR
|
|
case read:
|
|
flags = unix.O_RDONLY
|
|
case write:
|
|
flags = unix.O_WRONLY
|
|
default:
|
|
log.Debugf("openHandle called with read = write = false. Falling back to read only FD.")
|
|
}
|
|
if trunc {
|
|
flags |= unix.O_TRUNC
|
|
}
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.openHandle(ctx, flags)
|
|
case *directfsInode:
|
|
return it.openHandle(ctx, flags, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - i.handleMu must be locked.
|
|
// - !d.isSynthetic().
|
|
func (i *inode) updateHandles(ctx context.Context, h handle, readable, writable bool) {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
it.updateHandles(ctx, h, readable, writable)
|
|
case *directfsInode:
|
|
// No update needed.
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - i.handleMu must be locked.
|
|
// - !d.isSynthetic().
|
|
func (i *inode) closeHostFDs() {
|
|
// Close FDs, other than what is owned by i.mmapFile.
|
|
// We can use RacyLoad() because i.handleMu is locked.
|
|
realMmapFD := i.mmapFile.FD()
|
|
readFD := int(i.readFD.RacyLoad())
|
|
if readFD >= 0 && readFD != realMmapFD {
|
|
_ = unix.Close(readFD)
|
|
}
|
|
if writeFD := int(i.writeFD.RacyLoad()); writeFD >= 0 && readFD != writeFD && writeFD != realMmapFD {
|
|
_ = unix.Close(int(i.writeFD.RacyLoad()))
|
|
}
|
|
i.readFD = atomicbitops.FromInt32(-1)
|
|
i.writeFD = atomicbitops.FromInt32(-1)
|
|
i.mmapFD = atomicbitops.FromInt32(-1)
|
|
|
|
i.mmapFile.MappableRelease() // eventually closes realMmapFD
|
|
|
|
switch it := i.impl.(type) {
|
|
case *directfsInode:
|
|
if it.controlFD >= 0 {
|
|
_ = unix.Close(it.controlFD)
|
|
it.controlFD = -1
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateMetadataLocked updates the dentry's metadata fields. The h parameter
|
|
// is optional. If it is not provided, an appropriate FD should be chosen to
|
|
// stat the remote file.
|
|
//
|
|
// Preconditions:
|
|
// - !d.isSynthetic().
|
|
// - i.metadataMu is locked.
|
|
//
|
|
// +checklocks:i.metadataMu
|
|
func (i *inode) updateMetadataLocked(ctx context.Context, h handle) error {
|
|
// Need checklocksforce below because checklocks has no way of knowing that
|
|
// i.impl.(*dentryImpl).dentry == d. It can't know that the right metadataMu
|
|
// is already locked.
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.updateMetadataLocked(ctx, h) // +checklocksforce: acquired by precondition.
|
|
case *directfsInode:
|
|
return it.updateMetadataLocked(h) // +checklocksforce: acquired by precondition.
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - !d.isSynthetic().
|
|
// - fs.renameMu is locked.
|
|
func (d *dentry) prepareSetStat(ctx context.Context, stat *linux.Statx) error {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
// Nothing to be done.
|
|
return nil
|
|
case *directfsInode:
|
|
return it.prepareSetStat(ctx, stat, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: fs.renameMu is locked if d is a socket.
|
|
func (d *dentry) chmod(ctx context.Context, mode uint16) error {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return chmod(ctx, it.controlFD, mode)
|
|
case *directfsInode:
|
|
return it.chmod(ctx, mode, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - !d.isSynthetic().
|
|
// - i.handleMu is locked.
|
|
// - fs.renameMu is locked.
|
|
func (d *dentry) setStatLocked(ctx context.Context, stat *linux.Statx) (uint32, error, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.SetStat(ctx, stat)
|
|
case *directfsInode:
|
|
failureMask, failureErr := it.setStatLocked(ctx, stat, d)
|
|
return failureMask, failureErr, nil
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: i.handleMu must be locked.
|
|
func (i *inode) destroyImpl(ctx context.Context, d *dentry) {
|
|
switch i := i.impl.(type) {
|
|
case *lisafsInode:
|
|
i.destroy(ctx, d)
|
|
case *directfsInode:
|
|
i.destroy(ctx)
|
|
case nil: // synthetic dentry
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Postcondition: Caller must do dentry caching appropriately.
|
|
//
|
|
// +checklocksread:d.opMu
|
|
func (d *dentry) getRemoteChild(ctx context.Context, name string) (*dentry, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.getRemoteChild(ctx, name)
|
|
case *directfsInode:
|
|
return it.getHostChild(name)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - fs.renameMu must be locked.
|
|
// - parent.opMu must be locked for reading.
|
|
// - parent.isDir().
|
|
// - !rp.Done() && rp.Component() is not "." or "..".
|
|
//
|
|
// Postcondition: The returned dentry is already cached appropriately.
|
|
//
|
|
// +checklocksread:d.opMu
|
|
func (i *inode) getRemoteChildAndWalkPathLocked(ctx context.Context, rp resolvingPath, ds **[]*dentry, d *dentry) (*dentry, error) {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.getRemoteChildAndWalkPathLocked(ctx, rp, ds, d)
|
|
case *directfsInode:
|
|
// We need to check for races because opMu is read locked which allows
|
|
// concurrent walks to occur.
|
|
return i.fs.getRemoteChildLocked(ctx, d, rp.Component(), true /* checkForRace */, ds)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) listXattrImpl(ctx context.Context, size uint64) ([]string, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.ListXattr(ctx, size)
|
|
case *directfsInode:
|
|
return it.listXattr(ctx, size, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) getXattrImpl(ctx context.Context, opts *vfs.GetXattrOptions) (string, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.GetXattr(ctx, opts.Name, opts.Size)
|
|
case *directfsInode:
|
|
return it.getXattr(ctx, opts.Name, opts.Size, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) setXattrImpl(ctx context.Context, opts *vfs.SetXattrOptions) error {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.SetXattr(ctx, opts.Name, opts.Value, opts.Flags)
|
|
case *directfsInode:
|
|
return it.setXattr(ctx, opts, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) removeXattrImpl(ctx context.Context, name string) error {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.RemoveXattr(ctx, name)
|
|
case *directfsInode:
|
|
return it.removeXattr(ctx, name, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) mknod(ctx context.Context, name string, creds *auth.Credentials, opts *vfs.MknodOptions) (*dentry, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.mknod(ctx, name, creds, opts)
|
|
case *directfsInode:
|
|
return it.mknod(ctx, name, creds, opts, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - !d.isSynthetic().
|
|
// - !target.isSynthetic().
|
|
// - i.fs.renameMu must be locked.
|
|
func (d *dentry) link(ctx context.Context, target *dentry, name string) (*dentry, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.link(ctx, target.inode.impl.(*lisafsInode), name)
|
|
case *directfsInode:
|
|
return it.link(target, name, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) mkdir(ctx context.Context, name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.mkdir(ctx, name, mode, uid, gid, createDentry)
|
|
case *directfsInode:
|
|
return it.mkdir(name, mode, uid, gid, createDentry, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) symlink(ctx context.Context, name, target string, creds *auth.Credentials) (*dentry, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.symlink(ctx, name, target, creds)
|
|
case *directfsInode:
|
|
return it.symlink(name, target, creds, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (d *dentry) openCreate(ctx context.Context, name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, handle, error) {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.openCreate(ctx, name, accessFlags, mode, uid, gid, createDentry)
|
|
case *directfsInode:
|
|
return it.openCreate(name, accessFlags, mode, uid, gid, createDentry, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - d.isDir().
|
|
// - i.handleMu must be locked.
|
|
// - !d.isSynthetic().
|
|
func (d *dentry) getDirentsLocked(ctx context.Context, recordDirent func(name string, key inoKey, dType uint8)) error {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.getDirentsLocked(ctx, recordDirent)
|
|
case *directfsInode:
|
|
return it.getDirentsLocked(recordDirent, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (i *inode) flush(ctx context.Context) error {
|
|
i.handleMu.RLock()
|
|
defer i.handleMu.RUnlock()
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return flush(ctx, it.writeFDLisa)
|
|
case *directfsInode:
|
|
// Nothing to do here.
|
|
return nil
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (i *inode) allocate(ctx context.Context, mode, offset, length uint64) error {
|
|
i.handleMu.RLock()
|
|
defer i.handleMu.RUnlock()
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.writeFDLisa.Allocate(ctx, mode, offset, length)
|
|
case *directfsInode:
|
|
return unix.Fallocate(int(i.writeFD.RacyLoad()), uint32(mode), int64(offset), int64(length))
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - !d.isSynthetic().
|
|
// - fs.renameMu is locked.
|
|
func (d *dentry) connect(ctx context.Context, sockType linux.SockType) (int, error) {
|
|
creds := auth.CredentialsOrNilFromContext(ctx)
|
|
euid := lisafs.NoUID
|
|
egid := lisafs.NoGID
|
|
if creds != nil {
|
|
euid = lisafs.UID(creds.EffectiveKUID)
|
|
egid = lisafs.GID(creds.EffectiveKGID)
|
|
}
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.Connect(ctx, sockType, euid, egid)
|
|
case *directfsInode:
|
|
return it.connect(ctx, sockType, euid, egid, d)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (i *inode) readlinkImpl(ctx context.Context) (string, error) {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.ReadLinkAt(ctx)
|
|
case *directfsInode:
|
|
return it.readlink()
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (i *inode) unlink(ctx context.Context, name string, flags uint32) error {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.UnlinkAt(ctx, name, flags)
|
|
case *directfsInode:
|
|
return unix.Unlinkat(it.controlFD, name, int(flags))
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (i *inode) rename(ctx context.Context, oldName string, newParent *dentry, newName string, flags uint32) error {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.controlFD.RenameAt(ctx, oldName, newParent.inode.impl.(*lisafsInode).controlFD.ID(), newName, flags)
|
|
case *directfsInode:
|
|
return fsutil.RenameAt2(it.controlFD, oldName, newParent.inode.impl.(*directfsInode).controlFD, newName, flags)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: !d.isSynthetic().
|
|
func (i *inode) statfs(ctx context.Context) (linux.Statfs, error) {
|
|
switch it := i.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.statfs(ctx)
|
|
case *directfsInode:
|
|
return it.statfs()
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
func (fs *filesystem) restoreRoot(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
|
|
rootInode, rootHostFD, err := fs.initClientAndGetRoot(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize client and get root in mount %q: %w", fs.iopts.UniqueID, err)
|
|
}
|
|
|
|
// The root is always non-synthetic.
|
|
switch it := fs.root.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return it.restoreInode(ctx, &rootInode, opts, fs.root)
|
|
case *directfsInode:
|
|
it.controlFDLisa = fs.client.NewFD(rootInode.ControlFD)
|
|
return it.restoreFile(ctx, rootHostFD, opts, fs.root)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Preconditions:
|
|
// - !d.isSynthetic().
|
|
// - d.parent != nil and has been restored.
|
|
func (d *dentry) restoreFile(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
|
|
switch it := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
// With inode sharing, we may have multiple references to the same inode.
|
|
// There is a possibility we try to restore the same inode multiple times.
|
|
// If we already have a controlFD, we don't need to do anything.
|
|
if it.controlFD.Ok() {
|
|
return nil
|
|
}
|
|
controlFD := d.parent.Load().inode.impl.(*lisafsInode).controlFD
|
|
inode, err := controlFD.Walk(ctx, d.name)
|
|
if err != nil {
|
|
if !d.isDir() || !d.forMountpoint {
|
|
return fmt.Errorf("failed to walk %q of type %#o in mount %q: %w", genericDebugPathname(it.fs, d), linux.FileMode(it.inode.fileType()), it.fs.iopts.UniqueID, err)
|
|
}
|
|
|
|
// Recreate directories that were created during volume mounting, since
|
|
// during restore we don't attempt to remount them.
|
|
inode, err = controlFD.MkdirAt(ctx, d.name, linux.FileMode(it.mode.Load()), lisafs.UID(it.uid.Load()), lisafs.GID(it.gid.Load()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create mountpoint directory at %q in mount %q: %w", genericDebugPathname(it.fs, d), it.fs.iopts.UniqueID, err)
|
|
}
|
|
}
|
|
return it.restoreInode(ctx, &inode, opts, d)
|
|
|
|
case *directfsInode:
|
|
// With inode sharing, we may have multiple references to the same inode.
|
|
// There is a possibility we try to restore the same inode multiple times.
|
|
// If we already have a controlFD, we don't need to do anything.
|
|
if it.controlFD >= 0 {
|
|
return nil
|
|
}
|
|
controlFD := d.parent.Load().inode.impl.(*directfsInode).controlFD
|
|
childFD, err := tryOpen(func(flags int) (int, error) {
|
|
n, err := unix.Openat(controlFD, d.name, flags, 0)
|
|
return n, err
|
|
})
|
|
if err != nil {
|
|
if !d.isDir() || !d.forMountpoint {
|
|
return fmt.Errorf("failed to walk %q of type %#o in mount %q: %w", genericDebugPathname(it.fs, d), linux.FileMode(it.inode.fileType()), it.fs.iopts.UniqueID, err)
|
|
}
|
|
|
|
// Recreate directories that were created during volume mounting, since
|
|
// during restore we don't attempt to remount them.
|
|
if err := unix.Mkdirat(controlFD, d.name, it.mode.Load()); err != nil {
|
|
return fmt.Errorf("failed to create mountpoint directory at %q in mount %q: %w", genericDebugPathname(it.fs, d), it.fs.iopts.UniqueID, err)
|
|
}
|
|
|
|
// Try again...
|
|
childFD, err = tryOpen(func(flags int) (int, error) {
|
|
return unix.Openat(controlFD, d.name, flags, 0)
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open %q in mount %q: %w", genericDebugPathname(it.fs, d), it.fs.iopts.UniqueID, err)
|
|
}
|
|
}
|
|
return it.restoreFile(ctx, childFD, opts, d)
|
|
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// Precondition: d.handleMu is read locked.
|
|
func (d *dentry) readHandleForDeleted(ctx context.Context) (handle, error) {
|
|
if d.inode.isReadHandleOk() {
|
|
return d.inode.readHandle(), nil
|
|
}
|
|
switch dt := d.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
// ensureSharedHandle locks handleMu for write. Unlock it temporarily.
|
|
d.inode.handleMu.RUnlock()
|
|
err := d.ensureSharedHandle(ctx, true /* read */, false /* write */, false /* trunc */)
|
|
d.inode.handleMu.RLock()
|
|
if err != nil {
|
|
return handle{}, fmt.Errorf("failed to open read handle: %w", err)
|
|
}
|
|
return d.inode.readHandle(), nil
|
|
case *directfsInode:
|
|
// The sentry does not have access to any procfs mount which it could use
|
|
// to re-open dt.controlFD with a different mode (via /proc/self/fd/). The
|
|
// file is unlinked, so we can't use openat(parent.controlFD, name) either.
|
|
// dt.controlFD must be a read-only FD (see tryOpen() documentation). Just
|
|
// seek the control FD to 0 and return it. The control FD is not used for
|
|
// reading by the sentry, so this should be safe.
|
|
// TODO(b/431481259): Use dentry.ensureSharedHandle() here as well.
|
|
if _, err := unix.Seek(dt.controlFD, 0, unix.SEEK_SET); err != nil {
|
|
return handle{}, fmt.Errorf("failed to seek control FD to 0: %w", err)
|
|
}
|
|
return handle{fd: int32(dt.controlFD)}, nil
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|
|
|
|
// doRevalidation calls into r.start's dentry implementation to perform
|
|
// revalidation on all the dentries contained in r.
|
|
//
|
|
// Preconditions:
|
|
// - fs.renameMu must be locked.
|
|
// - InteropModeShared is in effect.
|
|
func (r *revalidateState) doRevalidation(ctx context.Context, vfsObj *vfs.VirtualFilesystem, ds **[]*dentry) error {
|
|
// Skip synthetic dentries because there is no actual implementation that can
|
|
// be used to walk the remote filesystem. A start dentry cannot be replaced.
|
|
if r.start.inode.isSynthetic() {
|
|
return nil
|
|
}
|
|
switch r.start.inode.impl.(type) {
|
|
case *lisafsInode:
|
|
return doRevalidationLisafs(ctx, vfsObj, r, ds)
|
|
case *directfsInode:
|
|
return doRevalidationDirectfs(ctx, vfsObj, r, ds)
|
|
default:
|
|
panic("unknown inode implementation")
|
|
}
|
|
}
|