diff options
author | gVisor bot <gvisor-bot@google.com> | 2019-06-02 06:44:55 +0000 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2019-06-02 06:44:55 +0000 |
commit | ceb0d792f328d1fc0692197d8856a43c3936a571 (patch) | |
tree | 83155f302eff44a78bcc30a3a08f4efe59a79379 /pkg/sentry/fs/ramfs | |
parent | deb7ecf1e46862d54f4b102f2d163cfbcfc37f3b (diff) | |
parent | 216da0b733dbed9aad9b2ab92ac75bcb906fd7ee (diff) |
Merge 216da0b7 (automated)
Diffstat (limited to 'pkg/sentry/fs/ramfs')
-rw-r--r-- | pkg/sentry/fs/ramfs/dir.go | 534 | ||||
-rwxr-xr-x | pkg/sentry/fs/ramfs/ramfs_state_autogen.go | 94 | ||||
-rw-r--r-- | pkg/sentry/fs/ramfs/socket.go | 85 | ||||
-rw-r--r-- | pkg/sentry/fs/ramfs/symlink.go | 106 | ||||
-rw-r--r-- | pkg/sentry/fs/ramfs/tree.go | 77 |
5 files changed, 896 insertions, 0 deletions
diff --git a/pkg/sentry/fs/ramfs/dir.go b/pkg/sentry/fs/ramfs/dir.go new file mode 100644 index 000000000..cd6e03d66 --- /dev/null +++ b/pkg/sentry/fs/ramfs/dir.go @@ -0,0 +1,534 @@ +// Copyright 2018 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 ramfs + +import ( + "fmt" + "sync" + "syscall" + + "gvisor.googlesource.com/gvisor/pkg/abi/linux" + "gvisor.googlesource.com/gvisor/pkg/sentry/context" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil" + "gvisor.googlesource.com/gvisor/pkg/sentry/socket/unix/transport" + "gvisor.googlesource.com/gvisor/pkg/syserror" +) + +// 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 transport.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. +// +// +stateify savable +type Dir struct { + fsutil.InodeGenericChecker `state:"nosave"` + fsutil.InodeIsDirAllocate `state:"nosave"` + fsutil.InodeIsDirTruncate `state:"nosave"` + fsutil.InodeNoopRelease `state:"nosave"` + fsutil.InodeNoopWriteOut `state:"nosave"` + fsutil.InodeNotMappable `state:"nosave"` + fsutil.InodeNotSocket `state:"nosave"` + fsutil.InodeNotSymlink `state:"nosave"` + fsutil.InodeVirtual `state:"nosave"` + + fsutil.InodeSimpleAttributes + fsutil.InodeSimpleExtendedAttributes + + // 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 are kept up-to-date with d.children. + dentryMap *fs.SortedDentryMap +} + +var _ fs.InodeOperations = (*Dir)(nil) + +// NewDir returns a new Dir with the given contents and attributes. +func NewDir(ctx context.Context, contents map[string]*fs.Inode, owner fs.FileOwner, perms fs.FilePermissions) *Dir { + d := &Dir{ + InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, perms, linux.RAMFS_MAGIC), + } + + 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() + + return d +} + +// addChildLocked add the child inode, inheriting its reference. +func (d *Dir) addChildLocked(ctx context.Context, 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() + // ctime updated below. + } + + // Given we're now adding this inode to the directory we must also + // increase its link count. Similarly we decrement it in removeChildLocked. + // + // Changing link count updates ctime. + inode.AddLink() + inode.InodeOperations.NotifyStatusChange(ctx) + + // We've change the directory. This always updates our mtime and ctime. + d.NotifyModificationAndStatusChange(ctx) +} + +// 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(ctx, 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 +} + +// Children returns the names and DentAttrs of all children. It can be used to +// implement Readdir for types that embed ramfs.Dir. +func (d *Dir) Children() ([]string, map[string]fs.DentAttr) { + d.mu.Lock() + defer d.mu.Unlock() + + // Return a copy to prevent callers from modifying our children. + names, entries := d.dentryMap.GetAll() + namesCopy := make([]string, len(names)) + copy(namesCopy, names) + + entriesCopy := make(map[string]fs.DentAttr) + for k, v := range entries { + entriesCopy[k] = v + } + + return namesCopy, entriesCopy +} + +// removeChildLocked attempts to remove an entry from this directory. +func (d *Dir) removeChildLocked(ctx context.Context, name string) (*fs.Inode, error) { + inode, ok := d.children[name] + if !ok { + return nil, syserror.EACCES + } + + delete(d.children, name) + d.dentryMap.Remove(name) + d.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() + // ctime changed below. + } + + // Given we're now removing this inode to the directory we must also + // decrease its link count. Similarly it is increased in addChildLocked. + // + // Changing link count updates ctime. + inode.DropLink() + inode.InodeOperations.NotifyStatusChange(ctx) + + // We've change the directory. This always updates our mtime and ctime. + d.NotifyModificationAndStatusChange(ctx) + + return inode, nil +} + +// Remove removes the named non-directory. +func (d *Dir) Remove(ctx context.Context, _ *fs.Inode, name string) error { + if len(name) > linux.NAME_MAX { + return syserror.ENAMETOOLONG + } + + 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 +} + +// RemoveDirectory removes the named directory. +func (d *Dir) RemoveDirectory(ctx context.Context, _ *fs.Inode, name string) error { + if len(name) > linux.NAME_MAX { + return syserror.ENAMETOOLONG + } + + d.mu.Lock() + defer d.mu.Unlock() + + // Get the child and make sure it is not empty. + childInode, err := d.walkLocked(ctx, name) + if err != nil { + return err + } + if ok, err := hasChildren(ctx, childInode); err != nil { + return err + } else if ok { + return syserror.ENOTEMPTY + } + + // Child was empty. Proceed with removal. + inode, err := d.removeChildLocked(ctx, name) + if err != nil { + return err + } + + // Remove our reference on the inode. + inode.DecRef() + + return nil +} + +// Lookup loads an inode at p into a Dirent. +func (d *Dir) Lookup(ctx context.Context, _ *fs.Inode, p string) (*fs.Dirent, error) { + if len(p) > linux.NAME_MAX { + return nil, syserror.ENAMETOOLONG + } + + 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 d.mu held. +func (d *Dir) walkLocked(ctx context.Context, p string) (*fs.Inode, error) { + // 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) { + if len(name) > linux.NAME_MAX { + return nil, syserror.ENAMETOOLONG + } + + d.mu.Lock() + defer d.mu.Unlock() + + inode, err := makeInodeOperations() + if err != nil { + return nil, err + } + + d.addChildLocked(ctx, name, inode) + + 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, syserror.EACCES + } + + 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 syserror.EACCES + } + _, 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 { + if len(name) > linux.NAME_MAX { + return syserror.ENAMETOOLONG + } + + 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(ctx, name, target) + + 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 syserror.EACCES + } + _, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) { + return d.NewDir(ctx, dir, perms) + }) + return err +} + +// Bind implements fs.InodeOperations.Bind. +func (d *Dir) Bind(ctx context.Context, dir *fs.Inode, name string, ep transport.BoundEndpoint, perms fs.FilePermissions) (*fs.Dirent, error) { + if d.CreateOps == nil || d.CreateOps.NewBoundEndpoint == nil { + return nil, syserror.EACCES + } + inode, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) { + return d.NewBoundEndpoint(ctx, dir, ep, perms) + }) + if err == syscall.EEXIST { + return nil, syscall.EADDRINUSE + } + if err != nil { + return nil, err + } + // Take another ref on inode which will be donated to the new dirent. + inode.IncRef() + return fs.NewDirent(inode, name), nil +} + +// 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 syserror.EACCES + } + _, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) { + return d.NewFifo(ctx, dir, perms) + }) + return err +} + +// GetFile implements fs.InodeOperations.GetFile. +func (d *Dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { + flags.Pread = true + return fs.NewFile(ctx, dirent, flags, &dirFileOperations{dir: d}), nil +} + +// Rename implements fs.InodeOperations.Rename. +func (*Dir) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { + return Rename(ctx, oldParent.InodeOperations, oldName, newParent.InodeOperations, newName, replacement) +} + +// dirFileOperations implements fs.FileOperations for a ramfs directory. +// +// +stateify savable +type dirFileOperations struct { + fsutil.DirFileOperations `state:"nosave"` + fsutil.FileUseInodeUnstableAttr `state:"nosave"` + + // dirCursor contains the name of the last directory entry that was + // serialized. + dirCursor string + + // dir is the ramfs dir that this file corresponds to. + dir *Dir +} + +var _ fs.FileOperations = (*dirFileOperations)(nil) + +// Seek implements fs.FileOperations.Seek. +func (dfo *dirFileOperations) Seek(ctx context.Context, file *fs.File, whence fs.SeekWhence, offset int64) (int64, error) { + return fsutil.SeekWithDirCursor(ctx, file, whence, offset, &dfo.dirCursor) +} + +// IterateDir implements DirIterator.IterateDir. +func (dfo *dirFileOperations) IterateDir(ctx context.Context, dirCtx *fs.DirCtx, offset int) (int, error) { + dfo.dir.mu.Lock() + defer dfo.dir.mu.Unlock() + + n, err := fs.GenericReaddir(dirCtx, dfo.dir.dentryMap) + return offset + n, err +} + +// Readdir implements FileOperations.Readdir. +func (dfo *dirFileOperations) Readdir(ctx context.Context, file *fs.File, serializer fs.DentrySerializer) (int64, error) { + root := fs.RootFromContext(ctx) + if root != nil { + defer root.DecRef() + } + dirCtx := &fs.DirCtx{ + Serializer: serializer, + DirCursor: &dfo.dirCursor, + } + dfo.dir.InodeSimpleAttributes.NotifyAccess(ctx) + return fs.DirentReaddir(ctx, file.Dirent, dfo, root, dirCtx, file.Offset()) +} + +// hasChildren is a helper method that determines whether an arbitrary inode +// (not necessarily ramfs) has any children. +func hasChildren(ctx context.Context, inode *fs.Inode) (bool, error) { + // Take an extra ref on inode which will be given to the dirent and + // dropped when that dirent is destroyed. + inode.IncRef() + d := fs.NewTransientDirent(inode) + defer d.DecRef() + + file, err := inode.GetFile(ctx, d, fs.FileFlags{Read: true}) + if err != nil { + return false, err + } + defer file.DecRef() + + ser := &fs.CollectEntriesSerializer{} + if err := file.Readdir(ctx, ser); err != nil { + return false, err + } + // We will always write "." and "..", so ignore those two. + if ser.Written() > 2 { + return true, nil + } + return false, nil +} + +// 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, replacement bool) error { + op, ok := oldParent.(*Dir) + if !ok { + return syserror.EXDEV + } + np, ok := newParent.(*Dir) + if !ok { + return syserror.EXDEV + } + if len(newName) > linux.NAME_MAX { + return syserror.ENAMETOOLONG + } + + np.mu.Lock() + defer np.mu.Unlock() + + // Is this is an overwriting rename? + if replacement { + replaced, ok := np.children[newName] + if !ok { + panic(fmt.Sprintf("Dirent claims rename is replacement, but %q is missing from %+v", newName, np)) + } + + // Non-empty directories cannot be replaced. + if fs.IsDir(replaced.StableAttr) { + if ok, err := hasChildren(ctx, replaced); err != nil { + return err + } else if ok { + return syserror.ENOTEMPTY + } + } + + // Remove the replaced child and drop our reference on it. + inode, err := np.removeChildLocked(ctx, newName) + if err != nil { + return err + } + inode.DecRef() + } + + // 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(ctx, newName, n) + + return nil +} diff --git a/pkg/sentry/fs/ramfs/ramfs_state_autogen.go b/pkg/sentry/fs/ramfs/ramfs_state_autogen.go new file mode 100755 index 000000000..dde1765e4 --- /dev/null +++ b/pkg/sentry/fs/ramfs/ramfs_state_autogen.go @@ -0,0 +1,94 @@ +// automatically generated by stateify. + +package ramfs + +import ( + "gvisor.googlesource.com/gvisor/pkg/state" +) + +func (x *Dir) beforeSave() {} +func (x *Dir) save(m state.Map) { + x.beforeSave() + m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes) + m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes) + m.Save("children", &x.children) + m.Save("dentryMap", &x.dentryMap) +} + +func (x *Dir) afterLoad() {} +func (x *Dir) load(m state.Map) { + m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes) + m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes) + m.Load("children", &x.children) + m.Load("dentryMap", &x.dentryMap) +} + +func (x *dirFileOperations) beforeSave() {} +func (x *dirFileOperations) save(m state.Map) { + x.beforeSave() + m.Save("dirCursor", &x.dirCursor) + m.Save("dir", &x.dir) +} + +func (x *dirFileOperations) afterLoad() {} +func (x *dirFileOperations) load(m state.Map) { + m.Load("dirCursor", &x.dirCursor) + m.Load("dir", &x.dir) +} + +func (x *Socket) beforeSave() {} +func (x *Socket) save(m state.Map) { + x.beforeSave() + m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes) + m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes) + m.Save("ep", &x.ep) +} + +func (x *Socket) afterLoad() {} +func (x *Socket) load(m state.Map) { + m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes) + m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes) + m.Load("ep", &x.ep) +} + +func (x *socketFileOperations) beforeSave() {} +func (x *socketFileOperations) save(m state.Map) { + x.beforeSave() +} + +func (x *socketFileOperations) afterLoad() {} +func (x *socketFileOperations) load(m state.Map) { +} + +func (x *Symlink) beforeSave() {} +func (x *Symlink) save(m state.Map) { + x.beforeSave() + m.Save("InodeSimpleAttributes", &x.InodeSimpleAttributes) + m.Save("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes) + m.Save("Target", &x.Target) +} + +func (x *Symlink) afterLoad() {} +func (x *Symlink) load(m state.Map) { + m.Load("InodeSimpleAttributes", &x.InodeSimpleAttributes) + m.Load("InodeSimpleExtendedAttributes", &x.InodeSimpleExtendedAttributes) + m.Load("Target", &x.Target) +} + +func (x *symlinkFileOperations) beforeSave() {} +func (x *symlinkFileOperations) save(m state.Map) { + x.beforeSave() +} + +func (x *symlinkFileOperations) afterLoad() {} +func (x *symlinkFileOperations) load(m state.Map) { +} + +func init() { + state.Register("ramfs.Dir", (*Dir)(nil), state.Fns{Save: (*Dir).save, Load: (*Dir).load}) + state.Register("ramfs.dirFileOperations", (*dirFileOperations)(nil), state.Fns{Save: (*dirFileOperations).save, Load: (*dirFileOperations).load}) + state.Register("ramfs.Socket", (*Socket)(nil), state.Fns{Save: (*Socket).save, Load: (*Socket).load}) + state.Register("ramfs.socketFileOperations", (*socketFileOperations)(nil), state.Fns{Save: (*socketFileOperations).save, Load: (*socketFileOperations).load}) + state.Register("ramfs.Symlink", (*Symlink)(nil), state.Fns{Save: (*Symlink).save, Load: (*Symlink).load}) + state.Register("ramfs.symlinkFileOperations", (*symlinkFileOperations)(nil), state.Fns{Save: (*symlinkFileOperations).save, Load: (*symlinkFileOperations).load}) +} diff --git a/pkg/sentry/fs/ramfs/socket.go b/pkg/sentry/fs/ramfs/socket.go new file mode 100644 index 000000000..7d8bca70e --- /dev/null +++ b/pkg/sentry/fs/ramfs/socket.go @@ -0,0 +1,85 @@ +// Copyright 2018 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 ramfs + +import ( + "gvisor.googlesource.com/gvisor/pkg/abi/linux" + "gvisor.googlesource.com/gvisor/pkg/sentry/context" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil" + "gvisor.googlesource.com/gvisor/pkg/sentry/socket/unix/transport" + "gvisor.googlesource.com/gvisor/pkg/waiter" +) + +// Socket represents a socket. +// +// +stateify savable +type Socket struct { + fsutil.InodeGenericChecker `state:"nosave"` + fsutil.InodeNoopRelease `state:"nosave"` + fsutil.InodeNoopWriteOut `state:"nosave"` + fsutil.InodeNotAllocatable `state:"nosave"` + fsutil.InodeNotDirectory `state:"nosave"` + fsutil.InodeNotMappable `state:"nosave"` + fsutil.InodeNotSymlink `state:"nosave"` + fsutil.InodeNotTruncatable `state:"nosave"` + fsutil.InodeVirtual `state:"nosave"` + + fsutil.InodeSimpleAttributes + fsutil.InodeSimpleExtendedAttributes + + // ep is the bound endpoint. + ep transport.BoundEndpoint +} + +var _ fs.InodeOperations = (*Socket)(nil) + +// NewSocket returns a new Socket. +func NewSocket(ctx context.Context, ep transport.BoundEndpoint, owner fs.FileOwner, perms fs.FilePermissions) *Socket { + return &Socket{ + InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, perms, linux.SOCKFS_MAGIC), + ep: ep, + } +} + +// BoundEndpoint returns the socket data. +func (s *Socket) BoundEndpoint(*fs.Inode, string) transport.BoundEndpoint { + // ramfs only supports stored sentry internal sockets. Only gofer sockets + // care about the path argument. + return s.ep +} + +// GetFile implements fs.FileOperations.GetFile. +func (s *Socket) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { + return fs.NewFile(ctx, dirent, flags, &socketFileOperations{}), nil +} + +// +stateify savable +type socketFileOperations struct { + fsutil.FileNoIoctl `state:"nosave"` + fsutil.FileNoMMap `state:"nosave"` + fsutil.FileNoRead `state:"nosave"` + fsutil.FileNoSeek `state:"nosave"` + fsutil.FileNoSplice `state:"nosave"` + fsutil.FileNoWrite `state:"nosave"` + fsutil.FileNoopFlush `state:"nosave"` + fsutil.FileNoopFsync `state:"nosave"` + fsutil.FileNoopRelease `state:"nosave"` + fsutil.FileNotDirReaddir `state:"nosave"` + fsutil.FileUseInodeUnstableAttr `state:"nosave"` + waiter.AlwaysReady `state:"nosave"` +} + +var _ fs.FileOperations = (*socketFileOperations)(nil) diff --git a/pkg/sentry/fs/ramfs/symlink.go b/pkg/sentry/fs/ramfs/symlink.go new file mode 100644 index 000000000..21c246169 --- /dev/null +++ b/pkg/sentry/fs/ramfs/symlink.go @@ -0,0 +1,106 @@ +// Copyright 2018 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 ramfs + +import ( + "gvisor.googlesource.com/gvisor/pkg/abi/linux" + "gvisor.googlesource.com/gvisor/pkg/sentry/context" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs" + "gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil" + "gvisor.googlesource.com/gvisor/pkg/waiter" +) + +// Symlink represents a symlink. +// +// +stateify savable +type Symlink struct { + fsutil.InodeGenericChecker `state:"nosave"` + fsutil.InodeNoopRelease `state:"nosave"` + fsutil.InodeNoopWriteOut `state:"nosave"` + fsutil.InodeNotAllocatable `state:"nosave"` + fsutil.InodeNotDirectory `state:"nosave"` + fsutil.InodeNotMappable `state:"nosave"` + fsutil.InodeNotSocket `state:"nosave"` + fsutil.InodeNotTruncatable `state:"nosave"` + fsutil.InodeVirtual `state:"nosave"` + + fsutil.InodeSimpleAttributes + fsutil.InodeSimpleExtendedAttributes + + // Target is the symlink target. + Target string +} + +var _ fs.InodeOperations = (*Symlink)(nil) + +// NewSymlink returns a new Symlink. +func NewSymlink(ctx context.Context, owner fs.FileOwner, target string) *Symlink { + // A symlink is assumed to always have permissions 0777. + return &Symlink{ + InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(0777), linux.RAMFS_MAGIC), + Target: target, + } +} + +// UnstableAttr returns all attributes of this ramfs symlink. +func (s *Symlink) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { + uattr, err := s.InodeSimpleAttributes.UnstableAttr(ctx, inode) + if err != nil { + return fs.UnstableAttr{}, err + } + uattr.Size = int64(len(s.Target)) + uattr.Usage = uattr.Size + return uattr, nil +} + +// 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.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 +} + +// GetFile implements fs.FileOperations.GetFile. +func (s *Symlink) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { + return fs.NewFile(ctx, dirent, flags, &symlinkFileOperations{}), nil +} + +// +stateify savable +type symlinkFileOperations struct { + fsutil.FileNoIoctl `state:"nosave"` + fsutil.FileNoMMap `state:"nosave"` + fsutil.FileNoRead `state:"nosave"` + fsutil.FileNoSeek `state:"nosave"` + fsutil.FileNoSplice `state:"nosave"` + fsutil.FileNoWrite `state:"nosave"` + fsutil.FileNoopFlush `state:"nosave"` + fsutil.FileNoopFsync `state:"nosave"` + fsutil.FileNoopRelease `state:"nosave"` + fsutil.FileNotDirReaddir `state:"nosave"` + fsutil.FileUseInodeUnstableAttr `state:"nosave"` + waiter.AlwaysReady `state:"nosave"` +} + +var _ fs.FileOperations = (*symlinkFileOperations)(nil) diff --git a/pkg/sentry/fs/ramfs/tree.go b/pkg/sentry/fs/ramfs/tree.go new file mode 100644 index 000000000..8c6b31f70 --- /dev/null +++ b/pkg/sentry/fs/ramfs/tree.go @@ -0,0 +1,77 @@ +// Copyright 2018 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 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 +// "/". +// +// All directories in the created tree will have full (read-write-execute) +// permissions, but note that file creation inside the directories is not +// actually supported because ramfs.Dir.CreateOpts == nil. However, these +// directory trees are normally "underlayed" under another filesystem (possibly +// the root), and file creation inside these directories in the overlay will be +// possible if the upper is writeable. +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 with all permissions granted. +func emptyDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { + dir := NewDir(ctx, make(map[string]*fs.Inode), fs.RootOwner, fs.FilePermsFromMode(0777)) + return fs.NewInode(dir, msrc, fs.StableAttr{ + DeviceID: anon.PseudoDevice.DeviceID(), + InodeID: anon.PseudoDevice.NextIno(), + BlockSize: usermem.PageSize, + Type: fs.Directory, + }) +} |