blob: 54bbad6756fc06b9bdd4ee50a7ca67855a96aa0e [file] [log] [blame]
Tim Windelschmidt18e9a3f2024-04-08 21:51:03 +02001// Package bringup implements a simple wrapper which configures all default
2// mounts, logging and the corresponding forwarders to tty0 and ttyS0. It
3// then configures a new logtree and starts a supervisor to run the provided
4// supervisor.Runnable. Said Runnable is expected to never return. If it does,
5// the supervisor will exit, an error will be printed and the system will
6// reboot after five seconds.
7package bringup
8
9import (
10 "context"
11 "fmt"
12 "os"
13 "time"
14
15 "go.uber.org/multierr"
16 "golang.org/x/sys/unix"
17
18 "source.monogon.dev/osbase/bootparam"
19 "source.monogon.dev/osbase/efivarfs"
20 "source.monogon.dev/osbase/logtree"
21 "source.monogon.dev/osbase/supervisor"
22)
23
24type Runnable supervisor.Runnable
25
26func (r Runnable) Run() {
27 // Pause execution on panic to require manual intervention.
28 defer func() {
29 if r := recover(); r != nil {
30 fmt.Printf("Fatal error: %v\n", r)
31 fmt.Printf("This node could not be started. Rebooting...\n")
32
33 time.Sleep(5 * time.Second)
34 unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART)
35 }
36 }()
37
38 if err := setupMounts(); err != nil {
39 // We cannot do anything if we fail to mount.
40 panic(err)
41 }
42
43 // Set up logger. Parse consoles from the kernel command line
44 // as well as adding the two standard tty0/ttyS0 consoles.
45 consoles := make(map[string]bool)
46 cmdline, err := os.ReadFile("/proc/cmdline")
47 if err == nil {
48 params, _, err := bootparam.Unmarshal(string(cmdline))
49 if err == nil {
50 consoles = params.Consoles()
51 }
52 }
53 consoles["tty0"] = true
54 consoles["ttyS0"] = true
55
56 lt := logtree.New()
57 for consolePath := range consoles {
58 f, err := os.OpenFile("/dev/"+consolePath, os.O_WRONLY, 0)
59 if err != nil {
60 continue
61 }
62 reader, err := lt.Read("", logtree.WithChildren(), logtree.WithStream())
63 if err != nil {
64 panic(fmt.Errorf("could not set up root log reader: %v", err))
65 }
66 go func() {
67 for {
68 p := <-reader.Stream
69 fmt.Fprintf(f, "%s\n", p.String())
70 }
71 }()
72 }
73
74 sCtx, cancel := context.WithCancelCause(context.Background())
75
76 // Don't reschedule the root runnable...
77 supervisor.New(sCtx, func(ctx context.Context) (err error) {
78 defer func() {
79 if r := recover(); r != nil {
80 err = fmt.Errorf("root runnable paniced: %v", r)
81 cancel(err)
82 }
83 }()
84
85 err = r(ctx)
86 if err == nil {
87 err = fmt.Errorf("root runnable exited without any error")
88 }
89
90 cancel(err)
91 return nil
92 }, supervisor.WithExistingLogtree(lt))
93
94 <-sCtx.Done()
95 panic(context.Cause(sCtx))
96}
97
98func mkdirAndMount(dir, fs string, flags uintptr) error {
99 if err := os.MkdirAll(dir, 0o755); err != nil {
100 return fmt.Errorf("could not make %s: %w", dir, err)
101 }
102 if err := unix.Mount(fs, dir, fs, flags, ""); err != nil {
103 return fmt.Errorf("could not mount %s on %s: %w", fs, dir, err)
104 }
105 return nil
106}
107
108// setupMounts sets up basic mounts like sysfs, procfs, devtmpfs and cgroups.
109// This should be called early during init as a lot of processes depend on this
110// being available.
111func setupMounts() (err error) {
112 // Set up target filesystems.
113 for _, el := range []struct {
114 dir string
115 fs string
116 flags uintptr
117 }{
118 {"/sys", "sysfs", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
119 {"/sys/kernel/tracing", "tracefs", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
120 {"/sys/fs/pstore", "pstore", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
121 {"/proc", "proc", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
122 {"/dev", "devtmpfs", unix.MS_NOEXEC | unix.MS_NOSUID},
123 {"/dev/pts", "devpts", unix.MS_NOEXEC | unix.MS_NOSUID},
124 } {
125 err = multierr.Append(err, mkdirAndMount(el.dir, el.fs, el.flags))
126 }
127
128 // We try to mount efivarfs but ignore any error,
129 // as we don't want to crash on non-EFI systems.
130 _ = mkdirAndMount(efivarfs.Path, "efivarfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV)
131 return
132}