github.com/andrewsun2898/u-root@v6.0.1-0.20200616011413-4b2895c1b815+incompatible/pkg/boot/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  	"context"
     9  	"fmt"
    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/boot/boottest"
    17  	"github.com/u-root/u-root/pkg/boot/multiboot"
    18  	"github.com/u-root/u-root/pkg/curl"
    19  )
    20  
    21  func mustParseURL(s string) *url.URL {
    22  	u, err := url.Parse(s)
    23  	if err != nil {
    24  		panic(fmt.Sprintf("parsing %q failed: %v", s, err))
    25  	}
    26  	return u
    27  }
    28  
    29  type errorReader struct {
    30  	err error
    31  }
    32  
    33  func (e errorReader) ReadAt(p []byte, n int64) (int, error) {
    34  	return 0, e.err
    35  }
    36  
    37  func TestParseGeneral(t *testing.T) {
    38  	kernel1 := "kernel1"
    39  	kernel2 := "kernel2"
    40  	globalInitrd := "globalInitrd"
    41  	initrd1 := "initrd1"
    42  	initrd2 := "initrd2"
    43  	xengz := "xengz"
    44  	mboot := "mboot.c32"
    45  
    46  	newMockScheme := func() *curl.MockScheme {
    47  		fs := curl.NewMockScheme("tftp")
    48  		fs.Add("1.2.3.4", "/foobar/pxelinux.0", "")
    49  		fs.Add("1.2.3.4", "/foobar/pxefiles/kernel1", kernel1)
    50  		fs.Add("1.2.3.4", "/foobar/pxefiles/kernel2", kernel2)
    51  		fs.Add("1.2.3.4", "/foobar/pxefiles/global_initrd", globalInitrd)
    52  		fs.Add("1.2.3.4", "/foobar/pxefiles/initrd1", initrd1)
    53  		fs.Add("1.2.3.4", "/foobar/pxefiles/initrd2", initrd2)
    54  		fs.Add("1.2.3.4", "/foobar/xen.gz", xengz)
    55  		fs.Add("1.2.3.4", "/foobar/mboot.c32", mboot)
    56  
    57  		fs.Add("2.3.4.5", "/barfoo/pxefiles/kernel1", kernel1)
    58  		return fs
    59  	}
    60  	http := curl.NewMockScheme("http")
    61  	http.Add("someplace.com", "/initrd2", initrd2)
    62  
    63  	for i, tt := range []struct {
    64  		desc        string
    65  		configFiles map[string]string
    66  		want        []boot.OSImage
    67  		err         error
    68  	}{
    69  		{
    70  			desc: "all files exist, simple config with cmdline initrd",
    71  			configFiles: map[string]string{
    72  				"/foobar/pxelinux.cfg/default": `
    73  					default foo
    74  					label foo
    75  					kernel ./pxefiles/kernel1
    76  					append initrd=./pxefiles/global_initrd`,
    77  			},
    78  			want: []boot.OSImage{
    79  				&boot.LinuxImage{
    80  					Name:    "foo",
    81  					Kernel:  strings.NewReader(kernel1),
    82  					Initrd:  strings.NewReader(globalInitrd),
    83  					Cmdline: "initrd=./pxefiles/global_initrd",
    84  				},
    85  			},
    86  		},
    87  		{
    88  			desc: "empty label",
    89  			configFiles: map[string]string{
    90  				"/foobar/pxelinux.cfg/default": `
    91  					default foo
    92  					label foo`,
    93  			},
    94  			want: nil,
    95  		},
    96  
    97  		{
    98  			desc: "all files exist, simple config with directive initrd",
    99  			configFiles: map[string]string{
   100  				"/foobar/pxelinux.cfg/default": `
   101  					default foo
   102  					label foo
   103  					kernel ./pxefiles/kernel1
   104  					initrd ./pxefiles/initrd1
   105  					append foo=bar`,
   106  			},
   107  			want: []boot.OSImage{
   108  				&boot.LinuxImage{
   109  					Name:    "foo",
   110  					Kernel:  strings.NewReader(kernel1),
   111  					Initrd:  strings.NewReader(initrd1),
   112  					Cmdline: "foo=bar",
   113  				},
   114  			},
   115  		},
   116  		{
   117  			desc: "all files exist, simple config, no initrd",
   118  			configFiles: map[string]string{
   119  				"/foobar/pxelinux.cfg/default": `
   120  					default foo
   121  					label foo
   122  					kernel ./pxefiles/kernel1`,
   123  			},
   124  			want: []boot.OSImage{
   125  				&boot.LinuxImage{
   126  					Name:    "foo",
   127  					Kernel:  strings.NewReader(kernel1),
   128  					Initrd:  nil,
   129  					Cmdline: "",
   130  				},
   131  			},
   132  		},
   133  		{
   134  			desc: "kernel does not exist, simple config",
   135  			configFiles: map[string]string{
   136  				"/foobar/pxelinux.cfg/default": `
   137  					default foo
   138  					label foo
   139  					kernel ./pxefiles/does-not-exist`,
   140  			},
   141  			want: []boot.OSImage{
   142  				&boot.LinuxImage{
   143  					Name: "foo",
   144  					Kernel: errorReader{&curl.URLError{
   145  						URL: &url.URL{
   146  							Scheme: "tftp",
   147  							Host:   "1.2.3.4",
   148  							Path:   "/foobar/pxefiles/does-not-exist",
   149  						},
   150  						Err: curl.ErrNoSuchFile,
   151  					}},
   152  					Initrd:  nil,
   153  					Cmdline: "",
   154  				},
   155  			},
   156  		},
   157  		{
   158  			desc: "config file does not exist",
   159  			err: &curl.URLError{
   160  				URL: &url.URL{
   161  					Scheme: "tftp",
   162  					Host:   "1.2.3.4",
   163  					Path:   "/foobar/pxelinux.cfg/default",
   164  				},
   165  				Err: curl.ErrNoSuchFile,
   166  			},
   167  		},
   168  		{
   169  			desc: "empty config",
   170  			configFiles: map[string]string{
   171  				"/foobar/pxelinux.cfg/default": "",
   172  			},
   173  			want: nil,
   174  		},
   175  		{
   176  			desc: "valid config with two Entries",
   177  			configFiles: map[string]string{
   178  				"/foobar/pxelinux.cfg/default": `
   179  					default foo
   180  
   181  					label bar
   182  					menu label Bla Bla Bla
   183  					kernel ./pxefiles/kernel2
   184  					append console=ttyS0
   185  
   186  					label foo
   187  					kernel ./pxefiles/kernel1
   188  					append earlyprintk=ttyS0 printk=ttyS0`,
   189  			},
   190  			want: []boot.OSImage{
   191  				&boot.LinuxImage{
   192  					Name:    "foo",
   193  					Kernel:  strings.NewReader(kernel1),
   194  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   195  				},
   196  				&boot.LinuxImage{
   197  					Name:    "Bla Bla Bla",
   198  					Kernel:  strings.NewReader(kernel2),
   199  					Cmdline: "console=ttyS0",
   200  				},
   201  			},
   202  		},
   203  		{
   204  			desc: "menu default, linux directives",
   205  			configFiles: map[string]string{
   206  				"/foobar/pxelinux.cfg/default": `
   207  					label bar
   208  					menu label Bla Bla Bla
   209  					kernel ./pxefiles/kernel2
   210  					append console=ttyS0
   211  
   212  					label foo
   213  					menu default
   214  					linux ./pxefiles/kernel1
   215  					append earlyprintk=ttyS0 printk=ttyS0`,
   216  			},
   217  			want: []boot.OSImage{
   218  				&boot.LinuxImage{
   219  					Name:    "foo",
   220  					Kernel:  strings.NewReader(kernel1),
   221  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   222  				},
   223  				&boot.LinuxImage{
   224  					Name:    "Bla Bla Bla",
   225  					Kernel:  strings.NewReader(kernel2),
   226  					Cmdline: "console=ttyS0",
   227  				},
   228  			},
   229  		},
   230  		{
   231  			desc: "valid config with two Entries, and a nerfdefault override",
   232  			configFiles: map[string]string{
   233  				"/foobar/pxelinux.cfg/default": `
   234  					default foo
   235  
   236  					nerfdefault bar
   237  
   238  					label foo
   239  					kernel ./pxefiles/kernel1
   240  					append earlyprintk=ttyS0 printk=ttyS0
   241  
   242  					label bar
   243  					kernel ./pxefiles/kernel2
   244  					append console=ttyS0`,
   245  			},
   246  			want: []boot.OSImage{
   247  				&boot.LinuxImage{
   248  					Name:    "bar",
   249  					Kernel:  strings.NewReader(kernel2),
   250  					Cmdline: "console=ttyS0",
   251  				},
   252  				&boot.LinuxImage{
   253  					Name:    "foo",
   254  					Kernel:  strings.NewReader(kernel1),
   255  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   256  				},
   257  			},
   258  		},
   259  		{
   260  			desc: "valid config with two Entries, and a nerfdefault override, order agnostic",
   261  			configFiles: map[string]string{
   262  				"/foobar/pxelinux.cfg/default": `
   263  					nerfdefault bar
   264  
   265  					default foo
   266  
   267  					label foo
   268  					kernel ./pxefiles/kernel1
   269  					append earlyprintk=ttyS0 printk=ttyS0
   270  
   271  					label bar
   272  					kernel ./pxefiles/kernel2
   273  					append console=ttyS0`,
   274  			},
   275  			want: []boot.OSImage{
   276  				&boot.LinuxImage{
   277  					Name:    "bar",
   278  					Kernel:  strings.NewReader(kernel2),
   279  					Cmdline: "console=ttyS0",
   280  				},
   281  				&boot.LinuxImage{
   282  					Name:    "foo",
   283  					Kernel:  strings.NewReader(kernel1),
   284  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   285  				},
   286  			},
   287  		},
   288  
   289  		{
   290  			desc: "valid config with global APPEND directive",
   291  			configFiles: map[string]string{
   292  				"/foobar/pxelinux.cfg/default": `
   293  					default foo
   294  					append foo=bar
   295  
   296  					label foo
   297  					kernel ./pxefiles/kernel1
   298  					append earlyprintk=ttyS0 printk=ttyS0
   299  
   300  					label bar
   301  					kernel ./pxefiles/kernel2
   302  
   303  					label baz
   304  					kernel ./pxefiles/kernel2
   305  					append -`,
   306  			},
   307  			want: []boot.OSImage{
   308  				&boot.LinuxImage{
   309  					Name:   "foo",
   310  					Kernel: strings.NewReader(kernel1),
   311  					// Does not contain global APPEND.
   312  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   313  				},
   314  				&boot.LinuxImage{
   315  					Name:   "bar",
   316  					Kernel: strings.NewReader(kernel2),
   317  					// Contains only global APPEND.
   318  					Cmdline: "foo=bar",
   319  				},
   320  				&boot.LinuxImage{
   321  					Name:   "baz",
   322  					Kernel: strings.NewReader(kernel2),
   323  					// "APPEND -" means ignore global APPEND.
   324  					Cmdline: "",
   325  				},
   326  			},
   327  		},
   328  		{
   329  			desc: "valid config with global APPEND with initrd",
   330  			configFiles: map[string]string{
   331  				"/foobar/pxelinux.cfg/default": `
   332  					default mcnulty
   333  					append initrd=./pxefiles/global_initrd
   334  
   335  					label mcnulty
   336  					kernel ./pxefiles/kernel1
   337  					append earlyprintk=ttyS0 printk=ttyS0
   338  
   339  					label lester
   340  					kernel ./pxefiles/kernel1
   341  
   342  					label omar
   343  					kernel ./pxefiles/kernel2
   344  					append -
   345  
   346  					label stringer
   347  					kernel ./pxefiles/kernel2
   348  					initrd ./pxefiles/initrd2
   349  				`,
   350  			},
   351  			want: []boot.OSImage{
   352  				&boot.LinuxImage{
   353  					Name:   "mcnulty",
   354  					Kernel: strings.NewReader(kernel1),
   355  					// Does not contain global APPEND.
   356  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   357  				},
   358  				&boot.LinuxImage{
   359  					Name:   "lester",
   360  					Kernel: strings.NewReader(kernel1),
   361  					Initrd: strings.NewReader(globalInitrd),
   362  					// Contains only global APPEND.
   363  					Cmdline: "initrd=./pxefiles/global_initrd",
   364  				},
   365  				&boot.LinuxImage{
   366  					Name:   "omar",
   367  					Kernel: strings.NewReader(kernel2),
   368  					// "APPEND -" means ignore global APPEND.
   369  					Cmdline: "",
   370  				},
   371  				&boot.LinuxImage{
   372  					Name:   "stringer",
   373  					Kernel: strings.NewReader(kernel2),
   374  					Initrd: strings.NewReader(initrd2),
   375  
   376  					// TODO: See syslinux initrd handling. This SHOULD be
   377  					//
   378  					// initrd=./pxefiles/global_initrd initrd=./pxefiles/initrd2
   379  					//
   380  					// https://wiki.syslinux.org/wiki/index.php?title=Directives/append
   381  					Cmdline: "initrd=./pxefiles/global_initrd",
   382  				},
   383  			},
   384  		},
   385  		{
   386  			desc: "default label does not exist",
   387  			configFiles: map[string]string{
   388  				"/foobar/pxelinux.cfg/default": `default not-exist`,
   389  			},
   390  			want: nil,
   391  		},
   392  		{
   393  			desc: "multi-scheme valid config",
   394  			configFiles: map[string]string{
   395  				"/foobar/pxelinux.cfg/default": `
   396  				default sheeeit
   397  
   398  				label sheeeit
   399  				kernel ./pxefiles/kernel2
   400  				initrd http://someplace.com/initrd2`,
   401  			},
   402  			want: []boot.OSImage{
   403  				&boot.LinuxImage{
   404  					Name:   "sheeeit",
   405  					Kernel: strings.NewReader(kernel2),
   406  					Initrd: strings.NewReader(initrd2),
   407  				},
   408  			},
   409  		},
   410  		{
   411  			desc: "valid config with three includes",
   412  			configFiles: map[string]string{
   413  				"/foobar/pxelinux.cfg/default": `
   414  					default mcnulty
   415  
   416  					include installer/txt.cfg
   417  					include installer/stdmenu.cfg
   418  
   419  					menu begin advanced
   420  					  menu title Advanced Options
   421  					  include installer/stdmenu.cfg
   422  					menu end
   423  				`,
   424  
   425  				"/foobar/installer/txt.cfg": `
   426  					label mcnulty
   427  					kernel ./pxefiles/kernel1
   428  					append earlyprintk=ttyS0 printk=ttyS0
   429  				`,
   430  
   431  				"/foobar/installer/stdmenu.cfg": `
   432  					label omar
   433  					kernel ./pxefiles/kernel2
   434  				`,
   435  			},
   436  			want: []boot.OSImage{
   437  				&boot.LinuxImage{
   438  					Name:    "mcnulty",
   439  					Kernel:  strings.NewReader(kernel1),
   440  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   441  				},
   442  				&boot.LinuxImage{
   443  					Name:   "omar",
   444  					Kernel: strings.NewReader(kernel2),
   445  				},
   446  			},
   447  		},
   448  		{
   449  			desc: "multiboot images",
   450  			configFiles: map[string]string{
   451  				"/foobar/pxelinux.cfg/default": `
   452  					default foo
   453  
   454  					label bar
   455  					menu label Bla Bla Bla
   456  					kernel mboot.c32
   457  					append xen.gz console=none --- ./pxefiles/kernel1 foobar hahaha --- ./pxefiles/initrd1
   458  
   459  					label mbootnomodules
   460  					kernel mboot.c32
   461  					append xen.gz
   462  
   463  					label foo
   464  					linux mboot.c32
   465  					append earlyprintk=ttyS0 printk=ttyS0`,
   466  			},
   467  			want: []boot.OSImage{
   468  				&boot.LinuxImage{
   469  					Name:    "foo",
   470  					Kernel:  strings.NewReader(mboot),
   471  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   472  				},
   473  				&boot.MultibootImage{
   474  					Name:    "Bla Bla Bla",
   475  					Kernel:  strings.NewReader(xengz),
   476  					Cmdline: "console=none",
   477  					Modules: []multiboot.Module{
   478  						{
   479  							Module:  strings.NewReader(kernel1),
   480  							Name:    "./pxefiles/kernel1",
   481  							CmdLine: "./pxefiles/kernel1 foobar hahaha",
   482  						},
   483  						{
   484  							Module:  strings.NewReader(initrd1),
   485  							Name:    "./pxefiles/initrd1",
   486  							CmdLine: "./pxefiles/initrd1",
   487  						},
   488  					},
   489  				},
   490  				&boot.MultibootImage{
   491  					Name:   "mbootnomodules",
   492  					Kernel: strings.NewReader(xengz),
   493  				},
   494  			},
   495  		},
   496  	} {
   497  		t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) {
   498  			fs := newMockScheme()
   499  			for filename, content := range tt.configFiles {
   500  				fs.Add("1.2.3.4", filename, content)
   501  			}
   502  			s := make(curl.Schemes)
   503  			s.Register(fs.Scheme, fs)
   504  			s.Register(http.Scheme, http)
   505  
   506  			rootdir := &url.URL{
   507  				Scheme: "tftp",
   508  				Host:   "1.2.3.4",
   509  				Path:   "/",
   510  			}
   511  
   512  			got, err := ParseConfigFile(context.Background(), s, "pxelinux.cfg/default", rootdir, "foobar")
   513  			if !reflect.DeepEqual(err, tt.err) {
   514  				t.Errorf("AppendFile() got %v, want %v", err, tt.err)
   515  			} else if err != nil {
   516  				return
   517  			}
   518  
   519  			if len(tt.want) != len(got) {
   520  				t.Errorf("ParseConfigFile yielded %d images, want %d images", len(got), len(tt.want))
   521  			}
   522  
   523  			for i, want := range tt.want {
   524  				if err := boottest.SameBootImage(got[i], want); err != nil {
   525  					t.Errorf("Boot image index %d not same: %v", i, err)
   526  				}
   527  			}
   528  		})
   529  	}
   530  }
   531  
   532  func TestParseCorner(t *testing.T) {
   533  	for _, tt := range []struct {
   534  		name       string
   535  		s          curl.Schemes
   536  		configFile string
   537  		rootdir    *url.URL
   538  		wd         string
   539  		err        error
   540  	}{
   541  		{
   542  			name:       "no schemes",
   543  			s:          nil,
   544  			configFile: "pxelinux.cfg/default",
   545  			rootdir: &url.URL{
   546  				Scheme: "tftp",
   547  				Host:   "1.2.3.4",
   548  				Path:   "/foobar",
   549  			},
   550  			err: &curl.URLError{
   551  				URL: &url.URL{
   552  					Scheme: "tftp",
   553  					Host:   "1.2.3.4",
   554  					Path:   "/foobar/pxelinux.cfg/default",
   555  				},
   556  				Err: curl.ErrNoSuchScheme,
   557  			},
   558  		},
   559  		{
   560  			name:       "no scheme and config file",
   561  			s:          nil,
   562  			configFile: "",
   563  			rootdir: &url.URL{
   564  				Scheme: "tftp",
   565  				Host:   "1.2.3.4",
   566  				Path:   "/foobar",
   567  			},
   568  			err: &curl.URLError{
   569  				URL: &url.URL{
   570  					Scheme: "tftp",
   571  					Host:   "1.2.3.4",
   572  					Path:   "/foobar",
   573  				},
   574  				Err: curl.ErrNoSuchScheme,
   575  			},
   576  		},
   577  		{
   578  			name:       "no scheme, config file, and working dir",
   579  			s:          nil,
   580  			configFile: "",
   581  			rootdir:    nil,
   582  			err: &curl.URLError{
   583  				URL: &url.URL{},
   584  				Err: curl.ErrNoSuchScheme,
   585  			},
   586  		},
   587  	} {
   588  		t.Run(tt.name, func(t *testing.T) {
   589  			_, err := ParseConfigFile(context.Background(), tt.s, tt.configFile, tt.rootdir, tt.wd)
   590  			if !reflect.DeepEqual(err, tt.err) {
   591  				t.Errorf("ParseConfigFile() = %v, want %v", err, tt.err)
   592  			}
   593  		})
   594  	}
   595  }
   596  
   597  func TestParseURL(t *testing.T) {
   598  	for _, tt := range []struct {
   599  		filename string
   600  		rootdir  *url.URL
   601  		wd       string
   602  		want     *url.URL
   603  	}{
   604  		{
   605  			filename: "foobar",
   606  			rootdir:  mustParseURL("http://[2001::1]:18282/"),
   607  			wd:       "files/more",
   608  			want:     mustParseURL("http://[2001::1]:18282/files/more/foobar"),
   609  		},
   610  		{
   611  			filename: "/foobar",
   612  			rootdir:  mustParseURL("http://[2001::1]:18282"),
   613  			wd:       "files/more",
   614  			want:     mustParseURL("http://[2001::1]:18282/foobar"),
   615  		},
   616  		{
   617  			filename: "http://[2002::2]/blabla",
   618  			rootdir:  mustParseURL("http://[2001::1]:18282/files"),
   619  			wd:       "more",
   620  			want:     mustParseURL("http://[2002::2]/blabla"),
   621  		},
   622  		{
   623  			filename: "http://[2002::2]/blabla",
   624  			rootdir:  nil,
   625  			want:     mustParseURL("http://[2002::2]/blabla"),
   626  		},
   627  	} {
   628  		got, err := parseURL(tt.filename, tt.rootdir, tt.wd)
   629  		if err != nil {
   630  			t.Errorf("parseURL(%q, %s, %s) = %v, want %v", tt.filename, tt.rootdir, tt.wd, err, nil)
   631  		}
   632  
   633  		if !reflect.DeepEqual(got, tt.want) {
   634  			t.Errorf("parseURL(%q, %s, %s) = %v, want %v", tt.filename, tt.rootdir, tt.wd, got, tt.want)
   635  		}
   636  	}
   637  }