github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/sys/sys.go (about)

     1  package sys
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"time"
     9  
    10  	experimentalsys "github.com/wasilibs/wazerox/experimental/sys"
    11  	"github.com/wasilibs/wazerox/internal/platform"
    12  	"github.com/wasilibs/wazerox/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  // sys.FS.
   114  //
   115  // Note: This is only used for testing.
   116  func DefaultContext(fs experimentalsys.FS) *Context {
   117  	if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil, []experimentalsys.FS{fs}, []string{""}, 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  	fs []experimentalsys.FS, guestPaths []string,
   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  	err = sysCtx.InitFSContext(stdin, stdout, stderr, fs, guestPaths, tcpListeners)
   192  
   193  	return
   194  }
   195  
   196  // clockResolutionInvalid returns true if the value stored isn't reasonable.
   197  func clockResolutionInvalid(resolution sys.ClockResolution) bool {
   198  	return resolution < 1 || resolution > sys.ClockResolution(time.Hour.Nanoseconds())
   199  }
   200  
   201  // nullTerminatedByteCount ensures the count or Nul-terminated length of the elements doesn't exceed max, and that no
   202  // element includes the nul character.
   203  func nullTerminatedByteCount(max uint32, elements [][]byte) (uint32, error) {
   204  	count := uint32(len(elements))
   205  	if count > max {
   206  		return 0, errors.New("exceeds maximum count")
   207  	}
   208  
   209  	// The buffer size is the total size including null terminators. The null terminator count == value count, sum
   210  	// count with each value length. This works because in Go, the length of a string is the same as its byte count.
   211  	bufSize, maxSize := uint64(count), uint64(max) // uint64 to allow summing without overflow
   212  	for _, e := range elements {
   213  		// As this is null-terminated, We have to validate there are no null characters in the string.
   214  		for _, c := range e {
   215  			if c == 0 {
   216  				return 0, errors.New("contains NUL character")
   217  			}
   218  		}
   219  
   220  		nextSize := bufSize + uint64(len(e))
   221  		if nextSize > maxSize {
   222  			return 0, errors.New("exceeds maximum size")
   223  		}
   224  		bufSize = nextSize
   225  
   226  	}
   227  	return uint32(bufSize), nil
   228  }