github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/pkg/netboot/ipxe/ipxe.go (about)

     1  // Copyright 2017-2019 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 ipxe implements a trivial IPXE config file parser.
     6  package ipxe
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"log"
    13  	"net/url"
    14  	"strings"
    15  
    16  	"github.com/u-root/u-root/pkg/boot"
    17  	"github.com/u-root/u-root/pkg/uio"
    18  	"github.com/u-root/u-root/pkg/urlfetch"
    19  )
    20  
    21  var (
    22  	// ErrNotIpxeScript is returned when the config file is not an
    23  	// ipxe script.
    24  	ErrNotIpxeScript = errors.New("config file is not ipxe as it does not start with #!ipxe")
    25  )
    26  
    27  // parser encapsulates a parsed ipxe configuration file.
    28  //
    29  // We currently only support kernel and initrd commands.
    30  type parser struct {
    31  	bootImage *boot.LinuxImage
    32  
    33  	schemes urlfetch.Schemes
    34  }
    35  
    36  // ParseConfig returns a new  configuration with the file at URL and default
    37  // schemes.
    38  //
    39  // See ParseConfigWithSchemes for more details.
    40  func ParseConfig(configURL *url.URL) (*boot.LinuxImage, error) {
    41  	return ParseConfigWithSchemes(configURL, urlfetch.DefaultSchemes)
    42  }
    43  
    44  // ParseConfigWithSchemes returns a new  configuration with the file at URL
    45  // and schemes `s`.
    46  //
    47  // `s` is used to get files referred to by URLs in the configuration.
    48  func ParseConfigWithSchemes(configURL *url.URL, s urlfetch.Schemes) (*boot.LinuxImage, error) {
    49  	c := &parser{
    50  		schemes: s,
    51  	}
    52  	if err := c.getAndParseFile(configURL); err != nil {
    53  		return nil, err
    54  	}
    55  	return c.bootImage, nil
    56  }
    57  
    58  // getAndParse parses the config file downloaded from `url` and fills in `c`.
    59  func (c *parser) getAndParseFile(u *url.URL) error {
    60  	r, err := c.schemes.LazyFetch(u)
    61  	if err != nil {
    62  		return err
    63  	}
    64  	data, err := uio.ReadAll(r)
    65  	if err != nil {
    66  		return err
    67  	}
    68  	config := string(data)
    69  	if !strings.HasPrefix(config, "#!ipxe") {
    70  		return ErrNotIpxeScript
    71  	}
    72  	log.Printf("Got ipxe config file %s:\n%s\n", r, config)
    73  	return c.parseIpxe(config)
    74  }
    75  
    76  // getFile parses `surl` and returns an io.Reader for the requested url.
    77  func (c *parser) getFile(surl string) (io.ReaderAt, error) {
    78  	u, err := url.Parse(surl)
    79  	if err != nil {
    80  		return nil, fmt.Errorf("could not parse URL %q: %v", surl, err)
    81  	}
    82  	return c.schemes.LazyFetch(u)
    83  }
    84  
    85  // parseIpxe parses `config` and constructs a BootImage for `c`.
    86  func (c *parser) parseIpxe(config string) error {
    87  	// A trivial ipxe script parser.
    88  	// Currently only supports kernel and initrd commands.
    89  	c.bootImage = &boot.LinuxImage{}
    90  
    91  	for _, line := range strings.Split(config, "\n") {
    92  		// Skip blank lines and comment lines.
    93  		line = strings.TrimSpace(line)
    94  		if line == "" || line[0] == '#' {
    95  			continue
    96  		}
    97  
    98  		args := strings.Fields(line)
    99  		if len(args) <= 1 {
   100  			log.Printf("Ignoring unsupported ipxe cmd: %s", line)
   101  			continue
   102  		}
   103  		cmd := strings.ToLower(args[0])
   104  
   105  		switch cmd {
   106  		case "kernel":
   107  			k, err := c.getFile(args[1])
   108  			if err != nil {
   109  				return err
   110  			}
   111  			c.bootImage.Kernel = k
   112  
   113  			// Add cmdline if there are any.
   114  			if len(args) > 2 {
   115  				c.bootImage.Cmdline = strings.Join(args[2:], " ")
   116  			}
   117  
   118  		case "initrd":
   119  			i, err := c.getFile(args[1])
   120  			if err != nil {
   121  				return err
   122  			}
   123  			c.bootImage.Initrd = i
   124  
   125  		default:
   126  			log.Printf("Ignoring unsupported ipxe cmd: %s", line)
   127  		}
   128  	}
   129  
   130  	return nil
   131  }