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