Files
Jing Chen 7cc17225e6 Remove references to math/rand package's Read function.
The helper function is deprecated. The package gvisor.dev/gvisor/pkg/rand
depends on crypto/rand which performs worse thatn math/rand, the changes
are fine since they are not at any gVisor's hot path.

The ultimate goal is to migrate math/rand to math/rand/v2.
2024-10-16 18:17:15 +00:00

709 lines
23 KiB
Go

// Copyright 2021 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 testsuite provides a integration testing suite for lisafs.
// These tests are intended for servers serving the local filesystem.
package testsuite
import (
"bytes"
"fmt"
"os"
"testing"
"time"
"github.com/syndtr/gocapability/capability"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/lisafs"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/unet"
)
// Tester is the client code using this test suite. This interface abstracts
// away all the caller specific details.
type Tester interface {
// NewServer returns a new instance of the tester server.
NewServer(t *testing.T) *lisafs.Server
// LinkSupported returns true if the backing server supports LinkAt.
LinkSupported() bool
// SetUserGroupIDSupported returns true if the backing server supports
// changing UID/GID for files.
SetUserGroupIDSupported() bool
// BindSupported returns true if the backing server supports BindAt.
BindSupported() bool
}
// RunAllLocalFSTests runs all local FS tests as subtests.
func RunAllLocalFSTests(t *testing.T, tester Tester) {
for name, testFn := range localFSTests {
mountPath, err := os.MkdirTemp(os.Getenv("TEST_TMPDIR"), "")
if err != nil {
t.Fatalf("creation of temporary mountpoint failed: %v", err)
}
RunTest(t, tester, name, testFn, mountPath)
os.RemoveAll(mountPath)
}
}
// TestFunc describes the signature of a test method.
type TestFunc func(context.Context, *testing.T, Tester, lisafs.ClientFD)
var localFSTests = map[string]TestFunc{
"Stat": testStat,
"RegularFileIO": testRegularFileIO,
"RegularFileOpen": testRegularFileOpen,
"SetStat": testSetStat,
"Allocate": testAllocate,
"StatFS": testStatFS,
"Unlink": testUnlink,
"Symlink": testSymlink,
"HardLink": testHardLink,
"Walk": testWalk,
"Rename": testRename,
"Mknod": testMknod,
"UDS": testUDS,
"Getdents": testGetdents,
}
// RunTest runs the passed test function as a subtest.
func RunTest(t *testing.T, tester Tester, testName string, testFn TestFunc, mountPath string) {
refs.SetLeakMode(refs.LeaksPanic)
// server should run with a umask of 0, because we want to preserve file
// modes exactly for testing purposes.
unix.Umask(0)
serverSocket, clientSocket, err := unet.SocketPair(false)
if err != nil {
t.Fatalf("socketpair got err %v expected nil", err)
}
server := tester.NewServer(t)
conn, err := server.CreateConnection(serverSocket, mountPath, false /* readonly */)
if err != nil {
t.Fatalf("starting connection failed: %v", err)
return
}
server.StartConnection(conn)
c, root, _, err := lisafs.NewClient(clientSocket)
if err != nil {
t.Fatalf("client creation failed: %v", err)
}
if err := c.StartChannels(); err != nil {
t.Fatalf("failed to start channels: %v", err)
}
if !root.ControlFD.Ok() {
t.Fatalf("root control FD is not valid")
}
rootFile := c.NewFD(root.ControlFD)
ctx := context.Background()
t.Run(testName, func(t *testing.T) {
testFn(ctx, t, tester, rootFile)
})
closeFD(ctx, t, rootFile)
// Release server resources and check for leaks. Note that leak check must
// happen before c.Close() because server cleans up resources on shutdown.
server.Destroy()
refs.DoRepeatedLeakCheck()
c.Close() // This should trigger client and server shutdown.
server.Wait()
}
func closeFD(ctx context.Context, t testing.TB, fdLisa lisafs.ClientFD) {
fdLisa.Close(ctx, true /* flush */)
}
func statTo(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, stat *linux.Statx) {
if err := fdLisa.StatTo(ctx, stat); err != nil {
t.Fatalf("stat failed: %v", err)
}
}
func openCreateFile(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, name string) (lisafs.ClientFD, linux.Statx, lisafs.ClientFD, int) {
child, childFD, childHostFD, err := fdLisa.OpenCreateAt(ctx, name, unix.O_RDWR, 0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
if err != nil {
t.Fatalf("OpenCreateAt failed: %v", err)
}
if childHostFD == -1 {
t.Error("no host FD donated")
}
client := fdLisa.Client()
return client.NewFD(child.ControlFD), child.Stat, fdLisa.Client().NewFD(childFD), childHostFD
}
func openFile(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, flags uint32, isReg bool) (lisafs.ClientFD, int) {
openFD, hostFD, err := fdLisa.OpenAt(ctx, flags)
if err != nil {
t.Fatalf("OpenAt failed: %v", err)
}
if hostFD == -1 && isReg {
t.Error("no host FD donated")
}
return fdLisa.Client().NewFD(openFD), hostFD
}
func unlinkFile(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string, isDir bool) {
var flags uint32
if isDir {
flags = unix.AT_REMOVEDIR
}
if err := dir.UnlinkAt(ctx, name, flags); err != nil {
t.Errorf("unlinking file %s failed: %v", name, err)
}
}
func symlink(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name, target string) (lisafs.ClientFD, linux.Statx) {
linkIno, err := dir.SymlinkAt(ctx, name, target, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
if err != nil {
t.Fatalf("symlink failed: %v", err)
}
return dir.Client().NewFD(linkIno.ControlFD), linkIno.Stat
}
func link(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string, target lisafs.ClientFD) (lisafs.ClientFD, linux.Statx) {
linkIno, err := dir.LinkAt(ctx, target.ID(), name)
if err != nil {
t.Fatalf("link failed: %v", err)
}
return dir.Client().NewFD(linkIno.ControlFD), linkIno.Stat
}
func mkdir(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string) (lisafs.ClientFD, linux.Statx) {
childIno, err := dir.MkdirAt(ctx, name, 0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
if err != nil {
t.Fatalf("mkdir failed: %v", err)
}
return dir.Client().NewFD(childIno.ControlFD), childIno.Stat
}
func mknod(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string) (lisafs.ClientFD, linux.Statx) {
nodeIno, err := dir.MknodAt(ctx, name, unix.S_IFREG|0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()), 0, 0)
if err != nil {
t.Fatalf("mknod failed: %v", err)
}
return dir.Client().NewFD(nodeIno.ControlFD), nodeIno.Stat
}
func bind(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string, sockType linux.SockType) (lisafs.ClientFD, *lisafs.ClientBoundSocketFD, linux.Statx) {
nodeIno, socket, err := dir.BindAt(ctx, sockType, name, 0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
if err != nil {
t.Fatalf("bind failed: %v", err)
}
return dir.Client().NewFD(nodeIno.ControlFD), socket, nodeIno.Stat
}
func walk(ctx context.Context, t *testing.T, dir lisafs.ClientFD, names []string) []lisafs.Inode {
_, inodes, err := dir.WalkMultiple(ctx, names)
if err != nil {
t.Fatalf("walk failed while trying to walk components %+v: %v", names, err)
}
return inodes
}
func walkStat(ctx context.Context, t *testing.T, dir lisafs.ClientFD, names []string) []linux.Statx {
stats, err := dir.WalkStat(ctx, names)
if err != nil {
t.Fatalf("walk failed while trying to walk components %+v: %v", names, err)
}
return stats
}
func writeFD(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, off uint64, buf []byte) error {
count, err := fdLisa.Write(ctx, buf, off)
if err != nil {
return err
}
if int(count) != len(buf) {
t.Errorf("partial write: buf size = %d, written = %d", len(buf), count)
}
return nil
}
func readFDAndCmp(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, off uint64, want []byte) {
buf := make([]byte, len(want))
n, err := fdLisa.Read(ctx, buf, off)
if err != nil {
t.Errorf("read failed: %v", err)
return
}
if int(n) != len(want) {
t.Errorf("partial read: buf size = %d, read = %d", len(want), n)
return
}
if bytes.Compare(buf, want) != 0 {
t.Errorf("bytes read differ from what was expected: want = %v, got = %v", want, buf)
}
}
func allocateAndVerify(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, off uint64, length uint64) {
if err := fdLisa.Allocate(ctx, 0, off, length); err != nil {
t.Fatalf("fallocate failed: %v", err)
}
var stat linux.Statx
statTo(ctx, t, fdLisa, &stat)
if want := off + length; stat.Size != want {
t.Errorf("incorrect file size after allocate: expected %d, got %d", off+length, stat.Size)
}
}
func cmpStatx(t *testing.T, want, got linux.Statx) {
if got.Mask&unix.STATX_MODE != 0 && want.Mask&unix.STATX_MODE != 0 {
if got.Mode != want.Mode {
t.Errorf("mode differs: want %d, got %d", want.Mode, got.Mode)
}
}
if got.Mask&unix.STATX_INO != 0 && want.Mask&unix.STATX_INO != 0 {
if got.Ino != want.Ino {
t.Errorf("inode number differs: want %d, got %d", want.Ino, got.Ino)
}
}
if got.Mask&unix.STATX_NLINK != 0 && want.Mask&unix.STATX_NLINK != 0 {
if got.Nlink != want.Nlink {
t.Errorf("nlink differs: want %d, got %d", want.Nlink, got.Nlink)
}
}
if got.Mask&unix.STATX_UID != 0 && want.Mask&unix.STATX_UID != 0 {
if got.UID != want.UID {
t.Errorf("UID differs: want %d, got %d", want.UID, got.UID)
}
}
if got.Mask&unix.STATX_GID != 0 && want.Mask&unix.STATX_GID != 0 {
if got.GID != want.GID {
t.Errorf("GID differs: want %d, got %d", want.GID, got.GID)
}
}
if got.Mask&unix.STATX_SIZE != 0 && want.Mask&unix.STATX_SIZE != 0 {
if got.Size != want.Size {
t.Errorf("size differs: want %d, got %d", want.Size, got.Size)
}
}
if got.Mask&unix.STATX_BLOCKS != 0 && want.Mask&unix.STATX_BLOCKS != 0 {
if got.Blocks != want.Blocks {
t.Errorf("blocks differs: want %d, got %d", want.Blocks, got.Blocks)
}
}
if got.Mask&unix.STATX_ATIME != 0 && want.Mask&unix.STATX_ATIME != 0 {
if got.Atime != want.Atime {
t.Errorf("atime differs: want %d, got %d", want.Atime, got.Atime)
}
}
if got.Mask&unix.STATX_MTIME != 0 && want.Mask&unix.STATX_MTIME != 0 {
if got.Mtime != want.Mtime {
t.Errorf("mtime differs: want %d, got %d", want.Mtime, got.Mtime)
}
}
if got.Mask&unix.STATX_CTIME != 0 && want.Mask&unix.STATX_CTIME != 0 {
if got.Ctime != want.Ctime {
t.Errorf("ctime differs: want %d, got %d", want.Ctime, got.Ctime)
}
}
}
func hasCapability(c capability.Cap) bool {
caps, err := capability.NewPid2(os.Getpid())
if err != nil {
return false
}
if err := caps.Load(); err != nil {
return false
}
return caps.Get(capability.EFFECTIVE, c)
}
func testStat(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
var rootStat linux.Statx
if err := root.StatTo(ctx, &rootStat); err != nil {
t.Errorf("stat on the root dir failed: %v", err)
}
if ftype := rootStat.Mode & unix.S_IFMT; ftype != unix.S_IFDIR {
t.Errorf("root inode is not a directory, file type = %d", ftype)
}
}
func testRegularFileIO(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
name := "tempFile"
controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
defer closeFD(ctx, t, controlFile)
defer closeFD(ctx, t, fd)
defer unix.Close(hostFD)
// Test Read/Write RPCs with 2MB of data to test IO in chunks.
data := make([]byte, 1<<21)
rand.Read(data)
if err := writeFD(ctx, t, fd, 0, data); err != nil {
t.Fatalf("write failed: %v", err)
}
readFDAndCmp(ctx, t, fd, 0, data)
readFDAndCmp(ctx, t, fd, 50, data[50:])
// Make sure the host FD is configured properly.
hostReadData := make([]byte, len(data))
if n, err := unix.Pread(hostFD, hostReadData, 0); err != nil {
t.Errorf("host read failed: %v", err)
} else if n != len(hostReadData) {
t.Errorf("partial read: buf size = %d, read = %d", len(hostReadData), n)
} else if bytes.Compare(hostReadData, data) != 0 {
t.Errorf("bytes read differ from what was expected: want = %v, got = %v", data, hostReadData)
}
// Test syncing the writable FD.
if err := fd.Sync(ctx); err != nil {
t.Errorf("syncing the FD failed: %v", err)
}
}
func testRegularFileOpen(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
name := "tempFile"
controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
defer closeFD(ctx, t, controlFile)
defer closeFD(ctx, t, fd)
defer unix.Close(hostFD)
// Open a readonly FD and try writing to it to get an EBADF.
roFile, roHostFD := openFile(ctx, t, controlFile, unix.O_RDONLY, true /* isReg */)
defer closeFD(ctx, t, roFile)
defer unix.Close(roHostFD)
if err := writeFD(ctx, t, roFile, 0, []byte{1, 2, 3}); err != unix.EBADF {
t.Errorf("writing to read only FD should generate EBADF, but got %v", err)
}
}
func testSetStat(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
name := "tempFile"
controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
defer closeFD(ctx, t, controlFile)
defer closeFD(ctx, t, fd)
defer unix.Close(hostFD)
now := time.Now()
wantStat := linux.Statx{
Mask: unix.STATX_MODE | unix.STATX_ATIME | unix.STATX_MTIME | unix.STATX_SIZE,
Mode: 0760,
UID: uint32(unix.Getuid()),
GID: uint32(unix.Getgid()),
Size: 50,
Atime: linux.NsecToStatxTimestamp(now.UnixNano()),
Mtime: linux.NsecToStatxTimestamp(now.UnixNano()),
}
if tester.SetUserGroupIDSupported() {
wantStat.Mask |= unix.STATX_UID | unix.STATX_GID
}
failureMask, failureErr, err := controlFile.SetStat(ctx, &wantStat)
if err != nil {
t.Fatalf("setstat failed: %v", err)
}
if failureMask != 0 {
t.Fatalf("some setstat operations failed: failureMask = %#b, failureErr = %v", failureMask, failureErr)
}
// Verify that attributes were updated.
var gotStat linux.Statx
statTo(ctx, t, controlFile, &gotStat)
if gotStat.Mode&07777 != wantStat.Mode ||
gotStat.Size != wantStat.Size ||
gotStat.Atime.ToNsec() != wantStat.Atime.ToNsec() ||
gotStat.Mtime.ToNsec() != wantStat.Mtime.ToNsec() ||
(tester.SetUserGroupIDSupported() && (uint32(gotStat.UID) != wantStat.UID || uint32(gotStat.GID) != wantStat.GID)) {
t.Errorf("setStat did not update file correctly: setStat = %+v, stat = %+v", wantStat, gotStat)
}
}
func testAllocate(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
name := "tempFile"
controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
defer closeFD(ctx, t, controlFile)
defer closeFD(ctx, t, fd)
defer unix.Close(hostFD)
allocateAndVerify(ctx, t, fd, 0, 40)
allocateAndVerify(ctx, t, fd, 20, 100)
}
func testStatFS(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
var statFS lisafs.StatFS
if err := root.StatFSTo(ctx, &statFS); err != nil {
t.Errorf("statfs failed: %v", err)
}
}
func testUnlink(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
name := "tempFile"
controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
defer closeFD(ctx, t, controlFile)
defer closeFD(ctx, t, fd)
defer unix.Close(hostFD)
unlinkFile(ctx, t, root, name, false /* isDir */)
if inodes := walk(ctx, t, root, []string{name}); len(inodes) > 0 {
t.Errorf("deleted file should not be generating inodes on walk: %+v", inodes)
}
}
func testSymlink(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
target := "/tmp/some/path"
name := "symlinkFile"
link, linkStat := symlink(ctx, t, root, name, target)
defer closeFD(ctx, t, link)
if linkStat.Mode&unix.S_IFMT != unix.S_IFLNK {
t.Errorf("stat return from symlink RPC indicates that the inode is not a symlink: mode = %d", linkStat.Mode)
}
if gotTarget, err := link.ReadLinkAt(ctx); err != nil {
t.Fatalf("readlink failed: %v", err)
} else if gotTarget != target {
t.Errorf("readlink return incorrect target: expected %q, got %q", target, gotTarget)
}
}
func testHardLink(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
if !tester.LinkSupported() {
t.Skipf("server does not support LinkAt RPC")
}
if !hasCapability(capability.CAP_DAC_READ_SEARCH) {
t.Skipf("TestHardLink requires CAP_DAC_READ_SEARCH, running as %d", unix.Getuid())
}
name := "tempFile"
controlFile, fileIno, fd, hostFD := openCreateFile(ctx, t, root, name)
defer closeFD(ctx, t, controlFile)
defer closeFD(ctx, t, fd)
defer unix.Close(hostFD)
linkName := "linkFile"
link, linkStat := link(ctx, t, root, linkName, controlFile)
defer closeFD(ctx, t, link)
if linkStat.Ino != fileIno.Ino {
t.Errorf("hard linked files have different inode numbers: %d %d", linkStat.Ino, fileIno.Ino)
}
if linkStat.DevMinor != fileIno.DevMinor {
t.Errorf("hard linked files have different minor device numbers: %d %d", linkStat.DevMinor, fileIno.DevMinor)
}
if linkStat.DevMajor != fileIno.DevMajor {
t.Errorf("hard linked files have different major device numbers: %d %d", linkStat.DevMajor, fileIno.DevMajor)
}
}
func testWalk(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
// Create 10 nested directories.
n := 10
curDir := root
dirNames := make([]string, 0, n)
for i := 0; i < n; i++ {
name := fmt.Sprintf("tmpdir-%d", i)
childDir, _ := mkdir(ctx, t, curDir, name)
defer closeFD(ctx, t, childDir)
defer unlinkFile(ctx, t, curDir, name, true /* isDir */)
curDir = childDir
dirNames = append(dirNames, name)
}
// Walk all these directories. Add some junk at the end which should not be
// walked on.
dirNames = append(dirNames, []string{"a", "b", "c"}...)
inodes := walk(ctx, t, root, dirNames)
if len(inodes) != n {
t.Errorf("walk returned the incorrect number of inodes: wanted %d, got %d", n, len(inodes))
}
// Close all control FDs and collect stat results for all dirs including
// the root directory.
dirStats := make([]linux.Statx, 0, n+1)
var stat linux.Statx
statTo(ctx, t, root, &stat)
dirStats = append(dirStats, stat)
for _, inode := range inodes {
dirStats = append(dirStats, inode.Stat)
closeFD(ctx, t, root.Client().NewFD(inode.ControlFD))
}
// Test WalkStat which additionally returns Statx for root because the first
// path component is "".
dirNames = append([]string{""}, dirNames...)
gotStats := walkStat(ctx, t, root, dirNames)
if len(gotStats) != len(dirStats) {
t.Errorf("walkStat returned the incorrect number of statx: wanted %d, got %d", len(dirStats), len(gotStats))
} else {
for i := range gotStats {
cmpStatx(t, dirStats[i], gotStats[i])
}
}
}
func testRename(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
name := "tempFile"
tempFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
defer closeFD(ctx, t, tempFile)
defer closeFD(ctx, t, fd)
defer unix.Close(hostFD)
tempDir, _ := mkdir(ctx, t, root, "tempDir")
defer closeFD(ctx, t, tempDir)
// Move tempFile into tempDir.
if err := root.RenameAt(ctx, name, tempDir.ID(), "movedFile"); err != nil {
t.Fatalf("rename failed: %v", err)
}
inodes := walkStat(ctx, t, root, []string{"tempDir", "movedFile"})
if len(inodes) != 2 {
t.Errorf("expected 2 files on walk but only found %d", len(inodes))
}
}
func testMknod(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
name := "regular-file"
pipeFile, pipeStat := mknod(ctx, t, root, name)
defer closeFD(ctx, t, pipeFile)
if got := pipeStat.Mode & unix.S_IFMT; got != unix.S_IFREG {
t.Errorf("socket file mode is incorrect: want %#x, got %#x", unix.S_IFSOCK, got)
}
if tester.SetUserGroupIDSupported() {
if want := unix.Getuid(); int(pipeStat.UID) != want {
t.Errorf("socket file uid is incorrect: want %d, got %d", want, pipeStat.UID)
}
if want := unix.Getgid(); int(pipeStat.GID) != want {
t.Errorf("socket file gid is incorrect: want %d, got %d", want, pipeStat.GID)
}
}
var stat linux.Statx
statTo(ctx, t, pipeFile, &stat)
if stat.Mode != pipeStat.Mode {
t.Errorf("mknod mode is incorrect: want %d, got %d", pipeStat.Mode, stat.Mode)
}
if stat.UID != pipeStat.UID {
t.Errorf("mknod UID is incorrect: want %d, got %d", pipeStat.UID, stat.UID)
}
if stat.GID != pipeStat.GID {
t.Errorf("mknod GID is incorrect: want %d, got %d", pipeStat.GID, stat.GID)
}
}
func testUDS(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
if !tester.BindSupported() {
t.Skipf("server does not support BindAt RPC")
}
const name = "sock"
file, socket, stat := bind(ctx, t, root, name, unix.SOCK_STREAM)
defer closeFD(ctx, t, file)
defer socket.Close(ctx)
if got := stat.Mode & unix.S_IFMT; got != unix.S_IFSOCK {
t.Errorf("socket file mode is incorrect: want %#x, got %#x", unix.S_IFSOCK, got)
}
if tester.SetUserGroupIDSupported() {
if want := unix.Getuid(); int(stat.UID) != want {
t.Errorf("socket file uid is incorrect: want %d, got %d", want, stat.UID)
}
if want := unix.Getgid(); int(stat.GID) != want {
t.Errorf("socket file gid is incorrect: want %d, got %d", want, stat.GID)
}
}
var got linux.Statx
statTo(ctx, t, file, &got)
if stat.Mode != got.Mode {
t.Errorf("UDS mode is incorrect: want %d, got %d", stat.Mode, got.Mode)
}
if stat.UID != got.UID {
t.Errorf("mknod UID is incorrect: want %d, got %d", stat.UID, got.UID)
}
if stat.GID != got.GID {
t.Errorf("mknod GID is incorrect: want %d, got %d", stat.GID, got.GID)
}
// TODO(b/194709873): Once listen and accept are implemented, test connecting
// and accepting a connection using sockF.
}
func testGetdents(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
tempDir, _ := mkdir(ctx, t, root, "tempDir")
defer closeFD(ctx, t, tempDir)
defer unlinkFile(ctx, t, root, "tempDir", true /* isDir */)
// Create 10 files in tempDir.
n := 10
fileStats := make(map[string]linux.Statx)
for i := 0; i < n; i++ {
name := fmt.Sprintf("file-%d", i)
newFile, fileStat := mknod(ctx, t, tempDir, name)
defer closeFD(ctx, t, newFile)
defer unlinkFile(ctx, t, tempDir, name, false /* isDir */)
fileStats[name] = fileStat
}
// Use opened directory FD for getdents.
openDirFile, dirHostFD := openFile(ctx, t, tempDir, unix.O_RDONLY, false /* isReg */)
unix.Close(dirHostFD)
defer closeFD(ctx, t, openDirFile)
dirents := make([]lisafs.Dirent64, 0, n)
for i := 0; i < n+2; i++ {
gotDirents, err := openDirFile.Getdents64(ctx, 40)
if err != nil {
t.Fatalf("getdents failed: %v", err)
}
if len(gotDirents) == 0 {
break
}
for _, dirent := range gotDirents {
if dirent.Name != "." && dirent.Name != ".." {
dirents = append(dirents, dirent)
}
}
}
if len(dirents) != n {
t.Errorf("got incorrect number of dirents: wanted %d, got %d", n, len(dirents))
}
for _, dirent := range dirents {
stat, ok := fileStats[string(dirent.Name)]
if !ok {
t.Errorf("received a dirent that was not created: %+v", dirent)
continue
}
if dirent.Type != unix.DT_REG {
t.Errorf("dirent type of %s is incorrect: %d", dirent.Name, dirent.Type)
}
if uint64(dirent.Ino) != stat.Ino {
t.Errorf("dirent ino of %s is incorrect: want %d, got %d", dirent.Name, stat.Ino, dirent.Ino)
}
if uint32(dirent.DevMinor) != stat.DevMinor {
t.Errorf("dirent dev minor of %s is incorrect: want %d, got %d", dirent.Name, stat.DevMinor, dirent.DevMinor)
}
if uint32(dirent.DevMajor) != stat.DevMajor {
t.Errorf("dirent dev major of %s is incorrect: want %d, got %d", dirent.Name, stat.DevMajor, dirent.DevMajor)
}
}
}