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 }