github.com/aristanetworks/quantumfs@v0.21.1-0.20190207191202-4636946c38db/cmd/qfs/QfsChroot_test.go (about) 1 // Copyright (c) 2016 Arista Networks, Inc. 2 // Use of this source code is governed by the Apache License 2.0 3 // that can be found in the COPYING file. 4 5 // tests of qfs chroot tool 6 package main 7 8 import ( 9 "fmt" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "runtime" 14 "strings" 15 "syscall" 16 "testing" 17 "time" 18 19 "github.com/aristanetworks/quantumfs/testutils" 20 "github.com/aristanetworks/quantumfs/utils" 21 ) 22 23 var commandsInUsrBin = []string{ 24 umount, 25 "/usr/bin/setarch", 26 sh, 27 "/usr/bin/bash", 28 "/usr/bin/ls", 29 } 30 31 var libsToCopy map[string]bool 32 33 var testqfs string 34 35 func init() { 36 testqfs = os.Getenv("GOPATH") + "/bin/qfs" 37 38 libsToCopy = make(map[string]bool) 39 libsToCopy["/usr/lib64/ld-linux-x86-64.so.2"] = true 40 41 for _, binary := range commandsInUsrBin { 42 ldd := exec.Command("ldd", binary) 43 output, err := ldd.CombinedOutput() 44 if err != nil { 45 fmt.Printf("Failed to get libraries for binary %s: %v\n", 46 binary, err) 47 continue 48 } 49 50 lines := strings.Split(string(output), "\n") 51 52 for _, line := range lines { 53 if !strings.Contains(line, "=>") || 54 !strings.Contains(line, "/lib") { 55 56 // This line doesn't contain a library we can copy 57 continue 58 } 59 60 tokens := strings.Split(line, " ") 61 library := tokens[2] 62 libsToCopy[library] = true 63 } 64 } 65 } 66 67 // A helper function to run command which gives better error information 68 func runCommand(name string, args ...string) error { 69 cmd := exec.Command(name, args...) 70 71 if buf, err := cmd.CombinedOutput(); err != nil { 72 return fmt.Errorf("Error in runCommand: %s\n"+ 73 "Command: %s %v\n Output: %s", 74 err.Error(), name, args, string(buf)) 75 } 76 77 return nil 78 } 79 80 func runQfsChroot(wsr string, dir string, filename string) error { 81 return runCommand(testqfs, "chroot", "arastra", wsr, dir, "ls", filename) 82 } 83 84 // setup a minimal workspace 85 func setupWorkspace(t *testing.T) string { 86 dirTest := testutils.SetupTestspace("TestChroot") 87 88 dirUsrBin := dirTest + "/usr/bin" 89 if err := utils.MkdirAll(dirUsrBin, 0777); err != nil { 90 t.Fatalf("Creating directory %s error: %s", dirUsrBin, 91 err.Error()) 92 } 93 94 for _, command := range commandsInUsrBin { 95 if err := runCommand("cp", command, dirUsrBin); err != nil { 96 t.Fatal(err.Error()) 97 } 98 } 99 100 dirUsrSbin := dirTest + "/usr/sbin" 101 if err := utils.MkdirAll(dirUsrSbin, 0777); err != nil { 102 t.Fatalf("Creating directory %s error: %s", 103 dirUsrSbin, err.Error()) 104 } 105 106 dirUsrLib64 := dirTest + "/usr/lib64" 107 if err := utils.MkdirAll(dirUsrLib64, 0777); err != nil { 108 t.Fatalf("Creating directory %s error: %s", 109 dirUsrLib64, err.Error()) 110 111 } 112 113 for lib := range libsToCopy { 114 if err := runCommand("cp", lib, dirUsrLib64); err != nil { 115 t.Fatal(err.Error()) 116 } 117 } 118 119 dirBin := dirTest + "/bin" 120 if err := syscall.Symlink("usr/bin", dirBin); err != nil { 121 t.Fatal("Creating symlink usr/bin error: " + err.Error()) 122 } 123 124 dirSbin := dirTest + "/sbin" 125 if err := syscall.Symlink("usr/sbin", dirSbin); err != nil { 126 t.Fatal(err.Error()) 127 } 128 129 dirLib64 := dirTest + "/lib64" 130 if err := syscall.Symlink("usr/lib64", dirLib64); err != nil { 131 t.Fatal(err.Error()) 132 } 133 134 dirUsrShare := dirTest + "/usr/share" 135 if err := utils.MkdirAll(dirUsrShare, 0777); err != nil { 136 t.Fatalf("Creating directory %s error: %s", dirUsrShare, 137 err.Error()) 138 } 139 140 dirUsrMnt := dirTest + "/mnt" 141 if err := syscall.Mkdir(dirUsrMnt, 0777); err != nil { 142 t.Fatalf("Creating directory %s error: %s", dirUsrMnt, 143 err.Error()) 144 } 145 146 dirEtc := dirTest + "/etc" 147 if err := syscall.Mkdir(dirEtc, 0777); err != nil { 148 t.Fatalf("Creating directory %s error: %s", dirEtc, err.Error()) 149 } 150 151 if err := runCommand("cp", "/etc/passwd", dirEtc); err != nil { 152 t.Fatal(err.Error()) 153 } 154 155 dirTmp := dirTest + "/tmp" 156 if err := syscall.Mkdir(dirTmp, 0777); err != nil { 157 t.Fatalf("Creating directory %s error: %s", dirTmp, 158 err.Error()) 159 } 160 161 return dirTest 162 } 163 164 func cleanupWorkspace(workspace string, t *testing.T) { 165 var err error 166 167 for i := 0; i < 10; i++ { 168 if err = os.RemoveAll(workspace); err == nil { 169 break 170 } 171 time.Sleep(50 * time.Millisecond) 172 } 173 174 if err != nil { 175 t.Fatalf("Error cleaning up testing workspace: %s", err.Error()) 176 } 177 } 178 179 // Change the UID/GID the test thread to the given values. Use -1 not to change 180 // either the UID or GID. 181 func setUidGid(uid int, gid int, t *testing.T) { 182 // The quantumfs tests are run as root because some tests require 183 // root privileges. However, root can read or write any file 184 // irrespective of the file permissions. Obviously if we want to 185 // test permissions then we cannot run as root. 186 // 187 // To accomplish this we lock this goroutine to a particular OS 188 // thread, then we change the EUID of that thread to something which 189 // isn't root. Finally at the end we need to restore the EUID of the 190 // thread before unlocking ourselves from that thread. If we do not 191 // follow this precise cleanup order other tests or goroutines may 192 // run using the other UID incorrectly. 193 runtime.LockOSThread() 194 if gid != -1 { 195 err := syscall.Setregid(-1, gid) 196 if err != nil { 197 runtime.UnlockOSThread() 198 t.Fatal(err.Error()) 199 } 200 } 201 202 if uid != -1 { 203 err := syscall.Setreuid(-1, uid) 204 if err != nil { 205 syscall.Setregid(-1, 0) 206 runtime.UnlockOSThread() 207 t.Fatal(err.Error()) 208 } 209 } 210 211 } 212 213 // Set the UID and GID back to the defaults 214 func setUidGidToDefault(t *testing.T) { 215 defer runtime.UnlockOSThread() 216 217 // Test always runs as root, so its euid and egid is 0 218 err1 := syscall.Setreuid(-1, 0) 219 err2 := syscall.Setregid(-1, 0) 220 if err1 != nil { 221 t.Fatal(err1.Error()) 222 } 223 if err2 != nil { 224 t.Fatal(err2.Error()) 225 } 226 } 227 228 // Here we can define several variants of each of the following arguments 229 // <WSR>: 230 // AbsWsr: Absolute path of workspaceroot in the filesystem before chroot 231 // RelWsr: Workspaceroot relative to the directory where chroot is run 232 // <DIR>: 233 // AbsDir: Absolute path of working directory in filesystem after chroot 234 // RelDir: Working directory path relative to workspaceroot 235 // <CMD>: 236 // AbsCmd: Command with absolute path in the filesystem after chroot 237 // RelCmd: Command with path relative to working directory 238 239 func setupNonPersistentChrootTest(t *testing.T, rootTest string) (string, string) { 240 dirTest := "" 241 fileTest := "" 242 243 if dir, err := ioutil.TempDir(rootTest, "ChrootTestDirectory"); err != nil { 244 t.Fatalf("Creating test file error: %s", err.Error()) 245 } else { 246 dirTest = dir 247 } 248 249 if err := os.Chmod(dirTest, 0777); err != nil { 250 t.Fatalf("Changing mode of directory: %s error: %s", 251 dirTest, err.Error()) 252 } 253 254 if fd, err := ioutil.TempFile(dirTest, "ChrootTestFile"); err != nil { 255 t.Fatalf("Creating test file error: %s", err.Error()) 256 } else { 257 fileTest = fd.Name() 258 fd.Close() 259 } 260 261 if err := os.Chmod(fileTest, 0777); err != nil { 262 t.Fatalf("Changing mode of file: %s error: %s", 263 fileTest, err.Error()) 264 } 265 266 return dirTest, fileTest 267 } 268 269 func testNonPersistentChrootAbsWsrAbsDirAbsCmd(t *testing.T, rootTest string) { 270 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 271 272 fileTest = fileTest[len(rootTest):] 273 dirTest = dirTest[len(rootTest):] 274 275 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 276 t.Fatal(err.Error()) 277 } 278 } 279 280 func TestNonPersistentChrootAbsWsrAbsDirAbsCmd(t *testing.T) { 281 func() { 282 rootTest := setupWorkspace(t) 283 defer cleanupWorkspace(rootTest, t) 284 285 testNonPersistentChrootAbsWsrAbsDirAbsCmd(t, rootTest) 286 }() 287 288 func() { 289 rootTest := setupWorkspace(t) 290 291 defer cleanupWorkspace(rootTest, t) 292 293 setUidGid(99, 99, t) 294 defer setUidGidToDefault(t) 295 testNonPersistentChrootAbsWsrAbsDirAbsCmd(t, rootTest) 296 }() 297 } 298 299 func testNonPersistentChrootAbsWsrAbsDirRelCmd(t *testing.T, rootTest string) { 300 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 301 302 fileTest = "." + fileTest[len(dirTest):] 303 dirTest = dirTest[len(rootTest):] 304 305 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 306 t.Fatal(err.Error()) 307 } 308 } 309 310 func TestNonPersistentChrootAbsWsrAbsDirRelCmd(t *testing.T) { 311 func() { 312 rootTest := setupWorkspace(t) 313 defer cleanupWorkspace(rootTest, t) 314 315 testNonPersistentChrootAbsWsrAbsDirRelCmd(t, rootTest) 316 }() 317 318 func() { 319 rootTest := setupWorkspace(t) 320 321 defer cleanupWorkspace(rootTest, t) 322 323 setUidGid(99, 99, t) 324 defer setUidGidToDefault(t) 325 testNonPersistentChrootAbsWsrAbsDirRelCmd(t, rootTest) 326 }() 327 } 328 329 func testNonPersistentChrootRelWsrAbsDirAbsCmd(t *testing.T, rootTest string) { 330 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 331 332 if err := os.Chdir("/"); err != nil { 333 t.Fatalf("Changing to directory / error: %s", 334 err.Error()) 335 } 336 337 fileTest = fileTest[len(rootTest):] 338 dirTest = dirTest[len(rootTest):] 339 rootTest = "." + rootTest 340 341 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 342 t.Fatal(err.Error()) 343 } 344 } 345 346 func TestNonPersistentChrootRelWsrAbsDirAbsCmd(t *testing.T) { 347 func() { 348 rootTest := setupWorkspace(t) 349 defer cleanupWorkspace(rootTest, t) 350 351 testNonPersistentChrootRelWsrAbsDirAbsCmd(t, rootTest) 352 }() 353 354 func() { 355 rootTest := setupWorkspace(t) 356 357 defer cleanupWorkspace(rootTest, t) 358 359 setUidGid(99, 99, t) 360 defer setUidGidToDefault(t) 361 testNonPersistentChrootRelWsrAbsDirAbsCmd(t, rootTest) 362 }() 363 } 364 365 func testNonPersistentChrootRelWsrAbsDirRelCmd(t *testing.T, rootTest string) { 366 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 367 368 if err := os.Chdir("/"); err != nil { 369 t.Fatalf("Changing to directory / error: %s", 370 err.Error()) 371 } 372 373 fileTest = "." + fileTest[len(dirTest):] 374 dirTest = dirTest[len(rootTest):] 375 rootTest = "." + rootTest 376 377 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 378 t.Fatal(err.Error()) 379 } 380 } 381 382 func TestNonPersistentChrootRelWsrAbsDirRelCmd(t *testing.T) { 383 func() { 384 rootTest := setupWorkspace(t) 385 defer cleanupWorkspace(rootTest, t) 386 387 testNonPersistentChrootRelWsrAbsDirRelCmd(t, rootTest) 388 }() 389 390 func() { 391 rootTest := setupWorkspace(t) 392 393 defer cleanupWorkspace(rootTest, t) 394 395 setUidGid(99, 99, t) 396 defer setUidGidToDefault(t) 397 testNonPersistentChrootRelWsrAbsDirRelCmd(t, rootTest) 398 }() 399 } 400 401 func testNonPersistentChrootAbsWsrRelDirAbsCmd(t *testing.T, rootTest string) { 402 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 403 404 fileTest = fileTest[len(rootTest):] 405 dirTest = dirTest[len(rootTest)+1:] 406 407 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 408 t.Fatal(err.Error()) 409 } 410 } 411 412 func TestNonPersistentChrootAbsWsrRelDirAbsCmd(t *testing.T) { 413 func() { 414 rootTest := setupWorkspace(t) 415 defer cleanupWorkspace(rootTest, t) 416 417 testNonPersistentChrootAbsWsrRelDirAbsCmd(t, rootTest) 418 }() 419 420 func() { 421 rootTest := setupWorkspace(t) 422 423 defer cleanupWorkspace(rootTest, t) 424 425 setUidGid(99, 99, t) 426 defer setUidGidToDefault(t) 427 testNonPersistentChrootAbsWsrRelDirAbsCmd(t, rootTest) 428 }() 429 } 430 431 func testNonPersistentChrootAbsWsrRelDirRelCmd(t *testing.T, rootTest string) { 432 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 433 434 fileTest = "." + fileTest[len(dirTest):] 435 dirTest = dirTest[len(rootTest)+1:] 436 437 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 438 t.Fatal(err.Error()) 439 } 440 } 441 442 func TestNonPersistentChrootAbsWsrRelDirRelCmd(t *testing.T) { 443 func() { 444 rootTest := setupWorkspace(t) 445 defer cleanupWorkspace(rootTest, t) 446 447 testNonPersistentChrootAbsWsrRelDirRelCmd(t, rootTest) 448 }() 449 450 func() { 451 rootTest := setupWorkspace(t) 452 453 defer cleanupWorkspace(rootTest, t) 454 455 setUidGid(99, 99, t) 456 defer setUidGidToDefault(t) 457 testNonPersistentChrootAbsWsrRelDirRelCmd(t, rootTest) 458 459 }() 460 } 461 462 func testNonPersistentChrootRelWsrRelDirAbsCmd(t *testing.T, rootTest string) { 463 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 464 465 if err := os.Chdir("/"); err != nil { 466 t.Fatalf("Changing to directory / error: %s", 467 err.Error()) 468 } 469 470 fileTest = fileTest[len(rootTest):] 471 dirTest = dirTest[len(rootTest)+1:] 472 rootTest = "." + rootTest 473 474 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 475 t.Fatal(err.Error()) 476 } 477 } 478 479 func TestNonPersistentChrootRelWsrRelDirAbsCmd(t *testing.T) { 480 func() { 481 rootTest := setupWorkspace(t) 482 defer cleanupWorkspace(rootTest, t) 483 484 testNonPersistentChrootRelWsrRelDirAbsCmd(t, rootTest) 485 }() 486 487 func() { 488 rootTest := setupWorkspace(t) 489 490 defer cleanupWorkspace(rootTest, t) 491 492 setUidGid(99, 99, t) 493 defer setUidGidToDefault(t) 494 testNonPersistentChrootRelWsrRelDirAbsCmd(t, rootTest) 495 496 }() 497 } 498 499 func testNonPersistentChrootRelWsrRelDirRelCmd(t *testing.T, rootTest string) { 500 dirTest, fileTest := setupNonPersistentChrootTest(t, rootTest) 501 502 if err := os.Chdir("/"); err != nil { 503 t.Fatalf("Changing to directory / error: %s", 504 err.Error()) 505 } 506 507 fileTest = "." + fileTest[len(dirTest):] 508 dirTest = dirTest[len(rootTest)+1:] 509 rootTest = "." + rootTest 510 511 if err := runQfsChroot(rootTest, dirTest, fileTest); err != nil { 512 t.Fatal(err.Error()) 513 } 514 } 515 516 func TestNonPersistentChrootRelWsrRelDirRelCmd(t *testing.T) { 517 func() { 518 rootTest := setupWorkspace(t) 519 defer cleanupWorkspace(rootTest, t) 520 521 testNonPersistentChrootRelWsrRelDirRelCmd(t, rootTest) 522 }() 523 524 func() { 525 rootTest := setupWorkspace(t) 526 527 defer cleanupWorkspace(rootTest, t) 528 529 setUidGid(99, 99, t) 530 defer setUidGidToDefault(t) 531 testNonPersistentChrootRelWsrRelDirRelCmd(t, rootTest) 532 533 }() 534 } 535 536 func TestCopyDirStayOnFs(t *testing.T) { 537 err := checkCopyDirStayOnFs(true) 538 utils.Assert(err == nil, "Unable to copy source dir %s", err) 539 } 540 541 func TestCopyDirStayOnFsToFail(t *testing.T) { 542 err := checkCopyDirStayOnFs(false) 543 utils.Assert(err != nil, "Test not checking race condition") 544 } 545 546 func checkCopyDirStayOnFs(ignoreFails bool) error { 547 src, err := ioutil.TempDir("", "SourceDir") 548 utils.Assert(err == nil, "Unable to create source dir") 549 550 dst, err := ioutil.TempDir("", "DestDir") 551 utils.Assert(err == nil, "Unable to create destination dir") 552 553 // Create enough directories to make copyDirStayOnFs take a while 554 dirs := 1000 555 for i := 0; i < dirs; i++ { 556 err = os.MkdirAll(fmt.Sprintf(src+"/dir%d", i), 0777) 557 utils.Assert(err == nil, "Unable to create directory %d", i) 558 } 559 560 // make a bunch of file system removals in parallel 561 var perr error 562 go func() { 563 for i := 0; i < dirs; i += 10 { 564 perr = os.Remove(fmt.Sprintf(src+"/dir%d", i)) 565 if perr != nil { 566 return 567 } 568 569 time.Sleep(time.Millisecond) 570 } 571 }() 572 573 err = copyDirStayOnFs(src, dst, ignoreFails) 574 575 utils.Assert(perr == nil, "Unable to remove source dirs") 576 577 return err 578 }