github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/dirfs_test.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"errors"
     5  	"io/fs"
     6  	"os"
     7  	"path"
     8  	"runtime"
     9  	"syscall"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/tetratelabs/wazero/internal/fsapi"
    14  	"github.com/tetratelabs/wazero/internal/fstest"
    15  	"github.com/tetratelabs/wazero/internal/platform"
    16  	"github.com/tetratelabs/wazero/internal/testing/require"
    17  )
    18  
    19  func TestNewDirFS(t *testing.T) {
    20  	testFS := NewDirFS(".")
    21  
    22  	// Guest can look up /
    23  	f, errno := testFS.OpenFile("/", os.O_RDONLY, 0)
    24  	require.EqualErrno(t, 0, errno)
    25  	require.EqualErrno(t, 0, f.Close())
    26  
    27  	t.Run("host path not found", func(t *testing.T) {
    28  		testFS := NewDirFS("a")
    29  		_, errno = testFS.OpenFile(".", os.O_RDONLY, 0)
    30  		require.EqualErrno(t, syscall.ENOENT, errno)
    31  	})
    32  	t.Run("host path not a directory", func(t *testing.T) {
    33  		arg0 := os.Args[0] // should be safe in scratch tests which don't have the source mounted.
    34  
    35  		testFS := NewDirFS(arg0)
    36  		d, errno := testFS.OpenFile(".", os.O_RDONLY, 0)
    37  		require.EqualErrno(t, 0, errno)
    38  		_, errno = d.Readdir(-1)
    39  		require.EqualErrno(t, syscall.ENOTDIR, errno)
    40  	})
    41  }
    42  
    43  func TestDirFS_join(t *testing.T) {
    44  	testFS := NewDirFS("/").(*dirFS)
    45  	require.Equal(t, "/", testFS.join(""))
    46  	require.Equal(t, "/", testFS.join("."))
    47  	require.Equal(t, "/", testFS.join("/"))
    48  	require.Equal(t, "/tmp", testFS.join("tmp"))
    49  
    50  	testFS = NewDirFS(".").(*dirFS)
    51  	require.Equal(t, ".", testFS.join(""))
    52  	require.Equal(t, ".", testFS.join("."))
    53  	require.Equal(t, ".", testFS.join("/"))
    54  	require.Equal(t, "."+string(os.PathSeparator)+"tmp", testFS.join("tmp"))
    55  }
    56  
    57  func TestDirFS_String(t *testing.T) {
    58  	testFS := NewDirFS(".")
    59  
    60  	// String has the name of the path entered
    61  	require.Equal(t, ".", testFS.String())
    62  }
    63  
    64  func TestDirFS_Lstat(t *testing.T) {
    65  	tmpDir := t.TempDir()
    66  	require.NoError(t, fstest.WriteTestFiles(tmpDir))
    67  
    68  	testFS := NewDirFS(tmpDir)
    69  	for _, path := range []string{"animals.txt", "sub", "sub-link"} {
    70  		require.EqualErrno(t, 0, testFS.Symlink(path, path+"-link"))
    71  	}
    72  
    73  	testLstat(t, testFS)
    74  }
    75  
    76  func TestDirFS_MkDir(t *testing.T) {
    77  	tmpDir := t.TempDir()
    78  	testFS := NewDirFS(tmpDir)
    79  
    80  	name := "mkdir"
    81  	realPath := path.Join(tmpDir, name)
    82  
    83  	t.Run("doesn't exist", func(t *testing.T) {
    84  		require.EqualErrno(t, 0, testFS.Mkdir(name, fs.ModeDir))
    85  
    86  		stat, err := os.Stat(realPath)
    87  		require.NoError(t, err)
    88  
    89  		require.Equal(t, name, stat.Name())
    90  		require.True(t, stat.IsDir())
    91  	})
    92  
    93  	t.Run("dir exists", func(t *testing.T) {
    94  		err := testFS.Mkdir(name, fs.ModeDir)
    95  		require.EqualErrno(t, syscall.EEXIST, err)
    96  	})
    97  
    98  	t.Run("file exists", func(t *testing.T) {
    99  		require.NoError(t, os.Remove(realPath))
   100  		require.NoError(t, os.Mkdir(realPath, 0o700))
   101  
   102  		err := testFS.Mkdir(name, fs.ModeDir)
   103  		require.EqualErrno(t, syscall.EEXIST, err)
   104  	})
   105  	t.Run("try creating on file", func(t *testing.T) {
   106  		filePath := path.Join("non-existing-dir", "foo.txt")
   107  		err := testFS.Mkdir(filePath, fs.ModeDir)
   108  		require.EqualErrno(t, syscall.ENOENT, err)
   109  	})
   110  
   111  	// Remove the path so that we can test creating it with perms.
   112  	require.NoError(t, os.Remove(realPath))
   113  
   114  	// Setting mode only applies to files on windows
   115  	if runtime.GOOS != "windows" {
   116  		t.Run("dir", func(t *testing.T) {
   117  			require.NoError(t, os.Mkdir(realPath, 0o444))
   118  			defer os.RemoveAll(realPath)
   119  			testChmod(t, testFS, name)
   120  		})
   121  	}
   122  
   123  	t.Run("file", func(t *testing.T) {
   124  		require.NoError(t, os.WriteFile(realPath, nil, 0o444))
   125  		defer os.RemoveAll(realPath)
   126  		testChmod(t, testFS, name)
   127  	})
   128  }
   129  
   130  func testChmod(t *testing.T, testFS fsapi.FS, path string) {
   131  	// Test base case, using 0o444 not 0o400 for read-back on windows.
   132  	requireMode(t, testFS, path, 0o444)
   133  
   134  	// Test adding write, using 0o666 not 0o600 for read-back on windows.
   135  	require.EqualErrno(t, 0, testFS.Chmod(path, 0o666))
   136  	requireMode(t, testFS, path, 0o666)
   137  
   138  	if runtime.GOOS != "windows" {
   139  		// Test clearing group and world, setting owner read+execute.
   140  		require.EqualErrno(t, 0, testFS.Chmod(path, 0o500))
   141  		requireMode(t, testFS, path, 0o500)
   142  	}
   143  }
   144  
   145  func requireMode(t *testing.T, testFS fsapi.FS, path string, mode fs.FileMode) {
   146  	st, errno := testFS.Stat(path)
   147  	require.EqualErrno(t, 0, errno)
   148  
   149  	require.Equal(t, mode, st.Mode.Perm())
   150  }
   151  
   152  func TestDirFS_Rename(t *testing.T) {
   153  	t.Run("from doesn't exist", func(t *testing.T) {
   154  		tmpDir := t.TempDir()
   155  		testFS := NewDirFS(tmpDir)
   156  
   157  		file1 := "file1"
   158  		file1Path := path.Join(tmpDir, file1)
   159  		err := os.WriteFile(file1Path, []byte{1}, 0o600)
   160  		require.NoError(t, err)
   161  
   162  		err = testFS.Rename("file2", file1)
   163  		require.EqualErrno(t, syscall.ENOENT, err)
   164  	})
   165  	t.Run("file to non-exist", func(t *testing.T) {
   166  		tmpDir := t.TempDir()
   167  		testFS := NewDirFS(tmpDir)
   168  
   169  		file1 := "file1"
   170  		file1Path := path.Join(tmpDir, file1)
   171  		file1Contents := []byte{1}
   172  		errno := os.WriteFile(file1Path, file1Contents, 0o600)
   173  		require.NoError(t, errno)
   174  
   175  		file2 := "file2"
   176  		file2Path := path.Join(tmpDir, file2)
   177  		errno = testFS.Rename(file1, file2)
   178  		require.EqualErrno(t, 0, errno)
   179  
   180  		// Show the prior path no longer exists
   181  		_, errno = os.Stat(file1Path)
   182  		require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(errno))
   183  
   184  		s, err := os.Stat(file2Path)
   185  		require.NoError(t, err)
   186  		require.False(t, s.IsDir())
   187  	})
   188  	t.Run("dir to non-exist", func(t *testing.T) {
   189  		tmpDir := t.TempDir()
   190  		testFS := NewDirFS(tmpDir)
   191  
   192  		dir1 := "dir1"
   193  		dir1Path := path.Join(tmpDir, dir1)
   194  		require.NoError(t, os.Mkdir(dir1Path, 0o700))
   195  
   196  		dir2 := "dir2"
   197  		dir2Path := path.Join(tmpDir, dir2)
   198  		errrno := testFS.Rename(dir1, dir2)
   199  		require.EqualErrno(t, 0, errrno)
   200  
   201  		// Show the prior path no longer exists
   202  		_, err := os.Stat(dir1Path)
   203  		require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
   204  
   205  		s, err := os.Stat(dir2Path)
   206  		require.NoError(t, err)
   207  		require.True(t, s.IsDir())
   208  	})
   209  	t.Run("dir to file", func(t *testing.T) {
   210  		tmpDir := t.TempDir()
   211  		testFS := NewDirFS(tmpDir)
   212  
   213  		dir1 := "dir1"
   214  		dir1Path := path.Join(tmpDir, dir1)
   215  		require.NoError(t, os.Mkdir(dir1Path, 0o700))
   216  
   217  		dir2 := "dir2"
   218  		dir2Path := path.Join(tmpDir, dir2)
   219  
   220  		// write a file to that path
   221  		f, err := os.OpenFile(dir2Path, os.O_RDWR|os.O_CREATE, 0o600)
   222  		require.NoError(t, err)
   223  		require.NoError(t, f.Close())
   224  
   225  		errno := testFS.Rename(dir1, dir2)
   226  		require.EqualErrno(t, syscall.ENOTDIR, errno)
   227  	})
   228  	t.Run("file to dir", func(t *testing.T) {
   229  		tmpDir := t.TempDir()
   230  		testFS := NewDirFS(tmpDir)
   231  
   232  		file1 := "file1"
   233  		file1Path := path.Join(tmpDir, file1)
   234  		file1Contents := []byte{1}
   235  		err := os.WriteFile(file1Path, file1Contents, 0o600)
   236  		require.NoError(t, err)
   237  
   238  		dir1 := "dir1"
   239  		dir1Path := path.Join(tmpDir, dir1)
   240  		require.NoError(t, os.Mkdir(dir1Path, 0o700))
   241  
   242  		errno := testFS.Rename(file1, dir1)
   243  		require.EqualErrno(t, syscall.EISDIR, errno)
   244  	})
   245  
   246  	// Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L567-L582
   247  	t.Run("dir to empty dir should be fine", func(t *testing.T) {
   248  		tmpDir := t.TempDir()
   249  		testFS := NewDirFS(tmpDir)
   250  
   251  		dir1 := "dir1"
   252  		dir1Path := path.Join(tmpDir, dir1)
   253  		require.NoError(t, os.Mkdir(dir1Path, 0o700))
   254  
   255  		// add a file to that directory
   256  		file1 := "file1"
   257  		file1Path := path.Join(dir1Path, file1)
   258  		file1Contents := []byte{1}
   259  		err := os.WriteFile(file1Path, file1Contents, 0o600)
   260  		require.NoError(t, err)
   261  
   262  		dir2 := "dir2"
   263  		dir2Path := path.Join(tmpDir, dir2)
   264  		require.NoError(t, os.Mkdir(dir2Path, 0o700))
   265  
   266  		errno := testFS.Rename(dir1, dir2)
   267  		require.EqualErrno(t, 0, errno)
   268  
   269  		// Show the prior path no longer exists
   270  		_, err = os.Stat(dir1Path)
   271  		require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
   272  
   273  		// Show the file inside that directory moved
   274  		s, err := os.Stat(path.Join(dir2Path, file1))
   275  		require.NoError(t, err)
   276  		require.False(t, s.IsDir())
   277  	})
   278  
   279  	// Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L584-L604
   280  	t.Run("dir to non empty dir should be EXIST", func(t *testing.T) {
   281  		tmpDir := t.TempDir()
   282  		testFS := NewDirFS(tmpDir)
   283  
   284  		dir1 := "dir1"
   285  		dir1Path := path.Join(tmpDir, dir1)
   286  		require.NoError(t, os.Mkdir(dir1Path, 0o700))
   287  
   288  		// add a file to that directory
   289  		file1 := "file1"
   290  		file1Path := path.Join(dir1Path, file1)
   291  		file1Contents := []byte{1}
   292  		err := os.WriteFile(file1Path, file1Contents, 0o600)
   293  		require.NoError(t, err)
   294  
   295  		dir2 := "dir2"
   296  		dir2Path := path.Join(tmpDir, dir2)
   297  		require.NoError(t, os.Mkdir(dir2Path, 0o700))
   298  
   299  		// Make the destination non-empty.
   300  		err = os.WriteFile(path.Join(dir2Path, "existing.txt"), []byte("any thing"), 0o600)
   301  		require.NoError(t, err)
   302  
   303  		errno := testFS.Rename(dir1, dir2)
   304  		require.EqualErrno(t, syscall.ENOTEMPTY, errno)
   305  	})
   306  
   307  	t.Run("file to file", func(t *testing.T) {
   308  		tmpDir := t.TempDir()
   309  		testFS := NewDirFS(tmpDir)
   310  
   311  		file1 := "file1"
   312  		file1Path := path.Join(tmpDir, file1)
   313  		file1Contents := []byte{1}
   314  		err := os.WriteFile(file1Path, file1Contents, 0o600)
   315  		require.NoError(t, err)
   316  
   317  		file2 := "file2"
   318  		file2Path := path.Join(tmpDir, file2)
   319  		file2Contents := []byte{2}
   320  		err = os.WriteFile(file2Path, file2Contents, 0o600)
   321  		require.NoError(t, err)
   322  
   323  		errno := testFS.Rename(file1, file2)
   324  		require.EqualErrno(t, 0, errno)
   325  
   326  		// Show the prior path no longer exists
   327  		_, err = os.Stat(file1Path)
   328  		require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
   329  
   330  		// Show the file1 overwrote file2
   331  		b, err := os.ReadFile(file2Path)
   332  		require.NoError(t, err)
   333  		require.Equal(t, file1Contents, b)
   334  	})
   335  	t.Run("dir to itself", func(t *testing.T) {
   336  		tmpDir := t.TempDir()
   337  		testFS := NewDirFS(tmpDir)
   338  
   339  		dir1 := "dir1"
   340  		dir1Path := path.Join(tmpDir, dir1)
   341  		require.NoError(t, os.Mkdir(dir1Path, 0o700))
   342  
   343  		errno := testFS.Rename(dir1, dir1)
   344  		require.EqualErrno(t, 0, errno)
   345  
   346  		s, err := os.Stat(dir1Path)
   347  		require.NoError(t, err)
   348  		require.True(t, s.IsDir())
   349  	})
   350  	t.Run("file to itself", func(t *testing.T) {
   351  		tmpDir := t.TempDir()
   352  		testFS := NewDirFS(tmpDir)
   353  
   354  		file1 := "file1"
   355  		file1Path := path.Join(tmpDir, file1)
   356  		file1Contents := []byte{1}
   357  		err := os.WriteFile(file1Path, file1Contents, 0o600)
   358  		require.NoError(t, err)
   359  
   360  		errno := testFS.Rename(file1, file1)
   361  		require.EqualErrno(t, 0, errno)
   362  
   363  		b, err := os.ReadFile(file1Path)
   364  		require.NoError(t, err)
   365  		require.Equal(t, file1Contents, b)
   366  	})
   367  }
   368  
   369  func TestDirFS_Rmdir(t *testing.T) {
   370  	t.Run("doesn't exist", func(t *testing.T) {
   371  		tmpDir := t.TempDir()
   372  		testFS := NewDirFS(tmpDir)
   373  
   374  		name := "rmdir"
   375  
   376  		err := testFS.Rmdir(name)
   377  		require.EqualErrno(t, syscall.ENOENT, err)
   378  	})
   379  
   380  	t.Run("dir not empty", func(t *testing.T) {
   381  		tmpDir := t.TempDir()
   382  		testFS := NewDirFS(tmpDir)
   383  
   384  		name := "rmdir"
   385  		realPath := path.Join(tmpDir, name)
   386  
   387  		require.NoError(t, os.Mkdir(realPath, 0o700))
   388  		fileInDir := path.Join(realPath, "file")
   389  		require.NoError(t, os.WriteFile(fileInDir, []byte{}, 0o600))
   390  
   391  		err := testFS.Rmdir(name)
   392  		require.EqualErrno(t, syscall.ENOTEMPTY, err)
   393  
   394  		require.NoError(t, os.Remove(fileInDir))
   395  	})
   396  
   397  	t.Run("dir previously not empty", func(t *testing.T) {
   398  		tmpDir := t.TempDir()
   399  		testFS := NewDirFS(tmpDir)
   400  
   401  		name := "rmdir"
   402  		realPath := path.Join(tmpDir, name)
   403  		require.NoError(t, os.Mkdir(realPath, 0o700))
   404  
   405  		// Create a file and then delete it.
   406  		fileInDir := path.Join(realPath, "file")
   407  		require.NoError(t, os.WriteFile(fileInDir, []byte{}, 0o600))
   408  		require.NoError(t, os.Remove(fileInDir))
   409  
   410  		// After deletion, try removing directory.
   411  		errno := testFS.Rmdir(name)
   412  		require.EqualErrno(t, 0, errno)
   413  	})
   414  
   415  	t.Run("dir empty", func(t *testing.T) {
   416  		tmpDir := t.TempDir()
   417  		testFS := NewDirFS(tmpDir)
   418  
   419  		name := "rmdir"
   420  		realPath := path.Join(tmpDir, name)
   421  		require.NoError(t, os.Mkdir(realPath, 0o700))
   422  		require.EqualErrno(t, 0, testFS.Rmdir(name))
   423  		_, err := os.Stat(realPath)
   424  		require.Error(t, err)
   425  	})
   426  
   427  	t.Run("dir empty while opening", func(t *testing.T) {
   428  		tmpDir := t.TempDir()
   429  		testFS := NewDirFS(tmpDir)
   430  
   431  		name := "rmdir"
   432  		realPath := path.Join(tmpDir, name)
   433  		require.NoError(t, os.Mkdir(realPath, 0o700))
   434  
   435  		f, errno := testFS.OpenFile(name, fsapi.O_DIRECTORY, 0o700)
   436  		require.EqualErrno(t, 0, errno)
   437  		defer f.Close()
   438  
   439  		require.EqualErrno(t, 0, testFS.Rmdir(name))
   440  		_, err := os.Stat(realPath)
   441  		require.Error(t, err)
   442  	})
   443  
   444  	t.Run("not directory", func(t *testing.T) {
   445  		tmpDir := t.TempDir()
   446  		testFS := NewDirFS(tmpDir)
   447  
   448  		name := "rmdir"
   449  		realPath := path.Join(tmpDir, name)
   450  
   451  		require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
   452  
   453  		err := testFS.Rmdir(name)
   454  		require.EqualErrno(t, syscall.ENOTDIR, err)
   455  
   456  		require.NoError(t, os.Remove(realPath))
   457  	})
   458  }
   459  
   460  func TestDirFS_Unlink(t *testing.T) {
   461  	t.Run("doesn't exist", func(t *testing.T) {
   462  		tmpDir := t.TempDir()
   463  		testFS := NewDirFS(tmpDir)
   464  		name := "unlink"
   465  
   466  		err := testFS.Unlink(name)
   467  		require.EqualErrno(t, syscall.ENOENT, err)
   468  	})
   469  
   470  	t.Run("target: dir", func(t *testing.T) {
   471  		tmpDir := t.TempDir()
   472  		testFS := NewDirFS(tmpDir)
   473  
   474  		dir := "dir"
   475  		realPath := path.Join(tmpDir, dir)
   476  
   477  		require.NoError(t, os.Mkdir(realPath, 0o700))
   478  
   479  		err := testFS.Unlink(dir)
   480  		require.EqualErrno(t, syscall.EISDIR, err)
   481  
   482  		require.NoError(t, os.Remove(realPath))
   483  	})
   484  
   485  	t.Run("target: symlink to dir", func(t *testing.T) {
   486  		tmpDir := t.TempDir()
   487  		testFS := NewDirFS(tmpDir)
   488  
   489  		// Create link target dir.
   490  		subDirName := "subdir"
   491  		subDirRealPath := path.Join(tmpDir, subDirName)
   492  		require.NoError(t, os.Mkdir(subDirRealPath, 0o700))
   493  
   494  		// Create a symlink to the subdirectory.
   495  		const symlinkName = "symlink-to-dir"
   496  		require.EqualErrno(t, 0, testFS.Symlink("subdir", symlinkName))
   497  
   498  		// Unlinking the symlink should suceed.
   499  		errno := testFS.Unlink(symlinkName)
   500  		require.EqualErrno(t, 0, errno)
   501  	})
   502  
   503  	t.Run("file exists", func(t *testing.T) {
   504  		tmpDir := t.TempDir()
   505  		testFS := NewDirFS(tmpDir)
   506  
   507  		name := "unlink"
   508  		realPath := path.Join(tmpDir, name)
   509  
   510  		require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
   511  
   512  		require.EqualErrno(t, 0, testFS.Unlink(name))
   513  
   514  		_, err := os.Stat(realPath)
   515  		require.Error(t, err)
   516  	})
   517  }
   518  
   519  func TestDirFS_Utimesns(t *testing.T) {
   520  	tmpDir := t.TempDir()
   521  	testFS := NewDirFS(tmpDir)
   522  
   523  	file := "file"
   524  	err := os.WriteFile(path.Join(tmpDir, file), []byte{}, 0o700)
   525  	require.NoError(t, err)
   526  
   527  	dir := "dir"
   528  	err = os.Mkdir(path.Join(tmpDir, dir), 0o700)
   529  	require.NoError(t, err)
   530  
   531  	t.Run("doesn't exist", func(t *testing.T) {
   532  		err := testFS.Utimens("nope", nil, true)
   533  		require.EqualErrno(t, syscall.ENOENT, err)
   534  		err = testFS.Utimens("nope", nil, false)
   535  		if SupportsSymlinkNoFollow {
   536  			require.EqualErrno(t, syscall.ENOENT, err)
   537  		} else {
   538  			require.EqualErrno(t, syscall.ENOSYS, err)
   539  		}
   540  	})
   541  
   542  	// Note: This sets microsecond granularity because Windows doesn't support
   543  	// nanosecond.
   544  	//
   545  	// Negative isn't tested as most platforms don't return consistent results.
   546  	tests := []struct {
   547  		name  string
   548  		times *[2]syscall.Timespec
   549  	}{
   550  		{
   551  			name: "nil",
   552  		},
   553  		{
   554  			name: "a=omit,m=omit",
   555  			times: &[2]syscall.Timespec{
   556  				{Sec: 123, Nsec: UTIME_OMIT},
   557  				{Sec: 123, Nsec: UTIME_OMIT},
   558  			},
   559  		},
   560  		{
   561  			name: "a=now,m=omit",
   562  			times: &[2]syscall.Timespec{
   563  				{Sec: 123, Nsec: UTIME_NOW},
   564  				{Sec: 123, Nsec: UTIME_OMIT},
   565  			},
   566  		},
   567  		{
   568  			name: "a=omit,m=now",
   569  			times: &[2]syscall.Timespec{
   570  				{Sec: 123, Nsec: UTIME_OMIT},
   571  				{Sec: 123, Nsec: UTIME_NOW},
   572  			},
   573  		},
   574  		{
   575  			name: "a=now,m=now",
   576  			times: &[2]syscall.Timespec{
   577  				{Sec: 123, Nsec: UTIME_NOW},
   578  				{Sec: 123, Nsec: UTIME_NOW},
   579  			},
   580  		},
   581  		{
   582  			name: "a=now,m=set",
   583  			times: &[2]syscall.Timespec{
   584  				{Sec: 123, Nsec: UTIME_NOW},
   585  				{Sec: 123, Nsec: 4 * 1e3},
   586  			},
   587  		},
   588  		{
   589  			name: "a=set,m=now",
   590  			times: &[2]syscall.Timespec{
   591  				{Sec: 123, Nsec: 4 * 1e3},
   592  				{Sec: 123, Nsec: UTIME_NOW},
   593  			},
   594  		},
   595  		{
   596  			name: "a=set,m=set",
   597  			times: &[2]syscall.Timespec{
   598  				{Sec: 123, Nsec: 4 * 1e3},
   599  				{Sec: 223, Nsec: 5 * 1e3},
   600  			},
   601  		},
   602  	}
   603  
   604  	for _, fileType := range []string{"dir", "file", "link", "link-follow"} {
   605  		for _, tt := range tests {
   606  			tc := tt
   607  			fileType := fileType
   608  			name := fileType + " " + tc.name
   609  			symlinkNoFollow := fileType == "link"
   610  
   611  			t.Run(name, func(t *testing.T) {
   612  				tmpDir := t.TempDir()
   613  				testFS := NewDirFS(tmpDir)
   614  
   615  				file := path.Join(tmpDir, "file")
   616  				errno := os.WriteFile(file, []byte{}, 0o700)
   617  				require.NoError(t, errno)
   618  
   619  				link := file + "-link"
   620  				require.NoError(t, os.Symlink(file, link))
   621  
   622  				dir := path.Join(tmpDir, "dir")
   623  				errno = os.Mkdir(dir, 0o700)
   624  				require.NoError(t, errno)
   625  
   626  				var path, statPath string
   627  				switch fileType {
   628  				case "dir":
   629  					path = "dir"
   630  					statPath = "dir"
   631  				case "file":
   632  					path = "file"
   633  					statPath = "file"
   634  				case "link":
   635  					path = "file-link"
   636  					statPath = "file-link"
   637  				case "link-follow":
   638  					path = "file-link"
   639  					statPath = "file"
   640  				default:
   641  					panic(tc)
   642  				}
   643  
   644  				oldSt, errno := testFS.Lstat(statPath)
   645  				require.EqualErrno(t, 0, errno)
   646  
   647  				errno = testFS.Utimens(path, tc.times, !symlinkNoFollow)
   648  				if symlinkNoFollow && !SupportsSymlinkNoFollow {
   649  					require.EqualErrno(t, syscall.ENOSYS, errno)
   650  					return
   651  				}
   652  				require.EqualErrno(t, 0, errno)
   653  
   654  				newSt, errno := testFS.Lstat(statPath)
   655  				require.EqualErrno(t, 0, errno)
   656  
   657  				if platform.CompilerSupported() {
   658  					if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {
   659  						require.Equal(t, oldSt.Atim, newSt.Atim)
   660  					} else if tc.times == nil || tc.times[0].Nsec == UTIME_NOW {
   661  						now := time.Now().UnixNano()
   662  						require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now)
   663  					} else {
   664  						require.Equal(t, tc.times[0].Nano(), newSt.Atim)
   665  					}
   666  				}
   667  
   668  				// When compiler isn't supported, we can still check mtim.
   669  				if tc.times != nil && tc.times[1].Nsec == UTIME_OMIT {
   670  					require.Equal(t, oldSt.Mtim, newSt.Mtim)
   671  				} else if tc.times == nil || tc.times[1].Nsec == UTIME_NOW {
   672  					now := time.Now().UnixNano()
   673  					require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now)
   674  				} else {
   675  					require.Equal(t, tc.times[1].Nano(), newSt.Mtim)
   676  				}
   677  			})
   678  		}
   679  	}
   680  }
   681  
   682  func TestDirFS_OpenFile(t *testing.T) {
   683  	tmpDir := t.TempDir()
   684  
   685  	// Create a subdirectory, so we can test reads outside the fsapi.FS root.
   686  	tmpDir = path.Join(tmpDir, t.Name())
   687  	require.NoError(t, os.Mkdir(tmpDir, 0o700))
   688  	require.NoError(t, fstest.WriteTestFiles(tmpDir))
   689  
   690  	testFS := NewDirFS(tmpDir)
   691  
   692  	testOpen_Read(t, testFS, true)
   693  
   694  	testOpen_O_RDWR(t, tmpDir, testFS)
   695  
   696  	t.Run("path outside root valid", func(t *testing.T) {
   697  		_, err := testFS.OpenFile("../foo", os.O_RDONLY, 0)
   698  
   699  		// fsapi.FS allows relative path lookups
   700  		require.True(t, errors.Is(err, fs.ErrNotExist))
   701  	})
   702  }
   703  
   704  func TestDirFS_Stat(t *testing.T) {
   705  	tmpDir := t.TempDir()
   706  	require.NoError(t, fstest.WriteTestFiles(tmpDir))
   707  
   708  	testFS := NewDirFS(tmpDir)
   709  	testStat(t, testFS)
   710  
   711  	// from os.TestDirFSPathsValid
   712  	if runtime.GOOS != "windows" {
   713  		t.Run("strange name", func(t *testing.T) {
   714  			name := `e:xperi\ment.txt`
   715  			require.NoError(t, os.WriteFile(path.Join(tmpDir, name), nil, 0o600))
   716  
   717  			_, errno := testFS.Stat(name)
   718  			require.EqualErrno(t, 0, errno)
   719  		})
   720  	}
   721  }
   722  
   723  func TestDirFS_Truncate(t *testing.T) {
   724  	content := []byte("123456")
   725  
   726  	tests := []struct {
   727  		name            string
   728  		size            int64
   729  		expectedContent []byte
   730  		expectedErr     error
   731  	}{
   732  		{
   733  			name:            "one less",
   734  			size:            5,
   735  			expectedContent: []byte("12345"),
   736  		},
   737  		{
   738  			name:            "same",
   739  			size:            6,
   740  			expectedContent: content,
   741  		},
   742  		{
   743  			name:            "zero",
   744  			size:            0,
   745  			expectedContent: []byte(""),
   746  		},
   747  		{
   748  			name:            "larger",
   749  			size:            106,
   750  			expectedContent: append(content, make([]byte, 100)...),
   751  		},
   752  	}
   753  
   754  	for _, tt := range tests {
   755  		tc := tt
   756  		t.Run(tc.name, func(t *testing.T) {
   757  			tmpDir := t.TempDir()
   758  			testFS := NewDirFS(tmpDir)
   759  
   760  			name := "truncate"
   761  			realPath := path.Join(tmpDir, name)
   762  			require.NoError(t, os.WriteFile(realPath, content, 0o0666))
   763  
   764  			errno := testFS.Truncate(name, tc.size)
   765  			require.EqualErrno(t, 0, errno)
   766  
   767  			actual, err := os.ReadFile(realPath)
   768  			require.NoError(t, err)
   769  			require.Equal(t, tc.expectedContent, actual)
   770  		})
   771  	}
   772  
   773  	tmpDir := t.TempDir()
   774  	testFS := NewDirFS(tmpDir)
   775  
   776  	name := "truncate"
   777  	realPath := path.Join(tmpDir, name)
   778  
   779  	if runtime.GOOS != "windows" {
   780  		// TODO: os.Truncate on windows can create the file even when it
   781  		// doesn't exist.
   782  		t.Run("doesn't exist", func(t *testing.T) {
   783  			err := testFS.Truncate(name, 0)
   784  			require.Equal(t, syscall.ENOENT, err)
   785  		})
   786  	}
   787  
   788  	t.Run("not file", func(t *testing.T) {
   789  		require.NoError(t, os.Mkdir(realPath, 0o700))
   790  
   791  		err := testFS.Truncate(name, 0)
   792  		require.Equal(t, syscall.EISDIR, err)
   793  
   794  		require.NoError(t, os.Remove(realPath))
   795  	})
   796  
   797  	require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
   798  
   799  	t.Run("negative", func(t *testing.T) {
   800  		err := testFS.Truncate(name, -1)
   801  		require.Equal(t, syscall.EINVAL, err)
   802  	})
   803  }
   804  
   805  // Test_fdReaddir_opened_file_written ensures that writing files to the already-opened directory
   806  // is visible. This is significant on Windows.
   807  // https://github.com/ziglang/zig/blob/2ccff5115454bab4898bae3de88f5619310bc5c1/lib/std/fs/test.zig#L156-L184
   808  func Test_fdReaddir_opened_file_written(t *testing.T) {
   809  	root := t.TempDir()
   810  	testFS := NewDirFS(root)
   811  
   812  	const readDirTarget = "dir"
   813  	errno := testFS.Mkdir(readDirTarget, 0o700)
   814  	require.EqualErrno(t, 0, errno)
   815  
   816  	// Open the directory, before writing files!
   817  	dirFile, errno := testFS.OpenFile(readDirTarget, os.O_RDONLY, 0)
   818  	require.EqualErrno(t, 0, errno)
   819  	defer dirFile.Close()
   820  
   821  	// Then write a file to the directory.
   822  	f, err := os.Create(path.Join(root, readDirTarget, "my-file"))
   823  	require.NoError(t, err)
   824  	defer f.Close()
   825  
   826  	dirents, errno := dirFile.Readdir(-1)
   827  	require.EqualErrno(t, 0, errno)
   828  
   829  	require.Equal(t, 1, len(dirents))
   830  	require.Equal(t, "my-file", dirents[0].Name)
   831  }
   832  
   833  func TestDirFS_Link(t *testing.T) {
   834  	t.Parallel()
   835  
   836  	// Set up the test files
   837  	tmpDir := t.TempDir()
   838  	require.NoError(t, fstest.WriteTestFiles(tmpDir))
   839  
   840  	testFS := NewDirFS(tmpDir)
   841  
   842  	require.EqualErrno(t, testFS.Link("cat", ""), syscall.ENOENT)
   843  	require.EqualErrno(t, testFS.Link("sub/test.txt", "sub/test.txt"), syscall.EEXIST)
   844  	require.EqualErrno(t, testFS.Link("sub/test.txt", "."), syscall.EEXIST)
   845  	require.EqualErrno(t, testFS.Link("sub/test.txt", ""), syscall.EEXIST)
   846  	require.EqualErrno(t, testFS.Link("sub/test.txt", "/"), syscall.EEXIST)
   847  	require.EqualErrno(t, 0, testFS.Link("sub/test.txt", "foo"))
   848  }
   849  
   850  func TestDirFS_Symlink(t *testing.T) {
   851  	t.Parallel()
   852  
   853  	// Set up the test files
   854  	tmpDir := t.TempDir()
   855  	require.NoError(t, fstest.WriteTestFiles(tmpDir))
   856  
   857  	testFS := NewDirFS(tmpDir)
   858  
   859  	require.EqualErrno(t, syscall.EEXIST, testFS.Symlink("sub/test.txt", "sub/test.txt"))
   860  	// Non-existing old name is allowed.
   861  	require.EqualErrno(t, 0, testFS.Symlink("non-existing", "aa"))
   862  	require.EqualErrno(t, 0, testFS.Symlink("sub/", "symlinked-subdir"))
   863  
   864  	st, err := os.Lstat(path.Join(tmpDir, "aa"))
   865  	require.NoError(t, err)
   866  	require.Equal(t, "aa", st.Name())
   867  	require.True(t, st.Mode()&fs.ModeSymlink > 0 && !st.IsDir())
   868  
   869  	st, err = os.Lstat(path.Join(tmpDir, "symlinked-subdir"))
   870  	require.NoError(t, err)
   871  	require.Equal(t, "symlinked-subdir", st.Name())
   872  	require.True(t, st.Mode()&fs.ModeSymlink > 0)
   873  }
   874  
   875  func TestDirFS_Readlink(t *testing.T) {
   876  	tmpDir := t.TempDir()
   877  	require.NoError(t, fstest.WriteTestFiles(tmpDir))
   878  
   879  	testFS := NewDirFS(tmpDir)
   880  	testReadlink(t, testFS, testFS)
   881  }