mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
354a15a234
commit
51f3ab85e0
@@ -53,7 +53,7 @@ go_template_instance(
|
||||
"Key": "uint64",
|
||||
"Range": "memmap.MappableRange",
|
||||
"Value": "uint64",
|
||||
"Functions": "fileRangeSetFunctions",
|
||||
"Functions": "FileRangeSetFunctions",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 := ®ularFile{}
|
||||
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",
|
||||
+6
-6
@@ -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)
|
||||
}
|
||||
@@ -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 := ®ularFile{
|
||||
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 ®ularFileReadWriter{}
|
||||
},
|
||||
}
|
||||
|
||||
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(®file.dataLen, 0)
|
||||
regfile.mu.Unlock()
|
||||
if regFile, ok := i.impl.(*regularFile); ok {
|
||||
regFile.mu.Lock()
|
||||
regFile.data.DropAll(regFile.memFile)
|
||||
atomic.StoreUint64(®File.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
|
||||
Reference in New Issue
Block a user