summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/ramfs
diff options
context:
space:
mode:
authorGoogler <noreply@google.com>2018-04-27 10:37:02 -0700
committerAdin Scannell <ascannell@google.com>2018-04-28 01:44:26 -0400
commitd02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 (patch)
tree54f95eef73aee6bacbfc736fffc631be2605ed53 /pkg/sentry/fs/ramfs
parentf70210e742919f40aa2f0934a22f1c9ba6dada62 (diff)
Check in gVisor.
PiperOrigin-RevId: 194583126 Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
Diffstat (limited to 'pkg/sentry/fs/ramfs')
-rw-r--r--pkg/sentry/fs/ramfs/BUILD62
-rw-r--r--pkg/sentry/fs/ramfs/dir.go364
-rw-r--r--pkg/sentry/fs/ramfs/file.go148
-rw-r--r--pkg/sentry/fs/ramfs/ramfs.go433
-rw-r--r--pkg/sentry/fs/ramfs/socket.go42
-rw-r--r--pkg/sentry/fs/ramfs/symlink.go72
-rw-r--r--pkg/sentry/fs/ramfs/test/BUILD31
-rw-r--r--pkg/sentry/fs/ramfs/test/test.go46
-rw-r--r--pkg/sentry/fs/ramfs/tree.go71
-rw-r--r--pkg/sentry/fs/ramfs/tree_test.go79
10 files changed, 1348 insertions, 0 deletions
diff --git a/pkg/sentry/fs/ramfs/BUILD b/pkg/sentry/fs/ramfs/BUILD
new file mode 100644
index 000000000..663a1aeb9
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/BUILD
@@ -0,0 +1,62 @@
+package(licenses = ["notice"]) # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("//tools/go_stateify:defs.bzl", "go_stateify")
+
+go_stateify(
+ name = "ramfs_state",
+ srcs = [
+ "dir.go",
+ "file.go",
+ "ramfs.go",
+ "socket.go",
+ "symlink.go",
+ ],
+ out = "ramfs_state.go",
+ package = "ramfs",
+)
+
+go_library(
+ name = "ramfs",
+ srcs = [
+ "dir.go",
+ "file.go",
+ "ramfs.go",
+ "ramfs_state.go",
+ "socket.go",
+ "symlink.go",
+ "tree.go",
+ ],
+ importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs",
+ visibility = ["//pkg/sentry:internal"],
+ deps = [
+ "//pkg/amutex",
+ "//pkg/log",
+ "//pkg/refs",
+ "//pkg/secio",
+ "//pkg/sentry/context",
+ "//pkg/sentry/device",
+ "//pkg/sentry/fs",
+ "//pkg/sentry/fs/anon",
+ "//pkg/sentry/fs/fsutil",
+ "//pkg/sentry/kernel/time",
+ "//pkg/sentry/memmap",
+ "//pkg/sentry/safemem",
+ "//pkg/sentry/usermem",
+ "//pkg/state",
+ "//pkg/syserror",
+ "//pkg/tcpip/transport/unix",
+ "//pkg/waiter",
+ ],
+)
+
+go_test(
+ name = "ramfs_test",
+ size = "small",
+ srcs = ["tree_test.go"],
+ embed = [":ramfs"],
+ deps = [
+ "//pkg/sentry/context/contexttest",
+ "//pkg/sentry/fs",
+ ],
+)
diff --git a/pkg/sentry/fs/ramfs/dir.go b/pkg/sentry/fs/ramfs/dir.go
new file mode 100644
index 000000000..bf4cd8dfd
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/dir.go
@@ -0,0 +1,364 @@
+// 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
+
+import (
+ "sync"
+ "syscall"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
+ "gvisor.googlesource.com/gvisor/pkg/syserror"
+ "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/unix"
+)
+
+// CreateOps represents operations to create different file types.
+type CreateOps struct {
+ // NewDir creates a new directory.
+ NewDir func(ctx context.Context, dir *fs.Inode, perms fs.FilePermissions) (*fs.Inode, error)
+
+ // NewFile creates a new file.
+ NewFile func(ctx context.Context, dir *fs.Inode, perms fs.FilePermissions) (*fs.Inode, error)
+
+ // NewSymlink creates a new symlink with permissions 0777.
+ NewSymlink func(ctx context.Context, dir *fs.Inode, target string) (*fs.Inode, error)
+
+ // NewBoundEndpoint creates a new socket.
+ NewBoundEndpoint func(ctx context.Context, dir *fs.Inode, ep unix.BoundEndpoint, perms fs.FilePermissions) (*fs.Inode, error)
+
+ // NewFifo creates a new fifo.
+ NewFifo func(ctx context.Context, dir *fs.Inode, perm fs.FilePermissions) (*fs.Inode, error)
+}
+
+// Dir represents a single directory in the filesystem.
+type Dir struct {
+ Entry
+
+ // CreateOps may be provided.
+ //
+ // These may only be modified during initialization (while the application
+ // is not running). No sychronization is performed when accessing these
+ // operations during syscalls.
+ *CreateOps `state:"nosave"`
+
+ // mu protects the fields below.
+ mu sync.Mutex `state:"nosave"`
+
+ // children are inodes that are in this directory. A reference is held
+ // on each inode while it is in the map.
+ children map[string]*fs.Inode
+
+ // dentryMap is a sortedDentryMap containing entries for all children.
+ // Its entries ar kept up-to-date with d.children.
+ dentryMap *fs.SortedDentryMap
+}
+
+// InitDir initializes a directory.
+func (d *Dir) InitDir(ctx context.Context, contents map[string]*fs.Inode, owner fs.FileOwner, perms fs.FilePermissions) {
+ d.InitEntry(ctx, owner, perms)
+ if contents == nil {
+ contents = make(map[string]*fs.Inode)
+ }
+ d.children = contents
+ // Build the entries map ourselves, rather than calling addChildLocked,
+ // because it will be faster.
+ entries := make(map[string]fs.DentAttr, len(contents))
+ for name, inode := range contents {
+ entries[name] = fs.DentAttr{
+ Type: inode.StableAttr.Type,
+ InodeID: inode.StableAttr.InodeID,
+ }
+ }
+ d.dentryMap = fs.NewSortedDentryMap(entries)
+
+ // Directories have an extra link, corresponding to '.'.
+ d.AddLink()
+}
+
+// addChildLocked add the child inode, inheriting its reference.
+func (d *Dir) addChildLocked(name string, inode *fs.Inode) {
+ d.children[name] = inode
+ d.dentryMap.Add(name, fs.DentAttr{
+ Type: inode.StableAttr.Type,
+ InodeID: inode.StableAttr.InodeID,
+ })
+
+ // If the child is a directory, increment this dir's link count,
+ // corresponding to '..' from the subdirectory.
+ if fs.IsDir(inode.StableAttr) {
+ d.AddLink()
+ }
+
+ // Given we're now adding this inode to the directory we must also
+ // increase its link count. Similiarly we decremented it in removeChildLocked.
+ inode.AddLink()
+}
+
+// AddChild adds a child to this dir.
+func (d *Dir) AddChild(ctx context.Context, name string, inode *fs.Inode) {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ d.addChildLocked(name, inode)
+}
+
+// FindChild returns (child, true) if the directory contains name.
+func (d *Dir) FindChild(name string) (*fs.Inode, bool) {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ child, ok := d.children[name]
+ return child, ok
+}
+
+// removeChildLocked attempts to remove an entry from this directory.
+// This Entry's mutex must be held. It returns the removed Inode.
+func (d *Dir) removeChildLocked(ctx context.Context, name string) (*fs.Inode, error) {
+ inode, ok := d.children[name]
+ if !ok {
+ return nil, ErrNotFound
+ }
+
+ delete(d.children, name)
+ d.dentryMap.Remove(name)
+ d.Entry.NotifyModification(ctx)
+
+ // If the child was a subdirectory, then we must decrement this dir's
+ // link count which was the child's ".." directory entry.
+ if fs.IsDir(inode.StableAttr) {
+ d.DropLink()
+ }
+
+ // Update ctime.
+ inode.NotifyStatusChange(ctx)
+
+ // Given we're now removing this inode to the directory we must also
+ // decrease its link count. Similiarly it is increased in addChildLocked.
+ inode.DropLink()
+
+ return inode, nil
+}
+
+// RemoveEntry attempts to remove an entry from this directory.
+func (d *Dir) RemoveEntry(ctx context.Context, name string) error {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ inode, err := d.removeChildLocked(ctx, name)
+ if err != nil {
+ return err
+ }
+
+ // Remove our reference on the inode.
+ inode.DecRef()
+ return nil
+}
+
+// Remove removes the named non-directory.
+func (d *Dir) Remove(ctx context.Context, dir *fs.Inode, name string) error {
+ return d.RemoveEntry(ctx, name)
+}
+
+// RemoveDirectory removes the named directory.
+func (d *Dir) RemoveDirectory(ctx context.Context, dir *fs.Inode, name string) error {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ n, err := d.walkLocked(ctx, name)
+ if err != nil {
+ return err
+ }
+ dirCtx := &fs.DirCtx{}
+ if _, err := n.HandleOps().DeprecatedReaddir(ctx, dirCtx, 0); err != nil {
+ return err
+ }
+ if len(dirCtx.DentAttrs()) > 0 {
+ return ErrNotEmpty
+ }
+ inode, err := d.removeChildLocked(ctx, name)
+ if err != nil {
+ return err
+ }
+
+ // Remove our reference on the inode.
+ inode.DecRef()
+
+ return err
+}
+
+// Lookup loads an inode at p into a Dirent.
+func (d *Dir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ inode, err := d.walkLocked(ctx, p)
+ if err != nil {
+ return nil, err
+ }
+
+ // Take a reference on the inode before returning it. This reference
+ // is owned by the dirent we are about to create.
+ inode.IncRef()
+ return fs.NewDirent(inode, p), nil
+}
+
+// walkLocked must be called with this Entry's mutex held.
+func (d *Dir) walkLocked(ctx context.Context, p string) (*fs.Inode, error) {
+ d.Entry.NotifyAccess(ctx)
+
+ // Lookup a child node.
+ if inode, ok := d.children[p]; ok {
+ return inode, nil
+ }
+
+ // fs.InodeOperations.Lookup returns syserror.ENOENT if p
+ // does not exist.
+ return nil, syserror.ENOENT
+}
+
+// createInodeOperationsCommon creates a new child node at this dir by calling
+// makeInodeOperations. It is the common logic for creating a new child.
+func (d *Dir) createInodeOperationsCommon(ctx context.Context, name string, makeInodeOperations func() (*fs.Inode, error)) (*fs.Inode, error) {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ if _, ok := d.children[name]; ok {
+ return nil, syscall.EEXIST
+ }
+
+ inode, err := makeInodeOperations()
+ if err != nil {
+ return nil, err
+ }
+
+ d.addChildLocked(name, inode)
+ d.Entry.NotifyModification(ctx)
+
+ return inode, nil
+}
+
+// Create creates a new Inode with the given name and returns its File.
+func (d *Dir) Create(ctx context.Context, dir *fs.Inode, name string, flags fs.FileFlags, perms fs.FilePermissions) (*fs.File, error) {
+ if d.CreateOps == nil || d.CreateOps.NewFile == nil {
+ return nil, ErrDenied
+ }
+
+ inode, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) {
+ return d.NewFile(ctx, dir, perms)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Take an extra ref on inode, which will be owned by the dirent.
+ inode.IncRef()
+
+ // Create the Dirent and corresponding file.
+ created := fs.NewDirent(inode, name)
+ defer created.DecRef()
+ return created.Inode.GetFile(ctx, created, flags)
+}
+
+// CreateLink returns a new link.
+func (d *Dir) CreateLink(ctx context.Context, dir *fs.Inode, oldname, newname string) error {
+ if d.CreateOps == nil || d.CreateOps.NewSymlink == nil {
+ return ErrDenied
+ }
+ _, err := d.createInodeOperationsCommon(ctx, newname, func() (*fs.Inode, error) {
+ return d.NewSymlink(ctx, dir, oldname)
+ })
+ return err
+}
+
+// CreateHardLink creates a new hard link.
+func (d *Dir) CreateHardLink(ctx context.Context, dir *fs.Inode, target *fs.Inode, name string) error {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ // Take an extra reference on the inode and add it to our children.
+ target.IncRef()
+
+ // The link count will be incremented in addChildLocked.
+ d.addChildLocked(name, target)
+ d.Entry.NotifyModification(ctx)
+
+ // Update ctime.
+ target.NotifyStatusChange(ctx)
+
+ return nil
+}
+
+// CreateDirectory returns a new subdirectory.
+func (d *Dir) CreateDirectory(ctx context.Context, dir *fs.Inode, name string, perms fs.FilePermissions) error {
+ if d.CreateOps == nil || d.CreateOps.NewDir == nil {
+ return ErrDenied
+ }
+ _, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) {
+ return d.NewDir(ctx, dir, perms)
+ })
+ // TODO: Support updating status times, as those should be
+ // updated by links.
+ return err
+}
+
+// Bind implements fs.InodeOperations.Bind.
+func (d *Dir) Bind(ctx context.Context, dir *fs.Inode, name string, ep unix.BoundEndpoint, perms fs.FilePermissions) error {
+ if d.CreateOps == nil || d.CreateOps.NewBoundEndpoint == nil {
+ return ErrDenied
+ }
+ _, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) {
+ return d.NewBoundEndpoint(ctx, dir, ep, perms)
+ })
+ if err == syscall.EEXIST {
+ return syscall.EADDRINUSE
+ }
+ return err
+}
+
+// CreateFifo implements fs.InodeOperations.CreateFifo.
+func (d *Dir) CreateFifo(ctx context.Context, dir *fs.Inode, name string, perms fs.FilePermissions) error {
+ if d.CreateOps == nil || d.CreateOps.NewFifo == nil {
+ return ErrDenied
+ }
+ _, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) {
+ return d.NewFifo(ctx, dir, perms)
+ })
+ return err
+}
+
+func (d *Dir) readdirLocked(ctx context.Context, dirCtx *fs.DirCtx, offset int) (int, error) {
+ // Serialize the entries in dentryMap.
+ n, err := fs.GenericReaddir(dirCtx, d.dentryMap)
+
+ // Touch the access time.
+ d.Entry.NotifyAccess(ctx)
+
+ return offset + n, err
+}
+
+// DeprecatedReaddir emits the entries contained in this directory.
+func (d *Dir) DeprecatedReaddir(ctx context.Context, dirCtx *fs.DirCtx, offset int) (int, error) {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ return d.readdirLocked(ctx, dirCtx, offset)
+}
+
+// DeprecatedPreadv always returns ErrIsDirectory
+func (*Dir) DeprecatedPreadv(context.Context, usermem.IOSequence, int64) (int64, error) {
+ return 0, ErrIsDirectory
+}
+
+// DeprecatedPwritev always returns ErrIsDirectory
+func (*Dir) DeprecatedPwritev(context.Context, usermem.IOSequence, int64) (int64, error) {
+ return 0, ErrIsDirectory
+}
diff --git a/pkg/sentry/fs/ramfs/file.go b/pkg/sentry/fs/ramfs/file.go
new file mode 100644
index 000000000..e8363c3e2
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/file.go
@@ -0,0 +1,148 @@
+// 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
+
+import (
+ "io"
+ "sync"
+
+ "gvisor.googlesource.com/gvisor/pkg/secio"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/safemem"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
+ "gvisor.googlesource.com/gvisor/pkg/syserror"
+)
+
+// File represents a unique file. It uses a simple byte slice as storage, and
+// thus should only be used for small files.
+//
+// A File is not mappable.
+type File struct {
+ Entry
+
+ // mu protects the fields below.
+ mu sync.Mutex `state:"nosave"`
+
+ // data tracks backing data for the file.
+ data []byte
+}
+
+// InitFile initializes a file.
+func (f *File) InitFile(ctx context.Context, owner fs.FileOwner, perms fs.FilePermissions) {
+ f.InitEntry(ctx, owner, perms)
+}
+
+// UnstableAttr returns unstable attributes of this ramfs file.
+func (f *File) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ uattr, _ := f.Entry.UnstableAttr(ctx, inode)
+ uattr.Size = int64(len(f.data))
+ uattr.Usage = f.usageLocked()
+
+ return uattr, nil
+}
+
+// usageLocked returns the disk usage. Caller must hold f.mu.
+func (f *File) usageLocked() int64 {
+ return int64(len(f.data))
+}
+
+// Append appends the given data. This is for internal use.
+func (f *File) Append(data []byte) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ f.data = append(f.data, data...)
+}
+
+// Truncate truncates this node.
+func (f *File) Truncate(ctx context.Context, inode *fs.Inode, l int64) error {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ if l < int64(len(f.data)) {
+ // Remove excess bytes.
+ f.data = f.data[:l]
+ return nil
+ } else if l > int64(len(f.data)) {
+ // Create a new slice with size l, and copy f.data into it.
+ d := make([]byte, l)
+ copy(d, f.data)
+ f.data = d
+ }
+ f.Entry.NotifyModification(ctx)
+ return nil
+}
+
+// ReadAt implements io.ReaderAt.
+func (f *File) ReadAt(data []byte, offset int64) (int, error) {
+ if offset < 0 {
+ return 0, ErrInvalidOp
+ }
+ if offset >= int64(len(f.data)) {
+ return 0, io.EOF
+ }
+ n := copy(data, f.data[offset:])
+ // Did we read past the end?
+ if offset+int64(len(data)) >= int64(len(f.data)) {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// DeprecatedPreadv reads into a collection of slices from a given offset.
+func (f *File) DeprecatedPreadv(ctx context.Context, dst usermem.IOSequence, offset int64) (int64, error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ if offset >= int64(len(f.data)) {
+ return 0, io.EOF
+ }
+ n, err := dst.CopyOut(ctx, f.data[offset:])
+ if n > 0 {
+ f.Entry.NotifyAccess(ctx)
+ }
+ return int64(n), err
+}
+
+// WriteAt implements io.WriterAt.
+func (f *File) WriteAt(data []byte, offset int64) (int, error) {
+ if offset < 0 {
+ return 0, ErrInvalidOp
+ }
+ newLen := offset + int64(len(data))
+ if newLen < 0 {
+ // Overflow.
+ return 0, syserror.EINVAL
+ }
+ if newLen > int64(len(f.data)) {
+ // Copy f.data into new slice with expanded length.
+ d := make([]byte, newLen)
+ copy(d, f.data)
+ f.data = d
+ }
+ return copy(f.data[offset:], data), nil
+}
+
+// DeprecatedPwritev writes from a collection of slices at a given offset.
+func (f *File) DeprecatedPwritev(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ n, err := src.CopyInTo(ctx, safemem.FromIOWriter{secio.NewOffsetWriter(f, offset)})
+ if n > 0 {
+ f.Entry.NotifyModification(ctx)
+ }
+ return n, err
+}
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)
+}
diff --git a/pkg/sentry/fs/ramfs/socket.go b/pkg/sentry/fs/ramfs/socket.go
new file mode 100644
index 000000000..b0c79325f
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/socket.go
@@ -0,0 +1,42 @@
+// 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
+
+import (
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/unix"
+)
+
+// Socket represents a socket.
+type Socket struct {
+ Entry
+
+ // ep is the bound endpoint.
+ ep unix.BoundEndpoint
+}
+
+// InitSocket initializes a socket.
+func (s *Socket) InitSocket(ctx context.Context, ep unix.BoundEndpoint, owner fs.FileOwner, perms fs.FilePermissions) {
+ s.InitEntry(ctx, owner, perms)
+ s.ep = ep
+}
+
+// BoundEndpoint returns the socket data.
+func (s *Socket) BoundEndpoint(*fs.Inode, string) unix.BoundEndpoint {
+ // ramfs only supports stored sentry internal sockets. Only gofer sockets
+ // care about the path argument.
+ return s.ep
+}
diff --git a/pkg/sentry/fs/ramfs/symlink.go b/pkg/sentry/fs/ramfs/symlink.go
new file mode 100644
index 000000000..9bbf78619
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/symlink.go
@@ -0,0 +1,72 @@
+// 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
+
+import (
+ "sync"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+)
+
+// Symlink represents a symlink.
+type Symlink struct {
+ Entry
+
+ mu sync.Mutex `state:"nosave"`
+
+ // Target is the symlink target.
+ Target string
+}
+
+// InitSymlink initializes a symlink, pointing to the given target.
+// A symlink is assumed to always have permissions 0777.
+func (s *Symlink) InitSymlink(ctx context.Context, owner fs.FileOwner, target string) {
+ s.InitEntry(ctx, owner, fs.FilePermsFromMode(0777))
+ s.Target = target
+}
+
+// UnstableAttr returns all attributes of this ramfs symlink.
+func (s *Symlink) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) {
+ uattr, _ := s.Entry.UnstableAttr(ctx, inode)
+ uattr.Size = int64(len(s.Target))
+ uattr.Usage = uattr.Size
+ return uattr, nil
+}
+
+// Check implements InodeOperations.Check.
+func (s *Symlink) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool {
+ return fs.ContextCanAccessFile(ctx, inode, p)
+}
+
+// SetPermissions on a symlink is always rejected.
+func (s *Symlink) SetPermissions(context.Context, *fs.Inode, fs.FilePermissions) bool {
+ return false
+}
+
+// Readlink reads the symlink value.
+func (s *Symlink) Readlink(ctx context.Context, _ *fs.Inode) (string, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.Entry.NotifyAccess(ctx)
+ return s.Target, nil
+}
+
+// Getlink returns ErrResolveViaReadlink, falling back to walking to the result
+// of Readlink().
+func (*Symlink) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) {
+ return nil, fs.ErrResolveViaReadlink
+}
diff --git a/pkg/sentry/fs/ramfs/test/BUILD b/pkg/sentry/fs/ramfs/test/BUILD
new file mode 100644
index 000000000..074b0f5ad
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/test/BUILD
@@ -0,0 +1,31 @@
+package(licenses = ["notice"]) # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("//tools/go_stateify:defs.bzl", "go_stateify")
+
+go_stateify(
+ name = "test_state",
+ srcs = [
+ "test.go",
+ ],
+ out = "test_state.go",
+ package = "test",
+)
+
+go_library(
+ name = "test",
+ testonly = 1,
+ srcs = [
+ "test.go",
+ "test_state.go",
+ ],
+ importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs/test",
+ visibility = ["//pkg/sentry:internal"],
+ deps = [
+ "//pkg/sentry/context",
+ "//pkg/sentry/device",
+ "//pkg/sentry/fs",
+ "//pkg/sentry/fs/ramfs",
+ "//pkg/state",
+ ],
+)
diff --git a/pkg/sentry/fs/ramfs/test/test.go b/pkg/sentry/fs/ramfs/test/test.go
new file mode 100644
index 000000000..fb669558f
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/test/test.go
@@ -0,0 +1,46 @@
+// 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 test provides a simple ramfs-based filesystem for use in testing.
+package test
+
+import (
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs"
+)
+
+// Dir is a simple ramfs.Dir that supports save/restore as-is.
+type Dir struct {
+ ramfs.Dir
+}
+
+// NewDir returns a simple ramfs directory with the passed contents.
+func NewDir(ctx context.Context, contents map[string]*fs.Inode, perms fs.FilePermissions) *Dir {
+ d := &Dir{}
+ d.InitDir(ctx, contents, fs.RootOwner, perms)
+ return d
+}
+
+// File is a simple ramfs.File that supports save/restore as-is.
+type File struct {
+ ramfs.File
+}
+
+// NewFile returns a simple ramfs File.
+func NewFile(ctx context.Context, perms fs.FilePermissions) *File {
+ f := &File{}
+ f.InitFile(ctx, fs.RootOwner, perms)
+ return f
+}
diff --git a/pkg/sentry/fs/ramfs/tree.go b/pkg/sentry/fs/ramfs/tree.go
new file mode 100644
index 000000000..1fb335f74
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/tree.go
@@ -0,0 +1,71 @@
+// 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
+
+import (
+ "fmt"
+ "path"
+ "strings"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs/anon"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
+)
+
+// MakeDirectoryTree constructs a ramfs tree of all directories containing
+// subdirs. Each element of subdir must be a clean path, and cannot be empty or
+// "/".
+func MakeDirectoryTree(ctx context.Context, msrc *fs.MountSource, subdirs []string) (*fs.Inode, error) {
+ root := emptyDir(ctx, msrc)
+ for _, subdir := range subdirs {
+ if path.Clean(subdir) != subdir {
+ return nil, fmt.Errorf("cannot add subdir at an unclean path: %q", subdir)
+ }
+ if subdir == "" || subdir == "/" {
+ return nil, fmt.Errorf("cannot add subdir at %q", subdir)
+ }
+ makeSubdir(ctx, msrc, root.InodeOperations.(*Dir), subdir)
+ }
+ return root, nil
+}
+
+// makeSubdir installs into root each component of subdir. The final component is
+// a *ramfs.Dir.
+func makeSubdir(ctx context.Context, msrc *fs.MountSource, root *Dir, subdir string) {
+ for _, c := range strings.Split(subdir, "/") {
+ if len(c) == 0 {
+ continue
+ }
+ child, ok := root.FindChild(c)
+ if !ok {
+ child = emptyDir(ctx, msrc)
+ root.AddChild(ctx, c, child)
+ }
+ root = child.InodeOperations.(*Dir)
+ }
+}
+
+// emptyDir returns an empty *ramfs.Dir that is traversable but not writable.
+func emptyDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
+ dir := &Dir{}
+ dir.InitDir(ctx, make(map[string]*fs.Inode), fs.RootOwner, fs.FilePermsFromMode(0555))
+ return fs.NewInode(dir, msrc, fs.StableAttr{
+ DeviceID: anon.PseudoDevice.DeviceID(),
+ InodeID: anon.PseudoDevice.NextIno(),
+ BlockSize: usermem.PageSize,
+ Type: fs.Directory,
+ })
+}
diff --git a/pkg/sentry/fs/ramfs/tree_test.go b/pkg/sentry/fs/ramfs/tree_test.go
new file mode 100644
index 000000000..68e2929d5
--- /dev/null
+++ b/pkg/sentry/fs/ramfs/tree_test.go
@@ -0,0 +1,79 @@
+// 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
+
+import (
+ "testing"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+)
+
+func TestMakeDirectoryTree(t *testing.T) {
+ mount := fs.NewNonCachingMountSource(nil, fs.MountSourceFlags{})
+
+ for _, test := range []struct {
+ name string
+ subdirs []string
+ }{
+ {
+ name: "abs paths",
+ subdirs: []string{
+ "/tmp",
+ "/tmp/a/b",
+ "/tmp/a/c/d",
+ "/tmp/c",
+ "/proc",
+ "/dev/a/b",
+ "/tmp",
+ },
+ },
+ {
+ name: "rel paths",
+ subdirs: []string{
+ "tmp",
+ "tmp/a/b",
+ "tmp/a/c/d",
+ "tmp/c",
+ "proc",
+ "dev/a/b",
+ "tmp",
+ },
+ },
+ } {
+ ctx := contexttest.Context(t)
+ tree, err := MakeDirectoryTree(ctx, mount, test.subdirs)
+ if err != nil {
+ t.Errorf("%s: failed to make ramfs tree, got error %v, want nil", test.name, err)
+ continue
+ }
+
+ // Expect to be able to find each of the paths.
+ mm, err := fs.NewMountNamespace(ctx, tree)
+ if err != nil {
+ t.Errorf("%s: failed to create mount manager: %v", test.name, err)
+ continue
+ }
+ root := mm.Root()
+ defer mm.DecRef()
+
+ for _, p := range test.subdirs {
+ if _, err := mm.FindInode(ctx, root, nil, p, 0); err != nil {
+ t.Errorf("%s: failed to find node %s: %v", test.name, p, err)
+ break
+ }
+ }
+ }
+}