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  }