package main import ( "fmt" "os" "os/signal" "strings" "syscall" "time" "golang.org/x/sys/unix" ) const fatalRebootDelay = 5 * time.Minute var fatalCh = make(chan any, 1) // Deferred in every goroutine that can panic: as PID 1 the message is shown by // the run loop instead of killing init; anywhere else the panic keeps its stack. func holdPanic() { p := recover() if p == nil { return } if os.Getpid() != 1 { panic(p) } select { case fatalCh <- p: default: } } // Exiting would take init with it and the kernel's panic screen would cover // the cause, so it is drawn and held instead, with a reboot to retry. func fatal(cause error) { if os.Getpid() != 1 { panic(cause) } if err := drawFatal(cause); err != nil { panic(fmt.Sprintf("%v; drawing it: %v", cause, err)) } sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) select { case <-sig: case <-time.After(fatalRebootDelay): } if err := unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART); err != nil { panic(err) } } func drawFatal(cause error) error { fb, err := openFramebuffer() if err != nil { return err } title, err := loadFace(true, 40) if err != nil { return err } body, err := loadFace(false, 34) if err != nil { return err } fb.fill(uiBg) noteY := fb.h - spaceRow - body.lineH y := spaceRow title.draw(fb, spaceRow, y-title.capTop, "FATAL", uiRed) y += title.lineH + spaceRow for _, line := range wrapWords(cause.Error(), (fb.w-2*spaceRow)/body.cellW) { if y+body.lineH > noteY-spaceRow { body.draw(fb, spaceRow, y-body.capTop, "...", uiFg) break } body.draw(fb, spaceRow, y-body.capTop, line, uiFg) y += body.lineH + spaceTight } note := fmt.Sprintf("rebooting in %d minutes", int(fatalRebootDelay.Minutes())) body.draw(fb, spaceRow, noteY-body.capTop, note, uiDim) return fb.flush() } func wrapWords(s string, cols int) []string { var lines []string line := "" for _, w := range strings.Fields(s) { for len(w) > cols { if line != "" { lines = append(lines, line) line = "" } lines = append(lines, w[:cols]) w = w[cols:] } switch { case line == "": line = w case len(line)+1+len(w) <= cols: line += " " + w default: lines = append(lines, line) line = w } } if line != "" { lines = append(lines, line) } return lines }