github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/bootloader/uboot.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package bootloader
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	"github.com/snapcore/snapd/bootloader/ubootenv"
    28  	"github.com/snapcore/snapd/snap"
    29  )
    30  
    31  type uboot struct {
    32  	rootdir string
    33  	basedir string
    34  
    35  	ubootEnvFileName string
    36  }
    37  
    38  func (u *uboot) setDefaults() {
    39  	u.basedir = "/boot/uboot/"
    40  	u.ubootEnvFileName = "uboot.env"
    41  }
    42  
    43  func (u *uboot) processBlOpts(blOpts *Options) {
    44  	if blOpts != nil {
    45  		switch {
    46  		case blOpts.Role == RoleRecovery || blOpts.NoSlashBoot:
    47  			// RoleRecovery or NoSlashBoot imply we use
    48  			// the "boot.sel" simple text format file in
    49  			// /uboot/ubuntu as it exists on the partition
    50  			// directly
    51  			u.basedir = "/uboot/ubuntu/"
    52  			fallthrough
    53  		case blOpts.Role == RoleRunMode:
    54  			// if RoleRunMode (and no NoSlashBoot), we
    55  			// expect to find /boot/uboot/boot.sel
    56  			u.ubootEnvFileName = "boot.sel"
    57  		}
    58  	}
    59  }
    60  
    61  // newUboot create a new Uboot bootloader object
    62  func newUboot(rootdir string, blOpts *Options) Bootloader {
    63  	u := &uboot{
    64  		rootdir: rootdir,
    65  	}
    66  	u.setDefaults()
    67  	u.processBlOpts(blOpts)
    68  
    69  	return u
    70  }
    71  
    72  func (u *uboot) Name() string {
    73  	return "uboot"
    74  }
    75  
    76  func (u *uboot) setRootDir(rootdir string) {
    77  	u.rootdir = rootdir
    78  }
    79  
    80  func (u *uboot) dir() string {
    81  	if u.rootdir == "" {
    82  		panic("internal error: unset rootdir")
    83  	}
    84  	return filepath.Join(u.rootdir, u.basedir)
    85  }
    86  
    87  func (u *uboot) InstallBootConfig(gadgetDir string, blOpts *Options) (bool, error) {
    88  	gadgetFile := filepath.Join(gadgetDir, u.Name()+".conf")
    89  	// if the gadget file is empty, then we don't install anything
    90  	// this is because there are some gadgets, namely the 20 pi gadget right
    91  	// now, that don't use a uboot.env to boot and instead use a boot.scr, and
    92  	// installing a uboot.env file of any form in the root directory will break
    93  	// the boot.scr, so for these setups we just don't install anything
    94  	// TODO:UC20: how can we do this better? maybe parse the file to get the
    95  	//            actual format?
    96  	st, err := os.Stat(gadgetFile)
    97  	if err != nil {
    98  		return false, err
    99  	}
   100  	if st.Size() == 0 {
   101  		// we have an empty uboot.conf, and hence a uboot bootloader in the
   102  		// gadget, but nothing to copy in this case and instead just install our
   103  		// own boot.sel file
   104  		u.processBlOpts(blOpts)
   105  
   106  		err := os.MkdirAll(filepath.Dir(u.envFile()), 0755)
   107  		if err != nil {
   108  			return false, err
   109  		}
   110  
   111  		// TODO:UC20: what's a reasonable size for this file?
   112  		env, err := ubootenv.Create(u.envFile(), 4096)
   113  		if err != nil {
   114  			return false, err
   115  		}
   116  
   117  		if err := env.Save(); err != nil {
   118  			return false, nil
   119  		}
   120  
   121  		return true, nil
   122  	}
   123  
   124  	// InstallBootConfig gets called on a uboot that does not come from newUboot
   125  	// so we need to apply the defaults here
   126  	u.setDefaults()
   127  
   128  	if blOpts != nil && blOpts.Role == RoleRecovery {
   129  		// not supported yet, this is traditional uboot.env from gadget
   130  		// TODO:UC20: support this use-case
   131  		return false, fmt.Errorf("non-empty uboot.env not supported on UC20 yet")
   132  	}
   133  
   134  	systemFile := u.ConfigFile()
   135  	return genericInstallBootConfig(gadgetFile, systemFile)
   136  }
   137  
   138  func (u *uboot) ConfigFile() string {
   139  	return u.envFile()
   140  }
   141  
   142  func (u *uboot) envFile() string {
   143  	return filepath.Join(u.dir(), u.ubootEnvFileName)
   144  }
   145  
   146  func (u *uboot) SetBootVars(values map[string]string) error {
   147  	env, err := ubootenv.OpenWithFlags(u.envFile(), ubootenv.OpenBestEffort)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	dirty := false
   153  	for k, v := range values {
   154  		// already set to the right value, nothing to do
   155  		if env.Get(k) == v {
   156  			continue
   157  		}
   158  		env.Set(k, v)
   159  		dirty = true
   160  	}
   161  
   162  	if dirty {
   163  		return env.Save()
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func (u *uboot) GetBootVars(names ...string) (map[string]string, error) {
   170  	out := map[string]string{}
   171  
   172  	env, err := ubootenv.OpenWithFlags(u.envFile(), ubootenv.OpenBestEffort)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	for _, name := range names {
   178  		out[name] = env.Get(name)
   179  	}
   180  
   181  	return out, nil
   182  }
   183  
   184  func (u *uboot) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
   185  	dstDir := filepath.Join(u.dir(), s.Filename())
   186  	assets := []string{"kernel.img", "initrd.img", "dtbs/*"}
   187  	return extractKernelAssetsToBootDir(dstDir, snapf, assets)
   188  }
   189  
   190  func (u *uboot) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error {
   191  	if recoverySystemDir == "" {
   192  		return fmt.Errorf("internal error: recoverySystemDir unset")
   193  	}
   194  
   195  	recoverySystemUbootKernelAssetsDir := filepath.Join(u.rootdir, recoverySystemDir, "kernel")
   196  	assets := []string{"kernel.img", "initrd.img", "dtbs/*"}
   197  	return extractKernelAssetsToBootDir(recoverySystemUbootKernelAssetsDir, snapf, assets)
   198  }
   199  
   200  func (u *uboot) RemoveKernelAssets(s snap.PlaceInfo) error {
   201  	return removeKernelAssetsFromBootDir(u.dir(), s)
   202  }