blob: 6a3a8f20c241ec1b52a56e10df535aeb32ca925e [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Brun5a5c66b2024-08-22 16:11:44 +02004package mgmt
5
6import (
7 "context"
8 "os"
9 "time"
10
Lorenz Bruna036c4e2024-09-10 19:11:57 +020011 "github.com/vishvananda/netlink"
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020012 "golang.org/x/sys/unix"
13 "google.golang.org/grpc/codes"
14 "google.golang.org/grpc/status"
15
16 apb "source.monogon.dev/metropolis/proto/api"
17 "source.monogon.dev/osbase/efivarfs"
18)
19
20func (s *Service) Reboot(ctx context.Context, req *apb.RebootRequest) (*apb.RebootResponse, error) {
21 var method int
22 // Do not yet perform any system-wide actions here as the request might
23 // still get rejected. There is another switch statement for that below.
24 switch req.Type {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010025 case apb.RebootRequest_TYPE_KEXEC:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020026 method = unix.LINUX_REBOOT_CMD_KEXEC
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010027 case apb.RebootRequest_TYPE_FIRMWARE:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020028 method = unix.LINUX_REBOOT_CMD_RESTART
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010029 case apb.RebootRequest_TYPE_POWER_OFF:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020030 method = unix.LINUX_REBOOT_CMD_POWER_OFF
31 default:
32 return nil, status.Error(codes.Unimplemented, "unimplemented type value")
33 }
34 switch req.NextBoot {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010035 case apb.RebootRequest_NEXT_BOOT_START_NORMAL:
36 case apb.RebootRequest_NEXT_BOOT_START_ROLLBACK:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020037 if err := s.UpdateService.Rollback(); err != nil {
38 return nil, status.Errorf(codes.Unavailable, "performing rollback failed: %v", err)
39 }
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010040 case apb.RebootRequest_NEXT_BOOT_START_FIRMWARE_UI:
41 if req.Type == apb.RebootRequest_TYPE_KEXEC {
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020042 return nil, status.Error(codes.InvalidArgument, "START_FIRMWARE_UI cannot be used with KEXEC type")
43 }
44 supp, err := efivarfs.OSIndicationsSupported()
45 if err != nil || supp&efivarfs.BootToFirmwareUI == 0 {
46 return nil, status.Error(codes.Unimplemented, "Unable to boot into firmware UI on this platform")
47 }
48 if err := efivarfs.SetOSIndications(efivarfs.BootToFirmwareUI); err != nil {
49 return nil, status.Errorf(codes.Unavailable, "Unable to set UEFI boot to UI indication: %v", err)
50 }
51 default:
52 return nil, status.Error(codes.Unimplemented, "unimplemented next_boot value")
53 }
54
55 switch req.Type {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010056 case apb.RebootRequest_TYPE_KEXEC:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020057 if err := s.UpdateService.KexecLoadNext(); err != nil {
58 return nil, status.Errorf(codes.Unavailable, "failed to stage kexec kernel: %v", err)
59 }
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010060 case apb.RebootRequest_TYPE_FIRMWARE:
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020061 // Best-effort, if it fails this will still be a firmware reboot.
62 os.WriteFile("/sys/kernel/reboot/mode", []byte("cold"), 0644)
63 }
Lorenz Bruna036c4e2024-09-10 19:11:57 +020064 s.initiateReboot(method)
65 return &apb.RebootResponse{}, nil
66}
67
68func (s *Service) initiateReboot(method int) {
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020069 s.LogTree.MustLeveledFor("root.mgmt").Warning("Reboot requested, rebooting in 2s")
Lorenz Bruna036c4e2024-09-10 19:11:57 +020070 // TODO(#253): Tell Supervisor to shut down gracefully and reboot
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020071 go func() {
72 time.Sleep(2 * time.Second)
73 unix.Unmount(s.UpdateService.ESPPath, 0)
74 unix.Sync()
Lorenz Bruna036c4e2024-09-10 19:11:57 +020075 s.disableNetworkInterfaces()
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020076 unix.Reboot(method)
77 }()
Lorenz Bruna036c4e2024-09-10 19:11:57 +020078}
79
80// For kexec it's recommended to disable all physical network interfaces
81// before doing it. This function doesn't return any errors as it's best-
82// effort anyways as we cannot reliably log the error anymore.
83func (s *Service) disableNetworkInterfaces() {
84 links, err := netlink.LinkList()
85 if err != nil {
86 return
87 }
88 for _, link := range links {
89 d, ok := link.(*netlink.Device)
90 if !ok {
91 continue
92 }
93 if err := netlink.LinkSetDown(d); err != nil {
94 s.LogTree.MustLeveledFor("root.mgmt").Errorf("Error taking link %q down: %v", link.Attrs().Name, err)
95 continue
96 }
97 }
Lorenz Brun5a5c66b2024-08-22 16:11:44 +020098}