github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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/osutil"
    29  	"github.com/snapcore/snapd/snap"
    30  )
    31  
    32  // sanity - uboot implements the required interfaces
    33  var (
    34  	_ Bootloader                             = (*uboot)(nil)
    35  	_ ExtractedRecoveryKernelImageBootloader = (*uboot)(nil)
    36  )
    37  
    38  type uboot struct {
    39  	rootdir string
    40  	basedir string
    41  
    42  	ubootEnvFileName string
    43  }
    44  
    45  func (u *uboot) setDefaults() {
    46  	u.basedir = "/boot/uboot/"
    47  	u.ubootEnvFileName = "uboot.env"
    48  }
    49  
    50  func (u *uboot) processBlOpts(blOpts *Options) {
    51  	if blOpts != nil {
    52  		switch {
    53  		case blOpts.Role == RoleRecovery || blOpts.NoSlashBoot:
    54  			// RoleRecovery or NoSlashBoot imply we use
    55  			// the "boot.sel" simple text format file in
    56  			// /uboot/ubuntu as it exists on the partition
    57  			// directly
    58  			u.basedir = "/uboot/ubuntu/"
    59  			fallthrough
    60  		case blOpts.Role == RoleRunMode:
    61  			// if RoleRunMode (and no NoSlashBoot), we
    62  			// expect to find /boot/uboot/boot.sel
    63  			u.ubootEnvFileName = "boot.sel"
    64  		}
    65  	}
    66  }
    67  
    68  // newUboot create a new Uboot bootloader object
    69  func newUboot(rootdir string, blOpts *Options) Bootloader {
    70  	u := &uboot{
    71  		rootdir: rootdir,
    72  	}
    73  	u.setDefaults()
    74  	u.processBlOpts(blOpts)
    75  
    76  	return u
    77  }
    78  
    79  func (u *uboot) Name() string {
    80  	return "uboot"
    81  }
    82  
    83  func (u *uboot) dir() string {
    84  	if u.rootdir == "" {
    85  		panic("internal error: unset rootdir")
    86  	}
    87  	return filepath.Join(u.rootdir, u.basedir)
    88  }
    89  
    90  func (u *uboot) InstallBootConfig(gadgetDir string, blOpts *Options) error {
    91  	gadgetFile := filepath.Join(gadgetDir, u.Name()+".conf")
    92  	// if the gadget file is empty, then we don't install anything
    93  	// this is because there are some gadgets, namely the 20 pi gadget right
    94  	// now, that don't use a uboot.env to boot and instead use a boot.scr, and
    95  	// installing a uboot.env file of any form in the root directory will break
    96  	// the boot.scr, so for these setups we just don't install anything
    97  	// TODO:UC20: how can we do this better? maybe parse the file to get the
    98  	//            actual format?
    99  	st, err := os.Stat(gadgetFile)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	if st.Size() == 0 {
   104  		// we have an empty uboot.conf, and hence a uboot bootloader in the
   105  		// gadget, but nothing to copy in this case and instead just install our
   106  		// own boot.sel file
   107  		u.processBlOpts(blOpts)
   108  
   109  		err := os.MkdirAll(filepath.Dir(u.envFile()), 0755)
   110  		if err != nil {
   111  			return err
   112  		}
   113  
   114  		// TODO:UC20: what's a reasonable size for this file?
   115  		env, err := ubootenv.Create(u.envFile(), 4096)
   116  		if err != nil {
   117  			return err
   118  		}
   119  
   120  		if err := env.Save(); err != nil {
   121  			return nil
   122  		}
   123  
   124  		return nil
   125  	}
   126  
   127  	// InstallBootConfig gets called on a uboot that does not come from newUboot
   128  	// so we need to apply the defaults here
   129  	u.setDefaults()
   130  
   131  	if blOpts != nil && blOpts.Role == RoleRecovery {
   132  		// not supported yet, this is traditional uboot.env from gadget
   133  		// TODO:UC20: support this use-case
   134  		return fmt.Errorf("non-empty uboot.env not supported on UC20 yet")
   135  	}
   136  
   137  	systemFile := u.envFile()
   138  	return genericInstallBootConfig(gadgetFile, systemFile)
   139  }
   140  
   141  func (u *uboot) Present() (bool, error) {
   142  	return osutil.FileExists(u.envFile()), nil
   143  }
   144  
   145  func (u *uboot) envFile() string {
   146  	return filepath.Join(u.dir(), u.ubootEnvFileName)
   147  }
   148  
   149  func (u *uboot) SetBootVars(values map[string]string) error {
   150  	env, err := ubootenv.OpenWithFlags(u.envFile(), ubootenv.OpenBestEffort)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	dirty := false
   156  	for k, v := range values {
   157  		// already set to the right value, nothing to do
   158  		if env.Get(k) == v {
   159  			continue
   160  		}
   161  		env.Set(k, v)
   162  		dirty = true
   163  	}
   164  
   165  	if dirty {
   166  		return env.Save()
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  func (u *uboot) GetBootVars(names ...string) (map[string]string, error) {
   173  	out := map[string]string{}
   174  
   175  	env, err := ubootenv.OpenWithFlags(u.envFile(), ubootenv.OpenBestEffort)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	for _, name := range names {
   181  		out[name] = env.Get(name)
   182  	}
   183  
   184  	return out, nil
   185  }
   186  
   187  func (u *uboot) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
   188  	dstDir := filepath.Join(u.dir(), s.Filename())
   189  	assets := []string{"kernel.img", "initrd.img", "dtbs/*"}
   190  	return extractKernelAssetsToBootDir(dstDir, snapf, assets)
   191  }
   192  
   193  func (u *uboot) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error {
   194  	if recoverySystemDir == "" {
   195  		return fmt.Errorf("internal error: recoverySystemDir unset")
   196  	}
   197  
   198  	recoverySystemUbootKernelAssetsDir := filepath.Join(u.rootdir, recoverySystemDir, "kernel")
   199  	assets := []string{"kernel.img", "initrd.img", "dtbs/*"}
   200  	return extractKernelAssetsToBootDir(recoverySystemUbootKernelAssetsDir, snapf, assets)
   201  }
   202  
   203  func (u *uboot) RemoveKernelAssets(s snap.PlaceInfo) error {
   204  	return removeKernelAssetsFromBootDir(u.dir(), s)
   205  }