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 }