github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/pkg/syslinux/syslinux_test.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 syslinux
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"net/url"
    11  	"reflect"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/u-root/u-root/pkg/boot"
    16  	"github.com/u-root/u-root/pkg/uio"
    17  	"github.com/u-root/u-root/pkg/urlfetch"
    18  )
    19  
    20  func mustReadAll(r io.ReaderAt) string {
    21  	if r == nil {
    22  		return ""
    23  	}
    24  	b, err := uio.ReadAll(r)
    25  	if err != nil {
    26  		return fmt.Sprintf("read error: %s", err)
    27  	}
    28  	return string(b)
    29  }
    30  
    31  type errorReader struct {
    32  	err error
    33  }
    34  
    35  func (e errorReader) ReadAt(p []byte, n int64) (int, error) {
    36  	return 0, e.err
    37  }
    38  
    39  func TestAppendFile(t *testing.T) {
    40  	content1 := "1111"
    41  	content2 := "2222"
    42  	content3 := "3333"
    43  	content4 := "4444"
    44  
    45  	for i, tt := range []struct {
    46  		desc          string
    47  		configFileURI string
    48  		schemeFunc    func() urlfetch.Schemes
    49  		wd            *url.URL
    50  		want          *Config
    51  		err           error
    52  	}{
    53  		{
    54  			desc:          "all files exist, simple config with cmdline initrd",
    55  			configFileURI: "pxelinux.cfg/default",
    56  			schemeFunc: func() urlfetch.Schemes {
    57  				s := make(urlfetch.Schemes)
    58  				fs := urlfetch.NewMockScheme("tftp")
    59  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
    60  				conf := `default foo
    61  				label foo
    62  				kernel ./pxefiles/kernel
    63  				append initrd=./pxefiles/initrd`
    64  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
    65  				fs.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1)
    66  				fs.Add("1.2.3.4", "/foobar/pxefiles/initrd", content2)
    67  				s.Register(fs.Scheme, fs)
    68  				return s
    69  			},
    70  			wd: &url.URL{
    71  				Scheme: "tftp",
    72  				Host:   "1.2.3.4",
    73  				Path:   "/foobar",
    74  			},
    75  			want: &Config{
    76  				DefaultEntry: "foo",
    77  				Entries: map[string]*boot.LinuxImage{
    78  					"foo": {
    79  						Kernel:  strings.NewReader(content1),
    80  						Initrd:  strings.NewReader(content2),
    81  						Cmdline: "initrd=./pxefiles/initrd",
    82  					},
    83  				},
    84  			},
    85  		},
    86  		{
    87  			desc:          "all files exist, simple config with directive initrd",
    88  			configFileURI: "pxelinux.cfg/default",
    89  			schemeFunc: func() urlfetch.Schemes {
    90  				s := make(urlfetch.Schemes)
    91  				fs := urlfetch.NewMockScheme("tftp")
    92  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
    93  				conf := `default foo
    94  				label foo
    95  				kernel ./pxefiles/kernel
    96  				initrd ./pxefiles/initrd
    97  				append foo=bar`
    98  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
    99  				fs.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1)
   100  				fs.Add("1.2.3.4", "/foobar/pxefiles/initrd", content2)
   101  				s.Register(fs.Scheme, fs)
   102  				return s
   103  			},
   104  			wd: &url.URL{
   105  				Scheme: "tftp",
   106  				Host:   "1.2.3.4",
   107  				Path:   "/foobar",
   108  			},
   109  			want: &Config{
   110  				DefaultEntry: "foo",
   111  				Entries: map[string]*boot.LinuxImage{
   112  					"foo": {
   113  						Kernel:  strings.NewReader(content1),
   114  						Initrd:  strings.NewReader(content2),
   115  						Cmdline: "foo=bar",
   116  					},
   117  				},
   118  			},
   119  		},
   120  		{
   121  			desc:          "all files exist, simple config, no initrd",
   122  			configFileURI: "pxelinux.cfg/default",
   123  			schemeFunc: func() urlfetch.Schemes {
   124  				s := make(urlfetch.Schemes)
   125  				fs := urlfetch.NewMockScheme("tftp")
   126  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   127  				conf := `default foo
   128  				label foo
   129  				kernel ./pxefiles/kernel`
   130  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   131  				fs.Add("1.2.3.4", "/foobar/pxefiles/kernel", content1)
   132  				s.Register(fs.Scheme, fs)
   133  				return s
   134  			},
   135  			wd: &url.URL{
   136  				Scheme: "tftp",
   137  				Host:   "1.2.3.4",
   138  				Path:   "/foobar",
   139  			},
   140  			want: &Config{
   141  				DefaultEntry: "foo",
   142  				Entries: map[string]*boot.LinuxImage{
   143  					"foo": {
   144  						Kernel:  strings.NewReader(content1),
   145  						Initrd:  nil,
   146  						Cmdline: "",
   147  					},
   148  				},
   149  			},
   150  		},
   151  		{
   152  			desc:          "kernel does not exist, simple config",
   153  			configFileURI: "pxelinux.cfg/default",
   154  			schemeFunc: func() urlfetch.Schemes {
   155  				s := make(urlfetch.Schemes)
   156  				fs := urlfetch.NewMockScheme("tftp")
   157  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   158  				conf := `default foo
   159  				label foo
   160  				kernel ./pxefiles/kernel`
   161  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   162  				s.Register(fs.Scheme, fs)
   163  				return s
   164  			},
   165  			wd: &url.URL{
   166  				Scheme: "tftp",
   167  				Host:   "1.2.3.4",
   168  				Path:   "/foobar",
   169  			},
   170  			want: &Config{
   171  				DefaultEntry: "foo",
   172  				Entries: map[string]*boot.LinuxImage{
   173  					"foo": {
   174  						Kernel: errorReader{&urlfetch.URLError{
   175  							URL: &url.URL{
   176  								Scheme: "tftp",
   177  								Host:   "1.2.3.4",
   178  								Path:   "/foobar/pxefiles/kernel",
   179  							},
   180  							Err: urlfetch.ErrNoSuchFile,
   181  						}},
   182  						Initrd:  nil,
   183  						Cmdline: "",
   184  					},
   185  				},
   186  			},
   187  		},
   188  		{
   189  			desc:          "config file does not exist",
   190  			configFileURI: "pxelinux.cfg/default",
   191  			schemeFunc: func() urlfetch.Schemes {
   192  				s := make(urlfetch.Schemes)
   193  				fs := urlfetch.NewMockScheme("tftp")
   194  				s.Register(fs.Scheme, fs)
   195  				return s
   196  			},
   197  			wd: &url.URL{
   198  				Scheme: "tftp",
   199  				Host:   "1.2.3.4",
   200  				Path:   "/foobar",
   201  			},
   202  			err: &urlfetch.URLError{
   203  				URL: &url.URL{
   204  					Scheme: "tftp",
   205  					Host:   "1.2.3.4",
   206  					Path:   "/foobar/pxelinux.cfg/default",
   207  				},
   208  				Err: urlfetch.ErrNoSuchHost,
   209  			},
   210  		},
   211  		{
   212  			desc:          "empty config",
   213  			configFileURI: "pxelinux.cfg/default",
   214  			schemeFunc: func() urlfetch.Schemes {
   215  				s := make(urlfetch.Schemes)
   216  				fs := urlfetch.NewMockScheme("tftp")
   217  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", "")
   218  				s.Register(fs.Scheme, fs)
   219  				return s
   220  			},
   221  			wd: &url.URL{
   222  				Scheme: "tftp",
   223  				Host:   "1.2.3.4",
   224  				Path:   "/foobar",
   225  			},
   226  			want: &Config{
   227  				DefaultEntry: "",
   228  			},
   229  		},
   230  		{
   231  			desc:          "valid config with two Entries",
   232  			configFileURI: "pxelinux.cfg/default",
   233  			schemeFunc: func() urlfetch.Schemes {
   234  				s := make(urlfetch.Schemes)
   235  				fs := urlfetch.NewMockScheme("tftp")
   236  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   237  				conf := `default foo
   238  
   239  				label foo
   240  				kernel ./pxefiles/fookernel
   241  				append earlyprintk=ttyS0 printk=ttyS0
   242  
   243  				label bar
   244  				kernel ./pxefiles/barkernel
   245  				append console=ttyS0`
   246  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   247  				fs.Add("1.2.3.4", "/foobar/pxefiles/fookernel", content1)
   248  				fs.Add("1.2.3.4", "/foobar/pxefiles/barkernel", content2)
   249  				s.Register(fs.Scheme, fs)
   250  				return s
   251  			},
   252  			wd: &url.URL{
   253  				Scheme: "tftp",
   254  				Host:   "1.2.3.4",
   255  				Path:   "/foobar",
   256  			},
   257  			want: &Config{
   258  				DefaultEntry: "foo",
   259  				Entries: map[string]*boot.LinuxImage{
   260  					"foo": {
   261  						Kernel:  strings.NewReader(content1),
   262  						Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   263  					},
   264  					"bar": {
   265  						Kernel:  strings.NewReader(content2),
   266  						Cmdline: "console=ttyS0",
   267  					},
   268  				},
   269  			},
   270  		},
   271  		{
   272  			desc:          "valid config with global APPEND directive",
   273  			configFileURI: "pxelinux.cfg/default",
   274  			schemeFunc: func() urlfetch.Schemes {
   275  				s := make(urlfetch.Schemes)
   276  				fs := urlfetch.NewMockScheme("tftp")
   277  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   278  				conf := `default foo
   279  				append foo=bar
   280  
   281  				label foo
   282  				kernel ./pxefiles/fookernel
   283  				append earlyprintk=ttyS0 printk=ttyS0
   284  
   285  				label bar
   286  				kernel ./pxefiles/barkernel
   287  
   288  				label baz
   289  				kernel ./pxefiles/barkernel
   290  				append -`
   291  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   292  				fs.Add("1.2.3.4", "/foobar/pxefiles/fookernel", content1)
   293  				fs.Add("1.2.3.4", "/foobar/pxefiles/barkernel", content2)
   294  				s.Register(fs.Scheme, fs)
   295  				return s
   296  			},
   297  			wd: &url.URL{
   298  				Scheme: "tftp",
   299  				Host:   "1.2.3.4",
   300  				Path:   "/foobar",
   301  			},
   302  			want: &Config{
   303  				DefaultEntry: "foo",
   304  				Entries: map[string]*boot.LinuxImage{
   305  					"foo": {
   306  						Kernel: strings.NewReader(content1),
   307  						// Does not contain global APPEND.
   308  						Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   309  					},
   310  					"bar": {
   311  						Kernel: strings.NewReader(content2),
   312  						// Contains only global APPEND.
   313  						Cmdline: "foo=bar",
   314  					},
   315  					"baz": {
   316  						Kernel: strings.NewReader(content2),
   317  						// "APPEND -" means ignore global APPEND.
   318  						Cmdline: "",
   319  					},
   320  				},
   321  			},
   322  		},
   323  		{
   324  			desc:          "valid config with global APPEND with initrd",
   325  			configFileURI: "pxelinux.cfg/default",
   326  			schemeFunc: func() urlfetch.Schemes {
   327  				s := make(urlfetch.Schemes)
   328  				fs := urlfetch.NewMockScheme("tftp")
   329  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   330  				conf := `default mcnulty
   331  				append initrd=./pxefiles/normal_person
   332  
   333  				label mcnulty
   334  				kernel ./pxefiles/copkernel
   335  				append earlyprintk=ttyS0 printk=ttyS0
   336  
   337  				label lester
   338  				kernel ./pxefiles/copkernel
   339  
   340  				label omar
   341  				kernel ./pxefiles/drugkernel
   342  				append -
   343  
   344  				label stringer
   345  				kernel ./pxefiles/drugkernel
   346  				initrd ./pxefiles/criminal
   347  				`
   348  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   349  				fs.Add("1.2.3.4", "/foobar/pxefiles/copkernel", content1)
   350  				fs.Add("1.2.3.4", "/foobar/pxefiles/drugkernel", content2)
   351  				fs.Add("1.2.3.4", "/foobar/pxefiles/normal_person", content3)
   352  				fs.Add("1.2.3.4", "/foobar/pxefiles/criminal", content4)
   353  				s.Register(fs.Scheme, fs)
   354  				return s
   355  			},
   356  			wd: &url.URL{
   357  				Scheme: "tftp",
   358  				Host:   "1.2.3.4",
   359  				Path:   "/foobar",
   360  			},
   361  			want: &Config{
   362  				DefaultEntry: "mcnulty",
   363  				Entries: map[string]*boot.LinuxImage{
   364  					"mcnulty": {
   365  						Kernel: strings.NewReader(content1),
   366  						// Does not contain global APPEND.
   367  						Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   368  					},
   369  					"lester": {
   370  						Kernel: strings.NewReader(content1),
   371  						Initrd: strings.NewReader(content3),
   372  						// Contains only global APPEND.
   373  						Cmdline: "initrd=./pxefiles/normal_person",
   374  					},
   375  					"omar": {
   376  						Kernel: strings.NewReader(content2),
   377  						// "APPEND -" means ignore global APPEND.
   378  						Cmdline: "",
   379  					},
   380  					"stringer": {
   381  						Kernel: strings.NewReader(content2),
   382  						// See TODO in pxe.go initrd handling.
   383  						Initrd:  strings.NewReader(content4),
   384  						Cmdline: "initrd=./pxefiles/normal_person",
   385  					},
   386  				},
   387  			},
   388  		},
   389  		{
   390  			desc:          "default label does not exist",
   391  			configFileURI: "pxelinux.cfg/default",
   392  			schemeFunc: func() urlfetch.Schemes {
   393  				s := make(urlfetch.Schemes)
   394  				fs := urlfetch.NewMockScheme("tftp")
   395  				conf := `default avon`
   396  
   397  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   398  				s.Register(fs.Scheme, fs)
   399  				return s
   400  			},
   401  			wd: &url.URL{
   402  				Scheme: "tftp",
   403  				Host:   "1.2.3.4",
   404  				Path:   "/foobar",
   405  			},
   406  			err: ErrDefaultEntryNotFound,
   407  			want: &Config{
   408  				DefaultEntry: "avon",
   409  			},
   410  		},
   411  		{
   412  			desc:          "multi-scheme valid config",
   413  			configFileURI: "pxelinux.cfg/default",
   414  			schemeFunc: func() urlfetch.Schemes {
   415  				conf := `default sheeeit
   416  
   417  				label sheeeit
   418  				kernel ./pxefiles/kernel
   419  				initrd http://someplace.com/someinitrd.gz`
   420  
   421  				tftp := urlfetch.NewMockScheme("tftp")
   422  				tftp.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   423  				tftp.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   424  				tftp.Add("1.2.3.4", "/foobar/pxefiles/kernel", content2)
   425  
   426  				http := urlfetch.NewMockScheme("http")
   427  				http.Add("someplace.com", "/someinitrd.gz", content3)
   428  
   429  				s := make(urlfetch.Schemes)
   430  				s.Register(tftp.Scheme, tftp)
   431  				s.Register(http.Scheme, http)
   432  				return s
   433  			},
   434  			wd: &url.URL{
   435  				Scheme: "tftp",
   436  				Host:   "1.2.3.4",
   437  				Path:   "/foobar",
   438  			},
   439  			want: &Config{
   440  				DefaultEntry: "sheeeit",
   441  				Entries: map[string]*boot.LinuxImage{
   442  					"sheeeit": {
   443  						Kernel: strings.NewReader(content2),
   444  						Initrd: strings.NewReader(content3),
   445  					},
   446  				},
   447  			},
   448  		},
   449  		{
   450  			desc:          "valid config with three includes",
   451  			configFileURI: "pxelinux.cfg/default",
   452  			schemeFunc: func() urlfetch.Schemes {
   453  				s := make(urlfetch.Schemes)
   454  				fs := urlfetch.NewMockScheme("tftp")
   455  				fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
   456  				conf := `default mcnulty
   457  
   458  				include installer/txt.cfg
   459  				include installer/stdmenu.cfg
   460  
   461  				menu begin advanced
   462  				  menu title Advanced Options
   463  				  include installer/stdmenu.cfg
   464  				menu end
   465  				`
   466  
   467  				txt := `
   468  				label mcnulty
   469  				kernel ./pxefiles/copkernel
   470  				append earlyprintk=ttyS0 printk=ttyS0
   471  				`
   472  
   473  				stdmenu := `
   474  				label omar
   475  				kernel ./pxefiles/drugkernel
   476  				`
   477  				fs.Add("1.2.3.4", "/foobar/pxelinux.cfg/default", conf)
   478  				fs.Add("1.2.3.4", "/foobar/installer/stdmenu.cfg", stdmenu)
   479  				fs.Add("1.2.3.4", "/foobar/installer/txt.cfg", txt)
   480  				fs.Add("1.2.3.4", "/foobar/pxefiles/copkernel", content1)
   481  				fs.Add("1.2.3.4", "/foobar/pxefiles/drugkernel", content2)
   482  				s.Register(fs.Scheme, fs)
   483  				return s
   484  			},
   485  			wd: &url.URL{
   486  				Scheme: "tftp",
   487  				Host:   "1.2.3.4",
   488  				Path:   "/foobar",
   489  			},
   490  			want: &Config{
   491  				DefaultEntry: "mcnulty",
   492  				Entries: map[string]*boot.LinuxImage{
   493  					"mcnulty": {
   494  						Kernel:  strings.NewReader(content1),
   495  						Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   496  					},
   497  					"omar": {
   498  						Kernel: strings.NewReader(content2),
   499  					},
   500  				},
   501  			},
   502  		},
   503  	} {
   504  		t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) {
   505  			s := tt.schemeFunc()
   506  			par := newParserWithSchemes(tt.wd, s)
   507  
   508  			if err := par.appendFile(tt.configFileURI); !reflect.DeepEqual(err, tt.err) {
   509  				t.Errorf("AppendFile() got %v, want %v", err, tt.err)
   510  			} else if err != nil {
   511  				return
   512  			}
   513  			c := par.config
   514  
   515  			if got, want := c.DefaultEntry, tt.want.DefaultEntry; got != want {
   516  				t.Errorf("DefaultEntry got %v, want %v", got, want)
   517  			}
   518  
   519  			for labelName, want := range tt.want.Entries {
   520  				t.Run(fmt.Sprintf("label %s", labelName), func(t *testing.T) {
   521  					got, ok := c.Entries[labelName]
   522  					if !ok {
   523  						t.Errorf("Config label %v does not exist", labelName)
   524  						return
   525  					}
   526  
   527  					// Same kernel?
   528  					if !uio.ReaderAtEqual(got.Kernel, want.Kernel) {
   529  						t.Errorf("got kernel %s, want %s", mustReadAll(got.Kernel), mustReadAll(want.Kernel))
   530  					}
   531  
   532  					// Same initrd?
   533  					if !uio.ReaderAtEqual(got.Initrd, want.Initrd) {
   534  						t.Errorf("got initrd %s, want %s", mustReadAll(got.Initrd), mustReadAll(want.Initrd))
   535  					}
   536  
   537  					// Same cmdline?
   538  					if got.Cmdline != want.Cmdline {
   539  						t.Errorf("got cmdline %s, want %s", got.Cmdline, want.Cmdline)
   540  					}
   541  				})
   542  			}
   543  
   544  			// Check that the parser didn't make up Entries.
   545  			for labelName := range c.Entries {
   546  				if _, ok := tt.want.Entries[labelName]; !ok {
   547  					t.Errorf("config has extra label %s, but not wanted", labelName)
   548  				}
   549  			}
   550  		})
   551  	}
   552  }
   553  
   554  func TestParseURL(t *testing.T) {
   555  	for i, tt := range []struct {
   556  		url  string
   557  		wd   *url.URL
   558  		err  bool
   559  		want *url.URL
   560  	}{
   561  		{
   562  			url: "default",
   563  			wd: &url.URL{
   564  				Scheme: "tftp",
   565  				Host:   "192.168.1.1",
   566  				Path:   "/foobar/pxelinux.cfg",
   567  			},
   568  			want: &url.URL{
   569  				Scheme: "tftp",
   570  				Host:   "192.168.1.1",
   571  				Path:   "/foobar/pxelinux.cfg/default",
   572  			},
   573  		},
   574  		{
   575  			url: "http://192.168.2.1/configs/your-machine.cfg",
   576  			wd: &url.URL{
   577  				Scheme: "tftp",
   578  				Host:   "192.168.1.1",
   579  				Path:   "/foobar/pxelinux.cfg",
   580  			},
   581  			want: &url.URL{
   582  				Scheme: "http",
   583  				Host:   "192.168.2.1",
   584  				Path:   "/configs/your-machine.cfg",
   585  			},
   586  		},
   587  	} {
   588  		t.Run(fmt.Sprintf("Test #%02d", i), func(t *testing.T) {
   589  			got, err := parseURL(tt.url, tt.wd)
   590  			if (err != nil) != tt.err {
   591  				t.Errorf("Wanted error (%v), but got %v", tt.err, err)
   592  			}
   593  			if !reflect.DeepEqual(got, tt.want) {
   594  				t.Errorf("parseURL() = %#v, want %#v", got, tt.want)
   595  			}
   596  		})
   597  	}
   598  }