github.hscsec.cn/u-root/u-root@v7.0.0+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: "all files exist, simple config with two initrd files",
   135  			configFiles: map[string]string{
   136  				"/foobar/pxelinux.cfg/default": `
   137  					default foo
   138  					label multi-initrd
   139  					kernel ./pxefiles/kernel1
   140  					initrd ./pxefiles/initrd1,./pxefiles/initrd2
   141  					append foo=bar`,
   142  			},
   143  			want: []boot.OSImage{
   144  				&boot.LinuxImage{
   145  					Name:    "multi-initrd",
   146  					Kernel:  strings.NewReader(kernel1),
   147  					Initrd:  boot.CatInitrds(strings.NewReader(initrd1), strings.NewReader(initrd2)),
   148  					Cmdline: "foo=bar",
   149  				},
   150  			},
   151  		},
   152  		{
   153  			desc: "an initrd file missing, config with two initrd files",
   154  			configFiles: map[string]string{
   155  				"/foobar/pxelinux.cfg/default": `
   156  					default foo
   157  					label multi-initrd
   158  					kernel ./pxefiles/kernel1
   159  					initrd ./pxefiles/initrd1,./pxefiles/no-initrd-here
   160  					append foo=bar`,
   161  			},
   162  			want: []boot.OSImage{
   163  				&boot.LinuxImage{
   164  					Name:   "multi-initrd",
   165  					Kernel: strings.NewReader(kernel1),
   166  					Initrd: errorReader{&curl.URLError{
   167  						URL: &url.URL{
   168  							Scheme: "tftp",
   169  							Host:   "1.2.3.4",
   170  							Path:   "/foobar/pxefiles/no-initrd-here",
   171  						},
   172  						Err: curl.ErrNoSuchFile,
   173  					}},
   174  					Cmdline: "foo=bar",
   175  				},
   176  			},
   177  		},
   178  		{
   179  			desc: "kernel does not exist, simple config",
   180  			configFiles: map[string]string{
   181  				"/foobar/pxelinux.cfg/default": `
   182  					default foo
   183  					label foo
   184  					kernel ./pxefiles/does-not-exist`,
   185  			},
   186  			want: []boot.OSImage{
   187  				&boot.LinuxImage{
   188  					Name: "foo",
   189  					Kernel: errorReader{&curl.URLError{
   190  						URL: &url.URL{
   191  							Scheme: "tftp",
   192  							Host:   "1.2.3.4",
   193  							Path:   "/foobar/pxefiles/does-not-exist",
   194  						},
   195  						Err: curl.ErrNoSuchFile,
   196  					}},
   197  					Initrd:  nil,
   198  					Cmdline: "",
   199  				},
   200  			},
   201  		},
   202  		{
   203  			desc: "config file does not exist",
   204  			err: &curl.URLError{
   205  				URL: &url.URL{
   206  					Scheme: "tftp",
   207  					Host:   "1.2.3.4",
   208  					Path:   "/foobar/pxelinux.cfg/default",
   209  				},
   210  				Err: curl.ErrNoSuchFile,
   211  			},
   212  		},
   213  		{
   214  			desc: "empty config",
   215  			configFiles: map[string]string{
   216  				"/foobar/pxelinux.cfg/default": "",
   217  			},
   218  			want: nil,
   219  		},
   220  		{
   221  			desc: "valid config with two Entries",
   222  			configFiles: map[string]string{
   223  				"/foobar/pxelinux.cfg/default": `
   224  					default foo
   225  
   226  					label bar
   227  					menu label Bla Bla Bla
   228  					kernel ./pxefiles/kernel2
   229  					append console=ttyS0
   230  
   231  					label foo
   232  					kernel ./pxefiles/kernel1
   233  					append earlyprintk=ttyS0 printk=ttyS0`,
   234  			},
   235  			want: []boot.OSImage{
   236  				&boot.LinuxImage{
   237  					Name:    "foo",
   238  					Kernel:  strings.NewReader(kernel1),
   239  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   240  				},
   241  				&boot.LinuxImage{
   242  					Name:    "Bla Bla Bla",
   243  					Kernel:  strings.NewReader(kernel2),
   244  					Cmdline: "console=ttyS0",
   245  				},
   246  			},
   247  		},
   248  		{
   249  			desc: "menu default, linux directives",
   250  			configFiles: map[string]string{
   251  				"/foobar/pxelinux.cfg/default": `
   252  					label bar
   253  					menu label Bla Bla Bla
   254  					kernel ./pxefiles/kernel2
   255  					append console=ttyS0
   256  
   257  					label foo
   258  					menu default
   259  					linux ./pxefiles/kernel1
   260  					append earlyprintk=ttyS0 printk=ttyS0`,
   261  			},
   262  			want: []boot.OSImage{
   263  				&boot.LinuxImage{
   264  					Name:    "foo",
   265  					Kernel:  strings.NewReader(kernel1),
   266  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   267  				},
   268  				&boot.LinuxImage{
   269  					Name:    "Bla Bla Bla",
   270  					Kernel:  strings.NewReader(kernel2),
   271  					Cmdline: "console=ttyS0",
   272  				},
   273  			},
   274  		},
   275  		{
   276  			desc: "valid config with two Entries, and a nerfdefault override",
   277  			configFiles: map[string]string{
   278  				"/foobar/pxelinux.cfg/default": `
   279  					default foo
   280  
   281  					nerfdefault bar
   282  
   283  					label foo
   284  					kernel ./pxefiles/kernel1
   285  					append earlyprintk=ttyS0 printk=ttyS0
   286  
   287  					label bar
   288  					kernel ./pxefiles/kernel2
   289  					append console=ttyS0`,
   290  			},
   291  			want: []boot.OSImage{
   292  				&boot.LinuxImage{
   293  					Name:    "bar",
   294  					Kernel:  strings.NewReader(kernel2),
   295  					Cmdline: "console=ttyS0",
   296  				},
   297  				&boot.LinuxImage{
   298  					Name:    "foo",
   299  					Kernel:  strings.NewReader(kernel1),
   300  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   301  				},
   302  			},
   303  		},
   304  		{
   305  			desc: "valid config with two Entries, and a nerfdefault override, order agnostic",
   306  			configFiles: map[string]string{
   307  				"/foobar/pxelinux.cfg/default": `
   308  					nerfdefault bar
   309  
   310  					default foo
   311  
   312  					label foo
   313  					kernel ./pxefiles/kernel1
   314  					append earlyprintk=ttyS0 printk=ttyS0
   315  
   316  					label bar
   317  					kernel ./pxefiles/kernel2
   318  					append console=ttyS0`,
   319  			},
   320  			want: []boot.OSImage{
   321  				&boot.LinuxImage{
   322  					Name:    "bar",
   323  					Kernel:  strings.NewReader(kernel2),
   324  					Cmdline: "console=ttyS0",
   325  				},
   326  				&boot.LinuxImage{
   327  					Name:    "foo",
   328  					Kernel:  strings.NewReader(kernel1),
   329  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   330  				},
   331  			},
   332  		},
   333  
   334  		{
   335  			desc: "valid config with global APPEND directive",
   336  			configFiles: map[string]string{
   337  				"/foobar/pxelinux.cfg/default": `
   338  					default foo
   339  					append foo=bar
   340  
   341  					label foo
   342  					kernel ./pxefiles/kernel1
   343  					append earlyprintk=ttyS0 printk=ttyS0
   344  
   345  					label bar
   346  					kernel ./pxefiles/kernel2
   347  
   348  					label baz
   349  					kernel ./pxefiles/kernel2
   350  					append -`,
   351  			},
   352  			want: []boot.OSImage{
   353  				&boot.LinuxImage{
   354  					Name:   "foo",
   355  					Kernel: strings.NewReader(kernel1),
   356  					// Does not contain global APPEND.
   357  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   358  				},
   359  				&boot.LinuxImage{
   360  					Name:   "bar",
   361  					Kernel: strings.NewReader(kernel2),
   362  					// Contains only global APPEND.
   363  					Cmdline: "foo=bar",
   364  				},
   365  				&boot.LinuxImage{
   366  					Name:   "baz",
   367  					Kernel: strings.NewReader(kernel2),
   368  					// "APPEND -" means ignore global APPEND.
   369  					Cmdline: "",
   370  				},
   371  			},
   372  		},
   373  		{
   374  			desc: "valid config with global APPEND with initrd",
   375  			configFiles: map[string]string{
   376  				"/foobar/pxelinux.cfg/default": `
   377  					default mcnulty
   378  					append initrd=./pxefiles/global_initrd
   379  
   380  					label mcnulty
   381  					kernel ./pxefiles/kernel1
   382  					append earlyprintk=ttyS0 printk=ttyS0
   383  
   384  					label lester
   385  					kernel ./pxefiles/kernel1
   386  
   387  					label omar
   388  					kernel ./pxefiles/kernel2
   389  					append -
   390  
   391  					label stringer
   392  					kernel ./pxefiles/kernel2
   393  					initrd ./pxefiles/initrd2
   394  				`,
   395  			},
   396  			want: []boot.OSImage{
   397  				&boot.LinuxImage{
   398  					Name:   "mcnulty",
   399  					Kernel: strings.NewReader(kernel1),
   400  					// Does not contain global APPEND.
   401  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   402  				},
   403  				&boot.LinuxImage{
   404  					Name:   "lester",
   405  					Kernel: strings.NewReader(kernel1),
   406  					Initrd: strings.NewReader(globalInitrd),
   407  					// Contains only global APPEND.
   408  					Cmdline: "initrd=./pxefiles/global_initrd",
   409  				},
   410  				&boot.LinuxImage{
   411  					Name:   "omar",
   412  					Kernel: strings.NewReader(kernel2),
   413  					// "APPEND -" means ignore global APPEND.
   414  					Cmdline: "",
   415  				},
   416  				&boot.LinuxImage{
   417  					Name:   "stringer",
   418  					Kernel: strings.NewReader(kernel2),
   419  					Initrd: strings.NewReader(initrd2),
   420  
   421  					// TODO: See syslinux initrd handling. This SHOULD be
   422  					//
   423  					// initrd=./pxefiles/global_initrd initrd=./pxefiles/initrd2
   424  					//
   425  					// https://wiki.syslinux.org/wiki/index.php?title=Directives/append
   426  					Cmdline: "initrd=./pxefiles/global_initrd",
   427  				},
   428  			},
   429  		},
   430  		{
   431  			desc: "default label does not exist",
   432  			configFiles: map[string]string{
   433  				"/foobar/pxelinux.cfg/default": `default not-exist`,
   434  			},
   435  			want: nil,
   436  		},
   437  		{
   438  			desc: "multi-scheme valid config",
   439  			configFiles: map[string]string{
   440  				"/foobar/pxelinux.cfg/default": `
   441  				default sheeeit
   442  
   443  				label sheeeit
   444  				kernel ./pxefiles/kernel2
   445  				initrd http://someplace.com/initrd2`,
   446  			},
   447  			want: []boot.OSImage{
   448  				&boot.LinuxImage{
   449  					Name:   "sheeeit",
   450  					Kernel: strings.NewReader(kernel2),
   451  					Initrd: strings.NewReader(initrd2),
   452  				},
   453  			},
   454  		},
   455  		{
   456  			desc: "valid config with three includes",
   457  			configFiles: map[string]string{
   458  				"/foobar/pxelinux.cfg/default": `
   459  					default mcnulty
   460  
   461  					include installer/txt.cfg
   462  					include installer/stdmenu.cfg
   463  
   464  					menu begin advanced
   465  					  menu title Advanced Options
   466  					  include installer/stdmenu.cfg
   467  					menu end
   468  				`,
   469  
   470  				"/foobar/installer/txt.cfg": `
   471  					label mcnulty
   472  					kernel ./pxefiles/kernel1
   473  					append earlyprintk=ttyS0 printk=ttyS0
   474  				`,
   475  
   476  				"/foobar/installer/stdmenu.cfg": `
   477  					label omar
   478  					kernel ./pxefiles/kernel2
   479  				`,
   480  			},
   481  			want: []boot.OSImage{
   482  				&boot.LinuxImage{
   483  					Name:    "mcnulty",
   484  					Kernel:  strings.NewReader(kernel1),
   485  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   486  				},
   487  				&boot.LinuxImage{
   488  					Name:   "omar",
   489  					Kernel: strings.NewReader(kernel2),
   490  				},
   491  			},
   492  		},
   493  		{
   494  			desc: "multiboot images",
   495  			configFiles: map[string]string{
   496  				"/foobar/pxelinux.cfg/default": `
   497  					default foo
   498  
   499  					label bar
   500  					menu label Bla Bla Bla
   501  					kernel mboot.c32
   502  					append xen.gz console=none --- ./pxefiles/kernel1 foobar hahaha --- ./pxefiles/initrd1
   503  
   504  					label mbootnomodules
   505  					kernel mboot.c32
   506  					append xen.gz
   507  
   508  					label foo
   509  					linux mboot.c32
   510  					append earlyprintk=ttyS0 printk=ttyS0`,
   511  			},
   512  			want: []boot.OSImage{
   513  				&boot.LinuxImage{
   514  					Name:    "foo",
   515  					Kernel:  strings.NewReader(mboot),
   516  					Cmdline: "earlyprintk=ttyS0 printk=ttyS0",
   517  				},
   518  				&boot.MultibootImage{
   519  					Name:    "Bla Bla Bla",
   520  					Kernel:  strings.NewReader(xengz),
   521  					Cmdline: "console=none",
   522  					Modules: []multiboot.Module{
   523  						{
   524  							Module:  strings.NewReader(kernel1),
   525  							Cmdline: "./pxefiles/kernel1 foobar hahaha",
   526  						},
   527  						{
   528  							Module:  strings.NewReader(initrd1),
   529  							Cmdline: "./pxefiles/initrd1",
   530  						},
   531  					},
   532  				},
   533  				&boot.MultibootImage{
   534  					Name:   "mbootnomodules",
   535  					Kernel: strings.NewReader(xengz),
   536  				},
   537  			},
   538  		},
   539  	} {
   540  		t.Run(fmt.Sprintf("Test [%02d] %s", i, tt.desc), func(t *testing.T) {
   541  			fs := newMockScheme()
   542  			for filename, content := range tt.configFiles {
   543  				fs.Add("1.2.3.4", filename, content)
   544  			}
   545  			s := make(curl.Schemes)
   546  			s.Register(fs.Scheme, fs)
   547  			s.Register(http.Scheme, http)
   548  
   549  			rootdir := &url.URL{
   550  				Scheme: "tftp",
   551  				Host:   "1.2.3.4",
   552  				Path:   "/",
   553  			}
   554  
   555  			got, err := ParseConfigFile(context.Background(), s, "pxelinux.cfg/default", rootdir, "foobar")
   556  			if !reflect.DeepEqual(err, tt.err) {
   557  				t.Errorf("AppendFile() got %v, want %v", err, tt.err)
   558  			} else if err != nil {
   559  				return
   560  			}
   561  
   562  			if len(tt.want) != len(got) {
   563  				t.Errorf("ParseConfigFile yielded %d images, want %d images", len(got), len(tt.want))
   564  			}
   565  
   566  			for i, want := range tt.want {
   567  				if err := boottest.SameBootImage(got[i], want); err != nil {
   568  					t.Errorf("Boot image index %d not same: %v", i, err)
   569  				}
   570  			}
   571  		})
   572  	}
   573  }
   574  
   575  func TestParseCorner(t *testing.T) {
   576  	for _, tt := range []struct {
   577  		name       string
   578  		s          curl.Schemes
   579  		configFile string
   580  		rootdir    *url.URL
   581  		wd         string
   582  		err        error
   583  	}{
   584  		{
   585  			name:       "no schemes",
   586  			s:          nil,
   587  			configFile: "pxelinux.cfg/default",
   588  			rootdir: &url.URL{
   589  				Scheme: "tftp",
   590  				Host:   "1.2.3.4",
   591  				Path:   "/foobar",
   592  			},
   593  			err: &curl.URLError{
   594  				URL: &url.URL{
   595  					Scheme: "tftp",
   596  					Host:   "1.2.3.4",
   597  					Path:   "/foobar/pxelinux.cfg/default",
   598  				},
   599  				Err: curl.ErrNoSuchScheme,
   600  			},
   601  		},
   602  		{
   603  			name:       "no scheme and config file",
   604  			s:          nil,
   605  			configFile: "",
   606  			rootdir: &url.URL{
   607  				Scheme: "tftp",
   608  				Host:   "1.2.3.4",
   609  				Path:   "/foobar",
   610  			},
   611  			err: &curl.URLError{
   612  				URL: &url.URL{
   613  					Scheme: "tftp",
   614  					Host:   "1.2.3.4",
   615  					Path:   "/foobar",
   616  				},
   617  				Err: curl.ErrNoSuchScheme,
   618  			},
   619  		},
   620  		{
   621  			name:       "no scheme, config file, and working dir",
   622  			s:          nil,
   623  			configFile: "",
   624  			rootdir:    nil,
   625  			err: &curl.URLError{
   626  				URL: &url.URL{},
   627  				Err: curl.ErrNoSuchScheme,
   628  			},
   629  		},
   630  	} {
   631  		t.Run(tt.name, func(t *testing.T) {
   632  			_, err := ParseConfigFile(context.Background(), tt.s, tt.configFile, tt.rootdir, tt.wd)
   633  			if !reflect.DeepEqual(err, tt.err) {
   634  				t.Errorf("ParseConfigFile() = %v, want %v", err, tt.err)
   635  			}
   636  		})
   637  	}
   638  }
   639  
   640  func TestParseURL(t *testing.T) {
   641  	for _, tt := range []struct {
   642  		filename string
   643  		rootdir  *url.URL
   644  		wd       string
   645  		want     *url.URL
   646  	}{
   647  		{
   648  			filename: "foobar",
   649  			rootdir:  mustParseURL("http://[2001::1]:18282/"),
   650  			wd:       "files/more",
   651  			want:     mustParseURL("http://[2001::1]:18282/files/more/foobar"),
   652  		},
   653  		{
   654  			filename: "/foobar",
   655  			rootdir:  mustParseURL("http://[2001::1]:18282"),
   656  			wd:       "files/more",
   657  			want:     mustParseURL("http://[2001::1]:18282/foobar"),
   658  		},
   659  		{
   660  			filename: "http://[2002::2]/blabla",
   661  			rootdir:  mustParseURL("http://[2001::1]:18282/files"),
   662  			wd:       "more",
   663  			want:     mustParseURL("http://[2002::2]/blabla"),
   664  		},
   665  		{
   666  			filename: "http://[2002::2]/blabla",
   667  			rootdir:  nil,
   668  			want:     mustParseURL("http://[2002::2]/blabla"),
   669  		},
   670  	} {
   671  		got, err := parseURL(tt.filename, tt.rootdir, tt.wd)
   672  		if err != nil {
   673  			t.Errorf("parseURL(%q, %s, %s) = %v, want %v", tt.filename, tt.rootdir, tt.wd, err, nil)
   674  		}
   675  
   676  		if !reflect.DeepEqual(got, tt.want) {
   677  			t.Errorf("parseURL(%q, %s, %s) = %v, want %v", tt.filename, tt.rootdir, tt.wd, got, tt.want)
   678  		}
   679  	}
   680  }