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

View as plain text