diff --git a/busybox.go b/busybox.go new file mode 100644 index 0000000..d395380 --- /dev/null +++ b/busybox.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "strings" + "syscall" +) + +const wellKnownBusybox = "/usr/local/bin/busybox" + +// mountBin bind-mounts /bin to a tmpfs. +func mountBin() error { + b, err := os.ReadFile("/proc/self/mountinfo") + if err != nil { + return err + } + for _, line := range strings.Split(strings.TrimSpace(string(b)), "\n") { + parts := strings.Fields(line) + if len(parts) < 5 { + continue + } + mountpoint := parts[4] + log.Printf("Found mountpoint %q", parts[4]) + if mountpoint == "/bin" { + log.Printf("/bin file system already mounted, nothing to do") + return nil + } + } + + if err := syscall.Mount("tmpfs", "/bin", "tmpfs", 0, ""); err != nil { + return fmt.Errorf("mounting tmpfs on /bin: %v", err) + } + + return nil +} + +func installBusybox() error { + // /bin is read-only by default, so mount a tmpfs over it + if err := mountBin(); err != nil { + return err + } + + install := exec.Command(wellKnownBusybox, "--install", "-s", "/bin") + install.Stdout = os.Stdout + install.Stderr = os.Stderr + if err := install.Run(); err != nil { + return fmt.Errorf("%v: %v", install.Args, err) + } + return nil +} diff --git a/ssh.go b/ssh.go index bd55ed1..fdaa859 100644 --- a/ssh.go +++ b/ssh.go @@ -215,6 +215,16 @@ type exitStatus struct { } func findShell() string { + if _, err := os.Stat(wellKnownBusybox); err == nil { + // Install busybox to /bin to provide the typical userspace utilities + // in standard locations (makes Emacs TRAMP work, for example). + if err := installBusybox(); err != nil { + log.Printf("installing busybox failed: %v", err) + // fallthrough + } else { + return "/bin/sh" // available after installation + } + } if path, err := exec.LookPath("sh"); err == nil { return path }