1 Star 0 Fork 1

mysnapcore/mysnapd

forked from tupelo-shen/mysnapd 
Create your Gitee Account
Explore and code with more than 13.5 million developers,Free private repositories !:)
Sign up
文件
Clone or Download
snapstate.go 122.92 KB
Copy Edit Raw Blame History
tupelo-shen authored 2022-11-08 15:12 +08:00 . fix: overlord commit
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016-2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Package snapstate implements the manager and state aspects responsible for the installation and removal of snaps.
package snapstate
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strconv"
"strings"
"time"
"gitee.com/mysnapcore/mysnapd/asserts"
"gitee.com/mysnapcore/mysnapd/asserts/snapasserts"
"gitee.com/mysnapcore/mysnapd/boot"
"gitee.com/mysnapcore/mysnapd/client"
"gitee.com/mysnapcore/mysnapd/dirs"
"gitee.com/mysnapcore/mysnapd/features"
"gitee.com/mysnapcore/mysnapd/gadget"
"gitee.com/mysnapcore/mysnapd/i18n"
"gitee.com/mysnapcore/mysnapd/interfaces"
"gitee.com/mysnapcore/mysnapd/logger"
"gitee.com/mysnapcore/mysnapd/osutil"
"gitee.com/mysnapcore/mysnapd/overlord/auth"
"gitee.com/mysnapcore/mysnapd/overlord/configstate/config"
"gitee.com/mysnapcore/mysnapd/overlord/ifacestate/ifacerepo"
"gitee.com/mysnapcore/mysnapd/overlord/restart"
"gitee.com/mysnapcore/mysnapd/overlord/snapstate/backend"
"gitee.com/mysnapcore/mysnapd/overlord/state"
"gitee.com/mysnapcore/mysnapd/release"
"gitee.com/mysnapcore/mysnapd/snap"
"gitee.com/mysnapcore/mysnapd/snap/channel"
"gitee.com/mysnapcore/mysnapd/snapdenv"
"gitee.com/mysnapcore/mysnapd/store"
"gitee.com/mysnapcore/mysnapd/strutil"
)
// control flags for doInstall
const (
skipConfigure = 1 << iota
)
// control flags for "Configure()"
const (
IgnoreHookError = 1 << iota
TrackHookError
UseConfigDefaults
)
const (
BeginEdge = state.TaskSetEdge("begin")
BeforeHooksEdge = state.TaskSetEdge("before-hooks")
HooksEdge = state.TaskSetEdge("hooks")
BeforeMaybeRebootEdge = state.TaskSetEdge("before-maybe-reboot")
MaybeRebootEdge = state.TaskSetEdge("maybe-reboot")
MaybeRebootWaitEdge = state.TaskSetEdge("maybe-reboot-wait")
AfterMaybeRebootWaitEdge = state.TaskSetEdge("after-maybe-reboot-wait")
LastBeforeLocalModificationsEdge = state.TaskSetEdge("last-before-local-modifications")
)
const snapdDesktopIntegrationSnapID = "IrwRHakqtzhFRHJOOPxKVPU0Kk7Erhcu"
var ErrNothingToDo = errors.New("nothing to do")
var osutilCheckFreeSpace = osutil.CheckFreeSpace
// TestingLeaveOutKernelUpdateGadgetAssets can be used to simulate an upgrade
// from a broken snapd that does not generate a "update-gadget-assets" task.
// See LP:#1940553
var TestingLeaveOutKernelUpdateGadgetAssets bool = false
type minimalInstallInfo interface {
InstanceName() string
Type() snap.Type
SnapBase() string
DownloadSize() int64
Prereq(st *state.State) []string
}
type updateParamsFunc func(*snap.Info) (*RevisionOptions, Flags, *SnapState)
type readyUpdateInfo interface {
minimalInstallInfo
SnapSetupForUpdate(st *state.State, params updateParamsFunc, userID int, globalFlags *Flags) (*SnapSetup, *SnapState, error)
}
// ByType supports sorting by snap type. The most important types come first.
type byType []minimalInstallInfo
func (r byType) Len() int { return len(r) }
func (r byType) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byType) Less(i, j int) bool {
return r[i].Type().SortsBefore(r[j].Type())
}
type installSnapInfo struct {
*snap.Info
}
func (ins installSnapInfo) DownloadSize() int64 {
return ins.DownloadInfo.Size
}
// SnapBase returns the base snap of the snap.
func (ins installSnapInfo) SnapBase() string {
return ins.Base
}
func (ins installSnapInfo) Prereq(st *state.State) []string {
return getKeys(defaultProviderContentAttrs(st, ins.Info))
}
func (ins installSnapInfo) SnapSetupForUpdate(st *state.State, params updateParamsFunc, userID int, globalFlags *Flags) (*SnapSetup, *SnapState, error) {
update := ins.Info
revnoOpts, flags, snapst := params(update)
flags.IsAutoRefresh = globalFlags.IsAutoRefresh
flags, err := earlyChecks(st, snapst, update, flags)
if err != nil {
return nil, nil, err
}
snapUserID, err := userIDForSnap(st, snapst, userID)
if err != nil {
return nil, nil, err
}
providerContentAttrs := defaultProviderContentAttrs(st, update)
snapsup := SnapSetup{
Base: update.Base,
Prereq: getKeys(providerContentAttrs),
PrereqContentAttrs: providerContentAttrs,
Channel: revnoOpts.Channel,
CohortKey: revnoOpts.CohortKey,
UserID: snapUserID,
Flags: flags.ForSnapSetup(),
DownloadInfo: &update.DownloadInfo,
SideInfo: &update.SideInfo,
Type: update.Type(),
PlugsOnly: len(update.Slots) == 0,
InstanceKey: update.InstanceKey,
auxStoreInfo: auxStoreInfo{
Media: update.Media,
// XXX we store this for the benefit of old snapd
Website: update.Website(),
},
ExpectedProvenance: update.SnapProvenance,
}
snapsup.IgnoreRunning = globalFlags.IgnoreRunning
return &snapsup, snapst, nil
}
// pathInfo holds information about a path install
type pathInfo struct {
*snap.Info
path string
sideInfo *snap.SideInfo
}
func (i pathInfo) DownloadSize() int64 {
return i.Size
}
// SnapBase returns the base snap of the snap.
func (i pathInfo) SnapBase() string {
return i.Base
}
func (i pathInfo) Prereq(st *state.State) []string {
return getKeys(defaultProviderContentAttrs(st, i.Info))
}
func (i pathInfo) SnapSetupForUpdate(st *state.State, params updateParamsFunc, _ int, _ *Flags) (*SnapSetup, *SnapState, error) {
update := i.Info
_, flags, snapst := params(update)
providerContentAttrs := defaultProviderContentAttrs(st, update)
snapsup := SnapSetup{
Base: i.Base,
Prereq: getKeys(providerContentAttrs),
PrereqContentAttrs: providerContentAttrs,
SideInfo: i.sideInfo,
SnapPath: i.path,
Flags: flags.ForSnapSetup(),
Type: i.Type(),
PlugsOnly: len(i.Slots) == 0,
InstanceKey: i.InstanceKey,
}
return &snapsup, snapst, nil
}
// soundness check
var _ readyUpdateInfo = installSnapInfo{}
// InsufficientSpaceError represents an error where there is not enough disk
// space to perform an operation.
type InsufficientSpaceError struct {
// Path is the filesystem path checked for available disk space
Path string
// Snaps affected by the failing operation
Snaps []string
// Kind of the change that failed
ChangeKind string
// Message is optional, otherwise one is composed from the other information
Message string
}
func (e *InsufficientSpaceError) Error() string {
if e.Message != "" {
return e.Message
}
if len(e.Snaps) > 0 {
snaps := strings.Join(e.Snaps, ", ")
return fmt.Sprintf("insufficient space in %q to perform %q change for the following snaps: %s", e.Path, e.ChangeKind, snaps)
}
return fmt.Sprintf("insufficient space in %q", e.Path)
}
// safetyMarginDiskSpace returns size plus a safety margin (5Mb)
func safetyMarginDiskSpace(size uint64) uint64 {
return size + 5*1024*1024
}
func isParallelInstallable(snapsup *SnapSetup) error {
if snapsup.InstanceKey == "" {
return nil
}
if snapsup.Type == snap.TypeApp {
return nil
}
return fmt.Errorf("cannot install snap of type %v as %q", snapsup.Type, snapsup.InstanceName())
}
func optedIntoSnapdSnap(st *state.State) (bool, error) {
tr := config.NewTransaction(st)
experimentalAllowSnapd, err := features.Flag(tr, features.SnapdSnap)
if err != nil && !config.IsNoOption(err) {
return false, err
}
return experimentalAllowSnapd, nil
}
// refreshRetain returns refresh.retain value if set, or the default value (different for core and classic).
// It deals with potentially wrong type due to lax validation.
func refreshRetain(st *state.State) int {
var val interface{}
// due to lax validation of refresh.retain on set we might end up having a string representing a number here; handle it gracefully
// for backwards compatibility.
err := config.NewTransaction(st).Get("core", "refresh.retain", &val)
var retain int
if err == nil {
switch v := val.(type) {
// this is the expected value; confusingly, since we pass interface{} to Get(), we get json.Number type; if int reference was passed,
// we would get an int instead of json.Number.
case json.Number:
retain, err = strconv.Atoi(string(v))
// not really expected when requesting interface{}.
case int:
retain = v
// we can get string here due to lax validation of refresh.retain on Set in older releases.
case string:
retain, err = strconv.Atoi(v)
default:
logger.Noticef("internal error: refresh.retain system option has unexpected type: %T", v)
}
}
// this covers error from Get() and strconv above.
if err != nil && !config.IsNoOption(err) {
logger.Noticef("internal error: refresh.retain system option is not valid: %v", err)
}
// not set, use default value
if retain == 0 {
// on classic we only keep 2 copies by default
if release.OnClassic {
retain = 2
} else {
retain = 3
}
}
return retain
}
var excludeFromRefreshAppAwareness = func(t snap.Type) bool {
return t == snap.TypeSnapd || t == snap.TypeOS
}
func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int, fromChange string, inUseCheck func(snap.Type) (boot.InUseFunc, error)) (*state.TaskSet, error) {
// NB: we should strive not to need or propagate deviceCtx
// here, the resulting effects/changes were not pleasant at
// one point
tr := config.NewTransaction(st)
experimentalRefreshAppAwareness, err := features.Flag(tr, features.RefreshAppAwareness)
if err != nil && !config.IsNoOption(err) {
return nil, err
}
experimentalGateAutoRefreshHook, err := features.Flag(tr, features.GateAutoRefreshHook)
if err != nil && !config.IsNoOption(err) {
return nil, err
}
if snapsup.InstanceName() == "system" {
return nil, fmt.Errorf("cannot install reserved snap name 'system'")
}
if snapst.IsInstalled() && !snapst.Active {
return nil, fmt.Errorf("cannot update disabled snap %q", snapsup.InstanceName())
}
if snapst.IsInstalled() && !snapsup.Flags.Revert {
if inUseCheck == nil {
return nil, fmt.Errorf("internal error: doInstall: inUseCheck not provided for refresh")
}
}
if snapsup.Flags.Classic {
if !release.OnClassic {
return nil, fmt.Errorf("classic confinement is only supported on classic systems")
} else if !dirs.SupportsClassicConfinement() {
return nil, fmt.Errorf(i18n.G("classic confinement requires snaps under /snap or symlink from /snap to %s"), dirs.SnapMountDir)
}
}
if !snapst.IsInstalled() { // install?
// check that the snap command namespace doesn't conflict with an enabled alias
if err := checkSnapAliasConflict(st, snapsup.InstanceName()); err != nil {
return nil, err
}
}
if err := isParallelInstallable(snapsup); err != nil {
return nil, err
}
if err := checkChangeConflictIgnoringOneChange(st, snapsup.InstanceName(), snapst, fromChange); err != nil {
return nil, err
}
if snapst.IsInstalled() {
// consider also the current revision to set plugs-only hint
info, err := snapst.CurrentInfo()
if err != nil {
return nil, err
}
snapsup.PlugsOnly = snapsup.PlugsOnly && (len(info.Slots) == 0)
if experimentalRefreshAppAwareness && !excludeFromRefreshAppAwareness(snapsup.Type) && !snapsup.Flags.IgnoreRunning {
// Note that because we are modifying the snap state inside
// softCheckNothingRunningForRefresh, this block must be located
// after the conflict check done above.
if err := softCheckNothingRunningForRefresh(st, snapst, info); err != nil {
return nil, err
}
}
if experimentalGateAutoRefreshHook {
// If this snap was held, then remove it from snaps-hold.
if err := resetGatingForRefreshed(st, snapsup.InstanceName()); err != nil {
return nil, err
}
}
}
ts := state.NewTaskSet()
targetRevision := snapsup.Revision()
revisionStr := ""
if snapsup.SideInfo != nil {
revisionStr = fmt.Sprintf(" (%s)", targetRevision)
}
// check if we already have the revision locally (alters tasks)
revisionIsLocal := snapst.LastIndex(targetRevision) >= 0
prereq := st.NewTask("prerequisites", fmt.Sprintf(i18n.G("Ensure prerequisites for %q are available"), snapsup.InstanceName()))
prereq.Set("snap-setup", snapsup)
var prepare, prev *state.Task
fromStore := false
// if we have a local revision here we go back to that
if snapsup.SnapPath != "" || revisionIsLocal {
prepare = st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q%s"), snapsup.SnapPath, revisionStr))
} else {
fromStore = true
prepare = st.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q%s from channel %q"), snapsup.InstanceName(), revisionStr, snapsup.Channel))
}
prepare.Set("snap-setup", snapsup)
prepare.WaitFor(prereq)
tasks := []*state.Task{prereq, prepare}
addTask := func(t *state.Task) {
t.Set("snap-setup-task", prepare.ID())
t.WaitFor(prev)
tasks = append(tasks, t)
}
prev = prepare
var checkAsserts *state.Task
if fromStore {
// fetch and check assertions
checkAsserts = st.NewTask("validate-snap", fmt.Sprintf(i18n.G("Fetch and check assertions for snap %q%s"), snapsup.InstanceName(), revisionStr))
addTask(checkAsserts)
prev = checkAsserts
}
// mount
if !revisionIsLocal {
mount := st.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q%s"), snapsup.InstanceName(), revisionStr))
addTask(mount)
prev = mount
} else {
if snapsup.Flags.RemoveSnapPath {
// If the revision is local, we will not need the
// temporary snap. This can happen when
// e.g. side-loading a local revision again. The
// SnapPath is only needed in the "mount-snap" handler
// and that is skipped for local revisions.
if err := os.Remove(snapsup.SnapPath); err != nil {
return nil, err
}
}
}
// run refresh hooks when updating existing snap, otherwise run install hook further down.
runRefreshHooks := (snapst.IsInstalled() && !snapsup.Flags.Revert)
if runRefreshHooks {
preRefreshHook := SetupPreRefreshHook(st, snapsup.InstanceName())
addTask(preRefreshHook)
prev = preRefreshHook
}
if snapst.IsInstalled() {
// unlink-current-snap (will stop services for copy-data)
stop := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), snapsup.InstanceName()))
stop.Set("stop-reason", snap.StopReasonRefresh)
addTask(stop)
prev = stop
removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), snapsup.InstanceName()))
addTask(removeAliases)
prev = removeAliases
unlink := st.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapsup.InstanceName()))
addTask(unlink)
prev = unlink
}
// find out whether this is a core boot device
isCoreBoot, err := isCoreBootDevice(st)
if err != nil {
return nil, err
}
if isCoreBoot && (snapsup.Type == snap.TypeGadget || (snapsup.Type == snap.TypeKernel && !TestingLeaveOutKernelUpdateGadgetAssets)) {
// gadget update currently for core boot systems only
gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q%s"), snapsup.Type, snapsup.InstanceName(), revisionStr))
addTask(gadgetUpdate)
prev = gadgetUpdate
}
if isCoreBoot && snapsup.Type == snap.TypeGadget {
// kernel command line from gadget is for core boot systems only
gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from gadget %q%s"), snapsup.InstanceName(), revisionStr))
addTask(gadgetCmdline)
prev = gadgetCmdline
}
// copy-data (needs stopped services by unlink)
if !snapsup.Flags.Revert {
copyData := st.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), snapsup.InstanceName()))
addTask(copyData)
prev = copyData
}
// security
setupSecurity := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q%s security profiles"), snapsup.InstanceName(), revisionStr))
addTask(setupSecurity)
prev = setupSecurity
// finalize (wrappers+current symlink)
linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q%s available to the system"), snapsup.InstanceName(), revisionStr))
addTask(linkSnap)
prev = linkSnap
// auto-connections
autoConnect := st.NewTask("auto-connect", fmt.Sprintf(i18n.G("Automatically connect eligible plugs and slots of snap %q"), snapsup.InstanceName()))
addTask(autoConnect)
prev = autoConnect
// setup aliases
setAutoAliases := st.NewTask("set-auto-aliases", fmt.Sprintf(i18n.G("Set automatic aliases for snap %q"), snapsup.InstanceName()))
addTask(setAutoAliases)
prev = setAutoAliases
setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName()))
addTask(setupAliases)
prev = setupAliases
if isCoreBoot && snapsup.Type == snap.TypeSnapd {
// only run for core devices and the snapd snap, run late enough
// so that the task is executed by the new snapd
bootConfigUpdate := st.NewTask("update-managed-boot-config", fmt.Sprintf(i18n.G("Update managed boot config assets from %q%s"), snapsup.InstanceName(), revisionStr))
addTask(bootConfigUpdate)
prev = bootConfigUpdate
}
if runRefreshHooks {
postRefreshHook := SetupPostRefreshHook(st, snapsup.InstanceName())
addTask(postRefreshHook)
prev = postRefreshHook
}
var installHook *state.Task
// only run install hook if installing the snap for the first time
if !snapst.IsInstalled() {
installHook = SetupInstallHook(st, snapsup.InstanceName())
addTask(installHook)
prev = installHook
}
if snapsup.QuotaGroupName != "" {
quotaAddSnapTask, err := AddSnapToQuotaGroup(st, snapsup.InstanceName(), snapsup.QuotaGroupName)
if err != nil {
return nil, err
}
addTask(quotaAddSnapTask)
prev = quotaAddSnapTask
}
// run new services
startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q%s services"), snapsup.InstanceName(), revisionStr))
addTask(startSnapServices)
prev = startSnapServices
// Do not do that if we are reverting to a local revision
if snapst.IsInstalled() && !snapsup.Flags.Revert {
retain := refreshRetain(st)
// if we're not using an already present revision, account for the one being added
if snapst.LastIndex(targetRevision) == -1 {
retain-- // we're adding one
}
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(st, snapsup.InstanceName(), si.SnapID, si.Revision, snapsup.Type)
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
var inUse boot.InUseFunc
for i := 0; i <= currentIndex-retain; i++ {
if inUse == nil {
var err error
inUse, err = inUseCheck(snapsup.Type)
if err != nil {
return nil, err
}
}
si := seq[i]
if inUse(snapsup.InstanceName(), si.Revision) {
continue
}
ts := removeInactiveRevision(st, snapsup.InstanceName(), si.SnapID, si.Revision, snapsup.Type)
ts.WaitFor(prev)
tasks = append(tasks, ts.Tasks()...)
prev = tasks[len(tasks)-1]
}
addTask(st.NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", snapsup.InstanceName(), revisionStr)))
}
installSet := state.NewTaskSet(tasks...)
installSet.WaitAll(ts)
installSet.MarkEdge(prereq, BeginEdge)
installSet.MarkEdge(setupAliases, BeforeHooksEdge)
installSet.MarkEdge(setupSecurity, BeforeMaybeRebootEdge)
installSet.MarkEdge(linkSnap, MaybeRebootEdge)
installSet.MarkEdge(autoConnect, MaybeRebootWaitEdge)
installSet.MarkEdge(setAutoAliases, AfterMaybeRebootWaitEdge)
if installHook != nil {
installSet.MarkEdge(installHook, HooksEdge)
}
// if snap is being installed from the store, then the last task before
// any system modifications are done is check validate-snap, otherwise
// it's the prepare-snap
if checkAsserts != nil {
installSet.MarkEdge(checkAsserts, LastBeforeLocalModificationsEdge)
} else {
installSet.MarkEdge(prepare, LastBeforeLocalModificationsEdge)
}
if flags&skipConfigure != 0 {
return installSet, nil
}
ts.AddAllWithEdges(installSet)
// we do not support configuration for bases or the "snapd" snap yet
if snapsup.Type != snap.TypeBase && snapsup.Type != snap.TypeSnapd {
confFlags := 0
notCore := snapsup.InstanceName() != "core"
hasSnapID := snapsup.SideInfo != nil && snapsup.SideInfo.SnapID != ""
if !snapst.IsInstalled() && hasSnapID && notCore {
// installation, run configure using the gadget defaults
// if available, system config defaults (attached to
// "core") are consumed only during seeding, via an
// explicit configure step separate from installing
confFlags |= UseConfigDefaults
}
configSet := ConfigureSnap(st, snapsup.InstanceName(), confFlags)
configSet.WaitAll(ts)
ts.AddAll(configSet)
}
healthCheck := CheckHealthHook(st, snapsup.InstanceName(), snapsup.Revision())
healthCheck.WaitAll(ts)
ts.AddTask(healthCheck)
return ts, nil
}
// ConfigureSnap returns a set of tasks to configure snapName as done during installation/refresh.
func ConfigureSnap(st *state.State, snapName string, confFlags int) *state.TaskSet {
// This is slightly ugly, ideally we would check the type instead
// of hardcoding the name here. Unfortunately we do not have the
// type until we actually run the change.
if snapName == defaultCoreSnapName {
confFlags |= IgnoreHookError
confFlags |= TrackHookError
}
return Configure(st, snapName, nil, confFlags)
}
var Configure = func(st *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet {
panic("internal error: snapstate.Configure is unset")
}
var SetupInstallHook = func(st *state.State, snapName string) *state.Task {
panic("internal error: snapstate.SetupInstallHook is unset")
}
var SetupPreRefreshHook = func(st *state.State, snapName string) *state.Task {
panic("internal error: snapstate.SetupPreRefreshHook is unset")
}
var SetupPostRefreshHook = func(st *state.State, snapName string) *state.Task {
panic("internal error: snapstate.SetupPostRefreshHook is unset")
}
var SetupRemoveHook = func(st *state.State, snapName string) *state.Task {
panic("internal error: snapstate.SetupRemoveHook is unset")
}
var CheckHealthHook = func(st *state.State, snapName string, rev snap.Revision) *state.Task {
panic("internal error: snapstate.CheckHealthHook is unset")
}
var SetupGateAutoRefreshHook = func(st *state.State, snapName string) *state.Task {
panic("internal error: snapstate.SetupAutoRefreshGatingHook is unset")
}
var AddSnapToQuotaGroup = func(st *state.State, snapName string, quotaGroup string) (*state.Task, error) {
panic("internal error: snapstate.AddSnapToQuotaGroup is unset")
}
var generateSnapdWrappers = backend.GenerateSnapdWrappers
// FinishRestart will return a Retry error if there is a pending restart
// and a real error if anything went wrong (like a rollback across
// restarts).
// For snapd snap updates this will also rerun wrappers generation to fully
// catch up with any change.
func FinishRestart(task *state.Task, snapsup *SnapSetup) (err error) {
if snapdenv.Preseeding() {
// nothing to do when preseeding
return nil
}
if ok, _ := restart.Pending(task.State()); ok {
// don't continue until we are in the restarted snapd
task.Logf("Waiting for automatic snapd restart...")
return &state.Retry{}
}
if snapsup.Type == snap.TypeSnapd {
if os.Getenv("SNAPD_REVERT_TO_REV") != "" {
return fmt.Errorf("there was a snapd rollback across the restart")
}
// if we have restarted and snapd was refreshed, then we need to generate
// snapd wrappers again with current snapd, as the logic of generating
// wrappers may have changed between previous and new snapd code.
if !release.OnClassic {
snapdInfo, err := snap.ReadCurrentInfo(snapsup.SnapName())
if err != nil {
return fmt.Errorf("cannot get current snapd snap info: %v", err)
}
// TODO: if future changes to wrappers need one more snapd restart,
// then it should be handled here as well.
if err := generateSnapdWrappers(snapdInfo, nil); err != nil {
return err
}
}
}
// consider kernel and base
deviceCtx, err := DeviceCtx(task.State(), task, nil)
if err != nil {
return err
}
// Check if there was a rollback. A reboot can be triggered by:
// - core (old core16 world, system-reboot)
// - bootable base snap (new core18 world, system-reboot)
// - kernel
//
// If no mode and in ephemeral run modes (like install, recover)
// there can never be a rollback so we can skip the check there.
// For bases we do not reboot in classic.
//
// TODO: Detect "snapd" snap daemon-restarts here that
// fallback into the old version (once we have
// better snapd rollback support in core18).
//
// Applies only to core-like boot, except if classic with modes for
// base/core updates.
if deviceCtx.RunMode() && boot.SnapTypeParticipatesInBoot(snapsup.Type, deviceCtx) {
// get the name of the name relevant for booting
// based on the given type
model := deviceCtx.Model()
var bootName string
switch snapsup.Type {
case snap.TypeKernel:
bootName = model.Kernel()
case snap.TypeOS, snap.TypeBase:
bootName = "core"
if model.Base() != "" {
bootName = model.Base()
}
default:
return nil
}
// if it is not a snap related to our booting we are not
// interested
if snapsup.InstanceName() != bootName {
return nil
}
// compare what we think is "current" for snapd with what
// actually booted. The bootloader may revert on a failed
// boot from a bad os/base/kernel to a good one and in this
// case we need to catch this and error accordingly
current, err := boot.GetCurrentBoot(snapsup.Type, deviceCtx)
if err == boot.ErrBootNameAndRevisionNotReady {
return &state.Retry{After: 5 * time.Second}
}
if err != nil {
return err
}
if snapsup.InstanceName() != current.SnapName() || snapsup.SideInfo.Revision != current.SnapRevision() {
// TODO: make sure this revision gets ignored for
// automatic refreshes
return fmt.Errorf("cannot finish %s installation, there was a rollback across reboot", snapsup.InstanceName())
}
}
return nil
}
// FinishTaskWithRestart will finish a task that needs a restart, by
// setting its status and requesting a restart.
// It should usually be invoked returning its result immediately
// from the caller.
// TODO not implemented yet: we will return an error in the future if
// we want to hold the restart. We will also add post hold status and
// boot id information in the task in that case.
func FinishTaskWithRestart(task *state.Task, status state.Status, rt restart.RestartType,
rebootInfo *boot.RebootInfo) error {
// If system restart is requested, consider how the change the
// task belongs to is configured (system-restart-immediate) to
// choose whether request an immediate restart or not.
if rt == restart.RestartSystem {
chg := task.Change()
var immediate bool
if chg != nil {
// ignore errors intentionally, to follow
// RequestRestart itself which does not
// return errors. If the state is corrupt
// something else will error
chg.Get("system-restart-immediate", &immediate)
}
if immediate {
rt = restart.RestartSystemNow
}
}
task.SetStatus(status)
restart.Request(task.State(), rt, rebootInfo)
return nil
}
func contentAttr(attrer interfaces.Attrer) string {
var s string
err := attrer.Attr("content", &s)
if err != nil {
return ""
}
return s
}
// returns a map populated with content tags for which there is a content snap in the system
func contentIfaceAvailable(st *state.State) map[string]bool {
repo := ifacerepo.Get(st)
contentSlots := repo.AllSlots("content")
avail := make(map[string]bool, len(contentSlots))
for _, slot := range contentSlots {
contentTag := contentAttr(slot)
if contentTag == "" {
continue
}
avail[contentTag] = true
}
return avail
}
// defaultProviderContentAttrs takes a snap.Info and returns a map of
// default providers to the value of content attributes they should
// provide. Content attributes already provided by a snap in the system are omitted.
func defaultProviderContentAttrs(st *state.State, info *snap.Info) map[string][]string {
needed := snap.NeededDefaultProviders(info)
if len(needed) == 0 {
return nil
}
avail := contentIfaceAvailable(st)
out := make(map[string][]string)
for snapInstance, contentValues := range needed {
for _, content := range contentValues {
if !avail[content] {
out[snapInstance] = append(out[snapInstance], content)
}
}
}
return out
}
func getKeys(kv map[string][]string) []string {
keys := make([]string, 0, len(kv))
for key := range kv {
keys = append(keys, key)
}
return keys
}
// validateFeatureFlags validates the given snap only uses experimental
// features that are enabled by the user.
func validateFeatureFlags(st *state.State, info *snap.Info) error {
tr := config.NewTransaction(st)
if len(info.Layout) > 0 {
flag, err := features.Flag(tr, features.Layouts)
if err != nil {
return err
}
if !flag {
return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.layouts' to true")
}
}
if info.InstanceKey != "" {
flag, err := features.Flag(tr, features.ParallelInstances)
if err != nil {
return err
}
if !flag {
return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.parallel-instances' to true")
}
}
var hasUserService, usesDbusActivation bool
for _, app := range info.Apps {
if app.IsService() && app.DaemonScope == snap.UserDaemon {
hasUserService = true
}
if len(app.ActivatesOn) != 0 {
usesDbusActivation = true
}
}
if hasUserService {
flag, err := features.Flag(tr, features.UserDaemons)
if err != nil {
return err
}
// The snapd-desktop-integration snap is allowed to
// use user daemons, irrespective of the feature flag
// state.
//
// TODO: remove the special case once
// experimental.user-daemons is the default
if !flag && info.SnapID != snapdDesktopIntegrationSnapID {
return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.user-daemons' to true")
}
if !release.SystemctlSupportsUserUnits() {
return fmt.Errorf("user session daemons are not supported on this release")
}
}
if usesDbusActivation {
flag, err := features.Flag(tr, features.DbusActivation)
if err != nil {
return err
}
if !flag {
return fmt.Errorf("experimental feature disabled - test it by setting 'experimental.dbus-activation' to true")
}
}
return nil
}
func ensureInstallPreconditions(st *state.State, info *snap.Info, flags Flags, snapst *SnapState) (Flags, error) {
// if snap is allowed to be devmode via the dangerous model and it's
// confinement is indeed devmode, promote the flags.DevMode to true
if flags.ApplySnapDevMode && info.NeedsDevMode() {
// TODO: what about jail-mode? will we also allow putting devmode
// snaps (i.e. snaps with snap.yaml with confinement: devmode) into
// strict confinement via the model assertion?
flags.DevMode = true
}
if flags.Classic && !info.NeedsClassic() {
// snap does not require classic confinement, silently drop the flag
flags.Classic = false
}
if err := validateInfoAndFlags(info, snapst, flags); err != nil {
return flags, err
}
if err := validateFeatureFlags(st, info); err != nil {
return flags, err
}
if err := checkDBusServiceConflicts(st, info); err != nil {
return flags, err
}
return flags, nil
}
// InstallPath returns a set of tasks for installing a snap from a file path
// and the snap.Info for the given snap.
//
// Note that the state must be locked by the caller.
// The provided SideInfo can contain just a name which results in a
// local revision and sideloading, or full metadata in which case it
// the snap will appear as installed from the store.
func InstallPath(st *state.State, si *snap.SideInfo, path, instanceName, channel string, flags Flags) (*state.TaskSet, *snap.Info, error) {
if si.RealName == "" {
return nil, nil, fmt.Errorf("internal error: snap name to install %q not provided", path)
}
if flags.Lane != 0 {
return nil, nil, fmt.Errorf("transaction lane is unsupported in InstallPath")
}
if instanceName == "" {
instanceName = si.RealName
}
deviceCtx, err := DeviceCtxFromState(st, nil)
if err != nil {
return nil, nil, err
}
var snapst SnapState
err = Get(st, instanceName, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, nil, err
}
if si.SnapID != "" {
if si.Revision.Unset() {
return nil, nil, fmt.Errorf("internal error: snap id set to install %q but revision is unset", path)
}
}
channel, err = resolveChannel(instanceName, snapst.TrackingChannel, channel, deviceCtx)
if err != nil {
return nil, nil, err
}
var instFlags int
if flags.SkipConfigure {
// extract it as a doInstall flag, this is not passed
// into SnapSetup
instFlags |= skipConfigure
}
// It is ok do open the snap file here because we either
// have side info or the user passed --dangerous
info, container, err := backend.OpenSnapFile(path, si)
if err != nil {
return nil, nil, err
}
if err := validateContainer(container, info, logger.Noticef); err != nil {
return nil, nil, err
}
if err := snap.ValidateInstanceName(instanceName); err != nil {
return nil, nil, fmt.Errorf("invalid instance name: %v", err)
}
snapName, instanceKey := snap.SplitInstanceName(instanceName)
if info.SnapName() != snapName {
return nil, nil, fmt.Errorf("cannot install snap %q, the name does not match the metadata %q", instanceName, info.SnapName())
}
info.InstanceKey = instanceKey
flags, err = ensureInstallPreconditions(st, info, flags, &snapst)
if err != nil {
return nil, nil, err
}
// this might be a refresh; check the epoch before proceeding
if err := earlyEpochCheck(info, &snapst); err != nil {
return nil, nil, err
}
providerContentAttrs := defaultProviderContentAttrs(st, info)
snapsup := &SnapSetup{
Base: info.Base,
Prereq: getKeys(providerContentAttrs),
PrereqContentAttrs: providerContentAttrs,
SideInfo: si,
SnapPath: path,
Channel: channel,
Flags: flags.ForSnapSetup(),
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: info.InstanceKey,
}
ts, err := doInstall(st, &snapst, snapsup, instFlags, "", inUseFor(deviceCtx))
return ts, info, err
}
// TryPath returns a set of tasks for trying a snap from a file path.
// Note that the state must be locked by the caller.
func TryPath(st *state.State, name, path string, flags Flags) (*state.TaskSet, error) {
flags.TryMode = true
ts, _, err := InstallPath(st, &snap.SideInfo{RealName: name}, path, "", "", flags)
return ts, err
}
// Install returns a set of tasks for installing a snap.
// Note that the state must be locked by the caller.
//
// The returned TaskSet will contain a LastBeforeLocalModificationsEdge
// identifying the last task before the first task that introduces system
// modifications.
func Install(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) {
return InstallWithDeviceContext(ctx, st, name, opts, userID, flags, nil, "")
}
// InstallWithDeviceContext returns a set of tasks for installing a snap.
// It will query for the snap with the given deviceCtx.
// Note that the state must be locked by the caller.
//
// The returned TaskSet will contain a LastBeforeLocalModificationsEdge
// identifying the last task before the first task that introduces system
// modifications.
func InstallWithDeviceContext(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) {
if opts == nil {
opts = &RevisionOptions{}
}
if opts.CohortKey != "" && !opts.Revision.Unset() {
return nil, errors.New("cannot specify revision and cohort")
}
if flags.Lane != 0 {
return nil, fmt.Errorf("transaction lane is unsupported in InstallWithDeviceContext")
}
if opts.Channel == "" {
opts.Channel = "stable"
}
var snapst SnapState
err := Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
if snapst.IsInstalled() {
return nil, &snap.AlreadyInstalledError{Snap: name}
}
// need to have a model set before trying to talk the store
deviceCtx, err = DevicePastSeeding(st, deviceCtx)
if err != nil {
return nil, err
}
if err := snap.ValidateInstanceName(name); err != nil {
return nil, fmt.Errorf("invalid instance name: %v", err)
}
sar, err := installInfo(ctx, st, name, opts, userID, flags, deviceCtx)
if err != nil {
return nil, err
}
info := sar.Info
if flags.RequireTypeBase && info.Type() != snap.TypeBase && info.Type() != snap.TypeOS {
return nil, fmt.Errorf("unexpected snap type %q, instead of 'base'", info.Type())
}
flags, err = ensureInstallPreconditions(st, info, flags, &snapst)
if err != nil {
return nil, err
}
if err := checkDiskSpace(st, "install", []minimalInstallInfo{installSnapInfo{info}}, userID); err != nil {
return nil, err
}
providerContentAttrs := defaultProviderContentAttrs(st, info)
snapsup := &SnapSetup{
Channel: opts.Channel,
Base: info.Base,
Prereq: getKeys(providerContentAttrs),
PrereqContentAttrs: providerContentAttrs,
UserID: userID,
Flags: flags.ForSnapSetup(),
DownloadInfo: &info.DownloadInfo,
SideInfo: &info.SideInfo,
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: info.InstanceKey,
auxStoreInfo: auxStoreInfo{
Media: info.Media,
// XXX we store this for the benefit of old snapd
Website: info.Website(),
},
CohortKey: opts.CohortKey,
ExpectedProvenance: info.SnapProvenance,
}
if sar.RedirectChannel != "" {
snapsup.Channel = sar.RedirectChannel
}
return doInstall(st, &snapst, snapsup, 0, fromChange, nil)
}
// InstallPathMany returns a set of tasks for installing snaps from a file paths
// and snap.Infos.
//
// The state must be locked by the caller.
// The provided SideInfos can contain just a name which results in a
// local revision and sideloading, or full metadata in which case
// the snaps will appear as installed from the store.
func InstallPathMany(ctx context.Context, st *state.State, sideInfos []*snap.SideInfo, paths []string, userID int, flags *Flags) ([]*state.TaskSet, error) {
if flags == nil {
flags = &Flags{}
}
deviceCtx, err := DevicePastSeeding(st, nil)
if err != nil {
return nil, err
}
var updates []minimalInstallInfo
var names []string
stateByInstanceName := make(map[string]*SnapState, len(paths))
flagsByInstanceName := make(map[string]Flags, len(paths))
for i, path := range paths {
si := sideInfos[i]
name := si.RealName
info, container, err := backend.OpenSnapFile(path, si)
if err != nil {
return nil, err
}
if err := validateContainer(container, info, logger.Noticef); err != nil {
return nil, err
}
if err := snap.ValidateInstanceName(name); err != nil {
return nil, fmt.Errorf("invalid instance name: %v", err)
}
var snapst SnapState
if err = Get(st, name, &snapst); err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
flags, err := earlyChecks(st, &snapst, info, *flags)
if err != nil {
return nil, err
}
if !(flags.JailMode || flags.DevMode) {
flags.Classic = flags.Classic || snapst.Flags.Classic
}
updates = append(updates, pathInfo{Info: info, path: path, sideInfo: si})
names = append(names, name)
stateByInstanceName[name] = &snapst
flagsByInstanceName[name] = flags
}
if err := checkDiskSpace(st, "install", updates, userID); err != nil {
return nil, err
}
params := func(update *snap.Info) (*RevisionOptions, Flags, *SnapState) {
name := update.InstanceName()
return nil, flagsByInstanceName[name], stateByInstanceName[name]
}
_, tasksets, err := doUpdate(ctx, st, names, updates, params, userID, flags, deviceCtx, "")
if err != nil {
return nil, err
}
return tasksets, nil
}
// InstallMany installs everything from the given list of names. When specifying
// revisions, the checks against enforced validation sets are bypassed.
// Note that the state must be locked by the caller.
func InstallMany(st *state.State, names []string, revOpts []*RevisionOptions, userID int, flags *Flags) ([]string, []*state.TaskSet, error) {
if flags == nil {
flags = &Flags{}
}
// need to have a model set before trying to talk the store
deviceCtx, err := DevicePastSeeding(st, nil)
if err != nil {
return nil, nil, err
}
names = strutil.Deduplicate(names)
toInstall := make([]string, 0, len(names))
for _, name := range names {
var snapst SnapState
err := Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, nil, err
}
if snapst.IsInstalled() {
continue
}
if err := snap.ValidateInstanceName(name); err != nil {
return nil, nil, fmt.Errorf("invalid instance name: %v", err)
}
toInstall = append(toInstall, name)
}
user, err := userFromUserID(st, userID)
if err != nil {
return nil, nil, err
}
installs, err := installCandidates(st, toInstall, revOpts, "stable", user)
if err != nil {
return nil, nil, err
}
snapInfos := make([]minimalInstallInfo, len(installs))
for i, sar := range installs {
snapInfos[i] = installSnapInfo{sar.Info}
}
if err = checkDiskSpace(st, "install", snapInfos, userID); err != nil {
return nil, nil, err
}
// can only specify a lane when running multiple operations transactionally
if flags.Transaction != client.TransactionAllSnaps && flags.Lane != 0 {
return nil, nil, errors.New("cannot specify a lane without setting transaction to \"all-snaps\"")
}
var transactionLane int
if flags.Transaction == client.TransactionAllSnaps {
if flags.Lane != 0 {
transactionLane = flags.Lane
} else {
transactionLane = st.NewLane()
}
}
tasksets := make([]*state.TaskSet, 0, len(installs))
for _, sar := range installs {
info := sar.Info
var snapst SnapState
validatedFlags, err := ensureInstallPreconditions(st, info, *flags, &snapst)
if err != nil {
return nil, nil, err
}
channel := "stable"
if sar.RedirectChannel != "" {
channel = sar.RedirectChannel
}
providerContentAttrs := defaultProviderContentAttrs(st, info)
snapsup := &SnapSetup{
Channel: channel,
Base: info.Base,
Prereq: getKeys(providerContentAttrs),
PrereqContentAttrs: providerContentAttrs,
UserID: userID,
Flags: validatedFlags.ForSnapSetup(),
DownloadInfo: &info.DownloadInfo,
SideInfo: &info.SideInfo,
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: info.InstanceKey,
ExpectedProvenance: info.SnapProvenance,
}
ts, err := doInstall(st, &snapst, snapsup, 0, "", inUseFor(deviceCtx))
if err != nil {
return nil, nil, err
}
// If transactional, use a single lane for all snaps, so when
// one fails the changes for all affected snaps will be
// undone. Otherwise, have different lanes per snap so failures
// only affect the culprit snap.
if flags.Transaction == client.TransactionAllSnaps {
ts.JoinLane(transactionLane)
} else {
ts.JoinLane(st.NewLane())
}
tasksets = append(tasksets, ts)
}
return toInstall, tasksets, nil
}
// RefreshCandidates gets a list of candidates for update
// Note that the state must be locked by the caller.
func RefreshCandidates(st *state.State, user *auth.UserState) ([]*snap.Info, error) {
updates, _, _, err := refreshCandidates(context.TODO(), st, nil, nil, user, nil)
return updates, err
}
// ValidateRefreshes allows to hook validation into the handling of refresh candidates.
var ValidateRefreshes func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int, deviceCtx DeviceContext) (validated []*snap.Info, err error)
// 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(ctx context.Context, st *state.State, names []string, revOpts []*RevisionOptions, userID int, flags *Flags) ([]string, []*state.TaskSet, error) {
return updateManyFiltered(ctx, st, names, revOpts, userID, nil, flags, "")
}
// ResolveValidationSetsEnforcementError installs and updates snaps in order to
// meet the validation set constraints reported in the ValidationSetsValidationError..
func ResolveValidationSetsEnforcementError(ctx context.Context, st *state.State, valErr *snapasserts.ValidationSetsValidationError, pinnedSeqs map[string]int, userID int) ([]*state.TaskSet, []string, error) {
if len(valErr.InvalidSnaps) != 0 {
invSnaps := make([]string, 0, len(valErr.InvalidSnaps))
for invSnap := range valErr.InvalidSnaps {
invSnaps = append(invSnaps, invSnap)
}
return nil, nil, fmt.Errorf("cannot auto-resolve validation set constraints that require removing snaps: %s", strutil.Quoted(invSnaps))
}
affected := make([]string, 0, len(valErr.MissingSnaps)+len(valErr.WrongRevisionSnaps))
var tasksets []*state.TaskSet
// use the same lane for installing and refreshing so everything is reversed
lane := st.NewLane()
collectRevOpts := func(snapToRevToVss map[string]map[snap.Revision][]string) ([]string, []*RevisionOptions) {
var names []string
var revOpts []*RevisionOptions
for snapName, revAndVs := range snapToRevToVss {
for rev, valsets := range revAndVs {
vsKeys := make([]snapasserts.ValidationSetKey, 0, len(valsets))
for _, vs := range valsets {
vsKey := snapasserts.NewValidationSetKey(valErr.Sets[vs])
vsKeys = append(vsKeys, vsKey)
}
revOpts = append(revOpts, &RevisionOptions{Revision: rev, ValidationSets: vsKeys})
}
names = append(names, snapName)
}
return names, revOpts
}
if len(valErr.WrongRevisionSnaps) > 0 {
names, revOpts := collectRevOpts(valErr.WrongRevisionSnaps)
// we're targeting precise revisions so re-refreshes don't make sense. Refreshes
// between epochs should managed by through the validation sets
flags := &Flags{Transaction: client.TransactionAllSnaps, Lane: lane, NoReRefresh: true}
updated, tss, err := UpdateMany(ctx, st, names, revOpts, userID, flags)
if err != nil {
return nil, nil, fmt.Errorf("cannot auto-resolve enforcement constraints: %w", err)
}
tasksets = append(tasksets, tss...)
affected = append(affected, updated...)
}
if len(valErr.MissingSnaps) > 0 {
names, revOpts := collectRevOpts(valErr.MissingSnaps)
flags := &Flags{Transaction: client.TransactionAllSnaps, Lane: lane}
installed, tss, err := InstallMany(st, names, revOpts, userID, flags)
if err != nil {
return nil, nil, fmt.Errorf("cannot auto-resolve enforcement constraints: %w", err)
}
// updates should be done before the installs
for _, ts := range tss {
for _, prevTs := range tasksets {
ts.WaitAll(prevTs)
}
}
tasksets = append(tasksets, tss...)
affected = append(affected, installed...)
}
encodedAsserts := make(map[string][]byte, len(valErr.Sets))
for vsStr, vs := range valErr.Sets {
encodedAsserts[vsStr] = asserts.Encode(vs)
}
enforceTask := st.NewTask("enforce-validation-sets", "Enforce validation sets")
enforceTask.Set("validation-sets", encodedAsserts)
enforceTask.Set("pinned-sequence-numbers", pinnedSeqs)
enforceTask.Set("userID", userID)
for _, ts := range tasksets {
enforceTask.WaitAll(ts)
}
ts := state.NewTaskSet(enforceTask)
ts.JoinLane(lane)
tasksets = append(tasksets, ts)
return tasksets, affected, nil
}
// updateFilter is the type of function that can be passed to
// updateManyFromChange so it filters the updates.
//
// If the filter returns true, the update for that snap proceeds. If
// it returns false, the snap is removed from the list of updates to
// consider.
type updateFilter func(*snap.Info, *SnapState) bool
func rearrangeBaseKernelForSingleReboot(kernelTs, bootBaseTs *state.TaskSet) error {
haveBase, haveKernel := bootBaseTs != nil, kernelTs != nil
if !haveBase && !haveKernel {
// neither base nor kernel update
return nil
}
if haveBase != haveKernel {
// have one but not the other
return nil
}
// both kernel and boot base are being updated, reorder link-snap and
// auto-connect tasks from both snaps such that we end up with the
// following ordering:
//
// tasks of base and kernel ->
// link-snap(base) ->
// link-snap(kernel)(r) ->
// auto-connect(base) ->
// auto-connect(kernel) ->
// remaining tasks of base and kernel
//
// where (r) denotes the task that can effectively request a reboot
beforeLinkSnapKernel := kernelTs.MaybeEdge(BeforeMaybeRebootEdge)
linkSnapKernel := kernelTs.MaybeEdge(MaybeRebootEdge)
autoConnectKernel := kernelTs.MaybeEdge(MaybeRebootWaitEdge)
if linkSnapKernel == nil || autoConnectKernel == nil || beforeLinkSnapKernel == nil {
return fmt.Errorf("internal error: cannot identify link-snap or auto-connect or the preceding task for the kernel snap")
}
kernelLanes := linkSnapKernel.Lanes()
linkSnapBase := bootBaseTs.MaybeEdge(MaybeRebootEdge)
autoConnectBase := bootBaseTs.MaybeEdge(MaybeRebootWaitEdge)
afterAutoConnectBase := bootBaseTs.MaybeEdge(AfterMaybeRebootWaitEdge)
if linkSnapBase == nil || autoConnectBase == nil || afterAutoConnectBase == nil {
return fmt.Errorf("internal error: cannot identify link-snap or auto-connect or the following task for the base snap")
}
baseLanes := linkSnapBase.Lanes()
for _, lane := range kernelLanes {
linkSnapBase.JoinLane(lane)
autoConnectBase.JoinLane(lane)
}
for _, lane := range baseLanes {
linkSnapKernel.JoinLane(lane)
autoConnectKernel.JoinLane(lane)
}
// make link-snap base wait for the last task directly preceding
// link-snap of the kernel
linkSnapBase.WaitFor(beforeLinkSnapKernel)
// order: link-snap-base -> link-snap-kernel
linkSnapKernel.WaitFor(linkSnapBase)
// order: link-snap-kernel -> auto-connect-base
autoConnectBase.WaitFor(linkSnapKernel)
// order: auto-connect-base -> auto-connect-kernel
autoConnectKernel.WaitFor(autoConnectBase)
// make the first task after auto-connect base wait for auto-connect
// kernel, this task already waits for auto-connect of base
afterAutoConnectBase.WaitFor(autoConnectKernel)
// cannot-reboot indicates that a task cannot invoke a reboot
linkSnapBase.Set("cannot-reboot", true)
// first auto connect will wait for reboot, but the restart pending flag
// will be cleared for the second one that runs
return nil
}
func updateManyFiltered(ctx context.Context, st *state.State, names []string, revOpts []*RevisionOptions, userID int, filter updateFilter, flags *Flags, fromChange string) ([]string, []*state.TaskSet, error) {
if flags == nil {
flags = &Flags{}
}
user, err := userFromUserID(st, userID)
if err != nil {
return nil, nil, err
}
// need to have a model set before trying to talk the store
deviceCtx, err := DevicePastSeeding(st, nil)
if err != nil {
return nil, nil, err
}
names = strutil.Deduplicate(names)
refreshOpts := &store.RefreshOptions{IsAutoRefresh: flags.IsAutoRefresh}
updates, stateByInstanceName, ignoreValidation, err := refreshCandidates(ctx, st, names, revOpts, user, refreshOpts)
if err != nil {
return nil, nil, err
}
if filter != nil {
actual := updates[:0]
for _, update := range updates {
if filter(update, stateByInstanceName[update.InstanceName()]) {
actual = append(actual, update)
}
}
updates = actual
}
if ValidateRefreshes != nil && len(updates) != 0 {
updates, err = ValidateRefreshes(st, updates, ignoreValidation, userID, deviceCtx)
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)
}
}
params := func(update *snap.Info) (*RevisionOptions, Flags, *SnapState) {
snapst := stateByInstanceName[update.InstanceName()]
// setting options to what's in state as multi-refresh doesn't let you change these
opts := &RevisionOptions{
Channel: snapst.TrackingChannel,
CohortKey: snapst.CohortKey,
}
return opts, snapst.Flags, snapst
}
toUpdate := make([]minimalInstallInfo, len(updates))
for i, up := range updates {
toUpdate[i] = installSnapInfo{up}
}
// don't refresh held snaps in a general refresh
if len(names) == 0 {
toUpdate, err = filterHeldSnaps(st, toUpdate, flags)
if err != nil {
return nil, nil, err
}
}
if err = checkDiskSpace(st, "refresh", toUpdate, userID); err != nil {
return nil, nil, err
}
updated, tasksets, err := doUpdate(ctx, st, names, toUpdate, params, userID, flags, deviceCtx, fromChange)
if err != nil {
return nil, nil, err
}
tasksets = finalizeUpdate(st, tasksets, len(updates) > 0, updated, userID, flags)
return updated, tasksets, nil
}
// filterHeldSnaps filters held snaps from being updated in a general refresh.
func filterHeldSnaps(st *state.State, updates []minimalInstallInfo, flags *Flags) ([]minimalInstallInfo, error) {
holdLevel := HoldGeneral
if flags.IsAutoRefresh {
holdLevel = HoldAutoRefresh
}
heldSnaps, err := HeldSnaps(st, holdLevel)
if err != nil {
return nil, err
}
filteredUpdates := make([]minimalInstallInfo, 0, len(updates))
for _, update := range updates {
if !heldSnaps[update.InstanceName()] {
filteredUpdates = append(filteredUpdates, update)
}
}
return filteredUpdates, nil
}
func doUpdate(ctx context.Context, st *state.State, names []string, updates []minimalInstallInfo, params updateParamsFunc, userID int, globalFlags *Flags, deviceCtx DeviceContext, fromChange string) ([]string, []*state.TaskSet, error) {
if globalFlags == nil {
globalFlags = &Flags{}
}
tasksets := make([]*state.TaskSet, 0, len(updates)+2) // 1 for auto-aliases, 1 for re-refresh
refreshAll := len(names) == 0
var nameSet map[string]bool
if len(names) != 0 {
nameSet = make(map[string]bool, len(names))
for _, name := range names {
nameSet[name] = true
}
}
newAutoAliases, mustPruneAutoAliases, transferTargets, err := autoAliasesUpdate(st, names, updates)
if err != nil {
return nil, nil, err
}
reportUpdated := make(map[string]bool, len(updates))
var pruningAutoAliasesTs *state.TaskSet
if len(mustPruneAutoAliases) != 0 {
var err error
pruningAutoAliasesTs, err = applyAutoAliasesDelta(st, mustPruneAutoAliases, "prune", refreshAll, fromChange, func(snapName string, _ *state.TaskSet) {
if nameSet[snapName] {
reportUpdated[snapName] = true
}
})
if err != nil {
return nil, nil, err
}
tasksets = append(tasksets, pruningAutoAliasesTs)
}
// wait for the auto-alias prune tasks as needed
scheduleUpdate := func(snapName string, ts *state.TaskSet) {
if pruningAutoAliasesTs != nil && (mustPruneAutoAliases[snapName] != nil || transferTargets[snapName]) {
ts.WaitAll(pruningAutoAliasesTs)
}
reportUpdated[snapName] = true
}
// first snapd, core, kernel, bases, then rest
sort.Stable(byType(updates))
prereqs := make(map[string]*state.TaskSet)
waitPrereq := func(ts *state.TaskSet, prereqName string) {
preTs := prereqs[prereqName]
if preTs != nil {
ts.WaitAll(preTs)
}
}
var kernelTs, gadgetTs, bootBaseTs *state.TaskSet
// can only specify a lane when running multiple operations transactionally
if globalFlags.Transaction != client.TransactionAllSnaps && globalFlags.Lane != 0 {
return nil, nil, errors.New("cannot specify a lane without setting transaction to \"all-snaps\"")
}
// updates is sorted by kind so this will process first core
// and bases and then other snaps
var transactionLane int
if globalFlags.Transaction == client.TransactionAllSnaps {
if globalFlags.Lane != 0 {
transactionLane = globalFlags.Lane
} else {
transactionLane = st.NewLane()
}
}
for _, update := range updates {
snapsup, snapst, err := update.(readyUpdateInfo).SnapSetupForUpdate(st, params, userID, globalFlags)
if err != nil {
if refreshAll {
logger.Noticef("cannot update %q: %v", update.InstanceName(), err)
continue
}
return nil, nil, err
}
ts, err := doInstall(st, snapst, snapsup, 0, fromChange, inUseFor(deviceCtx))
if err != nil {
if refreshAll {
// doing "refresh all", just skip this snap
logger.Noticef("cannot refresh snap %q: %v", update.InstanceName(), err)
continue
}
return nil, nil, err
}
// If transactional, use a single lane for all snaps, so when
// one fails the changes for all affected snaps will be
// undone. Otherwise, have different lanes per snap so failures
// only affect the culprit snap.
if globalFlags.Transaction == client.TransactionAllSnaps {
ts.JoinLane(transactionLane)
} else {
ts.JoinLane(st.NewLane())
}
// because of the sorting of updates we fill prereqs
// first (if branch) and only then use it to setup
// waits (else branch)
typ := update.Type()
if typ == snap.TypeOS || typ == snap.TypeBase || typ == snap.TypeSnapd {
// prereq types come first in updates, we
// also assume bases don't have hooks, otherwise
// they would need to wait on core or snapd
prereqs[update.InstanceName()] = ts
if typ == snap.TypeBase {
waitPrereq(ts, "snapd")
}
} else {
// prereqs were processed already, wait for
// them as necessary for the other kind of
// snaps
// because "core" has type "os" it is ordered before all
// other updates, right after snapd, that introduces a
// dependency on "core" tasks for all other snaps, and
// thus preventing us from reordering them to perform a
// single reboot when both kernel and base (core in this
// case) are updated
waitPrereq(ts, defaultCoreSnapName)
waitPrereq(ts, "snapd")
if update.SnapBase() != "" {
// the kernel snap is ordered before the base,
// so its task will not have an implicit
// dependency on base update
waitPrereq(ts, update.SnapBase())
}
}
// keep track of kernel/gadget/base updates
switch typ {
case snap.TypeKernel:
kernelTs = ts
case snap.TypeGadget:
gadgetTs = ts
case snap.TypeBase:
if update.InstanceName() == deviceCtx.Model().Base() {
// only the boot base is relevant for reboots
bootBaseTs = ts
}
case snap.TypeOS:
// nothing to do, we cannot set up a single reboot with
// "core" due to all tasks of other snaps impolitely
// waiting for all tasks of "core", what makes us end up
// with a task wait loop
}
scheduleUpdate(update.InstanceName(), ts)
tasksets = append(tasksets, ts)
}
// Kernel must wait for gadget because the gadget may define
// new "$kernel:refs". Sorting the other way is impossible
// because a kernel with new kernel-assets would never refresh
// because the matching gadget could never get installed
// because the gadget always waits for the kernel and if the
// kernel aborts the wait tasks (the gadget) is put on "Hold".
if kernelTs != nil && gadgetTs != nil {
kernelTs.WaitAll(gadgetTs)
}
if deviceCtx.Model().Base() != "" && (gadgetTs == nil || enforcedSingleRebootForGadgetKernelBase) {
// reordering of kernel and base tasks is supported only on
// UC18+ devices
// this can only be done safely when the gadget is not a part of
// the same update, otherwise there will be a circular
// dependency, where gadget waits for base, kernel waits for
// gadget, but base waits for some of the kernel tasks
if err := rearrangeBaseKernelForSingleReboot(kernelTs, bootBaseTs); err != nil {
return nil, nil, err
}
}
if len(newAutoAliases) != 0 {
addAutoAliasesTs, err := applyAutoAliasesDelta(st, newAutoAliases, "refresh", refreshAll, fromChange, scheduleUpdate)
if err != nil {
return nil, nil, err
}
tasksets = append(tasksets, addAutoAliasesTs)
}
updated := make([]string, 0, len(reportUpdated))
for name := range reportUpdated {
updated = append(updated, name)
}
return updated, tasksets, nil
}
func finalizeUpdate(st *state.State, tasksets []*state.TaskSet, hasUpdates bool, updated []string, userID int, globalFlags *Flags) []*state.TaskSet {
if hasUpdates && !globalFlags.NoReRefresh {
// re-refresh will check the lanes to decide what to
// _actually_ re-refresh, but it'll be a subset of updated
// (and equal to updated if nothing goes wrong)
sort.Strings(updated)
rerefresh := st.NewTask("check-rerefresh", fmt.Sprintf("Handling re-refresh of %s as needed", strutil.Quoted(updated)))
rerefresh.Set("rerefresh-setup", reRefreshSetup{
UserID: userID,
Flags: globalFlags,
})
tasksets = append(tasksets, state.NewTaskSet(rerefresh))
}
return tasksets
}
func applyAutoAliasesDelta(st *state.State, delta map[string][]string, op string, refreshAll bool, fromChange string, linkTs func(instanceName string, ts *state.TaskSet)) (*state.TaskSet, error) {
applyTs := state.NewTaskSet()
kind := "refresh-aliases"
msg := i18n.G("Refresh aliases for snap %q")
if op == "prune" {
kind = "prune-auto-aliases"
msg = i18n.G("Prune automatic aliases for snap %q")
}
for instanceName, aliases := range delta {
if err := checkChangeConflictIgnoringOneChange(st, instanceName, nil, fromChange); err != nil {
if refreshAll {
// doing "refresh all", just skip this snap
logger.Noticef("cannot %s automatic aliases for snap %q: %v", op, instanceName, err)
continue
}
return nil, err
}
snapName, instanceKey := snap.SplitInstanceName(instanceName)
snapsup := &SnapSetup{
SideInfo: &snap.SideInfo{RealName: snapName},
InstanceKey: instanceKey,
}
alias := st.NewTask(kind, fmt.Sprintf(msg, snapsup.InstanceName()))
alias.Set("snap-setup", &snapsup)
if op == "prune" {
alias.Set("aliases", aliases)
}
ts := state.NewTaskSet(alias)
linkTs(instanceName, ts)
applyTs.AddAll(ts)
}
return applyTs, nil
}
func autoAliasesUpdate(st *state.State, names []string, updates []minimalInstallInfo) (changed map[string][]string, mustPrune map[string][]string, transferTargets map[string]bool, err error) {
changed, dropped, err := autoAliasesDelta(st, nil)
if err != nil {
if len(names) != 0 {
// not "refresh all", error
return nil, nil, nil, err
}
// log and continue
logger.Noticef("cannot find the delta for automatic aliases for some snaps: %v", err)
}
refreshAll := len(names) == 0
// dropped alias -> snapName
droppedAliases := make(map[string][]string, len(dropped))
for instanceName, aliases := range dropped {
for _, alias := range aliases {
droppedAliases[alias] = append(droppedAliases[alias], instanceName)
}
}
// filter changed considering only names if set:
// we add auto-aliases only for mentioned snaps
if !refreshAll && len(changed) != 0 {
filteredChanged := make(map[string][]string, len(changed))
for _, name := range names {
if changed[name] != nil {
filteredChanged[name] = changed[name]
}
}
changed = filteredChanged
}
// mark snaps that are sources or target of transfers
transferSources := make(map[string]bool, len(dropped))
transferTargets = make(map[string]bool, len(changed))
for instanceName, aliases := range changed {
for _, alias := range aliases {
if sources := droppedAliases[alias]; len(sources) != 0 {
transferTargets[instanceName] = true
for _, source := range sources {
transferSources[source] = true
}
}
}
}
// snaps with updates
updating := make(map[string]bool, len(updates))
for _, info := range updates {
updating[info.InstanceName()] = true
}
// add explicitly auto-aliases only for snaps that are not updated
for instanceName := range changed {
if updating[instanceName] {
delete(changed, instanceName)
}
}
// prune explicitly auto-aliases only for snaps that are mentioned
// and not updated OR the source of transfers
mustPrune = make(map[string][]string, len(dropped))
for instanceName := range transferSources {
mustPrune[instanceName] = dropped[instanceName]
}
if refreshAll {
for instanceName, aliases := range dropped {
if !updating[instanceName] {
mustPrune[instanceName] = aliases
}
}
} else {
for _, name := range names {
if !updating[name] && dropped[name] != nil {
mustPrune[name] = dropped[name]
}
}
}
return changed, mustPrune, transferTargets, nil
}
// resolveChannel returns the effective channel to use, based on the requested
// channel and constrains set by device model, or an error if switching to
// requested channel is forbidden.
func resolveChannel(snapName, oldChannel, newChannel string, deviceCtx DeviceContext) (effectiveChannel string, err error) {
if newChannel == "" {
return "", nil
}
// ensure we do not switch away from the kernel-track in the model
model := deviceCtx.Model()
var pinnedTrack, which string
if snapName == model.Kernel() && model.KernelTrack() != "" {
pinnedTrack, which = model.KernelTrack(), "kernel"
}
if snapName == model.Gadget() && model.GadgetTrack() != "" {
pinnedTrack, which = model.GadgetTrack(), "gadget"
}
if pinnedTrack == "" {
// no pinned track
return channel.Resolve(oldChannel, newChannel)
}
// channel name is valid and consist of risk level or
// risk/branch only, do the right thing and default to risk (or
// risk/branch) within the pinned track
resChannel, err := channel.ResolvePinned(pinnedTrack, newChannel)
if err == channel.ErrPinnedTrackSwitch {
// switching to a different track is not allowed
return "", fmt.Errorf("cannot switch from %s track %q as specified for the (device) model to %q", which, pinnedTrack, newChannel)
}
if err != nil {
return "", err
}
return resChannel, nil
}
var errRevisionSwitch = errors.New("cannot switch revision")
func switchSummary(snap, chanFrom, chanTo, cohFrom, cohTo string) string {
if cohFrom != cohTo {
if cohTo == "" {
// leave cohort
if chanFrom == chanTo {
return fmt.Sprintf(i18n.G("Switch snap %q away from cohort %q"),
snap, strutil.ElliptLeft(cohFrom, 10))
}
if chanFrom == "" {
return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and away from cohort %q"),
snap, chanTo, strutil.ElliptLeft(cohFrom, 10),
)
}
return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and away from cohort %q"),
snap, chanFrom, chanTo, strutil.ElliptLeft(cohFrom, 10),
)
}
if cohFrom == "" {
// moving into a cohort
if chanFrom == chanTo {
return fmt.Sprintf(i18n.G("Switch snap %q from no cohort to %q"),
snap, strutil.ElliptLeft(cohTo, 10))
}
if chanFrom == "" {
return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and from no cohort to %q"),
snap, chanTo, strutil.ElliptLeft(cohTo, 10),
)
}
// chanTo == "" is not interesting
return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and from no cohort to %q"),
snap, chanFrom, chanTo, strutil.ElliptLeft(cohTo, 10),
)
}
if chanFrom == chanTo {
return fmt.Sprintf(i18n.G("Switch snap %q from cohort %q to %q"),
snap, strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10))
}
if chanFrom == "" {
return fmt.Sprintf(i18n.G("Switch snap %q to channel %q and from cohort %q to %q"),
snap, chanTo, strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10),
)
}
return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q and from cohort %q to %q"),
snap, chanFrom, chanTo,
strutil.ElliptLeft(cohFrom, 10), strutil.ElliptLeft(cohTo, 10),
)
}
if chanFrom == "" {
return fmt.Sprintf(i18n.G("Switch snap %q to channel %q"),
snap, chanTo)
}
if chanFrom != chanTo {
return fmt.Sprintf(i18n.G("Switch snap %q from channel %q to %q"),
snap, chanFrom, chanTo)
}
// a no-change switch is accepted for idempotency
return "No change switch (no-op)"
}
// Switch switches a snap to a new channel and/or cohort
func Switch(st *state.State, name string, opts *RevisionOptions) (*state.TaskSet, error) {
if opts == nil {
opts = &RevisionOptions{}
}
if !opts.Revision.Unset() {
return nil, errRevisionSwitch
}
var snapst SnapState
err := Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
if !snapst.IsInstalled() {
return nil, &snap.NotInstalledError{Snap: name}
}
if err := CheckChangeConflict(st, name, nil); err != nil {
return nil, err
}
deviceCtx, err := DeviceCtxFromState(st, nil)
if err != nil {
return nil, err
}
opts.Channel, err = resolveChannel(name, snapst.TrackingChannel, opts.Channel, deviceCtx)
if err != nil {
return nil, err
}
snapsup := &SnapSetup{
SideInfo: snapst.CurrentSideInfo(),
InstanceKey: snapst.InstanceKey,
// set the from state (i.e. no change), they are overridden from opts as needed below
CohortKey: snapst.CohortKey,
Channel: snapst.TrackingChannel,
}
if opts.Channel != "" {
snapsup.Channel = opts.Channel
}
if opts.CohortKey != "" {
snapsup.CohortKey = opts.CohortKey
}
if opts.LeaveCohort {
snapsup.CohortKey = ""
}
summary := switchSummary(snapsup.InstanceName(), snapst.TrackingChannel, snapsup.Channel, snapst.CohortKey, snapsup.CohortKey)
switchSnap := st.NewTask("switch-snap", summary)
switchSnap.Set("snap-setup", &snapsup)
return state.NewTaskSet(switchSnap), nil
}
// RevisionOptions control the selection of a snap revision.
type RevisionOptions struct {
Channel string
Revision snap.Revision
ValidationSets []snapasserts.ValidationSetKey
CohortKey string
LeaveCohort bool
}
// Update initiates a change updating a snap.
// Note that the state must be locked by the caller.
//
// The returned TaskSet can contain a LastBeforeLocalModificationsEdge
// identifying the last task before the first task that introduces system
// modifications. If no such edge is set, then none of the tasks introduce
// system modifications.
func Update(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) {
return UpdateWithDeviceContext(st, name, opts, userID, flags, nil, "")
}
// UpdateWithDeviceContext initiates a change updating a snap.
// It will query for the snap with the given deviceCtx.
// Note that the state must be locked by the caller.
//
// The returned TaskSet can contain a LastBeforeLocalModificationsEdge
// identifying the last task before the first task that introduces system
// modifications. If no such edge is set, then none of the tasks introduce
// system modifications.
func UpdateWithDeviceContext(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) {
if opts == nil {
opts = &RevisionOptions{}
}
var snapst SnapState
err := Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
if !snapst.IsInstalled() {
return nil, &snap.NotInstalledError{Snap: name}
}
// FIXME: snaps that are not active are skipped for now
// until we know what we want to do
if !snapst.Active {
return nil, fmt.Errorf("refreshing disabled snap %q not supported", name)
}
// need to have a model set before trying to talk the store
deviceCtx, err = DevicePastSeeding(st, deviceCtx)
if err != nil {
return nil, err
}
opts.Channel, err = resolveChannel(name, snapst.TrackingChannel, opts.Channel, deviceCtx)
if err != nil {
return nil, err
}
if opts.Channel == "" {
// default to tracking the same channel
opts.Channel = snapst.TrackingChannel
}
if opts.CohortKey == "" {
// default to being in the same cohort
opts.CohortKey = snapst.CohortKey
}
if opts.LeaveCohort {
opts.CohortKey = ""
}
// TODO: make flags be per revision to avoid this logic (that
// leaves corner cases all over the place)
if !(flags.JailMode || flags.DevMode) {
flags.Classic = flags.Classic || snapst.Flags.Classic
}
var updates []*snap.Info
info, infoErr := infoForUpdate(st, &snapst, name, opts, userID, flags, deviceCtx)
switch infoErr {
case nil:
updates = append(updates, info)
case store.ErrNoUpdateAvailable:
// there may be some new auto-aliases
default:
return nil, infoErr
}
toUpdate := make([]minimalInstallInfo, len(updates))
for i, up := range updates {
toUpdate[i] = installSnapInfo{up}
}
if err = checkDiskSpace(st, "refresh", toUpdate, userID); err != nil {
return nil, err
}
params := func(update *snap.Info) (*RevisionOptions, Flags, *SnapState) {
return opts, flags, &snapst
}
_, tts, err := doUpdate(context.TODO(), st, []string{name}, toUpdate, params, userID, &flags, deviceCtx, fromChange)
if err != nil {
return nil, err
}
// see if we need to switch the channel or cohort, or toggle ignore-validation
switchChannel := snapst.TrackingChannel != opts.Channel
switchCohortKey := snapst.CohortKey != opts.CohortKey
toggleIgnoreValidation := snapst.IgnoreValidation != flags.IgnoreValidation
if infoErr == store.ErrNoUpdateAvailable && (switchChannel || switchCohortKey || toggleIgnoreValidation) {
if err := checkChangeConflictIgnoringOneChange(st, name, nil, fromChange); err != nil {
return nil, err
}
snapsup := &SnapSetup{
SideInfo: snapst.CurrentSideInfo(),
Flags: snapst.Flags.ForSnapSetup(),
InstanceKey: snapst.InstanceKey,
Type: snap.Type(snapst.SnapType),
CohortKey: opts.CohortKey,
}
if switchChannel || switchCohortKey {
// update the tracked channel and cohort
snapsup.Channel = opts.Channel
snapsup.CohortKey = opts.CohortKey
// Update the current snap channel as well. This ensures that
// the UI displays the right values.
snapsup.SideInfo.Channel = opts.Channel
summary := switchSummary(snapsup.InstanceName(), snapst.TrackingChannel, opts.Channel, snapst.CohortKey, opts.CohortKey)
switchSnap := st.NewTask("switch-snap-channel", summary)
switchSnap.Set("snap-setup", &snapsup)
switchSnapTs := state.NewTaskSet(switchSnap)
for _, ts := range tts {
switchSnapTs.WaitAll(ts)
}
tts = append(tts, switchSnapTs)
}
if toggleIgnoreValidation {
snapsup.IgnoreValidation = flags.IgnoreValidation
toggle := st.NewTask("toggle-snap-flags", fmt.Sprintf(i18n.G("Toggle snap %q flags"), snapsup.InstanceName()))
toggle.Set("snap-setup", &snapsup)
toggleTs := state.NewTaskSet(toggle)
for _, ts := range tts {
toggleTs.WaitAll(ts)
}
tts = append(tts, toggleTs)
}
}
if len(tts) == 0 && len(updates) == 0 {
// really nothing to do, return the original no-update-available error
return nil, infoErr
}
tts = finalizeUpdate(st, tts, len(updates) > 0, []string{name}, userID, &flags)
flat := state.NewTaskSet()
for _, ts := range tts {
// The tasksets we get from "doUpdate" contain important
// "TaskEdge" information that is needed for "Remodel".
// To preserve those we need to use "AddAllWithEdges()".
if err := flat.AddAllWithEdges(ts); err != nil {
return nil, err
}
}
return flat, nil
}
func infoForUpdate(st *state.State, snapst *SnapState, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext) (*snap.Info, error) {
if opts.Revision.Unset() {
// good ol' refresh
info, err := updateInfo(st, snapst, opts, userID, flags, deviceCtx)
if err != nil {
return nil, err
}
if ValidateRefreshes != nil && !flags.IgnoreValidation {
_, err := ValidateRefreshes(st, []*snap.Info{info}, nil, userID, deviceCtx)
if err != nil {
return nil, err
}
}
return info, nil
}
var sideInfo *snap.SideInfo
for _, si := range snapst.Sequence {
if si.Revision == opts.Revision {
sideInfo = si
break
}
}
if sideInfo == nil {
// refresh from given revision from store
return updateToRevisionInfo(st, snapst, opts, userID, flags, deviceCtx)
}
// refresh-to-local, this assumes the snap revision is mounted
return readInfo(name, sideInfo, errorOnBroken)
}
// AutoRefreshAssertions allows to hook fetching of important assertions
// into the Autorefresh function.
var AutoRefreshAssertions func(st *state.State, userID int) error
var AddCurrentTrackingToValidationSetsStack func(st *state.State) error
var RestoreValidationSetsTracking func(st *state.State) error
// AutoRefresh is the wrapper that will do a refresh of all the installed
// snaps on the system. In addition to that it will also refresh important
// assertions.
func AutoRefresh(ctx context.Context, st *state.State) ([]string, []*state.TaskSet, error) {
userID := 0
if AutoRefreshAssertions != nil {
// TODO: do something else if features.GateAutoRefreshHook is active
// since some snaps may be held and not refreshed.
if err := AutoRefreshAssertions(st, userID); err != nil {
return nil, nil, err
}
}
tr := config.NewTransaction(st)
gateAutoRefreshHook, err := features.Flag(tr, features.GateAutoRefreshHook)
if err != nil && !config.IsNoOption(err) {
return nil, nil, err
}
if !gateAutoRefreshHook {
// old-style refresh (gate-auto-refresh-hook feature disabled)
return UpdateMany(ctx, st, nil, nil, userID, &Flags{IsAutoRefresh: true})
}
// TODO: rename to autoRefreshTasks when old auto refresh logic gets removed.
return autoRefreshPhase1(ctx, st, "")
}
// autoRefreshPhase1 creates gate-auto-refresh hooks and conditional-auto-refresh
// task that initiates actual refresh. forGatingSnap is optional and limits auto-refresh
// to the snaps affecting the given snap only; it defaults to all snaps if nil.
// The state needs to be locked by the caller.
func autoRefreshPhase1(ctx context.Context, st *state.State, forGatingSnap string) ([]string, []*state.TaskSet, error) {
user, err := userFromUserID(st, 0)
if err != nil {
return nil, nil, err
}
refreshOpts := &store.RefreshOptions{IsAutoRefresh: true}
// XXX: should we skip refreshCandidates if forGatingSnap isn't empty (meaning we're handling proceed from a snap)?
candidates, snapstateByInstance, ignoreValidationByInstanceName, err := refreshCandidates(ctx, st, nil, nil, user, refreshOpts)
if err != nil {
// XXX: should we reset "refresh-candidates" to nil in state for some types
// of errors?
return nil, nil, err
}
deviceCtx, err := DeviceCtxFromState(st, nil)
if err != nil {
return nil, nil, err
}
hints, err := refreshHintsFromCandidates(st, candidates, ignoreValidationByInstanceName, deviceCtx)
if err != nil {
return nil, nil, err
}
st.Set("refresh-candidates", hints)
// prune affecting snaps that are not in refresh candidates from hold state.
if err := pruneGating(st, hints); err != nil {
return nil, nil, err
}
updates := make([]string, 0, len(hints))
// check conflicts
fromChange := ""
for _, up := range candidates {
if _, ok := hints[up.InstanceName()]; !ok {
// filtered out by refreshHintsFromCandidates
continue
}
snapst := snapstateByInstance[up.InstanceName()]
if err := checkChangeConflictIgnoringOneChange(st, up.InstanceName(), snapst, fromChange); err != nil {
logger.Noticef("cannot refresh snap %q: %v", up.InstanceName(), err)
} else {
updates = append(updates, up.InstanceName())
}
}
if forGatingSnap != "" {
var gatingSnapHasUpdate bool
for _, up := range updates {
if up == forGatingSnap {
gatingSnapHasUpdate = true
break
}
}
if !gatingSnapHasUpdate {
return nil, nil, nil
}
}
if len(updates) == 0 {
return nil, nil, nil
}
// all snaps in updates are now considered to be operated on and should
// provoke conflicts until updated or until we know (after running
// gate-auto-refresh hooks) that some are not going to be updated
// and can stop conflicting.
affectedSnaps, err := affectedByRefresh(st, updates)
if err != nil {
return nil, nil, err
}
// only used if forGatingSnap != ""
var snapsAffectingGatingSnap map[string]bool
// if specific gating snap was given, drop other affected snaps unless
// they are affected by same updates as forGatingSnap.
if forGatingSnap != "" {
snapsAffectingGatingSnap = affectedSnaps[forGatingSnap].AffectingSnaps
// check if there is an intersection between affecting snaps of this
// forGatingSnap and other gating snaps. If so, we need to run
// their gate-auto-refresh hooks as well.
for gatingSnap, affectedInfo := range affectedSnaps {
if gatingSnap == forGatingSnap {
continue
}
var found bool
for affectingSnap := range affectedInfo.AffectingSnaps {
if snapsAffectingGatingSnap[affectingSnap] {
found = true
break
}
}
if !found {
delete(affectedSnaps, gatingSnap)
}
}
}
var hooks *state.TaskSet
if len(affectedSnaps) > 0 {
affected := make([]string, 0, len(affectedSnaps))
for snapName := range affectedSnaps {
affected = append(affected, snapName)
}
sort.Strings(affected)
hooks = createGateAutoRefreshHooks(st, affected)
}
// gate-auto-refresh hooks, followed by conditional-auto-refresh task waiting
// for all hooks.
ar := st.NewTask("conditional-auto-refresh", "Run auto-refresh for ready snaps")
tss := []*state.TaskSet{state.NewTaskSet(ar)}
if hooks != nil {
ar.WaitAll(hooks)
tss = append(tss, hooks)
}
// return all names as potentially getting updated even though some may be
// held.
names := make([]string, 0, len(updates))
toUpdate := make(map[string]*refreshCandidate, len(updates))
for _, up := range updates {
// if specific gating snap was requested, filter out updates.
if forGatingSnap != "" && forGatingSnap != up {
if !snapsAffectingGatingSnap[up] {
continue
}
}
names = append(names, up)
toUpdate[up] = hints[up]
}
// store the list of snaps to update on the conditional-auto-refresh task
// (this may be a subset of refresh-candidates due to conflicts).
ar.Set("snaps", toUpdate)
// return all names as potentially getting updated even though some may be
// held.
sort.Strings(names)
return names, tss, nil
}
// autoRefreshPhase2 creates tasks for refreshing snaps from updates.
func autoRefreshPhase2(ctx context.Context, st *state.State, updates []*refreshCandidate, fromChange string) ([]*state.TaskSet, error) {
flags := &Flags{IsAutoRefresh: true}
userID := 0
deviceCtx, err := DeviceCtx(st, nil, nil)
if err != nil {
return nil, err
}
toUpdate := make([]minimalInstallInfo, len(updates))
for i, up := range updates {
toUpdate[i] = up
}
if err := checkDiskSpace(st, "refresh", toUpdate, 0); err != nil {
return nil, err
}
updated, tasksets, err := doUpdate(ctx, st, nil, toUpdate, nil, userID, flags, deviceCtx, fromChange)
if err != nil {
return nil, err
}
tasksets = finalizeUpdate(st, tasksets, len(updates) > 0, updated, userID, flags)
return tasksets, nil
}
// checkDiskSpace checks if there is enough space for the requested snaps and their prerequisites
func checkDiskSpace(st *state.State, changeKind string, infos []minimalInstallInfo, userID int) error {
var featFlag features.SnapdFeature
switch changeKind {
case "install":
featFlag = features.CheckDiskSpaceInstall
case "refresh":
featFlag = features.CheckDiskSpaceRefresh
default:
return fmt.Errorf("cannot check disk space for invalid change kind %q", changeKind)
}
tr := config.NewTransaction(st)
enabled, err := features.Flag(tr, featFlag)
if err != nil && !config.IsNoOption(err) {
return err
}
if !enabled {
return nil
}
totalSize, err := installSize(st, infos, userID)
if err != nil {
return err
}
requiredSpace := safetyMarginDiskSpace(totalSize)
path := dirs.SnapdStateDir(dirs.GlobalRootDir)
if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
snaps := make([]string, len(infos))
for i, up := range infos {
snaps[i] = up.InstanceName()
}
if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
return &InsufficientSpaceError{
Path: path,
Snaps: snaps,
ChangeKind: changeKind,
}
}
return err
}
return nil
}
// MigrateHome migrates a set of snaps to use a ~/Snap sub-directory as HOME.
// The state must be locked by the caller.
func MigrateHome(st *state.State, snaps []string) ([]*state.TaskSet, error) {
tr := config.NewTransaction(st)
moveDir, err := features.Flag(tr, features.MoveSnapHomeDir)
if err != nil {
return nil, err
}
if !moveDir {
_, confName := features.MoveSnapHomeDir.ConfigOption()
return nil, fmt.Errorf("cannot migrate to ~/Snap: flag %q is not set", confName)
}
allSnaps, err := All(st)
if err != nil {
return nil, err
}
for _, name := range snaps {
if snapst, ok := allSnaps[name]; !ok {
return nil, snap.NotInstalledError{Snap: name}
} else if snapst.MigratedToExposedHome {
return nil, fmt.Errorf("cannot migrate %q to ~/Snap: already migrated", name)
}
}
var tss []*state.TaskSet
for _, name := range snaps {
si := allSnaps[name].CurrentSideInfo()
snapsup := &SnapSetup{
SideInfo: si,
}
var tasks []*state.Task
prepare := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), name, si.Revision))
prepare.Set("snap-setup", snapsup)
tasks = append(tasks, prepare)
prev := prepare
addTask := func(t *state.Task) {
t.Set("snap-setup-task", prepare.ID())
t.WaitFor(prev)
tasks = append(tasks, t)
}
stop := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name))
stop.Set("stop-reason", "home-migration")
addTask(stop)
prev = stop
unlink := st.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), name))
addTask(unlink)
prev = unlink
migrate := st.NewTask("migrate-snap-home", fmt.Sprintf(i18n.G("Migrate %q to ~/Snap"), name))
addTask(migrate)
prev = migrate
// finalize (wrappers+current symlink)
linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), name, si.Revision))
addTask(linkSnap)
prev = linkSnap
// run new services
startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), name, si.Revision))
addTask(startSnapServices)
prev = startSnapServices
var ts state.TaskSet
for _, t := range tasks {
ts.AddTask(t)
}
ts.JoinLane(st.NewLane())
tss = append(tss, &ts)
}
return tss, nil
}
// LinkNewBaseOrKernel creates a new task set with prepare/link-snap, and
// additionally update-gadget-assets for the kernel snap, tasks for a remodel.
func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if errors.Is(err, state.ErrNoState) {
return nil, &snap.NotInstalledError{Snap: name}
}
if err != nil {
return nil, err
}
if err := CheckChangeConflict(st, name, nil); err != nil {
return nil, err
}
info, err := snapst.CurrentInfo()
if err != nil {
return nil, err
}
switch info.Type() {
case snap.TypeOS, snap.TypeBase, snap.TypeKernel:
// good
default:
// bad
return nil, fmt.Errorf("internal error: cannot link type %v", info.Type())
}
snapsup := &SnapSetup{
SideInfo: snapst.CurrentSideInfo(),
Flags: snapst.Flags.ForSnapSetup(),
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: snapst.InstanceKey,
}
prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s) for remodel"), snapsup.InstanceName(), snapst.Current))
prepareSnap.Set("snap-setup", &snapsup)
prev := prepareSnap
ts := state.NewTaskSet(prepareSnap)
// preserve the same order as during the update
if info.Type() == snap.TypeKernel {
// kernel snaps can carry boot assets
gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current))
gadgetUpdate.Set("snap-setup-task", prepareSnap.ID())
gadgetUpdate.WaitFor(prev)
ts.AddTask(gadgetUpdate)
prev = gadgetUpdate
}
linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapst.Current))
linkSnap.Set("snap-setup-task", prepareSnap.ID())
linkSnap.WaitFor(prev)
ts.AddTask(linkSnap)
// prepare-snap is the last task that carries no system modifications
ts.MarkEdge(prepareSnap, LastBeforeLocalModificationsEdge)
return ts, nil
}
func findSnapSetupTask(tasks []*state.Task) (*state.Task, *SnapSetup, error) {
var snapsup SnapSetup
for _, tsk := range tasks {
if tsk.Has("snap-setup") {
if err := tsk.Get("snap-setup", &snapsup); err != nil {
return nil, nil, err
}
return tsk, &snapsup, nil
}
}
return nil, nil, nil
}
// AddLinkNewBaseOrKernel creates the same tasks as LinkNewBaseOrKernel but adds
// them to the provided task set.
func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) {
allTasks := ts.Tasks()
snapSetupTask, snapsup, err := findSnapSetupTask(allTasks)
if err != nil {
return nil, err
}
if snapSetupTask == nil {
return nil, fmt.Errorf("internal error: cannot identify task with snap-setup")
}
// the first task added here waits for the last task in the existing set
prev := allTasks[len(allTasks)-1]
// preserve the same order as during the update
if snapsup.Type == snap.TypeKernel {
// kernel snaps can carry boot assets
gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision()))
gadgetUpdate.Set("snap-setup-task", snapSetupTask.ID())
// wait for the last task in existing set
gadgetUpdate.WaitFor(prev)
ts.AddTask(gadgetUpdate)
prev = gadgetUpdate
}
linkSnap := st.NewTask("link-snap",
fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapsup.SideInfo.Revision))
linkSnap.Set("snap-setup-task", snapSetupTask.ID())
linkSnap.WaitFor(prev)
ts.AddTask(linkSnap)
// make sure that remodel can identify which tasks introduce actual
// changes to the system and order them correctly
if edgeTask := ts.MaybeEdge(LastBeforeLocalModificationsEdge); edgeTask == nil {
// no task in the task set is marked as last before system
// modifications are introduced, so we need to mark the last
// task in the set, as tasks introduced here modify system state
ts.MarkEdge(allTasks[len(allTasks)-1], LastBeforeLocalModificationsEdge)
}
return ts, nil
}
// LinkNewBaseOrKernel creates a new task set with
// prepare/update-gadget-assets/update-gadget-cmdline tasks for the gadget snap,
// for remodel.
func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if errors.Is(err, state.ErrNoState) {
return nil, &snap.NotInstalledError{Snap: name}
}
if err != nil {
return nil, err
}
if err := CheckChangeConflict(st, name, nil); err != nil {
return nil, err
}
info, err := snapst.CurrentInfo()
if err != nil {
return nil, err
}
if info.Type() != snap.TypeGadget {
return nil, fmt.Errorf("internal error: cannot link type %v", info.Type())
}
snapsup := &SnapSetup{
SideInfo: snapst.CurrentSideInfo(),
Flags: snapst.Flags.ForSnapSetup(),
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: snapst.InstanceKey,
}
prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s) for remodel"), snapsup.InstanceName(), snapst.Current))
prepareSnap.Set("snap-setup", &snapsup)
gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current))
gadgetUpdate.WaitFor(prepareSnap)
gadgetUpdate.Set("snap-setup-task", prepareSnap.ID())
gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current))
gadgetCmdline.WaitFor(gadgetUpdate)
gadgetCmdline.Set("snap-setup-task", prepareSnap.ID())
ts := state.NewTaskSet(prepareSnap, gadgetUpdate, gadgetCmdline)
// prepare-snap is the last task that carries no system modifications
ts.MarkEdge(prepareSnap, LastBeforeLocalModificationsEdge)
return ts, nil
}
// AddGadgetAssetsTasks creates the same tasks as SwitchToNewGadget but adds
// them to the provided task set.
func AddGadgetAssetsTasks(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) {
allTasks := ts.Tasks()
snapSetupTask, snapsup, err := findSnapSetupTask(allTasks)
if err != nil {
return nil, err
}
if snapSetupTask == nil {
return nil, fmt.Errorf("internal error: cannot identify task with snap-setup")
}
gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision()))
gadgetUpdate.Set("snap-setup-task", snapSetupTask.ID())
// wait for the last task in existing set
gadgetUpdate.WaitFor(allTasks[len(allTasks)-1])
ts.AddTask(gadgetUpdate)
// gadget snaps can carry kernel command line fragments
gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision()))
gadgetCmdline.Set("snap-setup-task", snapSetupTask.ID())
gadgetCmdline.WaitFor(gadgetUpdate)
ts.AddTask(gadgetCmdline)
// make sure that remodel can identify which tasks introduce actual
// changes to the system and order them correctly
if edgeTask := ts.MaybeEdge(LastBeforeLocalModificationsEdge); edgeTask == nil {
// no task in the task set is marked as last before system
// modifications are introduced, so we need to mark the last
// task in the set, as tasks introduced here modify system state
ts.MarkEdge(allTasks[len(allTasks)-1], LastBeforeLocalModificationsEdge)
}
return ts, nil
}
// Enable sets a snap to the active state
func Enable(st *state.State, name string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if errors.Is(err, state.ErrNoState) {
return nil, &snap.NotInstalledError{Snap: name}
}
if err != nil {
return nil, err
}
if snapst.Active {
return nil, fmt.Errorf("snap %q already enabled", name)
}
if err := CheckChangeConflict(st, name, nil); err != nil {
return nil, err
}
info, err := snapst.CurrentInfo()
if err != nil {
return nil, err
}
snapsup := &SnapSetup{
SideInfo: snapst.CurrentSideInfo(),
Flags: snapst.Flags.ForSnapSetup(),
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: snapst.InstanceKey,
}
prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), snapsup.InstanceName(), snapst.Current))
prepareSnap.Set("snap-setup", &snapsup)
setupProfiles := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q (%s) security profiles"), snapsup.InstanceName(), snapst.Current))
setupProfiles.Set("snap-setup-task", prepareSnap.ID())
setupProfiles.WaitFor(prepareSnap)
linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), snapsup.InstanceName(), snapst.Current))
linkSnap.Set("snap-setup-task", prepareSnap.ID())
linkSnap.WaitFor(setupProfiles)
// setup aliases
setupAliases := st.NewTask("setup-aliases", fmt.Sprintf(i18n.G("Setup snap %q aliases"), snapsup.InstanceName()))
setupAliases.Set("snap-setup-task", prepareSnap.ID())
setupAliases.WaitFor(linkSnap)
startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), snapsup.InstanceName(), snapst.Current))
startSnapServices.Set("snap-setup-task", prepareSnap.ID())
startSnapServices.WaitFor(setupAliases)
return state.NewTaskSet(prepareSnap, setupProfiles, linkSnap, setupAliases, startSnapServices), nil
}
// Disable sets a snap to the inactive state
func Disable(st *state.State, name string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if errors.Is(err, state.ErrNoState) {
return nil, &snap.NotInstalledError{Snap: name}
}
if err != nil {
return nil, err
}
if !snapst.Active {
return nil, fmt.Errorf("snap %q already disabled", name)
}
info, err := Info(st, name, snapst.Current)
if err != nil {
return nil, err
}
if !canDisable(info) {
return nil, fmt.Errorf("snap %q cannot be disabled", name)
}
if err := CheckChangeConflict(st, name, nil); err != nil {
return nil, err
}
snapsup := &SnapSetup{
SideInfo: &snap.SideInfo{
RealName: snap.InstanceSnap(name),
Revision: snapst.Current,
},
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: snapst.InstanceKey,
}
stopSnapServices := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q (%s) services"), snapsup.InstanceName(), snapst.Current))
stopSnapServices.Set("snap-setup", &snapsup)
stopSnapServices.Set("stop-reason", snap.StopReasonDisable)
removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), snapsup.InstanceName()))
removeAliases.Set("snap-setup-task", stopSnapServices.ID())
removeAliases.WaitFor(stopSnapServices)
unlinkSnap := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) unavailable to the system"), snapsup.InstanceName(), snapst.Current))
unlinkSnap.Set("snap-setup-task", stopSnapServices.ID())
unlinkSnap.WaitFor(removeAliases)
removeProfiles := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profiles of snap %q"), snapsup.InstanceName()))
removeProfiles.Set("snap-setup-task", stopSnapServices.ID())
removeProfiles.WaitFor(unlinkSnap)
return state.NewTaskSet(stopSnapServices, removeAliases, unlinkSnap, removeProfiles), nil
}
// canDisable verifies that a snap can be deactivated.
func canDisable(si *snap.Info) bool {
for _, importantSnapType := range []snap.Type{snap.TypeGadget, snap.TypeKernel, snap.TypeOS} {
if importantSnapType == si.Type() {
return false
}
}
return true
}
// canRemove verifies that a snap can be removed.
func canRemove(st *state.State, si *snap.Info, snapst *SnapState, removeAll bool, deviceCtx DeviceContext) error {
rev := snap.Revision{}
if !removeAll {
rev = si.Revision
}
if err := PolicyFor(si.Type(), deviceCtx.Model()).CanRemove(st, snapst, rev, deviceCtx); err != nil {
return err
}
// check if this snap is required by any validation set in enforcing mode
enforcedSets, err := EnforcedValidationSets(st)
if err != nil {
return err
}
if enforcedSets == nil {
return nil
}
requiredValsets, requiredRevision, err := enforcedSets.CheckPresenceRequired(si)
if err != nil {
if _, ok := err.(*snapasserts.PresenceConstraintError); !ok {
return err
}
// else - presence is invalid, nothing to do (not really possible since
// it shouldn't be allowed to get installed in the first place).
return nil
}
if len(requiredValsets) == 0 {
// not required by any validation set (or is optional)
return nil
}
// removeAll is set if we're removing the snap completely
if removeAll {
if requiredRevision.Unset() {
return fmt.Errorf("snap %q is required by validation sets: %s", si.InstanceName(), snapasserts.ValidationSetKeySlice(requiredValsets).CommaSeparated())
}
return fmt.Errorf("snap %q at revision %s is required by validation sets: %s", si.InstanceName(), requiredRevision, snapasserts.ValidationSetKeySlice(requiredValsets).CommaSeparated())
}
// rev is set at this point (otherwise we would hit removeAll case)
if requiredRevision.N == rev.N {
return fmt.Errorf("snap %q at revision %s is required by validation sets: %s", si.InstanceName(), rev, snapasserts.ValidationSetKeySlice(requiredValsets).CommaSeparated())
} // else - it's ok to remove a revision different than the required
return nil
}
// RemoveFlags are used to pass additional flags to the Remove operation.
type RemoveFlags struct {
// Remove the snap without creating snapshot data
Purge bool
}
// Remove returns a set of tasks for removing snap.
// Note that the state must be locked by the caller.
func Remove(st *state.State, name string, revision snap.Revision, flags *RemoveFlags) (*state.TaskSet, error) {
ts, snapshotSize, err := removeTasks(st, name, revision, flags)
// removeTasks() checks check-disk-space-remove feature flag, so snapshotSize
// will only be greater than 0 if the feature is enabled.
if snapshotSize > 0 {
requiredSpace := safetyMarginDiskSpace(snapshotSize)
path := dirs.SnapdStateDir(dirs.GlobalRootDir)
if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
return nil, &InsufficientSpaceError{
Path: path,
Snaps: []string{name},
ChangeKind: "remove",
Message: fmt.Sprintf("cannot create automatic snapshot when removing last revision of the snap: %v", err)}
}
return nil, err
}
}
return ts, err
}
// removeTasks provides the task set to remove snap name after taking a snapshot
// if flags.Purge is not true, it also computes an estimate of the latter size.
func removeTasks(st *state.State, name string, revision snap.Revision, flags *RemoveFlags) (removeTs *state.TaskSet, snapshotSize uint64, err error) {
var snapst SnapState
err = Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, 0, err
}
if !snapst.IsInstalled() {
return nil, 0, &snap.NotInstalledError{Snap: name, Rev: snap.R(0)}
}
if err := CheckChangeConflict(st, name, nil); err != nil {
return nil, 0, err
}
deviceCtx, err := DeviceCtxFromState(st, nil)
if err != nil {
return nil, 0, err
}
active := snapst.Active
var removeAll bool
if revision.Unset() {
revision = snapst.Current
removeAll = true
} else {
if active {
if revision == snapst.Current {
msg := "cannot remove active revision %s of snap %q"
if len(snapst.Sequence) > 1 {
msg += " (revert first?)"
}
return nil, 0, fmt.Errorf(msg, revision, name)
}
active = false
}
if !revisionInSequence(&snapst, revision) {
return nil, 0, &snap.NotInstalledError{Snap: name, Rev: revision}
}
removeAll = len(snapst.Sequence) == 1
}
info, err := Info(st, name, revision)
if err != nil {
return nil, 0, err
}
// check if this is something that can be removed
if err := canRemove(st, info, &snapst, removeAll, deviceCtx); err != nil {
return nil, 0, fmt.Errorf("snap %q is not removable: %v", name, err)
}
// main/current SnapSetup
snapsup := SnapSetup{
SideInfo: &snap.SideInfo{
SnapID: info.SnapID,
RealName: snap.InstanceSnap(name),
Revision: revision,
},
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: snapst.InstanceKey,
}
// trigger remove
removeTs = state.NewTaskSet()
var chain *state.TaskSet
addNext := func(ts *state.TaskSet) {
if chain != nil {
ts.WaitAll(chain)
}
removeTs.AddAll(ts)
chain = ts
}
var prev *state.Task
var stopSnapServices *state.Task
if active {
stopSnapServices = st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name))
stopSnapServices.Set("snap-setup", snapsup)
stopSnapServices.Set("stop-reason", snap.StopReasonRemove)
addNext(state.NewTaskSet(stopSnapServices))
prev = stopSnapServices
}
// only run remove hook if uninstalling the snap completely
if removeAll {
removeHook := SetupRemoveHook(st, snapsup.InstanceName())
addNext(state.NewTaskSet(removeHook))
prev = removeHook
// run disconnect hooks
disconnect := st.NewTask("auto-disconnect", fmt.Sprintf(i18n.G("Disconnect interfaces of snap %q"), snapsup.InstanceName()))
disconnect.Set("snap-setup", snapsup)
if prev != nil {
disconnect.WaitFor(prev)
}
addNext(state.NewTaskSet(disconnect))
prev = disconnect
}
// 'purge' flag disables automatic snapshot for given remove op
if flags == nil || !flags.Purge {
if tp, _ := snapst.Type(); tp == snap.TypeApp && removeAll {
ts, err := AutomaticSnapshot(st, name)
if err == nil {
tr := config.NewTransaction(st)
checkDiskSpaceRemove, err := features.Flag(tr, features.CheckDiskSpaceRemove)
if err != nil && !config.IsNoOption(err) {
return nil, 0, err
}
if checkDiskSpaceRemove {
snapshotSize, err = EstimateSnapshotSize(st, name, nil)
if err != nil {
return nil, 0, err
}
}
addNext(ts)
} else {
if err != ErrNothingToDo {
return nil, 0, err
}
}
}
}
if active { // unlink
var tasks []*state.Task
removeAliases := st.NewTask("remove-aliases", fmt.Sprintf(i18n.G("Remove aliases for snap %q"), name))
removeAliases.WaitFor(prev) // prev is not needed beyond here
removeAliases.Set("snap-setup-task", stopSnapServices.ID())
unlink := st.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q unavailable to the system"), name))
unlink.Set("snap-setup-task", stopSnapServices.ID())
unlink.WaitFor(removeAliases)
removeSecurity := st.NewTask("remove-profiles", fmt.Sprintf(i18n.G("Remove security profile for snap %q (%s)"), name, revision))
removeSecurity.WaitFor(unlink)
removeSecurity.Set("snap-setup-task", stopSnapServices.ID())
tasks = append(tasks, removeAliases, unlink, removeSecurity)
addNext(state.NewTaskSet(tasks...))
}
if removeAll {
seq := snapst.Sequence
currentIndex := snapst.LastIndex(snapst.Current)
for i := len(seq) - 1; i >= 0; i-- {
if i != currentIndex {
si := seq[i]
addNext(removeInactiveRevision(st, name, info.SnapID, si.Revision, snapsup.Type))
}
}
// add tasks for removing the current revision last,
// this is then also when common data will be removed
if currentIndex >= 0 {
addNext(removeInactiveRevision(st, name, info.SnapID, seq[currentIndex].Revision, snapsup.Type))
}
} else {
addNext(removeInactiveRevision(st, name, info.SnapID, revision, snapsup.Type))
}
return removeTs, snapshotSize, nil
}
func removeInactiveRevision(st *state.State, name, snapID string, revision snap.Revision, typ snap.Type) *state.TaskSet {
snapName, instanceKey := snap.SplitInstanceName(name)
snapsup := SnapSetup{
SideInfo: &snap.SideInfo{
RealName: snapName,
SnapID: snapID,
Revision: revision,
},
InstanceKey: instanceKey,
Type: typ,
}
clearData := st.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q (%s)"), name, revision))
clearData.Set("snap-setup", snapsup)
discardSnap := st.NewTask("discard-snap", fmt.Sprintf(i18n.G("Remove snap %q (%s) from the system"), name, revision))
discardSnap.WaitFor(clearData)
discardSnap.Set("snap-setup-task", clearData.ID())
return state.NewTaskSet(clearData, discardSnap)
}
// RemoveMany removes everything from the given list of names.
// Note that the state must be locked by the caller.
func RemoveMany(st *state.State, names []string, flags *RemoveFlags) ([]string, []*state.TaskSet, error) {
names = strutil.Deduplicate(names)
if err := validateSnapNames(names); err != nil {
return nil, nil, err
}
removed := make([]string, 0, len(names))
tasksets := make([]*state.TaskSet, 0, len(names))
var totalSnapshotsSize uint64
path := dirs.SnapdStateDir(dirs.GlobalRootDir)
for _, name := range names {
ts, snapshotSize, err := removeTasks(st, name, snap.R(0), flags)
// FIXME: is this expected behavior?
if _, ok := err.(*snap.NotInstalledError); ok {
continue
}
if err != nil {
return nil, nil, err
}
totalSnapshotsSize += snapshotSize
removed = append(removed, name)
ts.JoinLane(st.NewLane())
tasksets = append(tasksets, ts)
}
// removeTasks() checks check-disk-space-remove feature flag, so totalSnapshotsSize
// will only be greater than 0 if the feature is enabled.
if totalSnapshotsSize > 0 {
requiredSpace := safetyMarginDiskSpace(totalSnapshotsSize)
if err := osutilCheckFreeSpace(path, requiredSpace); err != nil {
if _, ok := err.(*osutil.NotEnoughDiskSpaceError); ok {
return nil, nil, &InsufficientSpaceError{
Path: path,
Snaps: names,
ChangeKind: "remove",
}
}
return nil, nil, err
}
}
return removed, tasksets, nil
}
func validateSnapNames(names []string) error {
var invalidNames []string
for _, name := range names {
if err := snap.ValidateInstanceName(name); err != nil {
invalidNames = append(invalidNames, name)
}
}
if len(invalidNames) > 0 {
return fmt.Errorf("cannot remove invalid snap names: %v", strings.Join(invalidNames, ", "))
}
return nil
}
// Revert returns a set of tasks for reverting to the previous version of the snap.
// Note that the state must be locked by the caller.
func Revert(st *state.State, name string, flags Flags, fromChange string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
pi := snapst.previousSideInfo()
if pi == nil {
return nil, fmt.Errorf("no revision to revert to")
}
return RevertToRevision(st, name, pi.Revision, flags, fromChange)
}
func RevertToRevision(st *state.State, name string, rev snap.Revision, flags Flags, fromChange string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
if snapst.Current == rev {
return nil, fmt.Errorf("already on requested revision")
}
if !snapst.Active {
return nil, fmt.Errorf("cannot revert inactive snaps")
}
i := snapst.LastIndex(rev)
if i < 0 {
return nil, fmt.Errorf("cannot find revision %s for snap %q", rev, name)
}
flags.Revert = true
// TODO: make flags be per revision to avoid this logic (that
// leaves corner cases all over the place)
if !(flags.JailMode || flags.DevMode || flags.Classic) {
if snapst.Flags.DevMode {
flags.DevMode = true
}
if snapst.Flags.JailMode {
flags.JailMode = true
}
if snapst.Flags.Classic {
flags.Classic = true
}
}
info, err := Info(st, name, rev)
if err != nil {
return nil, err
}
snapsup := &SnapSetup{
Base: info.Base,
SideInfo: snapst.Sequence[i],
Flags: flags.ForSnapSetup(),
Type: info.Type(),
PlugsOnly: len(info.Slots) == 0,
InstanceKey: snapst.InstanceKey,
}
return doInstall(st, &snapst, snapsup, 0, fromChange, nil)
}
// TransitionCore transitions from an old core snap name to a new core
// snap name. It is used for the ubuntu-core -> core transition (that
// is not just a rename because the two snaps have different snapIDs)
//
// Note that this function makes some assumptions like:
// - no aliases setup for both snaps
// - no data needs to be copied
// - all interfaces are absolutely identical on both new and old
// Do not use this as a general way to transition from snap A to snap B.
func TransitionCore(st *state.State, oldName, newName string) ([]*state.TaskSet, error) {
var oldSnapst, newSnapst SnapState
err := Get(st, oldName, &oldSnapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
if !oldSnapst.IsInstalled() {
return nil, fmt.Errorf("cannot transition snap %q: not installed", oldName)
}
var all []*state.TaskSet
// install new core (if not already installed)
err = Get(st, newName, &newSnapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
if !newSnapst.IsInstalled() {
var userID int
newInfo, err := installInfo(context.TODO(), st, newName, &RevisionOptions{Channel: oldSnapst.TrackingChannel}, userID, Flags{}, nil)
if err != nil {
return nil, err
}
// start by installing the new snap
tsInst, err := doInstall(st, &newSnapst, &SnapSetup{
Channel: oldSnapst.TrackingChannel,
DownloadInfo: &newInfo.DownloadInfo,
SideInfo: &newInfo.SideInfo,
Type: newInfo.Type(),
}, 0, "", nil)
if err != nil {
return nil, err
}
all = append(all, tsInst)
}
// then transition the interface connections over
transIf := st.NewTask("transition-ubuntu-core", fmt.Sprintf(i18n.G("Transition security profiles from %q to %q"), oldName, newName))
transIf.Set("old-name", oldName)
transIf.Set("new-name", newName)
if len(all) > 0 {
transIf.WaitAll(all[0])
}
tsTrans := state.NewTaskSet(transIf)
all = append(all, tsTrans)
// FIXME: this is just here for the tests
transIf.Set("snap-setup", &SnapSetup{
SideInfo: &snap.SideInfo{
RealName: oldName,
},
})
// then remove the old snap
tsRm, err := Remove(st, oldName, snap.R(0), nil)
if err != nil {
return nil, err
}
tsRm.WaitFor(transIf)
all = append(all, tsRm)
return all, nil
}
// State/info accessors
// Installing returns whether there's an in-progress installation.
func Installing(st *state.State) bool {
for _, task := range st.Tasks() {
k := task.Kind()
chg := task.Change()
if k == "mount-snap" && chg != nil && !chg.Status().Ready() {
return true
}
}
return false
}
// Info returns the information about the snap with given name and revision.
// Works also for a mounted candidate snap in the process of being installed.
func Info(st *state.State, name string, revision snap.Revision) (*snap.Info, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if errors.Is(err, state.ErrNoState) {
return nil, &snap.NotInstalledError{Snap: name}
}
if err != nil {
return nil, err
}
for i := len(snapst.Sequence) - 1; i >= 0; i-- {
if si := snapst.Sequence[i]; si.Revision == revision {
return readInfo(name, si, 0)
}
}
return nil, fmt.Errorf("cannot find snap %q at revision %s", name, revision.String())
}
// CurrentInfo returns the information about the current revision of a snap with the given name.
func CurrentInfo(st *state.State, name string) (*snap.Info, error) {
var snapst SnapState
err := Get(st, name, &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
info, err := snapst.CurrentInfo()
if err == ErrNoCurrent {
return nil, &snap.NotInstalledError{Snap: name}
}
return info, err
}
// Get retrieves the SnapState of the given snap.
func Get(st *state.State, name string, snapst *SnapState) error {
if snapst == nil {
return fmt.Errorf("internal error: snapst is nil")
}
// SnapState is (un-)marshalled from/to JSON, fields having omitempty
// tag will not appear in the output (if empty) and subsequently will
// not be unmarshalled to (or cleared); if the caller reuses the same
// struct though subsequent calls, it is possible that they end up with
// garbage inside, clear the destination struct so that we always
// unmarshal to a clean state
*snapst = SnapState{}
var snaps map[string]*json.RawMessage
err := st.Get("snaps", &snaps)
if err != nil {
return err
}
raw, ok := snaps[name]
if !ok {
return state.ErrNoState
}
// XXX: &snapst pointer isn't needed here but it is likely historical
// (a bug in old JSON marshaling probably).
err = json.Unmarshal([]byte(*raw), &snapst)
if err != nil {
return fmt.Errorf("cannot unmarshal snap state: %v", err)
}
return nil
}
// All retrieves return a map from name to SnapState for all current snaps in the system state.
func All(st *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 := st.Get("snaps", &stateMap); err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
curStates := make(map[string]*SnapState, len(stateMap))
for instanceName, snapst := range stateMap {
curStates[instanceName] = snapst
}
return curStates, nil
}
// InstalledSnaps returns the list of all installed snaps suitable for
// ValidationSets checks.
func InstalledSnaps(st *state.State) (snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool, err error) {
all, err := All(st)
if err != nil {
return nil, nil, err
}
ignoreValidation = make(map[string]bool)
for _, snapState := range all {
cur, err := snapState.CurrentInfo()
if err != nil {
return nil, nil, err
}
snaps = append(snaps, snapasserts.NewInstalledSnap(snapState.InstanceName(),
snapState.CurrentSideInfo().SnapID, cur.Revision))
if snapState.IgnoreValidation {
ignoreValidation[snapState.InstanceName()] = true
}
}
return snaps, ignoreValidation, nil
}
// NumSnaps returns the number of installed snaps.
func NumSnaps(st *state.State) (int, error) {
var snaps map[string]*json.RawMessage
if err := st.Get("snaps", &snaps); err != nil && !errors.Is(err, state.ErrNoState) {
return -1, err
}
return len(snaps), nil
}
// Set sets the SnapState of the given snap, overwriting any earlier state.
// Note that a SnapState with an empty Sequence will be treated as if snapst was
// nil and name will be deleted from the state.
func Set(st *state.State, name string, snapst *SnapState) {
var snaps map[string]*json.RawMessage
err := st.Get("snaps", &snaps)
if err != nil && !errors.Is(err, state.ErrNoState) {
panic("internal error: cannot unmarshal snaps state: " + err.Error())
}
if snaps == nil {
snaps = make(map[string]*json.RawMessage)
}
if snapst == nil || (len(snapst.Sequence) == 0) {
delete(snaps, name)
} else {
data, err := json.Marshal(snapst)
if err != nil {
panic("internal error: cannot marshal snap state: " + err.Error())
}
raw := json.RawMessage(data)
snaps[name] = &raw
}
st.Set("snaps", snaps)
}
// ActiveInfos returns information about all active snaps.
func ActiveInfos(st *state.State) ([]*snap.Info, error) {
var stateMap map[string]*SnapState
var infos []*snap.Info
if err := st.Get("snaps", &stateMap); err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
for instanceName, snapst := range stateMap {
if !snapst.Active {
continue
}
snapInfo, err := snapst.CurrentInfo()
if err != nil {
logger.Noticef("cannot retrieve info for snap %q: %s", instanceName, err)
continue
}
infos = append(infos, snapInfo)
}
return infos, nil
}
func HasSnapOfType(st *state.State, snapType snap.Type) (bool, error) {
var stateMap map[string]*SnapState
if err := st.Get("snaps", &stateMap); err != nil && !errors.Is(err, state.ErrNoState) {
return false, err
}
for _, snapst := range stateMap {
typ, err := snapst.Type()
if err != nil {
return false, err
}
if typ == snapType {
return true, nil
}
}
return false, nil
}
func infosForType(st *state.State, snapType snap.Type) ([]*snap.Info, error) {
var stateMap map[string]*SnapState
if err := st.Get("snaps", &stateMap); err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
var res []*snap.Info
for _, snapst := range stateMap {
if !snapst.IsInstalled() {
continue
}
typ, err := snapst.Type()
if err != nil {
return nil, err
}
if typ != snapType {
continue
}
si, err := snapst.CurrentInfo()
if err != nil {
return nil, err
}
res = append(res, si)
}
if len(res) == 0 {
return nil, state.ErrNoState
}
return res, nil
}
func infoForDeviceSnap(st *state.State, deviceCtx DeviceContext, whichName func(*asserts.Model) string) (*snap.Info, error) {
if deviceCtx == nil {
return nil, fmt.Errorf("internal error: unset deviceCtx")
}
model := deviceCtx.Model()
snapName := whichName(model)
if snapName == "" {
return nil, state.ErrNoState
}
var snapst SnapState
err := Get(st, snapName, &snapst)
if err != nil {
return nil, err
}
return snapst.CurrentInfo()
}
// GadgetInfo finds the gadget snap's info for the given device context.
func GadgetInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) {
return infoForDeviceSnap(st, deviceCtx, (*asserts.Model).Gadget)
}
// KernelInfo finds the kernel snap's info for the given device context.
func KernelInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) {
return infoForDeviceSnap(st, deviceCtx, (*asserts.Model).Kernel)
}
// BootBaseInfo finds the boot base snap's info for the given device context.
func BootBaseInfo(st *state.State, deviceCtx DeviceContext) (*snap.Info, error) {
baseName := func(mod *asserts.Model) string {
base := mod.Base()
if base == "" {
return "core"
}
return base
}
return infoForDeviceSnap(st, deviceCtx, baseName)
}
// TODO: reintroduce a KernelInfo(state.State, DeviceContext) if needed
// KernelInfo finds the current kernel snap's info.
// coreInfo finds the current OS snap's info. If both
// "core" and "ubuntu-core" is installed then "core"
// is preferred. Different core names are not supported
// currently and will result in an error.
func coreInfo(st *state.State) (*snap.Info, error) {
res, err := infosForType(st, snap.TypeOS)
if err != nil {
return nil, err
}
// a single core: just return it
if len(res) == 1 {
return res[0], nil
}
// some systems have two cores: ubuntu-core/core
// we always return "core" in this case
if len(res) == 2 {
if res[0].InstanceName() == defaultCoreSnapName && res[1].InstanceName() == "ubuntu-core" {
return res[0], nil
}
if res[0].InstanceName() == "ubuntu-core" && res[1].InstanceName() == defaultCoreSnapName {
return res[1], nil
}
return nil, fmt.Errorf("unexpected cores %q and %q", res[0].InstanceName(), res[1].InstanceName())
}
return nil, fmt.Errorf("unexpected number of cores, got %d", len(res))
}
// ConfigDefaults returns the configuration defaults for the snap as
// specified in the gadget for the given device context.
// If gadget is absent or the snap has no snap-id it returns ErrNoState.
func ConfigDefaults(st *state.State, deviceCtx DeviceContext, snapName string) (map[string]interface{}, error) {
info, err := GadgetInfo(st, deviceCtx)
if err != nil {
return nil, err
}
// system configuration is kept under "core" so apply its defaults when
// configuring "core"
isSystemDefaults := snapName == defaultCoreSnapName
var snapst SnapState
if err := Get(st, snapName, &snapst); err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
var snapID string
if snapst.IsInstalled() {
snapID = snapst.CurrentSideInfo().SnapID
}
// system snaps (core and snapd) snaps can be addressed even without a
// snap-id via the special "system" value in the config; first-boot
// always configures the core snap with UseConfigDefaults
if snapID == "" && !isSystemDefaults {
return nil, state.ErrNoState
}
// no constraints enforced: those should have been checked before already
gadgetInfo, err := gadget.ReadInfo(info.MountDir(), nil)
if err != nil {
return nil, err
}
// we support setting core defaults via "system"
if isSystemDefaults {
if defaults, ok := gadgetInfo.Defaults["system"]; ok {
if _, ok := gadgetInfo.Defaults[snapID]; ok && snapID != "" {
logger.Noticef("core snap configuration defaults found under both 'system' key and core-snap-id, preferring 'system'")
}
return defaults, nil
}
}
defaults, ok := gadgetInfo.Defaults[snapID]
if !ok {
return nil, state.ErrNoState
}
return defaults, nil
}
// GadgetConnections returns the interface connection instructions
// specified in the gadget for the given device context.
// If gadget is absent it returns ErrNoState.
func GadgetConnections(st *state.State, deviceCtx DeviceContext) ([]gadget.Connection, error) {
info, err := GadgetInfo(st, deviceCtx)
if err != nil {
return nil, err
}
// no constraints enforced: those should have been checked before already
gadgetInfo, err := gadget.ReadInfo(info.MountDir(), nil)
if err != nil {
return nil, err
}
return gadgetInfo.Connections, nil
}
func MockOsutilCheckFreeSpace(mock func(path string, minSize uint64) error) (restore func()) {
old := osutilCheckFreeSpace
osutilCheckFreeSpace = mock
return func() { osutilCheckFreeSpace = old }
}
// only useful for procuring a buggy behavior in the tests
var enforcedSingleRebootForGadgetKernelBase = false
func MockEnforceSingleRebootForBaseKernelGadget(val bool) (restore func()) {
osutil.MustBeTestBinary("mocking can be done only in tests")
old := enforcedSingleRebootForGadgetKernelBase
enforcedSingleRebootForGadgetKernelBase = val
return func() {
enforcedSingleRebootForGadgetKernelBase = old
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/mysnapcore/mysnapd.git
git@gitee.com:mysnapcore/mysnapd.git
mysnapcore
mysnapd
mysnapd
v0.1.0

Search