github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/sysfs/file_test.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"embed"
     5  	"io"
     6  	"io/fs"
     7  	"os"
     8  	"path"
     9  	"runtime"
    10  	"testing"
    11  	gofstest "testing/fstest"
    12  
    13  	experimentalsys "github.com/bananabytelabs/wazero/experimental/sys"
    14  	"github.com/bananabytelabs/wazero/internal/fsapi"
    15  	"github.com/bananabytelabs/wazero/internal/platform"
    16  	"github.com/bananabytelabs/wazero/internal/testing/require"
    17  	"github.com/bananabytelabs/wazero/sys"
    18  )
    19  
    20  //go:embed file_test.go
    21  var embedFS embed.FS
    22  
    23  var (
    24  	//go:embed testdata
    25  	testdata   embed.FS
    26  	wazeroFile = "wazero.txt"
    27  	emptyFile  = "empty.txt"
    28  )
    29  
    30  func TestStdioFileSetNonblock(t *testing.T) {
    31  	// Test using os.Pipe as it is known to support non-blocking reads.
    32  	r, w, err := os.Pipe()
    33  	require.NoError(t, err)
    34  	defer r.Close()
    35  	defer w.Close()
    36  
    37  	rF, err := NewStdioFile(true, r)
    38  	require.NoError(t, err)
    39  
    40  	errno := rF.SetNonblock(true)
    41  	require.EqualErrno(t, 0, errno)
    42  	require.True(t, rF.IsNonblock())
    43  
    44  	errno = rF.SetNonblock(false)
    45  	require.EqualErrno(t, 0, errno)
    46  	require.False(t, rF.IsNonblock())
    47  }
    48  
    49  func TestRegularFileSetNonblock(t *testing.T) {
    50  	// Test using os.Pipe as it is known to support non-blocking reads.
    51  	r, w, err := os.Pipe()
    52  	require.NoError(t, err)
    53  	defer r.Close()
    54  	defer w.Close()
    55  
    56  	rF := newOsFile("", experimentalsys.O_RDONLY, 0, r)
    57  
    58  	errno := rF.SetNonblock(true)
    59  	require.EqualErrno(t, 0, errno)
    60  	require.True(t, rF.IsNonblock())
    61  
    62  	// Read from the file without ever writing to it should not block.
    63  	buf := make([]byte, 8)
    64  	_, e := rF.Read(buf)
    65  	require.EqualErrno(t, experimentalsys.EAGAIN, e)
    66  
    67  	errno = rF.SetNonblock(false)
    68  	require.EqualErrno(t, 0, errno)
    69  	require.False(t, rF.IsNonblock())
    70  }
    71  
    72  func TestReadFdNonblock(t *testing.T) {
    73  	// Test using os.Pipe as it is known to support non-blocking reads.
    74  	r, w, err := os.Pipe()
    75  	require.NoError(t, err)
    76  	defer r.Close()
    77  	defer w.Close()
    78  
    79  	fd := r.Fd()
    80  	errno := setNonblock(fd, true)
    81  	require.EqualErrno(t, 0, errno)
    82  
    83  	// Read from the file without ever writing to it should not block.
    84  	buf := make([]byte, 8)
    85  	_, errno = readFd(fd, buf)
    86  	require.EqualErrno(t, experimentalsys.EAGAIN, errno)
    87  }
    88  
    89  func TestWriteFdNonblock(t *testing.T) {
    90  	// Test using os.Pipe as it is known to support non-blocking reads.
    91  	r, w, err := os.Pipe()
    92  	require.NoError(t, err)
    93  	defer r.Close()
    94  	defer w.Close()
    95  
    96  	fd := w.Fd()
    97  	errno := setNonblock(fd, true)
    98  
    99  	require.EqualErrno(t, 0, errno)
   100  
   101  	// Create a buffer (the content is not relevant)
   102  	buf := make([]byte, 1024)
   103  	// Write to the file until the pipe buffer gets filled up.
   104  	numWrites := 100
   105  	for i := 0; i < numWrites; i++ {
   106  		_, e := writeFd(fd, buf)
   107  		if e != 0 {
   108  			if runtime.GOOS == "windows" {
   109  				// This is currently not supported on Windows.
   110  				require.EqualErrno(t, experimentalsys.ENOSYS, e)
   111  			} else {
   112  				require.EqualErrno(t, experimentalsys.EAGAIN, e)
   113  			}
   114  			return
   115  		}
   116  	}
   117  	t.Fatal("writeFd should return EAGAIN at some point")
   118  }
   119  
   120  func TestFileSetAppend(t *testing.T) {
   121  	tmpDir := t.TempDir()
   122  
   123  	fPath := path.Join(tmpDir, "file")
   124  	require.NoError(t, os.WriteFile(fPath, []byte("0123456789"), 0o600))
   125  
   126  	// Open without APPEND.
   127  	f, errno := OpenOSFile(fPath, experimentalsys.O_RDWR, 0o600)
   128  	require.EqualErrno(t, 0, errno)
   129  	require.False(t, f.IsAppend())
   130  
   131  	// Set the APPEND flag.
   132  	require.EqualErrno(t, 0, f.SetAppend(true))
   133  	require.True(t, f.IsAppend())
   134  
   135  	requireFileContent := func(exp string) {
   136  		buf, err := os.ReadFile(fPath)
   137  		require.NoError(t, err)
   138  		require.Equal(t, exp, string(buf))
   139  	}
   140  
   141  	// with O_APPEND flag, the data is appended to buffer.
   142  	_, errno = f.Write([]byte("wazero"))
   143  	require.EqualErrno(t, 0, errno)
   144  	requireFileContent("0123456789wazero")
   145  
   146  	// Remove the APPEND flag.
   147  	require.EqualErrno(t, 0, f.SetAppend(false))
   148  	require.False(t, f.IsAppend())
   149  
   150  	_, errno = f.Seek(0, 0)
   151  	require.EqualErrno(t, 0, errno)
   152  
   153  	// without O_APPEND flag, the data writes at offset zero
   154  	_, errno = f.Write([]byte("wazero"))
   155  	require.EqualErrno(t, 0, errno)
   156  	requireFileContent("wazero6789wazero")
   157  }
   158  
   159  func TestStdioFile_SetAppend(t *testing.T) {
   160  	// SetAppend should not affect Stdio.
   161  	file, err := NewStdioFile(false, os.Stdout)
   162  	require.NoError(t, err)
   163  	errno := file.SetAppend(true)
   164  	require.EqualErrno(t, 0, errno)
   165  	_, errno = file.Write([]byte{})
   166  	require.EqualErrno(t, 0, errno)
   167  }
   168  
   169  func TestFileIno(t *testing.T) {
   170  	tmpDir := t.TempDir()
   171  	dirFS, embedFS, mapFS := dirEmbedMapFS(t, tmpDir)
   172  
   173  	// get the expected inode
   174  	st, errno := stat(tmpDir)
   175  	require.EqualErrno(t, 0, errno)
   176  
   177  	tests := []struct {
   178  		name        string
   179  		fs          fs.FS
   180  		expectedIno sys.Inode
   181  	}{
   182  		{name: "os.DirFS", fs: dirFS, expectedIno: st.Ino},
   183  		{name: "embed.api.FS", fs: embedFS},
   184  		{name: "fstest.MapFS", fs: mapFS},
   185  	}
   186  
   187  	for _, tc := range tests {
   188  		tc := tc
   189  
   190  		t.Run(tc.name, func(t *testing.T) {
   191  			d, errno := OpenFSFile(tc.fs, ".", experimentalsys.O_RDONLY, 0)
   192  			require.EqualErrno(t, 0, errno)
   193  			defer d.Close()
   194  
   195  			ino, errno := d.Ino()
   196  			require.EqualErrno(t, 0, errno)
   197  			// Results are inconsistent, so don't validate the opposite.
   198  			if statSetsIno() {
   199  				require.Equal(t, tc.expectedIno, ino)
   200  			}
   201  		})
   202  	}
   203  
   204  	t.Run("OS", func(t *testing.T) {
   205  		d, errno := OpenOSFile(tmpDir, experimentalsys.O_RDONLY, 0)
   206  		require.EqualErrno(t, 0, errno)
   207  		defer d.Close()
   208  
   209  		ino, errno := d.Ino()
   210  		require.EqualErrno(t, 0, errno)
   211  		// Results are inconsistent, so don't validate the opposite.
   212  		if statSetsIno() {
   213  			require.Equal(t, st.Ino, ino)
   214  		}
   215  	})
   216  }
   217  
   218  // statSetsIno returns true if this will set sys.Stat_t Ino on stat. The
   219  // reverse doesn't mean it won't. Rather it is inconsistent. This is needed
   220  // because Windows on Go 1.19 sometimes, but not always returns non-zero inode.
   221  func statSetsIno() bool {
   222  	if runtime.GOOS != "windows" {
   223  		return true
   224  	} else {
   225  		// Go can read the inode via a Windows file handle, but it is
   226  		// inconsistent on Go 1.19.
   227  		return platform.IsAtLeastGo120
   228  	}
   229  }
   230  
   231  func TestFileIsDir(t *testing.T) {
   232  	dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
   233  
   234  	tests := []struct {
   235  		name string
   236  		fs   fs.FS
   237  	}{
   238  		{name: "os.DirFS", fs: dirFS},
   239  		{name: "embed.api.FS", fs: embedFS},
   240  		{name: "fstest.MapFS", fs: mapFS},
   241  	}
   242  
   243  	for _, tc := range tests {
   244  		tc := tc
   245  
   246  		t.Run(tc.name, func(t *testing.T) {
   247  			t.Run("file", func(t *testing.T) {
   248  				f, errno := OpenFSFile(tc.fs, wazeroFile, experimentalsys.O_RDONLY, 0)
   249  				require.EqualErrno(t, 0, errno)
   250  				defer f.Close()
   251  
   252  				isDir, errno := f.IsDir()
   253  				require.EqualErrno(t, 0, errno)
   254  				require.False(t, isDir)
   255  			})
   256  
   257  			t.Run("dir", func(t *testing.T) {
   258  				d, errno := OpenFSFile(tc.fs, ".", experimentalsys.O_RDONLY, 0)
   259  				require.EqualErrno(t, 0, errno)
   260  				defer d.Close()
   261  
   262  				isDir, errno := d.IsDir()
   263  				require.EqualErrno(t, 0, errno)
   264  				require.True(t, isDir)
   265  			})
   266  		})
   267  	}
   268  
   269  	t.Run("OS dir", func(t *testing.T) {
   270  		d, errno := OpenOSFile(t.TempDir(), experimentalsys.O_RDONLY, 0)
   271  		require.EqualErrno(t, 0, errno)
   272  		defer d.Close()
   273  
   274  		isDir, errno := d.IsDir()
   275  		require.EqualErrno(t, 0, errno)
   276  		require.True(t, isDir)
   277  	})
   278  }
   279  
   280  func TestFileReadAndPread(t *testing.T) {
   281  	dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
   282  
   283  	tests := []struct {
   284  		name string
   285  		fs   fs.FS
   286  	}{
   287  		{name: "os.DirFS", fs: dirFS},
   288  		{name: "embed.api.FS", fs: embedFS},
   289  		{name: "fstest.MapFS", fs: mapFS},
   290  	}
   291  
   292  	buf := make([]byte, 3)
   293  
   294  	for _, tc := range tests {
   295  		tc := tc
   296  
   297  		t.Run(tc.name, func(t *testing.T) {
   298  			f, errno := OpenFSFile(tc.fs, wazeroFile, experimentalsys.O_RDONLY, 0)
   299  			require.EqualErrno(t, 0, errno)
   300  			defer f.Close()
   301  
   302  			// The file should be readable (base case)
   303  			requireRead(t, f, buf)
   304  			require.Equal(t, "waz", string(buf))
   305  			buf = buf[:]
   306  
   307  			// We should be able to pread from zero also
   308  			requirePread(t, f, buf, 0)
   309  			require.Equal(t, "waz", string(buf))
   310  			buf = buf[:]
   311  
   312  			// If the offset didn't change, read should expect the next three chars.
   313  			requireRead(t, f, buf)
   314  			require.Equal(t, "ero", string(buf))
   315  			buf = buf[:]
   316  
   317  			// We should also be able pread from any offset
   318  			requirePread(t, f, buf, 2)
   319  			require.Equal(t, "zer", string(buf))
   320  		})
   321  	}
   322  }
   323  
   324  func TestFilePoll_POLLIN(t *testing.T) {
   325  	pflag := fsapi.POLLIN
   326  
   327  	// Test using os.Pipe as it is known to support poll.
   328  	r, w, err := os.Pipe()
   329  	require.NoError(t, err)
   330  	defer r.Close()
   331  	defer w.Close()
   332  
   333  	rF, err := NewStdioFile(true, r)
   334  	require.NoError(t, err)
   335  	buf := make([]byte, 10)
   336  	timeout := int32(0) // return immediately
   337  
   338  	// When there's nothing in the pipe, it isn't ready.
   339  	ready, errno := rF.Poll(pflag, timeout)
   340  	require.EqualErrno(t, 0, errno)
   341  	require.False(t, ready)
   342  
   343  	// Write to the pipe to make the data available
   344  	expected := []byte("wazero")
   345  	_, err = w.Write([]byte("wazero"))
   346  	require.NoError(t, err)
   347  
   348  	// We should now be able to poll ready
   349  	ready, errno = rF.Poll(pflag, timeout)
   350  	require.EqualErrno(t, 0, errno)
   351  	require.True(t, ready)
   352  
   353  	// We should now be able to read from the pipe
   354  	n, errno := rF.Read(buf)
   355  	require.EqualErrno(t, 0, errno)
   356  	require.Equal(t, len(expected), n)
   357  	require.Equal(t, expected, buf[:len(expected)])
   358  }
   359  
   360  func TestFilePoll_POLLOUT(t *testing.T) {
   361  	pflag := fsapi.POLLOUT
   362  
   363  	// Test using os.Pipe as it is known to support poll.
   364  	r, w, err := os.Pipe()
   365  	require.NoError(t, err)
   366  	defer r.Close()
   367  	defer w.Close()
   368  
   369  	wF, err := NewStdioFile(false, w)
   370  	require.NoError(t, err)
   371  	timeout := int32(0) // return immediately
   372  
   373  	// We don't yet implement write blocking.
   374  	ready, errno := wF.Poll(pflag, timeout)
   375  	require.EqualErrno(t, experimentalsys.ENOTSUP, errno)
   376  	require.False(t, ready)
   377  }
   378  
   379  func requireRead(t *testing.T, f experimentalsys.File, buf []byte) {
   380  	n, errno := f.Read(buf)
   381  	require.EqualErrno(t, 0, errno)
   382  	require.Equal(t, len(buf), n)
   383  }
   384  
   385  func requirePread(t *testing.T, f experimentalsys.File, buf []byte, off int64) {
   386  	n, errno := f.Pread(buf, off)
   387  	require.EqualErrno(t, 0, errno)
   388  	require.Equal(t, len(buf), n)
   389  }
   390  
   391  func TestFileRead_empty(t *testing.T) {
   392  	dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
   393  
   394  	tests := []struct {
   395  		name string
   396  		fs   fs.FS
   397  	}{
   398  		{name: "os.DirFS", fs: dirFS},
   399  		{name: "embed.api.FS", fs: embedFS},
   400  		{name: "fstest.MapFS", fs: mapFS},
   401  	}
   402  
   403  	buf := make([]byte, 3)
   404  
   405  	for _, tc := range tests {
   406  		tc := tc
   407  
   408  		t.Run(tc.name, func(t *testing.T) {
   409  			f, errno := OpenFSFile(tc.fs, emptyFile, experimentalsys.O_RDONLY, 0)
   410  			require.EqualErrno(t, 0, errno)
   411  			defer f.Close()
   412  
   413  			t.Run("Read", func(t *testing.T) {
   414  				// We should be able to read an empty file
   415  				n, errno := f.Read(buf)
   416  				require.EqualErrno(t, 0, errno)
   417  				require.Zero(t, n)
   418  			})
   419  
   420  			t.Run("Pread", func(t *testing.T) {
   421  				n, errno := f.Pread(buf, 0)
   422  				require.EqualErrno(t, 0, errno)
   423  				require.Zero(t, n)
   424  			})
   425  		})
   426  	}
   427  }
   428  
   429  type maskFS struct {
   430  	fs.FS
   431  }
   432  
   433  func (m *maskFS) Open(name string) (fs.File, error) {
   434  	f, err := m.FS.Open(name)
   435  	return struct{ fs.File }{f}, err
   436  }
   437  
   438  func TestFilePread_Unsupported(t *testing.T) {
   439  	embedFS, err := fs.Sub(testdata, "testdata")
   440  	require.NoError(t, err)
   441  
   442  	f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, experimentalsys.O_RDONLY, 0)
   443  	require.EqualErrno(t, 0, errno)
   444  	defer f.Close()
   445  
   446  	buf := make([]byte, 3)
   447  	_, errno = f.Pread(buf, 0)
   448  	require.EqualErrno(t, experimentalsys.ENOSYS, errno)
   449  }
   450  
   451  func TestFileRead_Errors(t *testing.T) {
   452  	// Create the file
   453  	path := path.Join(t.TempDir(), emptyFile)
   454  
   455  	// Open the file write-only
   456  	flag := experimentalsys.O_WRONLY | experimentalsys.O_CREAT
   457  	f := requireOpenFile(t, path, flag, 0o600)
   458  	defer f.Close()
   459  	buf := make([]byte, 5)
   460  
   461  	tests := []struct {
   462  		name string
   463  		fn   func(experimentalsys.File) experimentalsys.Errno
   464  	}{
   465  		{name: "Read", fn: func(f experimentalsys.File) experimentalsys.Errno {
   466  			_, errno := f.Read(buf)
   467  			return errno
   468  		}},
   469  		{name: "Pread", fn: func(f experimentalsys.File) experimentalsys.Errno {
   470  			_, errno := f.Pread(buf, 0)
   471  			return errno
   472  		}},
   473  	}
   474  
   475  	for _, tc := range tests {
   476  		tc := tc
   477  
   478  		t.Run(tc.name, func(t *testing.T) {
   479  			t.Run("EBADF when not open for reading", func(t *testing.T) {
   480  				// The descriptor exists, but not open for reading
   481  				errno := tc.fn(f)
   482  				require.EqualErrno(t, experimentalsys.EBADF, errno)
   483  			})
   484  			testEISDIR(t, tc.fn)
   485  		})
   486  	}
   487  }
   488  
   489  func TestFileSeek(t *testing.T) {
   490  	tmpDir := t.TempDir()
   491  	dirFS, embedFS, mapFS := dirEmbedMapFS(t, tmpDir)
   492  
   493  	tests := []struct {
   494  		name     string
   495  		openFile func(string) (experimentalsys.File, experimentalsys.Errno)
   496  	}{
   497  		{name: "fsFile os.DirFS", openFile: func(name string) (experimentalsys.File, experimentalsys.Errno) {
   498  			return OpenFSFile(dirFS, name, experimentalsys.O_RDONLY, 0)
   499  		}},
   500  		{name: "fsFile embed.api.FS", openFile: func(name string) (experimentalsys.File, experimentalsys.Errno) {
   501  			return OpenFSFile(embedFS, name, experimentalsys.O_RDONLY, 0)
   502  		}},
   503  		{name: "fsFile fstest.MapFS", openFile: func(name string) (experimentalsys.File, experimentalsys.Errno) {
   504  			return OpenFSFile(mapFS, name, experimentalsys.O_RDONLY, 0)
   505  		}},
   506  		{name: "osFile", openFile: func(name string) (experimentalsys.File, experimentalsys.Errno) {
   507  			return OpenOSFile(path.Join(tmpDir, name), experimentalsys.O_RDONLY, 0o666)
   508  		}},
   509  	}
   510  
   511  	buf := make([]byte, 3)
   512  
   513  	for _, tc := range tests {
   514  		tc := tc
   515  
   516  		t.Run(tc.name, func(t *testing.T) {
   517  			f, errno := tc.openFile(wazeroFile)
   518  			require.EqualErrno(t, 0, errno)
   519  			defer f.Close()
   520  
   521  			// Shouldn't be able to use an invalid whence
   522  			_, errno = f.Seek(0, io.SeekEnd+1)
   523  			require.EqualErrno(t, experimentalsys.EINVAL, errno)
   524  			_, errno = f.Seek(0, -1)
   525  			require.EqualErrno(t, experimentalsys.EINVAL, errno)
   526  
   527  			// Shouldn't be able to seek before the file starts.
   528  			_, errno = f.Seek(-1, io.SeekStart)
   529  			require.EqualErrno(t, experimentalsys.EINVAL, errno)
   530  
   531  			requireRead(t, f, buf) // read 3 bytes
   532  
   533  			// Seek to the start
   534  			newOffset, errno := f.Seek(0, io.SeekStart)
   535  			require.EqualErrno(t, 0, errno)
   536  
   537  			// verify we can re-read from the beginning now.
   538  			require.Zero(t, newOffset)
   539  			requireRead(t, f, buf) // read 3 bytes again
   540  			require.Equal(t, "waz", string(buf))
   541  			buf = buf[:]
   542  
   543  			// Seek to the start with zero allows you to read it back.
   544  			newOffset, errno = f.Seek(0, io.SeekCurrent)
   545  			require.EqualErrno(t, 0, errno)
   546  			require.Equal(t, int64(3), newOffset)
   547  
   548  			// Seek to the last two bytes
   549  			newOffset, errno = f.Seek(-2, io.SeekEnd)
   550  			require.EqualErrno(t, 0, errno)
   551  
   552  			// verify we can read the last two bytes
   553  			require.Equal(t, int64(5), newOffset)
   554  			n, errno := f.Read(buf)
   555  			require.EqualErrno(t, 0, errno)
   556  			require.Equal(t, 2, n)
   557  			require.Equal(t, "o\n", string(buf[:2]))
   558  
   559  			t.Run("directory seek to zero", func(t *testing.T) {
   560  				dotF, errno := tc.openFile(".")
   561  				require.EqualErrno(t, 0, errno)
   562  				defer dotF.Close()
   563  
   564  				dirents, errno := dotF.Readdir(-1)
   565  				require.EqualErrno(t, 0, errno)
   566  				direntCount := len(dirents)
   567  				require.False(t, direntCount == 0)
   568  
   569  				// rewind via seek to zero
   570  				newOffset, errno := dotF.Seek(0, io.SeekStart)
   571  				require.EqualErrno(t, 0, errno)
   572  				require.Zero(t, newOffset)
   573  
   574  				// redundantly seek to zero again
   575  				newOffset, errno = dotF.Seek(0, io.SeekStart)
   576  				require.EqualErrno(t, 0, errno)
   577  				require.Zero(t, newOffset)
   578  
   579  				// We should be able to read again
   580  				dirents, errno = dotF.Readdir(-1)
   581  				require.EqualErrno(t, 0, errno)
   582  				require.Equal(t, direntCount, len(dirents))
   583  			})
   584  
   585  			seekToZero := func(f experimentalsys.File) experimentalsys.Errno {
   586  				_, errno := f.Seek(0, io.SeekStart)
   587  				return errno
   588  			}
   589  			testEBADFIfFileClosed(t, seekToZero)
   590  		})
   591  	}
   592  }
   593  
   594  func requireSeek(t *testing.T, f experimentalsys.File, off int64, whence int) int64 {
   595  	n, errno := f.Seek(off, whence)
   596  	require.EqualErrno(t, 0, errno)
   597  	return n
   598  }
   599  
   600  func TestFileSeek_empty(t *testing.T) {
   601  	dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir())
   602  
   603  	tests := []struct {
   604  		name string
   605  		fs   fs.FS
   606  	}{
   607  		{name: "os.DirFS", fs: dirFS},
   608  		{name: "embed.api.FS", fs: embedFS},
   609  		{name: "fstest.MapFS", fs: mapFS},
   610  	}
   611  
   612  	for _, tc := range tests {
   613  		tc := tc
   614  
   615  		t.Run(tc.name, func(t *testing.T) {
   616  			f, errno := OpenFSFile(tc.fs, emptyFile, experimentalsys.O_RDONLY, 0)
   617  			require.EqualErrno(t, 0, errno)
   618  			defer f.Close()
   619  
   620  			t.Run("Start", func(t *testing.T) {
   621  				require.Zero(t, requireSeek(t, f, 0, io.SeekStart))
   622  			})
   623  
   624  			t.Run("Current", func(t *testing.T) {
   625  				require.Zero(t, requireSeek(t, f, 0, io.SeekCurrent))
   626  			})
   627  
   628  			t.Run("End", func(t *testing.T) {
   629  				require.Zero(t, requireSeek(t, f, 0, io.SeekEnd))
   630  			})
   631  		})
   632  	}
   633  }
   634  
   635  func TestFileSeek_Unsupported(t *testing.T) {
   636  	embedFS, err := fs.Sub(testdata, "testdata")
   637  	require.NoError(t, err)
   638  
   639  	f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, experimentalsys.O_RDONLY, 0)
   640  	require.EqualErrno(t, 0, errno)
   641  	defer f.Close()
   642  
   643  	_, errno = f.Seek(0, io.SeekCurrent)
   644  	require.EqualErrno(t, experimentalsys.ENOSYS, errno)
   645  }
   646  
   647  func TestFileWriteAndPwrite(t *testing.T) {
   648  	// sys.FS doesn't support writes, and there is no other built-in
   649  	// implementation except os.File.
   650  	path := path.Join(t.TempDir(), wazeroFile)
   651  	f := requireOpenFile(t, path, experimentalsys.O_RDWR|experimentalsys.O_CREAT, 0o600)
   652  	defer f.Close()
   653  
   654  	text := "wazero"
   655  	buf := make([]byte, 3)
   656  	copy(buf, text[:3])
   657  
   658  	// The file should be writeable
   659  	requireWrite(t, f, buf)
   660  
   661  	// We should be able to pwrite at gap
   662  	requirePwrite(t, f, buf, 6)
   663  
   664  	copy(buf, text[3:])
   665  
   666  	// If the offset didn't change, the next chars will write after the
   667  	// first
   668  	requireWrite(t, f, buf)
   669  
   670  	// We should be able to pwrite the same bytes as above
   671  	requirePwrite(t, f, buf, 9)
   672  
   673  	// We should also be able to pwrite past the above.
   674  	requirePwrite(t, f, buf, 12)
   675  
   676  	b, err := os.ReadFile(path)
   677  	require.NoError(t, err)
   678  
   679  	// We expect to have written the text two and a half times:
   680  	//  1. Write: (file offset 0) "waz"
   681  	//  2. Pwrite: offset 6 "waz"
   682  	//  3. Write: (file offset 3) "ero"
   683  	//  4. Pwrite: offset 9 "ero"
   684  	//  4. Pwrite: offset 12 "ero"
   685  	require.Equal(t, "wazerowazeroero", string(b))
   686  }
   687  
   688  func requireWrite(t *testing.T, f experimentalsys.File, buf []byte) {
   689  	n, errno := f.Write(buf)
   690  	require.EqualErrno(t, 0, errno)
   691  	require.Equal(t, len(buf), n)
   692  }
   693  
   694  func requirePwrite(t *testing.T, f experimentalsys.File, buf []byte, off int64) {
   695  	n, errno := f.Pwrite(buf, off)
   696  	require.EqualErrno(t, 0, errno)
   697  	require.Equal(t, len(buf), n)
   698  }
   699  
   700  func TestFileWrite_empty(t *testing.T) {
   701  	// sys.FS doesn't support writes, and there is no other built-in
   702  	// implementation except os.File.
   703  	path := path.Join(t.TempDir(), emptyFile)
   704  	f := requireOpenFile(t, path, experimentalsys.O_RDWR|experimentalsys.O_CREAT, 0o600)
   705  	defer f.Close()
   706  
   707  	tests := []struct {
   708  		name string
   709  		fn   func(experimentalsys.File, []byte) (int, experimentalsys.Errno)
   710  	}{
   711  		{name: "Write", fn: func(f experimentalsys.File, buf []byte) (int, experimentalsys.Errno) {
   712  			return f.Write(buf)
   713  		}},
   714  		{name: "Pwrite from zero", fn: func(f experimentalsys.File, buf []byte) (int, experimentalsys.Errno) {
   715  			return f.Pwrite(buf, 0)
   716  		}},
   717  		{name: "Pwrite from 3", fn: func(f experimentalsys.File, buf []byte) (int, experimentalsys.Errno) {
   718  			return f.Pwrite(buf, 3)
   719  		}},
   720  	}
   721  
   722  	var emptyBuf []byte
   723  
   724  	for _, tc := range tests {
   725  		tc := tc
   726  
   727  		t.Run(tc.name, func(t *testing.T) {
   728  			n, errno := tc.fn(f, emptyBuf)
   729  			require.EqualErrno(t, 0, errno)
   730  			require.Zero(t, n)
   731  
   732  			// The file should be empty
   733  			b, err := os.ReadFile(path)
   734  			require.NoError(t, err)
   735  			require.Zero(t, len(b))
   736  		})
   737  	}
   738  }
   739  
   740  func TestFileWrite_Unsupported(t *testing.T) {
   741  	embedFS, err := fs.Sub(testdata, "testdata")
   742  	require.NoError(t, err)
   743  
   744  	// Use sys.O_RDWR so that it fails due to type not flags
   745  	f, errno := OpenFSFile(&maskFS{embedFS}, wazeroFile, experimentalsys.O_RDWR, 0)
   746  	require.EqualErrno(t, 0, errno)
   747  	defer f.Close()
   748  
   749  	tests := []struct {
   750  		name string
   751  		fn   func(experimentalsys.File, []byte) (int, experimentalsys.Errno)
   752  	}{
   753  		{name: "Write", fn: func(f experimentalsys.File, buf []byte) (int, experimentalsys.Errno) {
   754  			return f.Write(buf)
   755  		}},
   756  		{name: "Pwrite", fn: func(f experimentalsys.File, buf []byte) (int, experimentalsys.Errno) {
   757  			return f.Pwrite(buf, 0)
   758  		}},
   759  	}
   760  
   761  	buf := []byte("wazero")
   762  
   763  	for _, tc := range tests {
   764  		tc := tc
   765  
   766  		t.Run(tc.name, func(t *testing.T) {
   767  			_, errno := tc.fn(f, buf)
   768  			require.EqualErrno(t, experimentalsys.ENOSYS, errno)
   769  		})
   770  	}
   771  }
   772  
   773  func TestFileWrite_Errors(t *testing.T) {
   774  	// Create the file
   775  	path := path.Join(t.TempDir(), emptyFile)
   776  	of, err := os.Create(path)
   777  	require.NoError(t, err)
   778  	require.NoError(t, of.Close())
   779  
   780  	// Open the file read-only
   781  	flag := experimentalsys.O_RDONLY
   782  	f := requireOpenFile(t, path, flag, 0o600)
   783  	defer f.Close()
   784  	buf := []byte("wazero")
   785  
   786  	tests := []struct {
   787  		name string
   788  		fn   func(experimentalsys.File) experimentalsys.Errno
   789  	}{
   790  		{name: "Write", fn: func(f experimentalsys.File) experimentalsys.Errno {
   791  			_, errno := f.Write(buf)
   792  			return errno
   793  		}},
   794  		{name: "Pwrite", fn: func(f experimentalsys.File) experimentalsys.Errno {
   795  			_, errno := f.Pwrite(buf, 0)
   796  			return errno
   797  		}},
   798  	}
   799  
   800  	for _, tc := range tests {
   801  		tc := tc
   802  
   803  		t.Run(tc.name, func(t *testing.T) {
   804  			t.Run("EBADF when not open for writing", func(t *testing.T) {
   805  				// The descriptor exists, but not open for writing
   806  				errno := tc.fn(f)
   807  				require.EqualErrno(t, experimentalsys.EBADF, errno)
   808  			})
   809  			testEISDIR(t, tc.fn)
   810  		})
   811  	}
   812  }
   813  
   814  func TestFileSync_NoError(t *testing.T) {
   815  	testSync_NoError(t, experimentalsys.File.Sync)
   816  }
   817  
   818  func TestFileDatasync_NoError(t *testing.T) {
   819  	testSync_NoError(t, experimentalsys.File.Datasync)
   820  }
   821  
   822  func testSync_NoError(t *testing.T, sync func(experimentalsys.File) experimentalsys.Errno) {
   823  	roPath := "file_test.go"
   824  	ro, errno := OpenFSFile(embedFS, roPath, experimentalsys.O_RDONLY, 0)
   825  	require.EqualErrno(t, 0, errno)
   826  	defer ro.Close()
   827  
   828  	rwPath := path.Join(t.TempDir(), "datasync")
   829  	rw, errno := OpenOSFile(rwPath, experimentalsys.O_CREAT|experimentalsys.O_RDWR, 0o600)
   830  	require.EqualErrno(t, 0, errno)
   831  	defer rw.Close()
   832  
   833  	tests := []struct {
   834  		name string
   835  		f    experimentalsys.File
   836  	}{
   837  		{name: "UnimplementedFile", f: experimentalsys.UnimplementedFile{}},
   838  		{name: "File of read-only FS.File", f: ro},
   839  		{name: "File of os.File", f: rw},
   840  	}
   841  
   842  	for _, tt := range tests {
   843  		tc := tt
   844  
   845  		t.Run(tc.name, func(t *testing.T) {
   846  			require.EqualErrno(t, 0, sync(tc.f))
   847  		})
   848  	}
   849  }
   850  
   851  func TestFileSync(t *testing.T) {
   852  	testSync(t, experimentalsys.File.Sync)
   853  }
   854  
   855  func TestFileDatasync(t *testing.T) {
   856  	testSync(t, experimentalsys.File.Datasync)
   857  }
   858  
   859  // testSync doesn't guarantee sync works because the operating system may
   860  // sync anyway. There is no test in Go for syscall.Fdatasync, but closest is
   861  // similar to below. Effectively, this only tests that things don't error.
   862  func testSync(t *testing.T, sync func(experimentalsys.File) experimentalsys.Errno) {
   863  	// Even though it is invalid, try to sync a directory
   864  	dPath := t.TempDir()
   865  	d := requireOpenFile(t, dPath, experimentalsys.O_RDONLY, 0)
   866  	defer d.Close()
   867  
   868  	errno := sync(d)
   869  	require.EqualErrno(t, 0, errno)
   870  
   871  	fPath := path.Join(dPath, t.Name())
   872  
   873  	f := requireOpenFile(t, fPath, experimentalsys.O_RDWR|experimentalsys.O_CREAT, 0o600)
   874  	defer f.Close()
   875  
   876  	expected := "hello world!"
   877  
   878  	// Write the expected data
   879  	_, errno = f.Write([]byte(expected))
   880  	require.EqualErrno(t, 0, errno)
   881  
   882  	// Sync the data.
   883  	errno = sync(f)
   884  	require.EqualErrno(t, 0, errno)
   885  
   886  	// Rewind while the file is still open.
   887  	_, errno = f.Seek(0, io.SeekStart)
   888  	require.EqualErrno(t, 0, errno)
   889  
   890  	// Read data from the file
   891  	buf := make([]byte, 50)
   892  	n, errno := f.Read(buf)
   893  	require.EqualErrno(t, 0, errno)
   894  
   895  	// It may be the case that sync worked.
   896  	require.Equal(t, expected, string(buf[:n]))
   897  
   898  	// Windows allows you to sync a closed file
   899  	if runtime.GOOS != "windows" {
   900  		testEBADFIfFileClosed(t, sync)
   901  		testEBADFIfDirClosed(t, sync)
   902  	}
   903  }
   904  
   905  func TestFileTruncate(t *testing.T) {
   906  	content := []byte("123456")
   907  
   908  	tests := []struct {
   909  		name            string
   910  		size            int64
   911  		expectedContent []byte
   912  		expectedErr     error
   913  	}{
   914  		{
   915  			name:            "one less",
   916  			size:            5,
   917  			expectedContent: []byte("12345"),
   918  		},
   919  		{
   920  			name:            "same",
   921  			size:            6,
   922  			expectedContent: content,
   923  		},
   924  		{
   925  			name:            "zero",
   926  			size:            0,
   927  			expectedContent: []byte(""),
   928  		},
   929  		{
   930  			name:            "larger",
   931  			size:            106,
   932  			expectedContent: append(content, make([]byte, 100)...),
   933  		},
   934  	}
   935  
   936  	for _, tt := range tests {
   937  		tc := tt
   938  		t.Run(tc.name, func(t *testing.T) {
   939  			tmpDir := t.TempDir()
   940  
   941  			fPath := path.Join(tmpDir, tc.name)
   942  			f := openForWrite(t, fPath, content)
   943  			defer f.Close()
   944  
   945  			errno := f.Truncate(tc.size)
   946  			require.EqualErrno(t, 0, errno)
   947  
   948  			actual, err := os.ReadFile(fPath)
   949  			require.NoError(t, err)
   950  			require.Equal(t, tc.expectedContent, actual)
   951  		})
   952  	}
   953  
   954  	truncateToZero := func(f experimentalsys.File) experimentalsys.Errno {
   955  		return f.Truncate(0)
   956  	}
   957  
   958  	if runtime.GOOS != "windows" {
   959  		// TODO: os.Truncate on windows passes even when closed
   960  		testEBADFIfFileClosed(t, truncateToZero)
   961  	}
   962  
   963  	testEISDIR(t, truncateToZero)
   964  
   965  	t.Run("negative", func(t *testing.T) {
   966  		tmpDir := t.TempDir()
   967  
   968  		f := openForWrite(t, path.Join(tmpDir, "truncate"), content)
   969  		defer f.Close()
   970  
   971  		errno := f.Truncate(-1)
   972  		require.EqualErrno(t, experimentalsys.EINVAL, errno)
   973  	})
   974  }
   975  
   976  func TestFileUtimens(t *testing.T) {
   977  	switch runtime.GOOS {
   978  	case "linux", "darwin": // supported
   979  	case "freebsd": // TODO: support freebsd w/o CGO
   980  	case "windows":
   981  		if !platform.IsAtLeastGo120 {
   982  			t.Skip("windows only works after Go 1.20") // TODO: possibly 1.19 ;)
   983  		}
   984  	default: // expect ENOSYS and callers need to fall back to Utimens
   985  		t.Skip("unsupported GOOS", runtime.GOOS)
   986  	}
   987  
   988  	testUtimens(t, true)
   989  
   990  	testEBADFIfFileClosed(t, func(f experimentalsys.File) experimentalsys.Errno {
   991  		return f.Utimens(experimentalsys.UTIME_OMIT, experimentalsys.UTIME_OMIT)
   992  	})
   993  	testEBADFIfDirClosed(t, func(d experimentalsys.File) experimentalsys.Errno {
   994  		return d.Utimens(experimentalsys.UTIME_OMIT, experimentalsys.UTIME_OMIT)
   995  	})
   996  }
   997  
   998  func TestNewStdioFile(t *testing.T) {
   999  	// simulate regular file attached to stdin
  1000  	f, err := os.CreateTemp(t.TempDir(), "somefile")
  1001  	require.NoError(t, err)
  1002  	defer f.Close()
  1003  
  1004  	stdin, err := NewStdioFile(true, os.Stdin)
  1005  	require.NoError(t, err)
  1006  	stdinStat, err := os.Stdin.Stat()
  1007  	require.NoError(t, err)
  1008  
  1009  	stdinFile, err := NewStdioFile(true, f)
  1010  	require.NoError(t, err)
  1011  
  1012  	stdout, err := NewStdioFile(false, os.Stdout)
  1013  	require.NoError(t, err)
  1014  	stdoutStat, err := os.Stdout.Stat()
  1015  	require.NoError(t, err)
  1016  
  1017  	stdoutFile, err := NewStdioFile(false, f)
  1018  	require.NoError(t, err)
  1019  
  1020  	tests := []struct {
  1021  		name string
  1022  		f    experimentalsys.File
  1023  		// Depending on how the tests run, os.Stdin won't necessarily be a char
  1024  		// device. We compare against an os.File, to account for this.
  1025  		expectedType fs.FileMode
  1026  	}{
  1027  		{
  1028  			name:         "stdin",
  1029  			f:            stdin,
  1030  			expectedType: stdinStat.Mode().Type(),
  1031  		},
  1032  		{
  1033  			name:         "stdin file",
  1034  			f:            stdinFile,
  1035  			expectedType: 0, // normal file
  1036  		},
  1037  		{
  1038  			name:         "stdout",
  1039  			f:            stdout,
  1040  			expectedType: stdoutStat.Mode().Type(),
  1041  		},
  1042  		{
  1043  			name:         "stdout file",
  1044  			f:            stdoutFile,
  1045  			expectedType: 0, // normal file
  1046  		},
  1047  	}
  1048  
  1049  	for _, tt := range tests {
  1050  		tc := tt
  1051  
  1052  		t.Run(tc.name+" Stat", func(t *testing.T) {
  1053  			st, errno := tc.f.Stat()
  1054  			require.EqualErrno(t, 0, errno)
  1055  			require.Equal(t, tc.expectedType, st.Mode&fs.ModeType)
  1056  			require.Equal(t, uint64(1), st.Nlink)
  1057  
  1058  			// Fake times are needed to pass wasi-testsuite.
  1059  			// See https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19
  1060  			require.Zero(t, st.Ctim)
  1061  			require.Zero(t, st.Mtim)
  1062  			require.Zero(t, st.Atim)
  1063  		})
  1064  	}
  1065  }
  1066  
  1067  func testEBADFIfDirClosed(t *testing.T, fn func(experimentalsys.File) experimentalsys.Errno) bool {
  1068  	return t.Run("EBADF if dir closed", func(t *testing.T) {
  1069  		d := requireOpenFile(t, t.TempDir(), experimentalsys.O_RDONLY, 0o755)
  1070  
  1071  		// close the directory underneath
  1072  		require.EqualErrno(t, 0, d.Close())
  1073  
  1074  		require.EqualErrno(t, experimentalsys.EBADF, fn(d))
  1075  	})
  1076  }
  1077  
  1078  func testEBADFIfFileClosed(t *testing.T, fn func(experimentalsys.File) experimentalsys.Errno) bool {
  1079  	return t.Run("EBADF if file closed", func(t *testing.T) {
  1080  		tmpDir := t.TempDir()
  1081  
  1082  		f := openForWrite(t, path.Join(tmpDir, "EBADF"), []byte{1, 2, 3, 4})
  1083  
  1084  		// close the file underneath
  1085  		require.EqualErrno(t, 0, f.Close())
  1086  
  1087  		require.EqualErrno(t, experimentalsys.EBADF, fn(f))
  1088  	})
  1089  }
  1090  
  1091  func testEISDIR(t *testing.T, fn func(experimentalsys.File) experimentalsys.Errno) bool {
  1092  	return t.Run("EISDIR if directory", func(t *testing.T) {
  1093  		f := requireOpenFile(t, os.TempDir(), experimentalsys.O_RDONLY|experimentalsys.O_DIRECTORY, 0o666)
  1094  		defer f.Close()
  1095  
  1096  		require.EqualErrno(t, experimentalsys.EISDIR, fn(f))
  1097  	})
  1098  }
  1099  
  1100  func openForWrite(t *testing.T, path string, content []byte) experimentalsys.File {
  1101  	require.NoError(t, os.WriteFile(path, content, 0o0666))
  1102  	f := requireOpenFile(t, path, experimentalsys.O_RDWR, 0o666)
  1103  	_, errno := f.Write(content)
  1104  	require.EqualErrno(t, 0, errno)
  1105  	return f
  1106  }
  1107  
  1108  func requireOpenFile(t *testing.T, path string, flag experimentalsys.Oflag, perm fs.FileMode) experimentalsys.File {
  1109  	f, errno := OpenOSFile(path, flag, perm)
  1110  	require.EqualErrno(t, 0, errno)
  1111  	return f
  1112  }
  1113  
  1114  func dirEmbedMapFS(t *testing.T, tmpDir string) (fs.FS, fs.FS, fs.FS) {
  1115  	embedFS, err := fs.Sub(testdata, "testdata")
  1116  	require.NoError(t, err)
  1117  
  1118  	f, err := embedFS.Open(wazeroFile)
  1119  	require.NoError(t, err)
  1120  	defer f.Close()
  1121  
  1122  	bytes, err := io.ReadAll(f)
  1123  	require.NoError(t, err)
  1124  
  1125  	mapFS := gofstest.MapFS{
  1126  		emptyFile:  &gofstest.MapFile{},
  1127  		wazeroFile: &gofstest.MapFile{Data: bytes},
  1128  	}
  1129  
  1130  	// Write a file as can't open "testdata" in scratch tests because they
  1131  	// can't read the original filesystem.
  1132  	require.NoError(t, os.WriteFile(path.Join(tmpDir, emptyFile), nil, 0o600))
  1133  	require.NoError(t, os.WriteFile(path.Join(tmpDir, wazeroFile), bytes, 0o600))
  1134  	dirFS := os.DirFS(tmpDir)
  1135  	return dirFS, embedFS, mapFS
  1136  }