github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/boot/localboot/main.go (about) 1 // Copyright 2017-2019 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "flag" 9 "fmt" 10 "log" 11 "os" 12 "path" 13 "path/filepath" 14 15 "github.com/u-root/u-root/pkg/boot/jsonboot" 16 "github.com/u-root/u-root/pkg/mount" 17 "github.com/u-root/u-root/pkg/mount/block" 18 ) 19 20 // TODO backward compatibility for BIOS mode with partition type 0xee 21 // TODO use a proper parser for grub config (see grub.go) 22 23 var ( 24 flagBaseMountPoint = flag.String("m", "/mnt", "Base mount point where to mount partitions") 25 flagDryRun = flag.Bool("dryrun", false, "Do not actually kexec into the boot config") 26 flagDebug = flag.Bool("d", false, "Print debug output") 27 flagConfigIdx = flag.Int("config", -1, "Specify the index of the configuration to boot. The order is determined by the menu entries in the Grub config") 28 flagGrubMode = flag.Bool("grub", false, "Use GRUB mode, i.e. look for valid Grub/Grub2 configuration in default locations to boot a kernel. GRUB mode ignores -kernel/-initramfs/-cmdline") 29 flagKernelPath = flag.String("kernel", "", "Specify the path of the kernel to execute. If using -grub, this argument is ignored") 30 flagInitramfsPath = flag.String("initramfs", "", "Specify the path of the initramfs to load. If using -grub, this argument is ignored") 31 flagKernelCmdline = flag.String("cmdline", "", "Specify the kernel command line. If using -grub, this argument is ignored") 32 flagDeviceGUID = flag.String("guid", "", "GUID of the device where the kernel (and optionally initramfs) are located. Ignored if -grub is set or if -kernel is not specified") 33 ) 34 35 var debug = func(string, ...interface{}) {} 36 37 // mountByGUID looks for a partition with the given GUID, and tries to mount it 38 // in a subdirectory under the specified mount point. The subdirectory has the 39 // same name of the device (e.g. /your/base/mountpoint/sda1). 40 // If more than one partition is found with the given GUID, the first that is 41 // found is used. 42 // This function returns a mount.Mountpoint object, or an error if any. 43 func mountByGUID(devices block.BlockDevices, guid, baseMountpoint string) (*mount.MountPoint, error) { 44 log.Printf("Looking for partition with GUID %s", guid) 45 partitions := devices.FilterPartType(guid) 46 if len(partitions) == 0 { 47 return nil, fmt.Errorf("no partitions with GUID %s", guid) 48 } 49 log.Printf("Partitions with GUID %s: %+v", guid, partitions) 50 if len(partitions) > 1 { 51 log.Printf("Warning: more than one partition found with the given GUID. Using the first one") 52 } 53 54 mountpath := filepath.Join(baseMountpoint, partitions[0].Name) 55 return partitions[0].Mount(mountpath, mount.MS_RDONLY) 56 } 57 58 // BootGrubMode tries to boot a kernel in GRUB mode. GRUB mode means: 59 // * look for the partition with the specified GUID, and mount it 60 // * if no GUID is specified, mount all of the specified devices 61 // * try to mount the device(s) using any of the kernel-supported filesystems 62 // * look for a GRUB configuration in various well-known locations 63 // * build a list of valid boot configurations from the found GRUB configuration files 64 // * try to boot every valid boot configuration until one succeeds 65 // 66 // The first parameter, `devices` is a list of block.BlockDev . The function 67 // will look for bootable configurations on these devices 68 // The second parameter, `baseMountPoint`, is the directory where the mount 69 // points for each device will be created. 70 // The third parameter, `guid`, is the partition GUID to look for. If it is an 71 // empty string, will search boot configurations on all of the specified devices 72 // instead. 73 // The fourth parameter, `dryrun`, will not boot the found configurations if set 74 // to true. 75 func BootGrubMode(devices block.BlockDevices, baseMountpoint string, guid string, dryrun bool, configIdx int) error { 76 var mounted []*mount.MountPoint 77 if guid == "" { 78 // try mounting all the available devices, with all the supported file 79 // systems 80 debug("trying to mount all the available block devices with all the supported file system types") 81 for _, dev := range devices { 82 mountpath := filepath.Join(baseMountpoint, dev.Name) 83 if mountpoint, err := dev.Mount(mountpath, mount.MS_RDONLY); err != nil { 84 debug("Failed to mount %s on %s: %v", dev, mountpath, err) 85 } else { 86 mounted = append(mounted, mountpoint) 87 } 88 } 89 } else { 90 mount, err := mountByGUID(devices, guid, baseMountpoint) 91 if err != nil { 92 return err 93 } 94 mounted = append(mounted, mount) 95 } 96 97 log.Printf("mounted: %+v", mounted) 98 defer func() { 99 // clean up 100 for _, mountpoint := range mounted { 101 if err := mountpoint.Unmount(mount.MNT_DETACH); err != nil { 102 debug("Failed to unmount %v: %v", mountpoint, err) 103 } 104 } 105 }() 106 107 // search for a valid grub config and extracts the boot configuration 108 bootconfigs := make([]jsonboot.BootConfig, 0) 109 for _, mountpoint := range mounted { 110 bootconfigs = append(bootconfigs, ScanGrubConfigs(devices, mountpoint.Path)...) 111 } 112 if len(bootconfigs) == 0 { 113 return fmt.Errorf("No boot configuration found") 114 } 115 log.Printf("Found %d boot configs", len(bootconfigs)) 116 for _, cfg := range bootconfigs { 117 debug("%+v", cfg) 118 } 119 for n, cfg := range bootconfigs { 120 log.Printf(" %d: %s\n", n, cfg.Name) 121 } 122 if configIdx > -1 { 123 for n, cfg := range bootconfigs { 124 if configIdx == n { 125 if dryrun { 126 debug("Dry-run mode: will not boot the found configuration") 127 debug("Boot configuration: %+v", cfg) 128 return nil 129 } 130 if err := cfg.Boot(); err != nil { 131 log.Printf("Failed to boot kernel %s: %v", cfg.Kernel, err) 132 } 133 } 134 } 135 log.Printf("Invalid arg -config %d: there are only %d bootconfigs available\n", configIdx, len(bootconfigs)) 136 return nil 137 } 138 if dryrun { 139 cfg := bootconfigs[0] 140 debug("Dry-run mode: will not boot the found configuration") 141 debug("Boot configuration: %+v", cfg) 142 return nil 143 } 144 145 // try to kexec into every boot config kernel until one succeeds 146 for _, cfg := range bootconfigs { 147 debug("Trying boot configuration %+v", cfg) 148 if err := cfg.Boot(); err != nil { 149 log.Printf("Failed to boot kernel %s: %v", cfg.Kernel, err) 150 } 151 } 152 // if we reach this point, no boot configuration succeeded 153 log.Print("No boot configuration succeeded") 154 155 return nil 156 } 157 158 // BootPathMode tries to boot a kernel in PATH mode. This means: 159 // * look for a partition with the given GUID and mount it 160 // * look for the kernel and initramfs in the provided locations 161 // * boot the kernel with the provided command line 162 // 163 // The first parameter, `devices` is a list of block.BlockDev . The function 164 // will look for bootable configurations on these devices 165 // The second parameter, `baseMountPoint`, is the directory where the mount 166 // points for each device will be created. 167 // The third parameter, `guid`, is the partition GUID to look for. 168 // The fourth parameter, `dryrun`, will not boot the found configurations if set 169 // to true. 170 func BootPathMode(devices block.BlockDevices, baseMountpoint string, guid string, dryrun bool) error { 171 mount, err := mountByGUID(devices, guid, baseMountpoint) 172 if err != nil { 173 return err 174 } 175 176 fullKernelPath := path.Join(mount.Path, *flagKernelPath) 177 fullInitramfsPath := path.Join(mount.Path, *flagInitramfsPath) 178 cfg := jsonboot.BootConfig{ 179 Kernel: fullKernelPath, 180 Initramfs: fullInitramfsPath, 181 KernelArgs: *flagKernelCmdline, 182 } 183 debug("Trying boot configuration %+v", cfg) 184 if dryrun { 185 log.Printf("Dry-run, will not actually boot") 186 } else { 187 if err := cfg.Boot(); err != nil { 188 return fmt.Errorf("Failed to boot kernel %s: %v", cfg.Kernel, err) 189 } 190 } 191 return nil 192 } 193 194 func main() { 195 flag.Parse() 196 if *flagGrubMode && *flagKernelPath != "" { 197 log.Fatal("Options -grub and -kernel are mutually exclusive") 198 } 199 if *flagDebug { 200 debug = log.Printf 201 } 202 203 // Get all the available block devices 204 devices, err := block.GetBlockDevices() 205 if err != nil { 206 log.Fatal(err) 207 } 208 // print partition info 209 if *flagDebug { 210 for _, dev := range devices { 211 log.Printf("Device: %+v", dev) 212 table, err := dev.GPTTable() 213 if err != nil { 214 continue 215 } 216 log.Printf(" Table: %+v", table) 217 for _, part := range table.Partitions { 218 log.Printf(" Partition: %+v\n", part) 219 if !part.IsEmpty() { 220 log.Printf(" UUID: %s\n", part.Type.String()) 221 } 222 } 223 } 224 } 225 226 // TODO boot from EFI system partitions. 227 228 if *flagGrubMode { 229 if err := BootGrubMode(devices, *flagBaseMountPoint, *flagDeviceGUID, *flagDryRun, *flagConfigIdx); err != nil { 230 log.Fatal(err) 231 } 232 } else if *flagKernelPath != "" { 233 if err := BootPathMode(devices, *flagBaseMountPoint, *flagDeviceGUID, *flagDryRun); err != nil { 234 log.Fatal(err) 235 } 236 } else { 237 log.Fatal("You must specify either -grub or -kernel") 238 } 239 os.Exit(1) 240 }