github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/gojs/testdata/writefs/main.go (about) 1 package writefs 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "log" 8 "os" 9 "path" 10 "syscall" 11 "time" 12 ) 13 14 func Main() { 15 // Create a test directory 16 dir := path.Join(os.TempDir(), "dir") 17 dir1 := path.Join(os.TempDir(), "dir1") 18 if err := os.Mkdir(dir, 0o700); err != nil { 19 log.Panicln(err) 20 return 21 } 22 defer os.Remove(dir) 23 24 // Create a test file in that directory 25 file := path.Join(dir, "file") 26 file1 := path.Join(os.TempDir(), "file1") 27 28 if err := os.WriteFile(file, []byte{}, 0o600); err != nil { 29 log.Panicln(err) 30 return 31 } 32 defer os.Remove(file) 33 34 // Ensure stat works, particularly mode. 35 for _, path := range []string{dir, file} { 36 if stat, err := os.Stat(path); err != nil { 37 log.Panicln(err) 38 } else { 39 fmt.Println(path, "mode", stat.Mode()) 40 } 41 } 42 43 // Now, test that syscall.WriteAt works 44 f, err := os.OpenFile(file1, os.O_RDWR|os.O_CREATE, 0o600) 45 if err != nil { 46 log.Panicln(err) 47 } 48 defer f.Close() 49 50 // Write segments to the file, noting map iteration isn't ordered. 51 bytes := []byte("wazero") 52 for o, b := range map[int][]byte{3: bytes[3:], 0: bytes[:3]} { 53 n, err := f.WriteAt(b, int64(o)) 54 if err != nil { 55 log.Panicln(err) 56 } else if n != 3 { 57 log.Panicln("expected 3, but wrote", n) 58 } 59 } 60 61 // Now, use ReadAt (tested in testfs package) to verify everything wrote! 62 if _, err = f.ReadAt(bytes, 0); err != nil { 63 log.Panicln(err) 64 } else if string(bytes) != "wazero" { 65 log.Panicln("unexpected contents:", string(bytes)) 66 } 67 68 // Next, truncate it. 69 if err = f.Truncate(2); err != nil { 70 log.Panicln(err) 71 } 72 // Next, sync it. 73 if err = f.Sync(); err != nil { 74 log.Panicln(err) 75 } 76 77 if stat, err := f.Stat(); err != nil { 78 log.Panicln(err) 79 } else if mode := stat.Mode() & fs.ModePerm; mode != 0o600 { 80 log.Panicln("expected mode = 0o600", mode) 81 } 82 83 // Finally, close it. 84 if err = f.Close(); err != nil { 85 log.Panicln(err) 86 } 87 88 // Set to read-only 89 if err = syscall.Chmod(file1, 0o400); err != nil { 90 log.Panicln(err) 91 } 92 93 // Test stat 94 stat, err := os.Stat(file1) 95 if err != nil { 96 log.Panicln(err) 97 } 98 99 if stat.Mode().Type() != 0 { 100 log.Panicln("expected type = 0", stat.Mode().Type()) 101 } 102 if stat.Mode().Perm() != 0o400 { 103 log.Panicln("expected perm = 0o400", stat.Mode().Perm()) 104 } 105 106 // Check the file was truncated. 107 if bytes, err := os.ReadFile(file1); err != nil { 108 log.Panicln(err) 109 } else if string(bytes) != "wa" { 110 log.Panicln("unexpected contents:", string(bytes)) 111 } 112 113 // create a hard link 114 link := file1 + "-link" 115 if err = os.Link(file1, link); err != nil { 116 log.Panicln(err) 117 } 118 119 // Ensure this is a hard link, so they have the same inode. 120 file1Stat, err := os.Lstat(file1) 121 if err != nil { 122 log.Panicln(err) 123 } 124 linkStat, err := os.Lstat(link) 125 if err != nil { 126 log.Panicln(err) 127 } 128 if !os.SameFile(file1Stat, linkStat) { 129 log.Panicln("expected file == link stat", file1Stat, linkStat) 130 } 131 132 // create a symbolic link 133 symlink := file1 + "-symlink" 134 if err = os.Symlink(file1, symlink); err != nil { 135 log.Panicln(err) 136 } 137 138 // verify we can read the symbolic link back 139 if dst, err := os.Readlink(symlink); err != nil { 140 log.Panicln(err) 141 } else if dst != dst { 142 log.Panicln("expected link dst = old value", dst, dst) 143 } 144 145 // Test lstat which should be about the link not its target. 146 symlinkStat, err := os.Lstat(symlink) 147 if err != nil { 148 log.Panicln(err) 149 } 150 151 if symlinkStat.Mode().Type() != fs.ModeSymlink { 152 log.Panicln("expected type = symlink", symlinkStat.Mode().Type()) 153 } 154 if size := int64(len(file1)); symlinkStat.Size() != size { 155 log.Panicln("unexpected symlink size", symlinkStat.Size(), size) 156 } 157 // A symbolic link isn't the same file as what it points to. 158 if os.SameFile(file1Stat, symlinkStat) { 159 log.Panicln("expected file != link stat", file1Stat, symlinkStat) 160 } 161 162 // Test removing a non-empty empty directory 163 if err = syscall.Rmdir(dir); err != syscall.ENOTEMPTY { 164 log.Panicln("unexpected error", err) 165 } 166 167 // Test updating the mod time of a file, noting JS has millis precision. 168 atime := time.Unix(123, 4*1e6) 169 mtime := time.Unix(567, 8*1e6) 170 171 // Ensure errors propagate 172 if err = os.Chtimes("noexist", atime, mtime); !errors.Is(err, syscall.ENOENT) { 173 log.Panicln("unexpected error", err) 174 } 175 176 // Now, try a real update. 177 if err = os.Chtimes(dir, atime, mtime); err != nil { 178 log.Panicln("unexpected error", err) 179 } 180 181 // Ensure the times translated properly. 182 dirAtimeNsec, dirMtimeNsec, dirDev, dirInode := statFields(dir) 183 fmt.Println("dir times:", dirAtimeNsec, dirMtimeNsec) 184 185 // Ensure we were able to read the dev and inode. 186 // 187 // Note: The size of syscall.Stat_t.Dev (32-bit) in js is smaller than 188 // linux (64-bit), so we can't compare its real value against the host. 189 if dirDev == 0 { 190 log.Panicln("expected dir dev != 0", dirDev) 191 } 192 if dirInode == 0 { 193 log.Panicln("expected dir inode != 0", dirInode) 194 } 195 196 // Test renaming a file, noting we can't verify error numbers as they 197 // vary per operating system. 198 if err = syscall.Rename(file, dir); err == nil { 199 log.Panicln("expected error") 200 } 201 if err = syscall.Rename(file, file1); err != nil { 202 log.Panicln("unexpected error", err) 203 } 204 205 // Test renaming a directory 206 if err = syscall.Rename(dir, file1); err == nil { 207 log.Panicln("expected error") 208 } 209 if err = syscall.Rename(dir, dir1); err != nil { 210 log.Panicln("unexpected error", err) 211 } 212 213 // Compare stat after renaming. 214 atimeNsec, mtimeNsec, dev, inode := statFields(dir1) 215 // atime shouldn't change as we didn't access (re-open) the directory. 216 if atimeNsec != dirAtimeNsec { 217 log.Panicln("expected dir atimeNsec = previous value", atimeNsec, dirAtimeNsec) 218 } 219 // mtime should change because we renamed the directory. 220 if mtimeNsec <= dirMtimeNsec { 221 log.Panicln("expected dir mtimeNsec > previous value", mtimeNsec, dirMtimeNsec) 222 } 223 // dev/inode shouldn't change during rename. 224 if dev != dirDev { 225 log.Panicln("expected dir dev = previous value", dev, dirDev) 226 } 227 if inode != dirInode { 228 log.Panicln("expected dir inode = previous value", dev, dirInode) 229 } 230 231 // Test unlinking a file 232 if err = syscall.Rmdir(file1); err != syscall.ENOTDIR { 233 log.Panicln("unexpected error", err) 234 } 235 if err = syscall.Unlink(file1); err != nil { 236 log.Panicln("unexpected error", err) 237 } 238 239 // Test removing an empty directory 240 if err = syscall.Unlink(dir1); err != syscall.EISDIR { 241 log.Panicln("unexpected error", err) 242 } 243 if err = syscall.Rmdir(dir1); err != nil { 244 log.Panicln("unexpected error", err) 245 } 246 247 // shouldn't fail 248 if err = os.RemoveAll(dir1); err != nil { 249 log.Panicln(err) 250 return 251 } 252 253 // ensure we can use zero as is used in TestRemoveReadOnlyDir 254 if err = os.Mkdir(dir1, 0); err != nil { 255 log.Panicln(err) 256 return 257 } 258 defer os.Remove(dir) 259 260 // Symlink and Readlink tests. 261 s := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" 262 from := "/symlink.txt" 263 err = os.Symlink(s, from) 264 if err != nil { 265 log.Panicln(err) 266 } 267 268 r, err := os.Readlink(from) 269 if err != nil { 270 log.Fatalf("readlink %q failed: %v", from, err) 271 } 272 if r != s { 273 log.Fatalf("after symlink %q != %q", r, s) 274 } 275 }