github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/hookstate/hooks.go (about) 1 /* 2 * Copyright (C) 2017 Canonical Ltd 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 3 as 6 * published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 * 16 */ 17 18 package hookstate 19 20 import ( 21 "fmt" 22 "regexp" 23 "sort" 24 "time" 25 26 "github.com/snapcore/snapd/cmd/snaplock" 27 "github.com/snapcore/snapd/cmd/snaplock/runinhibit" 28 "github.com/snapcore/snapd/features" 29 "github.com/snapcore/snapd/i18n" 30 "github.com/snapcore/snapd/osutil" 31 "github.com/snapcore/snapd/overlord/configstate/config" 32 "github.com/snapcore/snapd/overlord/snapstate" 33 "github.com/snapcore/snapd/overlord/state" 34 ) 35 36 func init() { 37 snapstate.SetupInstallHook = SetupInstallHook 38 snapstate.SetupPreRefreshHook = SetupPreRefreshHook 39 snapstate.SetupPostRefreshHook = SetupPostRefreshHook 40 snapstate.SetupRemoveHook = SetupRemoveHook 41 snapstate.SetupGateAutoRefreshHook = SetupGateAutoRefreshHook 42 } 43 44 func SetupInstallHook(st *state.State, snapName string) *state.Task { 45 hooksup := &HookSetup{ 46 Snap: snapName, 47 Hook: "install", 48 Optional: true, 49 } 50 51 summary := fmt.Sprintf(i18n.G("Run install hook of %q snap if present"), hooksup.Snap) 52 task := HookTask(st, summary, hooksup, nil) 53 54 return task 55 } 56 57 func SetupPostRefreshHook(st *state.State, snapName string) *state.Task { 58 hooksup := &HookSetup{ 59 Snap: snapName, 60 Hook: "post-refresh", 61 Optional: true, 62 } 63 64 summary := fmt.Sprintf(i18n.G("Run post-refresh hook of %q snap if present"), hooksup.Snap) 65 return HookTask(st, summary, hooksup, nil) 66 } 67 68 func SetupPreRefreshHook(st *state.State, snapName string) *state.Task { 69 hooksup := &HookSetup{ 70 Snap: snapName, 71 Hook: "pre-refresh", 72 Optional: true, 73 } 74 75 summary := fmt.Sprintf(i18n.G("Run pre-refresh hook of %q snap if present"), hooksup.Snap) 76 task := HookTask(st, summary, hooksup, nil) 77 78 return task 79 } 80 81 type gateAutoRefreshHookHandler struct { 82 context *Context 83 refreshAppAwareness bool 84 } 85 86 func (h *gateAutoRefreshHookHandler) Before() error { 87 st := h.context.State() 88 st.Lock() 89 defer st.Unlock() 90 91 tr := config.NewTransaction(st) 92 experimentalRefreshAppAwareness, err := features.Flag(tr, features.RefreshAppAwareness) 93 if err != nil && !config.IsNoOption(err) { 94 return err 95 } 96 if !experimentalRefreshAppAwareness { 97 return nil 98 } 99 100 h.refreshAppAwareness = true 101 102 snapName := h.context.InstanceName() 103 104 // obtain snap lock before manipulating runinhibit lock. 105 lock, err := snaplock.OpenLock(snapName) 106 if err != nil { 107 return err 108 } 109 if err := lock.Lock(); err != nil { 110 return err 111 } 112 defer lock.Unlock() 113 114 if err := runinhibit.LockWithHint(snapName, runinhibit.HintInhibitedGateRefresh); err != nil { 115 return err 116 } 117 118 return nil 119 } 120 121 func (h *gateAutoRefreshHookHandler) Done() (err error) { 122 ctx := h.context 123 st := ctx.State() 124 ctx.Lock() 125 defer ctx.Unlock() 126 127 snapName := h.context.InstanceName() 128 129 var action snapstate.GateAutoRefreshAction 130 a := ctx.Cached("action") 131 132 // obtain snap lock before manipulating runinhibit lock. 133 var lock *osutil.FileLock 134 if h.refreshAppAwareness { 135 lock, err = snaplock.OpenLock(snapName) 136 if err != nil { 137 return err 138 } 139 if err := lock.Lock(); err != nil { 140 return err 141 } 142 defer lock.Unlock() 143 } 144 145 // default behavior if action is not set 146 if a == nil { 147 // action is not set if the gate-auto-refresh hook exits 0 without 148 // invoking --hold/--proceed; this means proceed (except for respecting 149 // refresh inhibit). 150 if h.refreshAppAwareness { 151 if err := runinhibit.Unlock(snapName); err != nil { 152 return fmt.Errorf("cannot unlock inhibit lock for snap %s: %v", snapName, err) 153 } 154 } 155 return snapstate.ProceedWithRefresh(st, snapName) 156 } else { 157 var ok bool 158 action, ok = a.(snapstate.GateAutoRefreshAction) 159 if !ok { 160 return fmt.Errorf("internal error: unexpected action type %T", a) 161 } 162 } 163 164 // action is set if snapctl refresh --hold/--proceed was called from the hook. 165 switch action { 166 case snapstate.GateAutoRefreshHold: 167 // for action=hold the ctlcmd calls HoldRefresh; only unlock runinhibit. 168 if h.refreshAppAwareness { 169 if err := runinhibit.Unlock(snapName); err != nil { 170 return fmt.Errorf("cannot unlock inhibit lock of snap %s: %v", snapName, err) 171 } 172 } 173 case snapstate.GateAutoRefreshProceed: 174 // for action=proceed the ctlcmd doesn't call ProceedWithRefresh 175 // immediately, do it here. 176 if err := snapstate.ProceedWithRefresh(st, snapName); err != nil { 177 return err 178 } 179 if h.refreshAppAwareness { 180 // we have HintInhibitedGateRefresh lock already when running the hook, 181 // change it to HintInhibitedForRefresh. 182 if err := runinhibit.LockWithHint(snapName, runinhibit.HintInhibitedForRefresh); err != nil { 183 return fmt.Errorf("cannot set inhibit lock for snap %s: %v", snapName, err) 184 } 185 } 186 default: 187 return fmt.Errorf("internal error: unexpected action %v", action) 188 } 189 190 return nil 191 } 192 193 // Error handles gate-auto-refresh hook failure; it assumes hold. 194 func (h *gateAutoRefreshHookHandler) Error(hookErr error) (ignoreHookErr bool, err error) { 195 ctx := h.context 196 st := h.context.State() 197 ctx.Lock() 198 defer ctx.Unlock() 199 200 snapName := h.context.InstanceName() 201 202 var lock *osutil.FileLock 203 204 // the refresh is going to be held, release runinhibit lock. 205 if h.refreshAppAwareness { 206 // obtain snap lock before manipulating runinhibit lock. 207 lock, err = snaplock.OpenLock(snapName) 208 if err != nil { 209 return false, err 210 } 211 if err := lock.Lock(); err != nil { 212 return false, err 213 } 214 defer lock.Unlock() 215 216 if err := runinhibit.Unlock(snapName); err != nil { 217 return false, fmt.Errorf("cannot release inhibit lock of snap %s: %v", snapName, err) 218 } 219 } 220 221 if a := ctx.Cached("action"); a != nil { 222 action, ok := a.(snapstate.GateAutoRefreshAction) 223 if !ok { 224 return false, fmt.Errorf("internal error: unexpected action type %T", a) 225 } 226 // nothing to do if the hook already requested hold. 227 if action == snapstate.GateAutoRefreshHold { 228 ctx.Errorf("ignoring hook error: %v", hookErr) 229 // tell hook manager to ignore hook error. 230 return true, nil 231 } 232 } 233 234 // the hook didn't request --hold, or it was --proceed. since the hook 235 // errored out, assume hold. 236 237 var affecting []string 238 if err := ctx.Get("affecting-snaps", &affecting); err != nil { 239 return false, fmt.Errorf("internal error: cannot get affecting-snaps") 240 } 241 242 // no duration specified, use maximum allowed for this gating snap. 243 var holdDuration time.Duration 244 if err := snapstate.HoldRefresh(st, snapName, holdDuration, affecting...); err != nil { 245 // log the original hook error as we either ignore it or error out from 246 // this handler, in both cases hookErr won't be logged by hook manager. 247 h.context.Errorf("error: %v (while handling previous hook error: %v)", err, hookErr) 248 if _, ok := err.(*snapstate.HoldError); ok { 249 // TODO: consider delaying for another hour. 250 return true, nil 251 } 252 // anything other than HoldError becomes an error of the handler. 253 return false, err 254 } 255 256 // TODO: consider assigning a special health state for the snap. 257 258 ctx.Errorf("ignoring hook error: %v", hookErr) 259 // tell hook manager to ignore hook error. 260 return true, nil 261 } 262 263 func NewGateAutoRefreshHookHandler(context *Context) *gateAutoRefreshHookHandler { 264 return &gateAutoRefreshHookHandler{ 265 context: context, 266 } 267 } 268 269 func SetupGateAutoRefreshHook(st *state.State, snapName string, base, restart bool, affectingSnaps map[string]bool) *state.Task { 270 hookSup := &HookSetup{ 271 Snap: snapName, 272 Hook: "gate-auto-refresh", 273 Optional: true, 274 } 275 affecting := make([]string, 0, len(affectingSnaps)) 276 for sn := range affectingSnaps { 277 affecting = append(affecting, sn) 278 } 279 sort.Strings(affecting) 280 summary := fmt.Sprintf(i18n.G("Run hook %s of snap %q"), hookSup.Hook, hookSup.Snap) 281 hookCtx := map[string]interface{}{ 282 "base": base, 283 "restart": restart, 284 "affecting-snaps": affecting, 285 } 286 task := HookTask(st, summary, hookSup, hookCtx) 287 return task 288 } 289 290 type snapHookHandler struct { 291 } 292 293 func (h *snapHookHandler) Before() error { 294 return nil 295 } 296 297 func (h *snapHookHandler) Done() error { 298 return nil 299 } 300 301 func (h *snapHookHandler) Error(err error) (bool, error) { 302 return false, nil 303 } 304 305 func SetupRemoveHook(st *state.State, snapName string) *state.Task { 306 hooksup := &HookSetup{ 307 Snap: snapName, 308 Hook: "remove", 309 Optional: true, 310 IgnoreError: true, 311 } 312 313 summary := fmt.Sprintf(i18n.G("Run remove hook of %q snap if present"), hooksup.Snap) 314 task := HookTask(st, summary, hooksup, nil) 315 316 return task 317 } 318 319 func setupHooks(hookMgr *HookManager) { 320 handlerGenerator := func(context *Context) Handler { 321 return &snapHookHandler{} 322 } 323 gateAutoRefreshHandlerGenerator := func(context *Context) Handler { 324 return NewGateAutoRefreshHookHandler(context) 325 } 326 327 hookMgr.Register(regexp.MustCompile("^install$"), handlerGenerator) 328 hookMgr.Register(regexp.MustCompile("^post-refresh$"), handlerGenerator) 329 hookMgr.Register(regexp.MustCompile("^pre-refresh$"), handlerGenerator) 330 hookMgr.Register(regexp.MustCompile("^remove$"), handlerGenerator) 331 hookMgr.Register(regexp.MustCompile("^gate-auto-refresh$"), gateAutoRefreshHandlerGenerator) 332 }