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

View as plain text