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

View as plain text