blob: 9273ab371b09456358c355852716816d03aaa1a8 [file] [log] [blame]
Lorenz Brun5a5c66b2024-08-22 16:11:44 +02001package mgmt
2
3import (
4 "context"
5 "os"
6 "time"
7
Lorenz Bruna036c4e2024-09-10 19:11:57 +02008 "github.com/vishvananda/netlink"
Lorenz Brun5a5c66b2024-08-22 16:11:44 +02009 "golang.org/x/sys/unix"
10 "google.golang.org/grpc/codes"
11 "google.golang.org/grpc/status"
12
13 apb "source.monogon.dev/metropolis/proto/api"
14 "source.monogon.dev/osbase/efivarfs"
15)
16
17func (s *Service) Reboot(ctx context.Context, req *apb.RebootRequest) (*apb.RebootResponse, error) {
18 var method int
19 // Do not yet perform any system-wide actions here as the request might
20 // still get rejected. There is another switch statement for that below.
21 switch req.Type {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010022 case apb.RebootRequest_TYPE_KEXEC:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020023 method = unix.LINUX_REBOOT_CMD_KEXEC
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010024 case apb.RebootRequest_TYPE_FIRMWARE:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020025 method = unix.LINUX_REBOOT_CMD_RESTART
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010026 case apb.RebootRequest_TYPE_POWER_OFF:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020027 method = unix.LINUX_REBOOT_CMD_POWER_OFF
28 default:
29 return nil, status.Error(codes.Unimplemented, "unimplemented type value")
30 }
31 switch req.NextBoot {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010032 case apb.RebootRequest_NEXT_BOOT_START_NORMAL:
33 case apb.RebootRequest_NEXT_BOOT_START_ROLLBACK:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020034 if err := s.UpdateService.Rollback(); err != nil {
35 return nil, status.Errorf(codes.Unavailable, "performing rollback failed: %v", err)
36 }
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010037 case apb.RebootRequest_NEXT_BOOT_START_FIRMWARE_UI:
38 if req.Type == apb.RebootRequest_TYPE_KEXEC {
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020039 return nil, status.Error(codes.InvalidArgument, "START_FIRMWARE_UI cannot be used with KEXEC type")
40 }
41 supp, err := efivarfs.OSIndicationsSupported()
42 if err != nil || supp&efivarfs.BootToFirmwareUI == 0 {
43 return nil, status.Error(codes.Unimplemented, "Unable to boot into firmware UI on this platform")
44 }
45 if err := efivarfs.SetOSIndications(efivarfs.BootToFirmwareUI); err != nil {
46 return nil, status.Errorf(codes.Unavailable, "Unable to set UEFI boot to UI indication: %v", err)
47 }
48 default:
49 return nil, status.Error(codes.Unimplemented, "unimplemented next_boot value")
50 }
51
52 switch req.Type {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010053 case apb.RebootRequest_TYPE_KEXEC:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020054 if err := s.UpdateService.KexecLoadNext(); err != nil {
55 return nil, status.Errorf(codes.Unavailable, "failed to stage kexec kernel: %v", err)
56 }
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010057 case apb.RebootRequest_TYPE_FIRMWARE:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020058 // Best-effort, if it fails this will still be a firmware reboot.
59 os.WriteFile("/sys/kernel/reboot/mode", []byte("cold"), 0644)
60 }
Lorenz Bruna036c4e2024-09-10 19:11:57 +020061 s.initiateReboot(method)
62 return &apb.RebootResponse{}, nil
63}
64
65func (s *Service) initiateReboot(method int) {
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020066 s.LogTree.MustLeveledFor("root.mgmt").Warning("Reboot requested, rebooting in 2s")
Lorenz Bruna036c4e2024-09-10 19:11:57 +020067 // TODO(#253): Tell Supervisor to shut down gracefully and reboot
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020068 go func() {
69 time.Sleep(2 * time.Second)
70 unix.Unmount(s.UpdateService.ESPPath, 0)
71 unix.Sync()
Lorenz Bruna036c4e2024-09-10 19:11:57 +020072 s.disableNetworkInterfaces()
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020073 unix.Reboot(method)
74 }()
Lorenz Bruna036c4e2024-09-10 19:11:57 +020075}
76
77// For kexec it's recommended to disable all physical network interfaces
78// before doing it. This function doesn't return any errors as it's best-
79// effort anyways as we cannot reliably log the error anymore.
80func (s *Service) disableNetworkInterfaces() {
81 links, err := netlink.LinkList()
82 if err != nil {
83 return
84 }
85 for _, link := range links {
86 d, ok := link.(*netlink.Device)
87 if !ok {
88 continue
89 }
90 if err := netlink.LinkSetDown(d); err != nil {
91 s.LogTree.MustLeveledFor("root.mgmt").Errorf("Error taking link %q down: %v", link.Attrs().Name, err)
92 continue
93 }
94 }
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020095}