This forces all syscall functions, notably filesystem, to return numeric codes as opposed to mapping in two different areas. The result of this change is better consolidation in call sites of `sysfs.FS`, while further refactoring is needed to address consolidation of file errors. Signed-off-by: Adrian Cole <adrian@tetrate.io>
900 lines
23 KiB
Go
900 lines
23 KiB
Go
package sysfs
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tetratelabs/wazero/internal/fstest"
|
|
"github.com/tetratelabs/wazero/internal/platform"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
)
|
|
|
|
func TestNewDirFS(t *testing.T) {
|
|
testFS := NewDirFS(".")
|
|
|
|
// Guest can look up /
|
|
f, errno := testFS.OpenFile("/", os.O_RDONLY, 0)
|
|
require.Zero(t, errno)
|
|
require.NoError(t, f.Close())
|
|
|
|
t.Run("host path not found", func(t *testing.T) {
|
|
testFS := NewDirFS("a")
|
|
_, errno = testFS.OpenFile(".", os.O_RDONLY, 0)
|
|
require.EqualErrno(t, syscall.ENOENT, errno)
|
|
})
|
|
t.Run("host path not a directory", func(t *testing.T) {
|
|
arg0 := os.Args[0] // should be safe in scratch tests which don't have the source mounted.
|
|
|
|
testFS := NewDirFS(arg0)
|
|
d, errno := testFS.OpenFile(".", os.O_RDONLY, 0)
|
|
require.Zero(t, errno)
|
|
_, err := d.(fs.ReadDirFile).ReadDir(-1)
|
|
require.EqualErrno(t, syscall.ENOTDIR, platform.UnwrapOSError(err))
|
|
})
|
|
}
|
|
|
|
func TestDirFS_join(t *testing.T) {
|
|
testFS := NewDirFS("/").(*dirFS)
|
|
require.Equal(t, "/", testFS.join(""))
|
|
require.Equal(t, "/", testFS.join("."))
|
|
require.Equal(t, "/", testFS.join("/"))
|
|
require.Equal(t, "/tmp", testFS.join("tmp"))
|
|
|
|
testFS = NewDirFS(".").(*dirFS)
|
|
require.Equal(t, ".", testFS.join(""))
|
|
require.Equal(t, ".", testFS.join("."))
|
|
require.Equal(t, ".", testFS.join("/"))
|
|
require.Equal(t, "."+string(os.PathSeparator)+"tmp", testFS.join("tmp"))
|
|
}
|
|
|
|
func TestDirFS_String(t *testing.T) {
|
|
testFS := NewDirFS(".")
|
|
|
|
// String has the name of the path entered
|
|
require.Equal(t, ".", testFS.String())
|
|
}
|
|
|
|
func TestDirFS_Lstat(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
|
|
|
testFS := NewDirFS(tmpDir)
|
|
for _, path := range []string{"animals.txt", "sub", "sub-link"} {
|
|
require.Zero(t, testFS.Symlink(path, path+"-link"))
|
|
}
|
|
|
|
testLstat(t, testFS)
|
|
}
|
|
|
|
func TestDirFS_MkDir(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "mkdir"
|
|
realPath := path.Join(tmpDir, name)
|
|
|
|
t.Run("doesn't exist", func(t *testing.T) {
|
|
require.Zero(t, testFS.Mkdir(name, fs.ModeDir))
|
|
|
|
stat, err := os.Stat(realPath)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, name, stat.Name())
|
|
require.True(t, stat.IsDir())
|
|
})
|
|
|
|
t.Run("dir exists", func(t *testing.T) {
|
|
err := testFS.Mkdir(name, fs.ModeDir)
|
|
require.EqualErrno(t, syscall.EEXIST, err)
|
|
})
|
|
|
|
t.Run("file exists", func(t *testing.T) {
|
|
require.NoError(t, os.Remove(realPath))
|
|
require.NoError(t, os.Mkdir(realPath, 0o700))
|
|
|
|
err := testFS.Mkdir(name, fs.ModeDir)
|
|
require.EqualErrno(t, syscall.EEXIST, err)
|
|
})
|
|
t.Run("try creating on file", func(t *testing.T) {
|
|
filePath := path.Join("non-existing-dir", "foo.txt")
|
|
err := testFS.Mkdir(filePath, fs.ModeDir)
|
|
require.EqualErrno(t, syscall.ENOENT, err)
|
|
})
|
|
|
|
// Remove the path so that we can test creating it with perms.
|
|
require.NoError(t, os.Remove(realPath))
|
|
|
|
// Setting mode only applies to files on windows
|
|
if runtime.GOOS != "windows" {
|
|
t.Run("dir", func(t *testing.T) {
|
|
require.NoError(t, os.Mkdir(realPath, 0o444))
|
|
defer os.RemoveAll(realPath)
|
|
testChmod(t, testFS, name)
|
|
})
|
|
}
|
|
|
|
t.Run("file", func(t *testing.T) {
|
|
require.NoError(t, os.WriteFile(realPath, nil, 0o444))
|
|
defer os.RemoveAll(realPath)
|
|
testChmod(t, testFS, name)
|
|
})
|
|
}
|
|
|
|
func testChmod(t *testing.T, testFS FS, path string) {
|
|
// Test base case, using 0o444 not 0o400 for read-back on windows.
|
|
requireMode(t, testFS, path, 0o444)
|
|
|
|
// Test adding write, using 0o666 not 0o600 for read-back on windows.
|
|
require.Zero(t, testFS.Chmod(path, 0o666))
|
|
requireMode(t, testFS, path, 0o666)
|
|
|
|
if runtime.GOOS != "windows" {
|
|
// Test clearing group and world, setting owner read+execute.
|
|
require.Zero(t, testFS.Chmod(path, 0o500))
|
|
requireMode(t, testFS, path, 0o500)
|
|
}
|
|
}
|
|
|
|
func requireMode(t *testing.T, testFS FS, path string, mode fs.FileMode) {
|
|
st, errno := testFS.Stat(path)
|
|
require.Zero(t, errno)
|
|
|
|
require.Equal(t, mode, st.Mode.Perm())
|
|
}
|
|
|
|
func TestDirFS_Rename(t *testing.T) {
|
|
t.Run("from doesn't exist", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
file1 := "file1"
|
|
file1Path := path.Join(tmpDir, file1)
|
|
err := os.WriteFile(file1Path, []byte{1}, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
err = testFS.Rename("file2", file1)
|
|
require.EqualErrno(t, syscall.ENOENT, err)
|
|
})
|
|
t.Run("file to non-exist", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
file1 := "file1"
|
|
file1Path := path.Join(tmpDir, file1)
|
|
file1Contents := []byte{1}
|
|
errno := os.WriteFile(file1Path, file1Contents, 0o600)
|
|
require.NoError(t, errno)
|
|
|
|
file2 := "file2"
|
|
file2Path := path.Join(tmpDir, file2)
|
|
errno = testFS.Rename(file1, file2)
|
|
require.Zero(t, errno)
|
|
|
|
// Show the prior path no longer exists
|
|
_, errno = os.Stat(file1Path)
|
|
require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(errno))
|
|
|
|
s, err := os.Stat(file2Path)
|
|
require.NoError(t, err)
|
|
require.False(t, s.IsDir())
|
|
})
|
|
t.Run("dir to non-exist", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
dir1 := "dir1"
|
|
dir1Path := path.Join(tmpDir, dir1)
|
|
require.NoError(t, os.Mkdir(dir1Path, 0o700))
|
|
|
|
dir2 := "dir2"
|
|
dir2Path := path.Join(tmpDir, dir2)
|
|
errrno := testFS.Rename(dir1, dir2)
|
|
require.Zero(t, errrno)
|
|
|
|
// Show the prior path no longer exists
|
|
_, err := os.Stat(dir1Path)
|
|
require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
|
|
|
|
s, err := os.Stat(dir2Path)
|
|
require.NoError(t, err)
|
|
require.True(t, s.IsDir())
|
|
})
|
|
t.Run("dir to file", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
dir1 := "dir1"
|
|
dir1Path := path.Join(tmpDir, dir1)
|
|
require.NoError(t, os.Mkdir(dir1Path, 0o700))
|
|
|
|
dir2 := "dir2"
|
|
dir2Path := path.Join(tmpDir, dir2)
|
|
|
|
// write a file to that path
|
|
f, err := os.OpenFile(dir2Path, os.O_RDWR|os.O_CREATE, 0o600)
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.Close())
|
|
|
|
errno := testFS.Rename(dir1, dir2)
|
|
require.EqualErrno(t, syscall.ENOTDIR, errno)
|
|
})
|
|
t.Run("file to dir", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
file1 := "file1"
|
|
file1Path := path.Join(tmpDir, file1)
|
|
file1Contents := []byte{1}
|
|
err := os.WriteFile(file1Path, file1Contents, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
dir1 := "dir1"
|
|
dir1Path := path.Join(tmpDir, dir1)
|
|
require.NoError(t, os.Mkdir(dir1Path, 0o700))
|
|
|
|
errno := testFS.Rename(file1, dir1)
|
|
require.EqualErrno(t, syscall.EISDIR, errno)
|
|
})
|
|
|
|
// Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L567-L582
|
|
t.Run("dir to empty dir should be fine", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
dir1 := "dir1"
|
|
dir1Path := path.Join(tmpDir, dir1)
|
|
require.NoError(t, os.Mkdir(dir1Path, 0o700))
|
|
|
|
// add a file to that directory
|
|
file1 := "file1"
|
|
file1Path := path.Join(dir1Path, file1)
|
|
file1Contents := []byte{1}
|
|
err := os.WriteFile(file1Path, file1Contents, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
dir2 := "dir2"
|
|
dir2Path := path.Join(tmpDir, dir2)
|
|
require.NoError(t, os.Mkdir(dir2Path, 0o700))
|
|
|
|
errno := testFS.Rename(dir1, dir2)
|
|
require.Zero(t, errno)
|
|
|
|
// Show the prior path no longer exists
|
|
_, err = os.Stat(dir1Path)
|
|
require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
|
|
|
|
// Show the file inside that directory moved
|
|
s, err := os.Stat(path.Join(dir2Path, file1))
|
|
require.NoError(t, err)
|
|
require.False(t, s.IsDir())
|
|
})
|
|
|
|
// Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L584-L604
|
|
t.Run("dir to non empty dir should be EXIST", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
dir1 := "dir1"
|
|
dir1Path := path.Join(tmpDir, dir1)
|
|
require.NoError(t, os.Mkdir(dir1Path, 0o700))
|
|
|
|
// add a file to that directory
|
|
file1 := "file1"
|
|
file1Path := path.Join(dir1Path, file1)
|
|
file1Contents := []byte{1}
|
|
err := os.WriteFile(file1Path, file1Contents, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
dir2 := "dir2"
|
|
dir2Path := path.Join(tmpDir, dir2)
|
|
require.NoError(t, os.Mkdir(dir2Path, 0o700))
|
|
|
|
// Make the destination non-empty.
|
|
err = os.WriteFile(path.Join(dir2Path, "existing.txt"), []byte("any thing"), 0o600)
|
|
require.NoError(t, err)
|
|
|
|
errno := testFS.Rename(dir1, dir2)
|
|
require.EqualErrno(t, syscall.ENOTEMPTY, errno)
|
|
})
|
|
|
|
t.Run("file to file", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
file1 := "file1"
|
|
file1Path := path.Join(tmpDir, file1)
|
|
file1Contents := []byte{1}
|
|
err := os.WriteFile(file1Path, file1Contents, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
file2 := "file2"
|
|
file2Path := path.Join(tmpDir, file2)
|
|
file2Contents := []byte{2}
|
|
err = os.WriteFile(file2Path, file2Contents, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
errno := testFS.Rename(file1, file2)
|
|
require.Zero(t, errno)
|
|
|
|
// Show the prior path no longer exists
|
|
_, err = os.Stat(file1Path)
|
|
require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
|
|
|
|
// Show the file1 overwrote file2
|
|
b, err := os.ReadFile(file2Path)
|
|
require.NoError(t, err)
|
|
require.Equal(t, file1Contents, b)
|
|
})
|
|
t.Run("dir to itself", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
dir1 := "dir1"
|
|
dir1Path := path.Join(tmpDir, dir1)
|
|
require.NoError(t, os.Mkdir(dir1Path, 0o700))
|
|
|
|
errno := testFS.Rename(dir1, dir1)
|
|
require.Zero(t, errno)
|
|
|
|
s, err := os.Stat(dir1Path)
|
|
require.NoError(t, err)
|
|
require.True(t, s.IsDir())
|
|
})
|
|
t.Run("file to itself", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
file1 := "file1"
|
|
file1Path := path.Join(tmpDir, file1)
|
|
file1Contents := []byte{1}
|
|
err := os.WriteFile(file1Path, file1Contents, 0o600)
|
|
require.NoError(t, err)
|
|
|
|
errno := testFS.Rename(file1, file1)
|
|
require.Zero(t, errno)
|
|
|
|
b, err := os.ReadFile(file1Path)
|
|
require.NoError(t, err)
|
|
require.Equal(t, file1Contents, b)
|
|
})
|
|
}
|
|
|
|
func TestDirFS_Rmdir(t *testing.T) {
|
|
t.Run("doesn't exist", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "rmdir"
|
|
|
|
err := testFS.Rmdir(name)
|
|
require.EqualErrno(t, syscall.ENOENT, err)
|
|
})
|
|
|
|
t.Run("dir not empty", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "rmdir"
|
|
realPath := path.Join(tmpDir, name)
|
|
|
|
require.NoError(t, os.Mkdir(realPath, 0o700))
|
|
fileInDir := path.Join(realPath, "file")
|
|
require.NoError(t, os.WriteFile(fileInDir, []byte{}, 0o600))
|
|
|
|
err := testFS.Rmdir(name)
|
|
require.EqualErrno(t, syscall.ENOTEMPTY, err)
|
|
|
|
require.NoError(t, os.Remove(fileInDir))
|
|
})
|
|
|
|
t.Run("dir previously not empty", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "rmdir"
|
|
realPath := path.Join(tmpDir, name)
|
|
require.NoError(t, os.Mkdir(realPath, 0o700))
|
|
|
|
// Create a file and then delete it.
|
|
fileInDir := path.Join(realPath, "file")
|
|
require.NoError(t, os.WriteFile(fileInDir, []byte{}, 0o600))
|
|
require.NoError(t, os.Remove(fileInDir))
|
|
|
|
// After deletion, try removing directory.
|
|
errno := testFS.Rmdir(name)
|
|
require.Zero(t, errno)
|
|
})
|
|
|
|
t.Run("dir empty", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "rmdir"
|
|
realPath := path.Join(tmpDir, name)
|
|
require.NoError(t, os.Mkdir(realPath, 0o700))
|
|
require.Zero(t, testFS.Rmdir(name))
|
|
_, err := os.Stat(realPath)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("dir empty while opening", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "rmdir"
|
|
realPath := path.Join(tmpDir, name)
|
|
require.NoError(t, os.Mkdir(realPath, 0o700))
|
|
|
|
f, errno := testFS.OpenFile(name, platform.O_DIRECTORY, 0o700)
|
|
require.Zero(t, errno)
|
|
defer func() {
|
|
require.NoError(t, f.Close())
|
|
}()
|
|
|
|
require.Zero(t, testFS.Rmdir(name))
|
|
_, err := os.Stat(realPath)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("not directory", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "rmdir"
|
|
realPath := path.Join(tmpDir, name)
|
|
|
|
require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
|
|
|
|
err := testFS.Rmdir(name)
|
|
require.EqualErrno(t, syscall.ENOTDIR, err)
|
|
|
|
require.NoError(t, os.Remove(realPath))
|
|
})
|
|
}
|
|
|
|
func TestDirFS_Unlink(t *testing.T) {
|
|
t.Run("doesn't exist", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
name := "unlink"
|
|
|
|
err := testFS.Unlink(name)
|
|
require.EqualErrno(t, syscall.ENOENT, err)
|
|
})
|
|
|
|
t.Run("target: dir", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
dir := "dir"
|
|
realPath := path.Join(tmpDir, dir)
|
|
|
|
require.NoError(t, os.Mkdir(realPath, 0o700))
|
|
|
|
err := testFS.Unlink(dir)
|
|
require.EqualErrno(t, syscall.EISDIR, err)
|
|
|
|
require.NoError(t, os.Remove(realPath))
|
|
})
|
|
|
|
t.Run("target: symlink to dir", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
// Create link target dir.
|
|
subDirName := "subdir"
|
|
subDirRealPath := path.Join(tmpDir, subDirName)
|
|
require.NoError(t, os.Mkdir(subDirRealPath, 0o700))
|
|
|
|
// Create a symlink to the subdirectory.
|
|
const symlinkName = "symlink-to-dir"
|
|
require.Zero(t, testFS.Symlink("subdir", symlinkName))
|
|
|
|
// Unlinking the symlink should suceed.
|
|
err := testFS.Unlink(symlinkName)
|
|
require.Zero(t, err)
|
|
})
|
|
|
|
t.Run("file exists", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "unlink"
|
|
realPath := path.Join(tmpDir, name)
|
|
|
|
require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
|
|
|
|
require.Zero(t, testFS.Unlink(name))
|
|
|
|
_, err := os.Stat(realPath)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestDirFS_Utimesns(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
file := "file"
|
|
err := os.WriteFile(path.Join(tmpDir, file), []byte{}, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
dir := "dir"
|
|
err = os.Mkdir(path.Join(tmpDir, dir), 0o700)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("doesn't exist", func(t *testing.T) {
|
|
err := testFS.Utimens("nope", nil, true)
|
|
require.EqualErrno(t, syscall.ENOENT, err)
|
|
err = testFS.Utimens("nope", nil, false)
|
|
if platform.SupportsSymlinkNoFollow {
|
|
require.EqualErrno(t, syscall.ENOENT, err)
|
|
} else {
|
|
require.EqualErrno(t, syscall.ENOSYS, err)
|
|
}
|
|
})
|
|
|
|
// Note: This sets microsecond granularity because Windows doesn't support
|
|
// nanosecond.
|
|
//
|
|
// Negative isn't tested as most platforms don't return consistent results.
|
|
tests := []struct {
|
|
name string
|
|
times *[2]syscall.Timespec
|
|
}{
|
|
{
|
|
name: "nil",
|
|
},
|
|
{
|
|
name: "a=omit,m=omit",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: platform.UTIME_OMIT},
|
|
{Sec: 123, Nsec: platform.UTIME_OMIT},
|
|
},
|
|
},
|
|
{
|
|
name: "a=now,m=omit",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: platform.UTIME_NOW},
|
|
{Sec: 123, Nsec: platform.UTIME_OMIT},
|
|
},
|
|
},
|
|
{
|
|
name: "a=omit,m=now",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: platform.UTIME_OMIT},
|
|
{Sec: 123, Nsec: platform.UTIME_NOW},
|
|
},
|
|
},
|
|
{
|
|
name: "a=now,m=now",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: platform.UTIME_NOW},
|
|
{Sec: 123, Nsec: platform.UTIME_NOW},
|
|
},
|
|
},
|
|
{
|
|
name: "a=now,m=set",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: platform.UTIME_NOW},
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
},
|
|
},
|
|
{
|
|
name: "a=set,m=now",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
{Sec: 123, Nsec: platform.UTIME_NOW},
|
|
},
|
|
},
|
|
{
|
|
name: "a=set,m=set",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
{Sec: 223, Nsec: 5 * 1e3},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, fileType := range []string{"dir", "file", "link", "link-follow"} {
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
fileType := fileType
|
|
name := fileType + " " + tc.name
|
|
symlinkNoFollow := fileType == "link"
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
file := path.Join(tmpDir, "file")
|
|
errno := os.WriteFile(file, []byte{}, 0o700)
|
|
require.NoError(t, errno)
|
|
|
|
link := file + "-link"
|
|
require.NoError(t, os.Symlink(file, link))
|
|
|
|
dir := path.Join(tmpDir, "dir")
|
|
errno = os.Mkdir(dir, 0o700)
|
|
require.NoError(t, errno)
|
|
|
|
var path, statPath string
|
|
switch fileType {
|
|
case "dir":
|
|
path = "dir"
|
|
statPath = "dir"
|
|
case "file":
|
|
path = "file"
|
|
statPath = "file"
|
|
case "link":
|
|
path = "file-link"
|
|
statPath = "file-link"
|
|
case "link-follow":
|
|
path = "file-link"
|
|
statPath = "file"
|
|
default:
|
|
panic(tc)
|
|
}
|
|
|
|
oldSt, errno := testFS.Lstat(statPath)
|
|
require.Zero(t, errno)
|
|
|
|
errno = testFS.Utimens(path, tc.times, !symlinkNoFollow)
|
|
if symlinkNoFollow && !platform.SupportsSymlinkNoFollow {
|
|
require.EqualErrno(t, syscall.ENOSYS, errno)
|
|
return
|
|
}
|
|
require.Zero(t, errno)
|
|
|
|
newSt, errno := testFS.Lstat(statPath)
|
|
require.Zero(t, errno)
|
|
|
|
if platform.CompilerSupported() {
|
|
if tc.times != nil && tc.times[0].Nsec == platform.UTIME_OMIT {
|
|
require.Equal(t, oldSt.Atim, newSt.Atim)
|
|
} else if tc.times == nil || tc.times[0].Nsec == platform.UTIME_NOW {
|
|
now := time.Now().UnixNano()
|
|
require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now)
|
|
} else {
|
|
require.Equal(t, tc.times[0].Nano(), newSt.Atim)
|
|
}
|
|
}
|
|
|
|
// When compiler isn't supported, we can still check mtim.
|
|
if tc.times != nil && tc.times[1].Nsec == platform.UTIME_OMIT {
|
|
require.Equal(t, oldSt.Mtim, newSt.Mtim)
|
|
} else if tc.times == nil || tc.times[1].Nsec == platform.UTIME_NOW {
|
|
now := time.Now().UnixNano()
|
|
require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now)
|
|
} else {
|
|
require.Equal(t, tc.times[1].Nano(), newSt.Mtim)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDirFS_OpenFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a subdirectory, so we can test reads outside the FS root.
|
|
tmpDir = path.Join(tmpDir, t.Name())
|
|
require.NoError(t, os.Mkdir(tmpDir, 0o700))
|
|
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
|
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
testOpen_Read(t, testFS, true)
|
|
|
|
testOpen_O_RDWR(t, tmpDir, testFS)
|
|
|
|
t.Run("path outside root valid", func(t *testing.T) {
|
|
_, err := testFS.OpenFile("../foo", os.O_RDONLY, 0)
|
|
|
|
// syscall.FS allows relative path lookups
|
|
require.True(t, errors.Is(err, fs.ErrNotExist))
|
|
})
|
|
}
|
|
|
|
func TestDirFS_Stat(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
|
|
|
testFS := NewDirFS(tmpDir)
|
|
testStat(t, testFS)
|
|
|
|
// from os.TestDirFSPathsValid
|
|
if runtime.GOOS != "windows" {
|
|
t.Run("strange name", func(t *testing.T) {
|
|
name := `e:xperi\ment.txt`
|
|
require.NoError(t, os.WriteFile(path.Join(tmpDir, name), nil, 0o600))
|
|
|
|
_, errno := testFS.Stat(name)
|
|
require.Zero(t, errno)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDirFS_Truncate(t *testing.T) {
|
|
content := []byte("123456")
|
|
|
|
tests := []struct {
|
|
name string
|
|
size int64
|
|
expectedContent []byte
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "one less",
|
|
size: 5,
|
|
expectedContent: []byte("12345"),
|
|
},
|
|
{
|
|
name: "same",
|
|
size: 6,
|
|
expectedContent: content,
|
|
},
|
|
{
|
|
name: "zero",
|
|
size: 0,
|
|
expectedContent: []byte(""),
|
|
},
|
|
{
|
|
name: "larger",
|
|
size: 106,
|
|
expectedContent: append(content, make([]byte, 100)...),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "truncate"
|
|
realPath := path.Join(tmpDir, name)
|
|
require.NoError(t, os.WriteFile(realPath, content, 0o0600))
|
|
|
|
errno := testFS.Truncate(name, tc.size)
|
|
require.Zero(t, errno)
|
|
|
|
actual, err := os.ReadFile(realPath)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedContent, actual)
|
|
})
|
|
}
|
|
|
|
tmpDir := t.TempDir()
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
name := "truncate"
|
|
realPath := path.Join(tmpDir, name)
|
|
|
|
if runtime.GOOS != "windows" {
|
|
// TODO: os.Truncate on windows can create the file even when it
|
|
// doesn't exist.
|
|
t.Run("doesn't exist", func(t *testing.T) {
|
|
err := testFS.Truncate(name, 0)
|
|
require.Equal(t, syscall.ENOENT, err)
|
|
})
|
|
}
|
|
|
|
t.Run("not file", func(t *testing.T) {
|
|
require.NoError(t, os.Mkdir(realPath, 0o700))
|
|
|
|
err := testFS.Truncate(name, 0)
|
|
require.Equal(t, syscall.EISDIR, err)
|
|
|
|
require.NoError(t, os.Remove(realPath))
|
|
})
|
|
|
|
require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
|
|
|
|
t.Run("negative", func(t *testing.T) {
|
|
err := testFS.Truncate(name, -1)
|
|
require.Equal(t, syscall.EINVAL, err)
|
|
})
|
|
}
|
|
|
|
func TestDirFS_TestFS(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Set up the test files
|
|
tmpDir := t.TempDir()
|
|
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
|
|
|
// Create a writeable filesystem
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
// Run TestFS via the adapter
|
|
require.NoError(t, fstest.TestFS(testFS.(fs.FS)))
|
|
}
|
|
|
|
// Test_fdReaddir_opened_file_written ensures that writing files to the already-opened directory
|
|
// is visible. This is significant on Windows.
|
|
// https://github.com/ziglang/zig/blob/2ccff5115454bab4898bae3de88f5619310bc5c1/lib/std/fs/test.zig#L156-L184
|
|
func Test_fdReaddir_opened_file_written(t *testing.T) {
|
|
root := t.TempDir()
|
|
testFS := NewDirFS(root)
|
|
|
|
const readDirTarget = "dir"
|
|
errno := testFS.Mkdir(readDirTarget, 0o700)
|
|
require.Zero(t, errno)
|
|
|
|
// Open the directory, before writing files!
|
|
dirFile, errno := testFS.OpenFile(readDirTarget, os.O_RDONLY, 0)
|
|
require.Zero(t, errno)
|
|
defer dirFile.Close()
|
|
|
|
// Then write a file to the directory.
|
|
f, err := os.Create(path.Join(root, readDirTarget, "my-file"))
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
|
|
dir, ok := dirFile.(fs.ReadDirFile)
|
|
require.True(t, ok)
|
|
|
|
entries, err := dir.ReadDir(-1)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(entries))
|
|
require.Equal(t, "my-file", entries[0].Name())
|
|
}
|
|
|
|
func TestDirFS_Link(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Set up the test files
|
|
tmpDir := t.TempDir()
|
|
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
|
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
require.EqualErrno(t, testFS.Link("cat", ""), syscall.ENOENT)
|
|
require.EqualErrno(t, testFS.Link("sub/test.txt", "sub/test.txt"), syscall.EEXIST)
|
|
require.EqualErrno(t, testFS.Link("sub/test.txt", "."), syscall.EEXIST)
|
|
require.EqualErrno(t, testFS.Link("sub/test.txt", ""), syscall.EEXIST)
|
|
require.EqualErrno(t, testFS.Link("sub/test.txt", "/"), syscall.EEXIST)
|
|
require.Zero(t, testFS.Link("sub/test.txt", "foo"))
|
|
}
|
|
|
|
func TestDirFS_Symlink(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Set up the test files
|
|
tmpDir := t.TempDir()
|
|
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
|
|
|
testFS := NewDirFS(tmpDir)
|
|
|
|
require.EqualErrno(t, testFS.Symlink("sub/test.txt", "sub/test.txt"), syscall.EEXIST)
|
|
// Non-existing old name is allowed.
|
|
require.Zero(t, testFS.Symlink("non-existing", "aa"))
|
|
require.Zero(t, testFS.Symlink("sub/", "symlinked-subdir"))
|
|
|
|
st, err := os.Lstat(path.Join(tmpDir, "aa"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "aa", st.Name())
|
|
require.True(t, st.Mode()&fs.ModeSymlink > 0 && !st.IsDir())
|
|
|
|
st, err = os.Lstat(path.Join(tmpDir, "symlinked-subdir"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "symlinked-subdir", st.Name())
|
|
require.True(t, st.Mode()&fs.ModeSymlink > 0)
|
|
}
|
|
|
|
func TestDirFS_Readlink(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
|
|
|
testFS := NewDirFS(tmpDir)
|
|
testReadlink(t, testFS, testFS)
|
|
}
|