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