github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/poll.go (about)

     1  package wasi_snapshot_preview1
     2  
     3  import (
     4  	"context"
     5  	"syscall"
     6  	"time"
     7  
     8  	"github.com/tetratelabs/wazero/api"
     9  	internalsys "github.com/tetratelabs/wazero/internal/sys"
    10  	"github.com/tetratelabs/wazero/internal/wasip1"
    11  	"github.com/tetratelabs/wazero/internal/wasm"
    12  )
    13  
    14  // pollOneoff is the WASI function named PollOneoffName that concurrently
    15  // polls for the occurrence of a set of events.
    16  //
    17  // # Parameters
    18  //
    19  //   - in: pointer to the subscriptions (48 bytes each)
    20  //   - out: pointer to the resulting events (32 bytes each)
    21  //   - nsubscriptions: count of subscriptions, zero returns syscall.EINVAL.
    22  //   - resultNevents: count of events.
    23  //
    24  // Result (Errno)
    25  //
    26  // The return value is 0 except the following error conditions:
    27  //   - syscall.EINVAL: the parameters are invalid
    28  //   - syscall.ENOTSUP: a parameters is valid, but not yet supported.
    29  //   - syscall.EFAULT: there is not enough memory to read the subscriptions or
    30  //     write results.
    31  //
    32  // # Notes
    33  //
    34  //   - Since the `out` pointer nests Errno, the result is always 0.
    35  //   - This is similar to `poll` in POSIX.
    36  //
    37  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
    38  // See https://linux.die.net/man/3/poll
    39  var pollOneoff = newHostFunc(
    40  	wasip1.PollOneoffName, pollOneoffFn,
    41  	[]api.ValueType{i32, i32, i32, i32},
    42  	"in", "out", "nsubscriptions", "result.nevents",
    43  )
    44  
    45  type event struct {
    46  	eventType byte
    47  	userData  []byte
    48  	errno     wasip1.Errno
    49  	outOffset uint32
    50  }
    51  
    52  func pollOneoffFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
    53  	in := uint32(params[0])
    54  	out := uint32(params[1])
    55  	nsubscriptions := uint32(params[2])
    56  	resultNevents := uint32(params[3])
    57  
    58  	if nsubscriptions == 0 {
    59  		return syscall.EINVAL
    60  	}
    61  
    62  	mem := mod.Memory()
    63  
    64  	// Ensure capacity prior to the read loop to reduce error handling.
    65  	inBuf, ok := mem.Read(in, nsubscriptions*48)
    66  	if !ok {
    67  		return syscall.EFAULT
    68  	}
    69  	outBuf, ok := mem.Read(out, nsubscriptions*32)
    70  	// zero-out all buffer before writing
    71  	for i := range outBuf {
    72  		outBuf[i] = 0
    73  	}
    74  
    75  	if !ok {
    76  		return syscall.EFAULT
    77  	}
    78  
    79  	// Eagerly write the number of events which will equal subscriptions unless
    80  	// there's a fault in parsing (not processing).
    81  	if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) {
    82  		return syscall.EFAULT
    83  	}
    84  
    85  	// Loop through all subscriptions and write their output.
    86  
    87  	// Extract FS context, used in the body of the for loop for FS access.
    88  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
    89  	// Slice of events that are processed out of the loop (stdin subscribers).
    90  	var stdinSubs []*event
    91  	// The timeout is initialized at max Duration, the loop will find the minimum.
    92  	var timeout time.Duration = 1<<63 - 1
    93  	// Count of all the clock subscribers that have been already written back to outBuf.
    94  	clockEvents := uint32(0)
    95  	// Count of all the non-clock subscribers that have been already written back to outBuf.
    96  	readySubs := uint32(0)
    97  
    98  	// Layout is subscription_u: Union
    99  	// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#subscription_u
   100  	for i := uint32(0); i < nsubscriptions; i++ {
   101  		inOffset := i * 48
   102  		outOffset := i * 32
   103  
   104  		eventType := inBuf[inOffset+8] // +8 past userdata
   105  		// +8 past userdata +8 contents_offset
   106  		argBuf := inBuf[inOffset+8+8:]
   107  		userData := inBuf[inOffset : inOffset+8]
   108  
   109  		evt := &event{
   110  			eventType: eventType,
   111  			userData:  userData,
   112  			errno:     wasip1.ErrnoSuccess,
   113  			outOffset: outOffset,
   114  		}
   115  
   116  		switch eventType {
   117  		case wasip1.EventTypeClock: // handle later
   118  			clockEvents++
   119  			newTimeout, err := processClockEvent(argBuf)
   120  			if err != 0 {
   121  				return err
   122  			}
   123  			// Min timeout.
   124  			if newTimeout < timeout {
   125  				timeout = newTimeout
   126  			}
   127  			// Ack the clock event to the outBuf.
   128  			writeEvent(outBuf, evt)
   129  		case wasip1.EventTypeFdRead:
   130  			fd := int32(le.Uint32(argBuf))
   131  			if fd < 0 {
   132  				return syscall.EBADF
   133  			}
   134  			if fd == internalsys.FdStdin {
   135  				// if the fd is Stdin, do not ack yet,
   136  				// append to a slice for delayed evaluation.
   137  				stdinSubs = append(stdinSubs, evt)
   138  			} else {
   139  				evt.errno = processFDEventRead(fsc, fd)
   140  				writeEvent(outBuf, evt)
   141  				readySubs++
   142  			}
   143  		case wasip1.EventTypeFdWrite:
   144  			fd := int32(le.Uint32(argBuf))
   145  			if fd < 0 {
   146  				return syscall.EBADF
   147  			}
   148  			evt.errno = processFDEventWrite(fsc, fd)
   149  			readySubs++
   150  			writeEvent(outBuf, evt)
   151  		default:
   152  			return syscall.EINVAL
   153  		}
   154  	}
   155  
   156  	// If there are subscribers with data ready, we have already written them to outBuf,
   157  	// and we don't need to wait for the timeout: clear it.
   158  	if readySubs != 0 {
   159  		timeout = 0
   160  	}
   161  
   162  	// If there are stdin subscribers, check for data with given timeout.
   163  	if len(stdinSubs) > 0 {
   164  		stdin, ok := fsc.LookupFile(internalsys.FdStdin)
   165  		if !ok {
   166  			return syscall.EBADF
   167  		}
   168  		// Wait for the timeout to expire, or for some data to become available on Stdin.
   169  		stdinReady, errno := stdin.File.PollRead(&timeout)
   170  		if errno != 0 {
   171  			return errno
   172  		}
   173  		if stdinReady {
   174  			// stdin has data ready to for reading, write back all the events
   175  			for i := range stdinSubs {
   176  				readySubs++
   177  				evt := stdinSubs[i]
   178  				evt.errno = 0
   179  				writeEvent(outBuf, evt)
   180  			}
   181  		}
   182  	} else {
   183  		// No subscribers, just wait for the given timeout.
   184  		sysCtx := mod.(*wasm.ModuleInstance).Sys
   185  		sysCtx.Nanosleep(int64(timeout))
   186  	}
   187  
   188  	if readySubs != nsubscriptions {
   189  		if !mod.Memory().WriteUint32Le(resultNevents, readySubs+clockEvents) {
   190  			return syscall.EFAULT
   191  		}
   192  	}
   193  
   194  	return 0
   195  }
   196  
   197  // processClockEvent supports only relative name events, as that's what's used
   198  // to implement sleep in various compilers including Rust, Zig and TinyGo.
   199  func processClockEvent(inBuf []byte) (time.Duration, syscall.Errno) {
   200  	_ /* ID */ = le.Uint32(inBuf[0:8])          // See below
   201  	timeout := le.Uint64(inBuf[8:16])           // nanos if relative
   202  	_ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
   203  	flags := le.Uint16(inBuf[24:32])
   204  
   205  	var err syscall.Errno
   206  	// subclockflags has only one flag defined:  subscription_clock_abstime
   207  	switch flags {
   208  	case 0: // relative time
   209  	case 1: // subscription_clock_abstime
   210  		err = syscall.ENOTSUP
   211  	default: // subclockflags has only one flag defined.
   212  		err = syscall.EINVAL
   213  	}
   214  
   215  	if err != 0 {
   216  		return 0, err
   217  	} else {
   218  		// https://linux.die.net/man/3/clock_settime says relative timers are
   219  		// unaffected. Since this function only supports relative timeout, we can
   220  		// skip name ID validation and use a single sleep function.
   221  
   222  		return time.Duration(timeout), 0
   223  	}
   224  }
   225  
   226  // processFDEventRead returns ErrnoSuccess if the file exists and ErrnoBadf otherwise.
   227  func processFDEventRead(fsc *internalsys.FSContext, fd int32) wasip1.Errno {
   228  	if _, ok := fsc.LookupFile(fd); ok {
   229  		return wasip1.ErrnoSuccess
   230  	} else {
   231  		return wasip1.ErrnoBadf
   232  	}
   233  }
   234  
   235  // processFDEventWrite returns ErrnoNotsup if the file exists and ErrnoBadf otherwise.
   236  func processFDEventWrite(fsc *internalsys.FSContext, fd int32) wasip1.Errno {
   237  	if _, ok := fsc.LookupFile(fd); ok {
   238  		return wasip1.ErrnoNotsup
   239  	}
   240  	return wasip1.ErrnoBadf
   241  }
   242  
   243  // writeEvent writes the event corresponding to the processed subscription.
   244  // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
   245  func writeEvent(outBuf []byte, evt *event) {
   246  	copy(outBuf[evt.outOffset:], evt.userData) // userdata
   247  	outBuf[evt.outOffset+8] = byte(evt.errno)  // uint16, but safe as < 255
   248  	outBuf[evt.outOffset+9] = 0
   249  	le.PutUint32(outBuf[evt.outOffset+10:], uint32(evt.eventType))
   250  	// TODO: When FD events are supported, write outOffset+16
   251  }