func (p4 patch4T) mangle(task *state.Task) error { snapsup, snapst, err := p4.snapSetupAndState(task) if err != nil { return err } var hadCandidate bool if err := p4.getMaybe(task, "had-candidate", &hadCandidate); err != nil && err != state.ErrNoState { return err } if hadCandidate { change := task.Change() if change.Kind() != "revert-snap" { return fmt.Errorf("had-candidate true for task %s (%s) of non-revert change %s (%s)", task.ID(), task.Kind(), change.ID(), change.Kind()) } } task.Clear("had-candidate") task.Set("old-candidate-index", snapst.LastIndex(snapsup.SideInfo.Revision)) 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 getSerial(t *state.Task, privKey asserts.PrivateKey, device *auth.DeviceState, cfg *serialRequestConfig) (*asserts.Serial, error) { var serialSup serialSetup err := t.Get("serial-setup", &serialSup) if err != nil && err != state.ErrNoState { return nil, err } if serialSup.Serial != "" { // we got a serial, just haven't managed to save its info yet a, err := asserts.Decode([]byte(serialSup.Serial)) if err != nil { return nil, fmt.Errorf("internal error: cannot decode previously saved serial: %v", err) } return a.(*asserts.Serial), nil } client := &http.Client{Timeout: 30 * time.Second} // NB: until we get at least an Accepted (202) we need to // retry from scratch creating a new request-id because the // previous one used could have expired if serialSup.SerialRequest == "" { serialRequest, err := prepareSerialRequest(t, privKey, device, client, cfg) if err != nil { // errors & retries return nil, err } serialSup.SerialRequest = serialRequest } serial, err := submitSerialRequest(t, serialSup.SerialRequest, client, cfg) if err == errPoll { // we can/should reuse the serial-request t.Set("serial-setup", serialSup) return nil, errPoll } if err != nil { // errors & retries return nil, err } keyID := privKey.PublicKey().ID() if serial.BrandID() != device.Brand || serial.Model() != device.Model || serial.DeviceKey().ID() != keyID { return nil, fmt.Errorf("obtained serial assertion does not match provided device identity information (brand, model, key id): %s / %s / %s != %s / %s / %s", serial.BrandID(), serial.Model(), serial.DeviceKey().ID(), device.Brand, device.Model, keyID) } serialSup.Serial = string(asserts.Encode(serial)) t.Set("serial-setup", serialSup) if repeatRequestSerial == "after-got-serial" { // For testing purposes, ensure a crash in this state works. return nil, &state.Retry{} } return serial, nil }
func (p4 patch4T) addRevertFlag(task *state.Task) error { var snapsup patch4SnapSetup err := p4.getMaybe(task, "snap-setup", &snapsup) switch err { case nil: snapsup.Flags |= patch4FlagRevert // save it back task.Set("snap-setup", &snapsup) return nil case state.ErrNoState: return nil default: return err } }
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) 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 *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) doMountSnap(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() snapsup, 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(), snapsup.SnapPath, snapsup.SideInfo, curInfo, snapsup.Flags); err != nil { return err } pb := &TaskProgressAdapter{task: t} // TODO Use snapsup.Revision() to obtain the right info to mount // instead of assuming the candidate is the right one. if err := m.backend.SetupSnap(snapsup.SnapPath, snapsup.SideInfo, pb); err != nil { return err } // set snapst type for undoMountSnap newInfo, err := readInfo(snapsup.Name(), snapsup.SideInfo) if err != nil { return err } t.State().Lock() t.Set("snap-type", newInfo.Type) t.State().Unlock() if snapsup.Flags.RemoveSnapPath { if err := os.Remove(snapsup.SnapPath); err != nil { logger.Noticef("Failed to cleanup %s: %s", snapsup.SnapPath, err) } } return nil }
func (m *SnapManager) doClearAliases(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() aliasStatuses, err := getAliases(st, snapName) if err != nil && err != state.ErrNoState { return err } if len(aliasStatuses) == 0 { // nothing to do return nil } t.Set("old-aliases", aliasStatuses) setAliases(st, snapName, nil) return nil }
func (m *InterfaceManager) doDiscardConns(task *state.Task, _ *tomb.Tomb) error { st := task.State() st.Lock() defer st.Unlock() snapSetup, err := snapstate.TaskSnapSetup(task) if err != nil { return err } snapName := snapSetup.Name() var snapState snapstate.SnapState err = snapstate.Get(st, snapName, &snapState) if err != nil && err != state.ErrNoState { return err } if err == nil && len(snapState.Sequence) != 0 { return fmt.Errorf("cannot discard connections for snap %q while it is present", snapName) } conns, err := getConns(st) if err != nil { return err } removed := make(map[string]connState) for id := range conns { plugRef, slotRef, err := parseConnID(id) if err != nil { return err } if plugRef.Snap == snapName || slotRef.Snap == snapName { removed[id] = conns[id] delete(conns, id) } } task.Set("removed", removed) setConns(st, conns) return nil }
func (m *InterfaceManager) undoDiscardConns(task *state.Task, _ *tomb.Tomb) error { st := task.State() st.Lock() defer st.Unlock() var removed map[string]connState err := task.Get("removed", &removed) if err != nil && err != state.ErrNoState { return err } conns, err := getConns(st) if err != nil { return err } for id, connState := range removed { conns[id] = connState } setConns(st, conns) task.Set("removed", nil) return nil }
func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() ss, snapst, err := snapSetupAndState(t) if err != nil { return err } cand := ss.SideInfo m.backend.Candidate(cand) oldCandidateIndex := snapst.LastIndex(cand.Revision) if oldCandidateIndex < 0 { snapst.Sequence = append(snapst.Sequence, cand) } else if !ss.Flags.Revert() { // remove the old candidate from the sequence, add it at the end copy(snapst.Sequence[oldCandidateIndex:len(snapst.Sequence)-1], snapst.Sequence[oldCandidateIndex+1:]) snapst.Sequence[len(snapst.Sequence)-1] = cand } oldCurrent := snapst.Current snapst.Current = cand.Revision snapst.Active = true oldChannel := snapst.Channel if ss.Channel != "" { snapst.Channel = ss.Channel } oldTryMode := snapst.TryMode() snapst.SetTryMode(ss.TryMode()) oldDevMode := snapst.DevMode() snapst.SetDevMode(ss.DevMode()) oldJailMode := snapst.JailMode() snapst.SetJailMode(ss.JailMode()) newInfo, err := readInfo(ss.Name(), cand) if err != nil { return err } // record type snapst.SetType(newInfo.Type) st.Unlock() // XXX: this block is slightly ugly, find a pattern when we have more examples err = m.backend.LinkSnap(newInfo) if err != nil { pb := &TaskProgressAdapter{task: t} err := m.backend.UnlinkSnap(newInfo, pb) if err != nil { st.Lock() t.Errorf("cannot cleanup failed attempt at making snap %q available to the system: %v", ss.Name(), err) st.Unlock() } } st.Lock() if err != nil { return err } // save for undoLinkSnap t.Set("old-trymode", oldTryMode) t.Set("old-devmode", oldDevMode) t.Set("old-jailmode", oldJailMode) t.Set("old-channel", oldChannel) t.Set("old-current", oldCurrent) t.Set("old-candidate-index", oldCandidateIndex) // Do at the end so we only preserve the new state if it worked. Set(st, ss.Name(), snapst) // Make sure if state commits and snapst is mutated we won't be rerun t.SetStatus(state.DoneStatus) // if we just installed a core snap, request a restart // so that we switch executing its snapd if release.OnClassic && newInfo.Type == snap.TypeOS { t.Logf("Requested daemon restart.") st.Unlock() st.RequestRestart(state.RestartDaemon) st.Lock() } if !release.OnClassic && (newInfo.Type == snap.TypeOS || newInfo.Type == snap.TypeKernel) { t.Logf("Requested system restart.") st.Unlock() st.RequestRestart(state.RestartSystem) st.Lock() } return nil }
func doInstall(s *state.State, snapst *SnapState, ss *SnapSetup) (*state.TaskSet, error) { if err := checkChangeConflict(s, ss.Name(), snapst); err != nil { return nil, err } targetRevision := ss.Revision() revisionStr := "" if ss.SideInfo != nil { revisionStr = fmt.Sprintf(" (%s)", targetRevision) } // check if we already have the revision locally (alters tasks) revisionIsLocal := snapst.LastIndex(targetRevision) >= 0 var prepare, prev *state.Task fromStore := false // if we have a local revision here we go back to that if ss.SnapPath != "" || revisionIsLocal { prepare = s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q%s"), ss.SnapPath, revisionStr)) } else { fromStore = true prepare = s.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q%s from channel %q"), ss.Name(), revisionStr, ss.Channel)) } prepare.Set("snap-setup", ss) tasks := []*state.Task{prepare} addTask := func(t *state.Task) { t.Set("snap-setup-task", prepare.ID()) t.WaitFor(prev) tasks = append(tasks, t) } prev = prepare if fromStore { // fetch and check assertions checkAsserts := s.NewTask("validate-snap", fmt.Sprintf(i18n.G("Fetch and check assertions for snap %q%s"), ss.Name(), revisionStr)) addTask(checkAsserts) prev = checkAsserts } // mount if !revisionIsLocal { mount := s.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q%s"), ss.Name(), revisionStr)) addTask(mount) prev = mount } if snapst.Active { // unlink-current-snap (will stop services for copy-data) stop := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), ss.Name())) addTask(stop) prev = stop unlink := s.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), ss.Name())) addTask(unlink) prev = unlink } // copy-data (needs stopped services by unlink) if !ss.Flags.Revert { copyData := s.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), ss.Name())) addTask(copyData) prev = copyData } // security setupSecurity := s.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q%s security profiles"), ss.Name(), revisionStr)) addTask(setupSecurity) prev = setupSecurity // finalize (wrappers+current symlink) linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q%s available to the system"), ss.Name(), revisionStr)) addTask(linkSnap) prev = linkSnap // run new serices startSnapServices := s.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q%s services"), ss.Name(), revisionStr)) addTask(startSnapServices) prev = startSnapServices // Do not do that if we are reverting to a local revision if snapst.HasCurrent() && !ss.Flags.Revert { seq := snapst.Sequence currentIndex := snapst.LastIndex(snapst.Current) // discard everything after "current" (we may have reverted to // a previous versions earlier) for i := currentIndex + 1; i < len(seq); i++ { si := seq[i] if si.Revision == targetRevision { // but don't discard this one; its' the thing we're switching to! continue } ts := removeInactiveRevision(s, ss.Name(), si.Revision) ts.WaitFor(prev) tasks = append(tasks, ts.Tasks()...) prev = tasks[len(tasks)-1] } // make sure we're not scheduling the removal of the target // revision in the case where the target revision is already in // the sequence. for i := 0; i < currentIndex; i++ { si := seq[i] if si.Revision == targetRevision { // we do *not* want to removeInactiveRevision of this one copy(seq[i:], seq[i+1:]) seq = seq[:len(seq)-1] currentIndex-- } } // normal garbage collect for i := 0; i <= currentIndex-2; i++ { si := seq[i] ts := removeInactiveRevision(s, ss.Name(), si.Revision) ts.WaitFor(prev) tasks = append(tasks, ts.Tasks()...) prev = tasks[len(tasks)-1] } addTask(s.NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", ss.Name(), revisionStr))) } var defaults map[string]interface{} if !snapst.HasCurrent() && ss.SideInfo != nil && ss.SideInfo.SnapID != "" { gadget, err := GadgetInfo(s) if err != nil && err != state.ErrNoState { return nil, err } if err == nil { gadgetInfo, err := snap.ReadGadgetInfo(gadget) if err != nil { return nil, err } defaults = gadgetInfo.Defaults[ss.SideInfo.SnapID] } } installSet := state.NewTaskSet(tasks...) configSet := Configure(s, ss.Name(), defaults) configSet.WaitAll(installSet) installSet.AddAll(configSet) return installSet, nil }
func (m *SnapManager) doAlias(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() snapsup, snapst, err := snapSetupAndState(t) if err != nil { return err } var changes map[string]string err = t.Get("aliases", &changes) 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) } var add []*backend.Alias var remove []*backend.Alias for alias, newStatus := range changes { aliasApp := curInfo.Aliases[alias] if aliasApp == nil { var action string switch newStatus { case "enabled": action = "enable" case "disabled": action = "disable" } return fmt.Errorf("cannot %s alias %q for %q, no such alias", action, alias, snapName) } if aliasStatuses[alias] == newStatus { // nothing to do continue } beAlias := &backend.Alias{ Name: alias, Target: filepath.Base(aliasApp.WrapperPath()), } switch newStatus { case "enabled": err := checkAliasConflict(st, snapName, alias) if err != nil { return err } add = append(add, beAlias) case "disabled": if aliasStatuses[alias] != "" { remove = append(remove, beAlias) } } aliasStatuses[alias] = newStatus } st.Unlock() err = m.backend.UpdateAliases(add, remove) st.Lock() if err != nil { return err } setAliases(st, snapName, aliasStatuses) return nil }
func (m *SnapManager) doAlias(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() snapsup, snapst, err := snapSetupAndState(t) if err != nil { return err } var changes map[string]string err = t.Get("aliases", &changes) if err != nil { return err } snapName := snapsup.Name() curInfo, err := snapst.CurrentInfo() if err != nil { return err } autoAliases, err := AutoAliases(st, curInfo) if err != nil { return err } autoSet := make(map[string]bool, len(autoAliases)) for _, alias := range autoAliases { autoSet[alias] = true } 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) } var add []*backend.Alias var remove []*backend.Alias for alias, newStatus := range changes { if aliasStatuses[alias] == newStatus { // nothing to do continue } aliasApp := curInfo.Aliases[alias] if aliasApp == nil { if newStatus == "auto" { // reset to default disabled status delete(aliasStatuses, alias) continue } var action string switch newStatus { case "enabled": action = "enable" case "disabled": action = "disable" } return fmt.Errorf("cannot %s alias %q for %q, no such alias", action, alias, snapName) } beAlias := &backend.Alias{ Name: alias, Target: filepath.Base(aliasApp.WrapperPath()), } if newStatus == "auto" { if !autoSet[alias] { newStatus = "-" // default disabled status, not stored! } } switch newStatus { case "enabled", "auto": if !enabledAlias(aliasStatuses[alias]) { err := checkAliasConflict(st, snapName, alias) if err != nil { return err } add = append(add, beAlias) } case "disabled", "-": if enabledAlias(aliasStatuses[alias]) { remove = append(remove, beAlias) } } if newStatus != "-" { aliasStatuses[alias] = newStatus } else { delete(aliasStatuses, alias) } } st.Unlock() err = m.backend.UpdateAliases(add, remove) st.Lock() if err != nil { return err } setAliases(st, snapName, aliasStatuses) return nil }