func checkChangeConflict(s *state.State, snapName string, snapst *SnapState) error { for _, task := range s.Tasks() { k := task.Kind() chg := task.Change() if (k == "link-snap" || k == "unlink-snap") && (chg == nil || !chg.Status().Ready()) { ss, err := TaskSnapSetup(task) if err != nil { return fmt.Errorf("internal error: cannot obtain snap setup from task: %s", task.Summary()) } if ss.Name() == snapName { return fmt.Errorf("snap %q has changes in progress", snapName) } } } if snapst != nil { // caller wants us to also make sure the SnapState in state // matches the one they provided. Necessary because we need to // unlock while talking to the store, during which a change can // sneak in (if it's before the taskset is created) (e.g. for // install, while getting the snap info; for refresh, when // getting what needs refreshing). var cursnapst SnapState if err := Get(s, snapName, &cursnapst); err != nil && err != state.ErrNoState { return err } // TODO: implement the rather-boring-but-more-performant SnapState.Equals if !reflect.DeepEqual(snapst, &cursnapst) { return fmt.Errorf("snap %q state changed during install preparations", snapName) } } return nil }
func doFetch(s *state.State, userID int, fetching func(asserts.Fetcher) error) error { // TODO: once we have a bulk assertion retrieval endpoint this approach will change user, err := userFromUserID(s, userID) if err != nil { return err } sto := snapstate.Store(s) retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) { // TODO: ignore errors if already in db? return sto.Assertion(ref.Type, ref.PrimaryKey, user) } f := newFetcher(s, retrieve) s.Unlock() err = fetching(f) s.Lock() if err != nil { return err } // TODO: trigger w. caller a global sanity check if a is revoked // (but try to save as much possible still), // or err is a check error return f.commit() }
func checkAliasConflict(st *state.State, snapName, alias string) error { // check against snaps var snapNames map[string]*json.RawMessage err := st.Get("snaps", &snapNames) if err != nil && err != state.ErrNoState { return err } for name := range snapNames { if name == alias || strings.HasPrefix(alias, name+".") { return &aliasConflictError{ Alias: alias, Snap: snapName, Reason: fmt.Sprintf("it conflicts with the command namespace of installed snap %q", name), } } } // check against aliases return checkAgainstEnabledAliases(st, func(otherAlias, otherSnap string) error { if otherAlias == alias { return &aliasConflictError{ Alias: alias, Snap: snapName, Reason: fmt.Sprintf("already enabled for %q", otherSnap), } } return nil }) }
func cachedStore(s *state.State) StoreService { ubuntuStore := s.Cached(cachedStoreKey{}) if ubuntuStore == nil { return nil } return ubuntuStore.(StoreService) }
// Alias enables the provided aliases for the snap with the given name. func Alias(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, snapName, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", snapName) } if err != nil { return nil, err } if !snapst.Active { return nil, fmt.Errorf("enabling aliases for disabled snap %q not supported", snapName) } if err := checkChangeConflict(st, snapName, nil); err != nil { return nil, err } snapsup := &SnapSetup{ SideInfo: &snap.SideInfo{RealName: snapName}, } alias := st.NewTask("alias", fmt.Sprintf(i18n.G("Enable aliases for snap %q"), snapsup.Name())) alias.Set("snap-setup", &snapsup) toEnable := map[string]string{} for _, alias := range aliases { toEnable[alias] = "enabled" } alias.Set("aliases", toEnable) return state.NewTaskSet(alias), nil }
// NewUser tracks a new authenticated user and saves its details in the state func NewUser(st *state.State, username, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { authStateData = AuthState{} } else if err != nil { return nil, err } sort.Strings(discharges) authStateData.LastID++ authenticatedUser := UserState{ ID: authStateData.LastID, Username: username, Macaroon: macaroon, Discharges: discharges, StoreMacaroon: macaroon, StoreDischarges: discharges, } authStateData.Users = append(authStateData.Users, authenticatedUser) st.Set("auth", authStateData) return &authenticatedUser, nil }
// Disable sets a snap to the inactive state func Disable(s *state.State, name string) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", name) } if err != nil { return nil, err } if !snapst.Active { return nil, fmt.Errorf("snap %q already disabled", name) } if err := checkChangeConflict(s, name, nil); err != nil { return nil, err } ss := &SnapSetup{ SideInfo: &snap.SideInfo{ RealName: name, Revision: snapst.Current, }, } stopSnapServices := s.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q (%s) services"), ss.Name(), snapst.Current)) stopSnapServices.Set("snap-setup", &ss) unlinkSnap := s.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), ss.Name(), snapst.Current)) unlinkSnap.Set("snap-setup-task", stopSnapServices.ID()) unlinkSnap.WaitFor(stopSnapServices) return state.NewTaskSet(stopSnapServices, unlinkSnap), nil }
// allLocalSnapInfos returns the information about the all current snaps and their SnapStates. func allLocalSnapInfos(st *state.State) ([]aboutSnap, error) { st.Lock() defer st.Unlock() snapStates, err := snapstate.All(st) if err != nil { return nil, err } about := make([]aboutSnap, 0, len(snapStates)) var firstErr error for _, snapState := range snapStates { info, err := snapState.CurrentInfo() if err != nil { // XXX: aggregate instead? if firstErr == nil { firstErr = err } continue } about = append(about, aboutSnap{info, snapState}) } return about, firstErr }
// CheckMacaroon returns the UserState for the given macaroon/discharges credentials func CheckMacaroon(st *state.State, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return nil, ErrInvalidAuth } NextUser: for _, user := range authStateData.Users { if user.Macaroon != macaroon { continue } if len(user.Discharges) != len(discharges) { continue } // sort discharges (stored users' discharges are already sorted) sort.Strings(discharges) for i, d := range user.Discharges { if d != discharges[i] { continue NextUser } } return &user, nil } return nil, ErrInvalidAuth }
// patch1 adds the snap type and the current revision to the snap state. func patch1(s *state.State) error { var stateMap map[string]*snapstate.SnapState err := s.Get("snaps", &stateMap) if err == state.ErrNoState { return nil } if err != nil { return err } for snapName, snapState := range stateMap { seq := snapState.Sequence if len(seq) == 0 { continue } typ := snap.TypeApp snapInfo, err := readInfo(snapName, seq[len(seq)-1]) if err != nil { logger.Noticef("Recording type for snap %q: cannot retrieve info, assuming it's a app: %v", snapName, err) } else { logger.Noticef("Recording type for snap %q: setting to %q", snapName, snapInfo.Type) typ = snapInfo.Type } snapState.SetType(typ) snapState.Current = seq[len(seq)-1].Revision } s.Set("snaps", stateMap) return nil }
func cachedDB(s *state.State) *asserts.Database { db := s.Cached(cachedDBKey{}) if db == nil { panic("internal error: needing an assertion database before the assertion manager is initialized") } return db.(*asserts.Database) }
func updateInfo(st *state.State, snapst *SnapState, channel string, userID int, flags Flags) (*snap.Info, error) { user, err := userFromUserID(st, userID) if err != nil { return nil, err } curInfo, err := snapst.CurrentInfo() if err != nil { return nil, err } if curInfo.SnapID == "" { // covers also trymode return nil, fmt.Errorf("cannot refresh local snap %q", curInfo.Name()) } refreshCand := &store.RefreshCandidate{ // the desired channel Channel: channel, DevMode: flags.DevModeAllowed(), Block: snapst.Block(), SnapID: curInfo.SnapID, Revision: curInfo.Revision, Epoch: curInfo.Epoch, } theStore := Store(st) st.Unlock() // calls to the store should be done without holding the state lock res, err := theStore.ListRefresh([]*store.RefreshCandidate{refreshCand}, user) st.Lock() if len(res) == 0 { return nil, fmt.Errorf("snap %q has no updates available", curInfo.Name()) } return res[0], nil }
// ResetAliases resets the provided aliases for the snap with the given name to their default state, enabled for auto-aliases, disabled otherwise. func ResetAliases(st *state.State, snapName string, aliases []string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, snapName, &snapst) if err == state.ErrNoState { return nil, fmt.Errorf("cannot find snap %q", snapName) } if err != nil { return nil, err } if !snapst.Active { // TODO: we might want to support this return nil, fmt.Errorf("resetting aliases to their default state for disabled snap %q not supported", snapName) } if err := checkChangeConflict(st, snapName, nil); err != nil { return nil, err } snapsup := &SnapSetup{ SideInfo: &snap.SideInfo{RealName: snapName}, } alias := st.NewTask("alias", fmt.Sprintf(i18n.G("Reset aliases for snap %q"), snapsup.Name())) alias.Set("snap-setup", &snapsup) toReset := map[string]string{} for _, alias := range aliases { toReset[alias] = "auto" } alias.Set("aliases", toReset) return state.NewTaskSet(alias), nil }
// checkSnap ensures that the snap can be installed. func checkSnap(st *state.State, snapFilePath string, si *snap.SideInfo, curInfo *snap.Info, flags Flags) error { // This assumes that the snap was already verified or --dangerous was used. s, _, err := openSnapFile(snapFilePath, si) if err != nil { return err } if s.NeedsDevMode() && !flags.DevModeAllowed() { return fmt.Errorf("snap %q requires devmode or confinement override", s.Name()) } // verify we have a valid architecture if !arch.IsSupportedArchitecture(s.Architectures) { return fmt.Errorf("snap %q supported architectures (%s) are incompatible with this system (%s)", s.Name(), strings.Join(s.Architectures, ", "), arch.UbuntuArchitecture()) } // check assumes err = checkAssumes(s) if err != nil { return err } st.Lock() defer st.Unlock() for _, check := range checkSnapCallbacks { err := check(st, s, curInfo, flags) if err != nil { return err } } return nil }
// Disconnect returns a set of tasks for disconnecting an interface. func Disconnect(s *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) { summary := fmt.Sprintf(i18n.G("Disconnect %s:%s from %s:%s"), plugSnap, plugName, slotSnap, slotName) task := s.NewTask("disconnect", summary) task.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName}) task.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plugName}) return state.NewTaskSet(task), nil }
func importAssertionsFromSeed(st *state.State) error { st.Lock() defer st.Unlock() assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions") dc, err := ioutil.ReadDir(assertSeedDir) if err != nil { return fmt.Errorf("cannot read assert seed dir: %s", err) } // FIXME: remove this check once asserts are mandatory if len(dc) == 0 { return nil } // collect var modelRef *asserts.Ref batch := assertstate.NewBatch() for _, fi := range dc { fn := filepath.Join(assertSeedDir, fi.Name()) refs, err := readAsserts(fn, batch) if err != nil { return fmt.Errorf("cannot read assertions: %s", err) } for _, ref := range refs { if ref.Type == asserts.ModelType { if modelRef != nil && modelRef.Unique() != ref.Unique() { return fmt.Errorf("cannot add more than one model assertion") } modelRef = ref } } } // verify we have one model assertion if modelRef == nil { return fmt.Errorf("need a model assertion") } if err := batch.Commit(st); err != nil { return err } a, err := modelRef.Resolve(assertstate.DB(st).Find) if err != nil { return fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err) } modelAssertion := a.(*asserts.Model) // set device,model from the model assertion auth.SetDevice(st, &auth.DeviceState{ Brand: modelAssertion.BrandID(), Model: modelAssertion.Model(), }) return nil }
// Disconnect returns a set of tasks for disconnecting an interface. func Disconnect(s *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) { // TODO: Remove the intent-to-connect from the state so that we no longer // automatically try to reconnect on reboot. summary := fmt.Sprintf(i18n.G("Disconnect %s:%s from %s:%s"), plugSnap, plugName, slotSnap, slotName) task := s.NewTask("disconnect", summary) task.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName}) task.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plugName}) return state.NewTaskSet(task), nil }
// HookTask returns a task that will run the specified hook. Note that the // initial context must properly marshal and unmarshal with encoding/json. func HookTask(st *state.State, summary string, setup *HookSetup, contextData map[string]interface{}) *state.Task { task := st.NewTask("run-hook", summary) task.Set("hook-setup", setup) // Initial data for Context.Get/Set. if len(contextData) > 0 { task.Set("hook-context", contextData) } return task }
func newChange(st *state.State, kind, summary string, tsets []*state.TaskSet, snapNames []string) *state.Change { chg := st.NewChange(kind, summary) for _, ts := range tsets { chg.AddAll(ts) } if snapNames != nil { chg.Set("snap-names", snapNames) } return chg }
// CheckMacaroon returns the UserState for the given macaroon/discharges credentials func CheckMacaroon(st *state.State, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return nil, ErrInvalidAuth } snapdMacaroon, err := MacaroonDeserialize(macaroon) if err != nil { return nil, ErrInvalidAuth } // attempt snapd macaroon verification if snapdMacaroon.Location() == snapdMacaroonLocation { // no caveats to check so far check := func(caveat string) error { return nil } // ignoring discharges, unused for snapd macaroons atm err = snapdMacaroon.Verify(authStateData.MacaroonKey, check, nil) if err != nil { return nil, ErrInvalidAuth } macaroonID := snapdMacaroon.Id() userID, err := strconv.Atoi(macaroonID) if err != nil { return nil, ErrInvalidAuth } user, err := User(st, userID) if err != nil { return nil, ErrInvalidAuth } if macaroon != user.Macaroon { return nil, ErrInvalidAuth } return user, nil } // if macaroon is not a snapd macaroon, fallback to previous token-style check NextUser: for _, user := range authStateData.Users { if user.Macaroon != macaroon { continue } if len(user.Discharges) != len(discharges) { continue } // sort discharges (stored users' discharges are already sorted) sort.Strings(discharges) for i, d := range user.Discharges { if d != discharges[i] { continue NextUser } } return &user, nil } return nil, ErrInvalidAuth }
func snapInfo(st *state.State, name, channel string, revision snap.Revision, userID int, flags Flags) (*snap.Info, error) { user, err := userFromUserID(st, userID) if err != nil { return nil, err } theStore := Store(st) st.Unlock() // calls to the store should be done without holding the state lock snap, err := theStore.Snap(name, channel, flags.DevModeAllowed(), revision, user) st.Lock() return snap, err }
// Connect returns a set of tasks for connecting an interface. // func Connect(s *state.State, plugSnap, plugName, slotSnap, slotName string) (*state.TaskSet, error) { // TODO: Store the intent-to-connect in the state so that we automatically // try to reconnect on reboot (reconnection can fail or can connect with // different parameters so we cannot store the actual connection details). summary := fmt.Sprintf(i18n.G("Connect %s:%s to %s:%s"), plugSnap, plugName, slotSnap, slotName) task := s.NewTask("connect", summary) task.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slotName}) task.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plugName}) return state.NewTaskSet(task), nil }
func getConns(st *state.State) (map[string]connState, error) { // Get information about connections from the state var conns map[string]connState err := st.Get("conns", &conns) if err != nil && err != state.ErrNoState { return nil, fmt.Errorf("cannot obtain data about existing connections: %s", err) } if conns == nil { conns = make(map[string]connState) } return conns, nil }
// UpdateMany updates everything from the given list of names that the // store says is updateable. If the list is empty, update everything. // Note that the state must be locked by the caller. func UpdateMany(st *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) { user, err := userFromUserID(st, userID) if err != nil { return nil, nil, err } updates, stateByID, err := refreshCandidates(st, names, user) if err != nil { return nil, nil, err } if ValidateRefreshes != nil && len(updates) != 0 { updates, err = ValidateRefreshes(st, updates, userID) if err != nil { // not doing "refresh all" report the error if len(names) != 0 { return nil, nil, err } // doing "refresh all", log the problems logger.Noticef("cannot refresh some snaps: %v", err) } } updated := make([]string, 0, len(updates)) tasksets := make([]*state.TaskSet, 0, len(updates)) for _, update := range updates { snapst := stateByID[update.SnapID] ss := &SnapSetup{ Channel: snapst.Channel, UserID: userID, Flags: snapst.Flags.ForSnapSetup(), DownloadInfo: &update.DownloadInfo, SideInfo: &update.SideInfo, } ts, err := doInstall(st, snapst, ss) if err != nil { if len(names) == 0 { // doing "refresh all", just skip this snap logger.Noticef("cannot refresh snap %q: %v", update.Name(), err) continue } return nil, nil, err } ts.JoinLane(st.NewLane()) updated = append(updated, update.Name()) tasksets = append(tasksets, ts) } return updated, tasksets, nil }
// UpdateBootRevisions synchronizes the active kernel and OS snap versions // with the versions that actually booted. This is needed because a // system may install "os=v2" but that fails to boot. The bootloader // fallback logic will revert to "os=v1" but on the filesystem snappy // still has the "active" version set to "v2" which is // misleading. This code will check what kernel/os booted and set // those versions active.To do this it creates a Change and kicks // start it directly. func UpdateBootRevisions(st *state.State) error { const errorPrefix = "cannot update revisions after boot changes: " if release.OnClassic { return nil } bootloader, err := partition.FindBootloader() if err != nil { return fmt.Errorf(errorPrefix+"%s", err) } m, err := bootloader.GetBootVars("snap_kernel", "snap_core") if err != nil { return fmt.Errorf(errorPrefix+"%s", err) } var tsAll []*state.TaskSet for _, snapNameAndRevno := range []string{m["snap_kernel"], m["snap_core"]} { name, rev, err := nameAndRevnoFromSnap(snapNameAndRevno) if err != nil { logger.Noticef("cannot parse %q: %s", snapNameAndRevno, err) continue } info, err := CurrentInfo(st, name) if err != nil { logger.Noticef("cannot get info for %q: %s", name, err) continue } if rev != info.SideInfo.Revision { // FIXME: check that there is no task // for this already in progress ts, err := RevertToRevision(st, name, rev, Flags{}) if err != nil { return err } tsAll = append(tsAll, ts) } } if len(tsAll) == 0 { return nil } msg := fmt.Sprintf("Update kernel and core snap revisions") chg := st.NewChange("update-revisions", msg) for _, ts := range tsAll { chg.AddAll(ts) } st.EnsureBefore(0) return nil }
// checkSnap ensures that the snap can be installed. func checkSnap(st *state.State, snapFilePath string, curInfo *snap.Info, flags Flags) error { // This assumes that the snap was already verified or --dangerous was used. s, _, err := openSnapFile(snapFilePath, nil) if err != nil { return err } if s.NeedsDevMode() && !flags.DevModeAllowed() { return fmt.Errorf("snap %q requires devmode or confinement override", s.Name()) } // verify we have a valid architecture if !arch.IsSupportedArchitecture(s.Architectures) { return fmt.Errorf("snap %q supported architectures (%s) are incompatible with this system (%s)", s.Name(), strings.Join(s.Architectures, ", "), arch.UbuntuArchitecture()) } // check assumes err = checkAssumes(s) if err != nil { return err } if s.Type != snap.TypeGadget { return nil } // gadget specific checks if release.OnClassic { // for the time being return fmt.Errorf("cannot install a gadget snap on classic") } st.Lock() defer st.Unlock() currentGadget, err := GadgetInfo(st) // in firstboot we have no gadget yet - that is ok if err == state.ErrNoState && !firstboot.HasRun() { return nil } if err != nil { return fmt.Errorf("cannot find original gadget snap") } // TODO: actually compare snap ids, from current gadget and candidate if currentGadget.Name() != s.Name() { return fmt.Errorf("cannot replace gadget snap with a different one") } return nil }
// NewTransaction creates a new configuration transaction initialized with the given state. // // The provided state must be locked by the caller. func NewTransaction(st *state.State) *Transaction { transaction := &Transaction{state: st} transaction.changes = make(map[string]map[string]interface{}) // Record the current state of the map containing the config of every snap // in the system. We'll use it for this transaction. err := st.Get("config", &transaction.pristine) if err == state.ErrNoState { transaction.pristine = make(map[string]map[string]*json.RawMessage) } else if err != nil { panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) } return transaction }
// All retrieves return a map from name to SnapState for all current snaps in the system state. func All(s *state.State) (map[string]*SnapState, error) { // XXX: result is a map because sideloaded snaps carry no name // atm in their sideinfos var stateMap map[string]*SnapState if err := s.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { return nil, err } curStates := make(map[string]*SnapState, len(stateMap)) for snapName, snapState := range stateMap { if snapState.HasCurrent() { curStates[snapName] = snapState } } return curStates, nil }
// SetDevice updates the device details in the state. func SetDevice(st *state.State, device *DeviceState) error { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { authStateData = AuthState{} } else if err != nil { return err } authStateData.Device = device st.Set("auth", authStateData) return nil }
// Init initializes an empty state to the current implemented patch level. func Init(s *state.State) { s.Lock() defer s.Unlock() if s.Get("patch-level", new(int)) != state.ErrNoState { panic("internal error: expected empty state, attempting to override patch-level without actual patching") } s.Set("patch-level", Level) }