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 }