2019-07-18 22:09:14 +00:00
|
|
|
// 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.
|
|
|
|
|
2020-01-06 20:51:35 +00:00
|
|
|
package tmpfs
|
2019-07-18 22:09:14 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sync/atomic"
|
|
|
|
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
2019-12-11 21:40:57 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/fspath"
|
2019-07-18 22:09:14 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/sentry/context"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
|
|
"gvisor.dev/gvisor/pkg/syserror"
|
|
|
|
)
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// Sync implements vfs.FilesystemImpl.Sync.
|
|
|
|
func (fs *filesystem) Sync(ctx context.Context) error {
|
|
|
|
// All filesystem state is in-memory.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// stepLocked resolves rp.Component() to an existing file, starting from the
|
|
|
|
// given directory.
|
2019-07-18 22:09:14 +00:00
|
|
|
//
|
|
|
|
// stepLocked is loosely analogous to fs/namei.c:walk_component().
|
|
|
|
//
|
2019-12-23 21:17:29 +00:00
|
|
|
// Preconditions: filesystem.mu must be locked. !rp.Done().
|
|
|
|
func stepLocked(rp *vfs.ResolvingPath, d *dentry) (*dentry, error) {
|
|
|
|
if !d.inode.isDir() {
|
|
|
|
return nil, syserror.ENOTDIR
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := d.inode.checkPermissions(rp.Credentials(), vfs.MayExec, true); err != nil {
|
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
afterSymlink:
|
2019-12-23 21:17:29 +00:00
|
|
|
nextVFSD, err := rp.ResolveComponent(&d.vfsd)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
2019-12-23 21:17:29 +00:00
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
if nextVFSD == nil {
|
2020-01-06 20:51:35 +00:00
|
|
|
// Since the Dentry tree is the sole source of truth for tmpfs, if it's
|
2019-07-18 22:09:14 +00:00
|
|
|
// not in the Dentry tree, it doesn't exist.
|
2019-12-23 21:17:29 +00:00
|
|
|
return nil, syserror.ENOENT
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
next := nextVFSD.Impl().(*dentry)
|
|
|
|
if symlink, ok := next.inode.impl.(*symlink); ok && rp.ShouldFollowSymlink() {
|
2020-01-16 00:31:24 +00:00
|
|
|
// TODO(gvisor.dev/issues/1197): Symlink traversals updates
|
|
|
|
// access time.
|
2019-07-18 22:09:14 +00:00
|
|
|
if err := rp.HandleSymlink(symlink.target); err != nil {
|
2019-12-23 21:17:29 +00:00
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
goto afterSymlink // don't check the current directory again
|
|
|
|
}
|
|
|
|
rp.Advance()
|
2019-12-23 21:17:29 +00:00
|
|
|
return next, nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// 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.
|
2019-07-18 22:09:14 +00:00
|
|
|
//
|
2019-12-23 21:17:29 +00:00
|
|
|
// walkParentDirLocked is loosely analogous to Linux's
|
|
|
|
// fs/namei.c:path_parentat().
|
2019-07-18 22:09:14 +00:00
|
|
|
//
|
2019-12-23 21:17:29 +00:00
|
|
|
// Preconditions: filesystem.mu must be locked. !rp.Done().
|
|
|
|
func walkParentDirLocked(rp *vfs.ResolvingPath, d *dentry) (*dentry, error) {
|
|
|
|
for !rp.Final() {
|
|
|
|
next, err := stepLocked(rp, d)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
2019-12-23 21:17:29 +00:00
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
d = next
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if !d.inode.isDir() {
|
|
|
|
return nil, syserror.ENOTDIR
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
return d, nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// resolveLocked resolves rp to an existing file.
|
2019-07-18 22:09:14 +00:00
|
|
|
//
|
2019-12-23 21:17:29 +00:00
|
|
|
// resolveLocked is loosely analogous to Linux's fs/namei.c:path_lookupat().
|
2019-07-18 22:09:14 +00:00
|
|
|
//
|
2019-12-23 21:17:29 +00:00
|
|
|
// Preconditions: filesystem.mu must be locked.
|
|
|
|
func resolveLocked(rp *vfs.ResolvingPath) (*dentry, error) {
|
|
|
|
d := rp.Start().Impl().(*dentry)
|
|
|
|
for !rp.Done() {
|
|
|
|
next, err := stepLocked(rp, d)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
2019-12-23 21:17:29 +00:00
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
d = next
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if rp.MustBeDir() && !d.inode.isDir() {
|
|
|
|
return nil, syserror.ENOTDIR
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
return d, nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// doCreateAt checks that creating a file at rp is permitted, then invokes
|
|
|
|
// create to do so.
|
2019-07-18 22:09:14 +00:00
|
|
|
//
|
2019-12-23 21:17:29 +00:00
|
|
|
// doCreateAt is loosely analogous to a conjunction of Linux's
|
|
|
|
// fs/namei.c:filename_create() and done_path_create().
|
|
|
|
//
|
|
|
|
// Preconditions: !rp.Done(). For the final path component in rp,
|
|
|
|
// !rp.ShouldFollowSymlink().
|
|
|
|
func (fs *filesystem) doCreateAt(rp *vfs.ResolvingPath, dir bool, create func(parent *dentry, name string) error) error {
|
|
|
|
fs.mu.Lock()
|
|
|
|
defer fs.mu.Unlock()
|
|
|
|
parent, err := walkParentDirLocked(rp, rp.Start().Impl().(*dentry))
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
2019-12-23 21:17:29 +00:00
|
|
|
return err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := parent.inode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec, true /* isDir */); err != nil {
|
|
|
|
return err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
name := rp.Component()
|
|
|
|
if name == "." || name == ".." {
|
|
|
|
return syserror.EEXIST
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
// Call parent.vfsd.Child() instead of stepLocked() or rp.ResolveChild(),
|
|
|
|
// because if the child exists we want to return EEXIST immediately instead
|
|
|
|
// of attempting symlink/mount traversal.
|
|
|
|
if parent.vfsd.Child(name) != nil {
|
|
|
|
return syserror.EEXIST
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if !dir && rp.MustBeDir() {
|
2019-07-18 22:09:14 +00:00
|
|
|
return syserror.ENOENT
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
// In memfs, the only way to cause a dentry to be disowned is by removing
|
|
|
|
// it from the filesystem, so this check is equivalent to checking if
|
|
|
|
// parent has been removed.
|
|
|
|
if parent.vfsd.IsDisowned() {
|
|
|
|
return syserror.ENOENT
|
|
|
|
}
|
|
|
|
mnt := rp.Mount()
|
|
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer mnt.EndWrite()
|
|
|
|
return create(parent, name)
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetDentryAt implements vfs.FilesystemImpl.GetDentryAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) GetDentryAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetDentryOptions) (*vfs.Dentry, error) {
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
d, err := resolveLocked(rp)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if opts.CheckSearchable {
|
2019-12-23 21:17:29 +00:00
|
|
|
if !d.inode.isDir() {
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil, syserror.ENOTDIR
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := d.inode.checkPermissions(rp.Credentials(), vfs.MayExec, true /* isDir */); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
d.IncRef()
|
|
|
|
return &d.vfsd, nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// GetParentDentryAt implements vfs.FilesystemImpl.GetParentDentryAt.
|
|
|
|
func (fs *filesystem) GetParentDentryAt(ctx context.Context, rp *vfs.ResolvingPath) (*vfs.Dentry, error) {
|
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
|
|
|
d, err := walkParentDirLocked(rp, rp.Start().Impl().(*dentry))
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
2019-12-23 21:17:29 +00:00
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
d.IncRef()
|
|
|
|
return &d.vfsd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LinkAt implements vfs.FilesystemImpl.LinkAt.
|
|
|
|
func (fs *filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry) error {
|
|
|
|
return fs.doCreateAt(rp, false /* dir */, func(parent *dentry, name string) error {
|
|
|
|
if rp.Mount() != vd.Mount() {
|
|
|
|
return syserror.EXDEV
|
|
|
|
}
|
|
|
|
d := vd.Dentry().Impl().(*dentry)
|
|
|
|
if d.inode.isDir() {
|
|
|
|
return syserror.EPERM
|
|
|
|
}
|
|
|
|
if d.inode.nlink == 0 {
|
|
|
|
return syserror.ENOENT
|
|
|
|
}
|
|
|
|
if d.inode.nlink == maxLinks {
|
|
|
|
return syserror.EMLINK
|
|
|
|
}
|
|
|
|
d.inode.incLinksLocked()
|
|
|
|
child := fs.newDentry(d.inode)
|
|
|
|
parent.vfsd.InsertChild(&child.vfsd, name)
|
|
|
|
parent.inode.impl.(*directory).childList.PushBack(child)
|
|
|
|
return nil
|
|
|
|
})
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MkdirAt implements vfs.FilesystemImpl.MkdirAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MkdirOptions) error {
|
2019-12-23 21:17:29 +00:00
|
|
|
return fs.doCreateAt(rp, true /* dir */, func(parent *dentry, name string) error {
|
|
|
|
if parent.inode.nlink == maxLinks {
|
|
|
|
return syserror.EMLINK
|
|
|
|
}
|
|
|
|
parent.inode.incLinksLocked() // from child's ".."
|
|
|
|
child := fs.newDentry(fs.newDirectory(rp.Credentials(), opts.Mode))
|
|
|
|
parent.vfsd.InsertChild(&child.vfsd, name)
|
|
|
|
parent.inode.impl.(*directory).childList.PushBack(child)
|
|
|
|
return nil
|
|
|
|
})
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MknodAt implements vfs.FilesystemImpl.MknodAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MknodOptions) error {
|
2019-12-23 21:17:29 +00:00
|
|
|
return fs.doCreateAt(rp, false /* dir */, func(parent *dentry, name string) error {
|
|
|
|
switch opts.Mode.FileType() {
|
|
|
|
case 0, linux.S_IFREG:
|
|
|
|
child := fs.newDentry(fs.newRegularFile(rp.Credentials(), opts.Mode))
|
|
|
|
parent.vfsd.InsertChild(&child.vfsd, name)
|
|
|
|
parent.inode.impl.(*directory).childList.PushBack(child)
|
|
|
|
return nil
|
|
|
|
case linux.S_IFIFO:
|
|
|
|
child := fs.newDentry(fs.newNamedPipe(rp.Credentials(), opts.Mode))
|
|
|
|
parent.vfsd.InsertChild(&child.vfsd, name)
|
|
|
|
parent.inode.impl.(*directory).childList.PushBack(child)
|
|
|
|
return nil
|
|
|
|
case linux.S_IFBLK, linux.S_IFCHR, linux.S_IFSOCK:
|
|
|
|
// Not yet supported.
|
|
|
|
return syserror.EPERM
|
|
|
|
default:
|
|
|
|
return syserror.EINVAL
|
|
|
|
}
|
|
|
|
})
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// OpenAt implements vfs.FilesystemImpl.OpenAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
2019-12-23 21:17:29 +00:00
|
|
|
if opts.Flags&linux.O_TMPFILE != 0 {
|
|
|
|
// Not yet supported.
|
|
|
|
return nil, syserror.EOPNOTSUPP
|
|
|
|
}
|
2019-07-18 22:09:14 +00:00
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
// Handle O_CREAT and !O_CREAT separately, since in the latter case we
|
|
|
|
// don't need fs.mu for writing.
|
2019-07-18 22:09:14 +00:00
|
|
|
if opts.Flags&linux.O_CREAT == 0 {
|
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
d, err := resolveLocked(rp)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
return d.open(ctx, rp, opts.Flags, false /* afterCreate */)
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mustCreate := opts.Flags&linux.O_EXCL != 0
|
2019-12-23 21:17:29 +00:00
|
|
|
start := rp.Start().Impl().(*dentry)
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.Lock()
|
|
|
|
defer fs.mu.Unlock()
|
|
|
|
if rp.Done() {
|
2019-12-23 21:17:29 +00:00
|
|
|
// Reject attempts to open directories with O_CREAT.
|
2019-07-18 22:09:14 +00:00
|
|
|
if rp.MustBeDir() {
|
|
|
|
return nil, syserror.EISDIR
|
|
|
|
}
|
|
|
|
if mustCreate {
|
|
|
|
return nil, syserror.EEXIST
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
return start.open(ctx, rp, opts.Flags, false /* afterCreate */)
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
afterTrailingSymlink:
|
2019-12-23 21:17:29 +00:00
|
|
|
parent, err := walkParentDirLocked(rp, start)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
// Check for search permission in the parent directory.
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := parent.inode.checkPermissions(rp.Credentials(), vfs.MayExec, true); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Reject attempts to open directories with O_CREAT.
|
|
|
|
if rp.MustBeDir() {
|
|
|
|
return nil, syserror.EISDIR
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
name := rp.Component()
|
|
|
|
if name == "." || name == ".." {
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil, syserror.EISDIR
|
|
|
|
}
|
|
|
|
// Determine whether or not we need to create a file.
|
2019-12-23 21:17:29 +00:00
|
|
|
child, err := stepLocked(rp, parent)
|
|
|
|
if err == syserror.ENOENT {
|
2019-07-18 22:09:14 +00:00
|
|
|
// Already checked for searchability above; now check for writability.
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := parent.inode.checkPermissions(rp.Credentials(), vfs.MayWrite, true); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := rp.Mount().CheckBeginWrite(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rp.Mount().EndWrite()
|
|
|
|
// Create and open the child.
|
2019-12-23 21:17:29 +00:00
|
|
|
child := fs.newDentry(fs.newRegularFile(rp.Credentials(), opts.Mode))
|
|
|
|
parent.vfsd.InsertChild(&child.vfsd, name)
|
|
|
|
parent.inode.impl.(*directory).childList.PushBack(child)
|
|
|
|
return child.open(ctx, rp, opts.Flags, true)
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
// Do we need to resolve a trailing symlink?
|
|
|
|
if !rp.Done() {
|
|
|
|
start = parent
|
2019-07-18 22:09:14 +00:00
|
|
|
goto afterTrailingSymlink
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
// Open existing file.
|
|
|
|
if mustCreate {
|
|
|
|
return nil, syserror.EEXIST
|
|
|
|
}
|
|
|
|
return child.open(ctx, rp, opts.Flags, false)
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
2019-12-23 21:17:29 +00:00
|
|
|
func (d *dentry) open(ctx context.Context, rp *vfs.ResolvingPath, flags uint32, afterCreate bool) (*vfs.FileDescription, error) {
|
2019-07-18 22:09:14 +00:00
|
|
|
ats := vfs.AccessTypesForOpenFlags(flags)
|
|
|
|
if !afterCreate {
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := d.inode.checkPermissions(rp.Credentials(), ats, d.inode.isDir()); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
Minor VFS2 interface changes.
- Remove the Filesystem argument from DentryImpl.*Ref(); in general DentryImpls
that need the Filesystem for reference counting will probably also need it
for other interface methods that don't plumb Filesystem, so it's easier to
just store a pointer to the filesystem in the DentryImpl.
- Add a pointer to the VirtualFilesystem to Filesystem, which is needed by the
gofer client to disown dentries for cache eviction triggered by dentry
reference count changes.
- Rename FilesystemType.NewFilesystem to GetFilesystem; in some cases (e.g.
sysfs, cgroupfs) it's much cleaner for there to be only one Filesystem that
is used by all mounts, and in at least one case (devtmpfs) it's visibly
incorrect not to do so, so NewFilesystem doesn't always actually create and
return a *new* Filesystem.
- Require callers of FileDescription.Init() to increment Mount/Dentry
references. This is because the gofer client may, in the OpenAt() path, take
a reference on a dentry with 0 references, which is safe due to
synchronization that is outside the scope of this CL, and it would be safer
to still have its implementation of DentryImpl.IncRef() check for an
increment for 0 references in other cases.
- Add FileDescription.TryIncRef. This is used by the gofer client to take
references on "special file descriptions" (FDs for files such as pipes,
sockets, and devices), which use per-FD handles (fids) instead of
dentry-shared handles, for sync() and syncfs().
PiperOrigin-RevId: 282473364
2019-11-26 02:09:15 +00:00
|
|
|
mnt := rp.Mount()
|
2019-12-23 21:17:29 +00:00
|
|
|
switch impl := d.inode.impl.(type) {
|
2019-07-18 22:09:14 +00:00
|
|
|
case *regularFile:
|
|
|
|
var fd regularFileFD
|
|
|
|
fd.readable = vfs.MayReadFileWithOpenFlags(flags)
|
|
|
|
fd.writable = vfs.MayWriteFileWithOpenFlags(flags)
|
|
|
|
if fd.writable {
|
Minor VFS2 interface changes.
- Remove the Filesystem argument from DentryImpl.*Ref(); in general DentryImpls
that need the Filesystem for reference counting will probably also need it
for other interface methods that don't plumb Filesystem, so it's easier to
just store a pointer to the filesystem in the DentryImpl.
- Add a pointer to the VirtualFilesystem to Filesystem, which is needed by the
gofer client to disown dentries for cache eviction triggered by dentry
reference count changes.
- Rename FilesystemType.NewFilesystem to GetFilesystem; in some cases (e.g.
sysfs, cgroupfs) it's much cleaner for there to be only one Filesystem that
is used by all mounts, and in at least one case (devtmpfs) it's visibly
incorrect not to do so, so NewFilesystem doesn't always actually create and
return a *new* Filesystem.
- Require callers of FileDescription.Init() to increment Mount/Dentry
references. This is because the gofer client may, in the OpenAt() path, take
a reference on a dentry with 0 references, which is safe due to
synchronization that is outside the scope of this CL, and it would be safer
to still have its implementation of DentryImpl.IncRef() check for an
increment for 0 references in other cases.
- Add FileDescription.TryIncRef. This is used by the gofer client to take
references on "special file descriptions" (FDs for files such as pipes,
sockets, and devices), which use per-FD handles (fids) instead of
dentry-shared handles, for sync() and syncfs().
PiperOrigin-RevId: 282473364
2019-11-26 02:09:15 +00:00
|
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
Minor VFS2 interface changes.
- Remove the Filesystem argument from DentryImpl.*Ref(); in general DentryImpls
that need the Filesystem for reference counting will probably also need it
for other interface methods that don't plumb Filesystem, so it's easier to
just store a pointer to the filesystem in the DentryImpl.
- Add a pointer to the VirtualFilesystem to Filesystem, which is needed by the
gofer client to disown dentries for cache eviction triggered by dentry
reference count changes.
- Rename FilesystemType.NewFilesystem to GetFilesystem; in some cases (e.g.
sysfs, cgroupfs) it's much cleaner for there to be only one Filesystem that
is used by all mounts, and in at least one case (devtmpfs) it's visibly
incorrect not to do so, so NewFilesystem doesn't always actually create and
return a *new* Filesystem.
- Require callers of FileDescription.Init() to increment Mount/Dentry
references. This is because the gofer client may, in the OpenAt() path, take
a reference on a dentry with 0 references, which is safe due to
synchronization that is outside the scope of this CL, and it would be safer
to still have its implementation of DentryImpl.IncRef() check for an
increment for 0 references in other cases.
- Add FileDescription.TryIncRef. This is used by the gofer client to take
references on "special file descriptions" (FDs for files such as pipes,
sockets, and devices), which use per-FD handles (fids) instead of
dentry-shared handles, for sync() and syncfs().
PiperOrigin-RevId: 282473364
2019-11-26 02:09:15 +00:00
|
|
|
// mnt.EndWrite() is called by regularFileFD.Release().
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
fd.vfsfd.Init(&fd, flags, mnt, &d.vfsd, &vfs.FileDescriptionOptions{})
|
2019-07-18 22:09:14 +00:00
|
|
|
if flags&linux.O_TRUNC != 0 {
|
|
|
|
impl.mu.Lock()
|
2020-01-06 20:51:35 +00:00
|
|
|
impl.data.Truncate(0, impl.memFile)
|
|
|
|
atomic.StoreUint64(&impl.size, 0)
|
2019-07-18 22:09:14 +00:00
|
|
|
impl.mu.Unlock()
|
|
|
|
}
|
|
|
|
return &fd.vfsfd, nil
|
|
|
|
case *directory:
|
|
|
|
// Can't open directories writably.
|
|
|
|
if ats&vfs.MayWrite != 0 {
|
|
|
|
return nil, syserror.EISDIR
|
|
|
|
}
|
|
|
|
var fd directoryFD
|
2019-12-23 21:17:29 +00:00
|
|
|
fd.vfsfd.Init(&fd, flags, mnt, &d.vfsd, &vfs.FileDescriptionOptions{})
|
2019-07-18 22:09:14 +00:00
|
|
|
return &fd.vfsfd, nil
|
|
|
|
case *symlink:
|
|
|
|
// Can't open symlinks without O_PATH (which is unimplemented).
|
|
|
|
return nil, syserror.ELOOP
|
2019-10-19 18:48:09 +00:00
|
|
|
case *namedPipe:
|
2019-12-23 21:17:29 +00:00
|
|
|
return newNamedPipeFD(ctx, impl, rp, &d.vfsd, flags)
|
2019-07-18 22:09:14 +00:00
|
|
|
default:
|
2019-12-23 21:17:29 +00:00
|
|
|
panic(fmt.Sprintf("unknown inode type: %T", d.inode.impl))
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (string, error) {
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.RLock()
|
2019-12-23 21:17:29 +00:00
|
|
|
defer fs.mu.RUnlock()
|
|
|
|
d, err := resolveLocked(rp)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
symlink, ok := d.inode.impl.(*symlink)
|
2019-07-18 22:09:14 +00:00
|
|
|
if !ok {
|
|
|
|
return "", syserror.EINVAL
|
|
|
|
}
|
|
|
|
return symlink.target, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RenameAt implements vfs.FilesystemImpl.RenameAt.
|
2019-12-23 21:17:29 +00:00
|
|
|
func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldParentVD vfs.VirtualDentry, oldName string, opts vfs.RenameOptions) error {
|
|
|
|
if opts.Flags != 0 {
|
|
|
|
// TODO(b/145974740): Support renameat2 flags.
|
|
|
|
return syserror.EINVAL
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
|
|
|
|
// Resolve newParent first to verify that it's on this Mount.
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.Lock()
|
|
|
|
defer fs.mu.Unlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
newParent, err := walkParentDirLocked(rp, rp.Start().Impl().(*dentry))
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
newName := rp.Component()
|
|
|
|
if newName == "." || newName == ".." {
|
|
|
|
return syserror.EBUSY
|
|
|
|
}
|
|
|
|
mnt := rp.Mount()
|
|
|
|
if mnt != oldParentVD.Mount() {
|
|
|
|
return syserror.EXDEV
|
|
|
|
}
|
|
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
defer mnt.EndWrite()
|
|
|
|
|
|
|
|
oldParent := oldParentVD.Dentry().Impl().(*dentry)
|
|
|
|
if err := oldParent.inode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec, true /* isDir */); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
// Call vfs.Dentry.Child() instead of stepLocked() or rp.ResolveChild(),
|
|
|
|
// because if the existing child is a symlink or mount point then we want
|
|
|
|
// to rename over it rather than follow it.
|
|
|
|
renamedVFSD := oldParent.vfsd.Child(oldName)
|
|
|
|
if renamedVFSD == nil {
|
|
|
|
return syserror.ENOENT
|
|
|
|
}
|
|
|
|
renamed := renamedVFSD.Impl().(*dentry)
|
|
|
|
if renamed.inode.isDir() {
|
|
|
|
if renamed == newParent || renamedVFSD.IsAncestorOf(&newParent.vfsd) {
|
|
|
|
return syserror.EINVAL
|
|
|
|
}
|
|
|
|
if oldParent != newParent {
|
|
|
|
// Writability is needed to change renamed's "..".
|
|
|
|
if err := renamed.inode.checkPermissions(rp.Credentials(), vfs.MayWrite, true /* isDir */); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if opts.MustBeDir || rp.MustBeDir() {
|
|
|
|
return syserror.ENOTDIR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := newParent.inode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec, true /* isDir */); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
replacedVFSD := newParent.vfsd.Child(newName)
|
|
|
|
var replaced *dentry
|
|
|
|
if replacedVFSD != nil {
|
|
|
|
replaced = replacedVFSD.Impl().(*dentry)
|
|
|
|
if replaced.inode.isDir() {
|
|
|
|
if !renamed.inode.isDir() {
|
|
|
|
return syserror.EISDIR
|
|
|
|
}
|
|
|
|
if replaced.vfsd.HasChildren() {
|
|
|
|
return syserror.ENOTEMPTY
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if rp.MustBeDir() {
|
|
|
|
return syserror.ENOTDIR
|
|
|
|
}
|
|
|
|
if renamed.inode.isDir() {
|
|
|
|
return syserror.ENOTDIR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if renamed.inode.isDir() && newParent.inode.nlink == maxLinks {
|
|
|
|
return syserror.EMLINK
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if newParent.vfsd.IsDisowned() {
|
|
|
|
return syserror.ENOENT
|
|
|
|
}
|
|
|
|
|
|
|
|
// Linux places this check before some of those above; we do it here for
|
|
|
|
// simplicity, under the assumption that applications are not intentionally
|
|
|
|
// doing noop renames expecting them to succeed where non-noop renames
|
|
|
|
// would fail.
|
|
|
|
if renamedVFSD == replacedVFSD {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
vfsObj := rp.VirtualFilesystem()
|
|
|
|
oldParentDir := oldParent.inode.impl.(*directory)
|
|
|
|
newParentDir := newParent.inode.impl.(*directory)
|
|
|
|
if err := vfsObj.PrepareRenameDentry(vfs.MountNamespaceFromContext(ctx), renamedVFSD, replacedVFSD); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if replaced != nil {
|
|
|
|
newParentDir.childList.Remove(replaced)
|
|
|
|
if replaced.inode.isDir() {
|
|
|
|
newParent.inode.decLinksLocked() // from replaced's ".."
|
|
|
|
}
|
|
|
|
replaced.inode.decLinksLocked()
|
|
|
|
}
|
|
|
|
oldParentDir.childList.Remove(renamed)
|
|
|
|
newParentDir.childList.PushBack(renamed)
|
|
|
|
if renamed.inode.isDir() {
|
|
|
|
oldParent.inode.decLinksLocked()
|
|
|
|
newParent.inode.incLinksLocked()
|
|
|
|
}
|
2020-01-16 00:31:24 +00:00
|
|
|
// TODO(gvisor.dev/issues/1197): Update timestamps and parent directory
|
|
|
|
// sizes.
|
2019-12-23 21:17:29 +00:00
|
|
|
vfsObj.CommitRenameReplaceDentry(renamedVFSD, &newParent.vfsd, newName, replacedVFSD)
|
|
|
|
return nil
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RmdirAt implements vfs.FilesystemImpl.RmdirAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.Lock()
|
|
|
|
defer fs.mu.Unlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
parent, err := walkParentDirLocked(rp, rp.Start().Impl().(*dentry))
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := parent.inode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec, true /* isDir */); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
name := rp.Component()
|
|
|
|
if name == "." {
|
|
|
|
return syserror.EINVAL
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if name == ".." {
|
|
|
|
return syserror.ENOTEMPTY
|
|
|
|
}
|
|
|
|
childVFSD := parent.vfsd.Child(name)
|
|
|
|
if childVFSD == nil {
|
|
|
|
return syserror.ENOENT
|
|
|
|
}
|
|
|
|
child := childVFSD.Impl().(*dentry)
|
|
|
|
if !child.inode.isDir() {
|
2019-07-18 22:09:14 +00:00
|
|
|
return syserror.ENOTDIR
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if childVFSD.HasChildren() {
|
2019-07-18 22:09:14 +00:00
|
|
|
return syserror.ENOTEMPTY
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
mnt := rp.Mount()
|
|
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer mnt.EndWrite()
|
|
|
|
vfsObj := rp.VirtualFilesystem()
|
|
|
|
if err := vfsObj.PrepareDeleteDentry(vfs.MountNamespaceFromContext(ctx), childVFSD); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
parent.inode.impl.(*directory).childList.Remove(child)
|
|
|
|
parent.inode.decLinksLocked() // from child's ".."
|
|
|
|
child.inode.decLinksLocked()
|
|
|
|
vfsObj.CommitDeleteDentry(childVFSD)
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetStatAt implements vfs.FilesystemImpl.SetStatAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error {
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.RLock()
|
2019-12-23 21:17:29 +00:00
|
|
|
defer fs.mu.RUnlock()
|
2020-01-16 00:31:24 +00:00
|
|
|
d, err := resolveLocked(rp)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-16 00:31:24 +00:00
|
|
|
return d.inode.setStat(opts.Stat)
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StatAt implements vfs.FilesystemImpl.StatAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.StatOptions) (linux.Statx, error) {
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.RLock()
|
2019-12-23 21:17:29 +00:00
|
|
|
defer fs.mu.RUnlock()
|
|
|
|
d, err := resolveLocked(rp)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return linux.Statx{}, err
|
|
|
|
}
|
|
|
|
var stat linux.Statx
|
2019-12-23 21:17:29 +00:00
|
|
|
d.inode.statTo(&stat)
|
2019-07-18 22:09:14 +00:00
|
|
|
return stat, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StatFSAt implements vfs.FilesystemImpl.StatFSAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) {
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.RLock()
|
2019-12-23 21:17:29 +00:00
|
|
|
defer fs.mu.RUnlock()
|
|
|
|
_, err := resolveLocked(rp)
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return linux.Statfs{}, err
|
|
|
|
}
|
2020-01-16 00:31:24 +00:00
|
|
|
// TODO(gvisor.dev/issues/1197): Actually implement statfs.
|
2019-07-18 22:09:14 +00:00
|
|
|
return linux.Statfs{}, syserror.ENOSYS
|
|
|
|
}
|
|
|
|
|
|
|
|
// SymlinkAt implements vfs.FilesystemImpl.SymlinkAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, target string) error {
|
2019-12-23 21:17:29 +00:00
|
|
|
return fs.doCreateAt(rp, false /* dir */, func(parent *dentry, name string) error {
|
|
|
|
child := fs.newDentry(fs.newSymlink(rp.Credentials(), target))
|
|
|
|
parent.vfsd.InsertChild(&child.vfsd, name)
|
|
|
|
parent.inode.impl.(*directory).childList.PushBack(child)
|
|
|
|
return nil
|
|
|
|
})
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnlinkAt implements vfs.FilesystemImpl.UnlinkAt.
|
2019-08-08 18:45:33 +00:00
|
|
|
func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
2019-07-18 22:09:14 +00:00
|
|
|
fs.mu.Lock()
|
|
|
|
defer fs.mu.Unlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
parent, err := walkParentDirLocked(rp, rp.Start().Impl().(*dentry))
|
2019-07-18 22:09:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if err := parent.inode.checkPermissions(rp.Credentials(), vfs.MayWrite|vfs.MayExec, true /* isDir */); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
name := rp.Component()
|
|
|
|
if name == "." || name == ".." {
|
|
|
|
return syserror.EISDIR
|
2019-07-18 22:09:14 +00:00
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
childVFSD := parent.vfsd.Child(name)
|
|
|
|
if childVFSD == nil {
|
|
|
|
return syserror.ENOENT
|
|
|
|
}
|
|
|
|
child := childVFSD.Impl().(*dentry)
|
|
|
|
if child.inode.isDir() {
|
2019-07-18 22:09:14 +00:00
|
|
|
return syserror.EISDIR
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
if !rp.MustBeDir() {
|
|
|
|
return syserror.ENOTDIR
|
|
|
|
}
|
|
|
|
mnt := rp.Mount()
|
|
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer mnt.EndWrite()
|
|
|
|
vfsObj := rp.VirtualFilesystem()
|
|
|
|
if err := vfsObj.PrepareDeleteDentry(vfs.MountNamespaceFromContext(ctx), childVFSD); err != nil {
|
2019-07-18 22:09:14 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-12-23 21:17:29 +00:00
|
|
|
parent.inode.impl.(*directory).childList.Remove(child)
|
|
|
|
child.inode.decLinksLocked()
|
|
|
|
vfsObj.CommitDeleteDentry(childVFSD)
|
2019-07-18 22:09:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-12-11 21:40:57 +00:00
|
|
|
|
2019-12-18 23:47:24 +00:00
|
|
|
// ListxattrAt implements vfs.FilesystemImpl.ListxattrAt.
|
|
|
|
func (fs *filesystem) ListxattrAt(ctx context.Context, rp *vfs.ResolvingPath) ([]string, error) {
|
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
_, err := resolveLocked(rp)
|
2019-12-18 23:47:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// TODO(b/127675828): support extended attributes
|
|
|
|
return nil, syserror.ENOTSUP
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetxattrAt implements vfs.FilesystemImpl.GetxattrAt.
|
|
|
|
func (fs *filesystem) GetxattrAt(ctx context.Context, rp *vfs.ResolvingPath, name string) (string, error) {
|
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
_, err := resolveLocked(rp)
|
2019-12-18 23:47:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
// TODO(b/127675828): support extended attributes
|
|
|
|
return "", syserror.ENOTSUP
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetxattrAt implements vfs.FilesystemImpl.SetxattrAt.
|
|
|
|
func (fs *filesystem) SetxattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetxattrOptions) error {
|
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
_, err := resolveLocked(rp)
|
2019-12-18 23:47:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// TODO(b/127675828): support extended attributes
|
|
|
|
return syserror.ENOTSUP
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemovexattrAt implements vfs.FilesystemImpl.RemovexattrAt.
|
|
|
|
func (fs *filesystem) RemovexattrAt(ctx context.Context, rp *vfs.ResolvingPath, name string) error {
|
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
2019-12-23 21:17:29 +00:00
|
|
|
_, err := resolveLocked(rp)
|
2019-12-18 23:47:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// TODO(b/127675828): support extended attributes
|
|
|
|
return syserror.ENOTSUP
|
|
|
|
}
|
|
|
|
|
2019-12-11 21:40:57 +00:00
|
|
|
// PrependPath implements vfs.FilesystemImpl.PrependPath.
|
|
|
|
func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
|
|
|
|
fs.mu.RLock()
|
|
|
|
defer fs.mu.RUnlock()
|
|
|
|
return vfs.GenericPrependPath(vfsroot, vd, b)
|
|
|
|
}
|