mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Resolve remaining TODOs for tmpfs.
Closes #1197 PiperOrigin-RevId: 311438223
This commit is contained in:
committed by
gVisor bot
parent
8605c97136
commit
db655f020e
@@ -96,6 +96,7 @@ go_test(
|
||||
"pipe_test.go",
|
||||
"regular_file_test.go",
|
||||
"stat_test.go",
|
||||
"tmpfs_test.go",
|
||||
],
|
||||
library = ":tmpfs",
|
||||
deps = [
|
||||
|
||||
@@ -16,6 +16,7 @@ package tmpfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// Sync implements vfs.FilesystemImpl.Sync.
|
||||
@@ -76,8 +78,8 @@ afterSymlink:
|
||||
return nil, err
|
||||
}
|
||||
if symlink, ok := child.inode.impl.(*symlink); ok && rp.ShouldFollowSymlink() {
|
||||
// TODO(gvisor.dev/issue/1197): Symlink traversals updates
|
||||
// access time.
|
||||
// Symlink traversal updates access time.
|
||||
atomic.StoreInt64(&d.inode.atime, d.inode.fs.clock.Now().Nanoseconds())
|
||||
if err := rp.HandleSymlink(symlink.target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -361,8 +363,8 @@ afterTrailingSymlink:
|
||||
}
|
||||
// Do we need to resolve a trailing symlink?
|
||||
if symlink, ok := child.inode.impl.(*symlink); ok && rp.ShouldFollowSymlink() {
|
||||
// TODO(gvisor.dev/issue/1197): Symlink traversals updates
|
||||
// access time.
|
||||
// Symlink traversal updates access time.
|
||||
atomic.StoreInt64(&child.inode.atime, child.inode.fs.clock.Now().Nanoseconds())
|
||||
if err := rp.HandleSymlink(symlink.target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -636,12 +638,19 @@ func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
|
||||
func (fs *filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) {
|
||||
fs.mu.RLock()
|
||||
defer fs.mu.RUnlock()
|
||||
_, err := resolveLocked(rp)
|
||||
if err != nil {
|
||||
if _, err := resolveLocked(rp); err != nil {
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
// TODO(gvisor.dev/issue/1197): Actually implement statfs.
|
||||
return linux.Statfs{}, syserror.ENOSYS
|
||||
statfs := linux.Statfs{
|
||||
Type: linux.TMPFS_MAGIC,
|
||||
BlockSize: usermem.PageSize,
|
||||
FragmentSize: usermem.PageSize,
|
||||
NameLength: linux.NAME_MAX,
|
||||
// TODO(b/29637826): Allow configuring a tmpfs size and enforce it.
|
||||
Blocks: 0,
|
||||
BlocksFree: 0,
|
||||
}
|
||||
return statfs, nil
|
||||
}
|
||||
|
||||
// SymlinkAt implements vfs.FilesystemImpl.SymlinkAt.
|
||||
|
||||
@@ -18,152 +18,16 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs/lock"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// 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.VirtualFilesystem{}
|
||||
if err := vfsObj.Init(); err != nil {
|
||||
return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err)
|
||||
}
|
||||
|
||||
vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserMount: true,
|
||||
})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
root := mntns.Root()
|
||||
return vfsObj, root, func() {
|
||||
root.DecRef()
|
||||
mntns.DecRef()
|
||||
}, 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)
|
||||
|
||||
@@ -29,7 +29,6 @@ func TestStatAfterCreate(t *testing.T) {
|
||||
mode := linux.FileMode(0644)
|
||||
|
||||
// Run with different file types.
|
||||
// TODO(gvisor.dev/issue/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 (
|
||||
@@ -175,7 +174,6 @@ func TestSetStat(t *testing.T) {
|
||||
mode := linux.FileMode(0644)
|
||||
|
||||
// Run with different file types.
|
||||
// TODO(gvisor.dev/issue/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 (
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
// 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.VirtualFilesystem{}
|
||||
if err := vfsObj.Init(); err != nil {
|
||||
return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("VFS init: %v", err)
|
||||
}
|
||||
|
||||
vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserMount: true,
|
||||
})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("failed to create tmpfs root mount: %v", err)
|
||||
}
|
||||
root := mntns.Root()
|
||||
return vfsObj, root, func() {
|
||||
root.DecRef()
|
||||
mntns.DecRef()
|
||||
}, 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
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("tmpfs-test-%d", atomic.AddInt64(&nextFileID, 1))
|
||||
|
||||
if err := vfsObj.MknodAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(name),
|
||||
}, &vfs.MknodOptions{
|
||||
Mode: linux.ModeNamedPipe | mode,
|
||||
}); err != nil {
|
||||
cleanup()
|
||||
return nil, nil, fmt.Errorf("failed to create pipe %q: %v", name, err)
|
||||
}
|
||||
|
||||
fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
|
||||
Root: root,
|
||||
Start: root,
|
||||
Path: fspath.Parse(name),
|
||||
}, &vfs.OpenOptions{
|
||||
Flags: linux.O_RDWR,
|
||||
})
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, fmt.Errorf("failed to open pipe %q: %v", name, err)
|
||||
}
|
||||
|
||||
return fd, cleanup, nil
|
||||
}
|
||||
@@ -3288,6 +3288,7 @@ cc_binary(
|
||||
"//test/util:capability_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:fs_util",
|
||||
"@com_google_absl//absl/time",
|
||||
gtest,
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
|
||||
@@ -61,7 +61,7 @@ TEST(SocketTest, ProtocolInet) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SocketTest, UnixSocketFileMode) {
|
||||
TEST(SocketTest, UnixSocketStat) {
|
||||
// TODO(gvisor.dev/issue/1624): Re-enable this test once VFS1 is deleted. It
|
||||
// should pass in VFS2.
|
||||
SKIP_IF(IsRunningOnGvisor());
|
||||
@@ -83,7 +83,14 @@ TEST(SocketTest, UnixSocketFileMode) {
|
||||
|
||||
struct stat statbuf = {};
|
||||
ASSERT_THAT(stat(addr.sun_path, &statbuf), SyscallSucceeds());
|
||||
|
||||
// Mode should be S_IFSOCK.
|
||||
EXPECT_EQ(statbuf.st_mode, S_IFSOCK | sock_perm & ~mask);
|
||||
|
||||
// Timestamps should be equal and non-zero.
|
||||
EXPECT_NE(statbuf.st_atime, 0);
|
||||
EXPECT_EQ(statbuf.st_atime, statbuf.st_mtime);
|
||||
EXPECT_EQ(statbuf.st_atime, statbuf.st_ctime);
|
||||
}
|
||||
|
||||
TEST(SocketTest, UnixConnectNeedsWritePerm) {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "test/util/capability_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/fs_util.h"
|
||||
@@ -272,6 +273,30 @@ TEST(SymlinkTest, ChmodSymlink) {
|
||||
EXPECT_EQ(FilePermission(newpath), 0777);
|
||||
}
|
||||
|
||||
// Test that following a symlink updates the atime on the symlink.
|
||||
TEST(SymlinkTest, FollowUpdatesATime) {
|
||||
const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
||||
const std::string link = NewTempAbsPath();
|
||||
EXPECT_THAT(symlink(file.path().c_str(), link.c_str()), SyscallSucceeds());
|
||||
|
||||
// Lstat the symlink.
|
||||
struct stat st_before_follow;
|
||||
ASSERT_THAT(lstat(link.c_str(), &st_before_follow), SyscallSucceeds());
|
||||
|
||||
// Let the clock advance.
|
||||
absl::SleepFor(absl::Seconds(1));
|
||||
|
||||
// Open the file via the symlink.
|
||||
int fd;
|
||||
ASSERT_THAT(fd = open(link.c_str(), O_RDWR, 0666), SyscallSucceeds());
|
||||
FileDescriptor fd_closer(fd);
|
||||
|
||||
// Lstat the symlink again, and check that atime is updated.
|
||||
struct stat st_after_follow;
|
||||
ASSERT_THAT(lstat(link.c_str(), &st_after_follow), SyscallSucceeds());
|
||||
EXPECT_LT(st_before_follow.st_atime, st_after_follow.st_atime);
|
||||
}
|
||||
|
||||
class ParamSymlinkTest : public ::testing::TestWithParam<std::string> {};
|
||||
|
||||
// Test that creating an existing symlink with creat will create the target.
|
||||
|
||||
Reference in New Issue
Block a user