github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/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 }