Hold a fatal error on the framebuffer and reboot in five minutes rather than exiting, since init's death drops the kernel's panic screen over the cause
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user