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  }