github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/libinit/root_linux.go (about)

     1  // Copyright 2014-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 libinit creates the environment and root file system for u-root.
     6  package libinit
     7  
     8  import (
     9  	"bufio"
    10  	"errors"
    11  	"fmt"
    12  	"log"
    13  	"os"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/mvdan/u-root-coreutils/pkg/cmdline"
    20  	"github.com/mvdan/u-root-coreutils/pkg/cp"
    21  	"github.com/mvdan/u-root-coreutils/pkg/kmodule"
    22  	"github.com/mvdan/u-root-coreutils/pkg/ulog"
    23  	"golang.org/x/sys/unix"
    24  )
    25  
    26  type Creator interface {
    27  	Create() error
    28  	fmt.Stringer
    29  }
    30  
    31  type Dir struct {
    32  	Name string
    33  	Mode os.FileMode
    34  }
    35  
    36  func (d Dir) Create() error {
    37  	return os.MkdirAll(d.Name, d.Mode)
    38  }
    39  
    40  func (d Dir) String() string {
    41  	return fmt.Sprintf("dir %q (mode %#o)", d.Name, d.Mode)
    42  }
    43  
    44  type Symlink struct {
    45  	Target  string
    46  	NewPath string
    47  }
    48  
    49  func (s Symlink) Create() error {
    50  	os.Remove(s.NewPath)
    51  	return os.Symlink(s.Target, s.NewPath)
    52  }
    53  
    54  func (s Symlink) String() string {
    55  	return fmt.Sprintf("symlink %q -> %q", s.NewPath, s.Target)
    56  }
    57  
    58  type Dev struct {
    59  	Name string
    60  	Mode uint32
    61  	Dev  int
    62  }
    63  
    64  func (d Dev) Create() error {
    65  	os.Remove(d.Name)
    66  	return unix.Mknod(d.Name, d.Mode, d.Dev)
    67  }
    68  
    69  func (d Dev) String() string {
    70  	return fmt.Sprintf("dev %q (mode %#o; magic %d)", d.Name, d.Mode, d.Dev)
    71  }
    72  
    73  type Mount struct {
    74  	Source string
    75  	Target string
    76  	FSType string
    77  	Flags  uintptr
    78  	Opts   string
    79  }
    80  
    81  func (m Mount) Create() error {
    82  	return unix.Mount(m.Source, m.Target, m.FSType, m.Flags, m.Opts)
    83  }
    84  
    85  func (m Mount) String() string {
    86  	return fmt.Sprintf("mount -t %q -o %s %q %q flags %#x", m.FSType, m.Opts, m.Source, m.Target, m.Flags)
    87  }
    88  
    89  type CpDir struct {
    90  	Source string
    91  	Target string
    92  }
    93  
    94  func (c CpDir) Create() error {
    95  	return cp.CopyTree(c.Source, c.Target)
    96  }
    97  
    98  func (c CpDir) String() string {
    99  	return fmt.Sprintf("cp -a %q %q", c.Source, c.Target)
   100  }
   101  
   102  var (
   103  	// These have to be created / mounted first, so that the logging works correctly.
   104  	PreNamespace = []Creator{
   105  		Dir{Name: "/dev", Mode: 0o777},
   106  
   107  		// Kernel must be compiled with CONFIG_DEVTMPFS.
   108  		Mount{Source: "devtmpfs", Target: "/dev", FSType: "devtmpfs"},
   109  	}
   110  	Namespace = []Creator{
   111  		Dir{Name: "/buildbin", Mode: 0o777},
   112  		Dir{Name: "/ubin", Mode: 0o777},
   113  		Dir{Name: "/tmp", Mode: 0o777},
   114  		Dir{Name: "/env", Mode: 0o777},
   115  		Dir{Name: "/tcz", Mode: 0o777},
   116  		Dir{Name: "/lib", Mode: 0o777},
   117  		Dir{Name: "/usr/lib", Mode: 0o777},
   118  		Dir{Name: "/var/log", Mode: 0o777},
   119  		Dir{Name: "/go/pkg/linux_amd64", Mode: 0o777},
   120  
   121  		Dir{Name: "/etc", Mode: 0o777},
   122  
   123  		Dir{Name: "/proc", Mode: 0o555},
   124  		Mount{Source: "proc", Target: "/proc", FSType: "proc"},
   125  		Mount{Source: "tmpfs", Target: "/tmp", FSType: "tmpfs"},
   126  
   127  		Dev{Name: "/dev/tty", Mode: unix.S_IFCHR | 0o666, Dev: 0x0500},
   128  		Dev{Name: "/dev/urandom", Mode: unix.S_IFCHR | 0o444, Dev: 0x0109},
   129  		Dev{Name: "/dev/port", Mode: unix.S_IFCHR | 0o640, Dev: 0x0104},
   130  		Dev{Name: "/dev/ttyhvc0", Mode: unix.S_IFCHR | 0o666, Dev: 0xe500},
   131  
   132  		Dir{Name: "/dev/pts", Mode: 0o777},
   133  		Mount{Source: "devpts", Target: "/dev/pts", FSType: "devpts", Opts: "newinstance,ptmxmode=666,gid=5,mode=620"},
   134  		// Note: if we mount /dev/pts with "newinstance", we *must* make "/dev/ptmx" a symlink to "/dev/pts/ptmx"
   135  		Symlink{NewPath: "/dev/ptmx", Target: "/dev/pts/ptmx"},
   136  		// Note: shm is required at least for Chrome. If you don't mount
   137  		// it chrome throws a bogus "out of memory" error, not the more
   138  		// useful "I can't open /dev/shm/whatever". SAD!
   139  		Dir{Name: "/dev/shm", Mode: 0o777},
   140  		Mount{Source: "tmpfs", Target: "/dev/shm", FSType: "tmpfs"},
   141  
   142  		Dir{Name: "/sys", Mode: 0o555},
   143  		Mount{Source: "sysfs", Target: "/sys", FSType: "sysfs"},
   144  		Mount{Source: "securityfs", Target: "/sys/kernel/security", FSType: "securityfs"},
   145  		Mount{Source: "efivarfs", Target: "/sys/firmware/efi/efivars", FSType: "efivarfs"},
   146  		Mount{Source: "debugfs", Target: "/sys/kernel/debug", FSType: "debugfs"},
   147  
   148  		CpDir{Source: "/etc", Target: "/tmp/etc"},
   149  		Mount{Source: "/tmp/etc", Target: "/etc", FSType: "tmpfs", Flags: unix.MS_BIND},
   150  	}
   151  
   152  	// cgroups are optional for most u-root users, especially
   153  	// LinuxBoot/NERF. Some users use u-root for container stuff.
   154  	CgroupsNamespace = []Creator{
   155  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup", FSType: "tmpfs"},
   156  		Dir{Name: "/sys/fs/cgroup/memory", Mode: 0o555},
   157  		Dir{Name: "/sys/fs/cgroup/freezer", Mode: 0o555},
   158  		Dir{Name: "/sys/fs/cgroup/devices", Mode: 0o555},
   159  		Dir{Name: "/sys/fs/cgroup/cpu,cpuacct", Mode: 0o555},
   160  		Dir{Name: "/sys/fs/cgroup/blkio", Mode: 0o555},
   161  		Dir{Name: "/sys/fs/cgroup/cpuset", Mode: 0o555},
   162  		Dir{Name: "/sys/fs/cgroup/pids", Mode: 0o555},
   163  		Dir{Name: "/sys/fs/cgroup/net_cls,net_prio", Mode: 0o555},
   164  		Dir{Name: "/sys/fs/cgroup/hugetlb", Mode: 0o555},
   165  		Dir{Name: "/sys/fs/cgroup/perf_event", Mode: 0o555},
   166  		Symlink{NewPath: "/sys/fs/cgroup/cpu", Target: "/sys/fs/cgroup/cpu,cpuacct"},
   167  		Symlink{NewPath: "/sys/fs/cgroup/cpuacct", Target: "/sys/fs/cgroup/cpu,cpuacct"},
   168  		Symlink{NewPath: "/sys/fs/cgroup/net_cls", Target: "/sys/fs/cgroup/net_cls,net_prio"},
   169  		Symlink{NewPath: "/sys/fs/cgroup/net_prio", Target: "/sys/fs/cgroup/net_cls,net_prio"},
   170  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/memory", FSType: "cgroup", Opts: "memory"},
   171  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/freezer", FSType: "cgroup", Opts: "freezer"},
   172  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/devices", FSType: "cgroup", Opts: "devices"},
   173  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/cpu,cpuacct", FSType: "cgroup", Opts: "cpu,cpuacct"},
   174  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/blkio", FSType: "cgroup", Opts: "blkio"},
   175  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/cpuset", FSType: "cgroup", Opts: "cpuset"},
   176  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/pids", FSType: "cgroup", Opts: "pids"},
   177  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/net_cls,net_prio", FSType: "cgroup", Opts: "net_cls,net_prio"},
   178  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/hugetlb", FSType: "cgroup", Opts: "hugetlb"},
   179  		Mount{Source: "cgroup", Target: "/sys/fs/cgroup/perf_event", FSType: "cgroup", Opts: "perf_event"},
   180  	}
   181  )
   182  
   183  func goBin() string {
   184  	return fmt.Sprintf("/go/bin/%s_%s:/go/bin:/go/pkg/tool/%s_%s", runtime.GOOS, runtime.GOARCH, runtime.GOOS, runtime.GOARCH)
   185  }
   186  
   187  func Create(namespace []Creator, optional bool) {
   188  	// Clear umask bits so that we get stuff like ptmx right.
   189  	m := unix.Umask(0)
   190  	defer unix.Umask(m)
   191  	for _, c := range namespace {
   192  		if err := c.Create(); err != nil {
   193  			if optional {
   194  				ulog.KernelLog.Printf("u-root init [optional]: warning creating %s: %v", c, err)
   195  			} else {
   196  				ulog.KernelLog.Printf("u-root init: error creating %s: %v", c, err)
   197  			}
   198  		}
   199  	}
   200  }
   201  
   202  // SetEnv sets the default u-root environment.
   203  func SetEnv() {
   204  	env := map[string]string{
   205  		"LD_LIBRARY_PATH": "/usr/local/lib",
   206  		"GOROOT":          "/go",
   207  		"GOPATH":          "/",
   208  		"GOBIN":           "/ubin",
   209  		"CGO_ENABLED":     "0",
   210  		"USER":            "root",
   211  	}
   212  
   213  	// Not all these paths may be populated or even exist but OTOH they might.
   214  	path := "/ubin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin:/usr/local/sbin:/buildbin:/bbin"
   215  
   216  	env["PATH"] = fmt.Sprintf("%v:%v", goBin(), path)
   217  	for k, v := range env {
   218  		os.Setenv(k, v)
   219  	}
   220  }
   221  
   222  // CreateRootfs creates the default u-root file system.
   223  func CreateRootfs() {
   224  	// Mount devtmpfs, then open /dev/kmsg with Reinit.
   225  	Create(PreNamespace, false)
   226  	ulog.KernelLog.Reinit()
   227  
   228  	Create(Namespace, false)
   229  
   230  	// systemd gets upset when it discovers something has already setup cgroups
   231  	// We have to do this after the base namespace is created, so we have /proc
   232  	initFlags := cmdline.GetInitFlagMap()
   233  	systemd, present := initFlags["systemd"]
   234  	systemdEnabled, boolErr := strconv.ParseBool(systemd)
   235  	if !present || boolErr != nil || !systemdEnabled {
   236  		Create(CgroupsNamespace, true)
   237  	}
   238  }
   239  
   240  // InitModuleLoader wraps the resources we need for early module loading
   241  type InitModuleLoader struct {
   242  	Cmdline      *cmdline.CmdLine
   243  	Prober       func(name string, modParameters string) error
   244  	ExcludedMods map[string]bool
   245  }
   246  
   247  func (i *InitModuleLoader) IsExcluded(mod string) bool {
   248  	return i.ExcludedMods[mod]
   249  }
   250  
   251  func (i *InitModuleLoader) LoadModule(mod string) error {
   252  	flags := i.Cmdline.FlagsForModule(mod)
   253  	if err := i.Prober(mod, flags); err != nil {
   254  		return fmt.Errorf("failed to load module: %s", err)
   255  	}
   256  	return nil
   257  }
   258  
   259  func NewInitModuleLoader() *InitModuleLoader {
   260  	return &InitModuleLoader{
   261  		Cmdline: cmdline.NewCmdLine(),
   262  		Prober:  kmodule.Probe,
   263  		ExcludedMods: map[string]bool{
   264  			"idpf":     true,
   265  			"idpf_imc": true,
   266  		},
   267  	}
   268  }
   269  
   270  // InstallAllModules installs kernel modules form the following locations in order:
   271  // - .ko files from /lib/modules
   272  // - modules found in .conf files from /lib/modules-load.d/
   273  // - modules found in the cmdline argument modules_load= separated by ,
   274  // Useful for modules that need to be loaded for boot (ie a network
   275  // driver needed for netboot). It skips over blacklisted modules in
   276  // excludedMods.
   277  func InstallAllModules() error {
   278  	loader := NewInitModuleLoader()
   279  	modulePattern := "/lib/modules/*.ko"
   280  	if err := InstallModulesFromDir(modulePattern, loader); !errors.Is(err, ErrNoModulesFound) {
   281  		return err
   282  	}
   283  	var allModules []string
   284  	moduleConfPattern := "/lib/modules-load.d/*.conf"
   285  	modules, err := GetModulesFromConf(moduleConfPattern)
   286  	if err != nil {
   287  		return err
   288  	}
   289  	allModules = append(allModules, modules...)
   290  	modules, err = GetModulesFromCmdline(loader)
   291  	if err != nil {
   292  		return err
   293  	}
   294  	allModules = append(allModules, modules...)
   295  	InstallModules(loader, allModules)
   296  	return nil
   297  }
   298  
   299  // InstallModules installs the passed modules using the InitModuleLoader
   300  func InstallModules(m *InitModuleLoader, modules []string) {
   301  	for _, moduleName := range modules {
   302  		if m.IsExcluded(moduleName) {
   303  			log.Printf("Skipping module %q", moduleName)
   304  			continue
   305  		}
   306  		if err := m.LoadModule(moduleName); err != nil {
   307  			log.Printf("InstallModulesFromModulesLoad: can't install %q: %v", moduleName, err)
   308  		}
   309  	}
   310  }
   311  
   312  // ErrNoModulesFound is the error returned when InstallModulesFromDir does not
   313  // find any valid modules in the path.
   314  var ErrNoModulesFound = fmt.Errorf("no modules found")
   315  
   316  // InstallModulesFromDir installs kernel modules (.ko files) from /lib/modules that
   317  // match the given pattern, skipping those in the exclude list.
   318  func InstallModulesFromDir(pattern string, loader *InitModuleLoader) error {
   319  	files, err := filepath.Glob(pattern)
   320  	if err != nil {
   321  		return err
   322  	}
   323  	if len(files) == 0 {
   324  		return ErrNoModulesFound
   325  	}
   326  
   327  	for _, filename := range files {
   328  		f, err := os.Open(filename)
   329  		if err != nil {
   330  			log.Printf("InstallModules: can't open %q: %v", filename, err)
   331  			continue
   332  		}
   333  		defer f.Close()
   334  		// Module flags are passed to the command line in the from modulename.flag=val
   335  		// And must be passed to FileInit as flag=val to be installed properly
   336  		moduleName := strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename))
   337  		if loader.IsExcluded(moduleName) {
   338  			log.Printf("Skipping module %q", moduleName)
   339  			continue
   340  		}
   341  
   342  		flags := cmdline.FlagsForModule(moduleName)
   343  		if err = kmodule.FileInit(f, flags, 0); err != nil {
   344  			log.Printf("InstallModules: can't install %q: %v", filename, err)
   345  		}
   346  	}
   347  
   348  	return nil
   349  }
   350  
   351  func readModules(f *os.File) []string {
   352  	scanner := bufio.NewScanner(f)
   353  	modules := []string{}
   354  	for scanner.Scan() {
   355  		i := scanner.Text()
   356  		i = strings.TrimSpace(i)
   357  		if i == "" || strings.HasPrefix(i, "#") {
   358  			continue
   359  		}
   360  		modules = append(modules, i)
   361  	}
   362  	if err := scanner.Err(); err != nil {
   363  		log.Println("error on reading:", err)
   364  	}
   365  	return modules
   366  }
   367  
   368  // GetModulesFromConf finds kernel modules from .conf files in /lib/modules-load.d/
   369  func GetModulesFromConf(pattern string) ([]string, error) {
   370  	var ret []string
   371  	files, err := filepath.Glob(pattern)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	for _, filename := range files {
   376  		f, err := os.Open(filename)
   377  		if err != nil {
   378  			log.Printf("InstallModulesFromModulesLoad: can't open %q: %v", filename, err)
   379  			continue
   380  		}
   381  		defer f.Close()
   382  		modules := readModules(f)
   383  		ret = append(ret, modules...)
   384  	}
   385  	return ret, nil
   386  }
   387  
   388  // GetModulesFromCmdline finds kernel modules from the modules_load kernel parameter
   389  func GetModulesFromCmdline(m *InitModuleLoader) ([]string, error) {
   390  	var ret []string
   391  	modules, present := m.Cmdline.Flag("modules_load")
   392  	if !present {
   393  		return nil, nil
   394  	}
   395  
   396  	for _, moduleName := range strings.Split(modules, ",") {
   397  		moduleName = strings.TrimSpace(moduleName)
   398  		ret = append(ret, moduleName)
   399  	}
   400  	return ret, nil
   401  }