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