blob: 77601d7acdd41538e376cc70e98bccae8b76830d [file] [log] [blame]
Lorenz Brun5a5c66b2024-08-22 16:11:44 +02001package mgmt
2
3import (
4 "context"
5 "os"
6 "time"
7
8 "golang.org/x/sys/unix"
9 "google.golang.org/grpc/codes"
10 "google.golang.org/grpc/status"
11
12 apb "source.monogon.dev/metropolis/proto/api"
13 "source.monogon.dev/osbase/efivarfs"
14)
15
16func (s *Service) Reboot(ctx context.Context, req *apb.RebootRequest) (*apb.RebootResponse, error) {
17 var method int
18 // Do not yet perform any system-wide actions here as the request might
19 // still get rejected. There is another switch statement for that below.
20 switch req.Type {
21 case apb.RebootRequest_KEXEC:
22 method = unix.LINUX_REBOOT_CMD_KEXEC
23 case apb.RebootRequest_FIRMWARE:
24 method = unix.LINUX_REBOOT_CMD_RESTART
25 case apb.RebootRequest_POWER_OFF:
26 method = unix.LINUX_REBOOT_CMD_POWER_OFF
27 default:
28 return nil, status.Error(codes.Unimplemented, "unimplemented type value")
29 }
30 switch req.NextBoot {
31 case apb.RebootRequest_START_NORMAL:
32 case apb.RebootRequest_START_ROLLBACK:
33 if err := s.UpdateService.Rollback(); err != nil {
34 return nil, status.Errorf(codes.Unavailable, "performing rollback failed: %v", err)
35 }
36 case apb.RebootRequest_START_FIRMWARE_UI:
37 if req.Type == apb.RebootRequest_KEXEC {
38 return nil, status.Error(codes.InvalidArgument, "START_FIRMWARE_UI cannot be used with KEXEC type")
39 }
40 supp, err := efivarfs.OSIndicationsSupported()
41 if err != nil || supp&efivarfs.BootToFirmwareUI == 0 {
42 return nil, status.Error(codes.Unimplemented, "Unable to boot into firmware UI on this platform")
43 }
44 if err := efivarfs.SetOSIndications(efivarfs.BootToFirmwareUI); err != nil {
45 return nil, status.Errorf(codes.Unavailable, "Unable to set UEFI boot to UI indication: %v", err)
46 }
47 default:
48 return nil, status.Error(codes.Unimplemented, "unimplemented next_boot value")
49 }
50
51 switch req.Type {
52 case apb.RebootRequest_KEXEC:
53 if err := s.UpdateService.KexecLoadNext(); err != nil {
54 return nil, status.Errorf(codes.Unavailable, "failed to stage kexec kernel: %v", err)
55 }
56 case apb.RebootRequest_FIRMWARE:
57 // Best-effort, if it fails this will still be a firmware reboot.
58 os.WriteFile("/sys/kernel/reboot/mode", []byte("cold"), 0644)
59 }
60 s.LogTree.MustLeveledFor("root.mgmt").Warning("Reboot requested, rebooting in 2s")
61 go func() {
62 time.Sleep(2 * time.Second)
63 unix.Unmount(s.UpdateService.ESPPath, 0)
64 unix.Sync()
65 unix.Reboot(method)
66 }()
67 return &apb.RebootResponse{}, nil
68}