func (m *InterfaceManager) setupAffectedSnaps(task *state.Task, affectingSnap string, affectedSnaps []string) error { st := task.State() // Setup security of the affected snaps. for _, affectedSnapName := range affectedSnaps { // the snap that triggered the change needs to be skipped if affectedSnapName == affectingSnap { continue } var snapst snapstate.SnapState if err := snapstate.Get(st, affectedSnapName, &snapst); err != nil { return err } affectedSnapInfo, err := snapst.CurrentInfo() if err != nil { return err } snap.AddImplicitSlots(affectedSnapInfo) opts := confinementOptions(snapst.Flags) if err := setupSnapSecurity(task, affectedSnapInfo, opts, m.repo); err != nil { return err } } return nil }
func (m *InterfaceManager) doDisconnect(task *state.Task, _ *tomb.Tomb) error { st := task.State() st.Lock() defer st.Unlock() plugRef, slotRef, err := getPlugAndSlotRefs(task) if err != nil { return err } conns, err := getConns(st) if err != nil { return err } var affectedConns []interfaces.ConnRef if plugRef.Snap != "" && plugRef.Name != "" && slotRef.Snap != "" && slotRef.Name != "" { if err := m.repo.Disconnect(plugRef.Snap, plugRef.Name, slotRef.Snap, slotRef.Name); err != nil { return err } affectedConns = []interfaces.ConnRef{{plugRef, slotRef}} } else if plugRef.Name != "" && slotRef.Snap == "" && slotRef.Name == "" { // NOTE: plugRef.Snap can be either empty or not, Connected handles both affectedConns, err = m.repo.Connected(plugRef.Snap, plugRef.Name) if err != nil { return err } m.repo.DisconnectAll(affectedConns) } else if plugRef.Snap == "" && plugRef.Name == "" && slotRef.Name != "" { // Symmetrically, slotRef.Snap can be either empty or not affectedConns, err = m.repo.Connected(slotRef.Snap, slotRef.Name) if err != nil { return err } m.repo.DisconnectAll(affectedConns) } else { return fmt.Errorf("internal error, unhandled disconnect case plug: %q, slot: %q", plugRef, slotRef) } affectedSnaps := snapNamesFromConns(affectedConns) for _, snapName := range affectedSnaps { var snapst snapstate.SnapState if err := snapstate.Get(st, snapName, &snapst); err != nil { return err } snapInfo, err := snapst.CurrentInfo() if err != nil { return err } opts := confinementOptions(snapst.Flags) if err := setupSnapSecurity(task, snapInfo, opts, m.repo); err != nil { return &state.Retry{} } } for _, conn := range affectedConns { delete(conns, conn.ID()) } setConns(st, conns) return nil }
func (m *SnapManager) doUnlinkCurrentSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() ss, snapst, err := snapSetupAndState(t) if err != nil { return err } oldInfo, err := snapst.CurrentInfo() if err != nil { return err } snapst.Active = false pb := &TaskProgressAdapter{task: t} st.Unlock() // pb itself will ask for locking err = m.backend.UnlinkSnap(oldInfo, pb) st.Lock() if err != nil { return err } // mark as inactive Set(st, ss.Name(), snapst) return nil }
func submitSerialRequest(t *state.Task, serialRequest string, client *http.Client) (*asserts.Serial, error) { st := t.State() st.Unlock() defer st.Lock() resp, err := client.Post(serialRequestURL, asserts.MediaType, bytes.NewBufferString(serialRequest)) if err != nil { return nil, retryErr(t, "cannot deliver device serial request: %v", err) } defer resp.Body.Close() switch resp.StatusCode { case 200, 201: case 202: return nil, errPoll default: return nil, retryErr(t, "cannot deliver device serial request: unexpected status %d", resp.StatusCode) } // decode body with serial assertion dec := asserts.NewDecoder(resp.Body) got, err := dec.Decode() if err != nil { // assume broken i/o return nil, retryErr(t, "cannot read response to request for a serial: %v", err) } serial, ok := got.(*asserts.Serial) if !ok { return nil, fmt.Errorf("cannot use device serial assertion of type %q", got.Type().Name) } return serial, nil }
func (m *DeviceManager) doGenerateDeviceKey(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() device, err := auth.Device(st) if err != nil { return err } if device.KeyID != "" { // nothing to do return nil } keyPair, err := rsa.GenerateKey(rand.Reader, keyLength) if err != nil { return fmt.Errorf("cannot generate device key pair: %v", err) } privKey := asserts.RSAPrivateKey(keyPair) err = m.keypairMgr.Put(privKey) if err != nil { return fmt.Errorf("cannot store device key pair: %v", err) } device.KeyID = privKey.PublicKey().ID() err = auth.SetDevice(st, device) if err != nil { return err } t.SetStatus(state.DoneStatus) return nil }
func (m *SnapManager) cleanupCopySnapData(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() if t.Status() != state.DoneStatus { // it failed return nil } _, snapst, err := snapSetupAndState(t) if err != nil { return err } info, err := snapst.CurrentInfo() if err != nil { return err } m.backend.ClearTrashedData(info) return nil }
func (m *InterfaceManager) autoConnect(task *state.Task, snapName string, blacklist map[string]bool) error { var conns map[string]connState err := task.State().Get("conns", &conns) if err != nil && err != state.ErrNoState { return err } if conns == nil { conns = make(map[string]connState) } // XXX: quick hack, auto-connect everything for _, plug := range m.repo.Plugs(snapName) { if blacklist[plug.Name] { continue } candidates := m.repo.AutoConnectCandidates(snapName, plug.Name) if len(candidates) != 1 { continue } slot := candidates[0] if err := m.repo.Connect(snapName, plug.Name, slot.Snap.Name(), slot.Name); err != nil { task.Logf("cannot auto connect %s:%s to %s:%s: %s", snapName, plug.Name, slot.Snap.Name(), slot.Name, err) } key := fmt.Sprintf("%s:%s %s:%s", snapName, plug.Name, slot.Snap.Name(), slot.Name) conns[key] = connState{Interface: plug.Interface, Auto: true} } task.State().Set("conns", conns) return nil }
func (m *SnapManager) doSetupAliases(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() snapsup, snapst, err := snapSetupAndState(t) if err != nil { return err } snapName := snapsup.Name() curInfo, err := snapst.CurrentInfo() if err != nil { return err } aliasStatuses, err := getAliases(st, snapName) if err != nil && err != state.ErrNoState { return err } var aliases []*backend.Alias for alias, aliasStatus := range aliasStatuses { if aliasStatus == "enabled" { aliasApp := curInfo.Aliases[alias] if aliasApp == nil { // not a known alias anymore, skip continue } aliases = append(aliases, &backend.Alias{ Name: alias, Target: filepath.Base(aliasApp.WrapperPath()), }) } } st.Unlock() defer st.Lock() return m.backend.UpdateAliases(aliases, nil) }
func (m *InterfaceManager) undoSetupProfiles(task *state.Task, tomb *tomb.Tomb) error { st := task.State() st.Lock() defer st.Unlock() ss, err := snapstate.TaskSnapSetup(task) if err != nil { return err } snapName := ss.Name() // Get the name from SnapSetup and use it to find the current SideInfo // about the snap, if there is one. var snapst snapstate.SnapState err = snapstate.Get(st, snapName, &snapst) if err != nil && err != state.ErrNoState { return err } sideInfo := snapst.CurrentSideInfo() if sideInfo == nil { // The snap was not installed before so undo should remove security profiles. return m.removeProfilesForSnap(task, tomb, snapName) } else { // The snap was installed before so undo should setup the old security profiles. snapInfo, err := snap.ReadInfo(snapName, sideInfo) if err != nil { return err } return m.setupProfilesForSnap(task, tomb, snapInfo, snapst.DevMode) } }
func (p4 patch4T) addCleanup(task *state.Task) error { // NOTE we could check for the status of the change itself, but // copy-snap-data is the one creating the trash, so if it's run there's // no sense in fiddling with the change. if task.Status().Ready() { return nil } snapsup, err := p4.taskSnapSetup(task) if err != nil { return err } var tid string if err := p4.get(task, "snap-setup-task", &tid); err != nil { return err } change := task.Change() revisionStr := "" if snapsup.SideInfo != nil { revisionStr = fmt.Sprintf(" (%s)", snapsup.Revision()) } tasks := change.Tasks() last := tasks[len(tasks)-1] newTask := task.State().NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", snapsup.Name(), revisionStr)) newTask.Set("snap-setup-task", tid) newTask.WaitFor(last) change.AddTask(newTask) return nil }
func (m *SnapManager) undoUnlinkCurrentSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() ss, snapst, err := snapSetupAndState(t) if err != nil { return err } oldInfo, err := snapst.CurrentInfo() if err != nil { return err } snapst.Active = true st.Unlock() err = m.backend.LinkSnap(oldInfo) st.Lock() if err != nil { return err } // mark as active again Set(st, ss.Name(), snapst) return nil }
func (m *SnapManager) doPrepareSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() ss, snapst, err := snapSetupAndState(t) st.Unlock() if err != nil { return err } if ss.Revision().Unset() { // Local revisions start at -1 and go down. revision := snapst.LocalRevision() if revision.Unset() || revision.N > 0 { revision = snap.R(-1) } else { revision.N-- } if !revision.Local() { panic("internal error: invalid local revision built: " + revision.String()) } ss.SideInfo.Revision = revision } st.Lock() t.Set("snap-setup", ss) st.Unlock() return nil }
func (m *SnapManager) doClearSnapData(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() ss, snapst, err := snapSetupAndState(t) t.State().Unlock() if err != nil { return err } t.State().Lock() info, err := Info(t.State(), ss.Name(), ss.Revision()) t.State().Unlock() if err != nil { return err } if err = m.backend.RemoveSnapData(info); err != nil { return err } // Only remove data common between versions if this is the last version if len(snapst.Sequence) == 1 { if err = m.backend.RemoveSnapCommonData(info); err != nil { return err } } return nil }
func (m *SnapManager) doUnlinkSnap(t *state.Task, _ *tomb.Tomb) error { // invoked only if snap has a current active revision st := t.State() st.Lock() defer st.Unlock() ss, snapst, err := snapSetupAndState(t) if err != nil { return err } info, err := Info(t.State(), ss.Name(), ss.Revision()) if err != nil { return err } pb := &TaskProgressAdapter{task: t} st.Unlock() // pb itself will ask for locking err = m.backend.UnlinkSnap(info, pb) st.Lock() if err != nil { return err } // mark as inactive snapst.Active = false Set(st, ss.Name(), snapst) return nil }
func (m *DeviceManager) doMarkSeeded(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() st.Set("seeded", true) return nil }
func (m *SnapManager) doDiscardSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() ss, snapst, err := snapSetupAndState(t) st.Unlock() if err != nil { return err } if snapst.Current == ss.Revision() && snapst.Active { return fmt.Errorf("internal error: cannot discard snap %q: still active", ss.Name()) } if len(snapst.Sequence) == 1 { snapst.Sequence = nil snapst.Current = snap.Revision{} } else { newSeq := make([]*snap.SideInfo, 0, len(snapst.Sequence)) for _, si := range snapst.Sequence { if si.Revision == ss.Revision() { // leave out continue } newSeq = append(newSeq, si) } snapst.Sequence = newSeq if snapst.Current == ss.Revision() { snapst.Current = newSeq[len(newSeq)-1].Revision } } pb := &TaskProgressAdapter{task: t} typ, err := snapst.Type() if err != nil { return err } err = m.backend.RemoveSnapFiles(ss.placeInfo(), typ, pb) if err != nil { st.Lock() t.Errorf("cannot remove snap file %q, will retry in 3 mins: %s", ss.Name(), err) st.Unlock() return &state.Retry{After: 3 * time.Minute} } if len(snapst.Sequence) == 0 { err = m.backend.DiscardSnapNamespace(ss.Name()) if err != nil { st.Lock() t.Errorf("cannot discard snap namespace %q, will retry in 3 mins: %s", ss.Name(), err) st.Unlock() return &state.Retry{After: 3 * time.Minute} } } st.Lock() Set(st, ss.Name(), snapst) st.Unlock() return nil }
func (m *InterfaceManager) doSetupProfiles(task *state.Task, _ *tomb.Tomb) error { task.State().Lock() defer task.State().Unlock() // Get snap.Info from bits handed by the snap manager. ss, err := snapstate.TaskSnapSetup(task) if err != nil { return err } snapInfo, err := snap.ReadInfo(ss.Name(), ss.SideInfo) if err != nil { return err } snap.AddImplicitSlots(snapInfo) snapName := snapInfo.Name() // The snap may have been updated so perform the following operation to // ensure that we are always working on the correct state: // // - disconnect all connections to/from the given snap // - remembering the snaps that were affected by this operation // - remove the (old) snap from the interfaces repository // - add the (new) snap to the interfaces repository // - restore connections based on what is kept in the state // - if a connection cannot be restored then remove it from the state // - setup the security of all the affected snaps affectedSnaps, err := m.repo.DisconnectSnap(snapName) if err != nil { return err } // XXX: what about snap renames? We should remove the old name (or switch // to IDs in the interfaces repository) if err := m.repo.RemoveSnap(snapName); err != nil { return err } if err := m.repo.AddSnap(snapInfo); err != nil { if _, ok := err.(*interfaces.BadInterfacesError); ok { logger.Noticef("%s", err) } else { return err } } if err := m.reloadConnections(snapName); err != nil { return err } // FIXME: here we should not reconnect auto-connect plug/slot // pairs that were explicitly disconnected by the user if err := m.autoConnect(task, snapName, nil); err != nil { return err } if err := setupSnapSecurity(task, snapInfo, ss.DevModeAllowed(), m.repo); err != nil { return err } return m.setupAffectedSnaps(task, snapName, affectedSnaps) }
func snapSetupAndState(t *state.Task) (*SnapSetup, *SnapState, error) { ss, err := TaskSnapSetup(t) if err != nil { return nil, nil, err } var snapst SnapState err = Get(t.State(), ss.Name(), &snapst) if err != nil && err != state.ErrNoState { return nil, nil, err } return ss, &snapst, nil }
func (m *SnapManager) doRemoveAliases(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() snapsup, _, err := snapSetupAndState(t) if err != nil { return err } snapName := snapsup.Name() st.Unlock() defer st.Lock() return m.backend.RemoveSnapAliases(snapName) }
func removeSnapSecurity(task *state.Task, snapName string) error { st := task.State() for _, backend := range backends.All { st.Unlock() err := backend.Remove(snapName) st.Lock() if err != nil { task.Errorf("cannot setup %s for snap %q: %s", backend.Name(), snapName, err) return err } } return nil }
func prepareSerialRequest(t *state.Task, privKey asserts.PrivateKey, device *auth.DeviceState, client *http.Client, cfg *serialRequestConfig) (string, error) { st := t.State() st.Unlock() defer st.Lock() req, err := http.NewRequest("POST", cfg.requestIDURL, nil) if err != nil { return "", fmt.Errorf("internal error: cannot create request-id request %q", cfg.requestIDURL) } cfg.applyHeaders(req) resp, err := client.Do(req) if err != nil { return "", retryErr(t, "cannot retrieve request-id for making a request for a serial: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return "", retryErr(t, "cannot retrieve request-id for making a request for a serial: unexpected status %d", resp.StatusCode) } dec := json.NewDecoder(resp.Body) var requestID requestIDResp err = dec.Decode(&requestID) if err != nil { // assume broken i/o return "", retryErr(t, "cannot read response with request-id for making a request for a serial: %v", err) } encodedPubKey, err := asserts.EncodePublicKey(privKey.PublicKey()) if err != nil { return "", fmt.Errorf("internal error: cannot encode device public key: %v", err) } headers := map[string]interface{}{ "brand-id": device.Brand, "model": device.Model, "request-id": requestID.RequestID, "device-key": string(encodedPubKey), } if cfg.proposedSerial != "" { headers["serial"] = cfg.proposedSerial } serialReq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType, headers, cfg.body, privKey) if err != nil { return "", err } return string(asserts.Encode(serialReq)), nil }
func (m *InterfaceManager) doRemoveProfiles(task *state.Task, tomb *tomb.Tomb) error { st := task.State() st.Lock() defer st.Unlock() // Get SnapSetup for this snap. This is gives us the name of the snap. snapSetup, err := snapstate.TaskSnapSetup(task) if err != nil { return err } snapName := snapSetup.Name() return m.removeProfilesForSnap(task, tomb, snapName) }
func setupSnapSecurity(task *state.Task, snapInfo *snap.Info, devMode bool, repo *interfaces.Repository) error { st := task.State() snapName := snapInfo.Name() for _, backend := range backends.All { st.Unlock() err := backend.Setup(snapInfo, devMode, repo) st.Lock() if err != nil { task.Errorf("cannot setup %s for snap %q: %s", backend.Name(), snapName, err) return err } } return nil }
func (m *InterfaceManager) doSetupProfiles(task *state.Task, tomb *tomb.Tomb) error { task.State().Lock() defer task.State().Unlock() // Get snap.Info from bits handed by the snap manager. ss, err := snapstate.TaskSnapSetup(task) if err != nil { return err } snapInfo, err := snap.ReadInfo(ss.Name(), ss.SideInfo) if err != nil { return err } return m.setupProfilesForSnap(task, tomb, snapInfo, ss.DevModeAllowed()) }
func (m *SnapManager) doDownloadSnap(t *state.Task, tomb *tomb.Tomb) error { st := t.State() st.Lock() snapsup, err := TaskSnapSetup(t) st.Unlock() if err != nil { return err } meter := &TaskProgressAdapter{task: t} st.Lock() theStore := Store(st) user, err := userFromUserID(st, snapsup.UserID) st.Unlock() if err != nil { return err } targetFn := snapsup.MountFile() if snapsup.DownloadInfo == nil { var storeInfo *snap.Info // COMPATIBILITY - this task was created from an older version // of snapd that did not store the DownloadInfo in the state // yet. storeInfo, err = theStore.Snap(snapsup.Name(), snapsup.Channel, snapsup.DevModeAllowed(), snapsup.Revision(), user) if err != nil { return err } err = theStore.Download(tomb.Context(nil), snapsup.Name(), targetFn, &storeInfo.DownloadInfo, meter, user) snapsup.SideInfo = &storeInfo.SideInfo } else { err = theStore.Download(tomb.Context(nil), snapsup.Name(), targetFn, snapsup.DownloadInfo, meter, user) } if err != nil { return err } snapsup.SnapPath = targetFn // update the snap setup for the follow up tasks st.Lock() t.Set("snap-setup", snapsup) st.Unlock() return nil }
func (m *SnapManager) doMountSnap(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() ss, snapst, err := snapSetupAndState(t) t.State().Unlock() if err != nil { return err } curInfo, err := snapst.CurrentInfo() if err != nil && err != ErrNoCurrent { return err } m.backend.CurrentInfo(curInfo) if err := checkSnap(t.State(), ss.SnapPath, curInfo, Flags(ss.Flags)); err != nil { return err } pb := &TaskProgressAdapter{task: t} // TODO Use ss.Revision() to obtain the right info to mount // instead of assuming the candidate is the right one. if err := m.backend.SetupSnap(ss.SnapPath, ss.SideInfo, pb); err != nil { return err } // set snapst type for undoMountSnap newInfo, err := readInfo(ss.Name(), ss.SideInfo) if err != nil { return err } t.State().Lock() t.Set("snap-type", newInfo.Type) t.State().Unlock() // cleanup the downloaded snap after it got installed // in backend.SetupSnap. // // Note that we always remove the file because the // way sideloading works currently is to always create // a temporary file (see daemon/api.go:sideloadSnap() if err := os.Remove(ss.SnapPath); err != nil { logger.Noticef("Failed to cleanup %q: %s", err) } return nil }
func (m *SnapManager) doSetAutoAliases(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() snapsup, snapst, err := snapSetupAndState(t) if err != nil { return err } snapName := snapsup.Name() curInfo, err := snapst.CurrentInfo() if err != nil { return err } aliasStatuses, err := getAliases(st, snapName) if err != nil && err != state.ErrNoState { return err } t.Set("old-aliases", aliasStatuses) if aliasStatuses == nil { aliasStatuses = make(map[string]string) } allNew, allRetired, err := AutoAliasesDelta(st, []string{snapName}) if err != nil { return err } for _, alias := range allRetired[snapName] { delete(aliasStatuses, alias) } for _, alias := range allNew[snapName] { aliasApp := curInfo.Aliases[alias] if aliasApp == nil { // not a known alias anymore or yet, skip continue } // TODO: only mark/log conflict if this is an update instead of an install? err := checkAliasConflict(st, snapName, alias) if err != nil { return err } aliasStatuses[alias] = "auto" } setAliases(st, snapName, aliasStatuses) return nil }
func (m *SnapManager) doDownloadSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() ss, err := TaskSnapSetup(t) st.Unlock() if err != nil { return err } meter := &TaskProgressAdapter{task: t} st.Lock() theStore := Store(st) user, err := userFromUserID(st, ss.UserID) st.Unlock() if err != nil { return err } var downloadedSnapFile string if ss.DownloadInfo == nil { // COMPATIBILITY - this task was created from an older version // of snapd that did not store the DownloadInfo in the state // yet. storeInfo, err := theStore.Snap(ss.Name(), ss.Channel, ss.DevModeAllowed(), ss.Revision(), user) if err != nil { return err } downloadedSnapFile, err = theStore.Download(ss.Name(), &storeInfo.DownloadInfo, meter, user) ss.SideInfo = &storeInfo.SideInfo } else { downloadedSnapFile, err = theStore.Download(ss.Name(), ss.DownloadInfo, meter, user) } if err != nil { return err } ss.SnapPath = downloadedSnapFile // update the snap setup for the follow up tasks st.Lock() t.Set("snap-setup", ss) st.Unlock() return nil }
func (m *InterfaceManager) doSetupProfiles(task *state.Task, tomb *tomb.Tomb) error { task.State().Lock() defer task.State().Unlock() // Get snap.Info from bits handed by the snap manager. snapsup, err := snapstate.TaskSnapSetup(task) if err != nil { return err } snapInfo, err := snap.ReadInfo(snapsup.Name(), snapsup.SideInfo) if err != nil { return err } opts := confinementOptions(snapsup.Flags) return m.setupProfilesForSnap(task, tomb, snapInfo, opts) }
func hookSetup(task *state.Task) (*HookSetup, *snapstate.SnapState, error) { var hooksup HookSetup err := task.Get("hook-setup", &hooksup) if err != nil { return nil, nil, fmt.Errorf("cannot extract hook setup from task: %s", err) } var snapst snapstate.SnapState err = snapstate.Get(task.State(), hooksup.Snap, &snapst) if err == state.ErrNoState { return nil, nil, fmt.Errorf("cannot find %q snap", hooksup.Snap) } if err != nil { return nil, nil, fmt.Errorf("cannot handle %q snap: %v", hooksup.Snap, err) } return &hooksup, &snapst, nil }