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