github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/boot/jsonboot/bootconfig.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 jsonboot
     6  
     7  import (
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"hash/crc32"
    12  	"log"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"github.com/u-root/u-root/pkg/boot/kexec"
    18  	"github.com/u-root/u-root/pkg/boot/multiboot"
    19  	"github.com/u-root/u-root/pkg/crypto"
    20  )
    21  
    22  // BootConfig is a general-purpose boot configuration. It draws some
    23  // characteristics from FIT but it's not compatible with it. It uses
    24  // JSON for interoperability.
    25  type BootConfig struct {
    26  	Name          string   `json:"name,omitempty"`
    27  	Kernel        string   `json:"kernel"`
    28  	Initramfs     string   `json:"initramfs,omitempty"`
    29  	KernelArgs    string   `json:"kernel_args,omitempty"`
    30  	DeviceTree    string   `json:"devicetree,omitempty"`
    31  	Multiboot     string   `json:"multiboot_kernel,omitempty"`
    32  	MultibootArgs string   `json:"multiboot_args,omitempty"`
    33  	Modules       []string `json:"multiboot_modules,omitempty"`
    34  }
    35  
    36  // IsValid returns true if a BootConfig object has valid content, and false
    37  // otherwise
    38  func (bc *BootConfig) IsValid() bool {
    39  	return (bc.Kernel != "" && bc.Multiboot == "") || (bc.Kernel == "" && bc.Multiboot != "")
    40  }
    41  
    42  // ID retrurns an identifyer composed of bc's name and crc32 hash of bc.
    43  // The ID is suitable to be used as part of a filepath.
    44  func (bc *BootConfig) ID() string {
    45  	id := strings.Title(strings.ToLower(bc.Name))
    46  	id = strings.ReplaceAll(id, " ", "")
    47  	id = strings.ReplaceAll(id, "/", "")
    48  	id = strings.ReplaceAll(id, "\\", "")
    49  
    50  	buf := []byte(filepath.Base(bc.Kernel))
    51  	buf = append(buf, []byte(bc.KernelArgs)...)
    52  	buf = append(buf, []byte(filepath.Base(bc.Initramfs))...)
    53  	buf = append(buf, []byte(filepath.Base(bc.DeviceTree))...)
    54  	buf = append(buf, []byte(filepath.Base(bc.Multiboot))...)
    55  	buf = append(buf, []byte(bc.MultibootArgs)...)
    56  	for _, mod := range bc.Modules {
    57  		buf = append(buf, []byte(filepath.Base(mod))...)
    58  	}
    59  	h := crc32.ChecksumIEEE(buf)
    60  	x := fmt.Sprintf("%x", h)
    61  
    62  	return "BC_" + id + x
    63  }
    64  
    65  // FileNames returns a slice of all filenames in the bootconfig.
    66  func (bc *BootConfig) FileNames() []string {
    67  	var files []string
    68  	if bc.Kernel != "" {
    69  		files = append(files, bc.Kernel)
    70  	}
    71  	if bc.Initramfs != "" {
    72  		files = append(files, bc.Initramfs)
    73  	}
    74  	if bc.DeviceTree != "" {
    75  		files = append(files, bc.DeviceTree)
    76  	}
    77  	if bc.Multiboot != "" {
    78  		files = append(files, bc.Multiboot)
    79  	}
    80  	for _, mod := range bc.Modules {
    81  		if mod != "" {
    82  			files = append(files, mod)
    83  		}
    84  	}
    85  	return files
    86  }
    87  
    88  // ChangeFilePaths modifies the filepaths inside BootConfig. It replaces
    89  // the current paths with new path leaving the last element of the path
    90  // unchanged.
    91  func (bc *BootConfig) ChangeFilePaths(newPath string) {
    92  	if bc.Kernel != "" {
    93  		bc.Kernel = filepath.Join(newPath, filepath.Base(bc.Kernel))
    94  	}
    95  	if bc.Initramfs != "" {
    96  		bc.Initramfs = filepath.Join(newPath, filepath.Base(bc.Initramfs))
    97  	}
    98  	if bc.DeviceTree != "" {
    99  		bc.DeviceTree = filepath.Join(newPath, filepath.Base(bc.DeviceTree))
   100  	}
   101  	if bc.Multiboot != "" {
   102  		bc.Multiboot = filepath.Join(newPath, filepath.Base(bc.Multiboot))
   103  	}
   104  	for j, mod := range bc.Modules {
   105  		if mod != "" {
   106  			bc.Modules[j] = filepath.Join(newPath, filepath.Base(mod))
   107  		}
   108  	}
   109  }
   110  
   111  // SetFilePathsPrefix modifies the filepaths inside BootConfig. It appends
   112  // prefix at the beginning of the current paths
   113  func (bc *BootConfig) SetFilePathsPrefix(prefix string) {
   114  	if bc.Kernel != "" {
   115  		bc.Kernel = filepath.Join(prefix, bc.Kernel)
   116  	}
   117  	if bc.Initramfs != "" {
   118  		bc.Initramfs = filepath.Join(prefix, bc.Initramfs)
   119  	}
   120  	if bc.DeviceTree != "" {
   121  		bc.DeviceTree = filepath.Join(prefix, bc.DeviceTree)
   122  	}
   123  	if bc.Multiboot != "" {
   124  		bc.Multiboot = filepath.Join(prefix, bc.Multiboot)
   125  	}
   126  	for j, mod := range bc.Modules {
   127  		if mod != "" {
   128  			bc.Modules[j] = filepath.Join(prefix, mod)
   129  		}
   130  	}
   131  }
   132  
   133  func (bc *BootConfig) bytestream() []byte {
   134  	b := bc.Name + bc.Kernel + bc.Initramfs + bc.KernelArgs + bc.DeviceTree + bc.Multiboot + bc.MultibootArgs
   135  	for _, module := range bc.Modules {
   136  		b = b + module
   137  	}
   138  	return []byte(b)
   139  }
   140  
   141  // Boot tries to boot the kernel with optional initramfs and command line
   142  // options. If a device-tree is specified, that will be used too
   143  func (bc *BootConfig) Boot() error {
   144  	crypto.TryMeasureData(crypto.BootConfigPCR, bc.bytestream(), "bootconfig")
   145  	crypto.TryMeasureFiles(bc.FileNames()...)
   146  	if bc.Kernel != "" {
   147  		kernel, err := os.Open(bc.Kernel)
   148  		if err != nil {
   149  			return fmt.Errorf("can't open kernel file for measurement: %v", err)
   150  		}
   151  		var initramfs *os.File
   152  		if bc.Initramfs != "" {
   153  			initramfs, err = os.Open(bc.Initramfs)
   154  			if err != nil {
   155  				return fmt.Errorf("can't open initramfs file for measurement: %v", err)
   156  			}
   157  		}
   158  		defer func() {
   159  			// clean up
   160  			if kernel != nil {
   161  				if err := kernel.Close(); err != nil {
   162  					log.Printf("Error closing kernel file descriptor: %v", err)
   163  				}
   164  			}
   165  			if initramfs != nil {
   166  				if err := initramfs.Close(); err != nil {
   167  					log.Printf("Error closing initramfs file descriptor: %v", err)
   168  				}
   169  			}
   170  		}()
   171  		if err := kexec.FileLoad(kernel, initramfs, bc.KernelArgs); err != nil {
   172  			return fmt.Errorf("kexec.FileLoad() failed: %v", err)
   173  		}
   174  	} else if bc.Multiboot != "" {
   175  		mbkernel, err := os.Open(bc.Multiboot)
   176  		if err != nil {
   177  			log.Printf("Error opening multiboot kernel file: %v", err)
   178  			return err
   179  		}
   180  		defer mbkernel.Close()
   181  
   182  		// check multiboot header
   183  		if err := multiboot.Probe(mbkernel); err != nil {
   184  			log.Printf("Error parsing multiboot header: %v", err)
   185  			return err
   186  		}
   187  		modules, err := multiboot.OpenModules(bc.Modules)
   188  		if err != nil {
   189  			return err
   190  		}
   191  		defer modules.Close()
   192  		if err := multiboot.Load(true, mbkernel, bc.MultibootArgs, modules, nil); err != nil {
   193  			return fmt.Errorf("kexec.Load() error: %v", err)
   194  		}
   195  	}
   196  	err := kexec.Reboot()
   197  	if err == nil {
   198  		return errors.New("unexpectedly returned from Reboot() without error: system did not reboot")
   199  	}
   200  	return err
   201  }
   202  
   203  // NewBootConfig parses a boot configuration in JSON format and returns a
   204  // BootConfig object.
   205  func NewBootConfig(data []byte) (*BootConfig, error) {
   206  	var bootconfig BootConfig
   207  	if err := json.Unmarshal(data, &bootconfig); err != nil {
   208  		return nil, err
   209  	}
   210  	return &bootconfig, nil
   211  }