github.com/tetratelabs/wazero@v1.2.1/internal/sys/sys.go (about)

     1  package sys
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"time"
     9  
    10  	"github.com/tetratelabs/wazero/internal/fsapi"
    11  	"github.com/tetratelabs/wazero/internal/platform"
    12  	"github.com/tetratelabs/wazero/sys"
    13  )
    14  
    15  // Context holds module-scoped system resources currently only supported by
    16  // built-in host functions.
    17  type Context struct {
    18  	args, environ         [][]byte
    19  	argsSize, environSize uint32
    20  
    21  	walltime           sys.Walltime
    22  	walltimeResolution sys.ClockResolution
    23  	nanotime           sys.Nanotime
    24  	nanotimeResolution sys.ClockResolution
    25  	nanosleep          sys.Nanosleep
    26  	osyield            sys.Osyield
    27  	randSource         io.Reader
    28  	fsc                FSContext
    29  }
    30  
    31  // Args is like os.Args and defaults to nil.
    32  //
    33  // Note: The count will never be more than math.MaxUint32.
    34  // See wazero.ModuleConfig WithArgs
    35  func (c *Context) Args() [][]byte {
    36  	return c.args
    37  }
    38  
    39  // ArgsSize is the size to encode Args as Null-terminated strings.
    40  //
    41  // Note: To get the size without null-terminators, subtract the length of Args from this value.
    42  // See wazero.ModuleConfig WithArgs
    43  // See https://en.wikipedia.org/wiki/Null-terminated_string
    44  func (c *Context) ArgsSize() uint32 {
    45  	return c.argsSize
    46  }
    47  
    48  // Environ are "key=value" entries like os.Environ and default to nil.
    49  //
    50  // Note: The count will never be more than math.MaxUint32.
    51  // See wazero.ModuleConfig WithEnv
    52  func (c *Context) Environ() [][]byte {
    53  	return c.environ
    54  }
    55  
    56  // EnvironSize is the size to encode Environ as Null-terminated strings.
    57  //
    58  // Note: To get the size without null-terminators, subtract the length of Environ from this value.
    59  // See wazero.ModuleConfig WithEnv
    60  // See https://en.wikipedia.org/wiki/Null-terminated_string
    61  func (c *Context) EnvironSize() uint32 {
    62  	return c.environSize
    63  }
    64  
    65  // Walltime implements platform.Walltime.
    66  func (c *Context) Walltime() (sec int64, nsec int32) {
    67  	return c.walltime()
    68  }
    69  
    70  // WalltimeNanos returns platform.Walltime as epoch nanoseconds.
    71  func (c *Context) WalltimeNanos() int64 {
    72  	sec, nsec := c.Walltime()
    73  	return (sec * time.Second.Nanoseconds()) + int64(nsec)
    74  }
    75  
    76  // WalltimeResolution returns resolution of Walltime.
    77  func (c *Context) WalltimeResolution() sys.ClockResolution {
    78  	return c.walltimeResolution
    79  }
    80  
    81  // Nanotime implements sys.Nanotime.
    82  func (c *Context) Nanotime() int64 {
    83  	return c.nanotime()
    84  }
    85  
    86  // NanotimeResolution returns resolution of Nanotime.
    87  func (c *Context) NanotimeResolution() sys.ClockResolution {
    88  	return c.nanotimeResolution
    89  }
    90  
    91  // Nanosleep implements sys.Nanosleep.
    92  func (c *Context) Nanosleep(ns int64) {
    93  	c.nanosleep(ns)
    94  }
    95  
    96  // Osyield implements sys.Osyield.
    97  func (c *Context) Osyield() {
    98  	c.osyield()
    99  }
   100  
   101  // FS returns the possibly empty (UnimplementedFS) file system context.
   102  func (c *Context) FS() *FSContext {
   103  	return &c.fsc
   104  }
   105  
   106  // RandSource is a source of random bytes and defaults to a deterministic source.
   107  // see wazero.ModuleConfig WithRandSource
   108  func (c *Context) RandSource() io.Reader {
   109  	return c.randSource
   110  }
   111  
   112  // DefaultContext returns Context with no values set except a possible nil
   113  // fsapi.FS.
   114  //
   115  // Note: This is only used for testing.
   116  func DefaultContext(fs fsapi.FS) *Context {
   117  	if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, fs, nil); err != nil {
   118  		panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err))
   119  	} else {
   120  		return sysCtx
   121  	}
   122  }
   123  
   124  // NewContext is a factory function which helps avoid needing to know defaults or exporting all fields.
   125  // Note: max is exposed for testing. max is only used for env/args validation.
   126  func NewContext(
   127  	max uint32,
   128  	args, environ [][]byte,
   129  	stdin io.Reader,
   130  	stdout, stderr io.Writer,
   131  	randSource io.Reader,
   132  	walltime sys.Walltime,
   133  	walltimeResolution sys.ClockResolution,
   134  	nanotime sys.Nanotime,
   135  	nanotimeResolution sys.ClockResolution,
   136  	nanosleep sys.Nanosleep,
   137  	osyield sys.Osyield,
   138  	rootFS fsapi.FS,
   139  	tcpListeners []*net.TCPListener,
   140  ) (sysCtx *Context, err error) {
   141  	sysCtx = &Context{args: args, environ: environ}
   142  
   143  	if sysCtx.argsSize, err = nullTerminatedByteCount(max, args); err != nil {
   144  		return nil, fmt.Errorf("args invalid: %w", err)
   145  	}
   146  
   147  	if sysCtx.environSize, err = nullTerminatedByteCount(max, environ); err != nil {
   148  		return nil, fmt.Errorf("environ invalid: %w", err)
   149  	}
   150  
   151  	if randSource == nil {
   152  		sysCtx.randSource = platform.NewFakeRandSource()
   153  	} else {
   154  		sysCtx.randSource = randSource
   155  	}
   156  
   157  	if walltime != nil {
   158  		if clockResolutionInvalid(walltimeResolution) {
   159  			return nil, fmt.Errorf("invalid Walltime resolution: %d", walltimeResolution)
   160  		}
   161  		sysCtx.walltime = walltime
   162  		sysCtx.walltimeResolution = walltimeResolution
   163  	} else {
   164  		sysCtx.walltime = platform.NewFakeWalltime()
   165  		sysCtx.walltimeResolution = sys.ClockResolution(time.Microsecond.Nanoseconds())
   166  	}
   167  
   168  	if nanotime != nil {
   169  		if clockResolutionInvalid(nanotimeResolution) {
   170  			return nil, fmt.Errorf("invalid Nanotime resolution: %d", nanotimeResolution)
   171  		}
   172  		sysCtx.nanotime = nanotime
   173  		sysCtx.nanotimeResolution = nanotimeResolution
   174  	} else {
   175  		sysCtx.nanotime = platform.NewFakeNanotime()
   176  		sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond)
   177  	}
   178  
   179  	if nanosleep != nil {
   180  		sysCtx.nanosleep = nanosleep
   181  	} else {
   182  		sysCtx.nanosleep = platform.FakeNanosleep
   183  	}
   184  
   185  	if osyield != nil {
   186  		sysCtx.osyield = osyield
   187  	} else {
   188  		sysCtx.osyield = platform.FakeOsyield
   189  	}
   190  
   191  	if rootFS != nil {
   192  		err = sysCtx.NewFSContext(stdin, stdout, stderr, rootFS, tcpListeners)
   193  	} else {
   194  		err = sysCtx.NewFSContext(stdin, stdout, stderr, fsapi.UnimplementedFS{}, tcpListeners)
   195  	}
   196  
   197  	return
   198  }
   199  
   200  // clockResolutionInvalid returns true if the value stored isn't reasonable.
   201  func clockResolutionInvalid(resolution sys.ClockResolution) bool {
   202  	return resolution < 1 || resolution > sys.ClockResolution(time.Hour.Nanoseconds())
   203  }
   204  
   205  // nullTerminatedByteCount ensures the count or Nul-terminated length of the elements doesn't exceed max, and that no
   206  // element includes the nul character.
   207  func nullTerminatedByteCount(max uint32, elements [][]byte) (uint32, error) {
   208  	count := uint32(len(elements))
   209  	if count > max {
   210  		return 0, errors.New("exceeds maximum count")
   211  	}
   212  
   213  	// The buffer size is the total size including null terminators. The null terminator count == value count, sum
   214  	// count with each value length. This works because in Go, the length of a string is the same as its byte count.
   215  	bufSize, maxSize := uint64(count), uint64(max) // uint64 to allow summing without overflow
   216  	for _, e := range elements {
   217  		// As this is null-terminated, We have to validate there are no null characters in the string.
   218  		for _, c := range e {
   219  			if c == 0 {
   220  				return 0, errors.New("contains NUL character")
   221  			}
   222  		}
   223  
   224  		nextSize := bufSize + uint64(len(e))
   225  		if nextSize > maxSize {
   226  			return 0, errors.New("exceeds maximum size")
   227  		}
   228  		bufSize = nextSize
   229  
   230  	}
   231  	return uint32(bufSize), nil
   232  }