mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add timestamps to VFS2 tmpfs, and implement some of SetStat.
PiperOrigin-RevId: 289962040
This commit is contained in:
committed by
gVisor bot
parent
7b7ce29af3
commit
d6fb1ec6c7
@@ -234,6 +234,19 @@ type StatxTimestamp struct {
|
||||
_ int32
|
||||
}
|
||||
|
||||
// ToNsec returns the nanosecond representation.
|
||||
func (sxts StatxTimestamp) ToNsec() int64 {
|
||||
return int64(sxts.Sec)*1e9 + int64(sxts.Nsec)
|
||||
}
|
||||
|
||||
// ToNsecCapped returns the safe nanosecond representation.
|
||||
func (sxts StatxTimestamp) ToNsecCapped() int64 {
|
||||
if sxts.Sec > maxSecInDuration {
|
||||
return math.MaxInt64
|
||||
}
|
||||
return sxts.ToNsec()
|
||||
}
|
||||
|
||||
// NsecToStatxTimestamp translates nanoseconds to StatxTimestamp.
|
||||
func NsecToStatxTimestamp(nsec int64) (ts StatxTimestamp) {
|
||||
return StatxTimestamp{
|
||||
|
||||
@@ -40,6 +40,7 @@ go_library(
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/kernel/pipe",
|
||||
"//pkg/sentry/kernel/time",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/pgalloc",
|
||||
"//pkg/sentry/platform",
|
||||
@@ -77,6 +78,7 @@ go_test(
|
||||
srcs = [
|
||||
"pipe_test.go",
|
||||
"regular_file_test.go",
|
||||
"stat_test.go",
|
||||
],
|
||||
embed = [":tmpfs"],
|
||||
deps = [
|
||||
|
||||
@@ -56,7 +56,8 @@ afterSymlink:
|
||||
}
|
||||
next := nextVFSD.Impl().(*dentry)
|
||||
if symlink, ok := next.inode.impl.(*symlink); ok && rp.ShouldFollowSymlink() {
|
||||
// TODO: symlink traversals update access time
|
||||
// TODO(gvisor.dev/issues/1197): Symlink traversals updates
|
||||
// access time.
|
||||
if err := rp.HandleSymlink(symlink.target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -501,7 +502,8 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
|
||||
oldParent.inode.decLinksLocked()
|
||||
newParent.inode.incLinksLocked()
|
||||
}
|
||||
// TODO: update timestamps and parent directory sizes
|
||||
// TODO(gvisor.dev/issues/1197): Update timestamps and parent directory
|
||||
// sizes.
|
||||
vfsObj.CommitRenameReplaceDentry(renamedVFSD, &newParent.vfsd, newName, replacedVFSD)
|
||||
return nil
|
||||
}
|
||||
@@ -555,15 +557,11 @@ func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error
|
||||
func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error {
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
_, err := resolveLocked(rp)
|
||||
d, err := resolveLocked(rp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Stat.Mask == 0 {
|
||||
return nil
|
||||
}
|
||||
// TODO: implement inode.setStat
|
||||
return syserror.EPERM
|
||||
return d.inode.setStat(opts.Stat)
|
||||
}
|
||||
|
||||
// StatAt implements vfs.FilesystemImpl.StatAt.
|
||||
@@ -587,7 +585,7 @@ func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linu
|
||||
if err != nil {
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
// TODO: actually implement statfs
|
||||
// TODO(gvisor.dev/issues/1197): Actually implement statfs.
|
||||
return linux.Statfs{}, syserror.ENOSYS
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
@@ -29,10 +30,12 @@ import (
|
||||
"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) {
|
||||
// nextFileID is used to generate unique file names.
|
||||
var nextFileID int64
|
||||
|
||||
// newTmpfsRoot creates a new tmpfs mount, and returns the root. If the error
|
||||
// is not nil, then cleanup should be called when the root is no longer needed.
|
||||
func newTmpfsRoot(ctx context.Context) (*vfs.VirtualFilesystem, vfs.VirtualDentry, func(), error) {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
vfsObj := vfs.New()
|
||||
@@ -41,36 +44,124 @@ func newFileFD(ctx context.Context, filename string) (*vfs.FileDescription, func
|
||||
})
|
||||
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)
|
||||
return nil, vfs.VirtualDentry{}, 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() {
|
||||
return vfsObj, root, func() {
|
||||
root.DecRef()
|
||||
mntns.DecRef(vfsObj)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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, mode linux.FileMode) (*vfs.FileDescription, func(), error) {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
vfsObj, root, cleanup, err := newTmpfsRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("tmpfs-test-file-%d", atomic.AddInt64(&nextFileID, 1))
|
||||
|
||||
// Create the file that will be write/read.
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(filename),
|
||||
}, &vfs.OpenOptions{
|
||||
Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
|
||||
Mode: linux.ModeRegular | mode,
|
||||
})
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, fmt.Errorf("failed to create file %q: %v", filename, err)
|
||||
}
|
||||
|
||||
return fd, cleanup, nil
|
||||
}
|
||||
|
||||
// newDirFD is like newFileFD, but for directories.
|
||||
func newDirFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
vfsObj, root, cleanup, err := newTmpfsRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dirname := fmt.Sprintf("tmpfs-test-dir-%d", atomic.AddInt64(&nextFileID, 1))
|
||||
|
||||
// Create the dir.
|
||||
if err := vfsObj.MkdirAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(dirname),
|
||||
}, &vfs.MkdirOptions{
|
||||
Mode: linux.ModeDirectory | mode,
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
return nil, nil, fmt.Errorf("failed to create directory %q: %v", dirname, err)
|
||||
}
|
||||
|
||||
// Open the dir and return it.
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(dirname),
|
||||
}, &vfs.OpenOptions{
|
||||
Flags: linux.O_RDONLY | linux.O_DIRECTORY,
|
||||
})
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, fmt.Errorf("failed to open directory %q: %v", dirname, err)
|
||||
}
|
||||
|
||||
return fd, cleanup, nil
|
||||
}
|
||||
|
||||
// newPipeFD is like newFileFD, but for pipes.
|
||||
func newPipeFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, func(), error) {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
vfsObj, root, cleanup, err := newTmpfsRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pipename := fmt.Sprintf("tmpfs-test-pipe-%d", atomic.AddInt64(&nextFileID, 1))
|
||||
|
||||
// Create the pipe.
|
||||
if err := vfsObj.MknodAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(pipename),
|
||||
}, &vfs.MknodOptions{
|
||||
Mode: linux.ModeNamedPipe | mode,
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
return nil, nil, fmt.Errorf("failed to create pipe %q: %v", pipename, err)
|
||||
}
|
||||
|
||||
// Open the pipe and return it.
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(pipename),
|
||||
}, &vfs.OpenOptions{
|
||||
Flags: linux.O_RDWR,
|
||||
})
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, fmt.Errorf("failed to open pipe %q: %v", pipename, err)
|
||||
}
|
||||
|
||||
return fd, cleanup, 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")
|
||||
fd, cleanup, err := newFileFD(ctx, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -116,7 +207,7 @@ func TestSimpleWriteRead(t *testing.T) {
|
||||
|
||||
func TestPWrite(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
fd, cleanup, err := newFileFD(ctx, "PRead")
|
||||
fd, cleanup, err := newFileFD(ctx, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -171,7 +262,7 @@ func TestPWrite(t *testing.T) {
|
||||
|
||||
func TestPRead(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
fd, cleanup, err := newFileFD(ctx, "PRead")
|
||||
fd, cleanup, err := newFileFD(ctx, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright 2020 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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
func TestStatAfterCreate(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
mode := linux.FileMode(0644)
|
||||
|
||||
// Run with different file types.
|
||||
// TODO(gvisor.dev/issues/1197): Also test symlinks and sockets.
|
||||
for _, typ := range []string{"file", "dir", "pipe"} {
|
||||
t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) {
|
||||
var (
|
||||
fd *vfs.FileDescription
|
||||
cleanup func()
|
||||
err error
|
||||
)
|
||||
switch typ {
|
||||
case "file":
|
||||
fd, cleanup, err = newFileFD(ctx, mode)
|
||||
case "dir":
|
||||
fd, cleanup, err = newDirFD(ctx, mode)
|
||||
case "pipe":
|
||||
fd, cleanup, err = newPipeFD(ctx, mode)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown typ %q", typ))
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
got, err := fd.Stat(ctx, vfs.StatOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Stat failed: %v", err)
|
||||
}
|
||||
|
||||
// Atime, Ctime, Mtime should all be current time (non-zero).
|
||||
atime, ctime, mtime := got.Atime.ToNsec(), got.Ctime.ToNsec(), got.Mtime.ToNsec()
|
||||
if atime != ctime || ctime != mtime {
|
||||
t.Errorf("got atime=%d ctime=%d mtime=%d, wanted equal values", atime, ctime, mtime)
|
||||
}
|
||||
if atime == 0 {
|
||||
t.Errorf("got atime=%d, want non-zero", atime)
|
||||
}
|
||||
|
||||
// Btime should be 0, as it is not set by tmpfs.
|
||||
if btime := got.Btime.ToNsec(); btime != 0 {
|
||||
t.Errorf("got btime %d, want 0", got.Btime.ToNsec())
|
||||
}
|
||||
|
||||
// Size should be 0.
|
||||
if got.Size != 0 {
|
||||
t.Errorf("got size %d, want 0", got.Size)
|
||||
}
|
||||
|
||||
// Nlink should be 1 for files, 2 for dirs.
|
||||
wantNlink := uint32(1)
|
||||
if typ == "dir" {
|
||||
wantNlink = 2
|
||||
}
|
||||
if got.Nlink != wantNlink {
|
||||
t.Errorf("got nlink %d, want %d", got.Nlink, wantNlink)
|
||||
}
|
||||
|
||||
// UID and GID are set from context creds.
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
if got.UID != uint32(creds.EffectiveKUID) {
|
||||
t.Errorf("got uid %d, want %d", got.UID, uint32(creds.EffectiveKUID))
|
||||
}
|
||||
if got.GID != uint32(creds.EffectiveKGID) {
|
||||
t.Errorf("got gid %d, want %d", got.GID, uint32(creds.EffectiveKGID))
|
||||
}
|
||||
|
||||
// Mode.
|
||||
wantMode := uint16(mode)
|
||||
switch typ {
|
||||
case "file":
|
||||
wantMode |= linux.S_IFREG
|
||||
case "dir":
|
||||
wantMode |= linux.S_IFDIR
|
||||
case "pipe":
|
||||
wantMode |= linux.S_IFIFO
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown typ %q", typ))
|
||||
}
|
||||
|
||||
if got.Mode != wantMode {
|
||||
t.Errorf("got mode %x, want %x", got.Mode, wantMode)
|
||||
}
|
||||
|
||||
// Ino.
|
||||
if got.Ino == 0 {
|
||||
t.Errorf("got ino %d, want not 0", got.Ino)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStatAtime(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
fd, cleanup, err := newFileFD(ctx, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL}
|
||||
|
||||
// Get initial stat.
|
||||
initialStat, err := fd.Stat(ctx, allStatOptions)
|
||||
if err != nil {
|
||||
t.Fatalf("Stat failed: %v", err)
|
||||
}
|
||||
|
||||
// Set atime, but without the mask.
|
||||
if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{
|
||||
Mask: 0,
|
||||
Atime: linux.NsecToStatxTimestamp(100),
|
||||
}}); err != nil {
|
||||
t.Errorf("SetStat atime without mask failed: %v")
|
||||
}
|
||||
// Atime should be unchanged.
|
||||
if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
|
||||
t.Errorf("Stat got error: %v", err)
|
||||
} else if gotStat.Atime != initialStat.Atime {
|
||||
t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime)
|
||||
}
|
||||
|
||||
// Set atime, this time included in the mask.
|
||||
setStat := linux.Statx{
|
||||
Mask: linux.STATX_ATIME,
|
||||
Atime: linux.NsecToStatxTimestamp(100),
|
||||
}
|
||||
if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil {
|
||||
t.Errorf("SetStat atime with mask failed: %v")
|
||||
}
|
||||
if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
|
||||
t.Errorf("Stat got error: %v", err)
|
||||
} else if gotStat.Atime != setStat.Atime {
|
||||
t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStat(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
mode := linux.FileMode(0644)
|
||||
|
||||
// Run with different file types.
|
||||
// TODO(gvisor.dev/issues/1197): Also test symlinks and sockets.
|
||||
for _, typ := range []string{"file", "dir", "pipe"} {
|
||||
t.Run(fmt.Sprintf("type=%q", typ), func(t *testing.T) {
|
||||
var (
|
||||
fd *vfs.FileDescription
|
||||
cleanup func()
|
||||
err error
|
||||
)
|
||||
switch typ {
|
||||
case "file":
|
||||
fd, cleanup, err = newFileFD(ctx, mode)
|
||||
case "dir":
|
||||
fd, cleanup, err = newDirFD(ctx, mode)
|
||||
case "pipe":
|
||||
fd, cleanup, err = newPipeFD(ctx, mode)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown typ %q", typ))
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
allStatOptions := vfs.StatOptions{Mask: linux.STATX_ALL}
|
||||
|
||||
// Get initial stat.
|
||||
initialStat, err := fd.Stat(ctx, allStatOptions)
|
||||
if err != nil {
|
||||
t.Fatalf("Stat failed: %v", err)
|
||||
}
|
||||
|
||||
// Set atime, but without the mask.
|
||||
if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: linux.Statx{
|
||||
Mask: 0,
|
||||
Atime: linux.NsecToStatxTimestamp(100),
|
||||
}}); err != nil {
|
||||
t.Errorf("SetStat atime without mask failed: %v")
|
||||
}
|
||||
// Atime should be unchanged.
|
||||
if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
|
||||
t.Errorf("Stat got error: %v", err)
|
||||
} else if gotStat.Atime != initialStat.Atime {
|
||||
t.Errorf("Stat got atime %d, want %d", gotStat.Atime, initialStat.Atime)
|
||||
}
|
||||
|
||||
// Set atime, this time included in the mask.
|
||||
setStat := linux.Statx{
|
||||
Mask: linux.STATX_ATIME,
|
||||
Atime: linux.NsecToStatxTimestamp(100),
|
||||
}
|
||||
if err := fd.SetStat(ctx, vfs.SetStatOptions{Stat: setStat}); err != nil {
|
||||
t.Errorf("SetStat atime with mask failed: %v")
|
||||
}
|
||||
if gotStat, err := fd.Stat(ctx, allStatOptions); err != nil {
|
||||
t.Errorf("Stat got error: %v", err)
|
||||
} else if gotStat.Atime != setStat.Atime {
|
||||
t.Errorf("Stat got atime %d, want %d", gotStat.Atime, setStat.Atime)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,10 @@ 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/kernel/time"
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// FilesystemType implements vfs.FilesystemType.
|
||||
@@ -47,6 +47,9 @@ type filesystem struct {
|
||||
// memFile is used to allocate pages to for regular files.
|
||||
memFile *pgalloc.MemoryFile
|
||||
|
||||
// clock is a realtime clock used to set timestamps in file operations.
|
||||
clock time.Clock
|
||||
|
||||
// mu serializes changes to the Dentry tree.
|
||||
mu sync.RWMutex
|
||||
|
||||
@@ -59,8 +62,10 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
if memFileProvider == nil {
|
||||
panic("MemoryFileProviderFromContext returned nil")
|
||||
}
|
||||
clock := time.RealtimeClockFromContext(ctx)
|
||||
fs := filesystem{
|
||||
memFile: memFileProvider.MemoryFile(),
|
||||
clock: clock,
|
||||
}
|
||||
fs.vfsfs.Init(vfsObj, &fs)
|
||||
root := fs.newDentry(fs.newDirectory(creds, 01777))
|
||||
@@ -126,26 +131,36 @@ type inode struct {
|
||||
// filesystem.RmdirAt() drops the reference.
|
||||
refs int64
|
||||
|
||||
// Inode metadata; protected by mu and accessed using atomic memory
|
||||
// operations unless otherwise specified.
|
||||
mu sync.RWMutex
|
||||
// Inode metadata. Writing multiple fields atomically requires holding
|
||||
// mu, othewise atomic operations can be used.
|
||||
mu sync.Mutex
|
||||
mode uint32 // excluding file type bits, which are based on impl
|
||||
nlink uint32 // protected by filesystem.mu instead of inode.mu
|
||||
uid uint32 // auth.KUID, but stored as raw uint32 for sync/atomic
|
||||
gid uint32 // auth.KGID, but ...
|
||||
ino uint64 // immutable
|
||||
|
||||
// Linux's tmpfs has no concept of btime.
|
||||
atime int64 // nanoseconds
|
||||
ctime int64 // nanoseconds
|
||||
mtime int64 // nanoseconds
|
||||
|
||||
impl interface{} // immutable
|
||||
}
|
||||
|
||||
const maxLinks = math.MaxUint32
|
||||
|
||||
func (i *inode) init(impl interface{}, fs *filesystem, creds *auth.Credentials, mode linux.FileMode) {
|
||||
now := fs.clock.Now().Nanoseconds()
|
||||
i.refs = 1
|
||||
i.mode = uint32(mode)
|
||||
i.uid = uint32(creds.EffectiveKUID)
|
||||
i.gid = uint32(creds.EffectiveKGID)
|
||||
i.ino = atomic.AddUint64(&fs.nextInoMinusOne, 1)
|
||||
// Tmpfs creation sets atime, ctime, and mtime to current time.
|
||||
i.atime = now
|
||||
i.ctime = now
|
||||
i.mtime = now
|
||||
// i.nlink initialized by caller
|
||||
i.impl = impl
|
||||
}
|
||||
@@ -213,15 +228,24 @@ func (i *inode) checkPermissions(creds *auth.Credentials, ats vfs.AccessTypes, i
|
||||
// Go won't inline this function, and returning linux.Statx (which is quite
|
||||
// big) means spending a lot of time in runtime.duffcopy(), so instead it's an
|
||||
// output parameter.
|
||||
//
|
||||
// Note that Linux does not guarantee to return consistent data (in the case of
|
||||
// a concurrent modification), so we do not require holding inode.mu.
|
||||
func (i *inode) statTo(stat *linux.Statx) {
|
||||
stat.Mask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_NLINK | linux.STATX_UID | linux.STATX_GID | linux.STATX_INO
|
||||
stat.Mask = linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_NLINK |
|
||||
linux.STATX_UID | linux.STATX_GID | linux.STATX_INO | linux.STATX_ATIME |
|
||||
linux.STATX_BTIME | linux.STATX_CTIME | linux.STATX_MTIME
|
||||
stat.Blksize = 1 // usermem.PageSize in tmpfs
|
||||
stat.Nlink = atomic.LoadUint32(&i.nlink)
|
||||
stat.UID = atomic.LoadUint32(&i.uid)
|
||||
stat.GID = atomic.LoadUint32(&i.gid)
|
||||
stat.Mode = uint16(atomic.LoadUint32(&i.mode))
|
||||
stat.Ino = i.ino
|
||||
// TODO: device number
|
||||
// Linux's tmpfs has no concept of btime, so zero-value is returned.
|
||||
stat.Atime = linux.NsecToStatxTimestamp(i.atime)
|
||||
stat.Ctime = linux.NsecToStatxTimestamp(i.ctime)
|
||||
stat.Mtime = linux.NsecToStatxTimestamp(i.mtime)
|
||||
// TODO(gvisor.dev/issues/1197): Device number.
|
||||
switch impl := i.impl.(type) {
|
||||
case *regularFile:
|
||||
stat.Mode |= linux.S_IFREG
|
||||
@@ -245,6 +269,36 @@ func (i *inode) statTo(stat *linux.Statx) {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *inode) setStat(stat linux.Statx) error {
|
||||
// TODO(gvisor.dev/issues/1197): Handle stat.Size by growing/shrinking
|
||||
// the file.
|
||||
if stat.Mask == 0 {
|
||||
return nil
|
||||
}
|
||||
i.mu.Lock()
|
||||
mask := stat.Mask
|
||||
if mask&linux.STATX_MODE != 0 {
|
||||
atomic.StoreUint32(&i.mode, uint32(stat.Mode))
|
||||
}
|
||||
if mask&linux.STATX_UID != 0 {
|
||||
atomic.StoreUint32(&i.uid, stat.UID)
|
||||
}
|
||||
if mask&linux.STATX_GID != 0 {
|
||||
atomic.StoreUint32(&i.gid, stat.GID)
|
||||
}
|
||||
if mask&linux.STATX_ATIME != 0 {
|
||||
atomic.StoreInt64(&i.atime, stat.Atime.ToNsecCapped())
|
||||
}
|
||||
if mask&linux.STATX_CTIME != 0 {
|
||||
atomic.StoreInt64(&i.ctime, stat.Ctime.ToNsecCapped())
|
||||
}
|
||||
if mask&linux.STATX_MTIME != 0 {
|
||||
atomic.StoreInt64(&i.mtime, stat.Mtime.ToNsecCapped())
|
||||
}
|
||||
i.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// allocatedBlocksForSize returns the number of 512B blocks needed to
|
||||
// accommodate the given size in bytes, as appropriate for struct
|
||||
// stat::st_blocks and struct statx::stx_blocks. (Note that this 512B block
|
||||
@@ -291,9 +345,5 @@ func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linu
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
if opts.Stat.Mask == 0 {
|
||||
return nil
|
||||
}
|
||||
// TODO: implement inode.setStat
|
||||
return syserror.EPERM
|
||||
return fd.inode().setStat(opts.Stat)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user