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