diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go index b772e4a..6ebb594 100644 --- a/cmd/internal/run/run.go +++ b/cmd/internal/run/run.go @@ -2,24 +2,19 @@ package run import ( "bytes" - "context" "io" - "log/slog" "net/http" "os" "github.com/ipfs/kubo/client/rpc" iface "github.com/ipfs/kubo/core/coreiface" "github.com/libp2p/go-libp2p" - "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/p2p/discovery/mdns" ma "github.com/multiformats/go-multiaddr" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/urfave/cli/v2" ww "github.com/wetware/go" - "github.com/wetware/go/proc" "github.com/wetware/go/system" ) @@ -33,19 +28,14 @@ func Command() *cli.Command { Usage: "multi`addr` of IPFS node, or \"local\"", Value: "local", }, - &cli.StringFlag{ - Name: "dial", - EnvVars: []string{"WW_DIAL"}, - // Usage: "", - // Value: "", // TODO: default to /ipfs/ pointing to shell - }, &cli.StringSliceFlag{ Name: "env", EnvVars: []string{"WW_ENV"}, }, &cli.BoolFlag{ - Name: "stdin", - Usage: "bind stdin to wasm guest", + Name: "wasm-debug", + EnvVars: []string{"WW_WASM_DEBUG"}, + Usage: "enable wasm debug symbols", }, }, Action: run(), @@ -65,20 +55,20 @@ func run() cli.ActionFunc { } defer h.Close() - // Create an mDNS service to discover peers on the local network - peerHook := system.StorePeer{Peerstore: h.Peerstore()} - d := mdns.NewMdnsService(h, "ww.local", peerHook) - if err := d.Start(); err != nil { + // Start a multicast DNS service that searches for local + // peers in the background. + d, err := ww.MDNS{ + Host: h, + Handler: ww.StorePeer{Peerstore: h.Peerstore()}, + }.New(c.Context) + if err != nil { return err } defer d.Close() - // Set up WASM runtime and host modules r := wazero.NewRuntimeWithConfig(c.Context, wazero.NewRuntimeConfig(). - // WithCompilationCache(). WithDebugInfoEnabled(c.Bool("debug")). WithCloseOnContextDone(true)) - defer r.Close(c.Context) wasi, err := wasi_snapshot_preview1.Instantiate(c.Context, r) if err != nil { @@ -91,71 +81,23 @@ func run() cli.ActionFunc { Args: c.Args().Slice(), Env: c.StringSlice("env"), Stdin: stdin(c), - Stdout: stdout(c), - Stderr: stderr(c), - }, + Stdout: c.App.Writer, + Stderr: c.App.ErrWriter}, Net: system.Net{ - Host: h, - Handler: handler(c, h), + Proto: ww.Proto, + Host: h, }, - FS: system.IPFS{ + FS: system.FS{ Ctx: c.Context, - Unix: ipfs.Unixfs(), + Host: h, + IPFS: ipfs, }, }.Bind(c.Context, r) } } -func handler(c *cli.Context, h host.Host) system.HandlerFunc { - return func(ctx context.Context, p *proc.P) error { - sub, err := h.EventBus().Subscribe([]any{ - new(event.EvtLocalAddressesUpdated), - new(event.EvtLocalProtocolsUpdated)}) - if err != nil { - return err - } - defer sub.Close() - - // asynchronous event loop - slog.InfoContext(ctx, "wetware started", - "peer", h.ID(), - "path", c.Args().First(), - "proc", p.String(), - "proto", ww.Proto.String()) - defer slog.WarnContext(ctx, "wetware stopped", - "peer", h.ID(), - "path", c.Args().First(), - "proc", p.String(), - "proto", ww.Proto.String()) - - for { - var v any - select { - case <-ctx.Done(): - return ctx.Err() - - case v = <-sub.Out(): - // current event is assigned to v - - switch ev := v.(type) { - case *event.EvtLocalAddressesUpdated: - // TODO(easy): emit to libp2p topic - slog.InfoContext(ctx, "local addresses updated", - "peer", h.ID(), - "current", ev.Current, - "removed", ev.Removed, - "diffs", ev.Diffs) - - case *event.EvtLocalProtocolsUpdated: - // TODO(easy): emit to libp2p topic - slog.InfoContext(ctx, "local protocols updated", - "peer", h.ID(), - "added", ev.Added, - "removed", ev.Removed) - } - } - } - } +type procServer struct { + Host host.Host } func newIPFSClient(c *cli.Context) (ipfs iface.CoreAPI, err error) { @@ -189,11 +131,3 @@ func stdin(c *cli.Context) io.Reader { return &bytes.Reader{} // empty buffer } - -func stdout(c *cli.Context) io.Writer { - return c.App.Writer -} - -func stderr(c *cli.Context) io.Writer { - return c.App.ErrWriter -} diff --git a/cmd/internal/serve/serve.go b/cmd/internal/serve/serve.go new file mode 100644 index 0000000..702651f --- /dev/null +++ b/cmd/internal/serve/serve.go @@ -0,0 +1,189 @@ +package serve + +import ( + "bytes" + "context" + "io" + "io/fs" + "log/slog" + "net/http" + + "github.com/ipfs/kubo/client/rpc" + iface "github.com/ipfs/kubo/core/coreiface" + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/event" + ma "github.com/multiformats/go-multiaddr" + "github.com/tetratelabs/wazero" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" + ww "github.com/wetware/go" + "github.com/wetware/go/proc" + "github.com/wetware/go/system" + "github.com/wetware/go/util" +) + +func Command() *cli.Command { + return &cli.Command{ + Name: "serve", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "ipfs", + EnvVars: []string{"WW_IPFS"}, + Usage: "multi`addr` of IPFS node, or \"local\"", + Value: "local", + }, + &cli.StringSliceFlag{ + Name: "env", + EnvVars: []string{"WW_ENV"}, + }, + &cli.BoolFlag{ + Name: "wasm-debug", + EnvVars: []string{"WW_WASM_DEBUG"}, + Usage: "enable wasm debug symbols", + }, + }, + Action: func(c *cli.Context) error { + ipfs, err := newIPFSClient(c) + if err != nil { + return err + } + + h, err := libp2p.New() + if err != nil { + return err + } + defer h.Close() + + s := suture.New("ww", suture.Spec{ + EventHook: util.EventHook, + }) + + // Start a multicast DNS service that searches for local + // peers in the background. + s.Add(ww.MDNS{ + Host: h, + Handler: ww.StorePeer{Peerstore: h.Peerstore()}, + }) + + // Global compilation cache + cache := wazero.NewCompilationCache() + defer cache.Close(c.Context) + + s.Add(ww.Server{ + IPFS: ipfs, + Host: h, + Env: ww.Env{ + IO: system.IO{ + Args: c.Args().Slice(), + Env: c.StringSlice("env"), + Stdin: stdin(c), + Stdout: c.App.Writer, + Stderr: c.App.ErrWriter, + }, + Net: system.Net{ + Proto: ww.Proto, + Host: h, + Handler: system.HandlerFunc(func(ctx context.Context, p *proc.P) error { + slog.InfoContext(ctx, "process started", + "peer", h.ID(), + "proc", p.String()) + defer slog.WarnContext(ctx, "process stopped", + "peer", h.ID(), + "proc", p.String()) + <-ctx.Done() + return ctx.Err() + }), + }, + FS: system.FS{ + Ctx: c.Context, + Host: h, + IPFS: ipfs, + }, + }, + RuntimeConfig: wazero.NewRuntimeConfig(). + WithCompilationCache(cache). + WithDebugInfoEnabled(c.Bool("debug")). + WithCloseOnContextDone(true), + }) + + sub, err := h.EventBus().Subscribe([]any{ + new(event.EvtLocalAddressesUpdated)}) + if err != nil { + return err + } + defer sub.Close() + + done := s.ServeBackground(c.Context) + for { + var v any + select { + case err := <-done: + return err // exit + case v = <-sub.Out(): + // event received + } + + // Synchronous event handler + switch ev := v.(type) { + case *event.EvtLocalAddressesUpdated: + // TODO(easy): emit to libp2p topic + slog.InfoContext(c.Context, "local addresses updated", + "peer", h.ID(), + "current", ev.Current, + "removed", ev.Removed, + "diffs", ev.Diffs) + + case *event.EvtLocalProtocolsUpdated: + // TODO(easy): emit to libp2p topic + slog.InfoContext(c.Context, "local protocols updated", + "peer", h.ID(), + "added", ev.Added, + "removed", ev.Removed) + } + + } + }, + } +} + +func newIPFSClient(c *cli.Context) (ipfs iface.CoreAPI, err error) { + var a ma.Multiaddr + if s := c.String("ipfs"); s == "local" { + ipfs, err = rpc.NewLocalApi() + } else if a, err = ma.NewMultiaddr(s); err == nil { + ipfs, err = rpc.NewApiWithClient(a, http.DefaultClient) + } + + return +} + +func stdin(c *cli.Context) io.Reader { + switch r := c.App.Reader.(type) { + case interface{ Len() int }: + if r.Len() <= 0 { + break + } + + return &io.LimitedReader{ + R: c.App.Reader, + N: min(int64(r.Len()), 1<<32-1), // max u32 + } + + case interface{ Stat() (fs.FileInfo, error) }: + info, err := r.Stat() + if err != nil { + slog.Error("failed to get file info for stdin", + "reason", err) + break + } else if info.Size() <= 0 { + break + } + + return &io.LimitedReader{ + R: c.App.Reader, + N: min(info.Size(), 1<<32-1), // max u32 + } + } + + return &bytes.Reader{} // empty buffer +} diff --git a/cmd/ww/main.go b/cmd/ww/main.go index 9cfe6d4..8be0914 100644 --- a/cmd/ww/main.go +++ b/cmd/ww/main.go @@ -13,6 +13,7 @@ import ( "github.com/wetware/go/cmd/internal/call" "github.com/wetware/go/cmd/internal/export" "github.com/wetware/go/cmd/internal/run" + "github.com/wetware/go/cmd/internal/serve" ) func main() { @@ -41,6 +42,7 @@ func main() { }, Commands: []*cli.Command{ run.Command(), + serve.Command(), export.Command(), call.Command(), }, diff --git a/examples/deliver/main.go b/examples/deliver/main.go index 231e03e..db28062 100644 --- a/examples/deliver/main.go +++ b/examples/deliver/main.go @@ -7,8 +7,6 @@ import ( "io" "log/slog" "os" - - "github.com/wetware/go/std/system" ) func main() { @@ -17,7 +15,7 @@ func main() { "want", 1, "got", nargs, "args", os.Args) - os.Exit(system.StatusInvalidArgs) + os.Exit(1) } f, err := os.Open(os.Args[0]) @@ -25,7 +23,7 @@ func main() { slog.Error("failed to open file", "reason", err, "name", os.Args[0]) - os.Exit(system.StatusInvalidArgs) + os.Exit(1) } defer f.Close() @@ -40,7 +38,7 @@ func main() { slog.Error("failed to read message from stdin", "reason", err, "read", n) - os.Exit(system.StatusFailed) + os.Exit(2) } slog.Debug("delivered message", diff --git a/examples/deliver/main.wasm b/examples/deliver/main.wasm index 1e131e6..34f0ab3 100644 Binary files a/examples/deliver/main.wasm and b/examples/deliver/main.wasm differ diff --git a/examples/echo/main.go b/examples/echo/main.go index e7f8e20..8a5d662 100644 --- a/examples/echo/main.go +++ b/examples/echo/main.go @@ -8,17 +8,8 @@ import ( "io" "log/slog" "os" - - "github.com/wetware/go/std/system" ) -//export echo -func echo() { - if _, err := io.Copy(os.Stdout, os.Stdin); err != nil { - panic(err) - } -} - func main() { stdin := flag.Bool("stdin", false, "read data from stdin") flag.Parse() @@ -42,27 +33,11 @@ func main() { } } } - - if serve() { - // Yield control to the scheduler. - os.Exit(system.StatusAsync) - // The caller will intercept interface{ExitCode() uint32} and - // check if e.ExitCode() == system.StatusAwaiting. - // - // The top-level command will block until the runtime context - // expires. - } - - // Implicit status code 0 works as expected. - // Caller will resolve to err = nil. - // Top-level CLI command will unblock. } -func serve() bool { - switch os.Getenv("WW_SERVE") { - case "", "false", "0": - return false - default: - return true +//export echo +func echo() { + if _, err := io.Copy(os.Stdout, os.Stdin); err != nil { + panic(err) } } diff --git a/examples/echo/main.wasm b/examples/echo/main.wasm index 0791b4d..bf55504 100644 Binary files a/examples/echo/main.wasm and b/examples/echo/main.wasm differ diff --git a/mdns.go b/mdns.go new file mode 100644 index 0000000..5427a09 --- /dev/null +++ b/mdns.go @@ -0,0 +1,44 @@ +package ww + +import ( + "context" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/libp2p/go-libp2p/p2p/discovery/mdns" +) + +type MDNS struct { + Host host.Host + Handler mdns.Notifee +} + +// Serve mDNS to discover peers on the local network +func (m MDNS) Serve(ctx context.Context) error { + d, err := m.New(ctx) + if err != nil { + return err + } + defer d.Close() + + <-ctx.Done() + return ctx.Err() +} + +func (m MDNS) New(ctx context.Context) (mdns.Service, error) { + d := mdns.NewMdnsService(m.Host, "ww.local", m.Handler) + return d, d.Start() +} + +// StorePeer is a peer handler that inserts the peer in the +// supplied Peerstore. +type StorePeer struct { + peerstore.Peerstore +} + +func (s StorePeer) HandlePeerFound(info peer.AddrInfo) { + for _, addr := range info.Addrs { + s.AddAddr(info.ID, addr, peerstore.AddressTTL) // assume a dynamic environment + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..436c7bb --- /dev/null +++ b/server.go @@ -0,0 +1,42 @@ +package ww + +import ( + "context" + "path" + + iface "github.com/ipfs/kubo/core/coreiface" + "github.com/libp2p/go-libp2p/core/host" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" +) + +type Server struct { + IPFS iface.CoreAPI + Host host.Host + Env Env + RuntimeConfig wazero.RuntimeConfig +} + +func (s Server) String() string { + peer := string(s.Host.ID()) + return path.Join("/p2p", peer, Proto.String()) +} + +func (s Server) Serve(ctx context.Context) error { + if s.RuntimeConfig == nil { + s.RuntimeConfig = wazero.NewRuntimeConfig(). + WithCloseOnContextDone(true) + } + + // Set up WASM runtime and host modules + r := wazero.NewRuntimeWithConfig(ctx, s.RuntimeConfig) + defer r.Close(ctx) + + wasi, err := wasi_snapshot_preview1.Instantiate(ctx, r) + if err != nil { + return err + } + defer wasi.Close(ctx) + + return s.Env.Bind(ctx, r) +} diff --git a/std/system/system.go b/std/system/system.go deleted file mode 100644 index e00e31b..0000000 --- a/std/system/system.go +++ /dev/null @@ -1,37 +0,0 @@ -package system - -const ( - StatusAsync = 0x00ff0000 + iota - StatusInvalidArgs - StatusFailed -) - -// type StatusCode uint - -// func (status StatusCode) Error() string { -// switch status { -// case StatusAsync: -// return "awaiting method calls" -// case StatusInvalidArgs: -// return "invalid arguments" -// case StatusFailed: -// return "application failed" -// } - -// return status.Unwrap().Error() -// } - -// func (status StatusCode) Unwrap() error { -// switch status.ExitCode() { -// case sys.ExitCodeContextCanceled: -// return context.Canceled -// case sys.ExitCodeDeadlineExceeded: -// return context.DeadlineExceeded -// } - -// return sys.NewExitError(status.ExitCode()) -// } - -// func (status StatusCode) ExitCode() uint32 { -// return uint32(status) -// } diff --git a/system/fs.go b/system/fs.go new file mode 100644 index 0000000..eb78826 --- /dev/null +++ b/system/fs.go @@ -0,0 +1,62 @@ +package system + +import ( + "context" + "io/fs" + "strings" + + "github.com/ipfs/boxo/files" + "github.com/ipfs/boxo/path" + iface "github.com/ipfs/kubo/core/coreiface" + "github.com/libp2p/go-libp2p/core/host" + "github.com/pkg/errors" +) + +var _ fs.FS = (*FS)(nil) + +type FS struct { + Ctx context.Context + Host host.Host + IPFS iface.CoreAPI +} + +func (sys FS) Open(name string) (fs.File, error) { + switch { + case strings.HasPrefix(name, "/p2p/"): + return nil, &fs.PathError{ + Op: "open", + Path: name, + Err: errors.New("TODO"), // TODO(soon) + } + + case strings.HasPrefix(name, "/ipfs/"): + p, err := path.NewPath(name) + if err != nil { + return nil, err + } + + n, err := sys.OpenUnix(sys.Ctx, p) + if err != nil { + return nil, &fs.PathError{ + Op: "open", + Path: name, + Err: err, + } + } + + return &UnixNode{ + Path: p, + Node: n, + }, nil + } + + return nil, &fs.PathError{ + Op: "open", + Path: name, + Err: fs.ErrNotExist, + } +} + +func (sys FS) OpenUnix(ctx context.Context, p path.Path) (files.Node, error) { + return sys.IPFS.Unixfs().Get(ctx, p) +} diff --git a/system/ipfs.go b/system/ipfs.go index b0d64ec..3e12f9b 100644 --- a/system/ipfs.go +++ b/system/ipfs.go @@ -49,38 +49,38 @@ func (f IPFS) Open(name string) (fs.File, error) { } } - return &ipfsNode{ + return &UnixNode{ Path: path, Node: node, }, nil } var ( - _ fs.FileInfo = (*ipfsNode)(nil) - _ fs.ReadDirFile = (*ipfsNode)(nil) - _ fs.DirEntry = (*ipfsNode)(nil) + _ fs.FileInfo = (*UnixNode)(nil) + _ fs.ReadDirFile = (*UnixNode)(nil) + _ fs.DirEntry = (*UnixNode)(nil) ) -// ipfsNode provides access to a single file. The fs.File interface is the minimum +// UnixNode provides access to a single file. The fs.File interface is the minimum // implementation required of the file. Directory files should also implement [ReadDirFile]. // A file may implement io.ReaderAt or io.Seeker as optimizations. -type ipfsNode struct { +type UnixNode struct { Path path.Path files.Node } // base name of the file -func (n ipfsNode) Name() string { +func (n UnixNode) Name() string { segs := n.Path.Segments() return segs[len(segs)-1] // last segment is name } -func (n *ipfsNode) Stat() (fs.FileInfo, error) { +func (n *UnixNode) Stat() (fs.FileInfo, error) { return n, nil } // length in bytes for regular files; system-dependent for others -func (n ipfsNode) Size() int64 { +func (n UnixNode) Size() int64 { size, err := n.Node.Size() if err != nil { slog.Error("failed to obtain file size", @@ -92,7 +92,7 @@ func (n ipfsNode) Size() int64 { } // file mode bits -func (n ipfsNode) Mode() fs.FileMode { +func (n UnixNode) Mode() fs.FileMode { switch n.Node.(type) { case files.Directory: return fs.ModeDir @@ -102,21 +102,21 @@ func (n ipfsNode) Mode() fs.FileMode { } // modification time -func (n ipfsNode) ModTime() time.Time { +func (n UnixNode) ModTime() time.Time { return time.Time{} // zero-value time } // abbreviation for Mode().IsDir() -func (n ipfsNode) IsDir() bool { +func (n UnixNode) IsDir() bool { return n.Mode().IsDir() } // underlying data source (never returns nil) -func (n ipfsNode) Sys() any { +func (n UnixNode) Sys() any { return n.Node } -func (n ipfsNode) Read(b []byte) (int, error) { +func (n UnixNode) Read(b []byte) (int, error) { switch node := n.Node.(type) { case io.Reader: return node.Read(b) @@ -140,7 +140,7 @@ func (n ipfsNode) Read(b []byte) (int, error) { // to the end of the directory), it returns the slice and a nil error. // If it encounters an error before the end of the directory, // ReadDir returns the DirEntry list read until that point and a non-nil error. -func (n ipfsNode) ReadDir(max int) (entries []fs.DirEntry, err error) { +func (n UnixNode) ReadDir(max int) (entries []fs.DirEntry, err error) { root, ok := n.Node.(files.Directory) if !ok { return nil, errors.New("not a directory") @@ -166,7 +166,7 @@ func (n ipfsNode) ReadDir(max int) (entries []fs.DirEntry, err error) { return } - entries = append(entries, &ipfsNode{ + entries = append(entries, &UnixNode{ Path: subpath, Node: node}) @@ -194,13 +194,13 @@ func (n ipfsNode) ReadDir(max int) (entries []fs.DirEntry, err error) { // since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist). // If the entry denotes a symbolic link, Info reports the information about the link itself, // not the link's target. -func (n *ipfsNode) Info() (fs.FileInfo, error) { +func (n *UnixNode) Info() (fs.FileInfo, error) { return n, nil } // Type returns the type bits for the entry. // The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method. -func (n ipfsNode) Type() fs.FileMode { +func (n UnixNode) Type() fs.FileMode { if n.Mode().IsDir() { return fs.ModeDir } @@ -208,7 +208,7 @@ func (n ipfsNode) Type() fs.FileMode { return 0 } -func (n ipfsNode) Write(b []byte) (int, error) { +func (n UnixNode) Write(b []byte) (int, error) { dst, ok := n.Node.(io.Writer) if ok { return dst.Write(b) diff --git a/system/net.go b/system/net.go index 24b2fa8..93eb854 100644 --- a/system/net.go +++ b/system/net.go @@ -5,8 +5,6 @@ import ( "log/slog" "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/peerstore" "github.com/wetware/go/proc" protoutils "github.com/wetware/go/util/proto" ) @@ -42,16 +40,11 @@ func (n Net) Bind(ctx context.Context, p *proc.P) ReleaseFunc { } } -// StorePeer is a peer handler that inserts the peer in the -// supplied Peerstore. -type StorePeer struct { - peerstore.Peerstore -} - -func (s StorePeer) HandlePeerFound(info peer.AddrInfo) { - for _, addr := range info.Addrs { - s.AddAddr(info.ID, addr, peerstore.AddressTTL) // assume a dynamic environment +func (n Net) ServeProc(ctx context.Context, p *proc.P) (err error) { + if n.Handler != nil { + err = n.Handler.ServeProc(ctx, p) } + return } type Handler interface { diff --git a/system/system.go b/system/system.go index b31291f..b5a3171 100644 --- a/system/system.go +++ b/system/system.go @@ -1,44 +1,8 @@ package system -import ( - "context" - "io/fs" - "strings" - - iface "github.com/ipfs/kubo/core/coreiface" - "github.com/libp2p/go-libp2p/core/host" - "github.com/pkg/errors" -) - type ReleaseFunc func() -var _ fs.FS = (*FSConfig)(nil) - -type FSConfig struct { - Ctx context.Context - Host host.Host - IPFS iface.CoreAPI -} - -func (fsc FSConfig) Open(name string) (fs.File, error) { - if strings.HasPrefix(name, "/p2p/") { - return nil, &fs.PathError{ - Op: "open", - Path: name, - Err: errors.New("TODO"), - } - - // TODO: uncomment - - // return PeerFS{ - // Ctx: fsc.Ctx, - // Host: fsc.Host, - // }.Open(name) - } - - return IPFS{ - Ctx: fsc.Ctx, - Unix: fsc.IPFS.Unixfs(), - }.Open(name) - +type ExitError interface { + error + ExitCode() uint32 } diff --git a/ww.go b/ww.go index a999d5b..6a5d9c7 100644 --- a/ww.go +++ b/ww.go @@ -12,7 +12,6 @@ import ( "github.com/ipfs/boxo/path" "github.com/tetratelabs/wazero" "github.com/wetware/go/proc" - guest "github.com/wetware/go/std/system" "github.com/wetware/go/system" protoutils "github.com/wetware/go/util/proto" @@ -28,11 +27,17 @@ var Proto = protoutils.VersionedID{ type Env struct { IO system.IO Net system.Net - FS system.IPFS + FS system.FS } func (env Env) Bind(ctx context.Context, r wazero.Runtime) error { - p, err := env.Instantiate(ctx, r) + cm, err := env.LoadAndCompile(ctx, r, env.IO.Args[0]) // FIXME: panic if len(args)=0 + if err != nil { + return err + } + defer cm.Close(ctx) + + p, err := env.Instantiate(ctx, r, cm) if err != nil { return err } @@ -62,26 +67,16 @@ func (env Env) Bind(ctx context.Context, r wazero.Runtime) error { } err = p.Deliver(ctx, call) - switch e := err.(type) { - case interface{ ExitCode() uint32 }: - switch e.ExitCode() { - case 0: - return nil - case guest.StatusAsync: - return env.Net.ServeProc(ctx, p) - } + if e, ok := err.(system.ExitError); ok && e.ExitCode() != 0 { + return err + } else if err != nil { + return err } - return err + return env.Net.ServeProc(ctx, p) } -func (env Env) Instantiate(ctx context.Context, r wazero.Runtime) (*proc.P, error) { - cm, err := env.LoadAndCompile(ctx, r, env.IO.Args[0]) // FIXME: panic if len(args)=0 - if err != nil { - return nil, err - } - defer cm.Close(ctx) - +func (env Env) Instantiate(ctx context.Context, r wazero.Runtime, cm wazero.CompiledModule) (*proc.P, error) { return proc.Command{ Args: env.IO.Args, Env: env.IO.Env, @@ -96,7 +91,7 @@ func (env Env) LoadAndCompile(ctx context.Context, r wazero.Runtime, name string return nil, err } - n, err := env.FS.Unix.Get(ctx, p) + n, err := env.FS.OpenUnix(ctx, p) if err != nil { return nil, err } @@ -114,7 +109,7 @@ func (env Env) LoadAndCompile(ctx context.Context, r wazero.Runtime, name string it := node.Entries() for it.Next() { if it.Name() == "main.wasm" { - child := filepath.Join(p.String(), it.Name()) + child := filepath.Join(name, it.Name()) return env.LoadAndCompile(ctx, r, child) } }