Convert memfs into proto-tmpfs.

- Renamed memfs to tmpfs.
- Copied fileRangeSet bits from fs/fsutil/ to fsimpl/tmpfs/
- Changed tmpfs to be backed by filemem instead of byte slice.
- regularFileReadWriter uses a sync.Pool, similar to gofer client.

PiperOrigin-RevId: 288356380
This commit is contained in:
Nicolas Lacasse
2020-01-06 12:52:55 -08:00
committed by gVisor bot
parent 354a15a234
commit 51f3ab85e0
13 changed files with 653 additions and 208 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ go_template_instance(
"Key": "uint64",
"Range": "memmap.MappableRange",
"Value": "uint64",
"Functions": "fileRangeSetFunctions",
"Functions": "FileRangeSetFunctions",
},
)
+7 -7
View File
@@ -34,25 +34,25 @@ import (
//
// type FileRangeSet <generated by go_generics>
// fileRangeSetFunctions implements segment.Functions for FileRangeSet.
type fileRangeSetFunctions struct{}
// FileRangeSetFunctions implements segment.Functions for FileRangeSet.
type FileRangeSetFunctions struct{}
// MinKey implements segment.Functions.MinKey.
func (fileRangeSetFunctions) MinKey() uint64 {
func (FileRangeSetFunctions) MinKey() uint64 {
return 0
}
// MaxKey implements segment.Functions.MaxKey.
func (fileRangeSetFunctions) MaxKey() uint64 {
func (FileRangeSetFunctions) MaxKey() uint64 {
return math.MaxUint64
}
// ClearValue implements segment.Functions.ClearValue.
func (fileRangeSetFunctions) ClearValue(_ *uint64) {
func (FileRangeSetFunctions) ClearValue(_ *uint64) {
}
// Merge implements segment.Functions.Merge.
func (fileRangeSetFunctions) Merge(mr1 memmap.MappableRange, frstart1 uint64, _ memmap.MappableRange, frstart2 uint64) (uint64, bool) {
func (FileRangeSetFunctions) Merge(mr1 memmap.MappableRange, frstart1 uint64, _ memmap.MappableRange, frstart2 uint64) (uint64, bool) {
if frstart1+mr1.Length() != frstart2 {
return 0, false
}
@@ -60,7 +60,7 @@ func (fileRangeSetFunctions) Merge(mr1 memmap.MappableRange, frstart1 uint64, _
}
// Split implements segment.Functions.Split.
func (fileRangeSetFunctions) Split(mr memmap.MappableRange, frstart uint64, split uint64) (uint64, uint64) {
func (FileRangeSetFunctions) Split(mr memmap.MappableRange, frstart uint64, split uint64) (uint64, uint64) {
return frstart, frstart + (split - mr.Start)
}
-154
View File
@@ -1,154 +0,0 @@
// Copyright 2019 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 memfs
import (
"io"
"sync"
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
type regularFile struct {
inode inode
mu sync.RWMutex
data []byte
// dataLen is len(data), but accessed using atomic memory operations to
// avoid locking in inode.stat().
dataLen int64
}
func (fs *filesystem) newRegularFile(creds *auth.Credentials, mode linux.FileMode) *inode {
file := &regularFile{}
file.inode.init(file, fs, creds, mode)
file.inode.nlink = 1 // from parent directory
return &file.inode
}
type regularFileFD struct {
fileDescription
// These are immutable.
readable bool
writable bool
// off is the file offset. off is accessed using atomic memory operations.
// offMu serializes operations that may mutate off.
off int64
offMu sync.Mutex
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *regularFileFD) Release() {
if fd.writable {
fd.vfsfd.VirtualDentry().Mount().EndWrite()
}
}
// PRead implements vfs.FileDescriptionImpl.PRead.
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
if !fd.readable {
return 0, syserror.EINVAL
}
f := fd.inode().impl.(*regularFile)
f.mu.RLock()
if offset >= int64(len(f.data)) {
f.mu.RUnlock()
return 0, io.EOF
}
n, err := dst.CopyOut(ctx, f.data[offset:])
f.mu.RUnlock()
return int64(n), err
}
// Read implements vfs.FileDescriptionImpl.Read.
func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
fd.offMu.Lock()
n, err := fd.PRead(ctx, dst, fd.off, opts)
fd.off += n
fd.offMu.Unlock()
return n, err
}
// PWrite implements vfs.FileDescriptionImpl.PWrite.
func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
if !fd.writable {
return 0, syserror.EINVAL
}
if offset < 0 {
return 0, syserror.EINVAL
}
srclen := src.NumBytes()
if srclen == 0 {
return 0, nil
}
f := fd.inode().impl.(*regularFile)
f.mu.Lock()
end := offset + srclen
if end < offset {
// Overflow.
f.mu.Unlock()
return 0, syserror.EFBIG
}
if end > f.dataLen {
f.data = append(f.data, make([]byte, end-f.dataLen)...)
atomic.StoreInt64(&f.dataLen, end)
}
n, err := src.CopyIn(ctx, f.data[offset:end])
f.mu.Unlock()
return int64(n), err
}
// Write implements vfs.FileDescriptionImpl.Write.
func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
fd.offMu.Lock()
n, err := fd.PWrite(ctx, src, fd.off, opts)
fd.off += n
fd.offMu.Unlock()
return n, err
}
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
fd.offMu.Lock()
defer fd.offMu.Unlock()
switch whence {
case linux.SEEK_SET:
// use offset as specified
case linux.SEEK_CUR:
offset += fd.off
case linux.SEEK_END:
offset += atomic.LoadInt64(&fd.inode().impl.(*regularFile).dataLen)
default:
return 0, syserror.EINVAL
}
if offset < 0 {
return 0, syserror.EINVAL
}
fd.off = offset
return offset, nil
}
// Sync implements vfs.FileDescriptionImpl.Sync.
func (fd *regularFileFD) Sync(ctx context.Context) error {
return nil
}
@@ -1,14 +1,13 @@
load("//tools/go_stateify:defs.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_test")
load("//tools/go_generics:defs.bzl", "go_template_instance")
package(licenses = ["notice"])
load("//tools/go_generics:defs.bzl", "go_template_instance")
go_template_instance(
name = "dentry_list",
out = "dentry_list.go",
package = "memfs",
package = "tmpfs",
prefix = "dentry",
template = "//pkg/ilist:generic_list",
types = {
@@ -18,25 +17,34 @@ go_template_instance(
)
go_library(
name = "memfs",
name = "tmpfs",
srcs = [
"dentry_list.go",
"directory.go",
"filesystem.go",
"memfs.go",
"named_pipe.go",
"regular_file.go",
"symlink.go",
"tmpfs.go",
],
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/memfs",
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs",
deps = [
"//pkg/abi/linux",
"//pkg/amutex",
"//pkg/fspath",
"//pkg/log",
"//pkg/sentry/arch",
"//pkg/sentry/context",
"//pkg/sentry/fs",
"//pkg/sentry/fs/fsutil",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/pipe",
"//pkg/sentry/memmap",
"//pkg/sentry/pgalloc",
"//pkg/sentry/platform",
"//pkg/sentry/safemem",
"//pkg/sentry/usage",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
"//pkg/syserror",
@@ -48,7 +56,7 @@ go_test(
size = "small",
srcs = ["benchmark_test.go"],
deps = [
":memfs",
":tmpfs",
"//pkg/abi/linux",
"//pkg/fspath",
"//pkg/refs",
@@ -63,16 +71,20 @@ go_test(
)
go_test(
name = "memfs_test",
name = "tmpfs_test",
size = "small",
srcs = ["pipe_test.go"],
embed = [":memfs"],
srcs = [
"pipe_test.go",
"regular_file_test.go",
],
embed = [":tmpfs"],
deps = [
"//pkg/abi/linux",
"//pkg/fspath",
"//pkg/sentry/context",
"//pkg/sentry/context/contexttest",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/contexttest",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
"//pkg/syserror",
@@ -27,7 +27,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
"gvisor.dev/gvisor/pkg/sentry/fs"
_ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/memfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
@@ -176,10 +176,10 @@ func BenchmarkVFS2MemfsStat(b *testing.B) {
// Create VFS.
vfsObj := vfs.New()
vfsObj.MustRegisterFilesystemType("memfs", memfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "memfs", &vfs.GetFilesystemOptions{})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
if err != nil {
b.Fatalf("failed to create tmpfs root mount: %v", err)
}
@@ -367,10 +367,10 @@ func BenchmarkVFS2MemfsMountStat(b *testing.B) {
// Create VFS.
vfsObj := vfs.New()
vfsObj.MustRegisterFilesystemType("memfs", memfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "memfs", &vfs.GetFilesystemOptions{})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
if err != nil {
b.Fatalf("failed to create tmpfs root mount: %v", err)
}
@@ -399,7 +399,7 @@ func BenchmarkVFS2MemfsMountStat(b *testing.B) {
}
defer mountPoint.DecRef()
// Create and mount the submount.
if err := vfsObj.MountAt(ctx, creds, "", &pop, "memfs", &vfs.MountOptions{}); err != nil {
if err := vfsObj.MountAt(ctx, creds, "", &pop, "tmpfs", &vfs.MountOptions{}); err != nil {
b.Fatalf("failed to mount tmpfs submount: %v", err)
}
filePathBuilder.WriteString(mountPointName)
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package memfs
package tmpfs
import (
"gvisor.dev/gvisor/pkg/abi/linux"
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package memfs
package tmpfs
import (
"fmt"
@@ -50,7 +50,7 @@ afterSymlink:
return nil, err
}
if nextVFSD == nil {
// Since the Dentry tree is the sole source of truth for memfs, if it's
// Since the Dentry tree is the sole source of truth for tmpfs, if it's
// not in the Dentry tree, it doesn't exist.
return nil, syserror.ENOENT
}
@@ -351,8 +351,8 @@ func (d *dentry) open(ctx context.Context, rp *vfs.ResolvingPath, flags uint32,
fd.vfsfd.Init(&fd, flags, mnt, &d.vfsd, &vfs.FileDescriptionOptions{})
if flags&linux.O_TRUNC != 0 {
impl.mu.Lock()
impl.data = impl.data[:0]
atomic.StoreInt64(&impl.dataLen, 0)
impl.data.Truncate(0, impl.memFile)
atomic.StoreUint64(&impl.size, 0)
impl.mu.Unlock()
}
return &fd.vfsfd, nil
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package memfs
package tmpfs
import (
"gvisor.dev/gvisor/pkg/abi/linux"
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package memfs
package tmpfs
import (
"bytes"
@@ -152,10 +152,10 @@ func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesy
// Create VFS.
vfsObj := vfs.New()
vfsObj.MustRegisterFilesystemType("memfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "memfs", &vfs.GetFilesystemOptions{})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
if err != nil {
t.Fatalf("failed to create tmpfs root mount: %v", err)
}
+357
View File
@@ -0,0 +1,357 @@
// Copyright 2019 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 tmpfs
import (
"io"
"math"
"sync"
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/safemem"
"gvisor.dev/gvisor/pkg/sentry/usage"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
type regularFile struct {
inode inode
// memFile is a platform.File used to allocate pages to this regularFile.
memFile *pgalloc.MemoryFile
// mu protects the fields below.
mu sync.RWMutex
// data maps offsets into the file to offsets into memFile that store
// the file's data.
data fsutil.FileRangeSet
// size is the size of data, but accessed using atomic memory
// operations to avoid locking in inode.stat().
size uint64
// seals represents file seals on this inode.
seals uint32
}
func (fs *filesystem) newRegularFile(creds *auth.Credentials, mode linux.FileMode) *inode {
file := &regularFile{
memFile: fs.memFile,
}
file.inode.init(file, fs, creds, mode)
file.inode.nlink = 1 // from parent directory
return &file.inode
}
type regularFileFD struct {
fileDescription
// These are immutable.
readable bool
writable bool
// off is the file offset. off is accessed using atomic memory operations.
// offMu serializes operations that may mutate off.
off int64
offMu sync.Mutex
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *regularFileFD) Release() {
if fd.writable {
fd.vfsfd.VirtualDentry().Mount().EndWrite()
}
}
// PRead implements vfs.FileDescriptionImpl.PRead.
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
if !fd.readable {
return 0, syserror.EINVAL
}
if offset < 0 {
return 0, syserror.EINVAL
}
if dst.NumBytes() == 0 {
return 0, nil
}
f := fd.inode().impl.(*regularFile)
rw := getRegularFileReadWriter(f, offset)
n, err := dst.CopyOutFrom(ctx, rw)
putRegularFileReadWriter(rw)
return int64(n), err
}
// Read implements vfs.FileDescriptionImpl.Read.
func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
fd.offMu.Lock()
n, err := fd.PRead(ctx, dst, fd.off, opts)
fd.off += n
fd.offMu.Unlock()
return n, err
}
// PWrite implements vfs.FileDescriptionImpl.PWrite.
func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
if !fd.writable {
return 0, syserror.EINVAL
}
if offset < 0 {
return 0, syserror.EINVAL
}
srclen := src.NumBytes()
if srclen == 0 {
return 0, nil
}
f := fd.inode().impl.(*regularFile)
end := offset + srclen
if end < offset {
// Overflow.
return 0, syserror.EFBIG
}
rw := getRegularFileReadWriter(f, offset)
n, err := src.CopyInTo(ctx, rw)
putRegularFileReadWriter(rw)
return n, err
}
// Write implements vfs.FileDescriptionImpl.Write.
func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
fd.offMu.Lock()
n, err := fd.PWrite(ctx, src, fd.off, opts)
fd.off += n
fd.offMu.Unlock()
return n, err
}
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
fd.offMu.Lock()
defer fd.offMu.Unlock()
switch whence {
case linux.SEEK_SET:
// use offset as specified
case linux.SEEK_CUR:
offset += fd.off
case linux.SEEK_END:
offset += int64(atomic.LoadUint64(&fd.inode().impl.(*regularFile).size))
default:
return 0, syserror.EINVAL
}
if offset < 0 {
return 0, syserror.EINVAL
}
fd.off = offset
return offset, nil
}
// Sync implements vfs.FileDescriptionImpl.Sync.
func (fd *regularFileFD) Sync(ctx context.Context) error {
return nil
}
// regularFileReadWriter implements safemem.Reader and Safemem.Writer.
type regularFileReadWriter struct {
file *regularFile
// Offset into the file to read/write at. Note that this may be
// different from the FD offset if PRead/PWrite is used.
off uint64
}
var regularFileReadWriterPool = sync.Pool{
New: func() interface{} {
return &regularFileReadWriter{}
},
}
func getRegularFileReadWriter(file *regularFile, offset int64) *regularFileReadWriter {
rw := regularFileReadWriterPool.Get().(*regularFileReadWriter)
rw.file = file
rw.off = uint64(offset)
return rw
}
func putRegularFileReadWriter(rw *regularFileReadWriter) {
rw.file = nil
regularFileReadWriterPool.Put(rw)
}
// ReadToBlocks implements safemem.Reader.ReadToBlocks.
func (rw *regularFileReadWriter) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
rw.file.mu.RLock()
// Compute the range to read (limited by file size and overflow-checked).
if rw.off >= rw.file.size {
rw.file.mu.RUnlock()
return 0, io.EOF
}
end := rw.file.size
if rend := rw.off + dsts.NumBytes(); rend > rw.off && rend < end {
end = rend
}
var done uint64
seg, gap := rw.file.data.Find(uint64(rw.off))
for rw.off < end {
mr := memmap.MappableRange{uint64(rw.off), uint64(end)}
switch {
case seg.Ok():
// Get internal mappings.
ims, err := rw.file.memFile.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), usermem.Read)
if err != nil {
rw.file.mu.RUnlock()
return done, err
}
// Copy from internal mappings.
n, err := safemem.CopySeq(dsts, ims)
done += n
rw.off += uint64(n)
dsts = dsts.DropFirst64(n)
if err != nil {
rw.file.mu.RUnlock()
return done, err
}
// Continue.
seg, gap = seg.NextNonEmpty()
case gap.Ok():
// Tmpfs holes are zero-filled.
gapmr := gap.Range().Intersect(mr)
dst := dsts.TakeFirst64(gapmr.Length())
n, err := safemem.ZeroSeq(dst)
done += n
rw.off += uint64(n)
dsts = dsts.DropFirst64(n)
if err != nil {
rw.file.mu.RUnlock()
return done, err
}
// Continue.
seg, gap = gap.NextSegment(), fsutil.FileRangeGapIterator{}
}
}
rw.file.mu.RUnlock()
return done, nil
}
// WriteFromBlocks implements safemem.Writer.WriteFromBlocks.
func (rw *regularFileReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) {
rw.file.mu.Lock()
// Compute the range to write (overflow-checked).
end := rw.off + srcs.NumBytes()
if end <= rw.off {
end = math.MaxInt64
}
// Check if seals prevent either file growth or all writes.
switch {
case rw.file.seals&linux.F_SEAL_WRITE != 0: // Write sealed
rw.file.mu.Unlock()
return 0, syserror.EPERM
case end > rw.file.size && rw.file.seals&linux.F_SEAL_GROW != 0: // Grow sealed
// When growth is sealed, Linux effectively allows writes which would
// normally grow the file to partially succeed up to the current EOF,
// rounded down to the page boundary before the EOF.
//
// This happens because writes (and thus the growth check) for tmpfs
// files proceed page-by-page on Linux, and the final write to the page
// containing EOF fails, resulting in a partial write up to the start of
// that page.
//
// To emulate this behaviour, artifically truncate the write to the
// start of the page containing the current EOF.
//
// See Linux, mm/filemap.c:generic_perform_write() and
// mm/shmem.c:shmem_write_begin().
if pgstart := uint64(usermem.Addr(rw.file.size).RoundDown()); end > pgstart {
end = pgstart
}
if end <= rw.off {
// Truncation would result in no data being written.
rw.file.mu.Unlock()
return 0, syserror.EPERM
}
}
// Page-aligned mr for when we need to allocate memory. RoundUp can't
// overflow since end is an int64.
pgstartaddr := usermem.Addr(rw.off).RoundDown()
pgendaddr, _ := usermem.Addr(end).RoundUp()
pgMR := memmap.MappableRange{uint64(pgstartaddr), uint64(pgendaddr)}
var (
done uint64
retErr error
)
seg, gap := rw.file.data.Find(uint64(rw.off))
for rw.off < end {
mr := memmap.MappableRange{uint64(rw.off), uint64(end)}
switch {
case seg.Ok():
// Get internal mappings.
ims, err := rw.file.memFile.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), usermem.Write)
if err != nil {
retErr = err
goto exitLoop
}
// Copy to internal mappings.
n, err := safemem.CopySeq(ims, srcs)
done += n
rw.off += uint64(n)
srcs = srcs.DropFirst64(n)
if err != nil {
retErr = err
goto exitLoop
}
// Continue.
seg, gap = seg.NextNonEmpty()
case gap.Ok():
// Allocate memory for the write.
gapMR := gap.Range().Intersect(pgMR)
fr, err := rw.file.memFile.Allocate(gapMR.Length(), usage.Tmpfs)
if err != nil {
retErr = err
goto exitLoop
}
// Write to that memory as usual.
seg, gap = rw.file.data.Insert(gap, gapMR, fr.Start), fsutil.FileRangeGapIterator{}
}
}
exitLoop:
// If the write ends beyond the file's previous size, it causes the
// file to grow.
if rw.off > rw.file.size {
atomic.StoreUint64(&rw.file.size, rw.off)
}
rw.file.mu.Unlock()
return done, retErr
}
@@ -0,0 +1,224 @@
// Copyright 2019 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 tmpfs
import (
"bytes"
"fmt"
"io"
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// newFileFD creates a new file in a new tmpfs mount, and returns the FD. If
// the returned err is not nil, then cleanup should be called when the FD is no
// longer needed.
func newFileFD(ctx context.Context, filename string) (*vfs.FileDescription, func(), error) {
creds := auth.CredentialsFromContext(ctx)
vfsObj := vfs.New()
vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
if err != nil {
return nil, nil, fmt.Errorf("failed to create tmpfs root mount: %v", err)
}
root := mntns.Root()
// Create the file that will be write/read.
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse(filename),
FollowFinalSymlink: true,
}, &vfs.OpenOptions{
Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
Mode: 0644,
})
if err != nil {
root.DecRef()
mntns.DecRef(vfsObj)
return nil, nil, fmt.Errorf("failed to create file %q: %v", filename, err)
}
return fd, func() {
root.DecRef()
mntns.DecRef(vfsObj)
}, nil
}
// Test that we can write some data to a file and read it back.`
func TestSimpleWriteRead(t *testing.T) {
ctx := contexttest.Context(t)
fd, cleanup, err := newFileFD(ctx, "simpleReadWrite")
if err != nil {
t.Fatal(err)
}
defer cleanup()
// Write.
data := []byte("foobarbaz")
n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
if err != nil {
t.Fatalf("fd.Write failed: %v", err)
}
if n != int64(len(data)) {
t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
}
if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
t.Errorf("fd.Write left offset at %d, want %d", got, want)
}
// Seek back to beginning.
if _, err := fd.Seek(ctx, 0, linux.SEEK_SET); err != nil {
t.Fatalf("fd.Seek failed: %v", err)
}
if got, want := fd.Impl().(*regularFileFD).off, int64(0); got != want {
t.Errorf("fd.Seek(0) left offset at %d, want %d", got, want)
}
// Read.
buf := make([]byte, len(data))
n, err = fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
if err != nil && err != io.EOF {
t.Fatalf("fd.Read failed: %v", err)
}
if n != int64(len(data)) {
t.Errorf("fd.Read got short read length %d, want %d", n, len(data))
}
if got, want := string(buf), string(data); got != want {
t.Errorf("Read got %q want %s", got, want)
}
if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
t.Errorf("fd.Write left offset at %d, want %d", got, want)
}
}
func TestPWrite(t *testing.T) {
ctx := contexttest.Context(t)
fd, cleanup, err := newFileFD(ctx, "PRead")
if err != nil {
t.Fatal(err)
}
defer cleanup()
// Fill file with 1k 'a's.
data := bytes.Repeat([]byte{'a'}, 1000)
n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
if err != nil {
t.Fatalf("fd.Write failed: %v", err)
}
if n != int64(len(data)) {
t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
}
// Write "gVisor is awesome" at various offsets.
buf := []byte("gVisor is awesome")
offsets := []int{0, 1, 2, 10, 20, 50, 100, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
for _, offset := range offsets {
name := fmt.Sprintf("PWrite offset=%d", offset)
t.Run(name, func(t *testing.T) {
n, err := fd.PWrite(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.WriteOptions{})
if err != nil {
t.Errorf("fd.PWrite got err %v want nil", err)
}
if n != int64(len(buf)) {
t.Errorf("fd.PWrite got %d bytes want %d", n, len(buf))
}
// Update data to reflect expected file contents.
if len(data) < offset+len(buf) {
data = append(data, make([]byte, (offset+len(buf))-len(data))...)
}
copy(data[offset:], buf)
// Read the whole file and compare with data.
readBuf := make([]byte, len(data))
n, err = fd.PRead(ctx, usermem.BytesIOSequence(readBuf), 0, vfs.ReadOptions{})
if err != nil {
t.Fatalf("fd.PRead failed: %v", err)
}
if n != int64(len(data)) {
t.Errorf("fd.PRead got short read length %d, want %d", n, len(data))
}
if got, want := string(readBuf), string(data); got != want {
t.Errorf("PRead got %q want %s", got, want)
}
})
}
}
func TestPRead(t *testing.T) {
ctx := contexttest.Context(t)
fd, cleanup, err := newFileFD(ctx, "PRead")
if err != nil {
t.Fatal(err)
}
defer cleanup()
// Write 100 sequences of 'gVisor is awesome'.
data := bytes.Repeat([]byte("gVisor is awsome"), 100)
n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
if err != nil {
t.Fatalf("fd.Write failed: %v", err)
}
if n != int64(len(data)) {
t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
}
// Read various sizes from various offsets.
sizes := []int{0, 1, 2, 10, 20, 50, 100, 1000}
offsets := []int{0, 1, 2, 10, 20, 50, 100, 1000, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
for _, size := range sizes {
for _, offset := range offsets {
name := fmt.Sprintf("PRead offset=%d size=%d", offset, size)
t.Run(name, func(t *testing.T) {
var (
wantRead []byte
wantErr error
)
if offset < len(data) {
wantRead = data[offset:]
} else if size > 0 {
wantErr = io.EOF
}
if offset+size < len(data) {
wantRead = wantRead[:size]
}
buf := make([]byte, size)
n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.ReadOptions{})
if err != wantErr {
t.Errorf("fd.PRead got err %v want %v", err, wantErr)
}
if n != int64(len(wantRead)) {
t.Errorf("fd.PRead got %d bytes want %d", n, len(wantRead))
}
if got := string(buf[:n]); got != string(wantRead) {
t.Errorf("fd.PRead got %q want %q", got, string(wantRead))
}
})
}
}
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package memfs
package tmpfs
import (
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
@@ -12,20 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package memfs provides a filesystem implementation that behaves like tmpfs:
// Package tmpfs provides a filesystem implementation that behaves like tmpfs:
// the Dentry tree is the sole source of truth for the state of the filesystem.
//
// memfs is intended primarily to demonstrate filesystem implementation
// patterns. Real uses cases for an in-memory filesystem should use tmpfs
// instead.
//
// Lock order:
//
// filesystem.mu
// regularFileFD.offMu
// regularFile.mu
// inode.mu
package memfs
package tmpfs
import (
"fmt"
@@ -36,6 +32,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
@@ -47,6 +44,9 @@ type FilesystemType struct{}
type filesystem struct {
vfsfs vfs.Filesystem
// memFile is used to allocate pages to for regular files.
memFile *pgalloc.MemoryFile
// mu serializes changes to the Dentry tree.
mu sync.RWMutex
@@ -55,7 +55,13 @@ type filesystem struct {
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
var fs filesystem
memFileProvider := pgalloc.MemoryFileProviderFromContext(ctx)
if memFileProvider == nil {
panic("MemoryFileProviderFromContext returned nil")
}
fs := filesystem{
memFile: memFileProvider.MemoryFile(),
}
fs.vfsfs.Init(vfsObj, &fs)
root := fs.newDentry(fs.newDirectory(creds, 01777))
return &fs.vfsfs, &root.vfsd, nil
@@ -74,11 +80,11 @@ type dentry struct {
// immutable.
inode *inode
// memfs doesn't count references on dentries; because the dentry tree is
// tmpfs doesn't count references on dentries; because the dentry tree is
// the sole source of truth, it is by definition always consistent with the
// state of the filesystem. However, it does count references on inodes,
// because inode resources are released when all references are dropped.
// (memfs doesn't really have resources to release, but we implement
// (tmpfs doesn't really have resources to release, but we implement
// reference counting because tmpfs regular files will.)
// dentryEntry (ugh) links dentries into their parent directory.childList.
@@ -150,7 +156,7 @@ func (i *inode) init(impl interface{}, fs *filesystem, creds *auth.Credentials,
// i.nlink < maxLinks.
func (i *inode) incLinksLocked() {
if i.nlink == 0 {
panic("memfs.inode.incLinksLocked() called with no existing links")
panic("tmpfs.inode.incLinksLocked() called with no existing links")
}
if i.nlink == maxLinks {
panic("memfs.inode.incLinksLocked() called with maximum link count")
@@ -163,14 +169,14 @@ func (i *inode) incLinksLocked() {
// Preconditions: filesystem.mu must be locked for writing. i.nlink != 0.
func (i *inode) decLinksLocked() {
if i.nlink == 0 {
panic("memfs.inode.decLinksLocked() called with no existing links")
panic("tmpfs.inode.decLinksLocked() called with no existing links")
}
atomic.AddUint32(&i.nlink, ^uint32(0))
}
func (i *inode) incRef() {
if atomic.AddInt64(&i.refs, 1) <= 1 {
panic("memfs.inode.incRef() called without holding a reference")
panic("tmpfs.inode.incRef() called without holding a reference")
}
}
@@ -189,14 +195,14 @@ func (i *inode) tryIncRef() bool {
func (i *inode) decRef() {
if refs := atomic.AddInt64(&i.refs, -1); refs == 0 {
// This is unnecessary; it's mostly to simulate what tmpfs would do.
if regfile, ok := i.impl.(*regularFile); ok {
regfile.mu.Lock()
regfile.data = nil
atomic.StoreInt64(&regfile.dataLen, 0)
regfile.mu.Unlock()
if regFile, ok := i.impl.(*regularFile); ok {
regFile.mu.Lock()
regFile.data.DropAll(regFile.memFile)
atomic.StoreUint64(&regFile.size, 0)
regFile.mu.Unlock()
}
} else if refs < 0 {
panic("memfs.inode.decRef() called without holding a reference")
panic("tmpfs.inode.decRef() called without holding a reference")
}
}
@@ -220,7 +226,7 @@ func (i *inode) statTo(stat *linux.Statx) {
case *regularFile:
stat.Mode |= linux.S_IFREG
stat.Mask |= linux.STATX_SIZE | linux.STATX_BLOCKS
stat.Size = uint64(atomic.LoadInt64(&impl.dataLen))
stat.Size = uint64(atomic.LoadUint64(&impl.size))
// In tmpfs, this will be FileRangeSet.Span() / 512 (but also cached in
// a uint64 accessed using atomic memory operations to avoid taking
// locks).
@@ -261,7 +267,7 @@ func (i *inode) direntType() uint8 {
}
}
// fileDescription is embedded by memfs implementations of
// fileDescription is embedded by tmpfs implementations of
// vfs.FileDescriptionImpl.
type fileDescription struct {
vfsfd vfs.FileDescription