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