442 lines
12 KiB
Go
442 lines
12 KiB
Go
// Copyright 2018 Google LLC
|
|
//
|
|
// 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 ramfs implements an in-memory file system that can be associated with
|
|
// any device.
|
|
package ramfs
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil"
|
|
ktime "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/memmap"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/socket/unix/transport"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
|
|
"gvisor.googlesource.com/gvisor/pkg/syserror"
|
|
"gvisor.googlesource.com/gvisor/pkg/waiter"
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidOp indicates the operation is not valid.
|
|
ErrInvalidOp = errors.New("invalid operation")
|
|
|
|
// ErrDenied indicates the operation was denied.
|
|
ErrDenied = errors.New("operation denied")
|
|
|
|
// ErrNotFound indicates that a node was not found on a walk.
|
|
ErrNotFound = errors.New("node not found")
|
|
|
|
// ErrCrossDevice indicates a cross-device link or rename.
|
|
ErrCrossDevice = errors.New("can't link across filesystems")
|
|
|
|
// ErrIsDirectory indicates that the operation failed because
|
|
// the node is a directory.
|
|
ErrIsDirectory = errors.New("is a directory")
|
|
|
|
// ErrNotDirectory indicates that the operation failed because
|
|
// the node is a not directory.
|
|
ErrNotDirectory = errors.New("not a directory")
|
|
|
|
// ErrNotEmpty indicates that the operation failed because the
|
|
// directory is not empty.
|
|
ErrNotEmpty = errors.New("directory not empty")
|
|
)
|
|
|
|
// Entry represents common internal state for file and directory nodes.
|
|
// This may be used by other packages to easily create ramfs files.
|
|
//
|
|
// +stateify savable
|
|
type Entry struct {
|
|
waiter.AlwaysReady `state:"nosave"`
|
|
fsutil.NoMappable `state:"nosave"`
|
|
fsutil.NoopWriteOut `state:"nosave"`
|
|
fsutil.InodeNotSocket `state:"nosave"`
|
|
|
|
// mu protects the fields below.
|
|
mu sync.Mutex `state:"nosave"`
|
|
|
|
// unstable is unstable attributes.
|
|
unstable fs.UnstableAttr
|
|
|
|
// xattrs are the extended attributes of the Entry.
|
|
xattrs map[string][]byte
|
|
}
|
|
|
|
// InitEntry initializes an entry.
|
|
func (e *Entry) InitEntry(ctx context.Context, owner fs.FileOwner, p fs.FilePermissions) {
|
|
e.InitEntryWithAttr(ctx, fs.WithCurrentTime(ctx, fs.UnstableAttr{
|
|
Owner: owner,
|
|
Perms: p,
|
|
// Always start unlinked.
|
|
Links: 0,
|
|
}))
|
|
}
|
|
|
|
// InitEntryWithAttr initializes an entry with a complete set of attributes.
|
|
func (e *Entry) InitEntryWithAttr(ctx context.Context, uattr fs.UnstableAttr) {
|
|
e.unstable = uattr
|
|
e.xattrs = make(map[string][]byte)
|
|
}
|
|
|
|
// UnstableAttr implements fs.InodeOperations.UnstableAttr.
|
|
func (e *Entry) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) {
|
|
e.mu.Lock()
|
|
attr := e.unstable
|
|
e.mu.Unlock()
|
|
return attr, nil
|
|
}
|
|
|
|
// Check implements fs.InodeOperations.Check.
|
|
func (*Entry) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool {
|
|
return fs.ContextCanAccessFile(ctx, inode, p)
|
|
}
|
|
|
|
// Getxattr implements fs.InodeOperations.Getxattr.
|
|
func (e *Entry) Getxattr(inode *fs.Inode, name string) ([]byte, error) {
|
|
// Hot path. Avoid defers.
|
|
e.mu.Lock()
|
|
value, ok := e.xattrs[name]
|
|
e.mu.Unlock()
|
|
if ok {
|
|
return value, nil
|
|
}
|
|
return nil, syserror.ENOATTR
|
|
}
|
|
|
|
// Setxattr implements fs.InodeOperations.Setxattr.
|
|
func (e *Entry) Setxattr(inode *fs.Inode, name string, value []byte) error {
|
|
e.mu.Lock()
|
|
e.xattrs[name] = value
|
|
e.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Listxattr implements fs.InodeOperations.Listxattr.
|
|
func (e *Entry) Listxattr(inode *fs.Inode) (map[string]struct{}, error) {
|
|
e.mu.Lock()
|
|
names := make(map[string]struct{}, len(e.xattrs))
|
|
for name := range e.xattrs {
|
|
names[name] = struct{}{}
|
|
}
|
|
e.mu.Unlock()
|
|
return names, nil
|
|
}
|
|
|
|
// GetFile returns a fs.File backed by the dirent argument and flags.
|
|
func (*Entry) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
|
return fsutil.NewHandle(ctx, d, flags, d.Inode.HandleOps()), nil
|
|
}
|
|
|
|
// SetPermissions always sets the permissions.
|
|
func (e *Entry) SetPermissions(ctx context.Context, inode *fs.Inode, p fs.FilePermissions) bool {
|
|
e.mu.Lock()
|
|
e.unstable.Perms = p
|
|
e.unstable.StatusChangeTime = ktime.NowFromContext(ctx)
|
|
e.mu.Unlock()
|
|
return true
|
|
}
|
|
|
|
// SetOwner always sets ownership.
|
|
func (e *Entry) SetOwner(ctx context.Context, inode *fs.Inode, owner fs.FileOwner) error {
|
|
e.mu.Lock()
|
|
if owner.UID.Ok() {
|
|
e.unstable.Owner.UID = owner.UID
|
|
}
|
|
if owner.GID.Ok() {
|
|
e.unstable.Owner.GID = owner.GID
|
|
}
|
|
e.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// SetTimestamps sets the timestamps.
|
|
func (e *Entry) SetTimestamps(ctx context.Context, inode *fs.Inode, ts fs.TimeSpec) error {
|
|
if ts.ATimeOmit && ts.MTimeOmit {
|
|
return nil
|
|
}
|
|
|
|
e.mu.Lock()
|
|
now := ktime.NowFromContext(ctx)
|
|
if !ts.ATimeOmit {
|
|
if ts.ATimeSetSystemTime {
|
|
e.unstable.AccessTime = now
|
|
} else {
|
|
e.unstable.AccessTime = ts.ATime
|
|
}
|
|
}
|
|
if !ts.MTimeOmit {
|
|
if ts.MTimeSetSystemTime {
|
|
e.unstable.ModificationTime = now
|
|
} else {
|
|
e.unstable.ModificationTime = ts.MTime
|
|
}
|
|
}
|
|
e.unstable.StatusChangeTime = now
|
|
e.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// NotifyStatusChange updates the status change time (ctime).
|
|
func (e *Entry) NotifyStatusChange(ctx context.Context) {
|
|
e.mu.Lock()
|
|
e.unstable.StatusChangeTime = ktime.NowFromContext(ctx)
|
|
e.mu.Unlock()
|
|
}
|
|
|
|
// StatusChangeTime returns the last status change time for this node.
|
|
func (e *Entry) StatusChangeTime() ktime.Time {
|
|
e.mu.Lock()
|
|
t := e.unstable.StatusChangeTime
|
|
e.mu.Unlock()
|
|
return t
|
|
}
|
|
|
|
// NotifyModification updates the modification time and the status change time.
|
|
func (e *Entry) NotifyModification(ctx context.Context) {
|
|
e.mu.Lock()
|
|
now := ktime.NowFromContext(ctx)
|
|
e.unstable.ModificationTime = now
|
|
e.unstable.StatusChangeTime = now
|
|
e.mu.Unlock()
|
|
}
|
|
|
|
// ModificationTime returns the last modification time for this node.
|
|
func (e *Entry) ModificationTime() ktime.Time {
|
|
e.mu.Lock()
|
|
t := e.unstable.ModificationTime
|
|
e.mu.Unlock()
|
|
return t
|
|
}
|
|
|
|
// NotifyAccess updates the access time.
|
|
func (e *Entry) NotifyAccess(ctx context.Context) {
|
|
e.mu.Lock()
|
|
now := ktime.NowFromContext(ctx)
|
|
e.unstable.AccessTime = now
|
|
e.mu.Unlock()
|
|
}
|
|
|
|
// AccessTime returns the last access time for this node.
|
|
func (e *Entry) AccessTime() ktime.Time {
|
|
e.mu.Lock()
|
|
t := e.unstable.AccessTime
|
|
e.mu.Unlock()
|
|
return t
|
|
}
|
|
|
|
// Permissions returns permissions on this entry.
|
|
func (e *Entry) Permissions() fs.FilePermissions {
|
|
e.mu.Lock()
|
|
p := e.unstable.Perms
|
|
e.mu.Unlock()
|
|
return p
|
|
}
|
|
|
|
// Lookup is not supported by default.
|
|
func (*Entry) Lookup(context.Context, *fs.Inode, string) (*fs.Dirent, error) {
|
|
return nil, ErrInvalidOp
|
|
}
|
|
|
|
// Create is not supported by default.
|
|
func (*Entry) Create(context.Context, *fs.Inode, string, fs.FileFlags, fs.FilePermissions) (*fs.File, error) {
|
|
return nil, ErrInvalidOp
|
|
}
|
|
|
|
// CreateLink is not supported by default.
|
|
func (*Entry) CreateLink(context.Context, *fs.Inode, string, string) error {
|
|
return ErrInvalidOp
|
|
}
|
|
|
|
// CreateHardLink is not supported by default.
|
|
func (*Entry) CreateHardLink(context.Context, *fs.Inode, *fs.Inode, string) error {
|
|
return ErrInvalidOp
|
|
}
|
|
|
|
// IsVirtual returns true.
|
|
func (*Entry) IsVirtual() bool {
|
|
return true
|
|
}
|
|
|
|
// CreateDirectory is not supported by default.
|
|
func (*Entry) CreateDirectory(context.Context, *fs.Inode, string, fs.FilePermissions) error {
|
|
return ErrInvalidOp
|
|
}
|
|
|
|
// Bind is not supported by default.
|
|
func (*Entry) Bind(context.Context, *fs.Inode, string, transport.BoundEndpoint, fs.FilePermissions) (*fs.Dirent, error) {
|
|
return nil, ErrInvalidOp
|
|
}
|
|
|
|
// CreateFifo implements fs.InodeOperations.CreateFifo. CreateFifo is not supported by
|
|
// default.
|
|
func (*Entry) CreateFifo(context.Context, *fs.Inode, string, fs.FilePermissions) error {
|
|
return ErrInvalidOp
|
|
}
|
|
|
|
// Remove is not supported by default.
|
|
func (*Entry) Remove(context.Context, *fs.Inode, string) error {
|
|
return ErrInvalidOp
|
|
}
|
|
|
|
// RemoveDirectory is not supported by default.
|
|
func (*Entry) RemoveDirectory(context.Context, *fs.Inode, string) error {
|
|
return ErrInvalidOp
|
|
}
|
|
|
|
// StatFS always returns ENOSYS.
|
|
func (*Entry) StatFS(context.Context) (fs.Info, error) {
|
|
return fs.Info{}, syscall.ENOSYS
|
|
}
|
|
|
|
// Rename implements fs.InodeOperations.Rename.
|
|
func (e *Entry) Rename(ctx context.Context, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string) error {
|
|
return Rename(ctx, oldParent.InodeOperations, oldName, newParent.InodeOperations, newName)
|
|
}
|
|
|
|
// Rename renames from a *ramfs.Dir to another *ramfs.Dir.
|
|
func Rename(ctx context.Context, oldParent fs.InodeOperations, oldName string, newParent fs.InodeOperations, newName string) error {
|
|
op, ok := oldParent.(*Dir)
|
|
if !ok {
|
|
return ErrCrossDevice
|
|
}
|
|
np, ok := newParent.(*Dir)
|
|
if !ok {
|
|
return ErrCrossDevice
|
|
}
|
|
|
|
np.mu.Lock()
|
|
defer np.mu.Unlock()
|
|
|
|
// Check whether the ramfs entry to be replaced is a non-empty directory.
|
|
if replaced, ok := np.children[newName]; ok {
|
|
if fs.IsDir(replaced.StableAttr) {
|
|
// FIXME: simplify by pinning children of ramfs-backed directories
|
|
// in the Dirent tree: this allows us to generalize ramfs operations without
|
|
// relying on an implementation of Readdir (which may do anything, like require
|
|
// that the file be open ... which would be reasonable).
|
|
dirCtx := &fs.DirCtx{}
|
|
_, err := replaced.HandleOps().DeprecatedReaddir(ctx, dirCtx, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
attrs := dirCtx.DentAttrs()
|
|
|
|
// ramfs-backed directories should not contain "." and "..", but we do this
|
|
// just in case.
|
|
delete(attrs, ".")
|
|
delete(attrs, "..")
|
|
|
|
// If the directory to be replaced is not empty, reject the rename.
|
|
if len(attrs) != 0 {
|
|
return ErrNotEmpty
|
|
}
|
|
}
|
|
}
|
|
|
|
// Be careful, we may have already grabbed this mutex above.
|
|
if op != np {
|
|
op.mu.Lock()
|
|
defer op.mu.Unlock()
|
|
}
|
|
|
|
// Do the swap.
|
|
n := op.children[oldName]
|
|
op.removeChildLocked(ctx, oldName)
|
|
np.addChildLocked(newName, n)
|
|
|
|
// Update ctime.
|
|
n.NotifyStatusChange(ctx)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Truncate is not supported by default.
|
|
func (*Entry) Truncate(context.Context, *fs.Inode, int64) error {
|
|
return ErrInvalidOp
|
|
}
|
|
|
|
// Readlink always returns ENOLINK.
|
|
func (*Entry) Readlink(context.Context, *fs.Inode) (string, error) {
|
|
return "", syscall.ENOLINK
|
|
}
|
|
|
|
// Getlink always returns ENOLINK.
|
|
func (*Entry) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) {
|
|
return nil, syscall.ENOLINK
|
|
}
|
|
|
|
// Release is a no-op.
|
|
func (e *Entry) Release(context.Context) {}
|
|
|
|
// AddLink implements InodeOperationss.AddLink.
|
|
func (e *Entry) AddLink() {
|
|
e.mu.Lock()
|
|
e.unstable.Links++
|
|
e.mu.Unlock()
|
|
}
|
|
|
|
// DropLink implements InodeOperationss.DropLink.
|
|
func (e *Entry) DropLink() {
|
|
e.mu.Lock()
|
|
e.unstable.Links--
|
|
e.mu.Unlock()
|
|
}
|
|
|
|
// DeprecatedReaddir is not supported by default.
|
|
func (*Entry) DeprecatedReaddir(context.Context, *fs.DirCtx, int) (int, error) {
|
|
return 0, ErrNotDirectory
|
|
}
|
|
|
|
// DeprecatedPreadv always returns ErrInvalidOp.
|
|
func (*Entry) DeprecatedPreadv(context.Context, usermem.IOSequence, int64) (int64, error) {
|
|
return 0, ErrInvalidOp
|
|
}
|
|
|
|
// DeprecatedPwritev always returns ErrInvalidOp.
|
|
func (*Entry) DeprecatedPwritev(context.Context, usermem.IOSequence, int64) (int64, error) {
|
|
return 0, ErrInvalidOp
|
|
}
|
|
|
|
// DeprecatedFsync is a noop.
|
|
func (*Entry) DeprecatedFsync() error {
|
|
// Ignore, this is in memory.
|
|
return nil
|
|
}
|
|
|
|
// DeprecatedFlush always returns nil.
|
|
func (*Entry) DeprecatedFlush() error {
|
|
return nil
|
|
}
|
|
|
|
// DeprecatedMappable implements fs.InodeOperations.DeprecatedMappable.
|
|
func (*Entry) DeprecatedMappable(context.Context, *fs.Inode) (memmap.Mappable, bool) {
|
|
return nil, false
|
|
}
|
|
|
|
func init() {
|
|
// Register ramfs errors.
|
|
syserror.AddErrorTranslation(ErrInvalidOp, syscall.EINVAL)
|
|
syserror.AddErrorTranslation(ErrDenied, syscall.EACCES)
|
|
syserror.AddErrorTranslation(ErrNotFound, syscall.ENOENT)
|
|
syserror.AddErrorTranslation(ErrCrossDevice, syscall.EXDEV)
|
|
syserror.AddErrorTranslation(ErrIsDirectory, syscall.EISDIR)
|
|
syserror.AddErrorTranslation(ErrNotDirectory, syscall.ENOTDIR)
|
|
syserror.AddErrorTranslation(ErrNotEmpty, syscall.ENOTEMPTY)
|
|
}
|