File: initramfs.go

package info (click to toggle)
snapd 2.71-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 79,536 kB
  • sloc: ansic: 16,114; sh: 16,105; python: 9,941; makefile: 1,890; exp: 190; awk: 40; xml: 22
file content (194 lines) | stat: -rw-r--r-- 5,868 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2020 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package boot

import (
	"os/exec"
	"time"

	"github.com/snapcore/snapd/bootloader"
	"github.com/snapcore/snapd/logger"
	"github.com/snapcore/snapd/osutil"
	"github.com/snapcore/snapd/osutil/kcmdline"
	"github.com/snapcore/snapd/snap"
)

// InitramfsRunModeSelectSnapsToMount returns a map of the snap paths to mount
// for the specified snap types.
func InitramfsRunModeSelectSnapsToMount(
	typs []snap.Type,
	modeenv *Modeenv,
	rootfsDir string,
) (map[snap.Type]snap.PlaceInfo, error) {
	var sn snap.PlaceInfo
	var err error
	m := make(map[snap.Type]snap.PlaceInfo)
	for _, typ := range typs {
		// TODO: consider passing a bootStateUpdate20 instead?
		var selectSnapFn func(*Modeenv, string) (snap.PlaceInfo, error)
		switch typ {
		case snap.TypeBase:
			bs := &bootState20Base{}
			selectSnapFn = bs.selectAndCommitSnapInitramfsMount
		case snap.TypeGadget:
			// Do not mount if modeenv does not have gadget entry
			if modeenv.Gadget == "" {
				continue
			}
			selectSnapFn = selectGadgetSnap
		case snap.TypeKernel:
			blOpts := &bootloader.Options{
				Role:        bootloader.RoleRunMode,
				NoSlashBoot: true,
			}
			blDir := InitramfsUbuntuBootDir
			bs := &bootState20Kernel{
				blDir:  blDir,
				blOpts: blOpts,
			}
			selectSnapFn = bs.selectAndCommitSnapInitramfsMount
		}
		sn, err = selectSnapFn(modeenv, rootfsDir)
		if err != nil {
			return nil, err
		}

		m[typ] = sn
	}

	return m, nil
}

// EnsureNextBootToRunMode will mark the bootenv of the recovery bootloader such
// that recover mode is now ready to switch back to run mode upon any reboot.
func EnsureNextBootToRunMode(systemLabel string) error {
	// at the end of the initramfs we need to set the bootenv such that a reboot
	// now at any point will rollback to run mode without additional config or
	// actions

	opts := &bootloader.Options{
		// setup the recovery bootloader
		Role: bootloader.RoleRecovery,
	}

	bl, err := bootloader.Find(InitramfsUbuntuSeedDir, opts)
	if err != nil {
		return err
	}

	m := map[string]string{
		"snapd_recovery_system": systemLabel,
		"snapd_recovery_mode":   "run",
	}
	return bl.SetBootVars(m)
}

// initramfsReboot triggers a reboot from the initramfs immediately
var initramfsReboot = func() error {
	if osutil.IsTestBinary() {
		panic("initramfsReboot must be mocked in tests")
	}

	out, err := exec.Command("systemctl", "reboot").CombinedOutput()
	if err != nil {
		return osutil.OutputErr(out, err)
	}

	// reboot command in practice seems to not return, but apparently it is
	// theoretically possible it could return, so to account for this we will
	// loop for a "long" time waiting for the system to be rebooted, and panic
	// after a timeout so that if something goes wrong with the reboot we do
	// exit with some info about the expected reboot
	time.Sleep(10 * time.Minute)
	panic("expected reboot to happen within 10 minutes after calling systemctl reboot")
}

func MockInitramfsReboot(f func() error) (restore func()) {
	osutil.MustBeTestBinary("initramfsReboot only can be mocked in tests")
	old := initramfsReboot
	initramfsReboot = f
	return func() {
		initramfsReboot = old
	}
}

// InitramfsReboot requests the system to reboot. Can be called while in
// initramfs.
func InitramfsReboot() error {
	return initramfsReboot()
}

// This function implements logic that is usually part of the
// bootloader, but that it is not possible to implement in, for
// instance, piboot. See handling of kernel_status in
// bootloader/assets/data/grub.cfg.
func updateNotScriptableBootloaderStatus(bl bootloader.NotScriptableBootloader) error {
	blVars, err := bl.GetBootVars("kernel_status")
	if err != nil {
		return err
	}
	curKernStatus := blVars["kernel_status"]
	if curKernStatus == "" {
		return nil
	}

	kVals, err := kcmdline.KeyValues("kernel_status")
	if err != nil {
		return err
	}
	// "" would be the value for the error case, which at this point is any
	// case different to kernel_status=trying in kernel command line and
	// kernel_status=try in configuration file. Note that kernel_status in
	// the file should be only "try" or empty, and for the latter we should
	// have returned a few lines up.
	newStatus := ""
	if kVals["kernel_status"] == "trying" && curKernStatus == "try" {
		newStatus = "trying"
	}

	logger.Debugf("setting %s kernel_status from %s to %s",
		bl.Name(), curKernStatus, newStatus)
	return bl.SetBootVarsFromInitramfs(map[string]string{"kernel_status": newStatus})
}

// InitramfsRunModeUpdateBootloaderVars updates bootloader variables
// from the initramfs. This is necessary only for piboot at the
// moment.
func InitramfsRunModeUpdateBootloaderVars() error {
	// For very limited bootloaders we need to change the kernel
	// status from the initramfs as we cannot do that from the
	// bootloader
	blOpts := &bootloader.Options{
		Role:        bootloader.RoleRunMode,
		NoSlashBoot: true,
	}

	bl, err := bootloader.Find(InitramfsUbuntuBootDir, blOpts)
	if err == nil {
		if nsb, ok := bl.(bootloader.NotScriptableBootloader); ok {
			if err := updateNotScriptableBootloaderStatus(nsb); err != nil {
				logger.Noticef("cannot update %s kernel status: %v", bl.Name(), err)
				return err
			}
		}
	}

	return nil
}