github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/boot/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 "context" 10 "errors" 11 "fmt" 12 "io" 13 "net/url" 14 "path" 15 "strings" 16 17 "github.com/u-root/u-root/pkg/boot" 18 "github.com/u-root/u-root/pkg/curl" 19 "github.com/u-root/u-root/pkg/uio" 20 "github.com/u-root/u-root/pkg/ulog" 21 ) 22 23 var ( 24 // ErrNotIpxeScript is returned when the config file is not an 25 // ipxe script. 26 ErrNotIpxeScript = errors.New("config file is not ipxe as it does not start with #!ipxe") 27 ) 28 29 // parser encapsulates a parsed ipxe configuration file. 30 // 31 // We currently only support kernel and initrd commands. 32 type parser struct { 33 bootImage *boot.LinuxImage 34 35 // wd is the current working directory. 36 // 37 // Relative file paths are interpreted relative to this URL. 38 wd *url.URL 39 40 log ulog.Logger 41 42 schemes curl.Schemes 43 } 44 45 // ParseConfig returns a new configuration with the file at URL and default 46 // schemes. 47 // 48 // `s` is used to get files referred to by URLs in the configuration. 49 func ParseConfig(ctx context.Context, l ulog.Logger, configURL *url.URL, s curl.Schemes) (*boot.LinuxImage, error) { 50 c := &parser{ 51 schemes: s, 52 log: l, 53 } 54 if err := c.getAndParseFile(ctx, configURL); err != nil { 55 return nil, err 56 } 57 return c.bootImage, nil 58 } 59 60 // getAndParse parses the config file downloaded from `url` and fills in `c`. 61 func (c *parser) getAndParseFile(ctx context.Context, u *url.URL) error { 62 r, err := c.schemes.Fetch(ctx, u) 63 if err != nil { 64 return err 65 } 66 data, err := uio.ReadAll(r) 67 if err != nil { 68 return err 69 } 70 config := string(data) 71 if !strings.HasPrefix(config, "#!ipxe") { 72 return ErrNotIpxeScript 73 } 74 c.log.Printf("Got ipxe config file %s:\n%s\n", r, config) 75 76 // Parent dir of the config file. 77 c.wd = &url.URL{ 78 Scheme: u.Scheme, 79 Host: u.Host, 80 Path: path.Dir(u.Path), 81 } 82 return c.parseIpxe(config) 83 } 84 85 // getFile parses `surl` and returns an io.Reader for the requested url. 86 func (c *parser) getFile(surl string) (io.ReaderAt, error) { 87 u, err := parseURL(surl, c.wd) 88 if err != nil { 89 return nil, fmt.Errorf("could not parse URL %q: %v", surl, err) 90 } 91 return c.schemes.LazyFetch(u) 92 } 93 94 func parseURL(name string, wd *url.URL) (*url.URL, error) { 95 u, err := url.Parse(name) 96 if err != nil { 97 return nil, fmt.Errorf("could not parse URL %q: %v", name, err) 98 } 99 100 // If it parsed, but it didn't have a Scheme or Host, use the working 101 // directory's values. 102 if len(u.Scheme) == 0 && wd != nil { 103 u.Scheme = wd.Scheme 104 105 if len(u.Host) == 0 { 106 // If this is not there, it was likely just a path. 107 u.Host = wd.Host 108 109 // Absolute file names don't get the parent 110 // directories, just the host and scheme. 111 if !path.IsAbs(name) { 112 u.Path = path.Join(wd.Path, path.Clean(u.Path)) 113 } 114 } 115 } 116 return u, nil 117 } 118 119 // parseIpxe parses `config` and constructs a BootImage for `c`. 120 func (c *parser) parseIpxe(config string) error { 121 // A trivial ipxe script parser. 122 // Currently only supports kernel and initrd commands. 123 c.bootImage = &boot.LinuxImage{} 124 125 for _, line := range strings.Split(config, "\n") { 126 // Skip blank lines and comment lines. 127 line = strings.TrimSpace(line) 128 if line == "" || line[0] == '#' { 129 continue 130 } 131 132 args := strings.Fields(line) 133 if len(args) == 0 { 134 continue 135 } 136 cmd := strings.ToLower(args[0]) 137 138 switch cmd { 139 case "kernel": 140 if len(args) > 1 { 141 k, err := c.getFile(args[1]) 142 if err != nil { 143 return err 144 } 145 c.bootImage.Kernel = k 146 } 147 148 // Add cmdline if there are any. 149 if len(args) > 2 { 150 c.bootImage.Cmdline = strings.Join(args[2:], " ") 151 } 152 153 case "initrd": 154 if len(args) > 1 { 155 var initrds []io.ReaderAt 156 for _, f := range strings.Split(args[1], ",") { 157 i, err := c.getFile(f) 158 if err != nil { 159 return err 160 } 161 initrds = append(initrds, i) 162 } 163 c.bootImage.Initrd = boot.CatInitrds(initrds...) 164 } 165 166 case "boot": 167 // Stop parsing at this point, we should go ahead and 168 // boot. 169 return nil 170 171 default: 172 c.log.Printf("Ignoring unsupported ipxe cmd: %s", line) 173 } 174 } 175 176 return nil 177 }