gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/lisafs/testsuite/testsuite.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package testsuite provides a integration testing suite for lisafs.
    16  // These tests are intended for servers serving the local filesystem.
    17  package testsuite
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"math/rand"
    24  	"os"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/syndtr/gocapability/capability"
    29  	"golang.org/x/sys/unix"
    30  	"gvisor.dev/gvisor/pkg/abi/linux"
    31  	"gvisor.dev/gvisor/pkg/context"
    32  	"gvisor.dev/gvisor/pkg/lisafs"
    33  	"gvisor.dev/gvisor/pkg/refs"
    34  	"gvisor.dev/gvisor/pkg/unet"
    35  )
    36  
    37  // Tester is the client code using this test suite. This interface abstracts
    38  // away all the caller specific details.
    39  type Tester interface {
    40  	// NewServer returns a new instance of the tester server.
    41  	NewServer(t *testing.T) *lisafs.Server
    42  
    43  	// LinkSupported returns true if the backing server supports LinkAt.
    44  	LinkSupported() bool
    45  
    46  	// SetUserGroupIDSupported returns true if the backing server supports
    47  	// changing UID/GID for files.
    48  	SetUserGroupIDSupported() bool
    49  
    50  	// BindSupported returns true if the backing server supports BindAt.
    51  	BindSupported() bool
    52  }
    53  
    54  // RunAllLocalFSTests runs all local FS tests as subtests.
    55  func RunAllLocalFSTests(t *testing.T, tester Tester) {
    56  	for name, testFn := range localFSTests {
    57  		mountPath, err := ioutil.TempDir(os.Getenv("TEST_TMPDIR"), "")
    58  		if err != nil {
    59  			t.Fatalf("creation of temporary mountpoint failed: %v", err)
    60  		}
    61  		RunTest(t, tester, name, testFn, mountPath)
    62  		os.RemoveAll(mountPath)
    63  	}
    64  }
    65  
    66  // TestFunc describes the signature of a test method.
    67  type TestFunc func(context.Context, *testing.T, Tester, lisafs.ClientFD)
    68  
    69  var localFSTests = map[string]TestFunc{
    70  	"Stat":            testStat,
    71  	"RegularFileIO":   testRegularFileIO,
    72  	"RegularFileOpen": testRegularFileOpen,
    73  	"SetStat":         testSetStat,
    74  	"Allocate":        testAllocate,
    75  	"StatFS":          testStatFS,
    76  	"Unlink":          testUnlink,
    77  	"Symlink":         testSymlink,
    78  	"HardLink":        testHardLink,
    79  	"Walk":            testWalk,
    80  	"Rename":          testRename,
    81  	"Mknod":           testMknod,
    82  	"UDS":             testUDS,
    83  	"Getdents":        testGetdents,
    84  }
    85  
    86  // RunTest runs the passed test function as a subtest.
    87  func RunTest(t *testing.T, tester Tester, testName string, testFn TestFunc, mountPath string) {
    88  	refs.SetLeakMode(refs.LeaksPanic)
    89  	// server should run with a umask of 0, because we want to preserve file
    90  	// modes exactly for testing purposes.
    91  	unix.Umask(0)
    92  
    93  	serverSocket, clientSocket, err := unet.SocketPair(false)
    94  	if err != nil {
    95  		t.Fatalf("socketpair got err %v expected nil", err)
    96  	}
    97  
    98  	server := tester.NewServer(t)
    99  	conn, err := server.CreateConnection(serverSocket, mountPath, false /* readonly */)
   100  	if err != nil {
   101  		t.Fatalf("starting connection failed: %v", err)
   102  		return
   103  	}
   104  	server.StartConnection(conn)
   105  
   106  	c, root, _, err := lisafs.NewClient(clientSocket)
   107  	if err != nil {
   108  		t.Fatalf("client creation failed: %v", err)
   109  	}
   110  	if err := c.StartChannels(); err != nil {
   111  		t.Fatalf("failed to start channels: %v", err)
   112  	}
   113  
   114  	if !root.ControlFD.Ok() {
   115  		t.Fatalf("root control FD is not valid")
   116  	}
   117  	rootFile := c.NewFD(root.ControlFD)
   118  
   119  	ctx := context.Background()
   120  	t.Run(testName, func(t *testing.T) {
   121  		testFn(ctx, t, tester, rootFile)
   122  	})
   123  	closeFD(ctx, t, rootFile)
   124  
   125  	// Release server resources and check for leaks. Note that leak check must
   126  	// happen before c.Close() because server cleans up resources on shutdown.
   127  	server.Destroy()
   128  	refs.DoRepeatedLeakCheck()
   129  
   130  	c.Close() // This should trigger client and server shutdown.
   131  	server.Wait()
   132  }
   133  
   134  func closeFD(ctx context.Context, t testing.TB, fdLisa lisafs.ClientFD) {
   135  	fdLisa.Close(ctx, true /* flush */)
   136  }
   137  
   138  func statTo(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, stat *linux.Statx) {
   139  	if err := fdLisa.StatTo(ctx, stat); err != nil {
   140  		t.Fatalf("stat failed: %v", err)
   141  	}
   142  }
   143  
   144  func openCreateFile(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, name string) (lisafs.ClientFD, linux.Statx, lisafs.ClientFD, int) {
   145  	child, childFD, childHostFD, err := fdLisa.OpenCreateAt(ctx, name, unix.O_RDWR, 0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
   146  	if err != nil {
   147  		t.Fatalf("OpenCreateAt failed: %v", err)
   148  	}
   149  	if childHostFD == -1 {
   150  		t.Error("no host FD donated")
   151  	}
   152  	client := fdLisa.Client()
   153  	return client.NewFD(child.ControlFD), child.Stat, fdLisa.Client().NewFD(childFD), childHostFD
   154  }
   155  
   156  func openFile(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, flags uint32, isReg bool) (lisafs.ClientFD, int) {
   157  	openFD, hostFD, err := fdLisa.OpenAt(ctx, flags)
   158  	if err != nil {
   159  		t.Fatalf("OpenAt failed: %v", err)
   160  	}
   161  	if hostFD == -1 && isReg {
   162  		t.Error("no host FD donated")
   163  	}
   164  	return fdLisa.Client().NewFD(openFD), hostFD
   165  }
   166  
   167  func unlinkFile(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string, isDir bool) {
   168  	var flags uint32
   169  	if isDir {
   170  		flags = unix.AT_REMOVEDIR
   171  	}
   172  	if err := dir.UnlinkAt(ctx, name, flags); err != nil {
   173  		t.Errorf("unlinking file %s failed: %v", name, err)
   174  	}
   175  }
   176  
   177  func symlink(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name, target string) (lisafs.ClientFD, linux.Statx) {
   178  	linkIno, err := dir.SymlinkAt(ctx, name, target, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
   179  	if err != nil {
   180  		t.Fatalf("symlink failed: %v", err)
   181  	}
   182  	return dir.Client().NewFD(linkIno.ControlFD), linkIno.Stat
   183  }
   184  
   185  func link(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string, target lisafs.ClientFD) (lisafs.ClientFD, linux.Statx) {
   186  	linkIno, err := dir.LinkAt(ctx, target.ID(), name)
   187  	if err != nil {
   188  		t.Fatalf("link failed: %v", err)
   189  	}
   190  	return dir.Client().NewFD(linkIno.ControlFD), linkIno.Stat
   191  }
   192  
   193  func mkdir(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string) (lisafs.ClientFD, linux.Statx) {
   194  	childIno, err := dir.MkdirAt(ctx, name, 0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
   195  	if err != nil {
   196  		t.Fatalf("mkdir failed: %v", err)
   197  	}
   198  	return dir.Client().NewFD(childIno.ControlFD), childIno.Stat
   199  }
   200  
   201  func mknod(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string) (lisafs.ClientFD, linux.Statx) {
   202  	nodeIno, err := dir.MknodAt(ctx, name, unix.S_IFREG|0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()), 0, 0)
   203  	if err != nil {
   204  		t.Fatalf("mknod failed: %v", err)
   205  	}
   206  	return dir.Client().NewFD(nodeIno.ControlFD), nodeIno.Stat
   207  }
   208  
   209  func bind(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string, sockType linux.SockType) (lisafs.ClientFD, *lisafs.ClientBoundSocketFD, linux.Statx) {
   210  	nodeIno, socket, err := dir.BindAt(ctx, sockType, name, 0777, lisafs.UID(unix.Getuid()), lisafs.GID(unix.Getgid()))
   211  	if err != nil {
   212  		t.Fatalf("bind failed: %v", err)
   213  	}
   214  	return dir.Client().NewFD(nodeIno.ControlFD), socket, nodeIno.Stat
   215  }
   216  
   217  func walk(ctx context.Context, t *testing.T, dir lisafs.ClientFD, names []string) []lisafs.Inode {
   218  	_, inodes, err := dir.WalkMultiple(ctx, names)
   219  	if err != nil {
   220  		t.Fatalf("walk failed while trying to walk components %+v: %v", names, err)
   221  	}
   222  	return inodes
   223  }
   224  
   225  func walkStat(ctx context.Context, t *testing.T, dir lisafs.ClientFD, names []string) []linux.Statx {
   226  	stats, err := dir.WalkStat(ctx, names)
   227  	if err != nil {
   228  		t.Fatalf("walk failed while trying to walk components %+v: %v", names, err)
   229  	}
   230  	return stats
   231  }
   232  
   233  func writeFD(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, off uint64, buf []byte) error {
   234  	count, err := fdLisa.Write(ctx, buf, off)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	if int(count) != len(buf) {
   239  		t.Errorf("partial write: buf size = %d, written = %d", len(buf), count)
   240  	}
   241  	return nil
   242  }
   243  
   244  func readFDAndCmp(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, off uint64, want []byte) {
   245  	buf := make([]byte, len(want))
   246  	n, err := fdLisa.Read(ctx, buf, off)
   247  	if err != nil {
   248  		t.Errorf("read failed: %v", err)
   249  		return
   250  	}
   251  	if int(n) != len(want) {
   252  		t.Errorf("partial read: buf size = %d, read = %d", len(want), n)
   253  		return
   254  	}
   255  	if bytes.Compare(buf, want) != 0 {
   256  		t.Errorf("bytes read differ from what was expected: want = %v, got = %v", want, buf)
   257  	}
   258  }
   259  
   260  func allocateAndVerify(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, off uint64, length uint64) {
   261  	if err := fdLisa.Allocate(ctx, 0, off, length); err != nil {
   262  		t.Fatalf("fallocate failed: %v", err)
   263  	}
   264  
   265  	var stat linux.Statx
   266  	statTo(ctx, t, fdLisa, &stat)
   267  	if want := off + length; stat.Size != want {
   268  		t.Errorf("incorrect file size after allocate: expected %d, got %d", off+length, stat.Size)
   269  	}
   270  }
   271  
   272  func cmpStatx(t *testing.T, want, got linux.Statx) {
   273  	if got.Mask&unix.STATX_MODE != 0 && want.Mask&unix.STATX_MODE != 0 {
   274  		if got.Mode != want.Mode {
   275  			t.Errorf("mode differs: want %d, got %d", want.Mode, got.Mode)
   276  		}
   277  	}
   278  	if got.Mask&unix.STATX_INO != 0 && want.Mask&unix.STATX_INO != 0 {
   279  		if got.Ino != want.Ino {
   280  			t.Errorf("inode number differs: want %d, got %d", want.Ino, got.Ino)
   281  		}
   282  	}
   283  	if got.Mask&unix.STATX_NLINK != 0 && want.Mask&unix.STATX_NLINK != 0 {
   284  		if got.Nlink != want.Nlink {
   285  			t.Errorf("nlink differs: want %d, got %d", want.Nlink, got.Nlink)
   286  		}
   287  	}
   288  	if got.Mask&unix.STATX_UID != 0 && want.Mask&unix.STATX_UID != 0 {
   289  		if got.UID != want.UID {
   290  			t.Errorf("UID differs: want %d, got %d", want.UID, got.UID)
   291  		}
   292  	}
   293  	if got.Mask&unix.STATX_GID != 0 && want.Mask&unix.STATX_GID != 0 {
   294  		if got.GID != want.GID {
   295  			t.Errorf("GID differs: want %d, got %d", want.GID, got.GID)
   296  		}
   297  	}
   298  	if got.Mask&unix.STATX_SIZE != 0 && want.Mask&unix.STATX_SIZE != 0 {
   299  		if got.Size != want.Size {
   300  			t.Errorf("size differs: want %d, got %d", want.Size, got.Size)
   301  		}
   302  	}
   303  	if got.Mask&unix.STATX_BLOCKS != 0 && want.Mask&unix.STATX_BLOCKS != 0 {
   304  		if got.Blocks != want.Blocks {
   305  			t.Errorf("blocks differs: want %d, got %d", want.Blocks, got.Blocks)
   306  		}
   307  	}
   308  	if got.Mask&unix.STATX_ATIME != 0 && want.Mask&unix.STATX_ATIME != 0 {
   309  		if got.Atime != want.Atime {
   310  			t.Errorf("atime differs: want %d, got %d", want.Atime, got.Atime)
   311  		}
   312  	}
   313  	if got.Mask&unix.STATX_MTIME != 0 && want.Mask&unix.STATX_MTIME != 0 {
   314  		if got.Mtime != want.Mtime {
   315  			t.Errorf("mtime differs: want %d, got %d", want.Mtime, got.Mtime)
   316  		}
   317  	}
   318  	if got.Mask&unix.STATX_CTIME != 0 && want.Mask&unix.STATX_CTIME != 0 {
   319  		if got.Ctime != want.Ctime {
   320  			t.Errorf("ctime differs: want %d, got %d", want.Ctime, got.Ctime)
   321  		}
   322  	}
   323  }
   324  
   325  func hasCapability(c capability.Cap) bool {
   326  	caps, err := capability.NewPid2(os.Getpid())
   327  	if err != nil {
   328  		return false
   329  	}
   330  	if err := caps.Load(); err != nil {
   331  		return false
   332  	}
   333  	return caps.Get(capability.EFFECTIVE, c)
   334  }
   335  
   336  func testStat(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   337  	var rootStat linux.Statx
   338  	if err := root.StatTo(ctx, &rootStat); err != nil {
   339  		t.Errorf("stat on the root dir failed: %v", err)
   340  	}
   341  
   342  	if ftype := rootStat.Mode & unix.S_IFMT; ftype != unix.S_IFDIR {
   343  		t.Errorf("root inode is not a directory, file type = %d", ftype)
   344  	}
   345  }
   346  
   347  func testRegularFileIO(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   348  	name := "tempFile"
   349  	controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
   350  	defer closeFD(ctx, t, controlFile)
   351  	defer closeFD(ctx, t, fd)
   352  	defer unix.Close(hostFD)
   353  
   354  	// Test Read/Write RPCs with 2MB of data to test IO in chunks.
   355  	data := make([]byte, 1<<21)
   356  	rand.Read(data)
   357  	if err := writeFD(ctx, t, fd, 0, data); err != nil {
   358  		t.Fatalf("write failed: %v", err)
   359  	}
   360  	readFDAndCmp(ctx, t, fd, 0, data)
   361  	readFDAndCmp(ctx, t, fd, 50, data[50:])
   362  
   363  	// Make sure the host FD is configured properly.
   364  	hostReadData := make([]byte, len(data))
   365  	if n, err := unix.Pread(hostFD, hostReadData, 0); err != nil {
   366  		t.Errorf("host read failed: %v", err)
   367  	} else if n != len(hostReadData) {
   368  		t.Errorf("partial read: buf size = %d, read = %d", len(hostReadData), n)
   369  	} else if bytes.Compare(hostReadData, data) != 0 {
   370  		t.Errorf("bytes read differ from what was expected: want = %v, got = %v", data, hostReadData)
   371  	}
   372  
   373  	// Test syncing the writable FD.
   374  	if err := fd.Sync(ctx); err != nil {
   375  		t.Errorf("syncing the FD failed: %v", err)
   376  	}
   377  }
   378  
   379  func testRegularFileOpen(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   380  	name := "tempFile"
   381  	controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
   382  	defer closeFD(ctx, t, controlFile)
   383  	defer closeFD(ctx, t, fd)
   384  	defer unix.Close(hostFD)
   385  
   386  	// Open a readonly FD and try writing to it to get an EBADF.
   387  	roFile, roHostFD := openFile(ctx, t, controlFile, unix.O_RDONLY, true /* isReg */)
   388  	defer closeFD(ctx, t, roFile)
   389  	defer unix.Close(roHostFD)
   390  	if err := writeFD(ctx, t, roFile, 0, []byte{1, 2, 3}); err != unix.EBADF {
   391  		t.Errorf("writing to read only FD should generate EBADF, but got %v", err)
   392  	}
   393  }
   394  
   395  func testSetStat(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   396  	name := "tempFile"
   397  	controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
   398  	defer closeFD(ctx, t, controlFile)
   399  	defer closeFD(ctx, t, fd)
   400  	defer unix.Close(hostFD)
   401  
   402  	now := time.Now()
   403  	wantStat := linux.Statx{
   404  		Mask:  unix.STATX_MODE | unix.STATX_ATIME | unix.STATX_MTIME | unix.STATX_SIZE,
   405  		Mode:  0760,
   406  		UID:   uint32(unix.Getuid()),
   407  		GID:   uint32(unix.Getgid()),
   408  		Size:  50,
   409  		Atime: linux.NsecToStatxTimestamp(now.UnixNano()),
   410  		Mtime: linux.NsecToStatxTimestamp(now.UnixNano()),
   411  	}
   412  	if tester.SetUserGroupIDSupported() {
   413  		wantStat.Mask |= unix.STATX_UID | unix.STATX_GID
   414  	}
   415  	failureMask, failureErr, err := controlFile.SetStat(ctx, &wantStat)
   416  	if err != nil {
   417  		t.Fatalf("setstat failed: %v", err)
   418  	}
   419  	if failureMask != 0 {
   420  		t.Fatalf("some setstat operations failed: failureMask = %#b, failureErr = %v", failureMask, failureErr)
   421  	}
   422  
   423  	// Verify that attributes were updated.
   424  	var gotStat linux.Statx
   425  	statTo(ctx, t, controlFile, &gotStat)
   426  	if gotStat.Mode&07777 != wantStat.Mode ||
   427  		gotStat.Size != wantStat.Size ||
   428  		gotStat.Atime.ToNsec() != wantStat.Atime.ToNsec() ||
   429  		gotStat.Mtime.ToNsec() != wantStat.Mtime.ToNsec() ||
   430  		(tester.SetUserGroupIDSupported() && (uint32(gotStat.UID) != wantStat.UID || uint32(gotStat.GID) != wantStat.GID)) {
   431  		t.Errorf("setStat did not update file correctly: setStat = %+v, stat = %+v", wantStat, gotStat)
   432  	}
   433  }
   434  
   435  func testAllocate(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   436  	name := "tempFile"
   437  	controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
   438  	defer closeFD(ctx, t, controlFile)
   439  	defer closeFD(ctx, t, fd)
   440  	defer unix.Close(hostFD)
   441  
   442  	allocateAndVerify(ctx, t, fd, 0, 40)
   443  	allocateAndVerify(ctx, t, fd, 20, 100)
   444  }
   445  
   446  func testStatFS(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   447  	var statFS lisafs.StatFS
   448  	if err := root.StatFSTo(ctx, &statFS); err != nil {
   449  		t.Errorf("statfs failed: %v", err)
   450  	}
   451  }
   452  
   453  func testUnlink(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   454  	name := "tempFile"
   455  	controlFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
   456  	defer closeFD(ctx, t, controlFile)
   457  	defer closeFD(ctx, t, fd)
   458  	defer unix.Close(hostFD)
   459  
   460  	unlinkFile(ctx, t, root, name, false /* isDir */)
   461  	if inodes := walk(ctx, t, root, []string{name}); len(inodes) > 0 {
   462  		t.Errorf("deleted file should not be generating inodes on walk: %+v", inodes)
   463  	}
   464  }
   465  
   466  func testSymlink(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   467  	target := "/tmp/some/path"
   468  	name := "symlinkFile"
   469  	link, linkStat := symlink(ctx, t, root, name, target)
   470  	defer closeFD(ctx, t, link)
   471  
   472  	if linkStat.Mode&unix.S_IFMT != unix.S_IFLNK {
   473  		t.Errorf("stat return from symlink RPC indicates that the inode is not a symlink: mode = %d", linkStat.Mode)
   474  	}
   475  
   476  	if gotTarget, err := link.ReadLinkAt(ctx); err != nil {
   477  		t.Fatalf("readlink failed: %v", err)
   478  	} else if gotTarget != target {
   479  		t.Errorf("readlink return incorrect target: expected %q, got %q", target, gotTarget)
   480  	}
   481  }
   482  
   483  func testHardLink(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   484  	if !tester.LinkSupported() {
   485  		t.Skipf("server does not support LinkAt RPC")
   486  	}
   487  	if !hasCapability(capability.CAP_DAC_READ_SEARCH) {
   488  		t.Skipf("TestHardLink requires CAP_DAC_READ_SEARCH, running as %d", unix.Getuid())
   489  	}
   490  	name := "tempFile"
   491  	controlFile, fileIno, fd, hostFD := openCreateFile(ctx, t, root, name)
   492  	defer closeFD(ctx, t, controlFile)
   493  	defer closeFD(ctx, t, fd)
   494  	defer unix.Close(hostFD)
   495  
   496  	linkName := "linkFile"
   497  	link, linkStat := link(ctx, t, root, linkName, controlFile)
   498  	defer closeFD(ctx, t, link)
   499  
   500  	if linkStat.Ino != fileIno.Ino {
   501  		t.Errorf("hard linked files have different inode numbers: %d %d", linkStat.Ino, fileIno.Ino)
   502  	}
   503  	if linkStat.DevMinor != fileIno.DevMinor {
   504  		t.Errorf("hard linked files have different minor device numbers: %d %d", linkStat.DevMinor, fileIno.DevMinor)
   505  	}
   506  	if linkStat.DevMajor != fileIno.DevMajor {
   507  		t.Errorf("hard linked files have different major device numbers: %d %d", linkStat.DevMajor, fileIno.DevMajor)
   508  	}
   509  }
   510  
   511  func testWalk(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   512  	// Create 10 nested directories.
   513  	n := 10
   514  	curDir := root
   515  
   516  	dirNames := make([]string, 0, n)
   517  	for i := 0; i < n; i++ {
   518  		name := fmt.Sprintf("tmpdir-%d", i)
   519  		childDir, _ := mkdir(ctx, t, curDir, name)
   520  		defer closeFD(ctx, t, childDir)
   521  		defer unlinkFile(ctx, t, curDir, name, true /* isDir */)
   522  
   523  		curDir = childDir
   524  		dirNames = append(dirNames, name)
   525  	}
   526  
   527  	// Walk all these directories. Add some junk at the end which should not be
   528  	// walked on.
   529  	dirNames = append(dirNames, []string{"a", "b", "c"}...)
   530  	inodes := walk(ctx, t, root, dirNames)
   531  	if len(inodes) != n {
   532  		t.Errorf("walk returned the incorrect number of inodes: wanted %d, got %d", n, len(inodes))
   533  	}
   534  
   535  	// Close all control FDs and collect stat results for all dirs including
   536  	// the root directory.
   537  	dirStats := make([]linux.Statx, 0, n+1)
   538  	var stat linux.Statx
   539  	statTo(ctx, t, root, &stat)
   540  	dirStats = append(dirStats, stat)
   541  	for _, inode := range inodes {
   542  		dirStats = append(dirStats, inode.Stat)
   543  		closeFD(ctx, t, root.Client().NewFD(inode.ControlFD))
   544  	}
   545  
   546  	// Test WalkStat which additionally returns Statx for root because the first
   547  	// path component is "".
   548  	dirNames = append([]string{""}, dirNames...)
   549  	gotStats := walkStat(ctx, t, root, dirNames)
   550  	if len(gotStats) != len(dirStats) {
   551  		t.Errorf("walkStat returned the incorrect number of statx: wanted %d, got %d", len(dirStats), len(gotStats))
   552  	} else {
   553  		for i := range gotStats {
   554  			cmpStatx(t, dirStats[i], gotStats[i])
   555  		}
   556  	}
   557  }
   558  
   559  func testRename(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   560  	name := "tempFile"
   561  	tempFile, _, fd, hostFD := openCreateFile(ctx, t, root, name)
   562  	defer closeFD(ctx, t, tempFile)
   563  	defer closeFD(ctx, t, fd)
   564  	defer unix.Close(hostFD)
   565  
   566  	tempDir, _ := mkdir(ctx, t, root, "tempDir")
   567  	defer closeFD(ctx, t, tempDir)
   568  
   569  	// Move tempFile into tempDir.
   570  	if err := root.RenameAt(ctx, name, tempDir.ID(), "movedFile"); err != nil {
   571  		t.Fatalf("rename failed: %v", err)
   572  	}
   573  
   574  	inodes := walkStat(ctx, t, root, []string{"tempDir", "movedFile"})
   575  	if len(inodes) != 2 {
   576  		t.Errorf("expected 2 files on walk but only found %d", len(inodes))
   577  	}
   578  }
   579  
   580  func testMknod(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   581  	name := "regular-file"
   582  	pipeFile, pipeStat := mknod(ctx, t, root, name)
   583  	defer closeFD(ctx, t, pipeFile)
   584  
   585  	if got := pipeStat.Mode & unix.S_IFMT; got != unix.S_IFREG {
   586  		t.Errorf("socket file mode is incorrect: want %#x, got %#x", unix.S_IFSOCK, got)
   587  	}
   588  	if tester.SetUserGroupIDSupported() {
   589  		if want := unix.Getuid(); int(pipeStat.UID) != want {
   590  			t.Errorf("socket file uid is incorrect: want %d, got %d", want, pipeStat.UID)
   591  		}
   592  		if want := unix.Getgid(); int(pipeStat.GID) != want {
   593  			t.Errorf("socket file gid is incorrect: want %d, got %d", want, pipeStat.GID)
   594  		}
   595  	}
   596  
   597  	var stat linux.Statx
   598  	statTo(ctx, t, pipeFile, &stat)
   599  
   600  	if stat.Mode != pipeStat.Mode {
   601  		t.Errorf("mknod mode is incorrect: want %d, got %d", pipeStat.Mode, stat.Mode)
   602  	}
   603  	if stat.UID != pipeStat.UID {
   604  		t.Errorf("mknod UID is incorrect: want %d, got %d", pipeStat.UID, stat.UID)
   605  	}
   606  	if stat.GID != pipeStat.GID {
   607  		t.Errorf("mknod GID is incorrect: want %d, got %d", pipeStat.GID, stat.GID)
   608  	}
   609  }
   610  
   611  func testUDS(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   612  	if !tester.BindSupported() {
   613  		t.Skipf("server does not support BindAt RPC")
   614  	}
   615  	const name = "sock"
   616  	file, socket, stat := bind(ctx, t, root, name, unix.SOCK_STREAM)
   617  	defer closeFD(ctx, t, file)
   618  	defer socket.Close(ctx)
   619  
   620  	if got := stat.Mode & unix.S_IFMT; got != unix.S_IFSOCK {
   621  		t.Errorf("socket file mode is incorrect: want %#x, got %#x", unix.S_IFSOCK, got)
   622  	}
   623  	if tester.SetUserGroupIDSupported() {
   624  		if want := unix.Getuid(); int(stat.UID) != want {
   625  			t.Errorf("socket file uid is incorrect: want %d, got %d", want, stat.UID)
   626  		}
   627  		if want := unix.Getgid(); int(stat.GID) != want {
   628  			t.Errorf("socket file gid is incorrect: want %d, got %d", want, stat.GID)
   629  		}
   630  	}
   631  
   632  	var got linux.Statx
   633  	statTo(ctx, t, file, &got)
   634  	if stat.Mode != got.Mode {
   635  		t.Errorf("UDS mode is incorrect: want %d, got %d", stat.Mode, got.Mode)
   636  	}
   637  	if stat.UID != got.UID {
   638  		t.Errorf("mknod UID is incorrect: want %d, got %d", stat.UID, got.UID)
   639  	}
   640  	if stat.GID != got.GID {
   641  		t.Errorf("mknod GID is incorrect: want %d, got %d", stat.GID, got.GID)
   642  	}
   643  
   644  	// TODO(b/194709873): Once listen and accept are implemented, test connecting
   645  	// and accepting a connection using sockF.
   646  }
   647  
   648  func testGetdents(ctx context.Context, t *testing.T, tester Tester, root lisafs.ClientFD) {
   649  	tempDir, _ := mkdir(ctx, t, root, "tempDir")
   650  	defer closeFD(ctx, t, tempDir)
   651  	defer unlinkFile(ctx, t, root, "tempDir", true /* isDir */)
   652  
   653  	// Create 10 files in tempDir.
   654  	n := 10
   655  	fileStats := make(map[string]linux.Statx)
   656  	for i := 0; i < n; i++ {
   657  		name := fmt.Sprintf("file-%d", i)
   658  		newFile, fileStat := mknod(ctx, t, tempDir, name)
   659  		defer closeFD(ctx, t, newFile)
   660  		defer unlinkFile(ctx, t, tempDir, name, false /* isDir */)
   661  
   662  		fileStats[name] = fileStat
   663  	}
   664  
   665  	// Use opened directory FD for getdents.
   666  	openDirFile, dirHostFD := openFile(ctx, t, tempDir, unix.O_RDONLY, false /* isReg */)
   667  	unix.Close(dirHostFD)
   668  	defer closeFD(ctx, t, openDirFile)
   669  
   670  	dirents := make([]lisafs.Dirent64, 0, n)
   671  	for i := 0; i < n+2; i++ {
   672  		gotDirents, err := openDirFile.Getdents64(ctx, 40)
   673  		if err != nil {
   674  			t.Fatalf("getdents failed: %v", err)
   675  		}
   676  		if len(gotDirents) == 0 {
   677  			break
   678  		}
   679  		for _, dirent := range gotDirents {
   680  			if dirent.Name != "." && dirent.Name != ".." {
   681  				dirents = append(dirents, dirent)
   682  			}
   683  		}
   684  	}
   685  
   686  	if len(dirents) != n {
   687  		t.Errorf("got incorrect number of dirents: wanted %d, got %d", n, len(dirents))
   688  	}
   689  	for _, dirent := range dirents {
   690  		stat, ok := fileStats[string(dirent.Name)]
   691  		if !ok {
   692  			t.Errorf("received a dirent that was not created: %+v", dirent)
   693  			continue
   694  		}
   695  
   696  		if dirent.Type != unix.DT_REG {
   697  			t.Errorf("dirent type of %s is incorrect: %d", dirent.Name, dirent.Type)
   698  		}
   699  		if uint64(dirent.Ino) != stat.Ino {
   700  			t.Errorf("dirent ino of %s is incorrect: want %d, got %d", dirent.Name, stat.Ino, dirent.Ino)
   701  		}
   702  		if uint32(dirent.DevMinor) != stat.DevMinor {
   703  			t.Errorf("dirent dev minor of %s is incorrect: want %d, got %d", dirent.Name, stat.DevMinor, dirent.DevMinor)
   704  		}
   705  		if uint32(dirent.DevMajor) != stat.DevMajor {
   706  			t.Errorf("dirent dev major of %s is incorrect: want %d, got %d", dirent.Name, stat.DevMajor, dirent.DevMajor)
   707  		}
   708  	}
   709  }