forked from snapcore/snapd
/
bootstate16.go
197 lines (169 loc) · 5.13 KB
/
bootstate16.go
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
195
196
197
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019 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 (
"fmt"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/snap"
)
type bootState16 struct {
varSuffix string
errName string
}
func newBootState16(typ snap.Type, dev Device) bootState {
var varSuffix, errName string
switch typ {
case snap.TypeKernel:
varSuffix = "kernel"
errName = "kernel"
case snap.TypeBase:
varSuffix = "core"
errName = "boot base"
default:
panic(fmt.Sprintf("cannot make a bootState16 for snap type %q", typ))
}
return &bootState16{varSuffix: varSuffix, errName: errName}
}
func (s16 *bootState16) revisions() (s, tryS snap.PlaceInfo, status string, err error) {
bloader, err := bootloader.Find("", nil)
if err != nil {
return nil, nil, "", fmt.Errorf("cannot get boot settings: %s", err)
}
snapVar := "snap_" + s16.varSuffix
trySnapVar := "snap_try_" + s16.varSuffix
vars := []string{"snap_mode", snapVar, trySnapVar}
snaps := make(map[string]snap.PlaceInfo, 2)
m, err := bloader.GetBootVars(vars...)
if err != nil {
return nil, nil, "", fmt.Errorf("cannot get boot variables: %s", err)
}
for _, vName := range vars {
v := m[vName]
if v == "" && vName != snapVar {
// snap_mode & snap_try_<type> can be empty
// snap_<type> cannot be! and will fail parsing
// below
continue
}
if vName == "snap_mode" {
status = v
} else {
// TODO: use trySnapError here somehow?
if v == "" {
return nil, nil, "", fmt.Errorf("cannot get name and revision of %s (%s): boot variable unset", s16.errName, vName)
}
snap, err := snap.ParsePlaceInfoFromSnapFileName(v)
if err != nil {
return nil, nil, "", fmt.Errorf("cannot get name and revision of %s (%s): %v", s16.errName, vName, err)
}
snaps[vName] = snap
}
}
return snaps[snapVar], snaps[trySnapVar], status, nil
}
type bootStateUpdate16 struct {
bl bootloader.Bootloader
env map[string]string
toCommit map[string]string
}
func newBootStateUpdate16(u bootStateUpdate, names ...string) (*bootStateUpdate16, error) {
if u != nil {
u16, ok := u.(*bootStateUpdate16)
if !ok {
return nil, fmt.Errorf("internal error: threading unexpected boot state update on UC16/18: %T", u)
}
return u16, nil
}
bl, err := bootloader.Find("", nil)
if err != nil {
return nil, err
}
m, err := bl.GetBootVars(names...)
if err != nil {
return nil, err
}
return &bootStateUpdate16{bl: bl, env: m, toCommit: make(map[string]string)}, nil
}
func (u16 *bootStateUpdate16) commit() error {
if len(u16.toCommit) == 0 {
// nothing to do
return nil
}
env := u16.env
// TODO: we could just SetBootVars(toCommit) but it's not
// fully backward compatible with the preexisting behavior
for k, v := range u16.toCommit {
env[k] = v
}
return u16.bl.SetBootVars(env)
}
func (s16 *bootState16) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) {
u16, err := newBootStateUpdate16(update, "snap_mode", "snap_try_core", "snap_try_kernel")
if err != nil {
return nil, err
}
env := u16.env
toCommit := u16.toCommit
tryBootVar := fmt.Sprintf("snap_try_%s", s16.varSuffix)
bootVar := fmt.Sprintf("snap_%s", s16.varSuffix)
// snap_mode goes from "" -> "try" -> "trying" -> ""
// so if we are not in "trying" mode, nothing to do here
if env["snap_mode"] != TryingStatus {
// clean the try var anyways in case it was leftover from a rollback,
// etc.
toCommit[tryBootVar] = ""
return u16, nil
}
// update the boot vars
if env[tryBootVar] != "" {
toCommit[bootVar] = env[tryBootVar]
toCommit[tryBootVar] = ""
}
toCommit["snap_mode"] = DefaultStatus
return u16, nil
}
func (s16 *bootState16) setNext(s snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) {
nextBoot := s.Filename()
nextBootVar := fmt.Sprintf("snap_try_%s", s16.varSuffix)
goodBootVar := fmt.Sprintf("snap_%s", s16.varSuffix)
u16, err := newBootStateUpdate16(nil, "snap_mode", goodBootVar)
if err != nil {
return false, nil, err
}
env := u16.env
toCommit := u16.toCommit
snapMode := TryStatus
rebootRequired = true
if env[goodBootVar] == nextBoot {
// If we were in anything but default ("") mode before
// and switched to the good core/kernel again, make
// sure to clean the snap_mode here. This also
// mitigates https://forum.snapcraft.io/t/5253
if env["snap_mode"] == DefaultStatus {
// already clean
return false, nil, nil
}
// clean
snapMode = DefaultStatus
nextBoot = ""
rebootRequired = false
}
toCommit["snap_mode"] = snapMode
toCommit[nextBootVar] = nextBoot
return rebootRequired, u16, nil
}