| Tim Windelschmidt | 7a1b27d | 2024-02-22 23:54:58 +0100 | [diff] [blame^] | 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "flag" |
| 5 | "io" |
| 6 | "log" |
| 7 | "os" |
| 8 | "os/exec" |
| 9 | "strings" |
| 10 | "time" |
| 11 | |
| 12 | "golang.org/x/sys/unix" |
| 13 | |
| 14 | "source.monogon.dev/osbase/bringup" |
| 15 | ) |
| 16 | |
| 17 | // Environment variable which tells the takeover binary to run the correct stage |
| 18 | const launchModeEnv = "TAKEOVER_LAUNCH_MODE" |
| 19 | |
| 20 | const ( |
| 21 | launchModeTakeover = "" |
| 22 | launchModeDetached = "DETACHED" |
| 23 | launchModeInit = "INIT" |
| 24 | ) |
| 25 | |
| 26 | func main() { |
| 27 | switch m := os.Getenv(launchModeEnv); m { |
| 28 | case launchModeTakeover: |
| 29 | launchTakeover() |
| 30 | case launchModeDetached: |
| 31 | launchDetached() |
| 32 | case launchModeInit: |
| 33 | launchInit() |
| 34 | default: |
| 35 | panic("unknown launch mode: " + m) |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | func launchTakeover() { |
| 40 | disk := flag.String("disk", "", "disk to install to without /dev/") |
| 41 | flag.Parse() |
| 42 | if disk == nil || *disk == "" { |
| 43 | log.Fatal("missing target disk") |
| 44 | } |
| 45 | |
| 46 | nodeParamsRaw, err := io.ReadAll(os.Stdin) |
| 47 | if err != nil { |
| 48 | log.Fatal(err) |
| 49 | } |
| 50 | |
| 51 | // try removing /dev/ just to be safe |
| 52 | diskName := strings.ReplaceAll(*disk, "/dev/", "") |
| 53 | warns, err := setupTakeover(nodeParamsRaw, diskName) |
| 54 | if err != nil { |
| 55 | log.Fatal(err) |
| 56 | } |
| 57 | |
| 58 | if len(warns) != 0 { |
| 59 | for _, s := range warns { |
| 60 | os.Stdout.WriteString(s) |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // Close stdout, we're done responding |
| 65 | os.Stdout.Close() |
| 66 | |
| 67 | // Start second stage which waits for 5 seconds while performing |
| 68 | // final cleanup. |
| 69 | detachedCmd := exec.Command("/proc/self/exe") |
| 70 | detachedCmd.Env = []string{launchModeEnv + "=" + launchModeDetached} |
| 71 | if err := detachedCmd.Start(); err != nil { |
| 72 | log.Fatalf("failed to launch final stage: %v", err) |
| 73 | } |
| 74 | // Release the second stage so that the first stage can cleanly terminate. |
| 75 | if err := detachedCmd.Process.Release(); err != nil { |
| 76 | log.Fatalf("error releasing final stage process: %v", err) |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // launchDetached executes the second stage |
| 81 | func launchDetached() { |
| 82 | // Wait 5 seconds for data to be sent, connections to be closed and |
| 83 | // syncs to be executed |
| 84 | time.Sleep(5 * time.Second) |
| 85 | // Perform kexec, this will not return unless it fails |
| 86 | err := unix.Reboot(unix.LINUX_REBOOT_CMD_KEXEC) |
| 87 | msg := "takeover: reboot succeeded, but we're still runing??" |
| 88 | if err != nil { |
| 89 | msg = err.Error() |
| 90 | } |
| 91 | // We have no standard output/error anymore, if this fails it's |
| 92 | // just borked. Attempt to dump the error into kmesg for manual |
| 93 | // debugging. |
| 94 | kmsg, err := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0) |
| 95 | if err != nil { |
| 96 | os.Exit(2) |
| 97 | } |
| 98 | kmsg.WriteString(msg) |
| 99 | kmsg.Close() |
| 100 | os.Exit(1) |
| 101 | } |
| 102 | |
| 103 | func launchInit() { |
| 104 | bringup.Runnable(takeoverRunnable).Run() |
| 105 | } |