github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/devicestate/handlers.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 /* 3 * Copyright (C) 2016-2020 Canonical Ltd 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 3 as 7 * published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 */ 18 19 package devicestate 20 21 import ( 22 "fmt" 23 "os/exec" 24 "time" 25 26 "gopkg.in/tomb.v2" 27 28 "github.com/snapcore/snapd/interfaces" 29 "github.com/snapcore/snapd/logger" 30 "github.com/snapcore/snapd/overlord/snapstate" 31 "github.com/snapcore/snapd/overlord/state" 32 ) 33 34 func (m *DeviceManager) doMarkPreseeded(t *state.Task, _ *tomb.Tomb) error { 35 st := t.State() 36 st.Lock() 37 defer st.Unlock() 38 39 snaps, err := snapstate.All(st) 40 if err != nil { 41 return err 42 } 43 44 systemKey, err := interfaces.RecordedSystemKey() 45 if err != nil { 46 return fmt.Errorf("cannot get recorded system key: %v", err) 47 } 48 49 if m.preseed { 50 var preseeded bool 51 // the "preseeded" flag on this task is set to allow skipping the logic 52 // below in case this handler is retried in preseeding mode due to an 53 // EnsureBefore(0) done somewhere else. 54 // XXX: we should probably drop the flag from the task now that we have 55 // one on the state. 56 if err := t.Get("preseeded", &preseeded); err != nil && err != state.ErrNoState { 57 return err 58 } 59 if !preseeded { 60 preseeded = true 61 t.Set("preseeded", preseeded) 62 // unmount all snaps 63 // TODO: move to snapstate.UnmountAllSnaps. 64 for _, snapSt := range snaps { 65 info, err := snapSt.CurrentInfo() 66 if err != nil { 67 return err 68 } 69 logger.Debugf("unmounting snap %s at %s", info.InstanceName(), info.MountDir()) 70 if _, err := exec.Command("umount", "-d", "-l", info.MountDir()).CombinedOutput(); err != nil { 71 return err 72 } 73 } 74 75 st.Set("preseeded", preseeded) 76 st.Set("preseed-system-key", systemKey) 77 st.Set("preseed-time", timeNow()) 78 79 // do not mark this task done as this makes it racy against taskrunner tear down (the next task 80 // could start). Let this task finish after snapd restart when preseed mode is off. 81 st.RequestRestart(state.StopDaemon) 82 } 83 84 return &state.Retry{Reason: "mark-preseeded will be marked done when snapd is executed in normal mode"} 85 } 86 87 // normal snapd run after snapd restart (not in preseed mode anymore) 88 89 st.Set("seed-restart-system-key", systemKey) 90 if err := m.setTimeOnce("seed-restart-time", startTime); err != nil { 91 return err 92 } 93 94 return nil 95 } 96 97 type seededSystem struct { 98 // System carries the recovery system label that was used to seed the 99 // current system 100 System string `json:"system"` 101 Model string `json:"model"` 102 // BrandID is the brand account ID 103 BrandID string `json:"brand-id"` 104 // Revision of the model assertion 105 Revision int `json:"revision"` 106 // Timestamp of model assertion 107 Timestamp time.Time `json:"timestamp"` 108 // SeedTime holds the timestamp when the system was seeded 109 SeedTime time.Time `json:"seed-time"` 110 } 111 112 func (m *DeviceManager) recordSeededSystem(st *state.State, whatSeeded *seededSystem) error { 113 var seeded []seededSystem 114 if err := st.Get("seeded-systems", &seeded); err != nil && err != state.ErrNoState { 115 return err 116 } 117 seeded = append([]seededSystem{*whatSeeded}, seeded...) 118 st.Set("seeded-systems", seeded) 119 return nil 120 } 121 122 func (m *DeviceManager) doMarkSeeded(t *state.Task, _ *tomb.Tomb) error { 123 st := t.State() 124 st.Lock() 125 defer st.Unlock() 126 127 if m.preseed { 128 return fmt.Errorf("internal error: mark-seeded task not expected in pre-seeding mode") 129 } 130 131 deviceCtx, err := DeviceCtx(st, t, nil) 132 if err != nil { 133 return fmt.Errorf("cannot get device context: %v", err) 134 } 135 136 if deviceCtx.HasModeenv() && deviceCtx.RunMode() { 137 modeEnv, err := maybeReadModeenv() 138 if err != nil { 139 return err 140 } 141 if modeEnv == nil { 142 return fmt.Errorf("missing modeenv, cannot proceed") 143 } 144 // unset recovery_system because that is only needed during install mode 145 modeEnv.RecoverySystem = "" 146 err = modeEnv.Write() 147 if err != nil { 148 return err 149 } 150 } 151 152 now := time.Now() 153 var whatSeeded *seededSystem 154 if err := t.Get("seed-system", &whatSeeded); err != nil && err != state.ErrNoState { 155 return err 156 } 157 if whatSeeded != nil && deviceCtx.RunMode() { 158 // record what seeded in the state only when in run mode 159 160 whatSeeded.SeedTime = now 161 // TODO:UC20 what about remodels? 162 if err := m.recordSeededSystem(st, whatSeeded); err != nil { 163 return fmt.Errorf("cannot record the seeded system: %v", err) 164 } 165 } 166 st.Set("seed-time", now) 167 st.Set("seeded", true) 168 // avoid possibly recording the same system multiple times etc. 169 t.SetStatus(state.DoneStatus) 170 // make sure we setup a fallback model/consider the next phase 171 // (registration) timely 172 st.EnsureBefore(0) 173 return nil 174 }