summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/ramfs/ramfs.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fs/ramfs/ramfs.go')
-rw-r--r--pkg/sentry/fs/ramfs/ramfs.go433
1 files changed, 433 insertions, 0 deletions
diff --git a/pkg/sentry/fs/ramfs/ramfs.go b/pkg/sentry/fs/ramfs/ramfs.go
new file mode 100644
index 000000000..04f2d38de
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/ramfs.go
@@ -0,0 +1,433 @@
+// Copyright 2018 Google Inc.
+//
+// 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/usermem"
+ "gvisor.googlesource.com/gvisor/pkg/syserror"
+ "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/unix"
+ "gvisor.googlesource.com/gvisor/pkg/waiter"
+)
+
+var (
+ // ErrInvalidOp indicates the operation is not valid.
+ ErrInvalidOp = errors.New("invalid operation")
+
+ // ErrDenied indicates the operation was denid.
+ 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.
+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()
+ defer e.mu.Unlock()
+ return e.unstable, 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) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ if value, ok := e.xattrs[name]; 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()
+ defer e.mu.Unlock()
+ e.xattrs[name] = value
+ return nil
+}
+
+// Listxattr implements fs.InodeOperations.Listxattr.
+func (e *Entry) Listxattr(inode *fs.Inode) (map[string]struct{}, error) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ names := make(map[string]struct{}, len(e.xattrs))
+ for name := range e.xattrs {
+ names[name] = struct{}{}
+ }
+ 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()
+ defer e.mu.Unlock()
+ e.unstable.Perms = p
+ e.unstable.StatusChangeTime = ktime.NowFromContext(ctx)
+ return true
+}
+
+// SetOwner always sets ownership.
+func (e *Entry) SetOwner(ctx context.Context, inode *fs.Inode, owner fs.FileOwner) error {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ if owner.UID.Ok() {
+ e.unstable.Owner.UID = owner.UID
+ }
+ if owner.GID.Ok() {
+ e.unstable.Owner.GID = owner.GID
+ }
+ 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()
+ defer e.mu.Unlock()
+
+ 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
+ return nil
+}
+
+// NotifyStatusChange updates the status change time (ctime).
+func (e *Entry) NotifyStatusChange(ctx context.Context) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.unstable.StatusChangeTime = ktime.NowFromContext(ctx)
+}
+
+// StatusChangeTime returns the last status change time for this node.
+func (e *Entry) StatusChangeTime() ktime.Time {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.unstable.StatusChangeTime
+}
+
+// NotifyModification updates the modification time and the status change time.
+func (e *Entry) NotifyModification(ctx context.Context) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ now := ktime.NowFromContext(ctx)
+ e.unstable.ModificationTime = now
+ e.unstable.StatusChangeTime = now
+}
+
+// ModificationTime returns the last modification time for this node.
+func (e *Entry) ModificationTime() ktime.Time {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.unstable.ModificationTime
+}
+
+// NotifyAccess updates the access time.
+func (e *Entry) NotifyAccess(ctx context.Context) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ now := ktime.NowFromContext(ctx)
+ e.unstable.AccessTime = now
+}
+
+// AccessTime returns the last access time for this node.
+func (e *Entry) AccessTime() ktime.Time {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.unstable.AccessTime
+}
+
+// Permissions returns permissions on this entry.
+func (e *Entry) Permissions() fs.FilePermissions {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.unstable.Perms
+}
+
+// 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, unix.BoundEndpoint, fs.FilePermissions) error {
+ return 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()
+ defer e.mu.Unlock()
+ e.unstable.Links++
+}
+
+// DropLink implements InodeOperationss.DropLink.
+func (e *Entry) DropLink() {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.unstable.Links--
+}
+
+// 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)
+}