gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/patch/patch4.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 patch 21 22 import ( 23 "encoding/json" 24 "errors" 25 "fmt" 26 27 "github.com/snapcore/snapd/overlord/state" 28 "github.com/snapcore/snapd/snap" 29 ) 30 31 func init() { 32 patches[4] = []PatchFunc{patch4} 33 } 34 35 type patch4Flags int 36 37 const ( 38 patch4FlagDevMode = 1 << iota 39 patch4FlagTryMode 40 patch4FlagJailMode 41 ) 42 43 const patch4FlagRevert = patch4Flags(0x40000000) 44 45 func (f patch4Flags) DevMode() bool { 46 return f&patch4FlagDevMode != 0 47 } 48 49 func (f patch4Flags) TryMode() bool { 50 return f&patch4FlagTryMode != 0 51 } 52 53 func (f patch4Flags) JailMode() bool { 54 return f&patch4FlagJailMode != 0 55 } 56 57 func (f patch4Flags) Revert() bool { 58 return f&patch4FlagRevert != 0 59 } 60 61 type patch4DownloadInfo struct { 62 AnonDownloadURL string `json:"anon-download-url,omitempty"` 63 DownloadURL string `json:"download-url,omitempty"` 64 65 Size int64 `json:"size,omitempty"` 66 Sha3_384 string `json:"sha3-384,omitempty"` 67 } 68 69 type patch4SideInfo struct { 70 RealName string `yaml:"name,omitempty" json:"name,omitempty"` 71 SnapID string `yaml:"snap-id" json:"snap-id"` 72 Revision snap.Revision `yaml:"revision" json:"revision"` 73 Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` 74 DeveloperID string `yaml:"developer-id,omitempty" json:"developer-id,omitempty"` 75 Developer string `yaml:"developer,omitempty" json:"developer,omitempty"` 76 EditedSummary string `yaml:"summary,omitempty" json:"summary,omitempty"` 77 EditedDescription string `yaml:"description,omitempty" json:"description,omitempty"` 78 Private bool `yaml:"private,omitempty" json:"private,omitempty"` 79 } 80 81 type patch4SnapSetup struct { 82 Channel string `json:"channel,omitempty"` 83 UserID int `json:"user-id,omitempty"` 84 Flags patch4Flags `json:"flags,omitempty"` 85 SnapPath string `json:"snap-path,omitempty"` 86 DownloadInfo *patch4DownloadInfo `json:"download-info,omitempty"` 87 SideInfo *patch4SideInfo `json:"side-info,omitempty"` 88 } 89 90 func (snapsup *patch4SnapSetup) Name() string { 91 if snapsup.SideInfo.RealName == "" { 92 panic("SnapSetup.SideInfo.RealName not set") 93 } 94 return snapsup.SideInfo.RealName 95 } 96 97 func (snapsup *patch4SnapSetup) Revision() snap.Revision { 98 return snapsup.SideInfo.Revision 99 } 100 101 type patch4SnapState struct { 102 SnapType string `json:"type"` // Use Type and SetType 103 Sequence []*patch4SideInfo `json:"sequence"` 104 Active bool `json:"active,omitempty"` 105 Current snap.Revision `json:"current"` 106 Channel string `json:"channel,omitempty"` 107 Flags patch4Flags `json:"flags,omitempty"` 108 } 109 110 func (snapst *patch4SnapState) LastIndex(revision snap.Revision) int { 111 for i := len(snapst.Sequence) - 1; i >= 0; i-- { 112 if snapst.Sequence[i].Revision == revision { 113 return i 114 } 115 } 116 return -1 117 } 118 119 type patch4T struct{} // for namespacing of the helpers 120 121 func (p4 patch4T) taskSnapSetup(task *state.Task) (*patch4SnapSetup, error) { 122 var snapsup patch4SnapSetup 123 124 switch err := p4.getMaybe(task, "snap-setup", &snapsup); err { 125 case state.ErrNoState: 126 // continue below 127 case nil: 128 return &snapsup, nil 129 default: 130 return nil, err 131 } 132 133 var id string 134 if err := p4.get(task, "snap-setup-task", &id); err != nil { 135 return nil, err 136 } 137 138 if err := p4.get(task.State().Task(id), "snap-setup", &snapsup); err != nil { 139 return nil, err 140 } 141 142 return &snapsup, nil 143 } 144 145 var errNoSnapState = errors.New("no snap state") 146 147 func (p4 patch4T) snapSetupAndState(task *state.Task) (*patch4SnapSetup, *patch4SnapState, error) { 148 var snapst patch4SnapState 149 150 snapsup, err := p4.taskSnapSetup(task) 151 if err != nil { 152 return nil, nil, err 153 } 154 155 var snaps map[string]*json.RawMessage 156 err = task.State().Get("snaps", &snaps) 157 if err != nil { 158 return nil, nil, errNoSnapState 159 } 160 raw, ok := snaps[snapsup.Name()] 161 if !ok { 162 return nil, nil, errNoSnapState 163 } 164 err = json.Unmarshal([]byte(*raw), &snapst) 165 if err != nil { 166 return nil, nil, fmt.Errorf("cannot get state for snap %q: %v", snapsup.Name(), err) 167 } 168 169 return snapsup, &snapst, err 170 } 171 172 // getMaybe calls task.Get and wraps any non-ErrNoState error in an informative message 173 func (p4 patch4T) getMaybe(task *state.Task, key string, value interface{}) error { 174 return p4.gget(task, key, true, value) 175 } 176 177 // get calls task.Get and wraps any error in an informative message 178 func (p4 patch4T) get(task *state.Task, key string, value interface{}) error { 179 return p4.gget(task, key, false, value) 180 } 181 182 // gget does the actual work of get and getMaybe 183 func (patch4T) gget(task *state.Task, key string, passThroughMissing bool, value interface{}) error { 184 err := task.Get(key, value) 185 if err == nil || (passThroughMissing && err == state.ErrNoState) { 186 return err 187 } 188 change := task.Change() 189 190 return fmt.Errorf("cannot get %q from task %s (%s) of change %s (%s): %v", 191 key, task.ID(), task.Kind(), change.ID(), change.Kind(), err) 192 } 193 194 func (p4 patch4T) addCleanup(task *state.Task) error { 195 // NOTE we could check for the status of the change itself, but 196 // copy-snap-data is the one creating the trash, so if it's run there's 197 // no sense in fiddling with the change. 198 if task.Status().Ready() { 199 return nil 200 } 201 202 snapsup, err := p4.taskSnapSetup(task) 203 if err != nil { 204 return err 205 } 206 207 var tid string 208 if err := p4.get(task, "snap-setup-task", &tid); err != nil { 209 return err 210 } 211 212 change := task.Change() 213 revisionStr := "" 214 if snapsup.SideInfo != nil { 215 revisionStr = fmt.Sprintf(" (%s)", snapsup.Revision()) 216 } 217 218 tasks := change.Tasks() 219 last := tasks[len(tasks)-1] 220 newTask := task.State().NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", snapsup.Name(), revisionStr)) 221 newTask.Set("snap-setup-task", tid) 222 newTask.WaitFor(last) 223 change.AddTask(newTask) 224 225 return nil 226 } 227 228 func (p4 patch4T) mangle(task *state.Task) error { 229 snapsup, snapst, err := p4.snapSetupAndState(task) 230 if err == errNoSnapState { 231 change := task.Change() 232 if change.Kind() != "install-snap" { 233 return fmt.Errorf("cannot get snap state for task %s (%s) of change %s (%s != install-snap)", task.ID(), task.Kind(), change.ID(), change.Kind()) 234 } 235 // we expect pending/in-progress install changes 236 // possibly not to have reached link-sanp yet and so 237 // have no snap state yet, nothing to do 238 return nil 239 } 240 if err != nil { 241 return err 242 } 243 244 var hadCandidate bool 245 if err := p4.getMaybe(task, "had-candidate", &hadCandidate); err != nil && err != state.ErrNoState { 246 return err 247 } 248 249 if hadCandidate { 250 change := task.Change() 251 if change.Kind() != "revert-snap" { 252 return fmt.Errorf("had-candidate true for task %s (%s) of non-revert change %s (%s)", 253 task.ID(), task.Kind(), change.ID(), change.Kind()) 254 } 255 } 256 257 task.Clear("had-candidate") 258 259 task.Set("old-candidate-index", snapst.LastIndex(snapsup.SideInfo.Revision)) 260 261 return nil 262 } 263 264 func (p4 patch4T) addRevertFlag(task *state.Task) error { 265 var snapsup patch4SnapSetup 266 err := p4.getMaybe(task, "snap-setup", &snapsup) 267 switch err { 268 case nil: 269 snapsup.Flags |= patch4FlagRevert 270 271 // save it back 272 task.Set("snap-setup", &snapsup) 273 return nil 274 case state.ErrNoState: 275 return nil 276 default: 277 return err 278 } 279 } 280 281 // patch4: 282 // - add Revert flag to in-progress revert-snap changes 283 // - move from had-candidate to old-candidate-index in link-snap tasks 284 // - add cleanup task to in-progress changes that have a copy-snap-data task 285 func patch4(s *state.State) error { 286 p4 := patch4T{} 287 for _, change := range s.Changes() { 288 // change is full done, take it easy 289 if change.Status().Ready() { 290 continue 291 } 292 293 if change.Kind() != "revert-snap" { 294 continue 295 } 296 for _, task := range change.Tasks() { 297 if err := p4.addRevertFlag(task); err != nil { 298 return err 299 } 300 } 301 } 302 303 for _, task := range s.Tasks() { 304 // change is full done, take it easy 305 if task.Change().Status().Ready() { 306 continue 307 } 308 309 switch task.Kind() { 310 case "link-snap": 311 if err := p4.mangle(task); err != nil { 312 return err 313 } 314 case "copy-snap-data": 315 if err := p4.addCleanup(task); err != nil { 316 return err 317 } 318 } 319 } 320 321 return nil 322 }