summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/fs')
-rw-r--r--pkg/sentry/fs/fsutil/BUILD2
-rw-r--r--pkg/sentry/fs/fsutil/host_mappable.go136
-rw-r--r--pkg/sentry/fs/fsutil/host_mappable_state.go22
-rw-r--r--pkg/sentry/fs/gofer/cache_policy.go22
-rw-r--r--pkg/sentry/fs/gofer/file.go9
-rw-r--r--pkg/sentry/fs/gofer/inode.go30
-rw-r--r--pkg/sentry/fs/gofer/path.go2
-rw-r--r--pkg/sentry/fs/gofer/session.go14
8 files changed, 218 insertions, 19 deletions
diff --git a/pkg/sentry/fs/fsutil/BUILD b/pkg/sentry/fs/fsutil/BUILD
index 4965e1a5f..d4767642b 100644
--- a/pkg/sentry/fs/fsutil/BUILD
+++ b/pkg/sentry/fs/fsutil/BUILD
@@ -70,6 +70,8 @@ go_library(
"host_file_mapper.go",
"host_file_mapper_state.go",
"host_file_mapper_unsafe.go",
+ "host_mappable.go",
+ "host_mappable_state.go",
"inode.go",
"inode_cached.go",
],
diff --git a/pkg/sentry/fs/fsutil/host_mappable.go b/pkg/sentry/fs/fsutil/host_mappable.go
new file mode 100644
index 000000000..4e4bcf4a4
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/host_mappable.go
@@ -0,0 +1,136 @@
+// Copyright 2019 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 fsutil
+
+import (
+ "sync"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/memmap"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/platform"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/safemem"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
+)
+
+// HostMappable implements memmap.Mappable and platform.File over an arbitrary
+// host file descriptor.
+//
+// +stateify savable
+type HostMappable struct {
+ hostFileMapper *HostFileMapper
+
+ mu sync.Mutex `state:"nosave"`
+
+ // fd is the file descriptor to the host. Protected by mu.
+ fd int `state:"nosave"`
+
+ // mappings tracks mappings of the cached file object into
+ // memmap.MappingSpaces so it can invalidated upon save. Protected by mu.
+ mappings memmap.MappingSet
+}
+
+// NewHostMappable creates a new mappable that maps directly to host FD.
+func NewHostMappable() *HostMappable {
+ return &HostMappable{
+ hostFileMapper: NewHostFileMapper(),
+ fd: -1,
+ }
+}
+
+func (h *HostMappable) getFD() int {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ if h.fd < 0 {
+ panic("HostMappable FD isn't set")
+ }
+ return h.fd
+}
+
+// UpdateFD sets the host FD iff FD hasn't been set before or if there are
+// no mappings.
+func (h *HostMappable) UpdateFD(fd int) {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ h.fd = fd
+}
+
+// AddMapping implements memmap.Mappable.AddMapping.
+func (h *HostMappable) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) error {
+ // Hot path. Avoid defers.
+ h.mu.Lock()
+ mapped := h.mappings.AddMapping(ms, ar, offset, writable)
+ for _, r := range mapped {
+ h.hostFileMapper.IncRefOn(r)
+ }
+ h.mu.Unlock()
+ return nil
+}
+
+// RemoveMapping implements memmap.Mappable.RemoveMapping.
+func (h *HostMappable) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) {
+ // Hot path. Avoid defers.
+ h.mu.Lock()
+ unmapped := h.mappings.RemoveMapping(ms, ar, offset, writable)
+ for _, r := range unmapped {
+ h.hostFileMapper.DecRefOn(r)
+ }
+ h.mu.Unlock()
+}
+
+// CopyMapping implements memmap.Mappable.CopyMapping.
+func (h *HostMappable) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR usermem.AddrRange, offset uint64, writable bool) error {
+ return h.AddMapping(ctx, ms, dstAR, offset, writable)
+}
+
+// Translate implements memmap.Mappable.Translate.
+func (h *HostMappable) Translate(ctx context.Context, required, optional memmap.MappableRange, at usermem.AccessType) ([]memmap.Translation, error) {
+ return []memmap.Translation{
+ {
+ Source: optional,
+ File: h,
+ Offset: optional.Start,
+ },
+ }, nil
+}
+
+// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
+func (h *HostMappable) InvalidateUnsavable(ctx context.Context) error {
+ h.mu.Lock()
+ h.mappings.InvalidateAll(memmap.InvalidateOpts{})
+ h.mu.Unlock()
+ return nil
+}
+
+// MapInto implements platform.File.MapInto.
+func (h *HostMappable) MapInto(as platform.AddressSpace, addr usermem.Addr, fr platform.FileRange, at usermem.AccessType, precommit bool) error {
+ return as.MapFile(addr, h.getFD(), fr, at, precommit)
+}
+
+// MapInternal implements platform.File.MapInternal.
+func (h *HostMappable) MapInternal(fr platform.FileRange, at usermem.AccessType) (safemem.BlockSeq, error) {
+ return h.hostFileMapper.MapInternal(fr, h.getFD(), at.Write)
+}
+
+// IncRef implements platform.File.IncRef.
+func (h *HostMappable) IncRef(fr platform.FileRange) {
+ mr := memmap.MappableRange{Start: fr.Start, End: fr.End}
+ h.hostFileMapper.IncRefOn(mr)
+}
+
+// DecRef implements platform.File.DecRef.
+func (h *HostMappable) DecRef(fr platform.FileRange) {
+ mr := memmap.MappableRange{Start: fr.Start, End: fr.End}
+ h.hostFileMapper.DecRefOn(mr)
+}
diff --git a/pkg/sentry/fs/fsutil/host_mappable_state.go b/pkg/sentry/fs/fsutil/host_mappable_state.go
new file mode 100644
index 000000000..765f1ec87
--- /dev/null
+++ b/pkg/sentry/fs/fsutil/host_mappable_state.go
@@ -0,0 +1,22 @@
+// Copyright 2019 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 fsutil
+
+// afterLoad is invoked by stateify.
+func (h *HostMappable) afterLoad() {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ h.fd = -1
+}
diff --git a/pkg/sentry/fs/gofer/cache_policy.go b/pkg/sentry/fs/gofer/cache_policy.go
index 3d380f0e8..507d6900f 100644
--- a/pkg/sentry/fs/gofer/cache_policy.go
+++ b/pkg/sentry/fs/gofer/cache_policy.go
@@ -90,17 +90,29 @@ func (cp cachePolicy) cacheReaddir() bool {
return cp == cacheAll || cp == cacheAllWritethrough
}
-// usePageCache determines whether the page cache should be used for the given
-// inode. If the remote filesystem donates host FDs to the sentry, then the
-// host kernel's page cache will be used, otherwise we will use a
+// useCachingInodeOps determines whether the page cache should be used for the
+// given inode. If the remote filesystem donates host FDs to the sentry, then
+// the host kernel's page cache will be used, otherwise we will use a
// sentry-internal page cache.
-func (cp cachePolicy) usePageCache(inode *fs.Inode) bool {
+func (cp cachePolicy) useCachingInodeOps(inode *fs.Inode) bool {
// Do cached IO for regular files only. Some "character devices" expect
// no caching.
if !fs.IsFile(inode.StableAttr) {
return false
}
- return cp == cacheAll || cp == cacheAllWritethrough || cp == cacheRemoteRevalidating
+ return cp == cacheAll || cp == cacheAllWritethrough
+}
+
+// cacheHandles determine whether handles need to be cached with the given
+// inode. Handles must be cached when inode can be mapped into memory to
+// implement InodeOperations.Mappable with stable handles.
+func (cp cachePolicy) cacheHandles(inode *fs.Inode) bool {
+ // Do cached IO for regular files only. Some "character devices" expect
+ // no caching.
+ if !fs.IsFile(inode.StableAttr) {
+ return false
+ }
+ return cp.useCachingInodeOps(inode) || cp == cacheRemoteRevalidating
}
// writeThough indicates whether writes to the file should be synced to the
diff --git a/pkg/sentry/fs/gofer/file.go b/pkg/sentry/fs/gofer/file.go
index 3578b07a0..2181ddc68 100644
--- a/pkg/sentry/fs/gofer/file.go
+++ b/pkg/sentry/fs/gofer/file.go
@@ -204,7 +204,7 @@ func (f *fileOperations) Write(ctx context.Context, file *fs.File, src usermem.I
return 0, syserror.EISDIR
}
cp := f.inodeOperations.session().cachePolicy
- if cp.usePageCache(file.Dirent.Inode) {
+ if cp.useCachingInodeOps(file.Dirent.Inode) {
n, err := f.inodeOperations.cachingInodeOps.Write(ctx, src, offset)
if err != nil {
return n, err
@@ -225,7 +225,7 @@ func (f *fileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IO
return 0, syserror.EISDIR
}
- if f.inodeOperations.session().cachePolicy.usePageCache(file.Dirent.Inode) {
+ if f.inodeOperations.session().cachePolicy.useCachingInodeOps(file.Dirent.Inode) {
return f.inodeOperations.cachingInodeOps.Read(ctx, file, dst, offset)
}
return dst.CopyOutFrom(ctx, f.handles.readWriterAt(ctx, offset))
@@ -267,10 +267,7 @@ func (f *fileOperations) Flush(ctx context.Context, file *fs.File) error {
// ConfigureMMap implements fs.FileOperations.ConfigureMMap.
func (f *fileOperations) ConfigureMMap(ctx context.Context, file *fs.File, opts *memmap.MMapOpts) error {
- if !f.inodeOperations.session().cachePolicy.usePageCache(file.Dirent.Inode) {
- return syserror.ENODEV
- }
- return fsutil.GenericConfigureMMap(file, f.inodeOperations.cachingInodeOps, opts)
+ return f.inodeOperations.configureMMap(file, opts)
}
// Seek implements fs.FileOperations.Seek.
diff --git a/pkg/sentry/fs/gofer/inode.go b/pkg/sentry/fs/gofer/inode.go
index f0dc99fd0..043705c58 100644
--- a/pkg/sentry/fs/gofer/inode.go
+++ b/pkg/sentry/fs/gofer/inode.go
@@ -125,6 +125,10 @@ type inodeFileState struct {
// failures. S/R is transparent to Sentry and the latter will continue
// using its cached values after restore.
savedUAttr *fs.UnstableAttr
+
+ // hostMappable is created when using 'cacheRemoteRevalidating' to map pages
+ // directly from host.
+ hostMappable *fsutil.HostMappable
}
// Release releases file handles.
@@ -166,6 +170,9 @@ func (i *inodeFileState) setHandlesForCachedIO(flags fs.FileFlags, h *handles) {
i.writebackRW = true
}
}
+ if i.hostMappable != nil {
+ i.hostMappable.UpdateFD(i.fdLocked())
+ }
}
// getCachedHandles returns any cached handles which would accelerate
@@ -287,7 +294,10 @@ func (i *inodeFileState) Sync(ctx context.Context) error {
func (i *inodeFileState) FD() int {
i.handlesMu.RLock()
defer i.handlesMu.RUnlock()
+ return i.fdLocked()
+}
+func (i *inodeFileState) fdLocked() int {
// Assert that the file was actually opened.
if i.writeback == nil && i.readthrough == nil {
panic("cannot get host FD for a file that was never opened")
@@ -344,9 +354,13 @@ func (i *inodeOperations) Release(ctx context.Context) {
// Mappable implements fs.InodeOperations.Mappable.
func (i *inodeOperations) Mappable(inode *fs.Inode) memmap.Mappable {
- if i.session().cachePolicy.usePageCache(inode) {
+ if i.session().cachePolicy.useCachingInodeOps(inode) {
return i.cachingInodeOps
}
+ // This check is necessary because it's returning an interface type.
+ if i.fileState.hostMappable != nil {
+ return i.fileState.hostMappable
+ }
return nil
}
@@ -434,7 +448,7 @@ func (i *inodeOperations) NonBlockingOpen(ctx context.Context, p fs.PermMask) (*
}
func (i *inodeOperations) getFileDefault(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
- if !i.session().cachePolicy.usePageCache(d.Inode) {
+ if !i.session().cachePolicy.cacheHandles(d.Inode) {
h, err := newHandles(ctx, i.fileState.file, flags)
if err != nil {
return nil, err
@@ -503,7 +517,7 @@ func (i *inodeOperations) SetTimestamps(ctx context.Context, inode *fs.Inode, ts
// Truncate implements fs.InodeOperations.Truncate.
func (i *inodeOperations) Truncate(ctx context.Context, inode *fs.Inode, length int64) error {
// This can only be called for files anyway.
- if i.session().cachePolicy.usePageCache(inode) {
+ if i.session().cachePolicy.useCachingInodeOps(inode) {
return i.cachingInodeOps.Truncate(ctx, inode, length)
}
@@ -561,6 +575,16 @@ func (i *inodeOperations) StatFS(ctx context.Context) (fs.Info, error) {
return info, nil
}
+func (i *inodeOperations) configureMMap(file *fs.File, opts *memmap.MMapOpts) error {
+ if i.session().cachePolicy.useCachingInodeOps(file.Dirent.Inode) {
+ return fsutil.GenericConfigureMMap(file, i.cachingInodeOps, opts)
+ }
+ if i.fileState.hostMappable != nil {
+ return fsutil.GenericConfigureMMap(file, i.fileState.hostMappable, opts)
+ }
+ return syserror.ENODEV
+}
+
func init() {
syserror.AddErrorUnwrapper(func(err error) (syscall.Errno, bool) {
if _, ok := err.(p9.ErrSocket); ok {
diff --git a/pkg/sentry/fs/gofer/path.go b/pkg/sentry/fs/gofer/path.go
index a324dc990..faedfb81c 100644
--- a/pkg/sentry/fs/gofer/path.go
+++ b/pkg/sentry/fs/gofer/path.go
@@ -128,7 +128,7 @@ func (i *inodeOperations) Create(ctx context.Context, dir *fs.Inode, name string
File: newFile,
Host: hostFile,
}
- if iops.session().cachePolicy.usePageCache(d.Inode) {
+ if iops.session().cachePolicy.cacheHandles(d.Inode) {
iops.fileState.setHandlesForCachedIO(flags, h)
}
return NewFile(ctx, d, name, flags, iops, h), nil
diff --git a/pkg/sentry/fs/gofer/session.go b/pkg/sentry/fs/gofer/session.go
index f76a83cd9..b5b1c8202 100644
--- a/pkg/sentry/fs/gofer/session.go
+++ b/pkg/sentry/fs/gofer/session.go
@@ -197,11 +197,17 @@ func newInodeOperations(ctx context.Context, s *session, file contextFile, qid p
}
}
+ var hm *fsutil.HostMappable
+ if s.cachePolicy == cacheRemoteRevalidating && fs.IsFile(sattr) {
+ hm = fsutil.NewHostMappable()
+ }
+
fileState := &inodeFileState{
- s: s,
- file: file,
- sattr: sattr,
- key: deviceKey,
+ s: s,
+ file: file,
+ sattr: sattr,
+ key: deviceKey,
+ hostMappable: hm,
}
uattr := unstable(ctx, valid, attr, s.mounter, s.client)