113 lines
2.3 KiB
Go
113 lines
2.3 KiB
Go
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
|
||
|
|
}
|