diff --git a/dev/bootloader.js b/dev/bootloader.js index 37a9332..92bea5d 100644 --- a/dev/bootloader.js +++ b/dev/bootloader.js @@ -81,31 +81,24 @@ if (!globalThis["ServiceWorkerGlobalScope"]) { await setupServiceWorker(); globalThis.initfs = {}; - const load = async (path) => { - const basename = (path) => path.replace(/\\/g,'/').split('/').pop(); - if (globalThis.initdata) { - // use embedded data if present - path = `./~init/${basename(path)}`; + const load = async (name, file) => { + // Determine if file contains a path to fetch or embedded file contents to load. + if(file.type === "text/plain") { + globalThis.initfs[name] = { mtimeMs: file.mtimeMs, blob: await (await fetch(`./sys/dev/${file.data}`)).blob() }; + } else { + globalThis.initfs[name] = { mtimeMs: file.mtimeMs, blob: await (await fetch(`./~init/${name}`)).blob() }; } - globalThis.initfs[basename(path)] = await (await fetch(path)).blob(); + }; + + // allow loading concurrently + let loads = []; + for(const property in globalThis.initdata) { + loads.push(load(property, globalThis.initdata[property])); } - // TODO: define these in one place. duplicated in initdata.go - await Promise.all([ - load("./sys/dev/kernel/web/lib/duplex.js"), - load("./sys/dev/kernel/web/lib/worker.js"), - load("./sys/dev/kernel/web/lib/syscall.js"), - load("./sys/dev/kernel/web/lib/task.js"), - load("./sys/dev/kernel/web/lib/wasm.js"), - load("./sys/dev/kernel/web/lib/host.js"), - load("./sys/dev/internal/indexedfs/indexedfs.js"), // maybe load from kernel? - load("./sys/dev/local/bin/kernel"), - load("./sys/dev/local/bin/shell"), - load("./sys/dev/local/bin/build"), - load("./sys/dev/local/bin/micro"), - ]); - - globalThis.duplex = await import(URL.createObjectURL(initfs["duplex.js"])); - globalThis.task = await import(URL.createObjectURL(initfs["task.js"])); + await Promise.all(loads); + + globalThis.duplex = await import(URL.createObjectURL(initfs["duplex.js"].blob)); + globalThis.task = await import(URL.createObjectURL(initfs["task.js"].blob)); globalThis.sys = new task.Task(initfs); @@ -114,7 +107,7 @@ if (!globalThis["ServiceWorkerGlobalScope"]) { await sys.exec("kernel"); // load host API - await import(URL.createObjectURL(initfs["host.js"])); + await import(URL.createObjectURL(initfs["host.js"].blob)); })(); } diff --git a/dev/bundle.go b/dev/bundle.go index d449154..31fe2a6 100644 --- a/dev/bundle.go +++ b/dev/bundle.go @@ -14,7 +14,7 @@ func main() { fatal(err) defer f.Close() - PackFilesTo(f) + PackFilesTo(f, PackFileData) src, err := os.ReadFile("./dev/bootloader.js") fatal(err) diff --git a/dev/initdata.go b/dev/initdata.go index 486aac9..2399b3f 100644 --- a/dev/initdata.go +++ b/dev/initdata.go @@ -7,42 +7,75 @@ import ( "io" "log" "os" - "path/filepath" "strings" "text/template" ) -func PackFilesTo(w io.Writer) { - var files []File - for _, path := range []string{ - "./kernel/web/lib/duplex.js", - "./kernel/web/lib/worker.js", - "./kernel/web/lib/syscall.js", - "./kernel/web/lib/task.js", - "./kernel/web/lib/wasm.js", - "./kernel/web/lib/host.js", - "./internal/indexedfs/indexedfs.js", - "./local/bin/kernel", - "./local/bin/shell", - "./local/bin/build", - "./local/bin/micro", - } { - typ := "application/octet-stream" - if strings.HasSuffix(path, ".js") { - typ = "application/javascript" +// Files must be local to the Wanix project root. +var files = []File{ + {Name: "duplex.js", Path: "./kernel/web/lib/duplex.js"}, + {Name: "worker.js", Path: "./kernel/web/lib/worker.js"}, + {Name: "syscall.js", Path: "./kernel/web/lib/syscall.js"}, + {Name: "task.js", Path: "./kernel/web/lib/task.js"}, + {Name: "wasm.js", Path: "./kernel/web/lib/wasm.js"}, + {Name: "host.js", Path: "./kernel/web/lib/host.js"}, + {Name: "indexedfs.js", Path: "./internal/indexedfs/indexedfs.js"}, + {Name: "kernel", Path: "./local/bin/kernel"}, + {Name: "build", Path: "./local/bin/build"}, + {Name: "macro", Path: "./local/bin/micro"}, + + // Shell source files + {Name: "shell/main.go", Path: "shell/main.go"}, + {Name: "shell/copy.go", Path: "shell/copy.go"}, + {Name: "shell/download.go", Path: "shell/download.go"}, + {Name: "shell/main.go", Path: "shell/main.go"}, + {Name: "shell/open.go", Path: "shell/open.go"}, + {Name: "shell/preprocessor.go", Path: "shell/preprocessor.go"}, + {Name: "shell/smallcmds.go", Path: "shell/smallcmds.go"}, + {Name: "shell/tree.go", Path: "shell/tree.go"}, + {Name: "shell/util.go", Path: "shell/util.go"}, + {Name: "shell/watch.go", Path: "shell/watch.go"}, +} + +type PackMode int + +const ( + PackFileData PackMode = iota + PackFilePaths +) + +func PackFilesTo(w io.Writer, mode PackMode) { + switch mode { + case PackFileData: + for i := range files { + if strings.HasSuffix(files[i].Path, ".js") { + files[i].Type = "application/javascript" + } else { + files[i].Type = "application/octet-stream" + } + + fi, err := os.Stat(files[i].Path) + fatal(err) + files[i].Mtime = fi.ModTime().UnixMilli() + + data, err := os.ReadFile(files[i].Path) + fatal(err) + var gzipBuffer bytes.Buffer + gzipWriter := gzip.NewWriter(&gzipBuffer) + _, err = gzipWriter.Write(data) + fatal(err) + fatal(gzipWriter.Close()) + files[i].Data = base64.StdEncoding.EncodeToString(gzipBuffer.Bytes()) + } + + case PackFilePaths: + for i := range files { + files[i].Type = "text/plain" + files[i].Data = files[i].Path + fi, err := os.Stat(files[i].Path) + fatal(err) + files[i].Mtime = fi.ModTime().UnixMilli() } - data, err := os.ReadFile(path) - fatal(err) - var gzipBuffer bytes.Buffer - gzipWriter := gzip.NewWriter(&gzipBuffer) - _, err = gzipWriter.Write(data) - fatal(err) - fatal(gzipWriter.Close()) - files = append(files, File{ - Name: filepath.Base(path), - Type: typ, - Data: base64.StdEncoding.EncodeToString(gzipBuffer.Bytes()), - }) } t := template.Must(template.New("initdata.tmpl").ParseFiles("./dev/initdata.tmpl")) @@ -52,9 +85,11 @@ func PackFilesTo(w io.Writer) { } type File struct { - Name string - Type string - Data string + Name string + Path string + Type string + Data string + Mtime int64 } func fatal(err error) { diff --git a/dev/initdata.tmpl b/dev/initdata.tmpl index a38408a..9143c70 100644 --- a/dev/initdata.tmpl +++ b/dev/initdata.tmpl @@ -1,5 +1,5 @@ globalThis.initdata = { {{range .}} - "{{.Name}}": {type: "{{.Type}}", data: "{{.Data}}"}, + "{{.Name}}": {type: "{{.Type}}", mtimeMs: {{.Mtime}}, data: "{{.Data}}"}, {{end}} } diff --git a/dev/server.go b/dev/server.go index 2827731..582eb6c 100644 --- a/dev/server.go +++ b/dev/server.go @@ -38,17 +38,21 @@ func main() { mux.Handle(fmt.Sprintf("%s/wanix-bootloader.js", basePath), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/javascript") - if os.Getenv("PROD") != "1" { - http.ServeFile(w, r, "./dev/bootloader.js") - return + var packMode PackMode + if os.Getenv("PROD") == "1" { + log.Printf("Packing self-contained bootloader...\n") + packMode = PackFileData + } else { + packMode = PackFilePaths } - // emulate a build - PackFilesTo(w) + // TODO: Does this need to pack on every request for the bootloader? + // I don't think you want to be changing PROD at runtime, so we can + // probably cache this. + PackFilesTo(w, packMode) f, err := os.ReadFile("./dev/bootloader.js") fatal(err) w.Write(f) - })) mux.Handle(fmt.Sprintf("%s/~init/", basePath), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "Not found", http.StatusNotFound) diff --git a/internal/app/terminal/index.html b/internal/app/terminal/index.html index 1069af8..ffbcc4e 100644 --- a/internal/app/terminal/index.html +++ b/internal/app/terminal/index.html @@ -61,7 +61,7 @@ const enc = new TextEncoder(); const dec = new TextDecoder(); - const resp = await sys.call("tty.open", ["shell", [], { TERM: "xterm-256color" }]); + const resp = await sys.call("tty.open", ["sys/cmd/shell", [], { TERM: "xterm-256color" }]); const pid = resp.value; const ch = resp.channel; //parent.wanix.termCh = ch; diff --git a/internal/indexedfs/indexedfs.go b/internal/indexedfs/indexedfs.go index d29f1e9..a68901d 100644 --- a/internal/indexedfs/indexedfs.go +++ b/internal/indexedfs/indexedfs.go @@ -54,7 +54,7 @@ type FS struct { func New() (*FS, error) { if helper.IsUndefined() { - blob := js.Global().Get("initfs").Get("indexedfs.js") + blob := js.Global().Get("initfs").Get("indexedfs.js").Get("blob") url := js.Global().Get("URL").Call("createObjectURL", blob) helper = jsutil.Await(js.Global().Call("import", url)) } diff --git a/kernel/fs/fs.go b/kernel/fs/fs.go index 78ecf7e..6890cfa 100644 --- a/kernel/fs/fs.go +++ b/kernel/fs/fs.go @@ -32,7 +32,9 @@ func log(args ...any) { } type Service struct { - fsys *watchfs.FS + fsys fs.MutableFS + // Wraps fsys, so it's actually the same filesystem. + watcher *watchfs.FS fds map[int]*fd nextFd int @@ -53,21 +55,30 @@ func (s *Service) Initialize() { if err != nil { panic(err) } - mntfs := mountablefs.New(ifs) - s.fsys = watchfs.New(mntfs) + s.fsys = mountablefs.New(ifs) + s.watcher = watchfs.New(s.fsys) // ensure basic system tree exists - fs.MkdirAll(s.fsys.FS, "app", 0755) - fs.MkdirAll(s.fsys.FS, "cmd", 0755) - fs.MkdirAll(s.fsys.FS, "sys/app", 0755) - fs.MkdirAll(s.fsys.FS, "sys/bin", 0755) - fs.MkdirAll(s.fsys.FS, "sys/cmd", 0755) - fs.MkdirAll(s.fsys.FS, "sys/dev", 0755) - fs.MkdirAll(s.fsys.FS, "sys/tmp", 0755) + fs.MkdirAll(s.fsys, "app", 0755) + fs.MkdirAll(s.fsys, "cmd", 0755) + fs.MkdirAll(s.fsys, "sys/app", 0755) + fs.MkdirAll(s.fsys, "sys/bin", 0755) + fs.MkdirAll(s.fsys, "sys/cmd", 0755) + fs.MkdirAll(s.fsys, "sys/dev", 0755) + fs.MkdirAll(s.fsys, "sys/tmp", 0755) // copy builtin exe's into filesystem - s.copyInitFileIntoFS("sys/cmd/build.wasm", "build") - s.copyInitFileIntoFS("cmd/micro.wasm", "micro") + s.copyFileFromInitFS("sys/cmd/build.wasm", "build") + s.copyFileFromInitFS("cmd/micro.wasm", "micro") + + // copy shell source into filesystem + fs.MkdirAll(s.fsys, "sys/cmd/shell", 0755) + shellFiles := getPrefixedInitFiles("shell/") + for _, path := range shellFiles { + if err = s.copyFileFromInitFS(filepath.Join("sys/cmd", path), path); err != nil { + panic(err) + } + } devURL := fmt.Sprintf("%ssys/dev", js.Global().Get("hostURL").String()) resp, err := http.DefaultClient.Get(devURL) @@ -75,19 +86,39 @@ func (s *Service) Initialize() { panic(err) } if resp.StatusCode == 200 { - if err := s.fsys.FS.(*mountablefs.FS).Mount(httpfs.New(devURL), "/sys/dev"); err != nil { + if err := s.fsys.(*mountablefs.FS).Mount(httpfs.New(devURL), "/sys/dev"); err != nil { panic(err) } } - if err := s.fsys.FS.(*mountablefs.FS).Mount(memfs.New(), "/sys/tmp"); err != nil { + if err := s.fsys.(*mountablefs.FS).Mount(memfs.New(), "/sys/tmp"); err != nil { panic(err) } } -func (s *Service) copyInitFileIntoFS(dst, src string) error { +func getPrefixedInitFiles(prefix string) []string { + names := js.Global().Get("Object").Call("getOwnPropertyNames", js.Global().Get("initfs")) + length := names.Length() + + var result []string + for i := 0; i < length; i += 1 { + name := names.Index(i).String() + if strings.HasPrefix(name, prefix) { + result = append(result, name) + } + } + + return result +} + +func (s *Service) copyFileFromInitFS(dst, src string) error { + initFile := js.Global().Get("initfs").Get(src) + if initFile.IsUndefined() { + return nil + } + var exists bool - fi, err := fs.Stat(s.fsys.FS, dst) + fi, err := fs.Stat(s.fsys, dst) if err == nil { exists = true } else if os.IsNotExist(err) { @@ -96,12 +127,8 @@ func (s *Service) copyInitFileIntoFS(dst, src string) error { return err } - blob := js.Global().Get("initfs").Get(src) - if blob.IsUndefined() { - return nil - } - - if !exists || int64(blob.Get("size").Int()) != fi.Size() { + if !exists || time.UnixMilli(int64(initFile.Get("mtimeMs").Float())).After(fi.ModTime()) { + blob := initFile.Get("blob") buffer, err := jsutil.AwaitErr(blob.Call("arrayBuffer")) if err != nil { return err @@ -110,7 +137,7 @@ func (s *Service) copyInitFileIntoFS(dst, src string) error { // TODO: creating the file and applying the blob directly in indexedfs would be faster. data := make([]byte, blob.Get("size").Int()) js.CopyBytesToGo(data, js.Global().Get("Uint8Array").New(buffer)) - err = fs.WriteFile(s.fsys.FS, dst, data, 0644) + err = fs.WriteFile(s.fsys, dst, data, 0644) if err != nil { return err } @@ -197,7 +224,7 @@ func (s *Service) open(this js.Value, args []js.Value) any { go func() { log("open", path, s.nextFd, strings.Join(flags, ","), fmt.Sprintf("%o\n", mode)) - f, err := s.fsys.FS.(*mountablefs.FS).OpenFile(path, flag, fs.FileMode(mode)) + f, err := s.fsys.OpenFile(path, flag, fs.FileMode(mode)) if err != nil { if f != nil { log("opened") @@ -346,7 +373,7 @@ func (s *Service) readdir(this js.Value, args []js.Value) any { go func() { log("readdir", path) - fi, err := fs.ReadDir(s.fsys.FS, path) + fi, err := fs.ReadDir(s.fsys, path) if err != nil { cb.Invoke(jsutil.ToJSError(err)) return @@ -385,7 +412,7 @@ func newStatEmpty() map[string]any { } func (s *Service) _stat(path string, cb js.Value) { - fi, err := s.fsys.FS.(*mountablefs.FS).Stat(path) + fi, err := s.fsys.Stat(path) if err != nil { cb.Invoke(jsutil.ToJSError(err)) return @@ -504,7 +531,7 @@ func (s *Service) chown(this js.Value, args []js.Value) any { go func() { log("chown", path) - if err := s.fsys.FS.(*mountablefs.FS).Chown(path, uid, gid); err != nil { + if err := s.fsys.Chown(path, uid, gid); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -533,7 +560,7 @@ func (s *Service) fchown(this js.Value, args []js.Value) any { return } - if err := s.fsys.FS.(*mountablefs.FS).Chown(f.Path, uid, gid); err != nil { + if err := s.fsys.Chown(f.Path, uid, gid); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -554,7 +581,7 @@ func (s *Service) lchown(this js.Value, args []js.Value) any { go func() { log("lchown", path) - if err := s.fsys.FS.(*mountablefs.FS).Chown(path, uid, gid); err != nil { + if err := s.fsys.Chown(path, uid, gid); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -574,7 +601,7 @@ func (s *Service) chmod(this js.Value, args []js.Value) any { go func() { log("chmod", path) - if err := s.fsys.FS.(*mountablefs.FS).Chmod(path, fs.FileMode(mode)); err != nil { + if err := s.fsys.Chmod(path, fs.FileMode(mode)); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -602,7 +629,7 @@ func (s *Service) fchmod(this js.Value, args []js.Value) any { return } - if err := s.fsys.FS.(*mountablefs.FS).Chmod(f.Path, fs.FileMode(mode)); err != nil { + if err := s.fsys.Chmod(f.Path, fs.FileMode(mode)); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -622,7 +649,7 @@ func (s *Service) mkdir(this js.Value, args []js.Value) any { go func() { log("mkdir", path) - if err := s.fsys.FS.(*mountablefs.FS).MkdirAll(path, os.FileMode(perm)); err != nil { + if err := s.fsys.MkdirAll(path, os.FileMode(perm)); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -641,7 +668,7 @@ func (s *Service) rename(this js.Value, args []js.Value) any { go func() { log("rename", from, to) - if err := s.fsys.FS.(*mountablefs.FS).Rename(from, to); err != nil { + if err := s.fsys.Rename(from, to); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -661,7 +688,7 @@ func (s *Service) rmdir(this js.Value, args []js.Value) any { log("rmdir", path) // TODO: should only remove if dir is empty i think? - if err := s.fsys.FS.(*mountablefs.FS).RemoveAll(path); err != nil { + if err := s.fsys.RemoveAll(path); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -681,7 +708,7 @@ func (s *Service) unlink(this js.Value, args []js.Value) any { log("unlink", path) // GOOS=js calls unlink for os.RemoveAll so we use RemoveAll here - if err := s.fsys.FS.(*mountablefs.FS).RemoveAll(path); err != nil { + if err := s.fsys.RemoveAll(path); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -733,7 +760,7 @@ func (s *Service) utimes(this js.Value, args []js.Value) any { go func() { log("utimes", path) - if err := s.fsys.FS.(*mountablefs.FS).Chtimes(path, atime, mtime); err != nil { + if err := s.fsys.Chtimes(path, atime, mtime); err != nil { cb.Invoke(jsutil.ToJSError(err)) return } @@ -762,7 +789,7 @@ func (s *Service) watchRPC(this js.Value, args []js.Value) any { log("watch", path, recursive, eventMask, params.Index(3)) - w, err := s.fsys.Watch(path, &watchfs.Config{ + w, err := s.watcher.Watch(path, &watchfs.Config{ Recursive: recursive, EventMask: eventMask, Ignores: ignores, diff --git a/kernel/main.go b/kernel/main.go index 4fb1e2d..7d41d3d 100644 --- a/kernel/main.go +++ b/kernel/main.go @@ -34,7 +34,7 @@ type Kernel struct { func (k *Kernel) Run(ctx context.Context) error { // import syscall.js - blob := js.Global().Get("initfs").Get("syscall.js") + blob := js.Global().Get("initfs").Get("syscall.js").Get("blob") url := js.Global().Get("URL").Call("createObjectURL", blob) jsutil.Await(js.Global().Call("import", url)) diff --git a/kernel/proc/proc.go b/kernel/proc/proc.go index 30018d0..83307e2 100644 --- a/kernel/proc/proc.go +++ b/kernel/proc/proc.go @@ -3,9 +3,13 @@ package proc import ( "fmt" "io" + "os" + "path/filepath" + "strings" "sync" "syscall/js" + "tractor.dev/toolkit-go/engine/fs" "tractor.dev/wanix/internal/jsutil" ) @@ -32,6 +36,25 @@ func (s *Service) Get(pid int) (*Process, error) { func (s *Service) Spawn(path string, args []string, env map[string]string, dir string) (*Process, error) { // TODO: check path exists, execute bit + // can't use jsutil.WanixSyscall inside the kernel + stat, err := jsutil.AwaitErr(js.Global().Get("api").Get("fs").Call("stat", path)) + if err != nil { + return nil, err + } + + if stat.Get("isDirectory").Bool() { + matches, _ := fs.Glob(os.DirFS(unixToFsPath(path)), "*.go") + if matches != nil && len(matches) > 0 { + path, err = s.buildCmdSource(path, dir) + if err != nil { + return nil, err + } + if path == "" { + return nil, os.ErrInvalid + } + } + } + if env == nil { // TODO: set from os.Environ() } @@ -53,7 +76,7 @@ func (s *Service) Spawn(path string, args []string, env map[string]string, dir s s.mu.Unlock() p.Task = js.Global().Get("task").Get("Task").New(js.Global().Get("initfs"), p.ID) - _, err := jsutil.AwaitErr(p.Task.Call("exec", p.Path, jsutil.ToJSArray(p.Args), map[string]any{ + _, err = jsutil.AwaitErr(p.Task.Call("exec", p.Path, jsutil.ToJSArray(p.Args), map[string]any{ "env": jsutil.ToJSMap(p.Env), "dir": p.Dir, })) @@ -103,3 +126,73 @@ func (p *Process) Wait() (int, error) { v, err := jsutil.AwaitErr(p.Task.Call("wait")) return v.Int(), err } + +func unixToFsPath(path string) string { + return filepath.Clean(strings.TrimLeft(path, "/")) +} + +// returns an empty wasmPath on error or non-zero exit code +func (s *Service) buildCmdSource(path, workingDir string) (wasmPath string, err error) { + wasmPath = filepath.Join("/sys/bin", filepath.Base(path)+".wasm") + + dfs := os.DirFS("/") + var wasmExists bool + wasmStat, err := fs.Stat(dfs, unixToFsPath(wasmPath)) + if err == nil { + wasmExists = true + } else if os.IsNotExist(err) { + wasmExists = false + } else { + return "", err + } + + var shouldBuild bool + if !wasmExists { + shouldBuild = true + } else { + wasmMtime := wasmStat.ModTime() + err = fs.WalkDir(dfs, unixToFsPath(path), func(walkPath string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + + fi, err := d.Info() + if err != nil { + return err + } + + if fi.ModTime().After(wasmMtime) { + shouldBuild = true + return fs.SkipAll + } + + return nil + }) + if err != nil { + return "", err + } + } + + if shouldBuild { + p, err := s.Spawn( + "/sys/cmd/build.wasm", + []string{"-output", wasmPath, path}, + map[string]string{}, + workingDir, + ) + if err != nil { + return "", err + } + + // TODO: https://github.com/tractordev/wanix/issues/69 + // go io.Copy(os.Stdout, p.Stdout()) + // go io.Copy(os.Stderr, p.Stderr()) + + exitCode, err := p.Wait() + if exitCode != 0 { + return "", err + } + } + + return wasmPath, nil +} diff --git a/kernel/web/lib/task.js b/kernel/web/lib/task.js index 91902c0..709e6d8 100644 --- a/kernel/web/lib/task.js +++ b/kernel/web/lib/task.js @@ -11,8 +11,8 @@ export class Task { const name = `${path.split('/').pop()}.${this.pid}`; const blob = new Blob([ - this.initfs["worker.js"], - this.initfs["wasm.js"], + this.initfs["worker.js"].blob, + this.initfs["wasm.js"].blob, `\n//# sourceURL=${name}\n` // names the worker in logs ], { type: 'application/javascript' }); @@ -34,7 +34,7 @@ export class Task { }) }); - const duplex = await import(URL.createObjectURL(this.initfs["duplex.js"])); + const duplex = await import(URL.createObjectURL(this.initfs["duplex.js"].blob)); this.pipe = duplex.open(new duplex.WorkerConn(this.worker), new duplex.CBORCodec()); this.pipe.respond(); diff --git a/kernel/web/lib/wasm.js b/kernel/web/lib/wasm.js index f92f4a8..02f64a2 100644 --- a/kernel/web/lib/wasm.js +++ b/kernel/web/lib/wasm.js @@ -101,10 +101,11 @@ }, // writeSync used by runtime.wasmWrite writeSync(fd, buf) { + if(!buf) return 0; + outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf("\n"); if (nl != -1) { - console.log(outputBuf.substring(0, nl)); outputBuf = outputBuf.substring(nl + 1); } return buf.length; diff --git a/kernel/web/lib/worker.js b/kernel/web/lib/worker.js index 0f9299e..45a4814 100644 --- a/kernel/web/lib/worker.js +++ b/kernel/web/lib/worker.js @@ -12,8 +12,8 @@ addEventListener("message", async (e) => { globalThis.process.ppid = e.data.init.ppid; globalThis.process.dir = e.data.init.dir; - globalThis.duplex = await import(URL.createObjectURL(initfs["duplex.js"])); - globalThis.task = await import(URL.createObjectURL(initfs["task.js"])); // only for kernel + globalThis.duplex = await import(URL.createObjectURL(initfs["duplex.js"].blob)); + globalThis.task = await import(URL.createObjectURL(initfs["task.js"].blob)); // only for kernel globalThis.sys = duplex.open(new duplex.WorkerConn(globalThis), new duplex.CBORCodec()); @@ -26,7 +26,7 @@ addEventListener("message", async (e) => { } let mod; if (initfs[params[0]]) { - mod = await blobToArrayBuffer(initfs[params[0]]); + mod = await blobToArrayBuffer(initfs[params[0]].blob); } else { mod = await globalThis.fs.readFile(params[0]); } diff --git a/shell/main.go b/shell/main.go index bd6f064..7bb6d89 100644 --- a/shell/main.go +++ b/shell/main.go @@ -249,18 +249,13 @@ func findCommand(name string, args []string) (*exec.Cmd, error) { if scriptPath != "" { shellArgs := append([]string{scriptPath}, args...) - // TODO: shell is currently only available in the initfs, - // but the process worker is able to exec it from there anyway. - // We should really mount the shell exe in /sys/bin though. - return exec.Command("shell", shellArgs...), nil + return exec.Command("/sys/bin/shell.wasm", shellArgs...), nil } - var err error if buildPath != "" { - wasmPath, err = buildCmdSource(buildPath) - if err != nil { - return nil, err - } + // kernel/proc will automatically build and execute the program if you + // pass it the path to it's source code. + return exec.Command(buildPath, args...), nil } if wasmPath != "" { diff --git a/shell/util.go b/shell/util.go index ef49910..5deccd3 100644 --- a/shell/util.go +++ b/shell/util.go @@ -8,35 +8,8 @@ import ( "path/filepath" "strings" "sync" - - "tractor.dev/toolkit-go/engine/fs/fsutil" - "tractor.dev/wanix/kernel/proc/exec" ) -// returns an empty wasmPath on error or non-zero exit code -func buildCmdSource(path string) (wasmPath string, err error) { - wasmPath = filepath.Join("/sys/bin", filepath.Base(path)+".wasm") - - // TODO: could also just change the search order in shell.go:findCommand() - wasmExists, err := fsutil.Exists(os.DirFS("/"), unixToFsPath(wasmPath)) - if err != nil { - return "", err - } - - if !wasmExists { - cmd := exec.Command("build", "-output", wasmPath, path) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - exitCode, err := cmd.Run() - if exitCode != 0 { - return "", err - } - } - - return wasmPath, nil -} - var WASM_MAGIC = []byte{0, 'a', 's', 'm'} func isWasmFile(name string) bool {