using System;
using System.Collections.Generic;
using System.Linq;
namespace Wasmtime
{
///
/// Represents a WASI configuration.
///
public class WasiConfiguration
{
///
/// Adds a command line argument to the configuration.
///
/// The command line argument to add.
/// Returns the current configuration.
public WasiConfiguration WithArg(string arg)
{
if (arg is null)
{
throw new ArgumentNullException(nameof(arg));
}
if (_inheritArgs)
{
_args.Clear();
_inheritArgs = false;
}
_args.Add(arg);
return this;
}
///
/// Adds multiple command line arguments to the configuration.
///
/// The command line arguments to add.
/// Returns the current configuration.
public WasiConfiguration WithArgs(IEnumerable args)
{
if (args is null)
{
throw new ArgumentNullException(nameof(args));
}
if (_inheritArgs)
{
_args.Clear();
_inheritArgs = false;
}
foreach (var arg in args)
{
_args.Add(arg);
}
return this;
}
///
/// Adds multiple command line arguments to the configuration.
///
/// The command line arguments to add.
/// Returns the current configuration.
public WasiConfiguration WithArgs(params string[] args)
{
return WithArgs((IEnumerable)args);
}
///
/// Sets the configuration to inherit command line arguments.
///
/// Any explicitly specified command line arguments will be removed.
/// Returns the current configuration.
public WasiConfiguration WithInheritedArgs()
{
_inheritArgs = true;
_args.Clear();
_args.AddRange(Environment.GetCommandLineArgs());
return this;
}
///
/// Adds an environment variable to the configuration.
///
/// The name of the environment variable.
/// The value of the environment variable.
/// Returns the current configuration.
public WasiConfiguration WithEnvironmentVariable(string name, string value)
{
if (name is null)
{
throw new ArgumentNullException(nameof(name));
}
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Environment variable name cannot be empty.", nameof(name));
}
_inheritEnv = false;
_vars.Add((name, value));
return this;
}
///
/// Adds multiple environment variables to the configuration.
///
/// The name-value tuples of the environment variables to add.
/// Returns the current configuration.
public WasiConfiguration WithEnvironmentVariables(IEnumerable<(string,string)> vars)
{
if (vars is null)
{
throw new ArgumentNullException(nameof(vars));
}
_inheritEnv = false;
foreach (var v in vars)
{
_vars.Add(v);
}
return this;
}
///
/// Sets the configuration to inherit environment variables.
///
/// Any explicitly specified environment variables will be removed.
/// Returns the current configuration.
public WasiConfiguration WithInheritedEnvironment()
{
_inheritEnv = true;
_vars.Clear();
return this;
}
///
/// Sets the configuration to use the given file path as stdin.
///
/// The file to use as stdin.
/// Returns the current configuration.
public WasiConfiguration WithStandardInput(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
_inheritStandardInput = false;
_standardInputPath = path;
return this;
}
///
/// Sets the configuration to inherit stdin.
///
/// Any explicitly specified stdin file will be removed.
/// Returns the current configuration.
public WasiConfiguration WithInheritedStandardInput()
{
_inheritStandardInput = true;
_standardInputPath = null;
return this;
}
///
/// Sets the configuration to use the given file path as stdout.
///
/// The file to use as stdout.
/// Returns the current configuration.
public WasiConfiguration WithStandardOutput(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
_inheritStandardOutput = false;
_standardOutputPath = path;
return this;
}
///
/// Sets the configuration to inherit stdout.
///
/// Any explicitly specified stdout file will be removed.
/// Returns the current configuration.
public WasiConfiguration WithInheritedStandardOutput()
{
_inheritStandardOutput = true;
_standardOutputPath = null;
return this;
}
///
/// Sets the configuration to use the given file path as stderr.
///
/// The file to use as stderr.
/// Returns the current configuration.
public WasiConfiguration WithStandardError(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
_inheritStandardError = false;
_standardErrorPath = path;
return this;
}
///
/// Sets the configuration to inherit stderr.
///
/// Any explicitly specified stderr file will be removed.
/// Returns the current configuration.
public WasiConfiguration WithInheritedStandardError()
{
_inheritStandardError = true;
_standardErrorPath = null;
return this;
}
///
/// Adds a preopen directory to the configuration.
///
/// The path to the directory to add.
/// The path the guest will use to open the directory.
/// Returns the current configuration.
public WasiConfiguration WithPreopenedDirectory(string path, string guestPath)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path cannot be null or empty.", nameof(path));
}
if (string.IsNullOrEmpty(guestPath))
{
throw new ArgumentException("The guest path cannot be null or empty.", nameof(guestPath));
}
_preopenDirs.Add((path, guestPath));
return this;
}
internal Interop.WasiInstanceHandle CreateWasi(Interop.StoreHandle store, string name)
{
var config = Interop.wasi_config_new();
SetConfigArgs(config);
SetEnvironmentVariables(config);
SetStandardIn(config);
SetStandardOut(config);
SetStandardError(config);
SetPreopenDirectories(config);
IntPtr trap;
var wasi = Interop.wasi_instance_new(store, name, config, out trap);
config.SetHandleAsInvalid();
if (trap != IntPtr.Zero)
{
throw TrapException.FromOwnedTrap(trap);
}
if (wasi.IsInvalid)
{
throw new WasmtimeException($"Failed to create instance for WASI module '{name}'.");
}
return wasi;
}
private unsafe void SetConfigArgs(Interop.WasiConfigHandle config)
{
// Don't call wasi_config_inherit_argv as the command line to the .NET program may not be
// the same as the process' command line (e.g. `dotnet foo.dll foo bar baz` => "foo.dll foo bar baz").
if (_args.Count == 0)
{
return;
}
var (args, handles) = Interop.ToUTF8PtrArray(_args);
try
{
Interop.wasi_config_set_argv(config, _args.Count, args);
}
finally
{
foreach (var handle in handles)
{
handle.Free();
}
}
}
private unsafe void SetEnvironmentVariables(Interop.WasiConfigHandle config)
{
if (_inheritEnv)
{
Interop.wasi_config_inherit_env(config);
return;
}
if (_vars.Count == 0)
{
return;
}
var (names, nameHandles) = Interop.ToUTF8PtrArray(_vars.Select(var => var.Name).ToArray());
var (values, valueHandles) = Interop.ToUTF8PtrArray(_vars.Select(var => var.Value).ToArray());
try
{
Interop.wasi_config_set_env(config, _vars.Count, names, values);
}
finally
{
foreach (var handle in nameHandles)
{
handle.Free();
}
foreach (var handle in valueHandles)
{
handle.Free();
}
}
}
private void SetStandardIn(Interop.WasiConfigHandle config)
{
if (_inheritStandardInput)
{
Interop.wasi_config_inherit_stdin(config);
return;
}
if (!string.IsNullOrEmpty(_standardInputPath))
{
if (!Interop.wasi_config_set_stdin_file(config, _standardInputPath))
{
throw new InvalidOperationException($"Failed to set stdin to file '{_standardInputPath}'.");
}
}
}
private void SetStandardOut(Interop.WasiConfigHandle config)
{
if (_inheritStandardOutput)
{
Interop.wasi_config_inherit_stdout(config);
return;
}
if (!string.IsNullOrEmpty(_standardOutputPath))
{
if (!Interop.wasi_config_set_stdout_file(config, _standardOutputPath))
{
throw new InvalidOperationException($"Failed to set stdout to file '{_standardOutputPath}'.");
}
}
}
private void SetStandardError(Interop.WasiConfigHandle config)
{
if (_inheritStandardError)
{
Interop.wasi_config_inherit_stderr(config);
return;
}
if (!string.IsNullOrEmpty(_standardErrorPath))
{
if (!Interop.wasi_config_set_stderr_file(config, _standardErrorPath))
{
throw new InvalidOperationException($"Failed to set stderr to file '{_standardErrorPath}'.");
}
}
}
private void SetPreopenDirectories(Interop.WasiConfigHandle config)
{
foreach (var dir in _preopenDirs)
{
if (!Interop.wasi_config_preopen_dir(config, dir.Path, dir.GuestPath))
{
throw new InvalidOperationException($"Failed to preopen directory '{dir.Path}'.");
}
}
}
private readonly List _args = new List();
private readonly List<(string Name, string Value)> _vars = new List<(string, string)>();
private string _standardInputPath;
private string _standardOutputPath;
private string _standardErrorPath;
private readonly List<(string Path, string GuestPath)> _preopenDirs = new List<(string, string)>();
private bool _inheritArgs = false;
private bool _inheritEnv = false;
private bool _inheritStandardInput = false;
private bool _inheritStandardOutput = false;
private bool _inheritStandardError = false;
}
}