Resolve remaining TODOs for tmpfs.

Closes #1197

PiperOrigin-RevId: 311438223
This commit is contained in:
Nicolas Lacasse
2020-05-13 17:36:37 -07:00
committed by gVisor bot
parent 8605c97136
commit db655f020e
8 changed files with 208 additions and 147 deletions
+1
View File
@@ -96,6 +96,7 @@ go_test(
"pipe_test.go",
"regular_file_test.go",
"stat_test.go",
"tmpfs_test.go",
],
library = ":tmpfs",
deps = [
+17 -8
View File
@@ -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)
-2
View File
@@ -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 (
+156
View File
@@ -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
}
+1
View File
@@ -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",
+8 -1
View File
@@ -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) {
+25
View File
@@ -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.