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 := readInfo(ss.Name, snapst.Current()) 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 *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 } err = m.repo.Disconnect(plugRef.Snap, plugRef.Name, slotRef.Snap, slotRef.Name) if err != nil { return err } plug := m.repo.Plug(plugRef.Snap, plugRef.Name) slot := m.repo.Slot(slotRef.Snap, slotRef.Name) if err := setupSnapSecurity(task, plug.Snap, m.repo); err != nil { return state.Retry } if err := setupSnapSecurity(task, slot.Snap, m.repo); err != nil { return state.Retry } delete(conns, connID(plugRef, slotRef)) setConns(st, conns) return nil }
func (m *SnapManager) doCopySnapData(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() ss, snapst, err := snapSetupAndState(t) t.State().Unlock() if err != nil { return err } newInfo, err := readInfo(ss.Name, snapst.Candidate) if err != nil { return err } var oldInfo *snap.Info if cur := snapst.Current(); cur != nil { var err error oldInfo, err = readInfo(ss.Name, cur) if err != nil { return err } } return m.backend.CopySnapData(newInfo, oldInfo, ss.Flags) }
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 } var curInfo *snap.Info if cur := snapst.Current(); cur != nil { var err error curInfo, err = readInfo(ss.Name, cur) if err != nil { return err } } if err := m.backend.CheckSnap(ss.SnapPath, curInfo, ss.Flags); err != nil { return err } // TODO Use ss.Revision to obtain the right info to mount // instead of assuming the candidate is the right one. return m.backend.SetupSnap(ss.SnapPath, snapst.Candidate, ss.Flags) }
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 *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 len(snapst.Sequence) == 1 { snapst.Sequence = nil } 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 } pb := &TaskProgressAdapter{task: t} err = m.backend.RemoveSnapFiles(ss.placeInfo(), pb) if err != nil { return err } st.Lock() Set(st, ss.Name, snapst) st.Unlock() 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 == 0 { // sideloading // to not clash with not sideload installs // and to not have clashes between them // use incremental revisions starting at 100001 // for sideloads revision := snapst.LocalRevision if revision == 0 { revision = firstLocalRevision } else { revision++ } snapst.LocalRevision = revision ss.Revision = revision } else { if err := checkRevisionIsNew(ss.Name, snapst, ss.Revision); err != nil { return err } } st.Lock() t.Set("snap-setup", ss) snapst.Candidate = &snap.SideInfo{Revision: ss.Revision} Set(st, ss.Name, snapst) st.Unlock() 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 := readInfo(ss.Name, snapst.Current()) 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 (m *SnapManager) undoMountSnap(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() ss, err := TaskSnapSetup(t) t.State().Unlock() if err != nil { return err } return m.backend.UndoSetupSnap(ss.placeInfo()) }
func getPlugAndSlotRefs(task *state.Task) (*interfaces.PlugRef, *interfaces.SlotRef, error) { var plugRef interfaces.PlugRef var slotRef interfaces.SlotRef if err := task.Get("plug", &plugRef); err != nil { return nil, nil, err } if err := task.Get("slot", &slotRef); err != nil { return nil, nil, err } return &plugRef, &slotRef, nil }
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 removeSnapSecurity(task *state.Task, snapName string) error { st := task.State() for _, backend := range securityBackends { 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 (m *SnapManager) undoPrepareSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() ss, snapst, err := snapSetupAndState(t) if err != nil { return err } snapst.Candidate = nil Set(st, ss.Name, snapst) return nil }
func (m *SnapManager) undoCopySnapData(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() ss, snapst, err := snapSetupAndState(t) t.State().Unlock() if err != nil { return err } newInfo, err := readInfo(ss.Name, snapst.Candidate) if err != nil { return err } return m.backend.UndoCopySnapData(newInfo, ss.Flags) }
func (m *SnapManager) doClearSnapData(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() ss, err := TaskSnapSetup(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 } return m.backend.RemoveSnapData(info) }
func (m *SnapManager) doDownloadSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() ss, snapst, err := snapSetupAndState(t) st.Unlock() if err != nil { return err } checker := func(info *snap.Info) error { return checkRevisionIsNew(ss.Name, snapst, info.Revision) } pb := &TaskProgressAdapter{task: t} var auther store.Authenticator if ss.UserID > 0 { st.Lock() user, err := auth.User(st, ss.UserID) st.Unlock() if err != nil { return err } auther = user.Authenticator() } storeInfo, downloadedSnapFile, err := m.backend.Download(ss.Name, ss.Channel, checker, pb, auther) if err != nil { return err } ss.SnapPath = downloadedSnapFile ss.Revision = storeInfo.Revision // update the snap setup and state for the follow up tasks st.Lock() t.Set("snap-setup", ss) snapst.Candidate = &storeInfo.SideInfo Set(st, ss.Name, snapst) st.Unlock() return nil }
func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() ss, snapst, err := snapSetupAndState(t) if err != nil { return err } var oldChannel string err = t.Get("old-channel", &oldChannel) if err != nil { return err } // relinking of the old snap is done in the undo of unlink-current-snap snapst.Candidate = snapst.Sequence[len(snapst.Sequence)-1] snapst.Sequence = snapst.Sequence[:len(snapst.Sequence)-1] snapst.Active = false snapst.Channel = oldChannel newInfo, err := readInfo(ss.Name, snapst.Candidate) if err != nil { return err } pb := &TaskProgressAdapter{task: t} st.Unlock() // pb itself will ask for locking err = m.backend.UnlinkSnap(newInfo, pb) st.Lock() if err != nil { return err } // mark as inactive Set(st, ss.Name, snapst) 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) 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) 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 := snapst.Candidate m.backend.Candidate(snapst.Candidate) snapst.Sequence = append(snapst.Sequence, snapst.Candidate) snapst.Candidate = nil snapst.Active = true oldChannel := snapst.Channel if ss.Channel != "" { snapst.Channel = ss.Channel } newInfo, err := readInfo(ss.Name, cand) if err != nil { return err } st.Unlock() err = m.backend.LinkSnap(newInfo) st.Lock() if err != nil { return err } t.Set("old-channel", oldChannel) // Do at the end so we only preserve the new state if it worked. Set(st, ss.Name, snapst) return nil }
func setupSnapSecurity(task *state.Task, snapInfo *snap.Info, repo *interfaces.Repository) error { st := task.State() var snapState snapstate.SnapState snapName := snapInfo.Name() if err := snapstate.Get(st, snapName, &snapState); err != nil { task.Errorf("cannot get state of snap %q: %s", snapName, err) return err } for _, backend := range securityBackends { st.Unlock() err := backend.Setup(snapInfo, snapState.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) 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 TaskSnapSetup(t *state.Task) (*SnapSetup, error) { var ss SnapSetup err := t.Get("snap-setup", &ss) if err != nil && err != state.ErrNoState { return nil, err } if err == nil { return &ss, nil } var id string err = t.Get("snap-setup-task", &id) if err != nil { return nil, err } ts := t.State().Task(id) if err := ts.Get("snap-setup", &ss); err != nil { return nil, err } return &ss, nil }
func (m *InterfaceManager) doRemoveProfiles(task *state.Task, _ *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 // Get SnapState for this snap var snapState snapstate.SnapState err = snapstate.Get(st, snapName, &snapState) if err != nil && err != state.ErrNoState { return err } // Get the old-devmode flag from the task. // This flag is set by setup-profiles in case we have to undo. var oldDevMode bool err = task.Get("old-devmode", &oldDevMode) if err != nil && err != state.ErrNoState { return err } // Restore the state of DevMode flag if old-devmode was saved in the task. if err == nil { if oldDevMode { snapState.Flags |= snapstate.DevMode } else { snapState.Flags &= ^snapstate.DevMode } snapstate.Set(st, snapName, &snapState) } // Disconnect the snap entirely. // This is required to remove the snap from the interface repository. // The returned list of affected snaps will need to have its security setup // to reflect the change. affectedSnaps, err := m.repo.DisconnectSnap(snapName) if err != nil { return err } // Setup security of the affected snaps. for _, snapInfo := range affectedSnaps { if snapInfo.Name() == snapName { // Skip setup for the snap being removed as this is handled below. continue } if err := setupSnapSecurity(task, snapInfo, m.repo); err != nil { return state.Retry } } // Remove the snap from the interface repository. // This discards all the plugs and slots belonging to that snap. if err := m.repo.RemoveSnap(snapName); err != nil { return err } // Remove security artefacts of the snap. if err := removeSnapSecurity(task, snapName); err != nil { return state.Retry } 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 := snapstate.Info(task.State(), ss.Name, ss.Revision) if err != nil { return err } snap.AddImplicitSlots(snapInfo) snapName := snapInfo.Name() var snapState snapstate.SnapState if err := snapstate.Get(task.State(), snapName, &snapState); err != nil { task.Errorf("cannot get state of snap %q: %s", snapName, err) return err } // Set DevMode flag if SnapSetup.Flags indicates it should be done // but remember the old value in the task in case we undo. task.Set("old-devmode", snapState.DevMode()) if ss.DevMode() { snapState.Flags |= snapstate.DevMode } else { snapState.Flags &= ^snapstate.DevMode } snapstate.Set(task.State(), snapName, &snapState) // 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 blacklist := m.repo.AutoConnectBlacklist(snapName) 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 } if err := m.autoConnect(task, snapName, blacklist); err != nil { return err } if len(affectedSnaps) == 0 { affectedSnaps = append(affectedSnaps, snapInfo) } for _, snapInfo := range affectedSnaps { if err := setupSnapSecurity(task, snapInfo, m.repo); err != nil { return state.Retry } } return nil }
func doInstall(s *state.State, curActive bool, snapName, snapPath, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { if err := checkChangeConflict(s, snapName); err != nil { return nil, err } if snapPath == "" && channel == "" { channel = "stable" } var prepare *state.Task ss := SnapSetup{ Channel: channel, UserID: userID, Flags: int(flags), } ss.Name = snapName ss.SnapPath = snapPath if snapPath != "" { prepare = s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q"), snapPath)) } else { prepare = s.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q from channel %q"), snapName, channel)) } prepare.Set("snap-setup", ss) tasks := []*state.Task{prepare} addTask := func(t *state.Task) { t.Set("snap-setup-task", prepare.ID()) tasks = append(tasks, t) } // mount mount := s.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q"), snapName)) addTask(mount) mount.WaitFor(prepare) precopy := mount if curActive { // unlink-current-snap (will stop services for copy-data) unlink := s.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapName)) addTask(unlink) unlink.WaitFor(mount) precopy = unlink } // copy-data (needs stopped services by unlink) copyData := s.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), snapName)) addTask(copyData) copyData.WaitFor(precopy) // security setupSecurity := s.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q security profiles"), snapName)) addTask(setupSecurity) setupSecurity.WaitFor(copyData) // finalize (wrappers+current symlink) linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q available to the system"), snapName)) addTask(linkSnap) linkSnap.WaitFor(setupSecurity) return state.NewTaskSet(tasks...), nil }