mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
renameat2(2) support was added in 61f761343f ("Implement renameat2."),
but only for the gofer filesystem; tmpfs and overlayfs still return
EINVAL for RENAME_EXCHANGE.
Implement RENAME_EXCHANGE in tmpfs by swapping the two dentries between
their parent directories. When the exchanged files differ in type, the
parents' link counts (for "..") and the exchanged directory's reference
on its parent are transferred.
Implement RENAME_EXCHANGE in overlayfs by copying up the destination
file (and its descendants, if it is a directory) in addition to the
source, and then exchanging both files on the upper layer. Unlike a
regular rename, no whiteout is created at the source location, since
both locations remain occupied. Exchanged directories are marked opaque
at their new locations to prevent merging with lower layer directories
there.
RENAME_EXCHANGE permits the two files to differ in type and does not
require a directory being exchanged to be empty, but exchanging a file
with an ancestor directory must fail with EINVAL; see Linux's
fs/namei.c:__start_renaming(). The gofer filesystem previously required
both files to have the same type; lift that restriction, and update
parent link counts when files of different types are exchanged across
directories.
The RENAME_EXCHANGE syscall tests no longer accept EINVAL; they are
skipped where the flag remains unsupported, i.e. on FUSE and on gVisor
kernels other than runsc. New tests cover mixed-type exchanges and
exchanges with ancestor directories.
Fixes #7895
2007 lines
64 KiB
Go
2007 lines
64 KiB
Go
// Copyright 2019 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package gofer
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
|
|
"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/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/fspath"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/pkg/refs"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsimpl/host"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsmetric"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/pipe"
|
|
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
)
|
|
|
|
// Sync implements vfs.FilesystemImpl.Sync.
|
|
func (fs *filesystem) Sync(ctx context.Context) error {
|
|
// Snapshot current syncable dentries and special file FDs.
|
|
fs.syncMu.Lock()
|
|
ds := make([]*dentry, 0, fs.syncableDentries.Len())
|
|
for elem := fs.syncableDentries.Front(); elem != nil; elem = elem.Next() {
|
|
ds = append(ds, elem.d)
|
|
}
|
|
sffds := make([]*specialFileFD, 0, fs.specialFileFDs.Len())
|
|
for sffd := fs.specialFileFDs.Front(); sffd != nil; sffd = sffd.Next() {
|
|
sffds = append(sffds, sffd)
|
|
}
|
|
fs.syncMu.Unlock()
|
|
|
|
// Return the first error we encounter, but sync everything we can
|
|
// regardless.
|
|
var retErr error
|
|
|
|
// Note that lisafs is capable of batching FSync RPCs. However, we can not
|
|
// batch all the FDIDs to be synced from ds and sffds. Because the error
|
|
// handling varies based on file type. FSync errors are only considered for
|
|
// regular file FDIDs that were opened for writing. We could do individual
|
|
// RPCs for such FDIDs and batch the rest, but it increases code complexity
|
|
// substantially. We could implement it in the future if need be.
|
|
|
|
// Sync syncable dentries.
|
|
for _, d := range ds {
|
|
if err := d.syncCachedFile(ctx, true /* forFilesystemSync */, false /* dataOnly */); err != nil {
|
|
ctx.Infof("gofer.filesystem.Sync: dentry.syncCachedFile failed: %v", err)
|
|
if retErr == nil {
|
|
retErr = err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sync special files, which may be writable but do not use dentry shared
|
|
// handles (so they won't be synced by the above).
|
|
for _, sffd := range sffds {
|
|
if err := sffd.sync(ctx, true /* forFilesystemSync */, false /* dataOnly */); err != nil {
|
|
ctx.Infof("gofer.filesystem.Sync: specialFileFD.sync failed: %v", err)
|
|
if retErr == nil {
|
|
retErr = err
|
|
}
|
|
}
|
|
}
|
|
|
|
return retErr
|
|
}
|
|
|
|
// MaxFilenameLen is the maximum length of a filename. This is dictated by 9P's
|
|
// encoding of strings, which uses 2 bytes for the length prefix.
|
|
const MaxFilenameLen = (1 << 16) - 1
|
|
|
|
// dentrySlicePool is a pool of *[]*dentry used to store dentries for which
|
|
// dentry.checkCachingLocked() must be called. The pool holds pointers to
|
|
// slices because Go lacks generics, so sync.Pool operates on any, so
|
|
// every call to (what should be) sync.Pool<[]*dentry>.Put() allocates a copy
|
|
// of the slice header on the heap.
|
|
var dentrySlicePool = sync.Pool{
|
|
New: func() any {
|
|
ds := make([]*dentry, 0, 4) // arbitrary non-zero initial capacity
|
|
return &ds
|
|
},
|
|
}
|
|
|
|
func appendDentry(ds *[]*dentry, d *dentry) *[]*dentry {
|
|
if ds == nil {
|
|
ds = dentrySlicePool.Get().(*[]*dentry)
|
|
}
|
|
*ds = append(*ds, d)
|
|
return ds
|
|
}
|
|
|
|
// Precondition: !parent.inode.isSynthetic() && !child.inode.isSynthetic().
|
|
func appendNewChildDentry(ds **[]*dentry, parent *dentry, child *dentry) {
|
|
// The new child was added to parent and took a ref on the parent (hence
|
|
// parent can be removed from cache). A new child has 0 refs for now. So
|
|
// checkCachingLocked() should be called on both. Call it first on the parent
|
|
// as it may create space in the cache for child to be inserted - hence
|
|
// avoiding a cache eviction.
|
|
*ds = appendDentry(*ds, parent)
|
|
*ds = appendDentry(*ds, child)
|
|
}
|
|
|
|
// Preconditions: ds != nil.
|
|
func putDentrySlice(ds *[]*dentry) {
|
|
// Allow dentries to be GC'd.
|
|
for i := range *ds {
|
|
(*ds)[i] = nil
|
|
}
|
|
*ds = (*ds)[:0]
|
|
dentrySlicePool.Put(ds)
|
|
}
|
|
|
|
// renameMuRUnlockAndCheckCaching calls fs.renameMu.RUnlock(), then calls
|
|
// dentry.checkCachingLocked on all dentries in *dsp with fs.renameMu locked
|
|
// for writing.
|
|
//
|
|
// dsp is a pointer-to-pointer since defer evaluates its arguments immediately,
|
|
// but dentry slices are allocated lazily, and it's much easier to say "defer
|
|
// fs.renameMuRUnlockAndCheckCaching(&ds)" than "defer func() {
|
|
// fs.renameMuRUnlockAndCheckCaching(ds) }()" to work around this.
|
|
// +checklocksreleaseread:fs.renameMu
|
|
func (fs *filesystem) renameMuRUnlockAndCheckCaching(ctx context.Context, dsp **[]*dentry) {
|
|
fs.renameMu.RUnlock()
|
|
if *dsp == nil {
|
|
return
|
|
}
|
|
ds := **dsp
|
|
for _, d := range ds {
|
|
d.checkCachingLocked(ctx, false /* renameMuWriteLocked */)
|
|
}
|
|
putDentrySlice(*dsp)
|
|
}
|
|
|
|
// +checklocksrelease:fs.renameMu
|
|
func (fs *filesystem) renameMuUnlockAndCheckCaching(ctx context.Context, ds **[]*dentry) {
|
|
if *ds == nil {
|
|
fs.renameMu.Unlock()
|
|
return
|
|
}
|
|
for _, d := range **ds {
|
|
d.checkCachingLocked(ctx, true /* renameMuWriteLocked */)
|
|
}
|
|
fs.renameMu.Unlock()
|
|
putDentrySlice(*ds)
|
|
}
|
|
|
|
// stepLocked resolves rp.Component() to an existing file, starting from the
|
|
// given directory.
|
|
//
|
|
// Dentries which may become cached as a result of the traversal are appended
|
|
// to *ds.
|
|
//
|
|
// Preconditions:
|
|
// - fs.renameMu must be locked.
|
|
// - d.opMu must be locked for reading.
|
|
// - !rp.Done().
|
|
// - If !d.inode.cachedMetadataAuthoritative(), then d and all children that are
|
|
// part of rp must have been revalidated.
|
|
//
|
|
// +checklocksread:d.opMu
|
|
func (fs *filesystem) stepLocked(ctx context.Context, rp resolvingPath, d *dentry, mayFollowSymlinks bool, ds **[]*dentry) (*dentry, bool, error) {
|
|
if !d.isDir() {
|
|
return nil, false, linuxerr.ENOTDIR
|
|
}
|
|
if err := d.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
|
|
}
|
|
child, err := fs.getChildAndWalkPathLocked(ctx, d, rp, ds)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if err := rp.CheckMount(ctx, &child.vfsd); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if child.inode.isSymlink() && mayFollowSymlinks && rp.ShouldFollowSymlink() {
|
|
target, err := child.readlink(ctx, rp.Mount())
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
followedSymlink, err := rp.HandleSymlink(target)
|
|
return d, followedSymlink, err
|
|
}
|
|
rp.Advance()
|
|
return child, false, nil
|
|
}
|
|
|
|
// getChildLocked returns a dentry representing the child of parent with the
|
|
// given name. Returns ENOENT if the child doesn't exist.
|
|
//
|
|
// Preconditions:
|
|
// - fs.renameMu must be locked.
|
|
// - parent.opMu must be locked.
|
|
// - parent.isDir().
|
|
// - name is not "." or "..".
|
|
// - parent and the dentry at name have been revalidated.
|
|
//
|
|
// +checklocks:parent.opMu
|
|
func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
|
|
if child, err := parent.getCachedChildLocked(name); child != nil || err != nil {
|
|
return child, err
|
|
}
|
|
// We don't need to check for race here because parent.opMu is held for
|
|
// writing.
|
|
return fs.getRemoteChildLocked(ctx, parent, name, false /* checkForRace */, ds)
|
|
}
|
|
|
|
// getRemoteChildLocked is similar to getChildLocked, with the additional
|
|
// precondition that the child identified by name does not exist in cache.
|
|
//
|
|
// If checkForRace argument is true, then this method will check to see if the
|
|
// call has raced with another getRemoteChild call, and will handle the race if
|
|
// so.
|
|
//
|
|
// Preconditions:
|
|
// - 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)
|
|
// Cache the result appropriately in the dentry tree.
|
|
if err != nil {
|
|
if linuxerr.Equals(linuxerr.ENOENT, err) {
|
|
parent.childrenMu.Lock()
|
|
defer parent.childrenMu.Unlock()
|
|
parent.cacheNegativeLookupLocked(name)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
parent.childrenMu.Lock()
|
|
defer parent.childrenMu.Unlock()
|
|
|
|
if checkForRace {
|
|
// See if we raced with another getRemoteChild call that added
|
|
// to the cache.
|
|
if cachedChild, ok := parent.children[name]; ok && cachedChild != nil {
|
|
// We raced. Destroy our child and return the cached
|
|
// one. This child has no handles, no data, and has not
|
|
// been cached, so destruction is quick and painless.
|
|
destroyInode := false
|
|
fs.inodeMu.Lock()
|
|
child.inode.refs.DecRef(func() {
|
|
destroyInode = true
|
|
if !child.isDir() {
|
|
// Only non-directory inodes are cached in inodeByKey.
|
|
delete(fs.inodeByKey, child.inode.inoKey)
|
|
}
|
|
})
|
|
fs.inodeMu.Unlock()
|
|
child.destroyDisconnected(ctx, destroyInode)
|
|
|
|
// All good. Return the cached child.
|
|
return cachedChild, nil
|
|
}
|
|
// No race, continue with the child we got.
|
|
}
|
|
parent.cacheNewChildLocked(child, name)
|
|
appendNewChildDentry(ds, parent, child)
|
|
return child, nil
|
|
}
|
|
|
|
// getChildAndWalkPathLocked is the same as getChildLocked, except that it
|
|
// may prefetch the entire path represented by rp.
|
|
//
|
|
// +checklocksread:parent.opMu
|
|
func (fs *filesystem) getChildAndWalkPathLocked(ctx context.Context, parent *dentry, rp resolvingPath, ds **[]*dentry) (*dentry, error) {
|
|
if child, err := parent.getCachedChildLocked(rp.Component()); child != nil || err != nil {
|
|
return child, err
|
|
}
|
|
// dentry.inode.getRemoteChildAndWalkPathLocked already handles dentry caching.
|
|
return parent.inode.getRemoteChildAndWalkPathLocked(ctx, rp, ds, parent)
|
|
}
|
|
|
|
// getCachedChildLocked returns a child dentry if it was cached earlier. If no
|
|
// cached child dentry exists, (nil, nil) is returned.
|
|
//
|
|
// Preconditions:
|
|
// - fs.renameMu must be locked.
|
|
// - d.opMu must be locked for reading.
|
|
// - d.isDir().
|
|
// - name is not "." or "..".
|
|
// - d and the dentry at name have been revalidated.
|
|
//
|
|
// +checklocksread:d.opMu
|
|
func (d *dentry) getCachedChildLocked(name string) (*dentry, error) {
|
|
if len(name) > MaxFilenameLen {
|
|
return nil, linuxerr.ENAMETOOLONG
|
|
}
|
|
d.childrenMu.Lock()
|
|
defer d.childrenMu.Unlock()
|
|
if child, ok := d.children[name]; ok || d.inode.isSynthetic() {
|
|
if child == nil {
|
|
return nil, linuxerr.ENOENT
|
|
}
|
|
return child, nil
|
|
}
|
|
|
|
if d.childrenSet != nil {
|
|
// Is the child even there? Don't make RPC if not.
|
|
if _, ok := d.childrenSet[name]; !ok {
|
|
return nil, linuxerr.ENOENT
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// walkParentDirLocked resolves all but the last path component of rp to an
|
|
// existing directory, starting from the given directory (which is usually
|
|
// rp.Start().Impl().(*dentry)). It does not check that the returned directory
|
|
// is searchable by the provider of rp.
|
|
//
|
|
// Preconditions:
|
|
// - fs.renameMu must be locked.
|
|
// - !rp.Done().
|
|
// - If !d.inode.cachedMetadataAuthoritative(), then d's cached metadata must be up
|
|
// to date.
|
|
func (fs *filesystem) walkParentDirLocked(ctx context.Context, vfsRP *vfs.ResolvingPath, d *dentry, ds **[]*dentry) (*dentry, error) {
|
|
rp := resolvingPathParent(vfsRP)
|
|
if err := fs.revalidatePath(ctx, rp, d, ds); err != nil {
|
|
return nil, err
|
|
}
|
|
for !rp.done() {
|
|
d.opMu.RLock()
|
|
next, followedSymlink, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
|
|
d.opMu.RUnlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d = next
|
|
if followedSymlink {
|
|
if err := fs.revalidatePath(ctx, rp, d, ds); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
if !d.isDir() {
|
|
return nil, linuxerr.ENOTDIR
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// resolveLocked resolves rp to an existing file.
|
|
//
|
|
// Preconditions: fs.renameMu must be locked.
|
|
func (fs *filesystem) resolveLocked(ctx context.Context, vfsRP *vfs.ResolvingPath, ds **[]*dentry) (*dentry, error) {
|
|
rp := resolvingPathFull(vfsRP)
|
|
d := rp.Start().Impl().(*dentry)
|
|
if err := fs.revalidatePath(ctx, rp, d, ds); err != nil {
|
|
return nil, err
|
|
}
|
|
for !rp.done() {
|
|
d.opMu.RLock()
|
|
next, followedSymlink, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
|
|
d.opMu.RUnlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d = next
|
|
if followedSymlink {
|
|
if err := fs.revalidatePath(ctx, rp, d, ds); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
if rp.MustBeDir() && !d.isDir() {
|
|
return nil, linuxerr.ENOTDIR
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// doCreateAt checks that creating a file at rp is permitted, then invokes
|
|
// createInRemoteDir (if the parent directory is a real remote directory) or
|
|
// createInSyntheticDir (if the parent directory is synthetic) to do so.
|
|
//
|
|
// Preconditions:
|
|
// - !rp.Done().
|
|
// - For the final path component in rp, !rp.ShouldFollowSymlink().
|
|
func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir bool, createInRemoteDir func(parent *dentry, name string, ds **[]*dentry) (*dentry, error), createInSyntheticDir func(parent *dentry, name string) (*dentry, error)) error {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
start := rp.Start().Impl().(*dentry)
|
|
parent, err := fs.walkParentDirLocked(ctx, rp, start, &ds)
|
|
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 := parent.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
|
return err
|
|
}
|
|
name := rp.Component()
|
|
if name == "." || name == ".." {
|
|
return linuxerr.EEXIST
|
|
}
|
|
if parent.isDeleted() {
|
|
return linuxerr.ENOENT
|
|
}
|
|
if err := fs.revalidateOne(ctx, rp.VirtualFilesystem(), parent, name, &ds); err != nil {
|
|
return err
|
|
}
|
|
|
|
parent.opMu.Lock()
|
|
defer parent.opMu.Unlock()
|
|
|
|
if len(name) > MaxFilenameLen {
|
|
return linuxerr.ENAMETOOLONG
|
|
}
|
|
// Check for existence only if caching information is available. Otherwise,
|
|
// don't check for existence just yet. We will check for existence if the
|
|
// checks for writability fail below. Existence check is done by the creation
|
|
// RPCs themselves.
|
|
parent.childrenMu.Lock()
|
|
if child, ok := parent.children[name]; ok && child != nil {
|
|
parent.childrenMu.Unlock()
|
|
return linuxerr.EEXIST
|
|
}
|
|
if parent.childrenSet != nil {
|
|
if _, ok := parent.childrenSet[name]; ok {
|
|
parent.childrenMu.Unlock()
|
|
return linuxerr.EEXIST
|
|
}
|
|
}
|
|
parent.childrenMu.Unlock()
|
|
checkExistence := func() error {
|
|
if child, err := fs.getChildLocked(ctx, parent, name, &ds); err != nil && !linuxerr.Equals(linuxerr.ENOENT, err) {
|
|
return err
|
|
} else if child != nil {
|
|
return linuxerr.EEXIST
|
|
}
|
|
return nil
|
|
}
|
|
|
|
mnt := rp.Mount()
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
// Existence check takes precedence.
|
|
if existenceErr := checkExistence(); existenceErr != nil {
|
|
return existenceErr
|
|
}
|
|
return err
|
|
}
|
|
defer mnt.EndWrite()
|
|
|
|
if err := parent.checkPermissions(rp.Credentials(), vfs.MayWrite); err != nil {
|
|
// Existence check takes precedence.
|
|
if existenceErr := checkExistence(); existenceErr != nil {
|
|
return existenceErr
|
|
}
|
|
return err
|
|
}
|
|
if !dir && rp.MustBeDir() {
|
|
return linuxerr.ENOENT
|
|
}
|
|
if parent.inode.isSynthetic() {
|
|
if createInSyntheticDir == nil {
|
|
return linuxerr.EPERM
|
|
}
|
|
child, err := createInSyntheticDir(parent, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
parent.childrenMu.Lock()
|
|
parent.cacheNewChildLocked(child, name)
|
|
parent.syntheticChildren++
|
|
parent.clearDirentsLocked()
|
|
parent.childrenMu.Unlock()
|
|
parent.touchCMtime()
|
|
ev := linux.IN_CREATE
|
|
if dir {
|
|
ev |= linux.IN_ISDIR
|
|
}
|
|
parent.inode.watches.Notify(ctx, name, uint32(ev), 0, vfs.InodeEvent, false /* unlinked */)
|
|
return nil
|
|
}
|
|
// No cached dentry exists; however, in InteropModeShared there might still be
|
|
// an existing file at name. Just attempt the file creation RPC anyways. If a
|
|
// file does exist, the RPC will fail with EEXIST like we would have.
|
|
child, err := createInRemoteDir(parent, name, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
parent.childrenMu.Lock()
|
|
parent.cacheNewChildLocked(child, name)
|
|
if child.inode.isSynthetic() {
|
|
parent.syntheticChildren++
|
|
ds = appendDentry(ds, parent)
|
|
} else {
|
|
appendNewChildDentry(&ds, parent, child)
|
|
}
|
|
if fs.opts.interop != InteropModeShared {
|
|
if child, ok := parent.children[name]; ok && child == nil {
|
|
// Delete the now-stale negative dentry.
|
|
delete(parent.children, name)
|
|
parent.negativeChildren--
|
|
}
|
|
parent.clearDirentsLocked()
|
|
parent.touchCMtime()
|
|
}
|
|
parent.childrenMu.Unlock()
|
|
ev := linux.IN_CREATE
|
|
if dir {
|
|
ev |= linux.IN_ISDIR
|
|
}
|
|
parent.inode.watches.Notify(ctx, name, uint32(ev), 0, vfs.InodeEvent, false /* unlinked */)
|
|
return nil
|
|
}
|
|
|
|
// Preconditions: !rp.Done().
|
|
func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir bool) error {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
// We need to DecRef outside of fs.renameMu because forgetting a dead
|
|
// mountpoint could result in this filesystem being released which acquires
|
|
// fs.renameMu.
|
|
var toDecRef []refs.RefCounter
|
|
defer func() {
|
|
for _, ref := range toDecRef {
|
|
ref.DecRef(ctx)
|
|
}
|
|
}()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
start := rp.Start().Impl().(*dentry)
|
|
parent, err := fs.walkParentDirLocked(ctx, rp, start, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := parent.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil {
|
|
return err
|
|
}
|
|
if err := rp.Mount().CheckBeginWrite(); err != nil {
|
|
return err
|
|
}
|
|
defer rp.Mount().EndWrite()
|
|
|
|
name := rp.Component()
|
|
if dir {
|
|
if name == "." {
|
|
return linuxerr.EINVAL
|
|
}
|
|
if name == ".." {
|
|
return linuxerr.ENOTEMPTY
|
|
}
|
|
} else {
|
|
if name == "." || name == ".." {
|
|
return linuxerr.EISDIR
|
|
}
|
|
}
|
|
|
|
vfsObj := rp.VirtualFilesystem()
|
|
if err := fs.revalidateOne(ctx, vfsObj, parent, rp.Component(), &ds); err != nil {
|
|
return err
|
|
}
|
|
|
|
mntns := vfs.MountNamespaceFromContext(ctx)
|
|
defer mntns.DecRef(ctx)
|
|
|
|
parent.opMu.Lock()
|
|
defer parent.opMu.Unlock()
|
|
|
|
parent.childrenMu.Lock()
|
|
if parent.childrenSet != nil {
|
|
if _, ok := parent.childrenSet[name]; !ok {
|
|
parent.childrenMu.Unlock()
|
|
return linuxerr.ENOENT
|
|
}
|
|
}
|
|
parent.childrenMu.Unlock()
|
|
|
|
// Load child if sticky bit is set because we need to determine whether
|
|
// deletion is allowed.
|
|
var child *dentry
|
|
if parent.inode.mode.Load()&linux.ModeSticky == 0 {
|
|
var ok bool
|
|
parent.childrenMu.Lock()
|
|
child, ok = parent.children[name]
|
|
parent.childrenMu.Unlock()
|
|
if ok && child == nil {
|
|
// Hit a negative cached entry, child doesn't exist.
|
|
return linuxerr.ENOENT
|
|
}
|
|
} else {
|
|
child, _, err = fs.stepLocked(ctx, resolvingPathFull(rp), parent, false /* mayFollowSymlinks */, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := parent.mayDelete(rp.Credentials(), child); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If a child dentry exists, prepare to delete it. This should fail if it is
|
|
// a mount point. We detect mount points by speculatively calling
|
|
// PrepareDeleteDentry, which fails if child is a mount point.
|
|
//
|
|
// Also note that if child is nil, then it can't be a mount point.
|
|
if child != nil {
|
|
// Hold child.childrenMu so we can check child.children and
|
|
// child.syntheticChildren. We don't access these fields until a bit later,
|
|
// but locking child.childrenMu after calling vfs.PrepareDeleteDentry() would
|
|
// create an inconsistent lock ordering between dentry.childrenMu and
|
|
// vfs.Dentry.mu (in the VFS lock order, it would make dentry.childrenMu both "a
|
|
// FilesystemImpl lock" and "a lock acquired by a FilesystemImpl between
|
|
// PrepareDeleteDentry and CommitDeleteDentry). To avoid this, lock
|
|
// child.childrenMu before calling PrepareDeleteDentry.
|
|
child.childrenMu.Lock()
|
|
defer child.childrenMu.Unlock()
|
|
if err := vfsObj.PrepareDeleteDentry(mntns, &child.vfsd); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
flags := uint32(0)
|
|
// If a dentry exists, use it for best-effort checks on its deletability.
|
|
if dir {
|
|
if child != nil {
|
|
// child must be an empty directory.
|
|
if child.syntheticChildren != 0 { // +checklocksforce: child.childrenMu is held if child != nil.
|
|
// This is definitely not an empty directory, irrespective of
|
|
// fs.opts.interop.
|
|
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: PrepareDeleteDentry called if child != nil.
|
|
return linuxerr.ENOTEMPTY
|
|
}
|
|
// If InteropModeShared is in effect and the first call to
|
|
// PrepareDeleteDentry above succeeded, then child wasn't
|
|
// revalidated (so we can't expect its file type to be correct) and
|
|
// individually revalidating its children (to confirm that they
|
|
// still exist) would be a waste of time.
|
|
if child.inode.cachedMetadataAuthoritative() {
|
|
if !child.isDir() {
|
|
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
|
return linuxerr.ENOTDIR
|
|
}
|
|
for _, grandchild := range child.children { // +checklocksforce: child.childrenMu is held if child != nil.
|
|
if grandchild != nil {
|
|
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
|
return linuxerr.ENOTEMPTY
|
|
}
|
|
}
|
|
}
|
|
}
|
|
flags = linux.AT_REMOVEDIR
|
|
} else {
|
|
// child must be a non-directory file.
|
|
if child != nil && child.isDir() {
|
|
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
|
return linuxerr.EISDIR
|
|
}
|
|
if rp.MustBeDir() {
|
|
if child != nil {
|
|
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
|
}
|
|
return linuxerr.ENOTDIR
|
|
}
|
|
}
|
|
if parent.inode.isSynthetic() {
|
|
if child == nil {
|
|
return linuxerr.ENOENT
|
|
}
|
|
} else if child == nil || !child.inode.isSynthetic() {
|
|
if err := parent.inode.unlink(ctx, name, flags); err != nil {
|
|
if child != nil {
|
|
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !dir {
|
|
var cw *vfs.Watches
|
|
if child != nil {
|
|
cw = &child.inode.watches
|
|
}
|
|
vfs.InotifyRemoveChild(ctx, cw, &parent.inode.watches, name)
|
|
}
|
|
|
|
parent.childrenMu.Lock()
|
|
defer parent.childrenMu.Unlock()
|
|
|
|
if child != nil {
|
|
toDecRef = vfsObj.CommitDeleteDentry(ctx, &child.vfsd) // +checklocksforce: see above.
|
|
child.setDeleted()
|
|
child.decLinks()
|
|
// If an extra reference is held on child as described by the comment
|
|
// for dentry.refs, drop that reference now. We can't race with another
|
|
// fs.unlinkAt() or invalidation since parent.opMu has been locked for
|
|
// writing since before we obtained child, and we can't race with
|
|
// fs.RenameAt() since fs.renameMu has been locked since before we
|
|
// obtained child.
|
|
if child.inode.isSynthetic() {
|
|
parent.syntheticChildren--
|
|
child.decRefNoCaching()
|
|
} else if child.inode.endpoint != nil {
|
|
child.decRefNoCaching()
|
|
}
|
|
if dir && child.refs.Load() == 0 {
|
|
// Linux sends the parent's IN_DELETE|IN_ISDIR at rmdir() time,
|
|
// but sends the child's IN_DELETE_SELF/IN_IGNORED when the
|
|
// last reference is dropped. refs==0 means no extra refs
|
|
// remain, so emit the child notifications now. Otherwise
|
|
// defer to destroyLocked(). HandleDeletion() is idempotent.
|
|
child.inode.watches.HandleDeletion(ctx)
|
|
}
|
|
ds = appendDentry(ds, child)
|
|
}
|
|
if dir {
|
|
parent.inode.watches.Notify(ctx, name, linux.IN_DELETE|linux.IN_ISDIR, 0, vfs.InodeEvent, true /* unlinked */)
|
|
}
|
|
parent.cacheNegativeLookupLocked(name)
|
|
if parent.inode.cachedMetadataAuthoritative() {
|
|
parent.clearDirentsLocked()
|
|
parent.touchCMtime()
|
|
if dir {
|
|
parent.decLinks()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AccessAt implements vfs.Filesystem.Impl.AccessAt.
|
|
func (fs *filesystem) AccessAt(ctx context.Context, rp *vfs.ResolvingPath, creds *auth.Credentials, ats vfs.AccessTypes) error {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := d.checkPermissions(creds, ats); err != nil {
|
|
return err
|
|
}
|
|
if ats.MayWrite() && rp.Mount().ReadOnly() {
|
|
return linuxerr.EROFS
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDentryAt implements vfs.FilesystemImpl.GetDentryAt.
|
|
func (fs *filesystem) GetDentryAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetDentryOptions) (*vfs.Dentry, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if opts.CheckSearchable {
|
|
if !d.isDir() {
|
|
return nil, linuxerr.ENOTDIR
|
|
}
|
|
if err := d.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
d.IncRef()
|
|
// Call d.checkCachingLocked() so it can be removed from the cache if needed.
|
|
ds = appendDentry(ds, d)
|
|
return &d.vfsd, nil
|
|
}
|
|
|
|
// GetParentDentryAt implements vfs.FilesystemImpl.GetParentDentryAt.
|
|
func (fs *filesystem) GetParentDentryAt(ctx context.Context, rp *vfs.ResolvingPath) (*vfs.Dentry, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
start := rp.Start().Impl().(*dentry)
|
|
d, err := fs.walkParentDirLocked(ctx, rp, start, &ds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d.IncRef()
|
|
// Call d.checkCachingLocked() so it can be removed from the cache if needed.
|
|
ds = appendDentry(ds, d)
|
|
return &d.vfsd, nil
|
|
}
|
|
|
|
// LinkAt implements vfs.FilesystemImpl.LinkAt.
|
|
func (fs *filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry) error {
|
|
err := fs.doCreateAt(ctx, rp, false /* dir */, func(parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
|
|
if rp.Mount() != vd.Mount() {
|
|
return nil, linuxerr.EXDEV
|
|
}
|
|
d := vd.Dentry().Impl().(*dentry)
|
|
if d.isDir() {
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
gid := auth.KGID(d.inode.gid.Load())
|
|
uid := auth.KUID(d.inode.uid.Load())
|
|
mode := linux.FileMode(d.inode.mode.Load())
|
|
if err := vfs.MayLink(rp.Credentials(), mode, nil, uid, gid); err != nil {
|
|
return nil, err
|
|
}
|
|
if d.inode.nlink.Load() == 0 {
|
|
return nil, linuxerr.ENOENT
|
|
}
|
|
if d.inode.nlink.Load() == math.MaxUint32 {
|
|
return nil, linuxerr.EMLINK
|
|
}
|
|
if d.inode.isSynthetic() {
|
|
return nil, linuxerr.EOPNOTSUPP
|
|
}
|
|
return parent.link(ctx, d, name)
|
|
}, nil)
|
|
|
|
if err == nil {
|
|
// Success!
|
|
vd.Dentry().Impl().(*dentry).incLinks()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// MkdirAt implements vfs.FilesystemImpl.MkdirAt.
|
|
func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MkdirOptions) error {
|
|
creds := rp.Credentials()
|
|
return fs.doCreateAt(ctx, rp, true /* dir */, func(parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
|
|
// If the parent is a setgid directory, use the parent's GID
|
|
// rather than the caller's and enable setgid.
|
|
kgid := creds.EffectiveKGID
|
|
mode := opts.Mode
|
|
if parent.inode.mode.Load()&linux.S_ISGID != 0 {
|
|
kgid = auth.KGID(parent.inode.gid.Load())
|
|
mode |= linux.S_ISGID
|
|
}
|
|
|
|
child, err := parent.mkdir(ctx, name, mode, creds.EffectiveKUID, kgid, true /* createDentry */)
|
|
if err == nil {
|
|
if fs.opts.interop != InteropModeShared {
|
|
parent.incLinks()
|
|
}
|
|
child.forMountpoint = opts.ForSyntheticMountpoint
|
|
return child, nil
|
|
}
|
|
|
|
if !opts.ForSyntheticMountpoint || linuxerr.Equals(linuxerr.EEXIST, err) {
|
|
return nil, err
|
|
}
|
|
ctx.Infof("Failed to create remote directory %q: %v; falling back to synthetic directory", name, err)
|
|
child = fs.newSyntheticDentry(&createSyntheticOpts{
|
|
name: name,
|
|
mode: linux.S_IFDIR | opts.Mode,
|
|
kuid: creds.EffectiveKUID,
|
|
kgid: creds.EffectiveKGID,
|
|
})
|
|
if fs.opts.interop != InteropModeShared {
|
|
parent.incLinks()
|
|
}
|
|
return child, nil
|
|
}, func(parent *dentry, name string) (*dentry, error) {
|
|
if !opts.ForSyntheticMountpoint {
|
|
// Can't create non-synthetic files in synthetic directories.
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
child := fs.newSyntheticDentry(&createSyntheticOpts{
|
|
name: name,
|
|
mode: linux.S_IFDIR | opts.Mode,
|
|
kuid: creds.EffectiveKUID,
|
|
kgid: creds.EffectiveKGID,
|
|
})
|
|
parent.incLinks()
|
|
return child, nil
|
|
})
|
|
}
|
|
|
|
// 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 */, func(parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
|
|
creds := rp.Credentials()
|
|
if child, err := parent.mknod(ctx, name, creds, &opts); err == nil {
|
|
return child, nil
|
|
} else if !linuxerr.Equals(linuxerr.EPERM, err) {
|
|
return nil, err
|
|
}
|
|
|
|
// EPERM means that gofer does not allow creating a socket or pipe. Fallback
|
|
// to creating a synthetic one, i.e. one that is kept entirely in memory.
|
|
|
|
// Check that we're not overriding an existing file with a synthetic one.
|
|
_, _, err := fs.stepLocked(ctx, resolvingPathFull(rp), parent, false /* mayFollowSymlinks */, ds) // +checklocksforce: parent.opMu taken by doCreateAt.
|
|
switch {
|
|
case err == nil:
|
|
// Step succeeded, another file exists.
|
|
return nil, linuxerr.EEXIST
|
|
case !linuxerr.Equals(linuxerr.ENOENT, err):
|
|
// Schrödinger. File/Cat may or may not exist.
|
|
return nil, err
|
|
}
|
|
|
|
switch opts.Mode.FileType() {
|
|
case linux.S_IFSOCK:
|
|
return fs.newSyntheticDentry(&createSyntheticOpts{
|
|
name: name,
|
|
mode: opts.Mode,
|
|
kuid: creds.EffectiveKUID,
|
|
kgid: creds.EffectiveKGID,
|
|
endpoint: opts.Endpoint,
|
|
}), nil
|
|
case linux.S_IFIFO:
|
|
return fs.newSyntheticDentry(&createSyntheticOpts{
|
|
name: name,
|
|
mode: opts.Mode,
|
|
kuid: creds.EffectiveKUID,
|
|
kgid: creds.EffectiveKGID,
|
|
pipe: pipe.NewVFSPipe(true /* isNamed */, pipe.DefaultPipeSize),
|
|
}), nil
|
|
}
|
|
// Retain error from gofer if synthetic file cannot be created internally.
|
|
return nil, linuxerr.EPERM
|
|
}, nil)
|
|
}
|
|
|
|
// OpenAt implements vfs.FilesystemImpl.OpenAt.
|
|
func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
// Reject O_TMPFILE, which is not supported; supporting it correctly in the
|
|
// presence of other remote filesystem users requires remote filesystem
|
|
// support, and it isn't clear that there's any way to implement this in
|
|
// 9P.
|
|
if opts.Flags&linux.O_TMPFILE != 0 {
|
|
return nil, linuxerr.EOPNOTSUPP
|
|
}
|
|
mayCreate := opts.Flags&linux.O_CREAT != 0
|
|
mustCreate := opts.Flags&(linux.O_CREAT|linux.O_EXCL) == (linux.O_CREAT | linux.O_EXCL)
|
|
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
unlocked := false
|
|
unlock := func() {
|
|
if !unlocked {
|
|
fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
unlocked = true
|
|
}
|
|
}
|
|
defer unlock()
|
|
|
|
start := rp.Start().Impl().(*dentry)
|
|
if rp.Done() {
|
|
// Reject attempts to open mount root directory with O_CREAT.
|
|
if mayCreate && rp.MustBeDir() {
|
|
return nil, linuxerr.EISDIR
|
|
}
|
|
if mustCreate {
|
|
return nil, linuxerr.EEXIST
|
|
}
|
|
if !start.inode.cachedMetadataAuthoritative() {
|
|
// Refresh dentry's attributes before opening.
|
|
if err := start.inode.updateMetadata(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
start.IncRef()
|
|
defer start.DecRef(ctx)
|
|
unlock()
|
|
// start is intentionally not added to ds (which would remove it from the
|
|
// cache) because doing so regresses performance in practice.
|
|
return start.open(ctx, rp, &opts)
|
|
}
|
|
|
|
afterTrailingSymlink:
|
|
parent, err := fs.walkParentDirLocked(ctx, rp, start, &ds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Check for search permission in the parent directory.
|
|
if err := parent.checkPermissions(rp.Credentials(), vfs.MayExec); err != nil {
|
|
return nil, err
|
|
}
|
|
// Reject attempts to open directories with O_CREAT.
|
|
if mayCreate && rp.MustBeDir() {
|
|
return nil, linuxerr.EISDIR
|
|
}
|
|
if err := fs.revalidateOne(ctx, rp.VirtualFilesystem(), parent, rp.Component(), &ds); err != nil {
|
|
return nil, err
|
|
}
|
|
// Determine whether or not we need to create a file.
|
|
// NOTE(b/263297063): Don't hold opMu for writing here, to avoid
|
|
// serializing OpenAt calls in the same directory in the common case
|
|
// that the file exists.
|
|
parent.opMu.RLock()
|
|
child, followedSymlink, err := fs.stepLocked(ctx, resolvingPathFull(rp), parent, true /* mayFollowSymlinks */, &ds)
|
|
parent.opMu.RUnlock()
|
|
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 = parent
|
|
goto afterTrailingSymlink
|
|
}
|
|
if linuxerr.Equals(linuxerr.ENOENT, err) && mayCreate {
|
|
if parent.inode.isSynthetic() {
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
|
|
// Take opMu for writing, but note that the file may have been
|
|
// created by another goroutine since we checked for existence
|
|
// a few lines ago. We must handle that case.
|
|
parent.opMu.Lock()
|
|
fd, createErr := parent.createAndOpenChildLocked(ctx, rp, &opts, &ds)
|
|
if !linuxerr.Equals(linuxerr.EEXIST, createErr) {
|
|
// Either the creation was a success, or we got an
|
|
// unexpected error. Either way we can return here.
|
|
parent.opMu.Unlock()
|
|
return fd, createErr
|
|
}
|
|
|
|
// We raced, and now the file exists.
|
|
if mustCreate {
|
|
parent.opMu.Unlock()
|
|
return nil, linuxerr.EEXIST
|
|
}
|
|
|
|
// Step to the file again. Since we still hold opMu for
|
|
// writing, there can't be a race here.
|
|
child, _, err = fs.stepLocked(ctx, resolvingPathFull(rp), parent, false /* mayFollowSymlinks */, &ds)
|
|
parent.opMu.Unlock()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mustCreate {
|
|
return nil, linuxerr.EEXIST
|
|
}
|
|
if rp.MustBeDir() && !child.isDir() {
|
|
return nil, linuxerr.ENOTDIR
|
|
}
|
|
child.IncRef()
|
|
defer child.DecRef(ctx)
|
|
unlock()
|
|
// child is intentionally not added to ds (which would remove it from the
|
|
// cache) because doing so regresses performance in practice.
|
|
return child.open(ctx, rp, &opts)
|
|
}
|
|
|
|
// Used to log a rejected fifo open, once.
|
|
var logRejectedFifoOpenOnce sync.Once
|
|
|
|
// Used to log an open of an unimplemented character device, once.
|
|
var logUnimplementedCharDevOpenOnce sync.Once
|
|
|
|
// Preconditions: The caller must hold no locks (since opening pipes may block
|
|
// indefinitely).
|
|
func (d *dentry) open(ctx context.Context, rp *vfs.ResolvingPath, opts *vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
ats := vfs.AccessTypesForOpenFlags(opts)
|
|
|
|
if err := d.checkPermissions(rp.Credentials(), ats); err != nil {
|
|
return nil, err
|
|
}
|
|
if !d.inode.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.inode.fs.renameMu.RLock()
|
|
defer d.inode.fs.renameMu.RUnlock()
|
|
}
|
|
|
|
mnt := rp.Mount()
|
|
trunc := opts.Flags&linux.O_TRUNC != 0 && d.inode.fileType() == linux.S_IFREG
|
|
if trunc {
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
return nil, err
|
|
}
|
|
defer mnt.EndWrite()
|
|
// Lock metadataMu *while* we open a regular file with O_TRUNC because
|
|
// open(2) will change the file size on server.
|
|
d.inode.metadataMu.Lock()
|
|
defer d.inode.metadataMu.Unlock()
|
|
}
|
|
|
|
var vfd *vfs.FileDescription
|
|
var err error
|
|
switch d.inode.fileType() {
|
|
case linux.S_IFREG:
|
|
if !d.inode.fs.opts.regularFilesUseSpecialFileFD {
|
|
if err := d.ensureSharedHandle(ctx, ats.MayRead(), ats.MayWrite(), trunc); err != nil {
|
|
return nil, err
|
|
}
|
|
fd, err := newRegularFileFD(mnt, d, opts.Flags, rp.Credentials())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vfd = &fd.vfsfd
|
|
}
|
|
case linux.S_IFDIR:
|
|
// Can't open directories with O_CREAT.
|
|
if opts.Flags&linux.O_CREAT != 0 {
|
|
return nil, linuxerr.EISDIR
|
|
}
|
|
// Can't open directories writably.
|
|
if ats&vfs.MayWrite != 0 {
|
|
return nil, linuxerr.EISDIR
|
|
}
|
|
if opts.Flags&linux.O_DIRECT != 0 {
|
|
return nil, linuxerr.EINVAL
|
|
}
|
|
if !d.inode.isSynthetic() {
|
|
if err := d.ensureSharedHandle(ctx, ats&vfs.MayRead != 0, false /* write */, false /* trunc */); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
fd := &directoryFD{}
|
|
fd.LockFD.Init(&d.inode.locks)
|
|
if err := fd.vfsfd.Init(fd, opts.Flags, rp.Credentials(), mnt, &d.vfsd, &vfs.FileDescriptionOptions{}); err != nil {
|
|
return nil, err
|
|
}
|
|
if d.inode.readFD.Load() >= 0 {
|
|
fsmetric.GoferOpensHost.Increment()
|
|
} else {
|
|
fsmetric.GoferOpens9P.Increment()
|
|
}
|
|
return &fd.vfsfd, nil
|
|
case linux.S_IFLNK:
|
|
// Can't open symlinks without O_PATH, which is handled at the VFS layer.
|
|
return nil, linuxerr.ELOOP
|
|
case linux.S_IFSOCK:
|
|
if d.inode.isSynthetic() {
|
|
return nil, linuxerr.ENXIO
|
|
}
|
|
if d.inode.fs.iopts.OpenSocketsByConnecting {
|
|
return d.openSocketByConnecting(ctx, opts)
|
|
}
|
|
case linux.S_IFIFO:
|
|
if d.inode.isSynthetic() {
|
|
return d.inode.pipe.Open(ctx, mnt, &d.vfsd, opts.Flags, &d.inode.locks)
|
|
}
|
|
if d.inode.fs.opts.disableFifoOpen {
|
|
logRejectedFifoOpenOnce.Do(func() {
|
|
log.Warningf("Rejecting attempt to open fifo/pipe from host filesystem: %q. If you want to allow this, set flag --host-fifo=open", d.name)
|
|
})
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
case linux.S_IFCHR:
|
|
emulate := false
|
|
switch d.inode.fs.opts.charDevicePolicy {
|
|
case charDevEmulatedOnly:
|
|
if !rp.VirtualFilesystem().IsDeviceRegistered(vfs.CharDevice, d.inode.rdevMajor, d.inode.rdevMinor) {
|
|
logUnimplementedCharDevOpenOnce.Do(func() {
|
|
log.Warningf("Opening character device %d:%d (%q), which the sentry does not implement; the open will fail with ENXIO. If you want to allow this device to be opened on the host instead, set flag --character-device-policy=prefer-emulated", d.inode.rdevMajor, d.inode.rdevMinor, d.name)
|
|
})
|
|
}
|
|
emulate = true
|
|
case charDevPreferEmulated:
|
|
emulate = rp.VirtualFilesystem().IsDeviceRegistered(vfs.CharDevice, d.inode.rdevMajor, d.inode.rdevMinor)
|
|
}
|
|
if emulate {
|
|
// Dispatch to the sentry's device registry with renameMu dropped (so
|
|
// that it may block and lock in peace).
|
|
//
|
|
// It is safe because renameMu guards the dentry tree (parents, names,
|
|
// children); this dispatch consults none of it. It passes only mnt,
|
|
// &d.vfsd, and the immutable device numbers, and d stays alive across
|
|
// the window because the caller holds a reference on it (see the
|
|
// child.IncRef in the open path). A concurrent rename may relink d
|
|
// while we are unlocked, but we read no tree state afterwards: we
|
|
// re-acquire only to satisfy the deferred RUnlock and return at once.
|
|
d.inode.fs.renameMu.RUnlock()
|
|
fd, err := rp.VirtualFilesystem().OpenDeviceSpecialFile(ctx, mnt, &d.vfsd, vfs.CharDevice, d.inode.rdevMajor, d.inode.rdevMinor, opts)
|
|
d.inode.fs.renameMu.RLock()
|
|
return fd, err
|
|
}
|
|
}
|
|
|
|
if vfd == nil {
|
|
if vfd, err = d.openSpecialFile(ctx, mnt, opts); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if trunc {
|
|
// If no errors occurred so far then update file size in memory. This
|
|
// step is required even if !d.inode.cachedMetadataAuthoritative() because
|
|
// d.mappings has to be updated.
|
|
// d.metadataMu has already been acquired if trunc == true.
|
|
d.inode.updateSizeLocked(0)
|
|
|
|
if d.inode.cachedMetadataAuthoritative() {
|
|
d.touchCMtimeLocked()
|
|
}
|
|
}
|
|
return vfd, err
|
|
}
|
|
|
|
// Precondition: fs.renameMu is locked.
|
|
func (d *dentry) openSocketByConnecting(ctx context.Context, opts *vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
fsmetric.GoferOpensByConnecting.Increment()
|
|
if opts.Flags&linux.O_DIRECT != 0 {
|
|
return nil, linuxerr.EINVAL
|
|
}
|
|
// Note that special value of linux.SockType = 0 is interpreted by lisafs
|
|
// as "do not care about the socket type". Analogous to p9.AnonymousSocket.
|
|
sockFD, err := d.connect(ctx, 0 /* sockType */)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fd, err := host.NewFD(ctx, kernel.KernelFromContext(ctx).HostMount(), sockFD, &host.NewFDOptions{
|
|
HaveFlags: true,
|
|
Flags: opts.Flags,
|
|
})
|
|
if err != nil {
|
|
unix.Close(sockFD)
|
|
return nil, err
|
|
}
|
|
return fd, nil
|
|
}
|
|
|
|
// Preconditions:
|
|
// - !d.inode.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.inode.isRegularFile() {
|
|
return nil, linuxerr.EINVAL
|
|
}
|
|
// We assume that the server silently inserts O_NONBLOCK in the open flags
|
|
// for all named pipes (because all existing gofers do this).
|
|
//
|
|
// NOTE(b/133875563): This makes named pipe opens racy, because the
|
|
// mechanisms for translating nonblocking to blocking opens can only detect
|
|
// the instantaneous presence of a peer holding the other end of the pipe
|
|
// open, not whether the pipe was *previously* opened by a peer that has
|
|
// since closed its end.
|
|
isBlockingOpenOfNamedPipe := d.inode.fileType() == linux.S_IFIFO && opts.Flags&linux.O_NONBLOCK == 0
|
|
retry:
|
|
h, err := d.openHandle(ctx, ats.MayRead(), ats.MayWrite(), opts.Flags&linux.O_TRUNC != 0)
|
|
if err != nil {
|
|
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. Release renameMu
|
|
// while blocking.
|
|
d.inode.fs.renameMu.RUnlock()
|
|
err := sleepBetweenNamedPipeOpenChecks(ctx)
|
|
d.inode.fs.renameMu.RLock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
goto retry
|
|
}
|
|
return nil, err
|
|
}
|
|
if isBlockingOpenOfNamedPipe && ats == vfs.MayRead && h.fd >= 0 {
|
|
// Release renameMu while blocking.
|
|
d.inode.fs.renameMu.RUnlock()
|
|
err := blockUntilNonblockingPipeHasWriter(ctx, h.fd)
|
|
d.inode.fs.renameMu.RLock()
|
|
if err != nil {
|
|
h.close(ctx)
|
|
return nil, err
|
|
}
|
|
}
|
|
fd, err := newSpecialFileFD(h, mnt, d, opts.Flags, auth.CredentialsFromContext(ctx))
|
|
if err != nil {
|
|
h.close(ctx)
|
|
return nil, err
|
|
}
|
|
return &fd.vfsfd, nil
|
|
}
|
|
|
|
// Preconditions:
|
|
// - d.inode.fs.renameMu must be locked.
|
|
// - d.opMu must be locked for writing.
|
|
// - !d.inode.isSynthetic().
|
|
//
|
|
// +checklocks:d.opMu
|
|
func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.ResolvingPath, opts *vfs.OpenOptions, ds **[]*dentry) (*vfs.FileDescription, error) {
|
|
if err := d.checkPermissions(rp.Credentials(), vfs.MayWrite); err != nil {
|
|
return nil, err
|
|
}
|
|
if d.isDeleted() {
|
|
return nil, linuxerr.ENOENT
|
|
}
|
|
mnt := rp.Mount()
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
return nil, err
|
|
}
|
|
defer mnt.EndWrite()
|
|
|
|
creds := rp.Credentials()
|
|
name := rp.Component()
|
|
// If the parent is a setgid directory, use the parent's GID rather
|
|
// than the caller's.
|
|
kgid := creds.EffectiveKGID
|
|
if d.inode.mode.Load()&linux.S_ISGID != 0 {
|
|
kgid = auth.KGID(d.inode.gid.Load())
|
|
}
|
|
|
|
child, h, err := d.openCreate(ctx, name, opts.Flags&linux.O_ACCMODE, opts.Mode, creds.EffectiveKUID, kgid, true /* createDentry */)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Incorporate the fid that was opened by lcreate.
|
|
useRegularFileFD := child.inode.fileType() == linux.S_IFREG && !d.inode.fs.opts.regularFilesUseSpecialFileFD
|
|
if useRegularFileFD {
|
|
var readable, writable bool
|
|
child.inode.handleMu.Lock()
|
|
if vfs.MayReadFileWithOpenFlags(opts.Flags) {
|
|
readable = true
|
|
if h.fd != -1 {
|
|
child.inode.readFD = atomicbitops.FromInt32(h.fd)
|
|
child.inode.mmapFD = atomicbitops.FromInt32(h.fd)
|
|
child.inode.mmapFile.SetFD(int(h.fd))
|
|
}
|
|
}
|
|
if vfs.MayWriteFileWithOpenFlags(opts.Flags) {
|
|
writable = true
|
|
child.inode.writeFD = atomicbitops.FromInt32(h.fd)
|
|
}
|
|
child.inode.updateHandles(ctx, h, readable, writable)
|
|
child.inode.handleMu.Unlock()
|
|
}
|
|
// Insert the dentry into the tree.
|
|
d.childrenMu.Lock()
|
|
// We have d.opMu for writing, so there can not be a cached child with
|
|
// this name. We could not have raced.
|
|
d.cacheNewChildLocked(child, name)
|
|
appendNewChildDentry(ds, d, child)
|
|
if d.inode.cachedMetadataAuthoritative() {
|
|
d.touchCMtime()
|
|
d.clearDirentsLocked()
|
|
}
|
|
d.childrenMu.Unlock()
|
|
|
|
// Finally, construct a file description representing the created file.
|
|
var childVFSFD *vfs.FileDescription
|
|
if useRegularFileFD {
|
|
fd, err := newRegularFileFD(mnt, child, opts.Flags, creds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
childVFSFD = &fd.vfsfd
|
|
} else {
|
|
fd, err := newSpecialFileFD(h, mnt, child, opts.Flags, creds)
|
|
if err != nil {
|
|
h.close(ctx)
|
|
return nil, err
|
|
}
|
|
childVFSFD = &fd.vfsfd
|
|
}
|
|
d.inode.watches.Notify(ctx, name, linux.IN_CREATE, 0, vfs.PathEvent, false /* unlinked */)
|
|
childVFSFD.SetCreated()
|
|
return childVFSFD, nil
|
|
}
|
|
|
|
// ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt.
|
|
func (fs *filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (string, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !d.inode.isSymlink() {
|
|
return "", linuxerr.EINVAL
|
|
}
|
|
return d.readlink(ctx, rp.Mount())
|
|
}
|
|
|
|
// 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.
|
|
var ds *[]*dentry
|
|
fs.renameMu.Lock()
|
|
// We need to DecRef outside of fs.mu because forgetting a dead mountpoint
|
|
// could result in this filesystem being released which acquires fs.mu.
|
|
var toDecRef []refs.RefCounter
|
|
defer func() {
|
|
for _, ref := range toDecRef {
|
|
ref.DecRef(ctx)
|
|
}
|
|
}()
|
|
defer fs.renameMuUnlockAndCheckCaching(ctx, &ds)
|
|
newParent, err := fs.walkParentDirLocked(ctx, rp, rp.Start().Impl().(*dentry), &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Support flags:
|
|
// - RENAME_NOREPLACE
|
|
// - RENAME_EXCHANGE
|
|
if opts.Flags&^(linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE) != 0 {
|
|
return linuxerr.EINVAL
|
|
}
|
|
|
|
// RENAME_NOREPLACE can't be employed together with RENAME_EXCHANGE.
|
|
if opts.Flags&(linux.RENAME_EXCHANGE|linux.RENAME_NOREPLACE) == linux.RENAME_EXCHANGE|linux.RENAME_NOREPLACE {
|
|
return linuxerr.EINVAL
|
|
}
|
|
if fs.opts.interop == InteropModeShared && opts.Flags&linux.RENAME_NOREPLACE != 0 {
|
|
// Requires 9P support to synchronize with other remote filesystem
|
|
// users.
|
|
return linuxerr.EINVAL
|
|
}
|
|
exchange := opts.Flags&linux.RENAME_EXCHANGE != 0
|
|
|
|
newName := rp.Component()
|
|
if newName == "." || newName == ".." {
|
|
if opts.Flags&linux.RENAME_NOREPLACE != 0 {
|
|
return linuxerr.EEXIST
|
|
}
|
|
return linuxerr.EBUSY
|
|
}
|
|
if len(newName) > MaxFilenameLen {
|
|
return linuxerr.ENAMETOOLONG
|
|
}
|
|
mnt := rp.Mount()
|
|
if mnt != oldParentVD.Mount() {
|
|
return linuxerr.EXDEV
|
|
}
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
return err
|
|
}
|
|
defer mnt.EndWrite()
|
|
|
|
oldParent := oldParentVD.Dentry().Impl().(*dentry)
|
|
if !oldParent.inode.cachedMetadataAuthoritative() {
|
|
if err := oldParent.inode.updateMetadata(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
creds := rp.Credentials()
|
|
if err := oldParent.checkPermissions(creds, vfs.MayWrite|vfs.MayExec); err != nil {
|
|
return err
|
|
}
|
|
|
|
vfsObj := rp.VirtualFilesystem()
|
|
if err := fs.revalidateOne(ctx, vfsObj, newParent, newName, &ds); err != nil {
|
|
return err
|
|
}
|
|
if err := fs.revalidateOne(ctx, vfsObj, oldParent, oldName, &ds); err != nil {
|
|
return err
|
|
}
|
|
|
|
// We need a dentry representing the renamed file since, if it's a
|
|
// directory, we need to check for write permission on it.
|
|
oldParent.opMu.Lock()
|
|
defer oldParent.opMu.Unlock()
|
|
renamed, err := fs.getChildLocked(ctx, oldParent, oldName, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := oldParent.mayDelete(creds, renamed); err != nil {
|
|
return err
|
|
}
|
|
if renamed.isDir() {
|
|
if renamed == newParent || genericIsAncestorDentry(fs, renamed, newParent) {
|
|
return linuxerr.EINVAL
|
|
}
|
|
if oldParent != newParent {
|
|
if err := renamed.checkPermissions(creds, vfs.MayWrite); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
if !exchange && (opts.MustBeDir || rp.MustBeDir()) {
|
|
return linuxerr.ENOTDIR
|
|
}
|
|
}
|
|
|
|
if oldParent != newParent {
|
|
if err := newParent.checkPermissions(creds, vfs.MayWrite|vfs.MayExec); err != nil {
|
|
return err
|
|
}
|
|
newParent.opMu.Lock()
|
|
defer newParent.opMu.Unlock()
|
|
}
|
|
if newParent.isDeleted() {
|
|
return linuxerr.ENOENT
|
|
}
|
|
replaced, err := fs.getChildLocked(ctx, newParent, newName, &ds) // +checklocksforce: newParent.opMu taken if newParent != oldParent.
|
|
if err != nil && !linuxerr.Equals(linuxerr.ENOENT, err) {
|
|
return err
|
|
}
|
|
var replacedVFSD *vfs.Dentry
|
|
if replaced != nil {
|
|
if opts.Flags&linux.RENAME_NOREPLACE != 0 {
|
|
return linuxerr.EEXIST
|
|
}
|
|
if err := newParent.mayDelete(creds, replaced); err != nil {
|
|
return err
|
|
}
|
|
replacedVFSD = &replaced.vfsd
|
|
if exchange {
|
|
// The exchanged files may differ in type, and a directory being
|
|
// exchanged may be non-empty; but exchanging a file with an
|
|
// ancestor directory would disconnect the latter from the tree.
|
|
if genericIsAncestorDentry(fs, replaced, renamed) {
|
|
return linuxerr.EINVAL
|
|
}
|
|
if rp.MustBeDir() && !replaced.isDir() {
|
|
return linuxerr.ENOTDIR
|
|
}
|
|
if opts.MustBeDir && !renamed.isDir() {
|
|
return linuxerr.ENOTDIR
|
|
}
|
|
if oldParent != newParent && replaced.isDir() {
|
|
// Writability is needed to change replaced's "..".
|
|
if err := replaced.checkPermissions(creds, vfs.MayWrite); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if replaced.isDir() {
|
|
if !renamed.isDir() {
|
|
return linuxerr.EISDIR
|
|
}
|
|
if genericIsAncestorDentry(fs, replaced, renamed) {
|
|
return linuxerr.ENOTEMPTY
|
|
}
|
|
} else {
|
|
if rp.MustBeDir() || renamed.isDir() {
|
|
return linuxerr.ENOTDIR
|
|
}
|
|
}
|
|
} else { // replaced == nil
|
|
if exchange {
|
|
// RENAME_EXCHANGE requires that the target file exist.
|
|
return linuxerr.ENOENT
|
|
}
|
|
}
|
|
|
|
if oldParent == newParent && oldName == newName {
|
|
return nil
|
|
}
|
|
mntns := vfs.MountNamespaceFromContext(ctx)
|
|
defer mntns.DecRef(ctx)
|
|
handle, err := vfsObj.PrepareRenameDentry(mntns, &renamed.vfsd, replacedVFSD)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the remote filesystem.
|
|
if !renamed.inode.isSynthetic() {
|
|
if err := oldParent.inode.rename(ctx, oldName, newParent, newName, opts.Flags); err != nil {
|
|
vfsObj.AbortRenameDentry(&handle, &renamed.vfsd, replacedVFSD)
|
|
return err
|
|
}
|
|
} else if replaced != nil && !replaced.inode.isSynthetic() && !exchange {
|
|
// We are replacing an existing real file with a synthetic one, so we
|
|
// need to unlink the former.
|
|
flags := uint32(0)
|
|
if replaced.isDir() {
|
|
flags = linux.AT_REMOVEDIR
|
|
}
|
|
if err := newParent.inode.unlink(ctx, newName, flags); err != nil {
|
|
vfsObj.AbortRenameDentry(&handle, &renamed.vfsd, replacedVFSD)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Update the dentry tree.
|
|
newParent.childrenMu.Lock()
|
|
defer newParent.childrenMu.Unlock()
|
|
if oldParent != newParent {
|
|
oldParent.childrenMu.Lock()
|
|
defer oldParent.childrenMu.Unlock()
|
|
}
|
|
|
|
vfsObj.RenameBegin(&handle)
|
|
if exchange {
|
|
if oldParent != newParent {
|
|
switch {
|
|
case replaced.inode.isSynthetic() && !renamed.inode.isSynthetic():
|
|
newParent.syntheticChildren--
|
|
oldParent.syntheticChildren++ // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
|
|
case !replaced.inode.isSynthetic() && renamed.inode.isSynthetic():
|
|
newParent.syntheticChildren++
|
|
oldParent.syntheticChildren-- // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
|
|
}
|
|
renamed.parent.Store(newParent)
|
|
replaced.parent.Store(oldParent)
|
|
}
|
|
renamed.name = newName
|
|
replaced.name = oldName
|
|
newParent.children[newName] = renamed
|
|
oldParent.children[oldName] = replaced // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
|
|
vfsObj.CommitRenameExchangeDentry(&handle, &renamed.vfsd, replacedVFSD)
|
|
|
|
// Update metadata.
|
|
if renamed.inode.cachedMetadataAuthoritative() {
|
|
renamed.touchCtime()
|
|
}
|
|
if replaced.inode.cachedMetadataAuthoritative() {
|
|
replaced.touchCtime()
|
|
}
|
|
if oldParent.inode.cachedMetadataAuthoritative() {
|
|
oldParent.clearDirentsLocked()
|
|
oldParent.touchCMtime()
|
|
}
|
|
if oldParent != newParent {
|
|
if newParent.inode.cachedMetadataAuthoritative() {
|
|
newParent.clearDirentsLocked()
|
|
newParent.touchCMtime()
|
|
}
|
|
// If exactly one of the exchanged files is a directory, its ".."
|
|
// entry moves from one parent directory to the other.
|
|
if renamed.isDir() && !replaced.isDir() {
|
|
if oldParent.inode.cachedMetadataAuthoritative() {
|
|
oldParent.decLinks()
|
|
}
|
|
if newParent.inode.cachedMetadataAuthoritative() {
|
|
newParent.incLinks()
|
|
}
|
|
} else if !renamed.isDir() && replaced.isDir() {
|
|
if newParent.inode.cachedMetadataAuthoritative() {
|
|
newParent.decLinks()
|
|
}
|
|
if oldParent.inode.cachedMetadataAuthoritative() {
|
|
oldParent.incLinks()
|
|
}
|
|
}
|
|
}
|
|
// Sends notifications for both the renamed and replaced dentries.
|
|
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
|
|
vfs.InotifyRename(ctx, &replaced.inode.watches, &newParent.inode.watches, &oldParent.inode.watches, newName, oldName, replaced.isDir())
|
|
return nil
|
|
}
|
|
if replaced != nil {
|
|
replaced.setDeleted()
|
|
// If an extra reference is held on replaced as described by the
|
|
// comment for dentry.refs, drop that reference now. We can't race with
|
|
// fs.unlinkAt() or invalidation since fs.renameMu has been locked for
|
|
// writing since before we obtained replaced.
|
|
if replaced.inode.isSynthetic() {
|
|
newParent.syntheticChildren--
|
|
replaced.decRefNoCaching()
|
|
} else if replaced.inode.endpoint != nil {
|
|
replaced.decRefNoCaching()
|
|
}
|
|
ds = appendDentry(ds, replaced)
|
|
// Remove the replaced entry from its parent's cache.
|
|
delete(newParent.children, newName)
|
|
}
|
|
oldParent.cacheNegativeLookupLocked(oldName) // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
|
|
if renamed.inode.isSynthetic() {
|
|
oldParent.syntheticChildren--
|
|
newParent.syntheticChildren++
|
|
}
|
|
// We have d.opMu for writing, so no need to check for existence of a
|
|
// child with the given name. We could not have raced.
|
|
newParent.cacheNewChildLocked(renamed, newName)
|
|
oldParent.decRefNoCaching()
|
|
if oldParent != newParent {
|
|
ds = appendDentry(ds, newParent)
|
|
ds = appendDentry(ds, oldParent)
|
|
}
|
|
toDecRef = vfsObj.CommitRenameReplaceDentry(ctx, &handle, &renamed.vfsd, replacedVFSD)
|
|
|
|
// Update metadata.
|
|
if renamed.inode.cachedMetadataAuthoritative() {
|
|
renamed.touchCtime()
|
|
}
|
|
if oldParent.inode.cachedMetadataAuthoritative() {
|
|
oldParent.clearDirentsLocked()
|
|
oldParent.touchCMtime()
|
|
if renamed.isDir() {
|
|
oldParent.decLinks()
|
|
}
|
|
}
|
|
if newParent.inode.cachedMetadataAuthoritative() {
|
|
newParent.clearDirentsLocked()
|
|
newParent.touchCMtime()
|
|
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
|
|
// Increase the link count if we did not replace another directory.
|
|
newParent.incLinks()
|
|
}
|
|
}
|
|
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
|
|
return nil
|
|
}
|
|
|
|
// RmdirAt implements vfs.FilesystemImpl.RmdirAt.
|
|
func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
|
return fs.unlinkAt(ctx, rp, true /* dir */)
|
|
}
|
|
|
|
// SetStatAt implements vfs.FilesystemImpl.SetStatAt.
|
|
func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
return err
|
|
}
|
|
err = d.setStat(ctx, rp.Credentials(), &opts, rp.Mount())
|
|
fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ev := vfs.InotifyEventFromStatMask(opts.Stat.Mask); ev != 0 {
|
|
d.InotifyWithParent(ctx, ev, 0, vfs.InodeEvent)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StatAt implements vfs.FilesystemImpl.StatAt.
|
|
func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.StatOptions) (linux.Statx, error) {
|
|
if rp.Done() && opts.Sync == linux.AT_STATX_DONT_SYNC {
|
|
var stat linux.Statx
|
|
rp.Start().Impl().(*dentry).statTo(&stat)
|
|
return stat, nil
|
|
}
|
|
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return linux.Statx{}, err
|
|
}
|
|
// Since walking updates metadata for all traversed dentries under
|
|
// InteropModeShared, including the returned one, we can return cached
|
|
// metadata here regardless of fs.opts.interop.
|
|
var stat linux.Statx
|
|
d.statTo(&stat)
|
|
return stat, nil
|
|
}
|
|
|
|
// StatFSAt implements vfs.FilesystemImpl.StatFSAt.
|
|
func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return linux.Statfs{}, err
|
|
}
|
|
// If d is synthetic, invoke statfs on the first ancestor of d that isn't.
|
|
for d.inode.isSynthetic() {
|
|
d = d.parent.Load()
|
|
}
|
|
statfs, err := d.inode.statfs(ctx)
|
|
if err != nil {
|
|
return linux.Statfs{}, err
|
|
}
|
|
if statfs.NameLength == 0 || statfs.NameLength > MaxFilenameLen {
|
|
statfs.NameLength = MaxFilenameLen
|
|
}
|
|
// This is primarily for distinguishing a gofer file system in
|
|
// tests. Testing is important, so instead of defining
|
|
// something completely random, use a standard value.
|
|
statfs.Type = linux.V9FS_MAGIC
|
|
return 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 */, func(parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
|
|
child, err := parent.symlink(ctx, name, target, rp.Credentials())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if parent.inode.fs.opts.interop != InteropModeShared {
|
|
// Cache the symlink target on creation. In practice, this helps avoid a
|
|
// lot of ReadLink RPCs. Note that when InteropModeShared is in effect,
|
|
// we are forced to make Readlink RPCs. Because in this mode, we use host
|
|
// timestamps, not timestamps based on our internal clock. And readlink
|
|
// updates the atime on the host.
|
|
child.inode.haveTarget = true
|
|
child.inode.target = target
|
|
}
|
|
return child, nil
|
|
}, nil)
|
|
}
|
|
|
|
// UnlinkAt implements vfs.FilesystemImpl.UnlinkAt.
|
|
func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
|
return fs.unlinkAt(ctx, rp, false /* dir */)
|
|
}
|
|
|
|
// BoundEndpointAt implements vfs.FilesystemImpl.BoundEndpointAt.
|
|
func (fs *filesystem) BoundEndpointAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.BoundEndpointOptions) (transport.BoundEndpoint, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := d.checkPermissions(rp.Credentials(), vfs.MayWrite); err != nil {
|
|
return nil, err
|
|
}
|
|
if !d.inode.isSocket() {
|
|
return nil, linuxerr.ECONNREFUSED
|
|
}
|
|
if d.inode.endpoint != nil {
|
|
return d.inode.endpoint, nil
|
|
}
|
|
if !d.inode.isSynthetic() {
|
|
d.IncRef()
|
|
ds = appendDentry(ds, d)
|
|
return &endpoint{
|
|
dentry: d,
|
|
path: opts.Addr,
|
|
}, nil
|
|
}
|
|
return nil, linuxerr.ECONNREFUSED
|
|
}
|
|
|
|
// ListXattrAt implements vfs.FilesystemImpl.ListXattrAt.
|
|
func (fs *filesystem) ListXattrAt(ctx context.Context, rp *vfs.ResolvingPath, size uint64) ([]string, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return d.listXattr(ctx, size)
|
|
}
|
|
|
|
// GetXattrAt implements vfs.FilesystemImpl.GetXattrAt.
|
|
func (fs *filesystem) GetXattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetXattrOptions) (string, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return d.getXattr(ctx, rp.Credentials(), &opts)
|
|
}
|
|
|
|
// SetXattrAt implements vfs.FilesystemImpl.SetXattrAt.
|
|
func (fs *filesystem) SetXattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetXattrOptions) error {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
return err
|
|
}
|
|
err = d.setXattr(ctx, rp.Credentials(), &opts)
|
|
fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.InotifyWithParent(ctx, linux.IN_ATTRIB, 0, vfs.InodeEvent)
|
|
return nil
|
|
}
|
|
|
|
// RemoveXattrAt implements vfs.FilesystemImpl.RemoveXattrAt.
|
|
func (fs *filesystem) RemoveXattrAt(ctx context.Context, rp *vfs.ResolvingPath, name string) error {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
d, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
return err
|
|
}
|
|
err = d.removeXattr(ctx, rp.Credentials(), name)
|
|
fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.InotifyWithParent(ctx, linux.IN_ATTRIB, 0, vfs.InodeEvent)
|
|
return nil
|
|
}
|
|
|
|
// GetPosixACLAt implements vfs.FilesystemImpl.GetPosixACLAt.
|
|
func (fs *filesystem) GetPosixACLAt(ctx context.Context, rp *vfs.ResolvingPath, t vfs.ACLType) (*vfs.PosixACL, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
// gofer does not currently support POSIX ACLs.
|
|
_, err := fs.resolveLocked(ctx, rp, &ds)
|
|
return nil, err
|
|
}
|
|
|
|
// SetPosixACLAt implements vfs.FilesystemImpl.SetPosixACLAt.
|
|
func (fs *filesystem) SetPosixACLAt(ctx context.Context, rp *vfs.ResolvingPath, t vfs.ACLType, acl *vfs.PosixACL, clearSGID bool) (*vfs.PosixACL, linux.FileMode, error) {
|
|
var ds *[]*dentry
|
|
fs.renameMu.RLock()
|
|
defer fs.renameMuRUnlockAndCheckCaching(ctx, &ds)
|
|
_, err := fs.resolveLocked(ctx, rp, &ds)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
// gofer does not currently support POSIX ACLs.
|
|
return nil, 0, linuxerr.EOPNOTSUPP
|
|
}
|
|
|
|
// PrependPath implements vfs.FilesystemImpl.PrependPath.
|
|
func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
|
|
return genericPrependPath(fs, vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
|
|
}
|
|
|
|
// IsDescendant implements vfs.FilesystemImpl.IsDescendant.
|
|
func (fs *filesystem) IsDescendant(vfsroot, vd vfs.VirtualDentry) bool {
|
|
return genericIsDescendant(fs, vfsroot.Dentry(), vd.Dentry().Impl().(*dentry))
|
|
}
|
|
|
|
type mopt struct {
|
|
key string
|
|
value any
|
|
}
|
|
|
|
func (m mopt) String() string {
|
|
if m.value == nil {
|
|
return m.key
|
|
}
|
|
return fmt.Sprintf("%s=%v", m.key, m.value)
|
|
}
|
|
|
|
// MountOptions implements vfs.FilesystemImpl.MountOptions.
|
|
func (fs *filesystem) MountOptions() string {
|
|
optsKV := []mopt{
|
|
{moptTransport, transportModeFD}, // Only valid value, currently.
|
|
{moptReadFD, fs.opts.fd}, // Currently, read and write FD are the same.
|
|
{moptWriteFD, fs.opts.fd}, // Currently, read and write FD are the same.
|
|
{moptAname, fs.opts.aname},
|
|
{moptDfltUID, fs.opts.dfltuid},
|
|
{moptDfltGID, fs.opts.dfltgid},
|
|
}
|
|
|
|
if globalDentryCache != nil {
|
|
optsKV = append(optsKV, mopt{moptDcache, fmt.Sprintf("%d-global", globalDentryCache.maxCachedDentries)})
|
|
} else {
|
|
optsKV = append(optsKV, mopt{moptDcache, fs.opts.dcache})
|
|
}
|
|
switch fs.opts.interop {
|
|
case InteropModeExclusive:
|
|
optsKV = append(optsKV, mopt{moptCache, cacheFSCache})
|
|
case InteropModeWritethrough:
|
|
optsKV = append(optsKV, mopt{moptCache, cacheFSCacheWritethrough})
|
|
case InteropModeShared:
|
|
optsKV = append(optsKV, mopt{moptCache, cacheRemoteRevalidating})
|
|
}
|
|
if fs.opts.regularFilesUseSpecialFileFD {
|
|
optsKV = append(optsKV, mopt{moptDisableFileHandleSharing, nil})
|
|
}
|
|
if fs.opts.disableFifoOpen {
|
|
optsKV = append(optsKV, mopt{moptDisableFifoOpen, nil})
|
|
}
|
|
if fs.opts.forcePageCache {
|
|
optsKV = append(optsKV, mopt{moptForcePageCache, nil})
|
|
}
|
|
if fs.opts.limitHostFDTranslation {
|
|
optsKV = append(optsKV, mopt{moptLimitHostFDTranslation, nil})
|
|
}
|
|
if fs.opts.overlayfsStaleRead {
|
|
optsKV = append(optsKV, mopt{moptOverlayfsStaleRead, nil})
|
|
}
|
|
if fs.opts.directfs.enabled {
|
|
optsKV = append(optsKV, mopt{moptDirectfs, nil})
|
|
}
|
|
|
|
opts := make([]string, 0, len(optsKV))
|
|
for _, opt := range optsKV {
|
|
opts = append(opts, opt.String())
|
|
}
|
|
return strings.Join(opts, ",")
|
|
}
|