package integration
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"syscall"
"testing"
"time"
"golang.org/x/sys/unix"
"github.com/bazelbuild/sandboxfs/integration/utils"
)
func openAndDelete(path string, mode int) (int, error) {
fd, err := syscall.Open(path, mode, 0)
if err != nil {
return -1, fmt.Errorf("failed to open %s: %v", path, err)
}
if err := os.Remove(path); err != nil {
return -1, fmt.Errorf("failed to remove %s: %v", path, err)
}
return fd, nil
}
func createAsDifferentUserTest(t *testing.T, createAsUser func(string, *utils.UnixUser) error) {
root := utils.RequireRoot(t, "Requires root privileges")
user := utils.GetConfig().UnprivilegedUser
if user == nil {
t.Skipf("unprivileged user not set; must contain the name of an unprivileged user with FUSE access")
}
t.Logf("Using primary unprivileged user: %v", user)
state := utils.MountSetupWithUser(t, root, "--mapping=rw:/:%ROOT%", "--allow=other")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0777)
if err := os.Chown(state.RootPath("dir"), user.UID, user.GID); err != nil {
t.Fatalf("Cannot create unprivileged work directory: %v", err)
}
wantFiles := []struct {
path string
user *utils.UnixUser
}{
{path: "dir/unprivileged", user: user},
{path: "privileged", user: root},
}
for _, wantFile := range wantFiles {
if err := createAsUser(state.MountPath(wantFile.path), wantFile.user); err != nil {
t.Fatalf("Cannot create %s as %v: %v", wantFile.path, wantFile.user, err)
}
}
for _, wantFile := range wantFiles {
for _, path := range []string{state.MountPath(wantFile.path), state.RootPath(wantFile.path)} {
fileInfo, err := os.Lstat(path)
if err != nil {
t.Fatalf("Cannot stat %s: %v", path, err)
}
stat := fileInfo.Sys().(*syscall.Stat_t)
if int(stat.Uid) != wantFile.user.UID || int(stat.Gid) != wantFile.user.GID {
t.Errorf("%s has wrong ownership; got %v:%v, want %v:%v", path, stat.Uid, stat.Gid, wantFile.user.UID, wantFile.user.GID)
}
}
}
}
func TestReadWrite_MkdirAsDifferentUser(t *testing.T) {
createAsDifferentUserTest(t, utils.MkdirAsUser)
}
func TestReadWrite_CreateFile(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "original content")
utils.MustMkdirAll(t, state.RootPath("subdir"), 0755)
utils.MustWriteFile(t, state.MountPath("subdir/file"), 0644, "new content")
if err := utils.FileEquals(state.MountPath("file"), "original content"); err != nil {
t.Error(err)
}
if err := utils.FileEquals(state.MountPath("subdir/file"), "new content"); err != nil {
t.Error(err)
}
}
func TestReadWrite_CreateFileAsDifferentUser(t *testing.T) {
createAsDifferentUserTest(t, utils.CreateFileAsUser)
}
func TestReadWrite_DirectoryNlinkCountsStayFixed(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
checkNlink := func(path string, wantNlink int) {
t.Helper()
var stat syscall.Stat_t
if err := syscall.Lstat(path, &stat); err != nil {
t.Fatalf("Lstat failed on deleted entry: %v", err)
}
if int(stat.Nlink) != wantNlink {
t.Errorf("Got nlink %d, want %d", stat.Nlink, wantNlink)
}
}
mustMkdir := func(path string) {
t.Helper()
if err := os.Mkdir(path, 755); err != nil {
t.Fatalf("Failed to mkdir %s: %v", path, err)
}
}
mustRemove := func(path string) {
t.Helper()
if err := os.Remove(path); err != nil {
t.Fatalf("Failed to remove %s: %v", path, err)
}
}
utils.MustMkdirAll(t, state.RootPath("subdir"), 0755)
utils.MustMkdirAll(t, state.RootPath("subdir/dir1"), 0755)
utils.MustMkdirAll(t, state.RootPath("subdir/dir2"), 0755)
utils.MustWriteFile(t, state.RootPath("subdir/file"), 0644, "original content")
checkNlink(state.MountPath("subdir"), 2)
mustRemove(state.MountPath("subdir/dir1"))
checkNlink(state.MountPath("subdir"), 2)
mustRemove(state.MountPath("subdir/file"))
checkNlink(state.MountPath("subdir"), 2)
mustMkdir(state.MountPath("subdir/dir3"))
checkNlink(state.MountPath("subdir"), 2)
mustRemove(state.MountPath("subdir/dir2"))
checkNlink(state.MountPath("subdir"), 2)
mustRemove(state.MountPath("subdir/dir3"))
checkNlink(state.MountPath("subdir"), 2)
}
func TestReadWrite_Remove(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%", "--mapping=rw:/mapped-dir:%ROOT%/mapped-dir", "--mapping=rw:/scaffold/dir:%ROOT%/scaffold-dir")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "")
utils.MustMkdirAll(t, state.RootPath("mapped-dir"), 0755)
t.Run("MappedDirCannotBeRemoved", func(t *testing.T) {
if err := os.Remove(state.MountPath("mapped-dir")); !os.IsPermission(err) {
t.Errorf("Want removal of mapped directory to return permission error; got %v", err)
}
if _, err := os.Lstat(state.MountPath("mapped-dir")); err != nil {
t.Errorf("Want mapped directory to remain within the mount point; got %v", err)
}
if _, err := os.Lstat(state.RootPath("mapped-dir")); err != nil {
t.Errorf("Want entry clobbered by mapping to remain on disk (no Lstat error); got %v", err)
}
})
t.Run("ScaffoldDirCannotBeRemoved", func(t *testing.T) {
if err := os.Remove(state.MountPath("scaffold")); !os.IsPermission(err) {
t.Errorf("Want removal of scaffold directory to return permission error; got %v", err)
}
if _, err := os.Lstat(state.MountPath("scaffold")); err != nil {
t.Errorf("Want scaffold directory to remain within the mount point; got %v", err)
}
})
t.Run("FileDoesNotExist", func(t *testing.T) {
if err := os.Remove(state.MountPath("non-existent")); !os.IsNotExist(err) {
t.Errorf("Want removal of non-existent file to return non-existence error; got %v", err)
}
})
t.Run("EntryExists", func(t *testing.T) {
for _, name := range []string{"dir", "file"} {
if err := os.Remove(state.MountPath(name)); err != nil {
t.Errorf("Want removal of existent file to succeed; got %v", err)
}
if _, err := os.Lstat(state.MountPath(name)); !os.IsNotExist(err) {
t.Errorf("Want stat of removed file within mount point to report non-existence error; got %v", err)
}
if _, err := os.Lstat(state.RootPath(name)); !os.IsNotExist(err) {
t.Errorf("Want stat of removed file in the underlying directory to report non-existence error; got %v", err)
}
}
})
}
func TestReadWRite_RemoveZeroesNlink(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "")
for _, name := range []string{"Dir", "File"} {
t.Run(name, func(t *testing.T) {
path := state.MountPath(strings.ToLower(name))
fd, err := openAndDelete(path, syscall.O_RDONLY)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
var stat syscall.Stat_t
if err := syscall.Fstat(fd, &stat); err != nil {
t.Fatalf("Fstat failed on deleted entry: %v", err)
}
if int(stat.Nlink) != 0 {
t.Errorf("Bad link count: got %d, want 0", stat.Nlink)
}
})
}
}
func TestReadWrite_RewriteFile(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "original content")
if err := utils.FileEquals(state.MountPath("file"), "original content"); err != nil {
t.Error(err)
}
utils.MustWriteFile(t, state.MountPath("file"), 0644, "rewritten content")
if err := utils.FileEquals(state.MountPath("file"), "rewritten content"); err != nil {
t.Error(err)
}
}
func TestReadWrite_RewriteFileWithShorterContent(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustWriteFile(t, state.MountPath("file"), 0644, "very long contents")
utils.MustWriteFile(t, state.MountPath("file"), 0644, "short")
if err := utils.FileEquals(state.MountPath("file"), "short"); err != nil {
t.Error(err)
}
}
func TestReadWrite_WriteOnDeletedAndDuppedFd(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
fd, err := openAndDelete(state.MountPath("some-file"), syscall.O_RDWR|syscall.O_CREAT)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
mustWrite := func(fd int, s string) {
t.Helper()
if n, err := syscall.Write(fd, []byte(s)); err != nil || n != len(s) {
t.Fatalf("Failed to write: got n=%d, err=%v; want n=%d, err=nil", n, err, len(s))
}
}
mustWrite(fd, "123")
fd2, err := syscall.Dup(fd)
if err != nil {
t.Fatalf("Dup failed: %v", err)
}
defer syscall.Close(fd2)
syscall.Close(fd)
mustWrite(fd2, "45")
if n, err := syscall.Seek(fd2, 0, io.SeekStart); err != nil || n != 0 {
t.Fatalf("Failed to rewind file: n=%d, err=%v", n, err)
}
wantBuf := []byte("12345")
buf := make([]byte, 8)
if n, err := syscall.Read(fd2, buf); err != nil || n != len(wantBuf) {
t.Fatalf("Failed to write: got n=%d, err=%v; want n=%d, err=nil", n, err, len(wantBuf))
} else {
buf = buf[0:n]
}
if !reflect.DeepEqual(buf, wantBuf) {
t.Errorf("Data read from file doesn't match written: got %v, want %v", buf, wantBuf)
}
var stat syscall.Stat_t
if err := syscall.Fstat(fd2, &stat); err != nil {
t.Fatalf("Fstat failed on deleted entry: %v", err)
}
if stat.Size != int64(len(wantBuf)) {
t.Errorf("Bad file length: got %d, want %d", stat.Size, len(wantBuf))
}
}
func TestReadWrite_InodesArePreservedDuringReaddir(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
inodeOf := func(path string) uint64 {
fileInfo, err := os.Lstat(path)
if err != nil {
t.Fatalf("Failed to get inode number of %s: %v", path, err)
}
return fileInfo.Sys().(*syscall.Stat_t).Ino
}
utils.MustMkdirAll(t, state.MountPath("dir"), 0755)
utils.MustWriteFile(t, state.MountPath("file"), 0644, "")
for _, name := range []string{"dir", "file"} {
previous := inodeOf(state.MountPath(name))
if found, err := existsViaReaddir(state.MountPath(), name); err != nil || !found {
t.Fatalf("Cannot find entry %s: %v", name, err)
}
newName := name + "-new"
if err := os.Rename(state.MountPath(name), state.MountPath(newName)); err != nil {
t.Fatalf("Cannot rename %s: %v", name, err)
}
if found, err := existsViaReaddir(state.MountPath(), newName); err != nil || !found {
t.Fatalf("Cannot find entry %s: %v", newName, err)
}
now := inodeOf(state.MountPath(newName))
if previous != now {
t.Errorf("Inode number was not stable for %s; got %v, want %v", name, now, previous)
}
}
}
func TestReadWrite_InodeReassignedAfterRecreation(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
testData := []struct {
name string
path string
create func(string, os.FileMode)
}{
{
"Dir",
"dir",
func(path string, mode os.FileMode) { utils.MustMkdirAll(t, path, mode) },
},
{
"File",
"file",
func(path string, mode os.FileMode) { utils.MustWriteFile(t, path, mode, "") },
},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.path)
d.create(path, 0755)
fileInfo, err := os.Lstat(path)
if err != nil {
t.Fatalf("Failed to get inode number of entry after first creation: %v", err)
}
originalInode := fileInfo.Sys().(*syscall.Stat_t).Ino
originalMode := fileInfo.Mode()
if err := os.Remove(path); err != nil {
t.Fatalf("Failed to remove entry: %v", err)
}
d.create(path, 0700)
fileInfo, err = os.Lstat(path)
if err != nil {
t.Fatalf("Failed to get inode number of entry after recreation: %v", err)
}
recreatedInode := fileInfo.Sys().(*syscall.Stat_t).Ino
recreatedMode := fileInfo.Mode()
if originalInode == recreatedInode {
t.Errorf("Still got inode number %v; want it to change after entry recreation", recreatedInode)
}
if originalMode == recreatedMode {
t.Errorf("Still got file mode %v; want it to change after entry recreation with different permissions", recreatedMode)
}
})
}
}
func TestReadWrite_FstatOnDeletedNode(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.MountPath("dir"), 0755)
utils.MustWriteFile(t, state.MountPath("file"), 0644, "")
testData := []struct {
name string
relativePath string
}{
{"MappedDir", "dir"},
{"MappedFile", "file"},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.relativePath)
var wantStat syscall.Stat_t
if err := syscall.Stat(path, &wantStat); err != nil {
t.Fatalf("Fstat failed on golden entry: %v", err)
}
fd, err := openAndDelete(path, syscall.O_RDONLY)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
var stat syscall.Stat_t
if err := syscall.Fstat(fd, &stat); err != nil {
t.Fatalf("Fstat failed on deleted entry: %v", err)
}
stat.Nlink = 0
wantStat.Nlink = 0
if stat != wantStat {
t.Errorf("Got stat %v; want %v", stat, wantStat)
}
})
}
}
func TestReadWrite_Truncate(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustWriteFile(t, state.MountPath("file"), 0644, "very long contents")
wantContent := "very"
if err := os.Truncate(state.MountPath("file"), int64(len(wantContent))); err != nil {
t.Fatalf("Truncate failed: %v", err)
}
if err := utils.FileEquals(state.MountPath("file"), wantContent); err != nil {
t.Error(err)
}
}
func TestReadWrite_FtruncateOnDeletedFile(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
originalContent := "very long contents"
utils.MustWriteFile(t, state.MountPath("file"), 0644, originalContent)
fd, err := openAndDelete(state.MountPath("file"), syscall.O_RDWR)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
wantContent := "very"
if err := syscall.Ftruncate(fd, int64(len(wantContent))); err != nil {
t.Fatalf("Ftruncate on deleted file failed: %v", err)
}
buf := make([]byte, len(originalContent))
n, err := syscall.Read(fd, buf)
if err != nil {
t.Fatalf("Failed to read from truncated file: %v", err)
}
if n != len(wantContent) {
t.Errorf("Got %d bytes from truncated file; want %d", n, len(wantContent))
}
buf = buf[:n]
if string(buf) != wantContent {
t.Errorf("Got content %s; want %s", string(buf), wantContent)
}
}
func sameInode(stat1 os.FileInfo, stat2 os.FileInfo) bool {
ino1 := stat1.Sys().(*syscall.Stat_t).Ino
ino2 := stat2.Sys().(*syscall.Stat_t).Ino
return ino1 == ino2
}
func equivalentStats(stat1 os.FileInfo, stat2 os.FileInfo) error {
ino1 := stat1.Sys().(*syscall.Stat_t).Ino
ino2 := stat2.Sys().(*syscall.Stat_t).Ino
if stat1.Mode() != stat2.Mode() || stat1.ModTime() != stat2.ModTime() || ino1 != ino2 {
return fmt.Errorf("got mode=%v, mtime=%v, inode=%v; want mode=%v, mtime=%v, inode=%v", stat1.Mode(), stat1.ModTime(), ino1, stat2.Mode(), stat2.ModTime(), ino2)
}
return nil
}
func doRenameTest(t *testing.T, oldOuterPath, newOuterPath, oldInnerPath, newInnerPath string) {
utils.MustMkdirAll(t, filepath.Dir(oldOuterPath), 0755)
utils.MustMkdirAll(t, filepath.Dir(newOuterPath), 0755)
utils.MustMkdirAll(t, filepath.Dir(oldInnerPath), 0755)
utils.MustMkdirAll(t, filepath.Dir(newInnerPath), 0755)
utils.MustWriteFile(t, oldOuterPath, 0644, "some content")
lstatOrFatal := func(path string) os.FileInfo {
stat, err := os.Lstat(path)
if err != nil {
t.Fatalf("Failed to lstat %s: %v", path, err)
}
return stat
}
oldOuterStat := lstatOrFatal(oldOuterPath)
oldInnerStat := lstatOrFatal(oldInnerPath)
if err := os.Rename(oldInnerPath, newInnerPath); err != nil {
t.Fatalf("Failed to rename %s to %s: %v", oldInnerPath, newInnerPath, err)
}
newOuterStat := lstatOrFatal(newOuterPath)
newInnerStat := lstatOrFatal(newInnerPath)
if _, err := os.Lstat(oldOuterPath); os.IsExist(err) {
t.Fatalf("Old file name in root still present but should have disappeared: %s", oldOuterPath)
}
if _, err := os.Lstat(oldInnerPath); os.IsExist(err) {
t.Fatalf("Old file name in mount point still present but should have disappeared: %s", oldInnerPath)
}
if err := utils.FileEquals(newOuterPath, "some content"); err != nil {
t.Fatalf("New file name in root missing or with bad contents: %s: %v", newOuterPath, err)
}
if err := utils.FileEquals(newInnerPath, "some content"); err != nil {
t.Fatalf("New file name in mount point missing or with bad contents: %s: %v", newInnerPath, err)
}
if !sameInode(oldOuterStat, newOuterStat) {
t.Errorf("Inode was not preserved for %s to %s move", oldOuterPath, newOuterPath)
}
if !sameInode(oldInnerStat, newInnerStat) {
t.Errorf("Inode was not preserved for %s to %s move", oldInnerPath, newInnerPath)
}
if err := equivalentStats(oldOuterStat, newOuterStat); err != nil {
t.Errorf("Stats for %s and %s differ: %v", oldOuterPath, newOuterPath, err)
}
if err := equivalentStats(oldInnerStat, newInnerStat); err != nil {
t.Errorf("Stats for %s and %s differ: %v", oldInnerPath, newInnerPath, err)
}
}
func TestReadWrite_NestedMappingsInheritDirectoryProperties(t *testing.T) {
rootSetup := func(root string) error {
if err := os.MkdirAll(filepath.Join(root, "already/exist"), 0755); err != nil {
return err
}
return os.MkdirAll(filepath.Join(root, "dir"), 0755)
}
state := utils.MountSetupWithRootSetup(t, rootSetup,
"--mapping=rw:/:%ROOT%",
"--mapping=ro:/already/exist/dir:%ROOT%/dir")
defer state.TearDown(t)
for _, path := range []string{"already/foo", "already/exist/foo"} {
if err := ioutil.WriteFile(state.MountPath(path), []byte(""), 0644); err != nil {
t.Errorf("Cannot create %s; possible mapping interference: %v", path, err)
}
if _, err := os.Lstat(state.RootPath(path)); err != nil {
t.Errorf("Cannot find %s in underlying root location: %v", path, err)
}
}
if err := ioutil.WriteFile(state.MountPath("already/exist/dir/foo"), []byte(""), 0644); err == nil {
t.Errorf("Successfully created file in read-only mapping")
}
}
func TestReadWrite_NestedMappingsClobberFiles(t *testing.T) {
rootSetup := func(root string) error {
if err := os.MkdirAll(filepath.Join(root, "dir"), 0755); err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join(root, "file"), []byte(""), 0644); err != nil {
return err
}
return os.Symlink("/non-existent", filepath.Join(root, "symlink"))
}
state := utils.MountSetupWithRootSetup(t, rootSetup,
"--mapping=rw:/:%ROOT%",
"--mapping=ro:/file/nested-dir:%ROOT%/dir",
"--mapping=ro:/symlink/nested-dir:%ROOT%/dir")
defer state.TearDown(t)
for _, component := range []string{"file", "symlink"} {
fileInfo, err := os.Lstat(state.MountPath(component, "nested-dir"))
if err != nil {
t.Errorf("Cannot navigate into mapping %s/nested-dir; underlying entry interfered: %v", component, err)
}
if fileInfo.Mode()&os.ModeType != os.ModeDir {
t.Errorf("Got mode %v for mapping; want directory", fileInfo.Mode())
}
if err := os.Mkdir(state.MountPath(component, "other"), 0755); err == nil {
t.Errorf("Intermediate mapping directory %s was not read-only", component)
}
}
}
func TestReadWrite_RenameFile(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
oldOuterPath := state.RootPath("old-name")
newOuterPath := state.RootPath("new-name")
oldInnerPath := state.MountPath("old-name")
newInnerPath := state.MountPath("new-name")
doRenameTest(t, oldOuterPath, newOuterPath, oldInnerPath, newInnerPath)
}
func TestReadWrite_MoveFile(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
oldOuterPath := state.RootPath("dir1/dir2/old-name")
newOuterPath := state.RootPath("dir2/dir3/dir4/new-name")
oldInnerPath := state.MountPath("dir1/dir2/old-name")
newInnerPath := state.MountPath("dir2/dir3/dir4/new-name")
doRenameTest(t, oldOuterPath, newOuterPath, oldInnerPath, newInnerPath)
}
func TestReadWrite_MoveRace(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir1"), 0755)
utils.MustWriteFile(t, state.RootPath("dir1/file1"), 0644, "")
utils.MustMkdirAll(t, state.RootPath("dir2"), 0755)
utils.MustWriteFile(t, state.RootPath("dir2/file2"), 0644, "")
doMove := func(errChan chan error, srcdir string, newdir string, file string) {
for i := 0; i < 1000; i++ {
if err := os.Rename(state.MountPath(srcdir, file), state.MountPath(newdir, file)); err != nil {
errChan <- err
return
}
if err := os.Rename(state.MountPath(newdir, file), state.MountPath(srcdir, file)); err != nil {
errChan <- err
return
}
}
errChan <- nil
}
errChan := make(chan error)
go doMove(errChan, "dir1", "dir2", "file1")
go doMove(errChan, "dir2", "dir1", "file2")
for i := 0; i < 2; i++ {
err := <-errChan
if err != nil {
t.Errorf("Renames failed: %v", err)
}
}
}
func TestReadWrite_MoveAsDifferentUser(t *testing.T) {
createAsDifferentUserTest(t, func(path string, user *utils.UnixUser) error {
if err := utils.CreateFileAsUser(path+".old", user); err != nil {
return err
}
return utils.MoveAsUser(path+".old", path, user)
})
}
func TestReadWrite_MoveDirectoryUpdatesContents(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.MountPath("dir1"), 0755)
utils.MustWriteFile(t, state.MountPath("dir1/file"), 0644, "First file")
utils.MustMkdirAll(t, state.MountPath("dir1/subdir"), 0755)
utils.MustWriteFile(t, state.MountPath("dir1/subdir/file"), 0644, "Second file")
if err := os.Rename(state.MountPath("dir1"), state.MountPath("dir2")); err != nil {
t.Fatalf("Rename failed: %v", err)
}
if err := utils.FileEquals(state.MountPath("dir2/file"), "First file"); err != nil {
t.Fatalf("dir2/file is invalid: %v", err)
}
if err := utils.FileEquals(state.MountPath("dir2/subdir/file"), "Second file"); err != nil {
t.Fatalf("dir2/subdir/file is invalid: %v", err)
}
}
func TestReadWrite_DeleteAfterMovedDirectory(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.MountPath("dir1"), 0755)
utils.MustWriteFile(t, state.MountPath("dir1/file"), 0644, "First file")
utils.MustMkdirAll(t, state.MountPath("dir1/subdir"), 0755)
utils.MustWriteFile(t, state.MountPath("dir1/subdir/file"), 0644, "Second file")
if err := os.Rename(state.MountPath("dir1"), state.MountPath("dir2")); err != nil {
t.Fatalf("Rename failed: %v", err)
}
if err := os.RemoveAll(state.MountPath("dir2")); err != nil {
t.Errorf("Remove failed: %v", err)
}
}
func TestReadWrite_Mknod(t *testing.T) {
utils.RequireRoot(t, "Requires root privileges to create arbitrary nodes")
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
checkNode := func(relPath string, wantMode os.FileMode, wantDev uint64) error {
for _, path := range []string{state.RootPath(relPath), state.MountPath(relPath)} {
fileInfo, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("failed to stat %s: %v", path, err)
}
stat := fileInfo.Sys().(*syscall.Stat_t)
if fileInfo.Mode() != wantMode {
return fmt.Errorf("got mode %v for %s, want %v", fileInfo.Mode(), path, wantMode)
}
if (wantMode&os.ModeType)&os.ModeDevice != 0 {
if uint64(stat.Rdev) != wantDev { return fmt.Errorf("got dev %v for %s, want %v", stat.Rdev, path, wantDev)
}
}
}
return nil
}
findOS := func(oses []string) bool {
for _, os := range oses {
if os == runtime.GOOS {
return true
}
}
return false
}
allOSes := []string{"darwin", "linux"}
if !findOS(allOSes) {
t.Fatalf("Don't know how this test behaves in this platform")
}
testData := []struct {
name string
filename string
perm uint32
mknodType uint32
dev int
statType os.FileMode
wantOS []string
}{
{"RegularFile", "file", 0644, syscall.S_IFREG, 0, 0, []string{"linux"}},
{"Directory", "dir", 0755, syscall.S_IFDIR, 0, os.ModeDir, []string{}},
{"BlockDevice", "blkdev", 0400, syscall.S_IFBLK, 1234, os.ModeDevice, allOSes},
{"CharDevice", "chrdev", 0400, syscall.S_IFCHR, 5678, os.ModeDevice | os.ModeCharDevice, allOSes},
{"NamedPipe", "fifo", 0640, syscall.S_IFIFO, 0, os.ModeNamedPipe, allOSes},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.filename)
shouldHaveFailed := false
err := syscall.Mknod(path, d.perm|d.mknodType, d.dev)
if findOS(d.wantOS) {
if err != nil {
t.Fatalf("Failed to mknod %s: %v", path, err)
}
} else {
if err == nil {
shouldHaveFailed = true
}
}
err = checkNode(d.filename, (os.FileMode(d.perm)&os.ModePerm)|d.statType, uint64(d.dev))
if findOS(d.wantOS) {
if err != nil {
t.Error(err)
}
} else {
if err == nil {
shouldHaveFailed = true
}
}
if shouldHaveFailed {
t.Fatalf("Test was expected to fail on this platform due to behavioral differences in mknod(2) but succeeded")
}
})
}
}
func TestReadWrite_MknodAsDifferentUser(t *testing.T) {
createAsDifferentUserTest(t, utils.MkfifoAsUser)
}
func TestReadWrite_Chmod(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
checkPerm := func(relPath string, wantPerm os.FileMode) error {
for _, path := range []string{state.RootPath(relPath), state.MountPath(relPath)} {
fileInfo, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("failed to stat %s: %v", path, err)
}
perm := fileInfo.Mode() & os.ModePerm
if perm != wantPerm {
return fmt.Errorf("got permissions %v for %s, want %v", perm, path, wantPerm)
}
}
return nil
}
t.Run("Dir", func(t *testing.T) {
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
path := state.MountPath("dir")
if err := os.Chmod(path, 0500); err != nil {
t.Fatalf("Failed to chmod %s: %v", path, err)
}
if err := checkPerm("dir", 0500); err != nil {
t.Error(err)
}
})
t.Run("File", func(t *testing.T) {
utils.MustWriteFile(t, state.RootPath("file"), 0644, "new content")
path := state.MountPath("file")
if err := os.Chmod(path, 0440); err != nil {
t.Fatalf("Failed to chmod %s: %v", path, err)
}
if err := checkPerm("file", 0440); err != nil {
t.Error(err)
}
})
t.Run("Symlink", func(t *testing.T) {
utils.MustWriteFile(t, state.RootPath("target"), 0644, "")
utils.MustSymlink(t, "target", state.RootPath("symlink"))
path := state.MountPath("symlink")
linkFileInfo, err := os.Lstat(path)
if err != nil {
t.Fatalf("Failed to stat %s: %v", path, err)
}
if err := os.Chmod(path, 0200); err != nil {
t.Fatalf("Failed to chmod %s: %v", path, err)
}
if err := checkPerm("symlink", linkFileInfo.Mode()&os.ModePerm); err != nil {
t.Error(err)
}
if err := checkPerm("target", 0200); err != nil {
t.Errorf("Mode of symlink target was modified but shouldn't have been: %v", err)
}
})
}
func TestReadWrite_FchmodOnDeletedNode(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.MountPath("dir"), 0755)
utils.MustWriteFile(t, state.MountPath("file"), 0644, "")
testData := []struct {
name string
relativePath string
}{
{"MappedDir", "dir"},
{"MappedFile", "file"},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.relativePath)
fd, err := openAndDelete(path, syscall.O_RDONLY)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
if err := syscall.Fchmod(fd, 0444); err != nil {
t.Fatalf("Fchmod failed on deleted entry: %v", err)
}
var stat syscall.Stat_t
if err := syscall.Fstat(fd, &stat); err != nil {
t.Fatalf("Fstat failed on deleted entry: %v", err)
}
if stat.Mode&^syscall.S_IFMT != 0444 {
t.Errorf("Want file mode %o, got %o", 0444, stat.Mode&^syscall.S_IFMT)
}
})
}
}
func TestReadWrite_Chown(t *testing.T) {
utils.RequireRoot(t, "Requires root privileges to change test file ownership")
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
checkOwners := func(relPath string, wantUID uint32, wantGID uint32) error {
for _, path := range []string{state.RootPath(relPath), state.MountPath(relPath)} {
fileInfo, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("failed to stat %s: %v", path, err)
}
stat := fileInfo.Sys().(*syscall.Stat_t)
if stat.Uid != wantUID {
return fmt.Errorf("got uid %v for %s, want %v", stat.Uid, path, wantUID)
}
if stat.Gid != wantGID {
return fmt.Errorf("got gid %v for %s, want %v", stat.Gid, path, wantGID)
}
}
return nil
}
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "new content")
utils.MustWriteFile(t, state.RootPath("target"), 0644, "")
utils.MustSymlink(t, "target", state.RootPath("symlink"))
targetFileInfo, err := os.Lstat(state.RootPath("target"))
if err != nil {
t.Fatalf("Failed to stat %s: %v", state.RootPath("target"), err)
}
targetStat := targetFileInfo.Sys().(*syscall.Stat_t)
testData := []struct {
name string
filename string
wantUID int
wantGID int
}{
{"Dir", "dir", 1, 2},
{"File", "file", 3, 4},
{"Symlink", "symlink", 7, 8},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.filename)
if err := os.Lchown(path, d.wantUID, d.wantGID); err != nil {
t.Fatalf("Failed to chown %s: %v", path, err)
}
if err := checkOwners(d.filename, uint32(d.wantUID), uint32(d.wantGID)); err != nil {
t.Error(err)
}
})
}
if err := checkOwners("target", targetStat.Uid, targetStat.Gid); err != nil {
t.Errorf("Ownership of symlink target was modified but shouldn't have been: %v", err)
}
}
func TestReadWrite_FchownOnDeletedNode(t *testing.T) {
utils.RequireRoot(t, "Requires root privileges to change test file ownership")
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.MountPath("dir"), 0755)
utils.MustWriteFile(t, state.MountPath("file"), 0644, "")
testData := []struct {
name string
relativePath string
}{
{"MappedDir", "dir"},
{"MappedFile", "file"},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.relativePath)
fd, err := openAndDelete(path, syscall.O_RDONLY)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
if err := syscall.Fchown(fd, 10, 20); err != nil {
t.Fatalf("Fchown failed on deleted entry: %v", err)
}
var stat syscall.Stat_t
if err := syscall.Fstat(fd, &stat); err != nil {
t.Fatalf("Fstat failed on deleted entry: %v", err)
}
if stat.Uid != 10 || stat.Gid != 20 {
t.Errorf("Want uid 10, gid 20; got uid %d, gid %d", stat.Uid, stat.Gid)
}
})
}
}
func TestReadWrite_Chtimes(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
checkTimes := func(relPath string, wantAtime time.Time, wantMtime time.Time, wantMinCtime time.Time) error {
for _, path := range []string{state.RootPath(relPath), state.MountPath(relPath)} {
fileInfo, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("failed to stat %s: %v", path, err)
}
stat := fileInfo.Sys().(*syscall.Stat_t)
if !fileInfo.ModTime().Equal(wantMtime) {
return fmt.Errorf("got mtime %v for %s, want %v", fileInfo.ModTime(), path, wantMtime)
}
if !wantAtime.Equal(time.Unix(0, 0)) && !utils.Atime(stat).Equal(wantAtime) {
return fmt.Errorf("got atime %v for %s, want %v", utils.Atime(stat), path, wantAtime)
}
if utils.Ctime(stat).Before(wantMinCtime) {
return fmt.Errorf("got ctime %v for %s, want <= %v", utils.Ctime(stat), path, wantMinCtime)
}
}
return nil
}
chtimes := func(path string, atime time.Time, mtime time.Time) (time.Time, error) {
minCtime := time.Now()
time.Sleep(1 * time.Second)
atimeTimespec, err := unix.TimeToTimespec(atime)
if err != nil {
t.Fatalf("Failed to convert %v to a timespec: %v", atime, err)
}
mtimeTimespec, err := unix.TimeToTimespec(mtime)
if err != nil {
t.Fatalf("Failed to convert %v to a timespec: %v", mtime, err)
}
if err := unix.UtimesNanoAt(unix.AT_FDCWD, path, []unix.Timespec{atimeTimespec, mtimeTimespec}, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return time.Unix(0, 0), err
}
return minCtime, nil
}
someAtime := time.Date(2009, 5, 25, 9, 0, 0, 0, time.UTC)
someMtime := time.Date(1984, 8, 10, 19, 15, 0, 0, time.UTC)
t.Run("Dir", func(t *testing.T) {
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
wantMinCtime, err := chtimes(state.MountPath("dir"), someAtime, someMtime)
if err != nil {
t.Fatal(err)
}
if err := checkTimes("dir", someAtime, someMtime, wantMinCtime); err != nil {
t.Error(err)
}
})
t.Run("File", func(t *testing.T) {
utils.MustWriteFile(t, state.RootPath("file"), 0644, "new content")
wantMinCtime, err := chtimes(state.MountPath("file"), someAtime, someMtime)
if err != nil {
t.Fatal(err)
}
if err := checkTimes("file", someAtime, someMtime, wantMinCtime); err != nil {
t.Error(err)
}
})
t.Run("Symlink", func(t *testing.T) {
utils.MustWriteFile(t, state.RootPath("target"), 0644, "")
targetBefore, err := os.Lstat(state.MountPath("target"))
if err != nil {
t.Fatalf("Cannot stat target: %v", err)
}
utils.MustSymlink(t, "target", state.RootPath("symlink"))
if _, ok := os.LookupEnv("DO"); runtime.GOOS != "darwin" || !ok {
wantMinCtime, err := chtimes(state.MountPath("symlink"), someAtime, someMtime)
if err != nil {
t.Fatal(err)
}
if err := checkTimes("symlink", someAtime, someMtime, wantMinCtime); err != nil {
t.Error(err)
}
} else {
if _, err := chtimes(state.MountPath("symlink"), someAtime, someMtime); err == nil || err != syscall.EOPNOTSUPP {
t.Fatalf("Expected EOPNOTSUPP changing the times of a symlink; got %v", err)
}
}
targetAfter, err := os.Lstat(state.MountPath("target"))
if err != nil {
t.Fatalf("Cannot stat target: %v", err)
}
if !reflect.DeepEqual(targetBefore, targetAfter) {
t.Errorf("Target file's times were unexpectedly modified: got %v, want %v", targetAfter, targetBefore)
}
})
}
func TestReadWrite_ChtimesResetsBirthtimeWithMtime(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
mustBtime := func(path string) time.Time {
t.Helper()
btime, err := utils.Btime(path)
if err != nil {
t.Fatalf("Cannot get birthtime for %s: %v", path, err)
return btime }
if btime == utils.ZeroBtime {
t.Skipf("Birthtime not supported on this platform for %s", path)
return btime }
return btime
}
utils.MustWriteFile(t, state.RootPath("file"), 0644, "new content")
path := state.MountPath("file")
birth := mustBtime(path)
time.Sleep(time.Second)
now := time.Now()
if err := os.Chtimes(path, now, now); err != nil {
t.Fatalf("Failed to chtimes on %s: %v", path, err)
}
newBirth := mustBtime(path)
if newBirth != birth {
t.Errorf("Birthtime unexpectedly changed: got %v, want %v", newBirth, birth)
}
past := time.Date(1984, 8, 10, 19, 15, 0, 0, time.Local)
if err := os.Chtimes(path, now, past); err != nil {
t.Fatalf("Failed to chtimes on %s: %v", path, err)
}
newBirth = mustBtime(path)
if newBirth != past {
t.Errorf("Birthtime was not updated: got %v, want %v", newBirth, past)
}
}
func TestReadWrite_FutimesOnDeletedNode(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.MountPath("dir"), 0755)
utils.MustWriteFile(t, state.MountPath("file"), 0644, "")
someAtime := time.Date(2010, 2, 20, 10, 30, 0, 0, time.UTC)
someMtime := time.Date(1980, 3, 26, 12, 10, 0, 0, time.UTC)
testData := []struct {
name string
relativePath string
}{
{"MappedDir", "dir"},
{"MappedFile", "file"},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.relativePath)
fd, err := openAndDelete(path, syscall.O_RDONLY)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
tv := []syscall.Timeval{
{Sec: int64(someAtime.Unix())},
{Sec: int64(someMtime.Unix())},
}
if err := syscall.Futimes(fd, tv); err != nil {
t.Fatalf("Fchown failed on deleted entry: %v", err)
}
var stat syscall.Stat_t
if err := syscall.Fstat(fd, &stat); err != nil {
t.Fatalf("Fstat failed on deleted entry: %v", err)
}
if !someAtime.Equal(utils.Atime(&stat)) || !someMtime.Equal(utils.Mtime(&stat)) {
t.Errorf("Want atime %v, mtime %v; got atime %v, mtime %v", someAtime, someMtime, utils.Atime(&stat), utils.Mtime(&stat))
}
})
}
}
func TestReadWrite_HardLinksNotSupported(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%", "--mapping=rw:/dir:%ROOT%/dir", "--mapping=rw:/scaffold/name3:%ROOT%/dir2")
defer state.TearDown(t)
utils.MustWriteFile(t, state.RootPath("name1"), 0644, "")
utils.MustWriteFile(t, state.RootPath("dir/name2"), 0644, "")
testData := []struct {
name string
dir string entryName string }{
{"Root", "", "name1"},
{"MappedDir", "dir", "name2"},
{"ScaffoldDir", "scaffold", "name3"},
}
for _, d := range testData {
t.Run(d.name, func(t *testing.T) {
path := state.MountPath(d.dir, d.entryName)
fileInfo, err := os.Lstat(path)
if err != nil {
t.Fatalf("Failed to stat %s before link attempt: %v", path, err)
}
wantNlink := fileInfo.Sys().(*syscall.Stat_t).Nlink
if err := os.Link(path, state.MountPath(d.dir, "new-name")); !os.IsPermission(err) {
t.Errorf("Want Link of %s to fail with permission error; got %v", path, err)
}
fileInfo, err = os.Lstat(path)
if err != nil {
t.Fatalf("Failed to stat %s after link attempt: %v", path, err)
}
stat := fileInfo.Sys().(*syscall.Stat_t)
if stat.Nlink != wantNlink {
t.Errorf("Want hard link count for %s to remain %d after failed link operation; got %d", path, wantNlink, stat.Nlink)
}
})
}
}
func TestReadWrite_SymlinkAndReadlink(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
path := state.MountPath("symlink")
target := "some/random/dangling/target"
if err := os.Symlink(target, path); err != nil {
t.Fatalf("Failed to create symlink %s: %v", path, err)
}
gotTarget, err := os.Readlink(path)
if err != nil {
t.Fatalf("Failed to read symlink %s: %v", path, err)
}
if target != gotTarget {
t.Errorf("Want symlink target to be %s, got %s", target, gotTarget)
}
}
func TestReadWrite_SymlinkAsDifferentUser(t *testing.T) {
createAsDifferentUserTest(t, func(path string, user *utils.UnixUser) error {
return utils.SymlinkAsUser("/non-existent/target", path, user)
})
}
func TestReadWrite_MmapAfterMovesWorks(t *testing.T) {
state := utils.MountSetup(t, "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
content := "some contents"
utils.MustWriteFile(t, state.RootPath("name1"), 0644, content)
utils.MustWriteFile(t, state.RootPath("name2"), 0644, "")
readViaMmap := func(fd int) {
data, err := syscall.Mmap(fd, 0, 8, syscall.PROT_READ, syscall.MAP_FILE|syscall.MAP_PRIVATE)
if err != nil {
t.Fatalf("Mmap failed: %v", err)
}
defer syscall.Munmap(data)
if dummy := data[0]; dummy != content[0] {
t.Errorf("Got byte %b, want %b", dummy, content[0])
}
}
path1 := state.MountPath("name1")
path2 := state.MountPath("name2")
fd, err := syscall.Open(path1, syscall.O_RDONLY, 0)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
readViaMmap(fd)
syscall.Close(fd)
fd, err = syscall.Open(path2, syscall.O_WRONLY|syscall.O_TRUNC, 0)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
if _, err := syscall.Write(fd, []byte(content)); err != nil {
t.Fatalf("Cannot write: %v", err)
}
syscall.Close(fd)
if err := os.Rename(path2, path1); err != nil {
t.Fatalf("Rename failed: %v", err)
}
fd, err = syscall.Open(path1, syscall.O_RDONLY, 0)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
readViaMmap(fd)
syscall.Close(fd)
}
func testXattrsOnDeletedFiles(t *testing.T, wantErr error, hook func(int) error) {
state := utils.MountSetup(t, "--xattrs", "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "content")
for _, name := range []string{"dir", "file"} {
fd, err := openAndDelete(state.MountPath(name), syscall.O_RDONLY)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
if err := hook(fd); err != wantErr {
t.Errorf("xattr operations via file handles not supported for %s; got %v, want %v", name, err, wantErr)
}
}
}
func TestReadWrite_Fgetxattr(t *testing.T) {
testXattrsOnDeletedFiles(t, utils.MissingXattrErr, func(fd int) error {
_, err := unix.Fgetxattr(fd, "user.foo", []byte{})
return err
})
}
func TestReadWrite_Flistxattr(t *testing.T) {
testXattrsOnDeletedFiles(t, nil, func(fd int) error {
_, err := unix.Flistxattr(fd, []byte{})
return err
})
}
func TestReadWrite_Setxattr(t *testing.T) {
state := utils.MountSetup(t, "--xattrs", "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "new content")
utils.MustSymlink(t, "missing", state.RootPath("symlink"))
tests := []string{"dir", "file"}
if runtime.GOOS != "linux" { tests = append(tests, "symlink")
}
for _, name := range tests {
wantValue := []byte("some-value")
if err := unix.Lsetxattr(state.MountPath(name), "user.foo", wantValue, 0); err != nil {
t.Fatalf("Lsetxattr(%s) failed: %v", name, err)
}
for _, path := range []string{state.MountPath(name), state.RootPath(name)} {
buf := make([]byte, 32)
sz, err := unix.Lgetxattr(path, "user.foo", buf)
if err != nil {
t.Fatalf("Listxattr(%s) failed: %v", path, err)
}
value := buf[0:sz]
if !reflect.DeepEqual(value, wantValue) {
t.Errorf("Invalid attribute for path %s: got %s, want %s", path, value, wantValue)
}
}
}
}
func TestReadWrite_SetxattrOnScaffoldDirectory(t *testing.T) {
state := utils.MountSetup(t, "--xattrs", "--mapping=rw:/:%ROOT%", "--mapping=rw:/scaffold/dir:%ROOT%")
defer state.TearDown(t)
path := state.MountPath("scaffold")
value := []byte("some-value")
wantErr := utils.WriteErrorForUnwritableNode()
if err := unix.Lsetxattr(path, "user.foo", value, 0); err != wantErr {
t.Errorf("Invalid error from Lsetxattr for %s: got %v, want %v", path, err, wantErr)
}
}
func TestReadWrite_SetxattrDisabled(t *testing.T) {
state := utils.MountSetup(t, "--mapping=ro:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
var wantErr error
switch runtime.GOOS {
case "darwin":
wantErr = unix.EPERM
case "linux":
wantErr = unix.EOPNOTSUPP
default:
panic("Don't know how this test behaves on this platform")
}
if err := unix.Lsetxattr(state.MountPath("dir"), "user.foo", []byte{}, 0); err != wantErr {
t.Fatalf("Lsetxattr should have failed with %v, got %v", wantErr, err)
}
}
func TestReadWrite_Fsetxattr(t *testing.T) {
testXattrsOnDeletedFiles(t, unix.EACCES, func(fd int) error {
return unix.Fsetxattr(fd, "user.foo", []byte{}, 0)
})
}
func TestReadWrite_Removexattr(t *testing.T) {
state := utils.MountSetup(t, "--xattrs", "--mapping=rw:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
utils.MustWriteFile(t, state.RootPath("file"), 0644, "new content")
utils.MustSymlink(t, "missing", state.RootPath("symlink"))
tests := []string{"dir", "file"}
if runtime.GOOS != "linux" { tests = append(tests, "symlink")
}
for _, name := range []string{"dir", "file"} {
if err := unix.Lsetxattr(state.RootPath(name), "user.foo", []byte("some-value"), 0); err != nil {
t.Fatalf("Lsetxattr(%s) failed: %v", name, err)
}
err := unix.Lremovexattr(state.MountPath(name), "user.foo")
if err != nil {
t.Fatalf("Lremovexattr(%s) failed: %v", name, err)
}
for _, path := range []string{state.MountPath(name), state.RootPath(name)} {
buf := make([]byte, 32)
if _, err := unix.Lgetxattr(path, "user.foo", buf); err == nil {
t.Fatalf("Lgetxattr(%s) succeeded but want error", path)
}
}
}
}
func TestReadWrite_RemovexattrOnScaffoldDirectory(t *testing.T) {
state := utils.MountSetup(t, "--xattrs", "--mapping=rw:/:%ROOT%", "--mapping=rw:/scaffold/dir:%ROOT%")
defer state.TearDown(t)
path := state.MountPath("scaffold")
wantErr := utils.WriteErrorForUnwritableNode()
if err := unix.Lremovexattr(path, "user.foo"); err != wantErr {
t.Errorf("Invalid error from Lremovexattr for %s: got %v, want %v", path, err, wantErr)
}
}
func TestReadWrite_RemovexattrDisabled(t *testing.T) {
state := utils.MountSetup(t, "--mapping=ro:/:%ROOT%")
defer state.TearDown(t)
utils.MustMkdirAll(t, state.RootPath("dir"), 0755)
var wantErr error
switch runtime.GOOS {
case "darwin":
wantErr = utils.MissingXattrErr
case "linux":
wantErr = unix.EOPNOTSUPP
default:
panic("Don't know how this test behaves on this platform")
}
if err := unix.Lremovexattr(state.MountPath("dir"), "user.foo"); err != wantErr {
t.Fatalf("Lremovexattr should have failed with %v, got %v", wantErr, err)
}
}
func TestReadWrite_Fremovexattr(t *testing.T) {
testXattrsOnDeletedFiles(t, unix.EACCES, func(fd int) error {
return unix.Fremovexattr(fd, "user.foo")
})
}