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

View as plain text