mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement kernfs.
PiperOrigin-RevId: 285231002
This commit is contained in:
committed by
gVisor bot
parent
378d6c1f36
commit
007707a072
@@ -286,6 +286,29 @@ func (m FileMode) String() string {
|
||||
return strings.Join(s, "|")
|
||||
}
|
||||
|
||||
// DirentType maps file types to dirent types appropriate for (struct
|
||||
// dirent)::d_type.
|
||||
func (m FileMode) DirentType() uint8 {
|
||||
switch m.FileType() {
|
||||
case ModeSocket:
|
||||
return DT_SOCK
|
||||
case ModeSymlink:
|
||||
return DT_LNK
|
||||
case ModeRegular:
|
||||
return DT_REG
|
||||
case ModeBlockDevice:
|
||||
return DT_BLK
|
||||
case ModeDirectory:
|
||||
return DT_DIR
|
||||
case ModeCharacterDevice:
|
||||
return DT_CHR
|
||||
case ModeNamedPipe:
|
||||
return DT_FIFO
|
||||
default:
|
||||
return DT_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
var modeExtraBits = abi.FlagSet{
|
||||
{
|
||||
Flag: ModeSetUID,
|
||||
|
||||
@@ -92,3 +92,10 @@ const (
|
||||
SYNC_FILE_RANGE_WRITE = 2
|
||||
SYNC_FILE_RANGE_WAIT_AFTER = 4
|
||||
)
|
||||
|
||||
// Flag argument to renameat2(2), from include/uapi/linux/fs.h.
|
||||
const (
|
||||
RENAME_NOREPLACE = (1 << 0) // Don't overwrite target.
|
||||
RENAME_EXCHANGE = (1 << 1) // Exchange src and dst.
|
||||
RENAME_WHITEOUT = (1 << 2) // Whiteout src.
|
||||
)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
load("//tools/go_stateify:defs.bzl", "go_library")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||
load("//tools/go_generics:defs.bzl", "go_template_instance")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_template_instance(
|
||||
name = "slot_list",
|
||||
out = "slot_list.go",
|
||||
package = "kernfs",
|
||||
prefix = "slot",
|
||||
template = "//pkg/ilist:generic_list",
|
||||
types = {
|
||||
"Element": "*slot",
|
||||
"Linker": "*slot",
|
||||
},
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "kernfs",
|
||||
srcs = [
|
||||
"dynamic_bytes_file.go",
|
||||
"fd_impl_util.go",
|
||||
"filesystem.go",
|
||||
"inode_impl_util.go",
|
||||
"kernfs.go",
|
||||
"slot_list.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs",
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/fspath",
|
||||
"//pkg/log",
|
||||
"//pkg/refs",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/usermem",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/syserror",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "kernfs_test",
|
||||
size = "small",
|
||||
srcs = ["kernfs_test.go"],
|
||||
deps = [
|
||||
":kernfs",
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/context/contexttest",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/usermem",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/syserror",
|
||||
"@com_github_google_go-cmp//cmp:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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 kernfs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// DynamicBytesFile implements kernfs.Inode and represents a read-only
|
||||
// file whose contents are backed by a vfs.DynamicBytesSource.
|
||||
//
|
||||
// Must be initialized with Init before first use.
|
||||
type DynamicBytesFile struct {
|
||||
InodeAttrs
|
||||
InodeNoopRefCount
|
||||
InodeNotDirectory
|
||||
InodeNotSymlink
|
||||
|
||||
data vfs.DynamicBytesSource
|
||||
}
|
||||
|
||||
// Init intializes a dynamic bytes file.
|
||||
func (f *DynamicBytesFile) Init(creds *auth.Credentials, ino uint64, data vfs.DynamicBytesSource) {
|
||||
f.InodeAttrs.Init(creds, ino, linux.ModeRegular|0444)
|
||||
f.data = data
|
||||
}
|
||||
|
||||
// Open implements Inode.Open.
|
||||
func (f *DynamicBytesFile) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
|
||||
fd := &DynamicBytesFD{}
|
||||
fd.Init(rp.Mount(), vfsd, f.data, flags)
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
|
||||
// SetStat implements Inode.SetStat.
|
||||
func (f *DynamicBytesFile) SetStat(*vfs.Filesystem, vfs.SetStatOptions) error {
|
||||
// DynamicBytesFiles are immutable.
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// DynamicBytesFD implements vfs.FileDescriptionImpl for an FD backed by a
|
||||
// DynamicBytesFile.
|
||||
//
|
||||
// Must be initialized with Init before first use.
|
||||
type DynamicBytesFD struct {
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.DynamicBytesFileDescriptionImpl
|
||||
|
||||
vfsfd vfs.FileDescription
|
||||
inode Inode
|
||||
flags uint32
|
||||
}
|
||||
|
||||
// Init initializes a DynamicBytesFD.
|
||||
func (fd *DynamicBytesFD) Init(m *vfs.Mount, d *vfs.Dentry, data vfs.DynamicBytesSource, flags uint32) {
|
||||
m.IncRef() // DecRef in vfs.FileDescription.vd.DecRef on final ref.
|
||||
d.IncRef() // DecRef in vfs.FileDescription.vd.DecRef on final ref.
|
||||
fd.flags = flags
|
||||
fd.inode = d.Impl().(*Dentry).inode
|
||||
fd.SetDataSource(data)
|
||||
fd.vfsfd.Init(fd, m, d)
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *DynamicBytesFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
return fd.DynamicBytesFileDescriptionImpl.Seek(ctx, offset, whence)
|
||||
}
|
||||
|
||||
// Read implmenets vfs.FileDescriptionImpl.Read.
|
||||
func (fd *DynamicBytesFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
return fd.DynamicBytesFileDescriptionImpl.Read(ctx, dst, opts)
|
||||
}
|
||||
|
||||
// PRead implmenets vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *DynamicBytesFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
return fd.DynamicBytesFileDescriptionImpl.PRead(ctx, dst, offset, opts)
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *DynamicBytesFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
return fd.FileDescriptionDefaultImpl.Write(ctx, src, opts)
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *DynamicBytesFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
return fd.FileDescriptionDefaultImpl.PWrite(ctx, src, offset, opts)
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *DynamicBytesFD) Release() {}
|
||||
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
func (fd *DynamicBytesFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
fs := fd.vfsfd.VirtualDentry().Mount().Filesystem()
|
||||
return fd.inode.Stat(fs), nil
|
||||
}
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *DynamicBytesFD) SetStat(context.Context, vfs.SetStatOptions) error {
|
||||
// DynamicBytesFiles are immutable.
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// StatusFlags implements vfs.FileDescriptionImpl.StatusFlags.
|
||||
func (fd *DynamicBytesFD) StatusFlags(ctx context.Context) (uint32, error) {
|
||||
return fd.flags, nil
|
||||
}
|
||||
|
||||
// SetStatusFlags implements vfs.FileDescriptionImpl.SetStatusFlags.
|
||||
func (fd *DynamicBytesFD) SetStatusFlags(ctx context.Context, flags uint32) error {
|
||||
// None of the flags settable by fcntl(F_SETFL) are supported, so this is a
|
||||
// no-op.
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// 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 kernfs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// GenericDirectoryFD implements vfs.FileDescriptionImpl for a generic directory
|
||||
// inode that uses OrderChildren to track child nodes. GenericDirectoryFD is not
|
||||
// compatible with dynamic directories.
|
||||
//
|
||||
// Note that GenericDirectoryFD holds a lock over OrderedChildren while calling
|
||||
// IterDirents callback. The IterDirents callback therefore cannot hash or
|
||||
// unhash children, or recursively call IterDirents on the same underlying
|
||||
// inode.
|
||||
//
|
||||
// Must be initialize with Init before first use.
|
||||
type GenericDirectoryFD struct {
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.DirectoryFileDescriptionDefaultImpl
|
||||
|
||||
vfsfd vfs.FileDescription
|
||||
children *OrderedChildren
|
||||
flags uint32
|
||||
off int64
|
||||
}
|
||||
|
||||
// Init initializes a GenericDirectoryFD.
|
||||
func (fd *GenericDirectoryFD) Init(m *vfs.Mount, d *vfs.Dentry, children *OrderedChildren, flags uint32) {
|
||||
m.IncRef() // DecRef in vfs.FileDescription.vd.DecRef on final ref.
|
||||
d.IncRef() // DecRef in vfs.FileDescription.vd.DecRef on final ref.
|
||||
fd.children = children
|
||||
fd.flags = flags
|
||||
fd.vfsfd.Init(fd, m, d)
|
||||
}
|
||||
|
||||
// VFSFileDescription returns a pointer to the vfs.FileDescription representing
|
||||
// this object.
|
||||
func (fd *GenericDirectoryFD) VFSFileDescription() *vfs.FileDescription {
|
||||
return &fd.vfsfd
|
||||
}
|
||||
|
||||
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
|
||||
func (fd *GenericDirectoryFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
||||
return fd.FileDescriptionDefaultImpl.ConfigureMMap(ctx, opts)
|
||||
}
|
||||
|
||||
// Read implmenets vfs.FileDescriptionImpl.Read.
|
||||
func (fd *GenericDirectoryFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
return fd.DirectoryFileDescriptionDefaultImpl.Read(ctx, dst, opts)
|
||||
}
|
||||
|
||||
// PRead implmenets vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *GenericDirectoryFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
return fd.DirectoryFileDescriptionDefaultImpl.PRead(ctx, dst, offset, opts)
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *GenericDirectoryFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
return fd.DirectoryFileDescriptionDefaultImpl.Write(ctx, src, opts)
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *GenericDirectoryFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
return fd.DirectoryFileDescriptionDefaultImpl.PWrite(ctx, src, offset, opts)
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDecriptionImpl.Release.
|
||||
func (fd *GenericDirectoryFD) Release() {}
|
||||
|
||||
func (fd *GenericDirectoryFD) filesystem() *vfs.Filesystem {
|
||||
return fd.vfsfd.VirtualDentry().Mount().Filesystem()
|
||||
}
|
||||
|
||||
func (fd *GenericDirectoryFD) inode() Inode {
|
||||
return fd.vfsfd.VirtualDentry().Dentry().Impl().(*Dentry).inode
|
||||
}
|
||||
|
||||
// IterDirents implements vfs.FileDecriptionImpl.IterDirents. IterDirents holds
|
||||
// o.mu when calling cb.
|
||||
func (fd *GenericDirectoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
|
||||
vfsFS := fd.filesystem()
|
||||
fs := vfsFS.Impl().(*Filesystem)
|
||||
vfsd := fd.vfsfd.VirtualDentry().Dentry()
|
||||
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Handle ".".
|
||||
if fd.off == 0 {
|
||||
stat := fd.inode().Stat(vfsFS)
|
||||
dirent := vfs.Dirent{
|
||||
Name: ".",
|
||||
Type: linux.DT_DIR,
|
||||
Ino: stat.Ino,
|
||||
NextOff: 1,
|
||||
}
|
||||
if !cb.Handle(dirent) {
|
||||
return nil
|
||||
}
|
||||
fd.off++
|
||||
}
|
||||
|
||||
// Handle "..".
|
||||
if fd.off == 1 {
|
||||
parentInode := vfsd.ParentOrSelf().Impl().(*Dentry).inode
|
||||
stat := parentInode.Stat(vfsFS)
|
||||
dirent := vfs.Dirent{
|
||||
Name: "..",
|
||||
Type: linux.FileMode(stat.Mode).DirentType(),
|
||||
Ino: stat.Ino,
|
||||
NextOff: 2,
|
||||
}
|
||||
if !cb.Handle(dirent) {
|
||||
return nil
|
||||
}
|
||||
fd.off++
|
||||
}
|
||||
|
||||
// Handle static children.
|
||||
fd.children.mu.RLock()
|
||||
defer fd.children.mu.RUnlock()
|
||||
// fd.off accounts for "." and "..", but fd.children do not track
|
||||
// these.
|
||||
childIdx := fd.off - 2
|
||||
for it := fd.children.nthLocked(childIdx); it != nil; it = it.Next() {
|
||||
inode := it.Dentry.Impl().(*Dentry).inode
|
||||
stat := inode.Stat(vfsFS)
|
||||
dirent := vfs.Dirent{
|
||||
Name: it.Name,
|
||||
Type: linux.FileMode(stat.Mode).DirentType(),
|
||||
Ino: stat.Ino,
|
||||
NextOff: fd.off + 1,
|
||||
}
|
||||
if !cb.Handle(dirent) {
|
||||
return nil
|
||||
}
|
||||
fd.off++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDecriptionImpl.Seek.
|
||||
func (fd *GenericDirectoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
fs := fd.filesystem().Impl().(*Filesystem)
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
switch whence {
|
||||
case linux.SEEK_SET:
|
||||
// Use offset as given.
|
||||
case linux.SEEK_CUR:
|
||||
offset += fd.off
|
||||
default:
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
fd.off = offset
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// StatusFlags implements vfs.FileDescriptionImpl.StatusFlags.
|
||||
func (fd *GenericDirectoryFD) StatusFlags(ctx context.Context) (uint32, error) {
|
||||
return fd.flags, nil
|
||||
}
|
||||
|
||||
// SetStatusFlags implements vfs.FileDescriptionImpl.SetStatusFlags.
|
||||
func (fd *GenericDirectoryFD) SetStatusFlags(ctx context.Context, flags uint32) error {
|
||||
// None of the flags settable by fcntl(F_SETFL) are supported, so this is a
|
||||
// no-op.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
func (fd *GenericDirectoryFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
fs := fd.filesystem()
|
||||
inode := fd.inode()
|
||||
return inode.Stat(fs), nil
|
||||
}
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *GenericDirectoryFD) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
fs := fd.filesystem()
|
||||
inode := fd.vfsfd.VirtualDentry().Dentry().Impl().(*Dentry).inode
|
||||
return inode.SetStat(fs, opts)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,492 @@
|
||||
// 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 kernfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/refs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// InodeNoopRefCount partially implements the Inode interface, specifically the
|
||||
// inodeRefs sub interface. InodeNoopRefCount implements a simple reference
|
||||
// count for inodes, performing no extra actions when references are obtained or
|
||||
// released. This is suitable for simple file inodes that don't reference any
|
||||
// resources.
|
||||
type InodeNoopRefCount struct {
|
||||
}
|
||||
|
||||
// IncRef implements Inode.IncRef.
|
||||
func (n *InodeNoopRefCount) IncRef() {
|
||||
}
|
||||
|
||||
// DecRef implements Inode.DecRef.
|
||||
func (n *InodeNoopRefCount) DecRef() {
|
||||
}
|
||||
|
||||
// TryIncRef implements Inode.TryIncRef.
|
||||
func (n *InodeNoopRefCount) TryIncRef() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy implements Inode.Destroy.
|
||||
func (n *InodeNoopRefCount) Destroy() {
|
||||
}
|
||||
|
||||
// InodeDirectoryNoNewChildren partially implements the Inode interface.
|
||||
// InodeDirectoryNoNewChildren represents a directory inode which does not
|
||||
// support creation of new children.
|
||||
type InodeDirectoryNoNewChildren struct{}
|
||||
|
||||
// NewFile implements Inode.NewFile.
|
||||
func (*InodeDirectoryNoNewChildren) NewFile(context.Context, string, vfs.OpenOptions) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
// NewDir implements Inode.NewDir.
|
||||
func (*InodeDirectoryNoNewChildren) NewDir(context.Context, string, vfs.MkdirOptions) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
// NewLink implements Inode.NewLink.
|
||||
func (*InodeDirectoryNoNewChildren) NewLink(context.Context, string, Inode) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
// NewSymlink implements Inode.NewSymlink.
|
||||
func (*InodeDirectoryNoNewChildren) NewSymlink(context.Context, string, string) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
// NewNode implements Inode.NewNode.
|
||||
func (*InodeDirectoryNoNewChildren) NewNode(context.Context, string, vfs.MknodOptions) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
// InodeNotDirectory partially implements the Inode interface, specifically the
|
||||
// inodeDirectory and inodeDynamicDirectory sub interfaces. Inodes that do not
|
||||
// represent directories can embed this to provide no-op implementations for
|
||||
// directory-related functions.
|
||||
type InodeNotDirectory struct {
|
||||
}
|
||||
|
||||
// HasChildren implements Inode.HasChildren.
|
||||
func (*InodeNotDirectory) HasChildren() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// NewFile implements Inode.NewFile.
|
||||
func (*InodeNotDirectory) NewFile(context.Context, string, vfs.OpenOptions) (*vfs.Dentry, error) {
|
||||
panic("NewFile called on non-directory inode")
|
||||
}
|
||||
|
||||
// NewDir implements Inode.NewDir.
|
||||
func (*InodeNotDirectory) NewDir(context.Context, string, vfs.MkdirOptions) (*vfs.Dentry, error) {
|
||||
panic("NewDir called on non-directory inode")
|
||||
}
|
||||
|
||||
// NewLink implements Inode.NewLinkink.
|
||||
func (*InodeNotDirectory) NewLink(context.Context, string, Inode) (*vfs.Dentry, error) {
|
||||
panic("NewLink called on non-directory inode")
|
||||
}
|
||||
|
||||
// NewSymlink implements Inode.NewSymlink.
|
||||
func (*InodeNotDirectory) NewSymlink(context.Context, string, string) (*vfs.Dentry, error) {
|
||||
panic("NewSymlink called on non-directory inode")
|
||||
}
|
||||
|
||||
// NewNode implements Inode.NewNode.
|
||||
func (*InodeNotDirectory) NewNode(context.Context, string, vfs.MknodOptions) (*vfs.Dentry, error) {
|
||||
panic("NewNode called on non-directory inode")
|
||||
}
|
||||
|
||||
// Unlink implements Inode.Unlink.
|
||||
func (*InodeNotDirectory) Unlink(context.Context, string, *vfs.Dentry) error {
|
||||
panic("Unlink called on non-directory inode")
|
||||
}
|
||||
|
||||
// RmDir implements Inode.RmDir.
|
||||
func (*InodeNotDirectory) RmDir(context.Context, string, *vfs.Dentry) error {
|
||||
panic("RmDir called on non-directory inode")
|
||||
}
|
||||
|
||||
// Rename implements Inode.Rename.
|
||||
func (*InodeNotDirectory) Rename(context.Context, string, string, *vfs.Dentry, *vfs.Dentry) (*vfs.Dentry, error) {
|
||||
panic("Rename called on non-directory inode")
|
||||
}
|
||||
|
||||
// Lookup implements Inode.Lookup.
|
||||
func (*InodeNotDirectory) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
|
||||
panic("Lookup called on non-directory inode")
|
||||
}
|
||||
|
||||
// Valid implements Inode.Valid.
|
||||
func (*InodeNotDirectory) Valid(context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// InodeNoDynamicLookup partially implements the Inode interface, specifically
|
||||
// the inodeDynamicLookup sub interface. Directory inodes that do not support
|
||||
// dymanic entries (i.e. entries that are not "hashed" into the
|
||||
// vfs.Dentry.children) can embed this to provide no-op implementations for
|
||||
// functions related to dynamic entries.
|
||||
type InodeNoDynamicLookup struct{}
|
||||
|
||||
// Lookup implements Inode.Lookup.
|
||||
func (*InodeNoDynamicLookup) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
|
||||
return nil, syserror.ENOENT
|
||||
}
|
||||
|
||||
// Valid implements Inode.Valid.
|
||||
func (*InodeNoDynamicLookup) Valid(ctx context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// InodeNotSymlink partially implements the Inode interface, specifically the
|
||||
// inodeSymlink sub interface. All inodes that are not symlinks may embed this
|
||||
// to return the appropriate errors from symlink-related functions.
|
||||
type InodeNotSymlink struct{}
|
||||
|
||||
// Readlink implements Inode.Readlink.
|
||||
func (*InodeNotSymlink) Readlink(context.Context) (string, error) {
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
|
||||
// InodeAttrs partially implements the Inode interface, specifically the
|
||||
// inodeMetadata sub interface. InodeAttrs provides functionality related to
|
||||
// inode attributes.
|
||||
//
|
||||
// Must be initialized by Init prior to first use.
|
||||
type InodeAttrs struct {
|
||||
ino uint64
|
||||
mode uint32
|
||||
uid uint32
|
||||
gid uint32
|
||||
nlink uint32
|
||||
}
|
||||
|
||||
// Init initializes this InodeAttrs.
|
||||
func (a *InodeAttrs) Init(creds *auth.Credentials, ino uint64, mode linux.FileMode) {
|
||||
if mode.FileType() == 0 {
|
||||
panic(fmt.Sprintf("No file type specified in 'mode' for InodeAttrs.Init(): mode=0%o", mode))
|
||||
}
|
||||
|
||||
nlink := uint32(1)
|
||||
if mode.FileType() == linux.ModeDirectory {
|
||||
nlink = 2
|
||||
}
|
||||
atomic.StoreUint64(&a.ino, ino)
|
||||
atomic.StoreUint32(&a.mode, uint32(mode))
|
||||
atomic.StoreUint32(&a.uid, uint32(creds.EffectiveKUID))
|
||||
atomic.StoreUint32(&a.gid, uint32(creds.EffectiveKGID))
|
||||
atomic.StoreUint32(&a.nlink, nlink)
|
||||
}
|
||||
|
||||
// Mode implements Inode.Mode.
|
||||
func (a *InodeAttrs) Mode() linux.FileMode {
|
||||
return linux.FileMode(atomic.LoadUint32(&a.mode))
|
||||
}
|
||||
|
||||
// Stat partially implements Inode.Stat. Note that this function doesn't provide
|
||||
// all the stat fields, and the embedder should consider extending the result
|
||||
// with filesystem-specific fields.
|
||||
func (a *InodeAttrs) Stat(*vfs.Filesystem) linux.Statx {
|
||||
var stat linux.Statx
|
||||
stat.Mask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO | linux.STATX_NLINK
|
||||
stat.Ino = atomic.LoadUint64(&a.ino)
|
||||
stat.Mode = uint16(a.Mode())
|
||||
stat.UID = atomic.LoadUint32(&a.uid)
|
||||
stat.GID = atomic.LoadUint32(&a.gid)
|
||||
stat.Nlink = atomic.LoadUint32(&a.nlink)
|
||||
|
||||
// TODO: Implement other stat fields like timestamps.
|
||||
|
||||
return stat
|
||||
}
|
||||
|
||||
// SetStat implements Inode.SetStat.
|
||||
func (a *InodeAttrs) SetStat(_ *vfs.Filesystem, opts vfs.SetStatOptions) error {
|
||||
stat := opts.Stat
|
||||
if stat.Mask&linux.STATX_MODE != 0 {
|
||||
for {
|
||||
old := atomic.LoadUint32(&a.mode)
|
||||
new := old | uint32(stat.Mode & ^uint16(linux.S_IFMT))
|
||||
if swapped := atomic.CompareAndSwapUint32(&a.mode, old, new); swapped {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if stat.Mask&linux.STATX_UID != 0 {
|
||||
atomic.StoreUint32(&a.uid, stat.UID)
|
||||
}
|
||||
if stat.Mask&linux.STATX_GID != 0 {
|
||||
atomic.StoreUint32(&a.gid, stat.GID)
|
||||
}
|
||||
|
||||
// Note that not all fields are modifiable. For example, the file type and
|
||||
// inode numbers are immutable after node creation.
|
||||
|
||||
// TODO: Implement other stat fields like timestamps.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPermissions implements Inode.CheckPermissions.
|
||||
func (a *InodeAttrs) CheckPermissions(creds *auth.Credentials, ats vfs.AccessTypes) error {
|
||||
mode := a.Mode()
|
||||
return vfs.GenericCheckPermissions(
|
||||
creds,
|
||||
ats,
|
||||
mode.FileType() == linux.ModeDirectory,
|
||||
uint16(mode),
|
||||
auth.KUID(atomic.LoadUint32(&a.uid)),
|
||||
auth.KGID(atomic.LoadUint32(&a.gid)),
|
||||
)
|
||||
}
|
||||
|
||||
// IncLinks implements Inode.IncLinks.
|
||||
func (a *InodeAttrs) IncLinks(n uint32) {
|
||||
if atomic.AddUint32(&a.nlink, n) <= n {
|
||||
panic("InodeLink.IncLinks called with no existing links")
|
||||
}
|
||||
}
|
||||
|
||||
// DecLinks implements Inode.DecLinks.
|
||||
func (a *InodeAttrs) DecLinks() {
|
||||
if nlink := atomic.AddUint32(&a.nlink, ^uint32(0)); nlink == ^uint32(0) {
|
||||
// Negative overflow
|
||||
panic("Inode.DecLinks called at 0 links")
|
||||
}
|
||||
}
|
||||
|
||||
type slot struct {
|
||||
Name string
|
||||
Dentry *vfs.Dentry
|
||||
slotEntry
|
||||
}
|
||||
|
||||
// OrderedChildrenOptions contains initialization options for OrderedChildren.
|
||||
type OrderedChildrenOptions struct {
|
||||
// Writable indicates whether vfs.FilesystemImpl methods implemented by
|
||||
// OrderedChildren may modify the tracked children. This applies to
|
||||
// operations related to rename, unlink and rmdir. If an OrderedChildren is
|
||||
// not writable, these operations all fail with EPERM.
|
||||
Writable bool
|
||||
}
|
||||
|
||||
// OrderedChildren partially implements the Inode interface. OrderedChildren can
|
||||
// be embedded in directory inodes to keep track of the children in the
|
||||
// directory, and can then be used to implement a generic directory FD -- see
|
||||
// GenericDirectoryFD. OrderedChildren is not compatible with dynamic
|
||||
// directories.
|
||||
//
|
||||
// Must be initialize with Init before first use.
|
||||
type OrderedChildren struct {
|
||||
refs.AtomicRefCount
|
||||
|
||||
// Can children be modified by user syscalls? It set to false, interface
|
||||
// methods that would modify the children return EPERM. Immutable.
|
||||
writable bool
|
||||
|
||||
mu sync.RWMutex
|
||||
order slotList
|
||||
set map[string]*slot
|
||||
}
|
||||
|
||||
// Init initializes an OrderedChildren.
|
||||
func (o *OrderedChildren) Init(opts OrderedChildrenOptions) {
|
||||
o.writable = opts.Writable
|
||||
o.set = make(map[string]*slot)
|
||||
}
|
||||
|
||||
// DecRef implements Inode.DecRef.
|
||||
func (o *OrderedChildren) DecRef() {
|
||||
o.AtomicRefCount.DecRefWithDestructor(o.Destroy)
|
||||
}
|
||||
|
||||
// Destroy cleans up resources referenced by this OrderedChildren.
|
||||
func (o *OrderedChildren) Destroy() {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
o.order.Reset()
|
||||
o.set = nil
|
||||
}
|
||||
|
||||
// Populate inserts children into this OrderedChildren, and d's dentry
|
||||
// cache. Populate returns the number of directories inserted, which the caller
|
||||
// may use to update the link count for the parent directory.
|
||||
//
|
||||
// Precondition: d.Impl() must be a kernfs Dentry. d must represent a directory
|
||||
// inode. children must not contain any conflicting entries already in o.
|
||||
func (o *OrderedChildren) Populate(d *Dentry, children map[string]*Dentry) uint32 {
|
||||
var links uint32
|
||||
for name, child := range children {
|
||||
if child.isDir() {
|
||||
links++
|
||||
}
|
||||
if err := o.Insert(name, child.VFSDentry()); err != nil {
|
||||
panic(fmt.Sprintf("Collision when attempting to insert child %q (%+v) into %+v", name, child, d))
|
||||
}
|
||||
d.InsertChild(name, child.VFSDentry())
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
// HasChildren implements Inode.HasChildren.
|
||||
func (o *OrderedChildren) HasChildren() bool {
|
||||
o.mu.RLock()
|
||||
defer o.mu.RUnlock()
|
||||
return len(o.set) > 0
|
||||
}
|
||||
|
||||
// Insert inserts child into o. This ignores the writability of o, as this is
|
||||
// not part of the vfs.FilesystemImpl interface, and is a lower-level operation.
|
||||
func (o *OrderedChildren) Insert(name string, child *vfs.Dentry) error {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if _, ok := o.set[name]; ok {
|
||||
return syserror.EEXIST
|
||||
}
|
||||
s := &slot{
|
||||
Name: name,
|
||||
Dentry: child,
|
||||
}
|
||||
o.order.PushBack(s)
|
||||
o.set[name] = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Precondition: caller must hold o.mu for writing.
|
||||
func (o *OrderedChildren) removeLocked(name string) {
|
||||
if s, ok := o.set[name]; ok {
|
||||
delete(o.set, name)
|
||||
o.order.Remove(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: caller must hold o.mu for writing.
|
||||
func (o *OrderedChildren) replaceChildLocked(name string, new *vfs.Dentry) *vfs.Dentry {
|
||||
if s, ok := o.set[name]; ok {
|
||||
// Existing slot with given name, simply replace the dentry.
|
||||
var old *vfs.Dentry
|
||||
old, s.Dentry = s.Dentry, new
|
||||
return old
|
||||
}
|
||||
|
||||
// No existing slot with given name, create and hash new slot.
|
||||
s := &slot{
|
||||
Name: name,
|
||||
Dentry: new,
|
||||
}
|
||||
o.order.PushBack(s)
|
||||
o.set[name] = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Precondition: caller must hold o.mu for reading or writing.
|
||||
func (o *OrderedChildren) checkExistingLocked(name string, child *vfs.Dentry) error {
|
||||
s, ok := o.set[name]
|
||||
if !ok {
|
||||
return syserror.ENOENT
|
||||
}
|
||||
if s.Dentry != child {
|
||||
panic(fmt.Sprintf("Dentry hashed into inode doesn't match what vfs thinks! OrderedChild: %+v, vfs: %+v", s.Dentry, child))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlink implements Inode.Unlink.
|
||||
func (o *OrderedChildren) Unlink(ctx context.Context, name string, child *vfs.Dentry) error {
|
||||
if !o.writable {
|
||||
return syserror.EPERM
|
||||
}
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if err := o.checkExistingLocked(name, child); err != nil {
|
||||
return err
|
||||
}
|
||||
o.removeLocked(name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rmdir implements Inode.Rmdir.
|
||||
func (o *OrderedChildren) RmDir(ctx context.Context, name string, child *vfs.Dentry) error {
|
||||
// We're not responsible for checking that child is a directory, that it's
|
||||
// empty, or updating any link counts; so this is the same as unlink.
|
||||
return o.Unlink(ctx, name, child)
|
||||
}
|
||||
|
||||
type renameAcrossDifferentImplementationsError struct{}
|
||||
|
||||
func (renameAcrossDifferentImplementationsError) Error() string {
|
||||
return "rename across inodes with different implementations"
|
||||
}
|
||||
|
||||
// Rename implements Inode.Rename.
|
||||
//
|
||||
// Precondition: Rename may only be called across two directory inodes with
|
||||
// identical implementations of Rename. Practically, this means filesystems that
|
||||
// implement Rename by embedding OrderedChildren for any directory
|
||||
// implementation must use OrderedChildren for all directory implementations
|
||||
// that will support Rename.
|
||||
//
|
||||
// Postcondition: reference on any replaced dentry transferred to caller.
|
||||
func (o *OrderedChildren) Rename(ctx context.Context, oldname, newname string, child, dstDir *vfs.Dentry) (*vfs.Dentry, error) {
|
||||
dst, ok := dstDir.Impl().(*Dentry).inode.(interface{}).(*OrderedChildren)
|
||||
if !ok {
|
||||
return nil, renameAcrossDifferentImplementationsError{}
|
||||
}
|
||||
if !o.writable || !dst.writable {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
// Note: There's a potential deadlock below if concurrent calls to Rename
|
||||
// refer to the same src and dst directories in reverse. We avoid any
|
||||
// ordering issues because the caller is required to serialize concurrent
|
||||
// calls to Rename in accordance with the interface declaration.
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if dst != o {
|
||||
dst.mu.Lock()
|
||||
defer dst.mu.Unlock()
|
||||
}
|
||||
if err := o.checkExistingLocked(oldname, child); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
replaced := dst.replaceChildLocked(newname, child)
|
||||
return replaced, nil
|
||||
}
|
||||
|
||||
// nthLocked returns an iterator to the nth child tracked by this object. The
|
||||
// iterator is valid until the caller releases o.mu. Returns nil if the
|
||||
// requested index falls out of bounds.
|
||||
//
|
||||
// Preconditon: Caller must hold o.mu for reading.
|
||||
func (o *OrderedChildren) nthLocked(i int64) *slot {
|
||||
for it := o.order.Front(); it != nil && i >= 0; it = it.Next() {
|
||||
if i == 0 {
|
||||
return it
|
||||
}
|
||||
i--
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
// 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 kernfs provides the tools to implement inode-based filesystems.
|
||||
// Kernfs has two main features:
|
||||
//
|
||||
// 1. The Inode interface, which maps VFS2's path-based filesystem operations to
|
||||
// specific filesystem nodes. Kernfs uses the Inode interface to provide a
|
||||
// blanket implementation for the vfs.FilesystemImpl. Kernfs also serves as
|
||||
// the synchronization mechanism for all filesystem operations by holding a
|
||||
// filesystem-wide lock across all operations.
|
||||
//
|
||||
// 2. Various utility types which provide generic implementations for various
|
||||
// parts of the Inode and vfs.FileDescription interfaces. Client filesystems
|
||||
// based on kernfs can embed the appropriate set of these to avoid having to
|
||||
// reimplement common filesystem operations. See inode_impl_util.go and
|
||||
// fd_impl_util.go.
|
||||
//
|
||||
// Reference Model:
|
||||
//
|
||||
// Kernfs dentries represents named pointers to inodes. Dentries and inode have
|
||||
// independent lifetimes and reference counts. A child dentry unconditionally
|
||||
// holds a reference on its parent directory's dentry. A dentry also holds a
|
||||
// reference on the inode it points to. Multiple dentries can point to the same
|
||||
// inode (for example, in the case of hardlinks). File descriptors hold a
|
||||
// reference to the dentry they're opened on.
|
||||
//
|
||||
// Dentries are guaranteed to exist while holding Filesystem.mu for
|
||||
// reading. Dropping dentries require holding Filesystem.mu for writing. To
|
||||
// queue dentries for destruction from a read critical section, see
|
||||
// Filesystem.deferDecRef.
|
||||
//
|
||||
// Lock ordering:
|
||||
//
|
||||
// kernfs.Filesystem.mu
|
||||
// kernfs.Dentry.dirMu
|
||||
// vfs.VirtualFilesystem.mountMu
|
||||
// vfs.Dentry.mu
|
||||
// kernfs.Filesystem.droppedDentriesMu
|
||||
// (inode implementation locks, if any)
|
||||
package kernfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/refs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
// FilesystemType implements vfs.FilesystemType.
|
||||
type FilesystemType struct{}
|
||||
|
||||
// Filesystem mostly implements vfs.FilesystemImpl for a generic in-memory
|
||||
// filesystem. Concrete implementations are expected to embed this in their own
|
||||
// Filesystem type.
|
||||
type Filesystem struct {
|
||||
vfsfs vfs.Filesystem
|
||||
|
||||
droppedDentriesMu sync.Mutex
|
||||
|
||||
// droppedDentries is a list of dentries waiting to be DecRef()ed. This is
|
||||
// used to defer dentry destruction until mu can be acquired for
|
||||
// writing. Protected by droppedDentriesMu.
|
||||
droppedDentries []*vfs.Dentry
|
||||
|
||||
// mu synchronizes the lifetime of Dentries on this filesystem. Holding it
|
||||
// for reading guarantees continued existence of any resolved dentries, but
|
||||
// the dentry tree may be modified.
|
||||
//
|
||||
// Kernfs dentries can only be DecRef()ed while holding mu for writing. For
|
||||
// example:
|
||||
//
|
||||
// fs.mu.Lock()
|
||||
// defer fs.mu.Unlock()
|
||||
// ...
|
||||
// dentry1.DecRef()
|
||||
// defer dentry2.DecRef() // Ok, will run before Unlock.
|
||||
//
|
||||
// If discarding dentries in a read context, use Filesystem.deferDecRef. For
|
||||
// example:
|
||||
//
|
||||
// fs.mu.RLock()
|
||||
// fs.mu.processDeferredDecRefs()
|
||||
// defer fs.mu.RUnlock()
|
||||
// ...
|
||||
// fs.deferDecRef(dentry)
|
||||
mu sync.RWMutex
|
||||
|
||||
// nextInoMinusOne is used to to allocate inode numbers on this
|
||||
// filesystem. Must be accessed by atomic operations.
|
||||
nextInoMinusOne uint64
|
||||
}
|
||||
|
||||
// deferDecRef defers dropping a dentry ref until the next call to
|
||||
// processDeferredDecRefs{,Locked}. See comment on Filesystem.mu.
|
||||
//
|
||||
// Precondition: d must not already be pending destruction.
|
||||
func (fs *Filesystem) deferDecRef(d *vfs.Dentry) {
|
||||
fs.droppedDentriesMu.Lock()
|
||||
fs.droppedDentries = append(fs.droppedDentries, d)
|
||||
fs.droppedDentriesMu.Unlock()
|
||||
}
|
||||
|
||||
// processDeferredDecRefs calls vfs.Dentry.DecRef on all dentries in the
|
||||
// droppedDentries list. See comment on Filesystem.mu.
|
||||
func (fs *Filesystem) processDeferredDecRefs() {
|
||||
fs.mu.Lock()
|
||||
fs.processDeferredDecRefsLocked()
|
||||
fs.mu.Unlock()
|
||||
}
|
||||
|
||||
// Precondition: fs.mu must be held for writing.
|
||||
func (fs *Filesystem) processDeferredDecRefsLocked() {
|
||||
fs.droppedDentriesMu.Lock()
|
||||
for _, d := range fs.droppedDentries {
|
||||
d.DecRef()
|
||||
}
|
||||
fs.droppedDentries = fs.droppedDentries[:0] // Keep slice memory for reuse.
|
||||
fs.droppedDentriesMu.Unlock()
|
||||
}
|
||||
|
||||
// Init initializes a kernfs filesystem. This should be called from during
|
||||
// vfs.FilesystemType.NewFilesystem for the concrete filesystem embedding
|
||||
// kernfs.
|
||||
func (fs *Filesystem) Init(vfsObj *vfs.VirtualFilesystem) {
|
||||
fs.vfsfs.Init(vfsObj, fs)
|
||||
}
|
||||
|
||||
// VFSFilesystem returns the generic vfs filesystem object.
|
||||
func (fs *Filesystem) VFSFilesystem() *vfs.Filesystem {
|
||||
return &fs.vfsfs
|
||||
}
|
||||
|
||||
// NextIno allocates a new inode number on this filesystem.
|
||||
func (fs *Filesystem) NextIno() uint64 {
|
||||
return atomic.AddUint64(&fs.nextInoMinusOne, 1)
|
||||
}
|
||||
|
||||
// These consts are used in the Dentry.flags field.
|
||||
const (
|
||||
// Dentry points to a directory inode.
|
||||
dflagsIsDir = 1 << iota
|
||||
|
||||
// Dentry points to a symlink inode.
|
||||
dflagsIsSymlink
|
||||
)
|
||||
|
||||
// Dentry implements vfs.DentryImpl.
|
||||
//
|
||||
// A kernfs dentry is similar to a dentry in a traditional filesystem: it's a
|
||||
// named reference to an inode. A dentry generally lives as long as it's part of
|
||||
// a mounted filesystem tree. Kernfs doesn't cache dentries once all references
|
||||
// to them are removed. Dentries hold a single reference to the inode they point
|
||||
// to, and child dentries hold a reference on their parent.
|
||||
//
|
||||
// Must be initialized by Init prior to first use.
|
||||
type Dentry struct {
|
||||
refs.AtomicRefCount
|
||||
|
||||
vfsd vfs.Dentry
|
||||
inode Inode
|
||||
|
||||
refs uint64
|
||||
|
||||
// flags caches useful information about the dentry from the inode. See the
|
||||
// dflags* consts above. Must be accessed by atomic ops.
|
||||
flags uint32
|
||||
|
||||
// dirMu protects vfsd.children for directory dentries.
|
||||
dirMu sync.Mutex
|
||||
}
|
||||
|
||||
// Init initializes this dentry.
|
||||
//
|
||||
// Precondition: Caller must hold a reference on inode.
|
||||
//
|
||||
// Postcondition: Caller's reference on inode is transferred to the dentry.
|
||||
func (d *Dentry) Init(inode Inode) {
|
||||
d.vfsd.Init(d)
|
||||
d.inode = inode
|
||||
ftype := inode.Mode().FileType()
|
||||
if ftype == linux.ModeDirectory {
|
||||
d.flags |= dflagsIsDir
|
||||
}
|
||||
if ftype == linux.ModeSymlink {
|
||||
d.flags |= dflagsIsSymlink
|
||||
}
|
||||
}
|
||||
|
||||
// VFSDentry returns the generic vfs dentry for this kernfs dentry.
|
||||
func (d *Dentry) VFSDentry() *vfs.Dentry {
|
||||
return &d.vfsd
|
||||
}
|
||||
|
||||
// isDir checks whether the dentry points to a directory inode.
|
||||
func (d *Dentry) isDir() bool {
|
||||
return atomic.LoadUint32(&d.flags)&dflagsIsDir != 0
|
||||
}
|
||||
|
||||
// isSymlink checks whether the dentry points to a symlink inode.
|
||||
func (d *Dentry) isSymlink() bool {
|
||||
return atomic.LoadUint32(&d.flags)&dflagsIsSymlink != 0
|
||||
}
|
||||
|
||||
// DecRef implements vfs.DentryImpl.DecRef.
|
||||
func (d *Dentry) DecRef() {
|
||||
d.AtomicRefCount.DecRefWithDestructor(d.destroy)
|
||||
}
|
||||
|
||||
// Precondition: Dentry must be removed from VFS' dentry cache.
|
||||
func (d *Dentry) destroy() {
|
||||
d.inode.DecRef() // IncRef from Init.
|
||||
d.inode = nil
|
||||
if parent := d.vfsd.Parent(); parent != nil {
|
||||
parent.DecRef() // IncRef from Dentry.InsertChild.
|
||||
}
|
||||
}
|
||||
|
||||
// InsertChild inserts child into the vfs dentry cache with the given name under
|
||||
// this dentry. This does not update the directory inode, so calling this on
|
||||
// it's own isn't sufficient to insert a child into a directory. InsertChild
|
||||
// updates the link count on d if required.
|
||||
//
|
||||
// Precondition: d must represent a directory inode.
|
||||
func (d *Dentry) InsertChild(name string, child *vfs.Dentry) {
|
||||
if !d.isDir() {
|
||||
panic(fmt.Sprintf("InsertChild called on non-directory Dentry: %+v.", d))
|
||||
}
|
||||
vfsDentry := d.VFSDentry()
|
||||
vfsDentry.IncRef() // DecRef in child's Dentry.destroy.
|
||||
d.dirMu.Lock()
|
||||
vfsDentry.InsertChild(child, name)
|
||||
d.dirMu.Unlock()
|
||||
}
|
||||
|
||||
// The Inode interface maps filesystem-level operations that operate on paths to
|
||||
// equivalent operations on specific filesystem nodes.
|
||||
//
|
||||
// The interface methods are groups into logical categories as sub interfaces
|
||||
// below. Generally, an implementation for each sub interface can be provided by
|
||||
// embedding an appropriate type from inode_impl_utils.go. The sub interfaces
|
||||
// are purely organizational. Methods declared directly in the main interface
|
||||
// have no generic implementations, and should be explicitly provided by the
|
||||
// client filesystem.
|
||||
//
|
||||
// Generally, implementations are not responsible for tasks that are common to
|
||||
// all filesystems. These include:
|
||||
//
|
||||
// - Checking that dentries passed to methods are of the appropriate file type.
|
||||
// - Checking permissions.
|
||||
// - Updating link and reference counts.
|
||||
//
|
||||
// Specific responsibilities of implementations are documented below.
|
||||
type Inode interface {
|
||||
// Methods related to reference counting. A generic implementation is
|
||||
// provided by InodeNoopRefCount. These methods are generally called by the
|
||||
// equivalent Dentry methods.
|
||||
inodeRefs
|
||||
|
||||
// Methods related to node metadata. A generic implementation is provided by
|
||||
// InodeAttrs.
|
||||
inodeMetadata
|
||||
|
||||
// Method for inodes that represent symlink. InodeNotSymlink provides a
|
||||
// blanket implementation for all non-symlink inodes.
|
||||
inodeSymlink
|
||||
|
||||
// Method for inodes that represent directories. InodeNotDirectory provides
|
||||
// a blanket implementation for all non-directory inodes.
|
||||
inodeDirectory
|
||||
|
||||
// Method for inodes that represent dynamic directories and their
|
||||
// children. InodeNoDynamicLookup provides a blanket implementation for all
|
||||
// non-dynamic-directory inodes.
|
||||
inodeDynamicLookup
|
||||
|
||||
// Open creates a file description for the filesystem object represented by
|
||||
// this inode. The returned file description should hold a reference on the
|
||||
// inode for its lifetime.
|
||||
//
|
||||
// Precondition: !rp.Done(). vfsd.Impl() must be a kernfs Dentry.
|
||||
Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error)
|
||||
}
|
||||
|
||||
type inodeRefs interface {
|
||||
IncRef()
|
||||
DecRef()
|
||||
TryIncRef() bool
|
||||
// Destroy is called when the inode reaches zero references. Destroy release
|
||||
// all resources (references) on objects referenced by the inode, including
|
||||
// any child dentries.
|
||||
Destroy()
|
||||
}
|
||||
|
||||
type inodeMetadata interface {
|
||||
// CheckPermissions checks that creds may access this inode for the
|
||||
// requested access type, per the the rules of
|
||||
// fs/namei.c:generic_permission().
|
||||
CheckPermissions(creds *auth.Credentials, atx vfs.AccessTypes) error
|
||||
|
||||
// Mode returns the (struct stat)::st_mode value for this inode. This is
|
||||
// separated from Stat for performance.
|
||||
Mode() linux.FileMode
|
||||
|
||||
// Stat returns the metadata for this inode. This corresponds to
|
||||
// vfs.FilesystemImpl.StatAt.
|
||||
Stat(fs *vfs.Filesystem) linux.Statx
|
||||
|
||||
// SetStat updates the metadata for this inode. This corresponds to
|
||||
// vfs.FilesystemImpl.SetStatAt.
|
||||
SetStat(fs *vfs.Filesystem, opts vfs.SetStatOptions) error
|
||||
}
|
||||
|
||||
// Precondition: All methods in this interface may only be called on directory
|
||||
// inodes.
|
||||
type inodeDirectory interface {
|
||||
// The New{File,Dir,Node,Symlink} methods below should return a new inode
|
||||
// hashed into this inode.
|
||||
//
|
||||
// These inode constructors are inode-level operations rather than
|
||||
// filesystem-level operations to allow client filesystems to mix different
|
||||
// implementations based on the new node's location in the
|
||||
// filesystem.
|
||||
|
||||
// HasChildren returns true if the directory inode has any children.
|
||||
HasChildren() bool
|
||||
|
||||
// NewFile creates a new regular file inode.
|
||||
NewFile(ctx context.Context, name string, opts vfs.OpenOptions) (*vfs.Dentry, error)
|
||||
|
||||
// NewDir creates a new directory inode.
|
||||
NewDir(ctx context.Context, name string, opts vfs.MkdirOptions) (*vfs.Dentry, error)
|
||||
|
||||
// NewLink creates a new hardlink to a specified inode in this
|
||||
// directory. Implementations should create a new kernfs Dentry pointing to
|
||||
// target, and update target's link count.
|
||||
NewLink(ctx context.Context, name string, target Inode) (*vfs.Dentry, error)
|
||||
|
||||
// NewSymlink creates a new symbolic link inode.
|
||||
NewSymlink(ctx context.Context, name, target string) (*vfs.Dentry, error)
|
||||
|
||||
// NewNode creates a new filesystem node for a mknod syscall.
|
||||
NewNode(ctx context.Context, name string, opts vfs.MknodOptions) (*vfs.Dentry, error)
|
||||
|
||||
// Unlink removes a child dentry from this directory inode.
|
||||
Unlink(ctx context.Context, name string, child *vfs.Dentry) error
|
||||
|
||||
// RmDir removes an empty child directory from this directory
|
||||
// inode. Implementations must update the parent directory's link count,
|
||||
// if required. Implementations are not responsible for checking that child
|
||||
// is a directory, checking for an empty directory.
|
||||
RmDir(ctx context.Context, name string, child *vfs.Dentry) error
|
||||
|
||||
// Rename is called on the source directory containing an inode being
|
||||
// renamed. child should point to the resolved child in the source
|
||||
// directory. If Rename replaces a dentry in the destination directory, it
|
||||
// should return the replaced dentry or nil otherwise.
|
||||
//
|
||||
// Precondition: Caller must serialize concurrent calls to Rename.
|
||||
Rename(ctx context.Context, oldname, newname string, child, dstDir *vfs.Dentry) (replaced *vfs.Dentry, err error)
|
||||
}
|
||||
|
||||
type inodeDynamicLookup interface {
|
||||
// Lookup should return an appropriate dentry if name should resolve to a
|
||||
// child of this dynamic directory inode. This gives the directory an
|
||||
// opportunity on every lookup to resolve additional entries that aren't
|
||||
// hashed into the directory. This is only called when the inode is a
|
||||
// directory. If the inode is not a directory, or if the directory only
|
||||
// contains a static set of children, the implementer can unconditionally
|
||||
// return an appropriate error (ENOTDIR and ENOENT respectively).
|
||||
//
|
||||
// The child returned by Lookup will be hashed into the VFS dentry tree. Its
|
||||
// lifetime can be controlled by the filesystem implementation with an
|
||||
// appropriate implementation of Valid.
|
||||
//
|
||||
// Lookup returns the child with an extra reference and the caller owns this
|
||||
// reference.
|
||||
Lookup(ctx context.Context, name string) (*vfs.Dentry, error)
|
||||
|
||||
// Valid should return true if this inode is still valid, or needs to
|
||||
// be resolved again by a call to Lookup.
|
||||
Valid(ctx context.Context) bool
|
||||
}
|
||||
|
||||
type inodeSymlink interface {
|
||||
// Readlink resolves the target of a symbolic link. If an inode is not a
|
||||
// symlink, the implementation should return EINVAL.
|
||||
Readlink(ctx context.Context) (string, error)
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
// 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 kernfs_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
const defaultMode linux.FileMode = 01777
|
||||
const staticFileContent = "This is sample content for a static test file."
|
||||
|
||||
// RootDentryFn is a generator function for creating the root dentry of a test
|
||||
// filesystem. See newTestSystem.
|
||||
type RootDentryFn func(*auth.Credentials, *filesystem) *kernfs.Dentry
|
||||
|
||||
// TestSystem represents the context for a single test.
|
||||
type TestSystem struct {
|
||||
t *testing.T
|
||||
ctx context.Context
|
||||
creds *auth.Credentials
|
||||
vfs *vfs.VirtualFilesystem
|
||||
mns *vfs.MountNamespace
|
||||
root vfs.VirtualDentry
|
||||
}
|
||||
|
||||
// newTestSystem sets up a minimal environment for running a test, including an
|
||||
// instance of a test filesystem. Tests can control the contents of the
|
||||
// filesystem by providing an appropriate rootFn, which should return a
|
||||
// pre-populated root dentry.
|
||||
func newTestSystem(t *testing.T, rootFn RootDentryFn) *TestSystem {
|
||||
ctx := contexttest.Context(t)
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
v := vfs.New()
|
||||
v.MustRegisterFilesystemType("testfs", &fsType{rootFn: rootFn})
|
||||
mns, err := v.NewMountNamespace(ctx, creds, "", "testfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create testfs root mount: %v", err)
|
||||
}
|
||||
|
||||
s := &TestSystem{
|
||||
t: t,
|
||||
ctx: ctx,
|
||||
creds: creds,
|
||||
vfs: v,
|
||||
mns: mns,
|
||||
root: mns.Root(),
|
||||
}
|
||||
runtime.SetFinalizer(s, func(s *TestSystem) { s.root.DecRef() })
|
||||
return s
|
||||
}
|
||||
|
||||
// PathOpAtRoot constructs a vfs.PathOperation for a path from the
|
||||
// root of the test filesystem.
|
||||
//
|
||||
// Precondition: path should be relative path.
|
||||
func (s *TestSystem) PathOpAtRoot(path string) vfs.PathOperation {
|
||||
return vfs.PathOperation{
|
||||
Root: s.root,
|
||||
Start: s.root,
|
||||
Pathname: path,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDentryOrDie attempts to resolve a dentry referred to by the
|
||||
// provided path operation. If unsuccessful, the test fails.
|
||||
func (s *TestSystem) GetDentryOrDie(pop vfs.PathOperation) vfs.VirtualDentry {
|
||||
vd, err := s.vfs.GetDentryAt(s.ctx, s.creds, &pop, &vfs.GetDentryOptions{})
|
||||
if err != nil {
|
||||
s.t.Fatalf("GetDentryAt(pop:%+v) failed: %v", pop, err)
|
||||
}
|
||||
return vd
|
||||
}
|
||||
|
||||
func (s *TestSystem) ReadToEnd(fd *vfs.FileDescription) (string, error) {
|
||||
buf := make([]byte, usermem.PageSize)
|
||||
bufIOSeq := usermem.BytesIOSequence(buf)
|
||||
opts := vfs.ReadOptions{}
|
||||
|
||||
var content bytes.Buffer
|
||||
for {
|
||||
n, err := fd.Impl().Read(s.ctx, bufIOSeq, opts)
|
||||
if n == 0 || err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return content.String(), err
|
||||
}
|
||||
content.Write(buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
type fsType struct {
|
||||
rootFn RootDentryFn
|
||||
}
|
||||
|
||||
type filesystem struct {
|
||||
kernfs.Filesystem
|
||||
}
|
||||
|
||||
type file struct {
|
||||
kernfs.DynamicBytesFile
|
||||
content string
|
||||
}
|
||||
|
||||
func (fs *filesystem) newFile(creds *auth.Credentials, content string) *kernfs.Dentry {
|
||||
f := &file{}
|
||||
f.content = content
|
||||
f.DynamicBytesFile.Init(creds, fs.NextIno(), f)
|
||||
|
||||
d := &kernfs.Dentry{}
|
||||
d.Init(f)
|
||||
return d
|
||||
}
|
||||
|
||||
func (f *file) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "%s", f.content)
|
||||
return nil
|
||||
}
|
||||
|
||||
type attrs struct {
|
||||
kernfs.InodeAttrs
|
||||
}
|
||||
|
||||
func (a *attrs) SetStat(fs *vfs.Filesystem, opt vfs.SetStatOptions) error {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
type readonlyDir struct {
|
||||
attrs
|
||||
kernfs.InodeNotSymlink
|
||||
kernfs.InodeNoDynamicLookup
|
||||
kernfs.InodeDirectoryNoNewChildren
|
||||
|
||||
kernfs.OrderedChildren
|
||||
dentry kernfs.Dentry
|
||||
}
|
||||
|
||||
func (fs *filesystem) newReadonlyDir(creds *auth.Credentials, mode linux.FileMode, contents map[string]*kernfs.Dentry) *kernfs.Dentry {
|
||||
dir := &readonlyDir{}
|
||||
dir.attrs.Init(creds, fs.NextIno(), linux.ModeDirectory|mode)
|
||||
dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
|
||||
dir.dentry.Init(dir)
|
||||
|
||||
dir.IncLinks(dir.OrderedChildren.Populate(&dir.dentry, contents))
|
||||
|
||||
return &dir.dentry
|
||||
}
|
||||
|
||||
func (d *readonlyDir) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &d.OrderedChildren, flags)
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
type dir struct {
|
||||
attrs
|
||||
kernfs.InodeNotSymlink
|
||||
kernfs.InodeNoDynamicLookup
|
||||
|
||||
fs *filesystem
|
||||
dentry kernfs.Dentry
|
||||
kernfs.OrderedChildren
|
||||
}
|
||||
|
||||
func (fs *filesystem) newDir(creds *auth.Credentials, mode linux.FileMode, contents map[string]*kernfs.Dentry) *kernfs.Dentry {
|
||||
dir := &dir{}
|
||||
dir.fs = fs
|
||||
dir.attrs.Init(creds, fs.NextIno(), linux.ModeDirectory|mode)
|
||||
dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{Writable: true})
|
||||
dir.dentry.Init(dir)
|
||||
|
||||
dir.IncLinks(dir.OrderedChildren.Populate(&dir.dentry, contents))
|
||||
|
||||
return &dir.dentry
|
||||
}
|
||||
|
||||
func (d *dir) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &d.OrderedChildren, flags)
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
func (d *dir) NewDir(ctx context.Context, name string, opts vfs.MkdirOptions) (*vfs.Dentry, error) {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
dir := d.fs.newDir(creds, opts.Mode, nil)
|
||||
dirVFSD := dir.VFSDentry()
|
||||
if err := d.OrderedChildren.Insert(name, dirVFSD); err != nil {
|
||||
dir.DecRef()
|
||||
return nil, err
|
||||
}
|
||||
d.IncLinks(1)
|
||||
return dirVFSD, nil
|
||||
}
|
||||
|
||||
func (d *dir) NewFile(ctx context.Context, name string, opts vfs.OpenOptions) (*vfs.Dentry, error) {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
f := d.fs.newFile(creds, "")
|
||||
fVFSD := f.VFSDentry()
|
||||
if err := d.OrderedChildren.Insert(name, fVFSD); err != nil {
|
||||
f.DecRef()
|
||||
return nil, err
|
||||
}
|
||||
return fVFSD, nil
|
||||
}
|
||||
|
||||
func (*dir) NewLink(context.Context, string, kernfs.Inode) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
func (*dir) NewSymlink(context.Context, string, string) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
func (*dir) NewNode(context.Context, string, vfs.MknodOptions) (*vfs.Dentry, error) {
|
||||
return nil, syserror.EPERM
|
||||
}
|
||||
|
||||
func (fst *fsType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opt vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
|
||||
fs := &filesystem{}
|
||||
fs.Init(vfsObj)
|
||||
root := fst.rootFn(creds, fs)
|
||||
return fs.VFSFilesystem(), root.VFSDentry(), nil
|
||||
}
|
||||
|
||||
// -------------------- Remainder of the file are test cases --------------------
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
sys := newTestSystem(t, func(creds *auth.Credentials, fs *filesystem) *kernfs.Dentry {
|
||||
return fs.newReadonlyDir(creds, 0755, map[string]*kernfs.Dentry{
|
||||
"file1": fs.newFile(creds, staticFileContent),
|
||||
})
|
||||
})
|
||||
sys.GetDentryOrDie(sys.PathOpAtRoot("file1")).DecRef()
|
||||
}
|
||||
|
||||
func TestMkdirGetDentry(t *testing.T) {
|
||||
sys := newTestSystem(t, func(creds *auth.Credentials, fs *filesystem) *kernfs.Dentry {
|
||||
return fs.newReadonlyDir(creds, 0755, map[string]*kernfs.Dentry{
|
||||
"dir1": fs.newDir(creds, 0755, nil),
|
||||
})
|
||||
})
|
||||
|
||||
pop := sys.PathOpAtRoot("dir1/a new directory")
|
||||
if err := sys.vfs.MkdirAt(sys.ctx, sys.creds, &pop, &vfs.MkdirOptions{Mode: 0755}); err != nil {
|
||||
t.Fatalf("MkdirAt for PathOperation %+v failed: %v", pop, err)
|
||||
}
|
||||
sys.GetDentryOrDie(pop).DecRef()
|
||||
}
|
||||
|
||||
func TestReadStaticFile(t *testing.T) {
|
||||
sys := newTestSystem(t, func(creds *auth.Credentials, fs *filesystem) *kernfs.Dentry {
|
||||
return fs.newReadonlyDir(creds, 0755, map[string]*kernfs.Dentry{
|
||||
"file1": fs.newFile(creds, staticFileContent),
|
||||
})
|
||||
})
|
||||
|
||||
pop := sys.PathOpAtRoot("file1")
|
||||
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
|
||||
if err != nil {
|
||||
sys.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
|
||||
}
|
||||
defer fd.DecRef()
|
||||
|
||||
content, err := sys.ReadToEnd(fd)
|
||||
if err != nil {
|
||||
sys.t.Fatalf("Read failed: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(staticFileContent, content); diff != "" {
|
||||
sys.t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNewFileInStaticDir(t *testing.T) {
|
||||
sys := newTestSystem(t, func(creds *auth.Credentials, fs *filesystem) *kernfs.Dentry {
|
||||
return fs.newReadonlyDir(creds, 0755, map[string]*kernfs.Dentry{
|
||||
"dir1": fs.newDir(creds, 0755, nil),
|
||||
})
|
||||
})
|
||||
|
||||
pop := sys.PathOpAtRoot("dir1/newfile")
|
||||
opts := &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_EXCL, Mode: defaultMode}
|
||||
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, opts)
|
||||
if err != nil {
|
||||
sys.t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err)
|
||||
}
|
||||
|
||||
// Close the file. The file should persist.
|
||||
fd.DecRef()
|
||||
|
||||
fd, err = sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
|
||||
if err != nil {
|
||||
sys.t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err)
|
||||
}
|
||||
fd.DecRef()
|
||||
}
|
||||
|
||||
// direntCollector provides an implementation for vfs.IterDirentsCallback for
|
||||
// testing. It simply iterates to the end of a given directory FD and collects
|
||||
// all dirents emitted by the callback.
|
||||
type direntCollector struct {
|
||||
mu sync.Mutex
|
||||
dirents map[string]vfs.Dirent
|
||||
}
|
||||
|
||||
// Handle implements vfs.IterDirentsCallback.Handle.
|
||||
func (d *direntCollector) Handle(dirent vfs.Dirent) bool {
|
||||
d.mu.Lock()
|
||||
if d.dirents == nil {
|
||||
d.dirents = make(map[string]vfs.Dirent)
|
||||
}
|
||||
d.dirents[dirent.Name] = dirent
|
||||
d.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// count returns the number of dirents currently in the collector.
|
||||
func (d *direntCollector) count() int {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return len(d.dirents)
|
||||
}
|
||||
|
||||
// contains checks whether the collector has a dirent with the given name and
|
||||
// type.
|
||||
func (d *direntCollector) contains(name string, typ uint8) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
dirent, ok := d.dirents[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("No dirent named %q found", name)
|
||||
}
|
||||
if dirent.Type != typ {
|
||||
return fmt.Errorf("Dirent named %q found, but was expecting type %d, got: %+v", name, typ, dirent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDirFDReadWrite(t *testing.T) {
|
||||
sys := newTestSystem(t, func(creds *auth.Credentials, fs *filesystem) *kernfs.Dentry {
|
||||
return fs.newReadonlyDir(creds, 0755, nil)
|
||||
})
|
||||
|
||||
pop := sys.PathOpAtRoot("/")
|
||||
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
|
||||
if err != nil {
|
||||
sys.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
|
||||
}
|
||||
defer fd.DecRef()
|
||||
|
||||
// Read/Write should fail for directory FDs.
|
||||
if _, err := fd.Read(sys.ctx, usermem.BytesIOSequence([]byte{}), vfs.ReadOptions{}); err != syserror.EISDIR {
|
||||
sys.t.Fatalf("Read for directory FD failed with unexpected error: %v", err)
|
||||
}
|
||||
if _, err := fd.Write(sys.ctx, usermem.BytesIOSequence([]byte{}), vfs.WriteOptions{}); err != syserror.EISDIR {
|
||||
sys.t.Fatalf("Wrire for directory FD failed with unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirFDIterDirents(t *testing.T) {
|
||||
sys := newTestSystem(t, func(creds *auth.Credentials, fs *filesystem) *kernfs.Dentry {
|
||||
return fs.newReadonlyDir(creds, 0755, map[string]*kernfs.Dentry{
|
||||
// Fill root with nodes backed by various inode implementations.
|
||||
"dir1": fs.newReadonlyDir(creds, 0755, nil),
|
||||
"dir2": fs.newDir(creds, 0755, map[string]*kernfs.Dentry{
|
||||
"dir3": fs.newDir(creds, 0755, nil),
|
||||
}),
|
||||
"file1": fs.newFile(creds, staticFileContent),
|
||||
})
|
||||
})
|
||||
|
||||
pop := sys.PathOpAtRoot("/")
|
||||
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
|
||||
if err != nil {
|
||||
sys.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
|
||||
}
|
||||
defer fd.DecRef()
|
||||
|
||||
collector := &direntCollector{}
|
||||
if err := fd.IterDirents(sys.ctx, collector); err != nil {
|
||||
sys.t.Fatalf("IterDirent failed: %v", err)
|
||||
}
|
||||
|
||||
// Root directory should contain ".", ".." and 3 children:
|
||||
if collector.count() != 5 {
|
||||
sys.t.Fatalf("IterDirent returned too many dirents")
|
||||
}
|
||||
for _, dirName := range []string{".", "..", "dir1", "dir2"} {
|
||||
if err := collector.contains(dirName, linux.DT_DIR); err != nil {
|
||||
sys.t.Fatalf("IterDirent had unexpected results: %v", err)
|
||||
}
|
||||
}
|
||||
if err := collector.contains("file1", linux.DT_REG); err != nil {
|
||||
sys.t.Fatalf("IterDirent had unexpected results: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user