]> Cypherpunks.ru repositories - gostls13.git/blob - src/syscall/fs_wasip1.go
25cabf82343660532cec038e15348a951ac77750
[gostls13.git] / src / syscall / fs_wasip1.go
1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 //go:build wasip1
6
7 package syscall
8
9 import (
10         "runtime"
11         "unsafe"
12 )
13
14 type uintptr32 = uint32
15 type size = uint32
16 type fdflags = uint32
17 type filesize = uint64
18 type filetype = uint8
19 type lookupflags = uint32
20 type oflags = uint32
21 type rights = uint64
22 type timestamp = uint64
23 type dircookie = uint64
24 type filedelta = int64
25 type fstflags = uint32
26
27 type iovec struct {
28         buf    uintptr32
29         bufLen size
30 }
31
32 const (
33         LOOKUP_SYMLINK_FOLLOW = 0x00000001
34 )
35
36 const (
37         OFLAG_CREATE    = 0x0001
38         OFLAG_DIRECTORY = 0x0002
39         OFLAG_EXCL      = 0x0004
40         OFLAG_TRUNC     = 0x0008
41 )
42
43 const (
44         FDFLAG_APPEND   = 0x0001
45         FDFLAG_DSYNC    = 0x0002
46         FDFLAG_NONBLOCK = 0x0004
47         FDFLAG_RSYNC    = 0x0008
48         FDFLAG_SYNC     = 0x0010
49 )
50
51 const (
52         RIGHT_FD_DATASYNC = 1 << iota
53         RIGHT_FD_READ
54         RIGHT_FD_SEEK
55         RIGHT_FDSTAT_SET_FLAGS
56         RIGHT_FD_SYNC
57         RIGHT_FD_TELL
58         RIGHT_FD_WRITE
59         RIGHT_FD_ADVISE
60         RIGHT_FD_ALLOCATE
61         RIGHT_PATH_CREATE_DIRECTORY
62         RIGHT_PATH_CREATE_FILE
63         RIGHT_PATH_LINK_SOURCE
64         RIGHT_PATH_LINK_TARGET
65         RIGHT_PATH_OPEN
66         RIGHT_FD_READDIR
67         RIGHT_PATH_READLINK
68         RIGHT_PATH_RENAME_SOURCE
69         RIGHT_PATH_RENAME_TARGET
70         RIGHT_PATH_FILESTAT_GET
71         RIGHT_PATH_FILESTAT_SET_SIZE
72         RIGHT_PATH_FILESTAT_SET_TIMES
73         RIGHT_FD_FILESTAT_GET
74         RIGHT_FD_FILESTAT_SET_SIZE
75         RIGHT_FD_FILESTAT_SET_TIMES
76         RIGHT_PATH_SYMLINK
77         RIGHT_PATH_REMOVE_DIRECTORY
78         RIGHT_PATH_UNLINK_FILE
79         RIGHT_POLL_FD_READWRITE
80         RIGHT_SOCK_SHUTDOWN
81         RIGHT_SOCK_ACCEPT
82 )
83
84 const (
85         WHENCE_SET = 0
86         WHENCE_CUR = 1
87         WHENCE_END = 2
88 )
89
90 const (
91         FILESTAT_SET_ATIM     = 0x0001
92         FILESTAT_SET_ATIM_NOW = 0x0002
93         FILESTAT_SET_MTIM     = 0x0004
94         FILESTAT_SET_MTIM_NOW = 0x0008
95 )
96
97 const (
98         // Despite the rights being defined as a 64 bits integer in the spec,
99         // wasmtime crashes the program if we set any of the upper 32 bits.
100         fullRights  = rights(^uint32(0))
101         readRights  = rights(RIGHT_FD_READ | RIGHT_FD_READDIR)
102         writeRights = rights(RIGHT_FD_DATASYNC | RIGHT_FD_WRITE | RIGHT_FD_ALLOCATE | RIGHT_PATH_FILESTAT_SET_SIZE)
103
104         // Some runtimes have very strict expectations when it comes to which
105         // rights can be enabled on files opened by path_open. The fileRights
106         // constant is used as a mask to retain only bits for operations that
107         // are supported on files.
108         fileRights rights = RIGHT_FD_DATASYNC |
109                 RIGHT_FD_READ |
110                 RIGHT_FD_SEEK |
111                 RIGHT_FDSTAT_SET_FLAGS |
112                 RIGHT_FD_SYNC |
113                 RIGHT_FD_TELL |
114                 RIGHT_FD_WRITE |
115                 RIGHT_FD_ADVISE |
116                 RIGHT_FD_ALLOCATE |
117                 RIGHT_PATH_CREATE_DIRECTORY |
118                 RIGHT_PATH_CREATE_FILE |
119                 RIGHT_PATH_LINK_SOURCE |
120                 RIGHT_PATH_LINK_TARGET |
121                 RIGHT_PATH_OPEN |
122                 RIGHT_FD_READDIR |
123                 RIGHT_PATH_READLINK |
124                 RIGHT_PATH_RENAME_SOURCE |
125                 RIGHT_PATH_RENAME_TARGET |
126                 RIGHT_PATH_FILESTAT_GET |
127                 RIGHT_PATH_FILESTAT_SET_SIZE |
128                 RIGHT_PATH_FILESTAT_SET_TIMES |
129                 RIGHT_FD_FILESTAT_GET |
130                 RIGHT_FD_FILESTAT_SET_SIZE |
131                 RIGHT_FD_FILESTAT_SET_TIMES |
132                 RIGHT_PATH_SYMLINK |
133                 RIGHT_PATH_REMOVE_DIRECTORY |
134                 RIGHT_PATH_UNLINK_FILE |
135                 RIGHT_POLL_FD_READWRITE
136
137         // Runtimes like wasmtime and wasmedge will refuse to open directories
138         // if the rights requested by the application exceed the operations that
139         // can be performed on a directory.
140         dirRights rights = RIGHT_FD_SEEK |
141                 RIGHT_FDSTAT_SET_FLAGS |
142                 RIGHT_FD_SYNC |
143                 RIGHT_PATH_CREATE_DIRECTORY |
144                 RIGHT_PATH_CREATE_FILE |
145                 RIGHT_PATH_LINK_SOURCE |
146                 RIGHT_PATH_LINK_TARGET |
147                 RIGHT_PATH_OPEN |
148                 RIGHT_FD_READDIR |
149                 RIGHT_PATH_READLINK |
150                 RIGHT_PATH_RENAME_SOURCE |
151                 RIGHT_PATH_RENAME_TARGET |
152                 RIGHT_PATH_FILESTAT_GET |
153                 RIGHT_PATH_FILESTAT_SET_SIZE |
154                 RIGHT_PATH_FILESTAT_SET_TIMES |
155                 RIGHT_FD_FILESTAT_GET |
156                 RIGHT_FD_FILESTAT_SET_TIMES |
157                 RIGHT_PATH_SYMLINK |
158                 RIGHT_PATH_REMOVE_DIRECTORY |
159                 RIGHT_PATH_UNLINK_FILE
160 )
161
162 // https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_closefd-fd---result-errno
163 //
164 //go:wasmimport wasi_snapshot_preview1 fd_close
165 //go:noescape
166 func fd_close(fd int32) Errno
167
168 // https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---result-errno
169 //
170 //go:wasmimport wasi_snapshot_preview1 fd_filestat_set_size
171 //go:noescape
172 func fd_filestat_set_size(fd int32, set_size filesize) Errno
173
174 // https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---resultsize-errno
175 //
176 //go:wasmimport wasi_snapshot_preview1 fd_pread
177 //go:noescape
178 func fd_pread(fd int32, iovs unsafe.Pointer, iovsLen size, offset filesize, nread unsafe.Pointer) Errno
179
180 //go:wasmimport wasi_snapshot_preview1 fd_pwrite
181 //go:noescape
182 func fd_pwrite(fd int32, iovs unsafe.Pointer, iovsLen size, offset filesize, nwritten unsafe.Pointer) Errno
183
184 //go:wasmimport wasi_snapshot_preview1 fd_read
185 //go:noescape
186 func fd_read(fd int32, iovs unsafe.Pointer, iovsLen size, nread unsafe.Pointer) Errno
187
188 //go:wasmimport wasi_snapshot_preview1 fd_readdir
189 //go:noescape
190 func fd_readdir(fd int32, buf unsafe.Pointer, bufLen size, cookie dircookie, nwritten unsafe.Pointer) Errno
191
192 //go:wasmimport wasi_snapshot_preview1 fd_seek
193 //go:noescape
194 func fd_seek(fd int32, offset filedelta, whence uint32, newoffset unsafe.Pointer) Errno
195
196 // https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_fdstat_set_rightsfd-fd-fs_rights_base-rights-fs_rights_inheriting-rights---result-errno
197 //
198 //go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_rights
199 //go:noescape
200 func fd_fdstat_set_rights(fd int32, rightsBase rights, rightsInheriting rights) Errno
201
202 //go:wasmimport wasi_snapshot_preview1 fd_filestat_get
203 //go:noescape
204 func fd_filestat_get(fd int32, buf unsafe.Pointer) Errno
205
206 //go:wasmimport wasi_snapshot_preview1 fd_write
207 //go:noescape
208 func fd_write(fd int32, iovs unsafe.Pointer, iovsLen size, nwritten unsafe.Pointer) Errno
209
210 //go:wasmimport wasi_snapshot_preview1 fd_sync
211 //go:noescape
212 func fd_sync(fd int32) Errno
213
214 //go:wasmimport wasi_snapshot_preview1 path_create_directory
215 //go:noescape
216 func path_create_directory(fd int32, path unsafe.Pointer, pathLen size) Errno
217
218 //go:wasmimport wasi_snapshot_preview1 path_filestat_get
219 //go:noescape
220 func path_filestat_get(fd int32, flags lookupflags, path unsafe.Pointer, pathLen size, buf unsafe.Pointer) Errno
221
222 //go:wasmimport wasi_snapshot_preview1 path_filestat_set_times
223 //go:noescape
224 func path_filestat_set_times(fd int32, flags lookupflags, path unsafe.Pointer, pathLen size, atim timestamp, mtim timestamp, fstflags fstflags) Errno
225
226 //go:wasmimport wasi_snapshot_preview1 path_link
227 //go:noescape
228 func path_link(oldFd int32, oldFlags lookupflags, oldPath unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno
229
230 //go:wasmimport wasi_snapshot_preview1 path_readlink
231 //go:noescape
232 func path_readlink(fd int32, path unsafe.Pointer, pathLen size, buf unsafe.Pointer, bufLen size, nwritten unsafe.Pointer) Errno
233
234 //go:wasmimport wasi_snapshot_preview1 path_remove_directory
235 //go:noescape
236 func path_remove_directory(fd int32, path unsafe.Pointer, pathLen size) Errno
237
238 //go:wasmimport wasi_snapshot_preview1 path_rename
239 //go:noescape
240 func path_rename(oldFd int32, oldPath unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno
241
242 //go:wasmimport wasi_snapshot_preview1 path_symlink
243 //go:noescape
244 func path_symlink(oldPath unsafe.Pointer, oldPathLen size, fd int32, newPath unsafe.Pointer, newPathLen size) Errno
245
246 //go:wasmimport wasi_snapshot_preview1 path_unlink_file
247 //go:noescape
248 func path_unlink_file(fd int32, path unsafe.Pointer, pathLen size) Errno
249
250 //go:wasmimport wasi_snapshot_preview1 path_open
251 //go:noescape
252 func path_open(rootFD int32, dirflags lookupflags, path unsafe.Pointer, pathLen size, oflags oflags, fsRightsBase rights, fsRightsInheriting rights, fsFlags fdflags, fd unsafe.Pointer) Errno
253
254 //go:wasmimport wasi_snapshot_preview1 random_get
255 //go:noescape
256 func random_get(buf unsafe.Pointer, bufLen size) Errno
257
258 // https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fdstat-record
259 // fdflags must be at offset 2, hence the uint16 type rather than the
260 // fdflags (uint32) type.
261 type fdstat struct {
262         filetype         filetype
263         fdflags          uint16
264         rightsBase       rights
265         rightsInheriting rights
266 }
267
268 //go:wasmimport wasi_snapshot_preview1 fd_fdstat_get
269 //go:noescape
270 func fd_fdstat_get(fd int32, buf unsafe.Pointer) Errno
271
272 //go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_flags
273 //go:noescape
274 func fd_fdstat_set_flags(fd int32, flags fdflags) Errno
275
276 func fd_fdstat_get_flags(fd int) (uint32, error) {
277         var stat fdstat
278         errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat))
279         return uint32(stat.fdflags), errnoErr(errno)
280 }
281
282 type preopentype = uint8
283
284 const (
285         preopentypeDir preopentype = iota
286 )
287
288 type prestatDir struct {
289         prNameLen size
290 }
291
292 type prestat struct {
293         typ preopentype
294         dir prestatDir
295 }
296
297 //go:wasmimport wasi_snapshot_preview1 fd_prestat_get
298 //go:noescape
299 func fd_prestat_get(fd int32, prestat unsafe.Pointer) Errno
300
301 //go:wasmimport wasi_snapshot_preview1 fd_prestat_dir_name
302 //go:noescape
303 func fd_prestat_dir_name(fd int32, path unsafe.Pointer, pathLen size) Errno
304
305 type opendir struct {
306         fd   int32
307         name string
308 }
309
310 // List of preopen directories that were exposed by the runtime. The first one
311 // is assumed to the be root directory of the file system, and others are seen
312 // as mount points at sub paths of the root.
313 var preopens []opendir
314
315 // Current working directory. We maintain this as a string and resolve paths in
316 // the code because wasmtime does not allow relative path lookups outside of the
317 // scope of a directory; a previous approach we tried consisted in maintaining
318 // open a file descriptor to the current directory so we could perform relative
319 // path lookups from that location, but it resulted in breaking path resolution
320 // from the current directory to its parent.
321 var cwd string
322
323 func init() {
324         dirNameBuf := make([]byte, 256)
325         // We start looking for preopens at fd=3 because 0, 1, and 2 are reserved
326         // for standard input and outputs.
327         for preopenFd := int32(3); ; preopenFd++ {
328                 var prestat prestat
329
330                 errno := fd_prestat_get(preopenFd, unsafe.Pointer(&prestat))
331                 if errno == EBADF {
332                         break
333                 }
334                 if errno != 0 {
335                         panic("fd_prestat: " + errno.Error())
336                 }
337                 if prestat.typ != preopentypeDir {
338                         continue
339                 }
340                 if int(prestat.dir.prNameLen) > len(dirNameBuf) {
341                         dirNameBuf = make([]byte, prestat.dir.prNameLen)
342                 }
343
344                 errno = fd_prestat_dir_name(preopenFd, unsafe.Pointer(&dirNameBuf[0]), prestat.dir.prNameLen)
345                 if errno != 0 {
346                         panic("fd_prestat_dir_name: " + errno.Error())
347                 }
348
349                 preopens = append(preopens, opendir{
350                         fd:   preopenFd,
351                         name: string(dirNameBuf[:prestat.dir.prNameLen]),
352                 })
353         }
354
355         if cwd, _ = Getenv("PWD"); cwd != "" {
356                 cwd = joinPath("/", cwd)
357         } else if len(preopens) > 0 {
358                 cwd = preopens[0].name
359         }
360 }
361
362 // Provided by package runtime.
363 func now() (sec int64, nsec int32)
364
365 //go:nosplit
366 func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) {
367         i := 0
368         for i < len(path) {
369                 for i < len(path) && path[i] == '/' {
370                         i++
371                 }
372
373                 j := i
374                 for j < len(path) && path[j] != '/' {
375                         j++
376                 }
377
378                 s := path[i:j]
379                 i = j
380
381                 switch s {
382                 case "":
383                         continue
384                 case ".":
385                         continue
386                 case "..":
387                         if !lookupParent {
388                                 k := len(buf)
389                                 for k > 0 && buf[k-1] != '/' {
390                                         k--
391                                 }
392                                 for k > 1 && buf[k-1] == '/' {
393                                         k--
394                                 }
395                                 buf = buf[:k]
396                                 if k == 0 {
397                                         lookupParent = true
398                                 } else {
399                                         s = ""
400                                         continue
401                                 }
402                         }
403                 default:
404                         lookupParent = false
405                 }
406
407                 if len(buf) > 0 && buf[len(buf)-1] != '/' {
408                         buf = append(buf, '/')
409                 }
410                 buf = append(buf, s...)
411         }
412         return buf, lookupParent
413 }
414
415 // joinPath concatenates dir and file paths, producing a cleaned path where
416 // "." and ".." have been removed, unless dir is relative and the references
417 // to parent directories in file represented a location relative to a parent
418 // of dir.
419 //
420 // This function is used for path resolution of all wasi functions expecting
421 // a path argument; the returned string is heap allocated, which we may want
422 // to optimize in the future. Instead of returning a string, the function
423 // could append the result to an output buffer that the functions in this
424 // file can manage to have allocated on the stack (e.g. initializing to a
425 // fixed capacity). Since it will significantly increase code complexity,
426 // we prefer to optimize for readability and maintainability at this time.
427 func joinPath(dir, file string) string {
428         buf := make([]byte, 0, len(dir)+len(file)+1)
429         if isAbs(dir) {
430                 buf = append(buf, '/')
431         }
432         buf, lookupParent := appendCleanPath(buf, dir, false)
433         buf, _ = appendCleanPath(buf, file, lookupParent)
434         // The appendCleanPath function cleans the path so it does not inject
435         // references to the current directory. If both the dir and file args
436         // were ".", this results in the output buffer being empty so we handle
437         // this condition here.
438         if len(buf) == 0 {
439                 buf = append(buf, '.')
440         }
441         // If the file ended with a '/' we make sure that the output also ends
442         // with a '/'. This is needed to ensure that programs have a mechanism
443         // to represent dereferencing symbolic links pointing to directories.
444         if buf[len(buf)-1] != '/' && isDir(file) {
445                 buf = append(buf, '/')
446         }
447         return unsafe.String(&buf[0], len(buf))
448 }
449
450 func isAbs(path string) bool {
451         return hasPrefix(path, "/")
452 }
453
454 func isDir(path string) bool {
455         return hasSuffix(path, "/")
456 }
457
458 func hasPrefix(s, p string) bool {
459         return len(s) >= len(p) && s[:len(p)] == p
460 }
461
462 func hasSuffix(s, x string) bool {
463         return len(s) >= len(x) && s[len(s)-len(x):] == x
464 }
465
466 // preparePath returns the preopen file descriptor of the directory to perform
467 // path resolution from, along with the pair of pointer and length for the
468 // relative expression of path from the directory.
469 //
470 // If the path argument is not absolute, it is first appended to the current
471 // working directory before resolution.
472 func preparePath(path string) (int32, unsafe.Pointer, size) {
473         var dirFd = int32(-1)
474         var dirName string
475
476         dir := "/"
477         if !isAbs(path) {
478                 dir = cwd
479         }
480         path = joinPath(dir, path)
481
482         for _, p := range preopens {
483                 if len(p.name) > len(dirName) && hasPrefix(path, p.name) {
484                         dirFd, dirName = p.fd, p.name
485                 }
486         }
487
488         path = path[len(dirName):]
489         for isAbs(path) {
490                 path = path[1:]
491         }
492         if len(path) == 0 {
493                 path = "."
494         }
495
496         return dirFd, stringPointer(path), size(len(path))
497 }
498
499 func Open(path string, openmode int, perm uint32) (int, error) {
500         if path == "" {
501                 return -1, EINVAL
502         }
503         dirFd, pathPtr, pathLen := preparePath(path)
504
505         var oflags oflags
506         if (openmode & O_CREATE) != 0 {
507                 oflags |= OFLAG_CREATE
508         }
509         if (openmode & O_TRUNC) != 0 {
510                 oflags |= OFLAG_TRUNC
511         }
512         if (openmode & O_EXCL) != 0 {
513                 oflags |= OFLAG_EXCL
514         }
515
516         var rights rights
517         switch openmode & (O_RDONLY | O_WRONLY | O_RDWR) {
518         case O_RDONLY:
519                 rights = fileRights & ^writeRights
520         case O_WRONLY:
521                 rights = fileRights & ^readRights
522         case O_RDWR:
523                 rights = fileRights
524         }
525
526         var fdflags fdflags
527         if (openmode & O_APPEND) != 0 {
528                 fdflags |= FDFLAG_APPEND
529         }
530         if (openmode & O_SYNC) != 0 {
531                 fdflags |= FDFLAG_SYNC
532         }
533
534         var fd int32
535         errno := path_open(
536                 dirFd,
537                 LOOKUP_SYMLINK_FOLLOW,
538                 pathPtr,
539                 pathLen,
540                 oflags,
541                 rights,
542                 fileRights,
543                 fdflags,
544                 unsafe.Pointer(&fd),
545         )
546         if errno == EISDIR && oflags == 0 && fdflags == 0 && ((rights & writeRights) == 0) {
547                 // wasmtime and wasmedge will error if attempting to open a directory
548                 // because we are asking for too many rights. However, we cannot
549                 // determine ahread of time if the path we are about to open is a
550                 // directory, so instead we fallback to a second call to path_open with
551                 // a more limited set of rights.
552                 //
553                 // This approach is subject to a race if the file system is modified
554                 // concurrently, so we also inject OFLAG_DIRECTORY to ensure that we do
555                 // not accidentally open a file which is not a directory.
556                 errno = path_open(
557                         dirFd,
558                         LOOKUP_SYMLINK_FOLLOW,
559                         pathPtr,
560                         pathLen,
561                         oflags|OFLAG_DIRECTORY,
562                         rights&dirRights,
563                         fileRights,
564                         fdflags,
565                         unsafe.Pointer(&fd),
566                 )
567         }
568         return int(fd), errnoErr(errno)
569 }
570
571 func Close(fd int) error {
572         errno := fd_close(int32(fd))
573         return errnoErr(errno)
574 }
575
576 func CloseOnExec(fd int) {
577         // nothing to do - no exec
578 }
579
580 func Mkdir(path string, perm uint32) error {
581         if path == "" {
582                 return EINVAL
583         }
584         dirFd, pathPtr, pathLen := preparePath(path)
585         errno := path_create_directory(dirFd, pathPtr, pathLen)
586         return errnoErr(errno)
587 }
588
589 func ReadDir(fd int, buf []byte, cookie dircookie) (int, error) {
590         var nwritten size
591         errno := fd_readdir(int32(fd), unsafe.Pointer(&buf[0]), size(len(buf)), cookie, unsafe.Pointer(&nwritten))
592         return int(nwritten), errnoErr(errno)
593 }
594
595 type Stat_t struct {
596         Dev      uint64
597         Ino      uint64
598         Filetype uint8
599         Nlink    uint64
600         Size     uint64
601         Atime    uint64
602         Mtime    uint64
603         Ctime    uint64
604
605         Mode int
606
607         // Uid and Gid are always zero on wasip1 platforms
608         Uid uint32
609         Gid uint32
610 }
611
612 func Stat(path string, st *Stat_t) error {
613         if path == "" {
614                 return EINVAL
615         }
616         dirFd, pathPtr, pathLen := preparePath(path)
617         errno := path_filestat_get(dirFd, LOOKUP_SYMLINK_FOLLOW, pathPtr, pathLen, unsafe.Pointer(st))
618         setDefaultMode(st)
619         return errnoErr(errno)
620 }
621
622 func Lstat(path string, st *Stat_t) error {
623         if path == "" {
624                 return EINVAL
625         }
626         dirFd, pathPtr, pathLen := preparePath(path)
627         errno := path_filestat_get(dirFd, 0, pathPtr, pathLen, unsafe.Pointer(st))
628         setDefaultMode(st)
629         return errnoErr(errno)
630 }
631
632 func Fstat(fd int, st *Stat_t) error {
633         errno := fd_filestat_get(int32(fd), unsafe.Pointer(st))
634         setDefaultMode(st)
635         return errnoErr(errno)
636 }
637
638 func setDefaultMode(st *Stat_t) {
639         // WASI does not support unix-like permissions, but Go programs are likely
640         // to expect the permission bits to not be zero so we set defaults to help
641         // avoid breaking applications that are migrating to WASM.
642         if st.Filetype == FILETYPE_DIRECTORY {
643                 st.Mode = 0700
644         } else {
645                 st.Mode = 0600
646         }
647 }
648
649 func Unlink(path string) error {
650         if path == "" {
651                 return EINVAL
652         }
653         dirFd, pathPtr, pathLen := preparePath(path)
654         errno := path_unlink_file(dirFd, pathPtr, pathLen)
655         return errnoErr(errno)
656 }
657
658 func Rmdir(path string) error {
659         if path == "" {
660                 return EINVAL
661         }
662         dirFd, pathPtr, pathLen := preparePath(path)
663         errno := path_remove_directory(dirFd, pathPtr, pathLen)
664         return errnoErr(errno)
665 }
666
667 func Chmod(path string, mode uint32) error {
668         var stat Stat_t
669         return Stat(path, &stat)
670 }
671
672 func Fchmod(fd int, mode uint32) error {
673         var stat Stat_t
674         return Fstat(fd, &stat)
675 }
676
677 func Chown(path string, uid, gid int) error {
678         return ENOSYS
679 }
680
681 func Fchown(fd int, uid, gid int) error {
682         return ENOSYS
683 }
684
685 func Lchown(path string, uid, gid int) error {
686         return ENOSYS
687 }
688
689 func UtimesNano(path string, ts []Timespec) error {
690         // UTIME_OMIT value must match internal/syscall/unix/at_wasip1.go
691         const UTIME_OMIT = -0x2
692         if path == "" {
693                 return EINVAL
694         }
695         dirFd, pathPtr, pathLen := preparePath(path)
696         atime := TimespecToNsec(ts[0])
697         mtime := TimespecToNsec(ts[1])
698         if ts[0].Nsec == UTIME_OMIT || ts[1].Nsec == UTIME_OMIT {
699                 var st Stat_t
700                 if err := Stat(path, &st); err != nil {
701                         return err
702                 }
703                 if ts[0].Nsec == UTIME_OMIT {
704                         atime = int64(st.Atime)
705                 }
706                 if ts[1].Nsec == UTIME_OMIT {
707                         mtime = int64(st.Mtime)
708                 }
709         }
710         errno := path_filestat_set_times(
711                 dirFd,
712                 LOOKUP_SYMLINK_FOLLOW,
713                 pathPtr,
714                 pathLen,
715                 timestamp(atime),
716                 timestamp(mtime),
717                 FILESTAT_SET_ATIM|FILESTAT_SET_MTIM,
718         )
719         return errnoErr(errno)
720 }
721
722 func Rename(from, to string) error {
723         if from == "" || to == "" {
724                 return EINVAL
725         }
726         oldDirFd, oldPathPtr, oldPathLen := preparePath(from)
727         newDirFd, newPathPtr, newPathLen := preparePath(to)
728         errno := path_rename(
729                 oldDirFd,
730                 oldPathPtr,
731                 oldPathLen,
732                 newDirFd,
733                 newPathPtr,
734                 newPathLen,
735         )
736         return errnoErr(errno)
737 }
738
739 func Truncate(path string, length int64) error {
740         if path == "" {
741                 return EINVAL
742         }
743         fd, err := Open(path, O_WRONLY, 0)
744         if err != nil {
745                 return err
746         }
747         defer Close(fd)
748         return Ftruncate(fd, length)
749 }
750
751 func Ftruncate(fd int, length int64) error {
752         errno := fd_filestat_set_size(int32(fd), filesize(length))
753         return errnoErr(errno)
754 }
755
756 const ImplementsGetwd = true
757
758 func Getwd() (string, error) {
759         return cwd, nil
760 }
761
762 func Chdir(path string) error {
763         if path == "" {
764                 return EINVAL
765         }
766
767         dir := "/"
768         if !isAbs(path) {
769                 dir = cwd
770         }
771         path = joinPath(dir, path)
772
773         var stat Stat_t
774         dirFd, pathPtr, pathLen := preparePath(path)
775         errno := path_filestat_get(dirFd, LOOKUP_SYMLINK_FOLLOW, pathPtr, pathLen, unsafe.Pointer(&stat))
776         if errno != 0 {
777                 return errnoErr(errno)
778         }
779         if stat.Filetype != FILETYPE_DIRECTORY {
780                 return ENOTDIR
781         }
782         cwd = path
783         return nil
784 }
785
786 func Readlink(path string, buf []byte) (n int, err error) {
787         if path == "" {
788                 return 0, EINVAL
789         }
790         if len(buf) == 0 {
791                 return 0, nil
792         }
793         dirFd, pathPtr, pathLen := preparePath(path)
794         var nwritten size
795         errno := path_readlink(
796                 dirFd,
797                 pathPtr,
798                 pathLen,
799                 unsafe.Pointer(&buf[0]),
800                 size(len(buf)),
801                 unsafe.Pointer(&nwritten),
802         )
803         // For some reason wasmtime returns ERANGE when the output buffer is
804         // shorter than the symbolic link value. os.Readlink expects a nil
805         // error and uses the fact that n is greater or equal to the buffer
806         // length to assume that it needs to try again with a larger size.
807         // This condition is handled in os.Readlink.
808         return int(nwritten), errnoErr(errno)
809 }
810
811 func Link(path, link string) error {
812         if path == "" || link == "" {
813                 return EINVAL
814         }
815         oldDirFd, oldPathPtr, oldPathLen := preparePath(path)
816         newDirFd, newPathPtr, newPathLen := preparePath(link)
817         errno := path_link(
818                 oldDirFd,
819                 0,
820                 oldPathPtr,
821                 oldPathLen,
822                 newDirFd,
823                 newPathPtr,
824                 newPathLen,
825         )
826         return errnoErr(errno)
827 }
828
829 func Symlink(path, link string) error {
830         if path == "" || link == "" {
831                 return EINVAL
832         }
833         dirFd, pathPtr, pathlen := preparePath(link)
834         errno := path_symlink(
835                 stringPointer(path),
836                 size(len(path)),
837                 dirFd,
838                 pathPtr,
839                 pathlen,
840         )
841         return errnoErr(errno)
842 }
843
844 func Fsync(fd int) error {
845         errno := fd_sync(int32(fd))
846         return errnoErr(errno)
847 }
848
849 func bytesPointer(b []byte) unsafe.Pointer {
850         return unsafe.Pointer(unsafe.SliceData(b))
851 }
852
853 func stringPointer(s string) unsafe.Pointer {
854         return unsafe.Pointer(unsafe.StringData(s))
855 }
856
857 func makeIOVec(b []byte) unsafe.Pointer {
858         return unsafe.Pointer(&iovec{
859                 buf:    uintptr32(uintptr(bytesPointer(b))),
860                 bufLen: size(len(b)),
861         })
862 }
863
864 func Read(fd int, b []byte) (int, error) {
865         var nread size
866         errno := fd_read(int32(fd), makeIOVec(b), 1, unsafe.Pointer(&nread))
867         runtime.KeepAlive(b)
868         return int(nread), errnoErr(errno)
869 }
870
871 func Write(fd int, b []byte) (int, error) {
872         var nwritten size
873         errno := fd_write(int32(fd), makeIOVec(b), 1, unsafe.Pointer(&nwritten))
874         runtime.KeepAlive(b)
875         return int(nwritten), errnoErr(errno)
876 }
877
878 func Pread(fd int, b []byte, offset int64) (int, error) {
879         var nread size
880         errno := fd_pread(int32(fd), makeIOVec(b), 1, filesize(offset), unsafe.Pointer(&nread))
881         runtime.KeepAlive(b)
882         return int(nread), errnoErr(errno)
883 }
884
885 func Pwrite(fd int, b []byte, offset int64) (int, error) {
886         var nwritten size
887         errno := fd_pwrite(int32(fd), makeIOVec(b), 1, filesize(offset), unsafe.Pointer(&nwritten))
888         runtime.KeepAlive(b)
889         return int(nwritten), errnoErr(errno)
890 }
891
892 func Seek(fd int, offset int64, whence int) (int64, error) {
893         var newoffset filesize
894         errno := fd_seek(int32(fd), filedelta(offset), uint32(whence), unsafe.Pointer(&newoffset))
895         return int64(newoffset), errnoErr(errno)
896 }
897
898 func Dup(fd int) (int, error) {
899         return 0, ENOSYS
900 }
901
902 func Dup2(fd, newfd int) error {
903         return ENOSYS
904 }
905
906 func Pipe(fd []int) error {
907         return ENOSYS
908 }
909
910 func RandomGet(b []byte) error {
911         errno := random_get(bytesPointer(b), size(len(b)))
912         return errnoErr(errno)
913 }