Implement kernfs.

PiperOrigin-RevId: 285231002
This commit is contained in:
Rahat Mahmood
2019-12-12 11:20:47 -08:00
committed by gVisor bot
parent 378d6c1f36
commit 007707a072
9 changed files with 2439 additions and 0 deletions
+23
View File
@@ -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,
+7
View File
@@ -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.
)
+60
View File
@@ -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
}
+207
View File
@@ -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
+492
View File
@@ -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
}
+405
View File
@@ -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)
}
+423
View File
@@ -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)
}
}