github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/pkg/boot/diskboot/config.go (about)

     1  // Copyright 2017-2018 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 diskboot
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"syscall"
    15  
    16  	"github.com/u-root/u-root/pkg/boot/kexec"
    17  	"github.com/u-root/u-root/pkg/cmdline"
    18  )
    19  
    20  // Config contains boot entries for a single configuration file
    21  // (grub, syslinux, etc.)
    22  type Config struct {
    23  	MountPath    string
    24  	ConfigPath   string
    25  	Entries      []Entry
    26  	DefaultEntry int
    27  }
    28  
    29  // EntryType dictates the method by which kexec should use to load
    30  // the new kernel
    31  type EntryType int
    32  
    33  // EntryType can be either Elf or Multiboot
    34  const (
    35  	Elf EntryType = iota
    36  	Multiboot
    37  )
    38  
    39  // Module represents a path to a binary along with arguments for its
    40  // xecution. The path in the module is relative to the mount path
    41  type Module struct {
    42  	Path   string
    43  	Params string
    44  }
    45  
    46  func (m Module) String() string {
    47  	return fmt.Sprintf("|'%v' (%v)|", m.Path, m.Params)
    48  }
    49  
    50  // NewModule constructs a module for a boot entry
    51  func NewModule(path string, args []string) Module {
    52  	return Module{
    53  		Path:   path,
    54  		Params: strings.Join(args, " "),
    55  	}
    56  }
    57  
    58  // Entry contains the necessary info to kexec into a new kernel
    59  type Entry struct {
    60  	Name    string
    61  	Type    EntryType
    62  	Modules []Module
    63  }
    64  
    65  // KexecLoad calls the appropriate kexec load routines based on the
    66  // type of Entry
    67  func (e *Entry) KexecLoad(mountPath string, filterCmdline cmdline.Filter, dryrun bool) error {
    68  	switch e.Type {
    69  	case Multiboot:
    70  		// TODO: implement using kexec_load syscall
    71  		return syscall.ENOSYS
    72  	case Elf:
    73  		// TODO: implement using kexec_file_load syscall
    74  		// e.Module[0].Path is kernel
    75  		// e.Module[0].Params is kernel parameters
    76  		// e.Module[1].Path is initrd
    77  		if len(e.Modules) < 1 {
    78  			return fmt.Errorf("missing kernel")
    79  		}
    80  		var ramfs *os.File
    81  		kernelPath := filepath.Join(mountPath, e.Modules[0].Path)
    82  		log.Print("Kernel Path:", kernelPath)
    83  		kernel, err := os.OpenFile(kernelPath, os.O_RDONLY, 0)
    84  		commandline := e.Modules[0].Params
    85  		if filterCmdline != nil {
    86  			commandline = filterCmdline.Update(commandline)
    87  		}
    88  
    89  		log.Print("Kernel Params:", commandline)
    90  		if err != nil {
    91  			return fmt.Errorf("failed to load kernel: %v", err)
    92  		}
    93  		if len(e.Modules) > 1 {
    94  			ramfsPath := filepath.Join(mountPath, e.Modules[1].Path)
    95  			log.Print("Ramfs Path:", ramfsPath)
    96  			ramfs, err = os.OpenFile(ramfsPath, os.O_RDONLY, 0)
    97  			if err != nil {
    98  				return fmt.Errorf("failed to load ramfs: %v", err)
    99  			}
   100  		}
   101  		if !dryrun {
   102  			return kexec.FileLoad(kernel, ramfs, commandline)
   103  		}
   104  	}
   105  	return nil
   106  }
   107  
   108  type location struct {
   109  	Path string
   110  	Type parserState
   111  }
   112  
   113  var (
   114  	locations = []location{
   115  		{"boot/grub/grub.cfg", grub},
   116  		{"grub/grub.cfg", grub},
   117  		{"grub2/grub.cfg", grub},
   118  		// following entries from the syslinux wiki
   119  		// TODO: add priorities override (top over bottom)
   120  		{"boot/isolinux/isolinux.cfg", syslinux},
   121  		{"isolinux/isolinux.cfg", syslinux},
   122  		{"isolinux.cfg", syslinux},
   123  		{"boot/syslinux/syslinux.cfg", syslinux},
   124  		{"syslinux/syslinux.cfg", syslinux},
   125  		{"syslinux.cfg", syslinux},
   126  	}
   127  )
   128  
   129  // TODO: add iso handling along with iso_path variable replacement
   130  
   131  // FindConfigs searching the path for valid boot configuration files
   132  // and returns a Config for each valid instance found.
   133  func FindConfigs(mountPath string) []*Config {
   134  	var configs []*Config
   135  
   136  	for _, location := range locations {
   137  		configPath := filepath.Join(mountPath, location.Path)
   138  		contents, err := ioutil.ReadFile(configPath)
   139  		if err != nil {
   140  			// TODO: log error
   141  			continue
   142  		}
   143  
   144  		var lines []string
   145  		if location.Type == syslinux {
   146  			lines = loadSyslinuxLines(configPath, contents)
   147  		} else {
   148  			lines = strings.Split(string(contents), "\n")
   149  		}
   150  
   151  		configs = append(configs, ParseConfig(mountPath, configPath, lines))
   152  	}
   153  
   154  	return configs
   155  }
   156  
   157  func loadSyslinuxLines(configPath string, contents []byte) []string {
   158  	// TODO: just parse includes inline with syslinux specific parser
   159  	var newLines, includeLines []string
   160  	menuKernel := false
   161  
   162  	lines := strings.Split(string(contents), "\n")
   163  	for _, line := range lines {
   164  		fields := strings.Fields(strings.TrimSpace(line))
   165  		includeDir := filepath.Dir(configPath)
   166  		if len(fields) == 2 && strings.ToUpper(fields[0]) == "INCLUDE" {
   167  			includeLines = loadSyslinuxInclude(includeDir, fields[1])
   168  		} else if len(fields) == 3 &&
   169  			strings.ToUpper(fields[0]) == "MENU" &&
   170  			strings.ToUpper(fields[1]) == "INCLUDE" {
   171  			includeLines = loadSyslinuxInclude(includeDir, fields[2])
   172  		} else if len(fields) > 1 &&
   173  			strings.ToUpper(fields[0]) == "APPEND" &&
   174  			menuKernel {
   175  			includeLines = []string{}
   176  			for _, includeFile := range fields[1:] {
   177  				includeLines = append(includeLines,
   178  					loadSyslinuxInclude(includeDir, includeFile)...)
   179  			}
   180  		} else {
   181  			if len(fields) > 0 && strings.ToUpper(fields[0]) == "LABEL" {
   182  				menuKernel = false
   183  			} else if len(fields) == 2 &&
   184  				strings.ToUpper(fields[0]) == "KERNEL" &&
   185  				(strings.ToUpper(fields[1]) == "VESAMENU.C32" ||
   186  					strings.ToUpper(fields[1]) == "MENU.C32") {
   187  				menuKernel = true
   188  			}
   189  			includeLines = []string{line}
   190  		}
   191  		newLines = append(newLines, includeLines...)
   192  	}
   193  	return newLines
   194  }
   195  
   196  func loadSyslinuxInclude(includePath, includeFile string) []string {
   197  	path := filepath.Join(includePath, includeFile)
   198  	includeContents, err := ioutil.ReadFile(path)
   199  	if err != nil {
   200  		// TODO: log error
   201  		return nil
   202  	}
   203  	return loadSyslinuxLines(path, includeContents)
   204  }