mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Implements POSIX ACLs for gVisor's tmpfs. As a summary: - Adds `acl` and `defaultACL` fields to tmpfs's inode - Allows `system.posix_acl_access` and `system.posix_acl_default` xattrs to set and retrieve POSIX ACLs - Updates tmpfs's stat implementation to account for the ACL in setting/retrieving a file's mode - Updates `GenericCheckPermission` to take an optional ACL. All non-tmpfs filesystems pass the `nil` ACL. - Updates tmpfs's tar save-and-restore logic to store the base64-encoded POSIX ACL xattrs in the tar header - Adds unit tests for POSIX ACL parsing/serialization - Adds integration tests for POSIX ACLs - Adds a container test to verify the tmpfs tar save/restore logic with ACLs. This required a new subcommand for `test_app` to set and fetch xattrs in the container The motivation for POSIX ACLs is for journald (which uses POSIX ACLs to give non-root users access to their own `systemctl --user` services' logs). This commit is not actually sufficient to get this working -- we will need to plumb ACLs to overlayfs, too, which will be done in a follow-up commit. The implementation is very similar to the implementation in Linux, although it is filesystem-specific rather than at the VFS layer (much like we store `mode`, `uid`, `gid`, etc at the FS layer currently). An optional access and default ACL is stored in each inode. If present, it is used for permission checks and is kept in sync with the `mode`. The `mode` is computed from the ACL when permissions are updated. If the ACL is simple enough to be stored equivalently as a `mode`, an ACL is not stored. One limitation at the moment is that upon file creation, the process's umask is masked against the creation mode regardless of the presence of a default ACL, whereas Linux only masks against umask when *not* inheriting from a default ACL. Fixing this would require a fair bit of refactoring since umask isn't passed through to filesystems at all, and is instead masked with the creation mode at the syscall layer. This is not a security issue since the resulting permissions are always of equal-or-greater restrictiveness to those of Linux, but is tracked in \#13688.
861 lines
25 KiB
Go
861 lines
25 KiB
Go
// Copyright 2019 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package kernfs
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/atomicbitops"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/hostarch"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/ktime"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
)
|
|
|
|
// 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.
|
|
//
|
|
// +stateify savable
|
|
type InodeNoopRefCount struct {
|
|
InodeTemporary
|
|
}
|
|
|
|
// IncRef implements Inode.IncRef.
|
|
func (InodeNoopRefCount) IncRef() {
|
|
}
|
|
|
|
// DecRef implements Inode.DecRef.
|
|
func (InodeNoopRefCount) DecRef(context.Context) {
|
|
}
|
|
|
|
// TryIncRef implements Inode.TryIncRef.
|
|
func (InodeNoopRefCount) TryIncRef() bool {
|
|
return true
|
|
}
|
|
|
|
// InodeDirectoryNoNewChildren partially implements the Inode interface.
|
|
// InodeDirectoryNoNewChildren represents a directory inode which does not
|
|
// support creation of new children.
|
|
//
|
|
// +stateify savable
|
|
type InodeDirectoryNoNewChildren struct{}
|
|
|
|
// NewFile implements Inode.NewFile.
|
|
func (InodeDirectoryNoNewChildren) NewFile(context.Context, string, vfs.OpenOptions) (Inode, error) {
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
|
|
// NewDir implements Inode.NewDir.
|
|
func (InodeDirectoryNoNewChildren) NewDir(context.Context, string, vfs.MkdirOptions) (Inode, error) {
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
|
|
// NewLink implements Inode.NewLink.
|
|
func (InodeDirectoryNoNewChildren) NewLink(context.Context, string, Inode) (Inode, error) {
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
|
|
// NewSymlink implements Inode.NewSymlink.
|
|
func (InodeDirectoryNoNewChildren) NewSymlink(context.Context, string, string) (Inode, error) {
|
|
return nil, linuxerr.EPERM
|
|
}
|
|
|
|
// NewNode implements Inode.NewNode.
|
|
func (InodeDirectoryNoNewChildren) NewNode(context.Context, string, vfs.MknodOptions) (Inode, error) {
|
|
return nil, linuxerr.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.
|
|
//
|
|
// +stateify savable
|
|
type InodeNotDirectory struct {
|
|
InodeAlwaysValid
|
|
}
|
|
|
|
// HasChildren implements Inode.HasChildren.
|
|
func (InodeNotDirectory) HasChildren() bool {
|
|
return false
|
|
}
|
|
|
|
// NewFile implements Inode.NewFile.
|
|
func (InodeNotDirectory) NewFile(context.Context, string, vfs.OpenOptions) (Inode, error) {
|
|
panic("NewFile called on non-directory inode")
|
|
}
|
|
|
|
// NewDir implements Inode.NewDir.
|
|
func (InodeNotDirectory) NewDir(context.Context, string, vfs.MkdirOptions) (Inode, error) {
|
|
panic("NewDir called on non-directory inode")
|
|
}
|
|
|
|
// NewLink implements Inode.NewLinkink.
|
|
func (InodeNotDirectory) NewLink(context.Context, string, Inode) (Inode, error) {
|
|
panic("NewLink called on non-directory inode")
|
|
}
|
|
|
|
// NewSymlink implements Inode.NewSymlink.
|
|
func (InodeNotDirectory) NewSymlink(context.Context, string, string) (Inode, error) {
|
|
panic("NewSymlink called on non-directory inode")
|
|
}
|
|
|
|
// NewNode implements Inode.NewNode.
|
|
func (InodeNotDirectory) NewNode(context.Context, string, vfs.MknodOptions) (Inode, error) {
|
|
panic("NewNode called on non-directory inode")
|
|
}
|
|
|
|
// Unlink implements Inode.Unlink.
|
|
func (InodeNotDirectory) Unlink(context.Context, string, Inode) error {
|
|
panic("Unlink called on non-directory inode")
|
|
}
|
|
|
|
// RmDir implements Inode.RmDir.
|
|
func (InodeNotDirectory) RmDir(context.Context, string, Inode) error {
|
|
panic("RmDir called on non-directory inode")
|
|
}
|
|
|
|
// Rename implements Inode.Rename.
|
|
func (InodeNotDirectory) Rename(context.Context, string, string, Inode, Inode) error {
|
|
panic("Rename called on non-directory inode")
|
|
}
|
|
|
|
// Lookup implements Inode.Lookup.
|
|
func (InodeNotDirectory) Lookup(ctx context.Context, name string) (Inode, error) {
|
|
panic("Lookup called on non-directory inode")
|
|
}
|
|
|
|
// IterDirents implements Inode.IterDirents.
|
|
func (InodeNotDirectory) IterDirents(ctx context.Context, mnt *vfs.Mount, callback vfs.IterDirentsCallback, offset, relOffset int64) (newOffset int64, err error) {
|
|
panic("IterDirents called on non-directory inode")
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// +stateify savable
|
|
type InodeNotSymlink struct{}
|
|
|
|
// Readlink implements Inode.Readlink.
|
|
func (InodeNotSymlink) Readlink(context.Context, *vfs.Mount) (string, error) {
|
|
return "", linuxerr.EINVAL
|
|
}
|
|
|
|
// Getlink implements Inode.Getlink.
|
|
func (InodeNotSymlink) Getlink(context.Context, *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
|
return vfs.VirtualDentry{}, "", linuxerr.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.
|
|
//
|
|
// +stateify savable
|
|
type InodeAttrs struct {
|
|
devMajor uint32
|
|
devMinor uint32
|
|
ino atomicbitops.Uint64
|
|
mode atomicbitops.Uint32
|
|
uid atomicbitops.Uint32
|
|
gid atomicbitops.Uint32
|
|
nlink atomicbitops.Uint32
|
|
blockSize atomicbitops.Uint32
|
|
|
|
// Timestamps, all nsecs from the Unix epoch.
|
|
atime atomicbitops.Int64
|
|
mtime atomicbitops.Int64
|
|
ctime atomicbitops.Int64
|
|
}
|
|
|
|
// Init initializes this InodeAttrs.
|
|
func (a *InodeAttrs) Init(ctx context.Context, creds *auth.Credentials, devMajor, devMinor uint32, ino uint64, mode linux.FileMode) {
|
|
a.InitWithIDs(ctx, creds.EffectiveKUID, creds.EffectiveKGID, devMajor, devMinor, ino, mode)
|
|
}
|
|
|
|
// InitWithIDs initializes this InodeAttrs.
|
|
func (a *InodeAttrs) InitWithIDs(ctx context.Context, uid auth.KUID, gid auth.KGID, devMajor, devMinor uint32, 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
|
|
}
|
|
a.devMajor = devMajor
|
|
a.devMinor = devMinor
|
|
a.ino.Store(ino)
|
|
a.mode.Store(uint32(mode))
|
|
a.uid.Store(uint32(uid))
|
|
a.gid.Store(uint32(gid))
|
|
a.nlink.Store(nlink)
|
|
a.blockSize.Store(hostarch.PageSize)
|
|
now := ktime.NowFromContext(ctx).Nanoseconds()
|
|
a.atime.Store(now)
|
|
a.mtime.Store(now)
|
|
a.ctime.Store(now)
|
|
}
|
|
|
|
// DevMajor returns the device major number.
|
|
func (a *InodeAttrs) DevMajor() uint32 {
|
|
return a.devMajor
|
|
}
|
|
|
|
// DevMinor returns the device minor number.
|
|
func (a *InodeAttrs) DevMinor() uint32 {
|
|
return a.devMinor
|
|
}
|
|
|
|
// Ino returns the inode id.
|
|
func (a *InodeAttrs) Ino() uint64 {
|
|
return a.ino.Load()
|
|
}
|
|
|
|
// UID implements Inode.UID.
|
|
func (a *InodeAttrs) UID() auth.KUID {
|
|
return auth.KUID(a.uid.Load())
|
|
}
|
|
|
|
// GID implements Inode.GID.
|
|
func (a *InodeAttrs) GID() auth.KGID {
|
|
return auth.KGID(a.gid.Load())
|
|
}
|
|
|
|
// Mode implements Inode.Mode.
|
|
func (a *InodeAttrs) Mode() linux.FileMode {
|
|
return linux.FileMode(a.mode.Load())
|
|
}
|
|
|
|
// Links returns the link count.
|
|
func (a *InodeAttrs) Links() uint32 {
|
|
return a.nlink.Load()
|
|
}
|
|
|
|
// TouchAtime updates a.atime to the current time.
|
|
func (a *InodeAttrs) TouchAtime(ctx context.Context, mnt *vfs.Mount) {
|
|
if opts := mnt.Options(); opts.Flags.NoATime || opts.ReadOnly {
|
|
return
|
|
}
|
|
if err := mnt.CheckBeginWrite(); err != nil {
|
|
return
|
|
}
|
|
a.atime.Store(ktime.NowFromContext(ctx).Nanoseconds())
|
|
mnt.EndWrite()
|
|
}
|
|
|
|
// TouchCMtime updates a.{c/m}time to the current time. The caller should
|
|
// synchronize calls to this so that ctime and mtime are updated to the same
|
|
// value.
|
|
func (a *InodeAttrs) TouchCMtime(ctx context.Context) {
|
|
now := ktime.NowFromContext(ctx).Nanoseconds()
|
|
a.mtime.Store(now)
|
|
a.ctime.Store(now)
|
|
}
|
|
|
|
// 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(context.Context, *vfs.Filesystem, vfs.StatOptions) (linux.Statx, error) {
|
|
var stat linux.Statx
|
|
stat.Mask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO | linux.STATX_NLINK | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME
|
|
stat.DevMajor = a.devMajor
|
|
stat.DevMinor = a.devMinor
|
|
stat.Ino = a.ino.Load()
|
|
stat.Mode = uint16(a.Mode())
|
|
stat.UID = a.uid.Load()
|
|
stat.GID = a.gid.Load()
|
|
stat.Nlink = a.nlink.Load()
|
|
stat.Blksize = a.blockSize.Load()
|
|
stat.Atime = linux.NsecToStatxTimestamp(a.atime.Load())
|
|
stat.Mtime = linux.NsecToStatxTimestamp(a.mtime.Load())
|
|
stat.Ctime = linux.NsecToStatxTimestamp(a.ctime.Load())
|
|
return stat, nil
|
|
}
|
|
|
|
// SetStat implements Inode.SetStat.
|
|
func (a *InodeAttrs) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
|
|
if opts.Stat.Mask == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Note that not all fields are modifiable. For example, the file type and
|
|
// inode numbers are immutable after node creation. Setting the size is often
|
|
// allowed by kernfs files but does not do anything. If some other behavior is
|
|
// needed, the embedder should consider extending SetStat.
|
|
if opts.Stat.Mask&^(linux.STATX_MODE|linux.STATX_UID|linux.STATX_GID|linux.STATX_ATIME|linux.STATX_MTIME|linux.STATX_SIZE) != 0 {
|
|
return linuxerr.EPERM
|
|
}
|
|
if opts.Stat.Mask&linux.STATX_SIZE != 0 && a.Mode().IsDir() {
|
|
return linuxerr.EISDIR
|
|
}
|
|
if err := vfs.CheckSetStat(ctx, creds, &opts, a.Mode(), nil, auth.KUID(a.uid.Load()), auth.KGID(a.gid.Load())); err != nil {
|
|
return err
|
|
}
|
|
|
|
stat := opts.Stat
|
|
if stat.Mask&linux.STATX_UID != 0 {
|
|
a.uid.Store(stat.UID)
|
|
}
|
|
if stat.Mask&linux.STATX_GID != 0 {
|
|
a.gid.Store(stat.GID)
|
|
}
|
|
|
|
clearSID := opts.ClearPrivs
|
|
if stat.Mask&linux.STATX_MODE != 0 {
|
|
for {
|
|
old := a.mode.Load()
|
|
ft := old & linux.S_IFMT
|
|
newMode := ft | uint32(stat.Mode & ^uint16(linux.S_IFMT))
|
|
if clearSID {
|
|
newMode = vfs.ClearSUIDAndSGID(newMode)
|
|
}
|
|
if swapped := a.mode.CompareAndSwap(old, newMode); swapped {
|
|
clearSID = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// We may have to clear the SUID/SGID bits, but didn't do so as part of
|
|
// STATX_MODE.
|
|
if clearSID {
|
|
for {
|
|
old := a.mode.Load()
|
|
newMode := vfs.ClearSUIDAndSGID(old)
|
|
if swapped := a.mode.CompareAndSwap(old, newMode); swapped {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
now := ktime.NowFromContext(ctx).Nanoseconds()
|
|
if stat.Mask&linux.STATX_ATIME != 0 {
|
|
if stat.Atime.Nsec == linux.UTIME_NOW {
|
|
stat.Atime = linux.NsecToStatxTimestamp(now)
|
|
}
|
|
a.atime.Store(stat.Atime.ToNsec())
|
|
}
|
|
if stat.Mask&linux.STATX_MTIME != 0 {
|
|
if stat.Mtime.Nsec == linux.UTIME_NOW {
|
|
stat.Mtime = linux.NsecToStatxTimestamp(now)
|
|
}
|
|
a.mtime.Store(stat.Mtime.ToNsec())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckPermissions implements Inode.CheckPermissions.
|
|
func (a *InodeAttrs) CheckPermissions(_ context.Context, creds *auth.Credentials, ats vfs.AccessTypes) error {
|
|
return vfs.GenericCheckPermissions(
|
|
creds,
|
|
ats,
|
|
a.Mode(),
|
|
nil,
|
|
auth.KUID(a.uid.Load()),
|
|
auth.KGID(a.gid.Load()),
|
|
)
|
|
}
|
|
|
|
// IncLinks implements Inode.IncLinks.
|
|
func (a *InodeAttrs) IncLinks(n uint32) {
|
|
if a.nlink.Add(n) <= n {
|
|
panic("InodeLink.IncLinks called with no existing links")
|
|
}
|
|
}
|
|
|
|
// DecLinks implements Inode.DecLinks.
|
|
func (a *InodeAttrs) DecLinks() {
|
|
if nlink := a.nlink.Add(^uint32(0)); nlink == ^uint32(0) {
|
|
// Negative overflow
|
|
panic("Inode.DecLinks called at 0 links")
|
|
}
|
|
}
|
|
|
|
// +stateify savable
|
|
type slot struct {
|
|
name string
|
|
inode Inode
|
|
static bool
|
|
slotEntry
|
|
}
|
|
|
|
// OrderedChildrenOptions contains initialization options for OrderedChildren.
|
|
//
|
|
// +stateify savable
|
|
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.
|
|
//
|
|
// Note that writable users must implement the sticky bit (I_SVTX).
|
|
Writable bool
|
|
}
|
|
|
|
// inodeWithOrderedChildren allows extraction of an OrderedChildren from an
|
|
// Inode implementation. A concrete type that both implements the Inode
|
|
// interface and embeds OrderedChildren will be castable to this interface, and
|
|
// we can get to the embedded OrderedChildren through the orderedChildren
|
|
// method.
|
|
type inodeWithOrderedChildren interface {
|
|
Inode
|
|
orderedChildren() *OrderedChildren
|
|
}
|
|
|
|
// OrderedChildren partially implements the Inode interface. OrderedChildren can
|
|
// be embedded in directory inodes to keep track of children in the
|
|
// directory, and can then be used to implement a generic directory FD -- see
|
|
// GenericDirectoryFD.
|
|
//
|
|
// OrderedChildren can represent a node in an Inode tree. The children inodes
|
|
// might be directories themselves using OrderedChildren; hence extending the
|
|
// tree. The parent inode (OrderedChildren user) holds a ref on all its static
|
|
// children. This lets the static inodes outlive their associated dentry.
|
|
// While the dentry might have to be regenerated via a Lookup() call, we can
|
|
// keep reusing the same static inode. These static children inodes are finally
|
|
// DecRef'd when this directory inode is being destroyed. This makes
|
|
// OrderedChildren suitable for static directory entries as well.
|
|
//
|
|
// Must be initialize with Init before first use.
|
|
//
|
|
// +stateify savable
|
|
type OrderedChildren struct {
|
|
// 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 `state:"nosave"`
|
|
order slotList
|
|
set map[string]*slot
|
|
}
|
|
|
|
// orderedChildren implements inodeWithOrderedChildren.orderedChildren.
|
|
func (o *OrderedChildren) orderedChildren() *OrderedChildren {
|
|
return o
|
|
}
|
|
|
|
// Init initializes an OrderedChildren.
|
|
func (o *OrderedChildren) Init(opts OrderedChildrenOptions) {
|
|
o.writable = opts.Writable
|
|
o.set = make(map[string]*slot)
|
|
}
|
|
|
|
// Destroy clears the children stored in o. It should be called by structs
|
|
// embedding OrderedChildren upon destruction, i.e. when their reference count
|
|
// reaches zero.
|
|
func (o *OrderedChildren) Destroy(ctx context.Context) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
// Drop the ref that o owns on the static inodes it holds.
|
|
for _, s := range o.set {
|
|
if s.static {
|
|
s.inode.DecRef(ctx)
|
|
}
|
|
}
|
|
o.order.Reset()
|
|
o.set = nil
|
|
}
|
|
|
|
// Populate inserts static children into this OrderedChildren.
|
|
// Populate returns the number of directories inserted, which the caller
|
|
// may use to update the link count for the parent directory.
|
|
//
|
|
// Precondition:
|
|
// - d must represent a directory inode.
|
|
// - children must not contain any conflicting entries already in o.
|
|
// - Caller must hold a reference on all inodes passed.
|
|
//
|
|
// Postcondition: Caller's references on inodes are transferred to o.
|
|
func (o *OrderedChildren) Populate(children map[string]Inode) uint32 {
|
|
var links uint32
|
|
for name, child := range children {
|
|
if child.Mode().IsDir() {
|
|
links++
|
|
}
|
|
if err := o.insert(name, child, true); err != nil {
|
|
panic(fmt.Sprintf("Collision when attempting to insert child %q (%+v)", name, child))
|
|
}
|
|
}
|
|
return links
|
|
}
|
|
|
|
// Lookup implements Inode.Lookup.
|
|
func (o *OrderedChildren) Lookup(ctx context.Context, name string) (Inode, error) {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
|
|
s, ok := o.set[name]
|
|
if !ok {
|
|
return nil, linuxerr.ENOENT
|
|
}
|
|
|
|
s.inode.IncRef() // This ref is passed to the dentry upon creation via Init.
|
|
return s.inode, nil
|
|
}
|
|
|
|
// ForEachChild calls fn on all children tracked by this ordered children.
|
|
func (o *OrderedChildren) ForEachChild(fn func(string, Inode)) {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
|
|
for name, slot := range o.set {
|
|
fn(name, slot.inode)
|
|
}
|
|
}
|
|
|
|
// IterDirents implements Inode.IterDirents.
|
|
func (o *OrderedChildren) IterDirents(ctx context.Context, mnt *vfs.Mount, cb vfs.IterDirentsCallback, offset, relOffset int64) (newOffset int64, err error) {
|
|
// All entries from OrderedChildren have already been handled in
|
|
// GenericDirectoryFD.IterDirents.
|
|
return offset, nil
|
|
}
|
|
|
|
// HasChildren implements Inode.HasChildren.
|
|
func (o *OrderedChildren) HasChildren() bool {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return len(o.set) > 0
|
|
}
|
|
|
|
// Insert inserts a dynamic 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 Inode) error {
|
|
return o.insert(name, child, false)
|
|
}
|
|
|
|
// Inserter is like Insert, but obtains the child to insert by calling
|
|
// makeChild. makeChild is only called if the insert will succeed. This allows
|
|
// the caller to atomically check and insert a child without having to
|
|
// clean up the child on failure.
|
|
func (o *OrderedChildren) Inserter(name string, makeChild func() Inode) (Inode, error) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
if _, ok := o.set[name]; ok {
|
|
return nil, linuxerr.EEXIST
|
|
}
|
|
|
|
// Note: We must not fail after we call makeChild().
|
|
|
|
child := makeChild()
|
|
s := &slot{
|
|
name: name,
|
|
inode: child,
|
|
static: false,
|
|
}
|
|
o.order.PushBack(s)
|
|
o.set[name] = s
|
|
return child, nil
|
|
}
|
|
|
|
// insert inserts child into o.
|
|
//
|
|
// Precondition: Caller must be holding a ref on child if static is true.
|
|
//
|
|
// Postcondition: Caller's ref on child is transferred to o if static is true.
|
|
func (o *OrderedChildren) insert(name string, child Inode, static bool) error {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
if _, ok := o.set[name]; ok {
|
|
return linuxerr.EEXIST
|
|
}
|
|
s := &slot{
|
|
name: name,
|
|
inode: child,
|
|
static: static,
|
|
}
|
|
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 {
|
|
if s.static {
|
|
panic(fmt.Sprintf("removeLocked called on a static inode: %v", s.inode))
|
|
}
|
|
delete(o.set, name)
|
|
o.order.Remove(s)
|
|
}
|
|
}
|
|
|
|
// Precondition: caller must hold o.mu for reading or writing.
|
|
func (o *OrderedChildren) checkExistingLocked(name string, child Inode) error {
|
|
s, ok := o.set[name]
|
|
if !ok {
|
|
return linuxerr.ENOENT
|
|
}
|
|
if s.inode != child {
|
|
panic(fmt.Sprintf("Inode doesn't match what kernfs thinks! Name: %q, OrderedChild: %p, kernfs: %p", name, s.inode, child))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unlink implements Inode.Unlink.
|
|
func (o *OrderedChildren) Unlink(ctx context.Context, name string, child Inode) error {
|
|
if !o.writable {
|
|
return linuxerr.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 Inode) 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)
|
|
}
|
|
|
|
// 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 Inode) error {
|
|
if !o.writable {
|
|
return linuxerr.EPERM
|
|
}
|
|
dstIOC, ok := dstDir.(inodeWithOrderedChildren)
|
|
if !ok {
|
|
return linuxerr.EXDEV
|
|
}
|
|
dst := dstIOC.orderedChildren()
|
|
if !dst.writable {
|
|
return linuxerr.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()
|
|
}
|
|
|
|
// Ensure target inode exists in src.
|
|
if err := o.checkExistingLocked(oldname, child); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure no name collision in dst.
|
|
if _, ok := dst.set[newname]; ok {
|
|
return linuxerr.EEXIST
|
|
}
|
|
|
|
// Remove from src.
|
|
o.removeLocked(oldname)
|
|
|
|
// Add to dst.
|
|
s := &slot{
|
|
name: newname,
|
|
inode: child,
|
|
}
|
|
dst.order.PushBack(s)
|
|
dst.set[newname] = s
|
|
|
|
return 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
|
|
}
|
|
|
|
// InodeSymlink partially implements Inode interface for symlinks.
|
|
//
|
|
// +stateify savable
|
|
type InodeSymlink struct {
|
|
InodeNotDirectory
|
|
}
|
|
|
|
// Open implements Inode.Open.
|
|
func (InodeSymlink) Open(ctx context.Context, rp *vfs.ResolvingPath, d *Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
return nil, linuxerr.ELOOP
|
|
}
|
|
|
|
// StaticDirectory is a standard implementation of a directory with static
|
|
// contents.
|
|
//
|
|
// +stateify savable
|
|
type StaticDirectory struct {
|
|
InodeAlwaysValid
|
|
InodeAttrs
|
|
InodeDirectoryNoNewChildren
|
|
InodeNoStatFS
|
|
InodeNotAnonymous
|
|
InodeNotSymlink
|
|
InodeTemporary
|
|
InodeWatches
|
|
OrderedChildren
|
|
StaticDirectoryRefs
|
|
InodeFSOwned
|
|
|
|
locks vfs.FileLocks
|
|
fdOpts GenericDirectoryFDOptions
|
|
}
|
|
|
|
var _ Inode = (*StaticDirectory)(nil)
|
|
|
|
// NewStaticDir creates a new static directory and returns its dentry.
|
|
func NewStaticDir(ctx context.Context, creds *auth.Credentials, devMajor, devMinor uint32, ino uint64, perm linux.FileMode, children map[string]Inode, fdOpts GenericDirectoryFDOptions) Inode {
|
|
inode := &StaticDirectory{}
|
|
inode.Init(ctx, creds, devMajor, devMinor, ino, perm, fdOpts)
|
|
inode.InitRefs()
|
|
|
|
inode.OrderedChildren.Init(OrderedChildrenOptions{})
|
|
links := inode.OrderedChildren.Populate(children)
|
|
inode.IncLinks(links)
|
|
|
|
return inode
|
|
}
|
|
|
|
// Init initializes StaticDirectory.
|
|
func (s *StaticDirectory) Init(ctx context.Context, creds *auth.Credentials, devMajor, devMinor uint32, ino uint64, perm linux.FileMode, fdOpts GenericDirectoryFDOptions) {
|
|
if perm&^linux.PermissionsMask != 0 {
|
|
panic(fmt.Sprintf("Only permission mask must be set: %x", perm&linux.PermissionsMask))
|
|
}
|
|
s.fdOpts = fdOpts
|
|
s.InodeAttrs.Init(ctx, creds, devMajor, devMinor, ino, linux.ModeDirectory|perm)
|
|
}
|
|
|
|
// Open implements Inode.Open.
|
|
func (s *StaticDirectory) Open(ctx context.Context, rp *vfs.ResolvingPath, d *Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
fd, err := NewGenericDirectoryFD(rp.Mount(), d, rp.Credentials(), &s.OrderedChildren, &s.locks, &opts, s.fdOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fd.VFSFileDescription(), nil
|
|
}
|
|
|
|
// SetStat implements Inode.SetStat not allowing inode attributes to be changed.
|
|
func (*StaticDirectory) SetStat(context.Context, *vfs.Filesystem, *auth.Credentials, vfs.SetStatOptions) error {
|
|
return linuxerr.EPERM
|
|
}
|
|
|
|
// DecRef implements Inode.DecRef.
|
|
func (s *StaticDirectory) DecRef(ctx context.Context) {
|
|
s.StaticDirectoryRefs.DecRef(func() { s.Destroy(ctx) })
|
|
}
|
|
|
|
// InodeAlwaysValid partially implements Inode.
|
|
//
|
|
// +stateify savable
|
|
type InodeAlwaysValid struct{}
|
|
|
|
// Valid implements Inode.Valid.
|
|
func (*InodeAlwaysValid) Valid(context.Context, *Dentry, string) bool {
|
|
return true
|
|
}
|
|
|
|
// InodeTemporary partially implements Inode.
|
|
//
|
|
// +stateify savable
|
|
type InodeTemporary struct{}
|
|
|
|
// Keep implements Inode.Keep.
|
|
func (*InodeTemporary) Keep() bool {
|
|
return false
|
|
}
|
|
|
|
// InodeNoStatFS partially implements the Inode interface, where the client
|
|
// filesystem doesn't support statfs(2).
|
|
//
|
|
// +stateify savable
|
|
type InodeNoStatFS struct{}
|
|
|
|
// StatFS implements Inode.StatFS.
|
|
func (*InodeNoStatFS) StatFS(context.Context, *vfs.Filesystem) (linux.Statfs, error) {
|
|
return linux.Statfs{}, linuxerr.ENOSYS
|
|
}
|
|
|
|
// InodeWatches partially implements Inode.
|
|
//
|
|
// +stateify savable
|
|
type InodeWatches struct {
|
|
watches vfs.Watches
|
|
}
|
|
|
|
// Watches implements Inode.Watches.
|
|
func (i *InodeWatches) Watches() *vfs.Watches {
|
|
return &i.watches
|
|
}
|
|
|
|
// InodeAnonymous partially implements Inode.
|
|
//
|
|
// +stateify savable
|
|
type InodeAnonymous struct{}
|
|
|
|
// Anonymous implements Inode.Anonymous
|
|
func (*InodeAnonymous) Anonymous() bool {
|
|
return true
|
|
}
|
|
|
|
// InodeNotAnonymous partially implements Inode.
|
|
//
|
|
// +stateify savable
|
|
type InodeNotAnonymous struct{}
|
|
|
|
// Anonymous implements Inode.Anonymous
|
|
func (*InodeNotAnonymous) Anonymous() bool {
|
|
return false
|
|
}
|
|
|
|
// InodeFSOwned represents inodes whose lifecycle is entirely managed by the
|
|
// filesystem.
|
|
//
|
|
// +stateify savable
|
|
type InodeFSOwned struct{}
|
|
|
|
// RegisterDentry implements Inode.RegisterDentry.
|
|
func (*InodeFSOwned) RegisterDentry(d *Dentry) {}
|
|
|
|
// UnregisterDentry implements Remove.UnregisterDentry.
|
|
func (*InodeFSOwned) UnregisterDentry(d *Dentry) {}
|