github.com/andrewsun2898/u-root@v6.0.1-0.20200616011413-4b2895c1b815+incompatible/pkg/boot/esxi/esxi.go (about) 1 // Copyright 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 esxi contains an ESXi boot config parser for disks and CDROMs. 6 // 7 // For CDROMs, it parses the boot.cfg found in the root directory and tries to 8 // boot from it. 9 // 10 // For disks, there may be multiple boot partitions: 11 // 12 // - Locates both <device>5/boot.cfg and <device>6/boot.cfg. 13 // 14 // - If parsable, chooses partition with bootstate=(0|2|empty) and greater 15 // updated=N. 16 // 17 // Sometimes, an ESXi partition can contain a valid boot.cfg, but not actually 18 // any of the named modules. Hence it is important to try fully loading ESXi 19 // into memory, and only then falling back to the other partition. 20 // 21 // Only boots partitions with bootstate=0, bootstate=2, bootstate=(empty) will 22 // boot at all. 23 // 24 // Most of the parsing logic in this package comes from 25 // https://github.com/vmware/esx-boot/blob/master/safeboot/bootbank.c 26 package esxi 27 28 import ( 29 "bufio" 30 "encoding/hex" 31 "fmt" 32 "io" 33 "io/ioutil" 34 "os" 35 "path/filepath" 36 "strconv" 37 "strings" 38 39 "golang.org/x/sys/unix" 40 41 "github.com/u-root/u-root/pkg/boot" 42 "github.com/u-root/u-root/pkg/boot/multiboot" 43 "github.com/u-root/u-root/pkg/gpt" 44 "github.com/u-root/u-root/pkg/mount" 45 "github.com/u-root/u-root/pkg/uio" 46 ) 47 48 // LoadDisk loads the right ESXi multiboot kernel from partitions 5 or 6 of the 49 // given device. 50 // 51 // The kernels are returned in the priority order according to the bootstate 52 // and updated values in their boot configurations. 53 // 54 // The caller should try loading all returned images in order, as some of them 55 // may not be valid. 56 // 57 // device5 and device6 will be mounted at temporary directories. 58 func LoadDisk(device string) ([]*boot.MultibootImage, error) { 59 opts5, err5 := mountPartition(fmt.Sprintf("%s5", device)) 60 opts6, err6 := mountPartition(fmt.Sprintf("%s6", device)) 61 if err5 != nil && err6 != nil { 62 return nil, fmt.Errorf("could not mount or read either partition 5 (%v) or partition 6 (%v)", err5, err6) 63 } 64 return getImages(device, opts5, opts6) 65 } 66 67 func getImages(device string, opts5, opts6 *options) ([]*boot.MultibootImage, error) { 68 var ( 69 img5, img6 *boot.MultibootImage 70 err5, err6 error 71 ) 72 if opts5 != nil { 73 img5, err5 = getBootImage(*opts5, device, 5) 74 } 75 if opts6 != nil { 76 img6, err6 = getBootImage(*opts6, device, 6) 77 } 78 if img5 == nil && img6 == nil { 79 return nil, fmt.Errorf("could not read boot configs on partition 5 (%v) or partition 6 (%v)", err5, err6) 80 } 81 82 if img5 != nil && img6 != nil { 83 if opts6.updated > opts5.updated { 84 return []*boot.MultibootImage{img6, img5}, nil 85 } 86 return []*boot.MultibootImage{img5, img6}, nil 87 } else if img5 != nil { 88 return []*boot.MultibootImage{img5}, nil 89 } 90 return []*boot.MultibootImage{img6}, nil 91 } 92 93 // LoadCDROM loads an ESXi multiboot kernel from a CDROM at device. 94 // 95 // device will be mounted at mountPoint. 96 func LoadCDROM(device string) (*boot.MultibootImage, error) { 97 mountPoint, err := ioutil.TempDir("", "esxi-mount-") 98 if err != nil { 99 return nil, err 100 } 101 if _, err := mount.Mount(device, mountPoint, "iso9660", "", unix.MS_RDONLY|unix.MS_NOATIME); err != nil { 102 return nil, err 103 } 104 // Don't pass the device to ESXi. It doesn't need it. 105 return LoadConfig(filepath.Join(mountPoint, "boot.cfg")) 106 } 107 108 // LoadConfig loads an ESXi configuration from configFile. 109 func LoadConfig(configFile string) (*boot.MultibootImage, error) { 110 opts, err := parse(configFile) 111 if err != nil { 112 return nil, fmt.Errorf("cannot parse config at %s: %v", configFile, err) 113 } 114 return getBootImage(opts, "", 0) 115 } 116 117 func mountPartition(dev string) (*options, error) { 118 base := filepath.Base(dev) 119 mountPoint, err := ioutil.TempDir("", fmt.Sprintf("%s-", base)) 120 if err != nil { 121 return nil, err 122 } 123 if _, err := mount.Mount(dev, mountPoint, "vfat", "", unix.MS_RDONLY|unix.MS_NOATIME); err != nil { 124 return nil, err 125 } 126 127 configFile := filepath.Join(mountPoint, "boot.cfg") 128 opts, err := parse(configFile) 129 if err != nil { 130 return nil, fmt.Errorf("cannot parse config at %s: %v", configFile, err) 131 } 132 return &opts, nil 133 } 134 135 // lazyOpenModules assigns modules to be opened as files. 136 // 137 // Each module is a path followed by optional command-line arguments, e.g. 138 // []string{"./module arg1 arg2", "./module2 arg3 arg4"}. 139 func lazyOpenModules(mods []module) multiboot.Modules { 140 modules := make([]multiboot.Module, 0, len(mods)) 141 for _, m := range mods { 142 name := strings.Fields(m.cmdline)[0] 143 modules = append(modules, multiboot.Module{ 144 CmdLine: m.cmdline, 145 Name: name, 146 Module: uio.NewLazyFile(m.path), 147 }) 148 } 149 return modules 150 } 151 152 func getBootImage(opts options, device string, partition int) (*boot.MultibootImage, error) { 153 // Only valid and upgrading are bootable partitions. 154 // 155 // We are supposed to support the following two state transitions (only 156 // one transition every boot!): 157 // 158 // upgrading -> dirty 159 // dirty -> invalid 160 // 161 // A validly booted system will set its own bootstate to "valid" from 162 // "dirty". 163 // 164 // We currently don't support writing the state back to disk, which is 165 // fine in our manual testing. 166 if opts.bootstate != bootValid && opts.bootstate != bootUpgrading { 167 return nil, fmt.Errorf("boot state %d invalid", opts.bootstate) 168 } 169 170 if len(device) > 0 { 171 if err := opts.addUUID(device, partition); err != nil { 172 return nil, fmt.Errorf("cannot add boot uuid of %s: %v", device, err) 173 } 174 } 175 176 return &boot.MultibootImage{ 177 Name: fmt.Sprintf("VMware ESXi from %s%d", device, partition), 178 Kernel: uio.NewLazyFile(opts.kernel), 179 Cmdline: opts.args, 180 Modules: lazyOpenModules(opts.modules), 181 }, nil 182 } 183 184 type module struct { 185 path string 186 cmdline string 187 } 188 189 type options struct { 190 kernel string 191 args string 192 modules []module 193 updated int 194 bootstate bootstate 195 } 196 197 type bootstate int 198 199 // From safeboot.c 200 const ( 201 bootValid bootstate = 0 202 bootUpgrading bootstate = 1 203 bootDirty bootstate = 2 204 bootInvalid bootstate = 3 205 ) 206 207 // So tests can replace this and don't have to have actual block devices. 208 var getBlockSize = gpt.GetBlockSize 209 210 func getUUID(device string, partition int) (string, error) { 211 device = strings.TrimRight(device, "/") 212 blockSize, err := getBlockSize(device) 213 if err != nil { 214 return "", err 215 } 216 217 f, err := os.Open(fmt.Sprintf("%s%d", device, partition)) 218 if err != nil { 219 return "", err 220 } 221 222 // Boot uuid is stored in the second block of the disk 223 // in the following format: 224 // 225 // VMWARE FAT16 <uuid> 226 // <---128 bit----><128 bit> 227 data := make([]byte, uuidSize) 228 n, err := f.ReadAt(data, int64(blockSize)) 229 if err != nil { 230 return "", err 231 } 232 if n != uuidSize { 233 return "", io.ErrUnexpectedEOF 234 } 235 236 if magic := string(data[:len(uuidMagic)]); magic != uuidMagic { 237 return "", fmt.Errorf("bad uuid magic %q, want %q", magic, uuidMagic) 238 } 239 240 uuid := hex.EncodeToString(data[len(uuidMagic):]) 241 return fmt.Sprintf("bootUUID=%s", uuid), nil 242 } 243 244 func (o *options) addUUID(device string, partition int) error { 245 uuid, err := getUUID(device, partition) 246 if err != nil { 247 return err 248 } 249 o.args += " " + uuid 250 return nil 251 } 252 253 const ( 254 comment = '#' 255 sep = "---" 256 257 uuidMagic = "VMWARE FAT16 " 258 uuidSize = 32 259 ) 260 261 func parse(configFile string) (options, error) { 262 dir := filepath.Dir(configFile) 263 264 f, err := os.Open(configFile) 265 if err != nil { 266 return options{}, err 267 } 268 defer f.Close() 269 270 // An empty or missing updated value is always 0, so we can let the 271 // ints be initialized to 0. 272 // 273 // see esx-boot/bootlib/parse.c:parse_config_file. 274 opt := options{ 275 // Default value taken from 276 // esx-boot/safeboot/bootbank.c:bank_scan. 277 bootstate: bootInvalid, 278 } 279 280 scanner := bufio.NewScanner(f) 281 for scanner.Scan() { 282 line := scanner.Text() 283 line = strings.TrimSpace(line) 284 285 if len(line) == 0 || line[0] == comment { 286 continue 287 } 288 289 tokens := strings.SplitN(line, "=", 2) 290 if len(tokens) != 2 { 291 return opt, fmt.Errorf("bad line %q", line) 292 } 293 key := strings.TrimSpace(tokens[0]) 294 val := strings.TrimSpace(tokens[1]) 295 switch key { 296 case "kernel": 297 opt.kernel = filepath.Join(dir, val) 298 299 // The kernel cmdline is expected to have the filename 300 // first, as in cmdlines[0] here: 301 // https://github.com/vmware/esx-boot/blob/1380fc86cffdfb83448e2913ae11f6b7f248cf23/mboot/mutiboot.c#L870 302 // 303 // Note that the kernel is module 0 in the esx-boot 304 // code base, but it doesn't get loaded like that into 305 // the info structure; see -- so don't panic like I did 306 // when you read that! 307 // https://github.com/vmware/esx-boot/blob/1380fc86cffdfb83448e2913ae11f6b7f248cf23/mboot/mutiboot.c#L578 308 opt.args = val + " " + opt.args 309 310 case "kernelopt": 311 opt.args += val 312 313 case "updated": 314 if len(val) == 0 { 315 // Explicitly setting to 0, as in 316 // esx-boot/bootlib/parse.c:parse_config_file, 317 // in case this value is specified twice. 318 opt.updated = 0 319 } else { 320 n, err := strconv.Atoi(val) 321 if err != nil { 322 return options{}, err 323 } 324 opt.updated = n 325 } 326 case "bootstate": 327 if len(val) == 0 { 328 // Explicitly setting to valid, as in 329 // esx-boot/bootlib/parse.c:parse_config_file, 330 // in case this value is specified twice. 331 opt.bootstate = bootValid 332 } else { 333 n, err := strconv.Atoi(val) 334 if err != nil { 335 return options{}, err 336 } 337 if n < 0 || n > 3 { 338 opt.bootstate = bootInvalid 339 } else { 340 opt.bootstate = bootstate(n) 341 } 342 } 343 case "modules": 344 for _, tok := range strings.Split(val, sep) { 345 // Each module is "filename arg0 arg1 arg2" and 346 // the filename is relative to the directory 347 // the module is in. 348 tok = strings.TrimSpace(tok) 349 if len(tok) > 0 { 350 entry := strings.Fields(tok) 351 opt.modules = append(opt.modules, module{ 352 path: filepath.Join(dir, entry[0]), 353 cmdline: tok, 354 }) 355 } 356 } 357 } 358 } 359 360 err = scanner.Err() 361 return opt, err 362 }