github.com/jlowellwofford/u-root@v1.0.0/pkg/diskboot/parser.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 "log" 9 "path/filepath" 10 "strconv" 11 "strings" 12 ) 13 14 type parserState int 15 16 const ( 17 search parserState = iota // searching for a valid entry 18 grub // building a grub entry 19 syslinux // building a syslinux entry 20 ) 21 22 type parser struct { 23 state parserState 24 config *Config 25 entry *Entry 26 defaultName string 27 defaultIndex int 28 } 29 30 func (p *parser) parseSearch(line string) { 31 trimmedLine := strings.TrimSpace(line) 32 f := strings.Fields(trimmedLine) 33 if len(f) == 0 { 34 return 35 } 36 37 newEntry := false 38 var name string 39 40 switch strings.ToUpper(f[0]) { 41 case "MENUENTRY": // grub 42 p.state = grub 43 newEntry = true 44 repNames := strings.Replace(trimmedLine, "'", "\"", -1) 45 names := strings.Split(repNames, "\"") 46 if len(names) > 1 { 47 name = names[1] 48 } 49 case "SET": // grub 50 if len(f) > 1 { 51 p.parseSearchHandleSet(f[1]) 52 } 53 case "LABEL": // syslinux 54 p.state = syslinux 55 newEntry = true 56 name = trimmedLine[6:] 57 if name == "" { 58 name = "linux" // default for syslinux 59 } 60 case "DEFAULT": // syslinux 61 if len(f) > 1 { 62 label := strings.Join(f[1:], " ") 63 if !strings.HasSuffix(label, ".c32") { 64 p.defaultName = label 65 } 66 } 67 } 68 69 if newEntry { 70 p.entry = &Entry{ 71 Name: name, 72 Type: Elf, 73 } 74 } 75 } 76 77 func (p *parser) parseSearchHandleSet(val string) { 78 val = strings.Replace(val, "\"", "", -1) 79 expr := strings.Split(val, "=") 80 if len(expr) != 2 { 81 return 82 } 83 84 switch expr[0] { 85 case "default": 86 index, err := strconv.Atoi(expr[1]) 87 if err == nil { 88 p.defaultIndex = index 89 } 90 default: 91 // TODO: handle variables when grub conditionals are implemented 92 return 93 } 94 } 95 96 func (p *parser) parseGrubEntry(line string) { 97 trimmedLine := strings.TrimSpace(line) 98 f := strings.Fields(trimmedLine) 99 if len(f) == 0 { 100 return 101 } 102 103 switch f[0] { 104 case "}": 105 p.finishEntry() 106 case "multiboot": 107 p.entry.Type = Multiboot 108 p.entry.Modules = append(p.entry.Modules, NewModule(f[1], f[2:])) 109 case "module": 110 var filteredParams []string 111 for _, param := range f { 112 if param != "--nounzip" { 113 filteredParams = append(filteredParams, param) 114 } 115 } 116 p.entry.Modules = append(p.entry.Modules, 117 NewModule(filteredParams[1], filteredParams[2:])) 118 case "linux": 119 p.entry.Modules = append(p.entry.Modules, NewModule(f[1], f[2:])) 120 case "initrd": 121 p.entry.Modules = append(p.entry.Modules, NewModule(f[1], nil)) 122 } 123 } 124 125 func (p *parser) parseSyslinuxEntry(line string) { 126 trimmedLine := strings.TrimSpace(line) 127 if len(trimmedLine) == 0 { 128 p.finishEntry() 129 return 130 } 131 132 f := strings.Fields(trimmedLine) 133 val := strings.Join(f[1:], " ") 134 switch strings.ToUpper(f[0]) { 135 case "LABEL": // new entry, finish this one and start a new one 136 p.finishEntry() 137 p.parseSearch(line) 138 case "MENU": 139 if len(f) > 1 { 140 switch strings.ToUpper(f[1]) { 141 case "LABEL": 142 tempName := strings.Join(f[2:], " ") 143 p.entry.Name = strings.Replace(tempName, "^", "", -1) 144 case "DEFAULT": 145 p.defaultName = p.entry.Name 146 } 147 } 148 case "LINUX", "KERNEL": 149 p.parseSyslinuxKernel(val) 150 case "INITRD": 151 p.entry.Modules = append(p.entry.Modules, NewModule(f[1], nil)) 152 case "APPEND": 153 p.parseSyslinuxAppend(val) 154 } 155 } 156 157 func (p *parser) parseSyslinuxKernel(val string) { 158 if strings.HasSuffix(val, "mboot.c32") { 159 p.entry.Type = Multiboot 160 } else if strings.HasSuffix(val, ".c32") { 161 // skip this entry - not valid for kexec 162 p.state = search 163 } else { 164 p.entry.Modules = append(p.entry.Modules, NewModule(val, nil)) 165 } 166 } 167 168 func (p *parser) parseSyslinuxAppend(val string) { 169 if p.entry.Type == Multiboot { 170 // split params by "---" for each module 171 modules := strings.Split(val, " --- ") 172 for _, module := range modules { 173 moduleFields := strings.Fields(module) 174 p.entry.Modules = append(p.entry.Modules, 175 NewModule(moduleFields[0], moduleFields[1:])) 176 } 177 } else { 178 if len(p.entry.Modules) == 0 { 179 // TODO: log error 180 return 181 } 182 p.entry.Modules[0].Params = val 183 } 184 } 185 186 func (p *parser) finishEntry() { 187 // skip empty entries 188 if len(p.entry.Modules) == 0 { 189 return 190 } 191 192 // try to fix up initrd from kernel params 193 if len(p.entry.Modules) == 1 && p.entry.Type == Elf { 194 var initrd string 195 var newParams []string 196 197 params := strings.Fields(p.entry.Modules[0].Params) 198 for _, param := range params { 199 if strings.HasPrefix(param, "initrd=") { 200 initrd = param[7:] 201 } else { 202 newParams = append(newParams, param) 203 } 204 } 205 if initrd != "" { 206 p.entry.Modules = append(p.entry.Modules, NewModule(initrd, nil)) 207 p.entry.Modules[0].Params = strings.Join(newParams, " ") 208 } 209 } 210 211 appendPath, err := filepath.Rel(p.config.MountPath, filepath.Dir(p.config.ConfigPath)) 212 if err != nil { 213 log.Fatal("Config file path not relative to mount path") 214 } 215 for i, module := range p.entry.Modules { 216 if !strings.HasPrefix(module.Path, "/") { 217 module.Path = filepath.Join("/"+appendPath, module.Path) 218 } 219 module.Path = filepath.Clean(module.Path) 220 p.entry.Modules[i] = module 221 } 222 223 p.state = search 224 p.config.Entries = append(p.config.Entries, *p.entry) 225 } 226 227 func (p *parser) parseLines(lines []string) { 228 p.state = search 229 230 for _, line := range lines { 231 switch p.state { 232 case search: 233 p.parseSearch(line) 234 case grub: 235 p.parseGrubEntry(line) 236 case syslinux: 237 p.parseSyslinuxEntry(line) 238 } 239 } 240 241 if p.state == syslinux { 242 p.finishEntry() 243 } 244 } 245 246 // ParseConfig attempts to construct a valid boot Config from the location 247 // and lines contents passed in. 248 func ParseConfig(mountPath, configPath string, lines []string) *Config { 249 p := &parser{ 250 config: &Config{ 251 MountPath: mountPath, 252 ConfigPath: configPath, 253 DefaultEntry: -1, 254 }, 255 defaultIndex: -1, 256 } 257 p.parseLines(lines) 258 259 if p.defaultName != "" { 260 for i, entry := range p.config.Entries { 261 if entry.Name == p.defaultName { 262 p.config.DefaultEntry = i 263 } 264 } 265 } 266 if p.defaultIndex >= 0 && len(p.config.Entries) > p.defaultIndex { 267 p.config.DefaultEntry = p.defaultIndex 268 } 269 270 return p.config 271 }