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

     1  package sysfs
     2  
     3  import (
     4  	"os"
     5  	"path"
     6  	"runtime"
     7  	"syscall"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/tetratelabs/wazero/internal/fsapi"
    12  	"github.com/tetratelabs/wazero/internal/platform"
    13  	"github.com/tetratelabs/wazero/internal/testing/require"
    14  )
    15  
    16  func TestStat(t *testing.T) {
    17  	tmpDir := t.TempDir()
    18  
    19  	_, errno := stat(path.Join(tmpDir, "cat"))
    20  	require.EqualErrno(t, syscall.ENOENT, errno)
    21  	_, errno = stat(path.Join(tmpDir, "sub/cat"))
    22  	require.EqualErrno(t, syscall.ENOENT, errno)
    23  
    24  	var st fsapi.Stat_t
    25  
    26  	t.Run("dir", func(t *testing.T) {
    27  		st, errno = stat(tmpDir)
    28  		require.EqualErrno(t, 0, errno)
    29  
    30  		require.True(t, st.Mode.IsDir())
    31  		require.NotEqual(t, uint64(0), st.Ino)
    32  	})
    33  
    34  	file := path.Join(tmpDir, "file")
    35  	var stFile fsapi.Stat_t
    36  
    37  	t.Run("file", func(t *testing.T) {
    38  		require.NoError(t, os.WriteFile(file, nil, 0o400))
    39  
    40  		stFile, errno = stat(file)
    41  		require.EqualErrno(t, 0, errno)
    42  
    43  		require.False(t, stFile.Mode.IsDir())
    44  		require.NotEqual(t, uint64(0), st.Ino)
    45  	})
    46  
    47  	t.Run("link to file", func(t *testing.T) {
    48  		link := path.Join(tmpDir, "file-link")
    49  		require.NoError(t, os.Symlink(file, link))
    50  
    51  		stLink, errno := stat(link)
    52  		require.EqualErrno(t, 0, errno)
    53  
    54  		require.Equal(t, stFile, stLink) // resolves to the file
    55  	})
    56  
    57  	subdir := path.Join(tmpDir, "sub")
    58  	var stSubdir fsapi.Stat_t
    59  	t.Run("subdir", func(t *testing.T) {
    60  		require.NoError(t, os.Mkdir(subdir, 0o500))
    61  
    62  		stSubdir, errno = stat(subdir)
    63  		require.EqualErrno(t, 0, errno)
    64  
    65  		require.True(t, stSubdir.Mode.IsDir())
    66  		require.NotEqual(t, uint64(0), st.Ino)
    67  	})
    68  
    69  	t.Run("link to dir", func(t *testing.T) {
    70  		link := path.Join(tmpDir, "dir-link")
    71  		require.NoError(t, os.Symlink(subdir, link))
    72  
    73  		stLink, errno := stat(link)
    74  		require.EqualErrno(t, 0, errno)
    75  
    76  		require.Equal(t, stSubdir, stLink) // resolves to the dir
    77  	})
    78  }
    79  
    80  func TestStatFile(t *testing.T) {
    81  	tmpDir := t.TempDir()
    82  
    83  	tmpDirF := requireOpenFile(t, tmpDir, syscall.O_RDONLY, 0)
    84  	defer tmpDirF.Close()
    85  
    86  	t.Run("dir", func(t *testing.T) {
    87  		st, errno := tmpDirF.Stat()
    88  		require.EqualErrno(t, 0, errno)
    89  
    90  		require.True(t, st.Mode.IsDir())
    91  		requireDirectoryDevIno(t, st)
    92  	})
    93  
    94  	// Windows allows you to stat a closed dir because it is accessed by path,
    95  	// not by file descriptor.
    96  	if runtime.GOOS != "windows" {
    97  		t.Run("closed dir", func(t *testing.T) {
    98  			require.EqualErrno(t, 0, tmpDirF.Close())
    99  			_, errno := tmpDirF.Stat()
   100  			require.EqualErrno(t, syscall.EBADF, errno)
   101  		})
   102  	}
   103  
   104  	file := path.Join(tmpDir, "file")
   105  	require.NoError(t, os.WriteFile(file, nil, 0o400))
   106  	fileF := requireOpenFile(t, file, syscall.O_RDONLY, 0)
   107  	defer fileF.Close()
   108  
   109  	t.Run("file", func(t *testing.T) {
   110  		st, errno := fileF.Stat()
   111  		require.EqualErrno(t, 0, errno)
   112  
   113  		require.False(t, st.Mode.IsDir())
   114  		require.NotEqual(t, uint64(0), st.Ino)
   115  	})
   116  
   117  	t.Run("closed fsFile", func(t *testing.T) {
   118  		require.EqualErrno(t, 0, fileF.Close())
   119  		_, errno := fileF.Stat()
   120  		require.EqualErrno(t, syscall.EBADF, errno)
   121  	})
   122  
   123  	subdir := path.Join(tmpDir, "sub")
   124  	require.NoError(t, os.Mkdir(subdir, 0o500))
   125  	subdirF := requireOpenFile(t, subdir, syscall.O_RDONLY, 0)
   126  	defer subdirF.Close()
   127  
   128  	t.Run("subdir", func(t *testing.T) {
   129  		st, errno := subdirF.Stat()
   130  		require.EqualErrno(t, 0, errno)
   131  
   132  		require.True(t, st.Mode.IsDir())
   133  		requireDirectoryDevIno(t, st)
   134  	})
   135  
   136  	if runtime.GOOS != "windows" { // windows allows you to stat a closed dir
   137  		t.Run("closed subdir", func(t *testing.T) {
   138  			require.EqualErrno(t, 0, subdirF.Close())
   139  			_, errno := subdirF.Stat()
   140  			require.EqualErrno(t, syscall.EBADF, errno)
   141  		})
   142  	}
   143  }
   144  
   145  func Test_StatFile_times(t *testing.T) {
   146  	tmpDir := t.TempDir()
   147  
   148  	file := path.Join(tmpDir, "file")
   149  	err := os.WriteFile(file, []byte{}, 0o700)
   150  	require.NoError(t, err)
   151  
   152  	type test struct {
   153  		name                 string
   154  		atimeNsec, mtimeNsec int64
   155  	}
   156  	// Note: This sets microsecond granularity because Windows doesn't support
   157  	// nanosecond.
   158  	tests := []test{
   159  		{
   160  			name:      "positive",
   161  			atimeNsec: time.Unix(123, 4*1e3).UnixNano(),
   162  			mtimeNsec: time.Unix(567, 8*1e3).UnixNano(),
   163  		},
   164  		{name: "zero"},
   165  	}
   166  
   167  	// linux and freebsd report inaccurate results when the input ts is negative.
   168  	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
   169  		tests = append(tests,
   170  			test{
   171  				name:      "negative",
   172  				atimeNsec: time.Unix(-123, -4*1e3).UnixNano(),
   173  				mtimeNsec: time.Unix(-567, -8*1e3).UnixNano(),
   174  			},
   175  		)
   176  	}
   177  
   178  	for _, tt := range tests {
   179  		tc := tt
   180  		t.Run(tc.name, func(t *testing.T) {
   181  			err := os.Chtimes(file, time.UnixMicro(tc.atimeNsec/1e3), time.UnixMicro(tc.mtimeNsec/1e3))
   182  			require.NoError(t, err)
   183  
   184  			f := requireOpenFile(t, file, syscall.O_RDONLY, 0)
   185  			defer f.Close()
   186  
   187  			st, errno := f.Stat()
   188  			require.EqualErrno(t, 0, errno)
   189  
   190  			require.Equal(t, st.Atim, tc.atimeNsec)
   191  			require.Equal(t, st.Mtim, tc.mtimeNsec)
   192  		})
   193  	}
   194  }
   195  
   196  func TestStatFile_dev_inode(t *testing.T) {
   197  	tmpDir := t.TempDir()
   198  	d := requireOpenFile(t, tmpDir, os.O_RDONLY, 0)
   199  	defer d.Close()
   200  
   201  	path1 := path.Join(tmpDir, "1")
   202  	f1 := requireOpenFile(t, path1, os.O_CREATE, 0o666)
   203  	defer f1.Close()
   204  
   205  	path2 := path.Join(tmpDir, "2")
   206  	f2 := requireOpenFile(t, path2, os.O_CREATE, 0o666)
   207  	defer f2.Close()
   208  
   209  	pathLink2 := path.Join(tmpDir, "link2")
   210  	err := os.Symlink(path2, pathLink2)
   211  	require.NoError(t, err)
   212  	l2 := requireOpenFile(t, pathLink2, os.O_RDONLY, 0)
   213  	defer l2.Close()
   214  
   215  	// First, stat the directory
   216  	st1, errno := d.Stat()
   217  	require.EqualErrno(t, 0, errno)
   218  
   219  	requireDirectoryDevIno(t, st1)
   220  
   221  	// Now, stat the files in it
   222  	st1, errno = f1.Stat()
   223  	require.EqualErrno(t, 0, errno)
   224  
   225  	st2, errno := f2.Stat()
   226  	require.EqualErrno(t, 0, errno)
   227  
   228  	st3, errno := l2.Stat()
   229  	require.EqualErrno(t, 0, errno)
   230  
   231  	// The files should be on the same device, but different inodes
   232  	require.Equal(t, st1.Dev, st2.Dev)
   233  	require.NotEqual(t, st1.Ino, st2.Ino)
   234  	require.Equal(t, st2, st3) // stat on a link is for its target
   235  
   236  	// Redoing stat should result in the same inodes
   237  	st1Again, errno := f1.Stat()
   238  	require.EqualErrno(t, 0, errno)
   239  	require.Equal(t, st1.Dev, st1Again.Dev)
   240  
   241  	// On Windows, we cannot rename while opening.
   242  	// So we manually close here before renaming.
   243  	require.EqualErrno(t, 0, f1.Close())
   244  	require.EqualErrno(t, 0, f2.Close())
   245  	require.EqualErrno(t, 0, l2.Close())
   246  
   247  	// Renaming a file shouldn't change its inodes.
   248  	require.EqualErrno(t, 0, Rename(path1, path2))
   249  	f1 = requireOpenFile(t, path2, os.O_RDONLY, 0)
   250  	defer f1.Close()
   251  
   252  	st1Again, errno = f1.Stat()
   253  	require.EqualErrno(t, 0, errno)
   254  	require.Equal(t, st1.Dev, st1Again.Dev)
   255  	require.Equal(t, st1.Ino, st1Again.Ino)
   256  }
   257  
   258  func requireDirectoryDevIno(t *testing.T, st fsapi.Stat_t) {
   259  	// windows before go 1.20 has trouble reading the inode information on
   260  	// directories.
   261  	if runtime.GOOS != "windows" || platform.IsGo120 {
   262  		require.NotEqual(t, uint64(0), st.Dev)
   263  		require.NotEqual(t, uint64(0), st.Ino)
   264  	} else {
   265  		require.Zero(t, st.Dev)
   266  		require.Zero(t, st.Ino)
   267  	}
   268  }
   269  
   270  // TestStat_uid_gid is similar to os.TestChown
   271  func TestStat_uid_gid(t *testing.T) {
   272  	if runtime.GOOS == "windows" {
   273  		t.Skip("windows")
   274  	}
   275  
   276  	// We don't attempt changing the uid of a file, as only root can do that.
   277  	// Also, this isn't a test of chown. The main goal here is to read-back
   278  	// the uid, gid, both of which are zero if run as root.
   279  	uid := uint32(os.Getuid())
   280  	gid := uint32(os.Getgid())
   281  
   282  	t.Run("Stat", func(t *testing.T) {
   283  		tmpDir := t.TempDir()
   284  		dir := path.Join(tmpDir, "dir")
   285  		require.NoError(t, os.Mkdir(dir, 0o0777))
   286  		require.EqualErrno(t, 0, chgid(dir, gid))
   287  
   288  		st, errno := stat(dir)
   289  		require.EqualErrno(t, 0, errno)
   290  
   291  		require.Equal(t, uid, st.Uid)
   292  		require.Equal(t, gid, st.Gid)
   293  	})
   294  
   295  	t.Run("LStat", func(t *testing.T) {
   296  		tmpDir := t.TempDir()
   297  		link := path.Join(tmpDir, "link")
   298  		require.NoError(t, os.Symlink(tmpDir, link))
   299  		require.EqualErrno(t, 0, chgid(link, gid))
   300  
   301  		st, errno := lstat(link)
   302  		require.EqualErrno(t, 0, errno)
   303  
   304  		require.Equal(t, uid, st.Uid)
   305  		require.Equal(t, gid, st.Gid)
   306  	})
   307  
   308  	t.Run("statFile", func(t *testing.T) {
   309  		tmpDir := t.TempDir()
   310  		file := path.Join(tmpDir, "file")
   311  		require.NoError(t, os.WriteFile(file, nil, 0o0666))
   312  		require.EqualErrno(t, 0, chgid(file, gid))
   313  
   314  		st, errno := lstat(file)
   315  		require.EqualErrno(t, 0, errno)
   316  
   317  		require.Equal(t, uid, st.Uid)
   318  		require.Equal(t, gid, st.Gid)
   319  	})
   320  }
   321  
   322  func chgid(path string, gid uint32) error {
   323  	// Note: In Chown, -1 is means leave the uid alone
   324  	return Chown(path, -1, int(gid))
   325  }