aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/earlyinit/main.go
blob: 987faeabac39e6d4eed1c23dcafce5948c2d07fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// The earlyinit is part of the Rosa OS initramfs and serves as the system init.
//
// This program is an internal detail of Rosa OS and is not usable on its own.
// It is not covered by the compatibility promise.
package main

import (
	"context"
	"crypto/rand"
	"io"
	"log"
	"os"
	"os/signal"
	"runtime"
	"runtime/pprof"
	"slices"
	"strings"
	. "syscall"

	"hakurei.app/internal/kobject"
	"hakurei.app/internal/report"
	"hakurei.app/internal/uevent"
	"hakurei.app/message"
)

var r report.Reporter

func init() {
	log.SetFlags(0)
	log.SetPrefix("earlyinit: ")
	r.SetOutput(log.Default())

	// this handles SIGQUIT to provide useful debugging information without
	// terminating, and prevents the runtime from throwing on the must family
	// of early error reporting functions, DO NOT REMOVE
	c := make(chan os.Signal, 1)
	signal.Notify(c, SIGQUIT)
	go func() {
		for {
			<-c
			if p := pprof.Lookup("goroutine"); p == nil {
				log.Println("initial built-in goroutine profile does not exist")
			} else if err := p.WriteTo(os.Stderr, 2); err != nil {
				log.Println(err)
			}
		}
	}()
}

// fatal calls [log.Println] with v and blocks forever. Must be called from
// main. Must not be used after error reporting is set up.
func fatal(v ...any) {
	log.Println(v...)
	log.Println("unable to continue, please reboot and resolve the problem manually")
	log.SetOutput(io.Discard)
	select {}
}

// must calls fatal with err if it is non-nil.
func must(err error) {
	if err != nil {
		fatal(err)
		select {}
	}
}

// mustSyscall is like must, but with an additional action name.
func mustSyscall(action string, err error) {
	if err != nil {
		fatal("cannot "+action+":", err)
		select {}
	}
}

// must1 is like must, but with an additional passed through value.
func must1[T any](v T, err error) T {
	must(err)
	return v
}

const (
	// optionSystem specifies devpath of the system device.
	optionSystem = "system"

	// flagVerbose increases output verbosity.
	flagVerbose = "verbose"
	// flagStrict sets [report.DStrict] on r.
	flagStrict = "strict"
	// flagNoRecover sets [report.DNoRecover] on r.
	flagNoRecover = "no_recover"
)

func main() {
	runtime.LockOSThread()

	var (
		option map[string]string
		flags  []string
	)
	if len(os.Args) > 1 {
		option = make(map[string]string)
		for _, s := range os.Args[1:] {
			key, value, ok := strings.Cut(s, "=")
			if !ok {
				flags = append(flags, s)
				continue
			}
			option[key] = value
		}
	}

	{
		var flag uint64
		if slices.Contains(flags, flagStrict) {
			flag |= report.DStrict
		}
		if slices.Contains(flags, flagNoRecover) {
			flag |= report.DNoRecover
		}
		log.Printf("reporting flags %x", flag)
		r.SetFlags(flag)
	}

	msg := message.New(log.Default())
	msg.SwapVerbose(slices.Contains(flags, flagVerbose))

	mustSyscall("mount devtmpfs", Mount(
		"devtmpfs",
		"/dev/",
		"devtmpfs",
		MS_NOSUID|MS_NOEXEC,
		"",
	))
	must(os.Mkdir("/dev/pts/", 0))
	mustSyscall("mount devpts", Mount(
		"devpts",
		"/dev/pts/",
		"devpts",
		MS_NOSUID|MS_NOEXEC,
		"mode=620,ptmxmode=666",
	))
	must(os.Mkdir("/dev/shm/", 0))
	mustSyscall("mount shm", Mount(
		"shm",
		"/dev/shm/",
		"tmpfs",
		MS_NOSUID|MS_NODEV,
		"",
	))

	// The kernel might be unable to set up the console. When that happens,
	// printk is called with "Warning: unable to open an initial console."
	// and the init runs with no files. The checkfds runtime function
	// populates 0-2 by opening /dev/null for them.
	//
	// This check replaces 1 and 2 with /dev/kmsg to improve the chance
	// of output being visible to the user.
	if fi, err := os.Stdout.Stat(); err == nil {
		if stat, ok := fi.Sys().(*Stat_t); ok {
			if stat.Rdev == 0x103 {
				var fd int
				if fd, err = Open(
					"/dev/kmsg",
					O_WRONLY|O_CLOEXEC,
					0,
				); err != nil {
					log.Fatalf("cannot open kmsg: %v", err)
				}

				if err = Dup3(fd, Stdout, 0); err != nil {
					log.Fatalf("cannot open stdout: %v", err)
				}
				if err = Dup3(fd, Stderr, 0); err != nil {
					log.Fatalf("cannot open stderr: %v", err)
				}

				if err = Close(fd); err != nil {
					log.Printf("cannot close kmsg: %v", err)
				}
			}
		}
	}

	// staying in rootfs, these are no longer used
	must(os.Remove("/root"))
	must(os.Remove("/init"))

	must(os.Mkdir("/proc", 0))
	mustSyscall("mount proc", Mount(
		"proc",
		"/proc",
		"proc",
		MS_NOSUID|MS_NOEXEC|MS_NODEV,
		"hidepid=1",
	))

	must(os.Mkdir("/sys", 0))
	mustSyscall("mount sysfs", Mount(
		"sysfs",
		"/sys",
		"sysfs",
		0,
		"",
	))

	conn := must1(uevent.Dial(-128 * 1024 * 1024))
	events := make(chan *uevent.Message, 1<<10)
	var uuid uevent.UUID
	must1(rand.Read(uuid[:]))
	ctx, cancel := context.WithCancel(context.Background())

	go consume(ctx, msg, &r, conn, uuid, events)
	s := kobject.New(uuid, func(o *kobject.Object, env map[string]string) {
		p := make([]string, 0, len(env))
		for k, v := range env {
			p = append(p, k+"="+v)
		}
		slices.Sort(p)
		log.Printf("change %s: %s", o.DevPath, strings.Join(p, ", "))
	}, func(err error) {
		severity := report.Inconsistent
		if e, ok := err.(kobject.EventError); ok && e.Kind == kobject.EBadTarget {
			severity = report.Trivial
		}
		r.Dispatch(
			severity,
			"processed inconsistent uevent",
			err,
		)
	})
	go func() {
		s.Consume(ctx, events)

		log.Println("closing NETLINK_KOBJECT_UEVENT socket")
		cancel()
		if err := conn.Close(); err != nil {
			log.Fatal(err) // not reached
		}
	}()

	must(os.Mkdir("/system", 0))
	if devpath := option[optionSystem]; devpath == "" {
		fatal("system must be nonempty")
	} else {
		log.Printf("waiting for devpath pattern %q", devpath)
		mustMountSystem(ctx, s, devpath)
	}

	// after top level has been set up
	mustSyscall("remount root", Mount(
		"",
		"/",
		"",
		MS_REMOUNT|MS_BIND|
			MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
		"",
	))

	must(os.WriteFile(
		"/sys/module/firmware_class/parameters/path",
		[]byte("/system/lib/firmware"),
		0,
	))
	go dispatchModprobe(ctx, s)

}