github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/hookstate/context.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 hookstate 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "sync" 27 "sync/atomic" 28 "time" 29 30 "github.com/snapcore/snapd/jsonutil" 31 "github.com/snapcore/snapd/overlord/state" 32 "github.com/snapcore/snapd/randutil" 33 "github.com/snapcore/snapd/snap" 34 ) 35 36 // Context represents the context under which the snap is calling back into snapd. 37 // It is associated with a task when the callback is happening from within a hook, 38 // or otherwise considered an ephemeral context in that its associated data will 39 // be discarded once that individual call is finished. 40 type Context struct { 41 task *state.Task 42 state *state.State 43 setup *HookSetup 44 id string 45 handler Handler 46 47 cache map[interface{}]interface{} 48 onDone []func() error 49 50 mutex sync.Mutex 51 mutexChecker int32 52 } 53 54 // NewContext returns a new context associated with the provided task or 55 // an ephemeral context if task is nil. 56 // 57 // A random ID is generated if contextID is empty. 58 func NewContext(task *state.Task, state *state.State, setup *HookSetup, handler Handler, contextID string) (*Context, error) { 59 if contextID == "" { 60 var err error 61 contextID, err = randutil.CryptoToken(32) 62 if err != nil { 63 return nil, err 64 } 65 } 66 67 return &Context{ 68 task: task, 69 state: state, 70 setup: setup, 71 id: contextID, 72 handler: handler, 73 cache: make(map[interface{}]interface{}), 74 }, nil 75 } 76 77 // InstanceName returns the name of the snap instance containing the hook. 78 func (c *Context) InstanceName() string { 79 return c.setup.Snap 80 } 81 82 // SnapRevision returns the revision of the snap containing the hook. 83 func (c *Context) SnapRevision() snap.Revision { 84 return c.setup.Revision 85 } 86 87 // Task returns the task associated with the hook or (nil, false) if the context is ephemeral 88 // and task is not available. 89 func (c *Context) Task() (*state.Task, bool) { 90 return c.task, c.task != nil 91 } 92 93 // HookName returns the name of the hook in this context. 94 func (c *Context) HookName() string { 95 return c.setup.Hook 96 } 97 98 // Timeout returns the maximum time this hook can run 99 func (c *Context) Timeout() time.Duration { 100 return c.setup.Timeout 101 } 102 103 // ID returns the ID of the context. 104 func (c *Context) ID() string { 105 return c.id 106 } 107 108 // Handler returns the handler for this context 109 func (c *Context) Handler() Handler { 110 return c.handler 111 } 112 113 // Lock acquires the lock for this context (required for Set/Get, Cache/Cached), 114 // and OnDone/Done). 115 func (c *Context) Lock() { 116 c.mutex.Lock() 117 c.state.Lock() 118 atomic.AddInt32(&c.mutexChecker, 1) 119 } 120 121 // Unlock releases the lock for this context. 122 func (c *Context) Unlock() { 123 atomic.AddInt32(&c.mutexChecker, -1) 124 c.state.Unlock() 125 c.mutex.Unlock() 126 } 127 128 func (c *Context) reading() { 129 if atomic.LoadInt32(&c.mutexChecker) != 1 { 130 panic("internal error: accessing context without lock") 131 } 132 } 133 134 func (c *Context) writing() { 135 if atomic.LoadInt32(&c.mutexChecker) != 1 { 136 panic("internal error: accessing context without lock") 137 } 138 } 139 140 // Set associates value with key. The provided value must properly marshal and 141 // unmarshal with encoding/json. Note that the context needs to be locked and 142 // unlocked by the caller. 143 func (c *Context) Set(key string, value interface{}) { 144 c.writing() 145 146 var data map[string]*json.RawMessage 147 if c.IsEphemeral() { 148 data, _ = c.cache["ephemeral-context"].(map[string]*json.RawMessage) 149 } else { 150 if err := c.task.Get("hook-context", &data); err != nil && err != state.ErrNoState { 151 panic(fmt.Sprintf("internal error: cannot unmarshal context: %v", err)) 152 } 153 } 154 if data == nil { 155 data = make(map[string]*json.RawMessage) 156 } 157 158 marshalledValue, err := json.Marshal(value) 159 if err != nil { 160 panic(fmt.Sprintf("internal error: cannot marshal context value for %q: %s", key, err)) 161 } 162 raw := json.RawMessage(marshalledValue) 163 data[key] = &raw 164 165 if c.IsEphemeral() { 166 c.cache["ephemeral-context"] = data 167 } else { 168 c.task.Set("hook-context", data) 169 } 170 } 171 172 // Get unmarshals the stored value associated with the provided key into the 173 // value parameter. Note that the context needs to be locked/unlocked by the 174 // caller. 175 func (c *Context) Get(key string, value interface{}) error { 176 c.reading() 177 178 var data map[string]*json.RawMessage 179 if c.IsEphemeral() { 180 data, _ = c.cache["ephemeral-context"].(map[string]*json.RawMessage) 181 if data == nil { 182 return state.ErrNoState 183 } 184 } else { 185 if err := c.task.Get("hook-context", &data); err != nil { 186 return err 187 } 188 } 189 190 raw, ok := data[key] 191 if !ok { 192 return state.ErrNoState 193 } 194 195 err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &value) 196 if err != nil { 197 return fmt.Errorf("cannot unmarshal context value for %q: %s", key, err) 198 } 199 200 return nil 201 } 202 203 // State returns the state contained within the context 204 func (c *Context) State() *state.State { 205 return c.state 206 } 207 208 // Cached returns the cached value associated with the provided key. It returns 209 // nil if there is no entry for key. Note that the context needs to be locked 210 // and unlocked by the caller. 211 func (c *Context) Cached(key interface{}) interface{} { 212 c.reading() 213 214 return c.cache[key] 215 } 216 217 // Cache associates value with key. The cached value is not persisted. Note that 218 // the context needs to be locked/unlocked by the caller. 219 func (c *Context) Cache(key, value interface{}) { 220 c.writing() 221 222 c.cache[key] = value 223 } 224 225 // OnDone requests the provided function to be run once the context knows it's 226 // complete. This can be called multiple times; each function will be called in 227 // the order in which they were added. Note that the context needs to be locked 228 // and unlocked by the caller. 229 func (c *Context) OnDone(f func() error) { 230 c.writing() 231 232 c.onDone = append(c.onDone, f) 233 } 234 235 // Done is called to notify the context that its hook has exited successfully. 236 // It will call all of the functions added in OnDone (even if one of them 237 // returns an error) and will return the first error encountered. Note that the 238 // context needs to be locked/unlocked by the caller. 239 func (c *Context) Done() error { 240 c.reading() 241 242 var firstErr error 243 for _, f := range c.onDone { 244 if err := f(); err != nil && firstErr == nil { 245 firstErr = err 246 } 247 } 248 249 return firstErr 250 } 251 252 func (c *Context) IsEphemeral() bool { 253 return c.task == nil 254 }