github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_sideload_n_try.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 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 daemon 21 22 import ( 23 "fmt" 24 "io" 25 "io/ioutil" 26 "mime/multipart" 27 "os" 28 "path/filepath" 29 "strconv" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/asserts/snapasserts" 33 "github.com/snapcore/snapd/client" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/i18n" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/overlord/assertstate" 38 "github.com/snapcore/snapd/overlord/auth" 39 "github.com/snapcore/snapd/overlord/snapstate" 40 "github.com/snapcore/snapd/overlord/state" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snap/snapfile" 43 ) 44 45 const maxReadBuflen = 1024 * 1024 46 47 func sideloadOrTrySnap(c *Command, body io.ReadCloser, boundary string, user *auth.UserState) Response { 48 route := c.d.router.Get(stateChangeCmd.Path) 49 if route == nil { 50 return InternalError("cannot find route for change") 51 } 52 53 // POSTs to sideload snaps must be a multipart/form-data file upload. 54 form, err := multipart.NewReader(body, boundary).ReadForm(maxReadBuflen) 55 if err != nil { 56 return BadRequest("cannot read POST form: %v", err) 57 } 58 59 dangerousOK := isTrue(form, "dangerous") 60 flags, err := modeFlags(isTrue(form, "devmode"), isTrue(form, "jailmode"), isTrue(form, "classic")) 61 if err != nil { 62 return BadRequest(err.Error()) 63 } 64 65 if len(form.Value["action"]) > 0 && form.Value["action"][0] == "try" { 66 if len(form.Value["snap-path"]) == 0 { 67 return BadRequest("need 'snap-path' value in form") 68 } 69 return trySnap(c.d.overlord.State(), form.Value["snap-path"][0], flags) 70 } 71 flags.RemoveSnapPath = true 72 73 flags.Unaliased = isTrue(form, "unaliased") 74 flags.IgnoreRunning = isTrue(form, "ignore-running") 75 76 // find the file for the "snap" form field 77 var snapBody multipart.File 78 var origPath string 79 out: 80 for name, fheaders := range form.File { 81 if name != "snap" { 82 continue 83 } 84 for _, fheader := range fheaders { 85 snapBody, err = fheader.Open() 86 origPath = fheader.Filename 87 if err != nil { 88 return BadRequest(`cannot open uploaded "snap" file: %v`, err) 89 } 90 defer snapBody.Close() 91 92 break out 93 } 94 } 95 defer form.RemoveAll() 96 97 if snapBody == nil { 98 return BadRequest(`cannot find "snap" file field in provided multipart/form-data payload`) 99 } 100 101 // we are in charge of the tempfile life cycle until we hand it off to the change 102 changeTriggered := false 103 // if you change this prefix, look for it in the tests 104 // also see localInstallCleanup in snapstate/snapmgr.go 105 tmpf, err := ioutil.TempFile(dirs.SnapBlobDir, dirs.LocalInstallBlobTempPrefix) 106 if err != nil { 107 return InternalError("cannot create temporary file: %v", err) 108 } 109 110 tempPath := tmpf.Name() 111 112 defer func() { 113 if !changeTriggered { 114 os.Remove(tempPath) 115 } 116 }() 117 118 if _, err := io.Copy(tmpf, snapBody); err != nil { 119 return InternalError("cannot copy request into temporary file: %v", err) 120 } 121 tmpf.Sync() 122 123 if len(form.Value["snap-path"]) > 0 { 124 origPath = form.Value["snap-path"][0] 125 } 126 127 var instanceName string 128 129 if len(form.Value["name"]) > 0 { 130 // caller has specified desired instance name 131 instanceName = form.Value["name"][0] 132 if err := snap.ValidateInstanceName(instanceName); err != nil { 133 return BadRequest(err.Error()) 134 } 135 } 136 137 st := c.d.overlord.State() 138 st.Lock() 139 defer st.Unlock() 140 141 var snapName string 142 var sideInfo *snap.SideInfo 143 144 if !dangerousOK { 145 si, err := snapasserts.DeriveSideInfo(tempPath, assertstate.DB(st)) 146 switch { 147 case err == nil: 148 snapName = si.RealName 149 sideInfo = si 150 case asserts.IsNotFound(err): 151 // with devmode we try to find assertions but it's ok 152 // if they are not there (implies --dangerous) 153 if !isTrue(form, "devmode") { 154 msg := "cannot find signatures with metadata for snap" 155 if origPath != "" { 156 msg = fmt.Sprintf("%s %q", msg, origPath) 157 } 158 return BadRequest(msg) 159 } 160 // TODO: set a warning if devmode 161 default: 162 return BadRequest(err.Error()) 163 } 164 } 165 166 if snapName == "" { 167 // potentially dangerous but dangerous or devmode params were set 168 info, err := unsafeReadSnapInfo(tempPath) 169 if err != nil { 170 return BadRequest("cannot read snap file: %v", err) 171 } 172 snapName = info.SnapName() 173 sideInfo = &snap.SideInfo{RealName: snapName} 174 } 175 176 if instanceName != "" { 177 requestedSnapName := snap.InstanceSnap(instanceName) 178 if requestedSnapName != snapName { 179 return BadRequest(fmt.Sprintf("instance name %q does not match snap name %q", instanceName, snapName)) 180 } 181 } else { 182 instanceName = snapName 183 } 184 185 msg := fmt.Sprintf(i18n.G("Install %q snap from file"), instanceName) 186 if origPath != "" { 187 msg = fmt.Sprintf(i18n.G("Install %q snap from file %q"), instanceName, origPath) 188 } 189 190 tset, _, err := snapstateInstallPath(st, sideInfo, tempPath, instanceName, "", flags) 191 if err != nil { 192 return errToResponse(err, []string{snapName}, InternalError, "cannot install snap file: %v") 193 } 194 195 chg := newChange(st, "install-snap", msg, []*state.TaskSet{tset}, []string{instanceName}) 196 chg.Set("api-data", map[string]string{"snap-name": instanceName}) 197 198 ensureStateSoon(st) 199 200 // only when the unlock succeeds (as opposed to panicing) is the handoff done 201 // but this is good enough 202 changeTriggered = true 203 204 return AsyncResponse(nil, &Meta{Change: chg.ID()}) 205 } 206 207 func isTrue(form *multipart.Form, key string) bool { 208 value := form.Value[key] 209 if len(value) == 0 { 210 return false 211 } 212 b, err := strconv.ParseBool(value[0]) 213 if err != nil { 214 return false 215 } 216 217 return b 218 } 219 220 func trySnap(st *state.State, trydir string, flags snapstate.Flags) Response { 221 st.Lock() 222 defer st.Unlock() 223 224 if !filepath.IsAbs(trydir) { 225 return BadRequest("cannot try %q: need an absolute path", trydir) 226 } 227 if !osutil.IsDirectory(trydir) { 228 return BadRequest("cannot try %q: not a snap directory", trydir) 229 } 230 231 // the developer asked us to do this with a trusted snap dir 232 info, err := unsafeReadSnapInfo(trydir) 233 if _, ok := err.(snap.NotSnapError); ok { 234 return SyncResponse(&resp{ 235 Type: ResponseTypeError, 236 Result: &errorResult{ 237 Message: err.Error(), 238 Kind: client.ErrorKindNotSnap, 239 }, 240 Status: 400, 241 }, nil) 242 } 243 if err != nil { 244 return BadRequest("cannot read snap info for %s: %s", trydir, err) 245 } 246 247 tset, err := snapstateTryPath(st, info.InstanceName(), trydir, flags) 248 if err != nil { 249 return errToResponse(err, []string{info.InstanceName()}, BadRequest, "cannot try %s: %s", trydir) 250 } 251 252 msg := fmt.Sprintf(i18n.G("Try %q snap from %s"), info.InstanceName(), trydir) 253 chg := newChange(st, "try-snap", msg, []*state.TaskSet{tset}, []string{info.InstanceName()}) 254 chg.Set("api-data", map[string]string{"snap-name": info.InstanceName()}) 255 256 ensureStateSoon(st) 257 258 return AsyncResponse(nil, &Meta{Change: chg.ID()}) 259 } 260 261 var unsafeReadSnapInfo = unsafeReadSnapInfoImpl 262 263 func unsafeReadSnapInfoImpl(snapPath string) (*snap.Info, error) { 264 // Condider using DeriveSideInfo before falling back to this! 265 snapf, err := snapfile.Open(snapPath) 266 if err != nil { 267 return nil, err 268 } 269 return snap.ReadInfoFromSnapFile(snapf, nil) 270 }