github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/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 "syscall" 14 15 "github.com/u-root/u-root/pkg/bootconfig" 16 "github.com/u-root/u-root/pkg/storage" 17 ) 18 19 // TODO backward compatibility for BIOS mode with partition type 0xee 20 // TODO use a proper parser for grub config (see grub.go) 21 22 var ( 23 flagBaseMountPoint = flag.String("m", "/mnt", "Base mount point where to mount partitions") 24 flagDryRun = flag.Bool("dryrun", false, "Do not actually kexec into the boot config") 25 flagDebug = flag.Bool("d", false, "Print debug output") 26 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") 27 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") 28 flagKernelPath = flag.String("kernel", "", "Specify the path of the kernel to execute. If using -grub, this argument is ignored") 29 flagInitramfsPath = flag.String("initramfs", "", "Specify the path of the initramfs to load. If using -grub, this argument is ignored") 30 flagKernelCmdline = flag.String("cmdline", "", "Specify the kernel command line. If using -grub, this argument is ignored") 31 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") 32 ) 33 34 var debug = func(string, ...interface{}) {} 35 36 // mountByGUID looks for a partition with the given GUID, and tries to mount it 37 // in a subdirectory under the specified mount point. The subdirectory has the 38 // same name of the device (e.g. /your/base/mountpoint/sda1). 39 // The specified filesystems will be used in the mount attempts. 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 storage.Mountpoint object, or an error if any. 43 func mountByGUID(devices []storage.BlockDev, filesystems []string, guid, baseMountpoint string) (*storage.Mountpoint, error) { 44 log.Printf("Looking for partition with GUID %s", guid) 45 partitions, err := storage.PartitionsByGUID(devices, guid) 46 if err != nil || len(partitions) == 0 { 47 return nil, fmt.Errorf("Error looking up for partition 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 dev := partitions[0] 54 mountpath := path.Join(baseMountpoint, dev.Name) 55 devname := path.Join("/dev", dev.Name) 56 mountpoint, err := storage.Mount(devname, mountpath, filesystems) 57 if err != nil { 58 return nil, fmt.Errorf("mountByGUID: cannot mount %s (GUID %s) on %s: %v", devname, guid, mountpath, err) 59 } 60 return mountpoint, nil 61 } 62 63 // BootGrubMode tries to boot a kernel in GRUB mode. GRUB mode means: 64 // * look for the partition with the specified GUID, and mount it 65 // * if no GUID is specified, mount all of the specified devices 66 // * try to mount the device(s) using any of the kernel-supported filesystems 67 // * look for a GRUB configuration in various well-known locations 68 // * build a list of valid boot configurations from the found GRUB configuration files 69 // * try to boot every valid boot configuration until one succeeds 70 // 71 // The first parameter, `devices` is a list of storage.BlockDev . The function 72 // will look for bootable configurations on these devices 73 // The second parameter, `baseMountPoint`, is the directory where the mount 74 // points for each device will be created. 75 // The third parameter, `guid`, is the partition GUID to look for. If it is an 76 // empty string, will search boot configurations on all of the specified devices 77 // instead. 78 // The fourth parameter, `dryrun`, will not boot the found configurations if set 79 // to true. 80 func BootGrubMode(devices []storage.BlockDev, baseMountpoint string, guid string, dryrun bool, configIdx int) error { 81 // get a list of supported file systems for real devices (i.e. skip nodev) 82 debug("Getting list of supported filesystems") 83 filesystems, err := storage.GetSupportedFilesystems() 84 if err != nil { 85 log.Fatal(err) 86 } 87 debug("Supported file systems: %v", filesystems) 88 89 var mounted []storage.Mountpoint 90 if guid == "" { 91 // try mounting all the available devices, with all the supported file 92 // systems 93 debug("trying to mount all the available block devices with all the supported file system types") 94 mounted = make([]storage.Mountpoint, 0) 95 for _, dev := range devices { 96 devname := path.Join("/dev", dev.Name) 97 mountpath := path.Join(baseMountpoint, dev.Name) 98 if mountpoint, err := storage.Mount(devname, mountpath, filesystems); err != nil { 99 debug("Failed to mount %s on %s: %v", devname, mountpath, err) 100 } else { 101 mounted = append(mounted, *mountpoint) 102 } 103 } 104 log.Printf("mounted: %+v", mounted) 105 defer func() { 106 // clean up 107 for _, mountpoint := range mounted { 108 syscall.Unmount(mountpoint.Path, syscall.MNT_DETACH) 109 } 110 }() 111 } else { 112 mount, err := mountByGUID(devices, filesystems, guid, baseMountpoint) 113 if err != nil { 114 return err 115 } 116 mounted = []storage.Mountpoint{*mount} 117 } 118 119 // search for a valid grub config and extracts the boot configuration 120 bootconfigs := make([]bootconfig.BootConfig, 0) 121 for _, mountpoint := range mounted { 122 bootconfigs = append(bootconfigs, ScanGrubConfigs(devices, mountpoint.Path)...) 123 } 124 if len(bootconfigs) == 0 { 125 return fmt.Errorf("No boot configuration found") 126 } 127 log.Printf("Found %d boot configs", len(bootconfigs)) 128 for _, cfg := range bootconfigs { 129 debug("%+v", cfg) 130 } 131 for n, cfg := range bootconfigs { 132 log.Printf(" %d: %s\n", n, cfg.Name) 133 } 134 if configIdx > -1 { 135 for n, cfg := range bootconfigs { 136 if configIdx == n { 137 if dryrun { 138 debug("Dry-run mode: will not boot the found configuration") 139 debug("Boot configuration: %+v", cfg) 140 return nil 141 } 142 if err := cfg.Boot(); err != nil { 143 log.Printf("Failed to boot kernel %s: %v", cfg.Kernel, err) 144 } 145 } 146 } 147 log.Printf("Invalid arg -config %d: there are only %d bootconfigs available\n", configIdx, len(bootconfigs)) 148 return nil 149 } 150 if dryrun { 151 cfg := bootconfigs[0] 152 debug("Dry-run mode: will not boot the found configuration") 153 debug("Boot configuration: %+v", cfg) 154 return nil 155 } 156 157 // try to kexec into every boot config kernel until one succeeds 158 for _, cfg := range bootconfigs { 159 debug("Trying boot configuration %+v", cfg) 160 if err := cfg.Boot(); err != nil { 161 log.Printf("Failed to boot kernel %s: %v", cfg.Kernel, err) 162 } 163 } 164 // if we reach this point, no boot configuration succeeded 165 log.Print("No boot configuration succeeded") 166 167 return nil 168 } 169 170 // BootPathMode tries to boot a kernel in PATH mode. This means: 171 // * look for a partition with the given GUID and mount it 172 // * look for the kernel and initramfs in the provided locations 173 // * boot the kernel with the provided command line 174 // 175 // The first parameter, `devices` is a list of storage.BlockDev . The function 176 // will look for bootable configurations on these devices 177 // The second parameter, `baseMountPoint`, is the directory where the mount 178 // points for each device will be created. 179 // The third parameter, `guid`, is the partition GUID to look for. 180 // The fourth parameter, `dryrun`, will not boot the found configurations if set 181 // to true. 182 func BootPathMode(devices []storage.BlockDev, baseMountpoint string, guid string, dryrun bool) error { 183 debug("Getting list of supported filesystems") 184 filesystems, err := storage.GetSupportedFilesystems() 185 if err != nil { 186 log.Fatal(err) 187 } 188 debug("Supported file systems: %v", filesystems) 189 190 mount, err := mountByGUID(devices, filesystems, guid, baseMountpoint) 191 if err != nil { 192 return err 193 } 194 195 fullKernelPath := path.Join(mount.Path, *flagKernelPath) 196 fullInitramfsPath := path.Join(mount.Path, *flagInitramfsPath) 197 cfg := bootconfig.BootConfig{ 198 Kernel: fullKernelPath, 199 Initramfs: fullInitramfsPath, 200 KernelArgs: *flagKernelCmdline, 201 } 202 debug("Trying boot configuration %+v", cfg) 203 if dryrun { 204 log.Printf("Dry-run, will not actually boot") 205 } else { 206 if err := cfg.Boot(); err != nil { 207 return fmt.Errorf("Failed to boot kernel %s: %v", cfg.Kernel, err) 208 } 209 } 210 return nil 211 } 212 213 func main() { 214 flag.Parse() 215 if *flagGrubMode && *flagKernelPath != "" { 216 log.Fatal("Options -grub and -kernel are mutually exclusive") 217 } 218 if *flagDebug { 219 debug = log.Printf 220 } 221 222 // Get all the available block devices 223 devices, err := storage.GetBlockStats() 224 if err != nil { 225 log.Fatal(err) 226 } 227 // print partition info 228 if *flagDebug { 229 for _, dev := range devices { 230 log.Printf("Device: %+v", dev) 231 table, err := storage.GetGPTTable(dev) 232 if err != nil { 233 continue 234 } 235 log.Printf(" Table: %+v", table) 236 for _, part := range table.Partitions { 237 log.Printf(" Partition: %+v\n", part) 238 if !part.IsEmpty() { 239 log.Printf(" UUID: %s\n", part.Type.String()) 240 } 241 } 242 } 243 } 244 245 // TODO boot from EFI system partitions. See storage.FilterEFISystemPartitions 246 247 if *flagGrubMode { 248 if err := BootGrubMode(devices, *flagBaseMountPoint, *flagDeviceGUID, *flagDryRun, *flagConfigIdx); err != nil { 249 log.Fatal(err) 250 } 251 } else if *flagKernelPath != "" { 252 if err := BootPathMode(devices, *flagBaseMountPoint, *flagDeviceGUID, *flagDryRun); err != nil { 253 log.Fatal(err) 254 } 255 } else { 256 log.Fatal("You must specify either -grub or -kernel") 257 } 258 os.Exit(1) 259 }