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
handlers.go 114.31 KB
Copy Edit Raw Blame History
tupelo-shen authored 2022-11-08 15:12 +08:00 . fix: overlord commit
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052
// -*- 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
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"time"
"gopkg.in/tomb.v2"
"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/cmd/snaplock/runinhibit"
"gitee.com/mysnapcore/mysnapd/dirs"
"gitee.com/mysnapcore/mysnapd/features"
"gitee.com/mysnapcore/mysnapd/i18n"
"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/configstate/settings"
"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/progress"
"gitee.com/mysnapcore/mysnapd/release"
"gitee.com/mysnapcore/mysnapd/snap"
"gitee.com/mysnapcore/mysnapd/snap/quota"
"gitee.com/mysnapcore/mysnapd/store"
"gitee.com/mysnapcore/mysnapd/strutil"
"gitee.com/mysnapcore/mysnapd/timings"
"gitee.com/mysnapcore/mysnapd/wrappers"
)
// SnapServiceOptions is a hook set by servicestate.
var SnapServiceOptions = func(st *state.State, instanceName string, grps map[string]*quota.Group) (opts *wrappers.SnapServiceOptions, err error) {
panic("internal error: snapstate.SnapServiceOptions is unset")
}
var EnsureSnapAbsentFromQuotaGroup = func(st *state.State, snap string) error {
panic("internal error: snapstate.EnsureSnapAbsentFromQuotaGroup is unset")
}
var SecurityProfilesRemoveLate = func(snapName string, rev snap.Revision, typ snap.Type) error {
panic("internal error: snapstate.SecurityProfilesRemoveLate is unset")
}
// TaskSnapSetup returns the SnapSetup with task params hold by or referred to by the task.
func TaskSnapSetup(t *state.Task) (*SnapSetup, error) {
var snapsup SnapSetup
err := t.Get("snap-setup", &snapsup)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
if err == nil {
return &snapsup, nil
}
var id string
err = t.Get("snap-setup-task", &id)
if err != nil {
return nil, err
}
ts := t.State().Task(id)
if ts == nil {
return nil, fmt.Errorf("internal error: tasks are being pruned")
}
if err := ts.Get("snap-setup", &snapsup); err != nil {
return nil, err
}
return &snapsup, nil
}
// SetTaskSnapSetup writes the given SnapSetup to the provided task's
// snap-setup-task Task, or to the task itself if the task does not have a
// snap-setup-task (i.e. it _is_ the snap-setup-task)
func SetTaskSnapSetup(t *state.Task, snapsup *SnapSetup) error {
if t.Has("snap-setup") {
// this is the snap-setup-task so just write to the task directly
t.Set("snap-setup", snapsup)
} else {
// this task isn't the snap-setup-task, so go get that and write to that
// one
var id string
err := t.Get("snap-setup-task", &id)
if err != nil {
return err
}
ts := t.State().Task(id)
if ts == nil {
return fmt.Errorf("internal error: tasks are being pruned")
}
ts.Set("snap-setup", snapsup)
}
return nil
}
func snapSetupAndState(t *state.Task) (*SnapSetup, *SnapState, error) {
snapsup, err := TaskSnapSetup(t)
if err != nil {
return nil, nil, err
}
var snapst SnapState
err = Get(t.State(), snapsup.InstanceName(), &snapst)
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, nil, err
}
return snapsup, &snapst, nil
}
/* State Locking
do* / undo* handlers should usually lock the state just once with:
st.Lock()
defer st.Unlock()
For tasks doing slow operations (long i/o, networking operations) it's OK
to unlock the state temporarily:
st.Unlock()
err := slowIOOp()
st.Lock()
if err != nil {
...
}
but if a task Get and then Set the SnapState of a snap it must avoid
releasing the state lock in between, other tasks might have
reasons to update the SnapState independently:
// DO NOT DO THIS!:
snapst := ...
snapst.Attr = ...
st.Unlock()
...
st.Lock()
Set(st, snapName, snapst)
if a task really needs to mix mutating a SnapState and releasing the state
lock it should be serialized at the task runner level, see
SnapManger.blockedTask and TaskRunner.SetBlocked
*/
const defaultCoreSnapName = "core"
func defaultBaseSnapsChannel() string {
channel := os.Getenv("SNAPD_BASES_CHANNEL")
if channel == "" {
return "stable"
}
return channel
}
func defaultSnapdSnapsChannel() string {
channel := os.Getenv("SNAPD_SNAPD_CHANNEL")
if channel == "" {
return "stable"
}
return channel
}
func defaultPrereqSnapsChannel() string {
channel := os.Getenv("SNAPD_PREREQS_CHANNEL")
if channel == "" {
return "stable"
}
return channel
}
func findLinkSnapTaskForSnap(st *state.State, snapName string) (*state.Task, error) {
for _, chg := range st.Changes() {
if chg.Status().Ready() {
continue
}
for _, tc := range chg.Tasks() {
if tc.Status().Ready() {
continue
}
if tc.Kind() == "link-snap" {
snapsup, err := TaskSnapSetup(tc)
if err != nil {
return nil, err
}
if snapsup.InstanceName() == snapName {
return tc, nil
}
}
}
}
return nil, nil
}
func isInstalled(st *state.State, snapName string) (bool, error) {
var snapState SnapState
err := Get(st, snapName, &snapState)
if err != nil && !errors.Is(err, state.ErrNoState) {
return false, err
}
return snapState.IsInstalled(), nil
}
// timeout for tasks to check if the prerequisites are ready
var prerequisitesRetryTimeout = 30 * time.Second
func (m *SnapManager) doPrerequisites(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
// check if we need to inject tasks to install core
snapsup, _, err := snapSetupAndState(t)
if err != nil {
return err
}
// os/base/kernel/gadget cannot have prerequisites other
// than the models default base (or core) which is installed anyway
switch snapsup.Type {
case snap.TypeOS, snap.TypeBase, snap.TypeKernel, snap.TypeGadget:
return nil
}
// snapd is special and has no prereqs
if snapsup.Type == snap.TypeSnapd {
return nil
}
// we need to make sure we install all prereqs together in one
// operation
base := defaultCoreSnapName
if snapsup.Base != "" {
base = snapsup.Base
}
// if a previous version of snapd persisted Prereq only, fill the contentAttrs.
// There will be no content attrs, so it will not update an outdated default provider
if len(snapsup.PrereqContentAttrs) == 0 && len(snapsup.Prereq) != 0 {
snapsup.PrereqContentAttrs = make(map[string][]string, len(snapsup.Prereq))
for _, prereq := range snapsup.Prereq {
snapsup.PrereqContentAttrs[prereq] = nil
}
}
if err := m.installPrereqs(t, base, snapsup.PrereqContentAttrs, snapsup.UserID, perfTimings, snapsup.Flags); err != nil {
return err
}
return nil
}
func (m *SnapManager) installOneBaseOrRequired(t *state.Task, snapName string, contentAttrs []string, requireTypeBase bool, channel string, onInFlight error, userID int, flags Flags) (*state.TaskSet, error) {
st := t.State()
// The core snap provides everything we need for core16.
coreInstalled, err := isInstalled(st, "core")
if err != nil {
return nil, err
}
if snapName == "core16" && coreInstalled {
return nil, nil
}
// installed already?
isInstalled, err := isInstalled(st, snapName)
if err != nil {
return nil, err
}
if isInstalled {
return updatePrereqIfOutdated(t, snapName, contentAttrs, userID, flags)
}
// in progress?
if linkTask, err := findLinkSnapTaskForSnap(st, snapName); err != nil {
return nil, err
} else if linkTask != nil {
return nil, onInFlight
}
// not installed, nor queued for install -> install it
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return nil, err
}
ts, err := InstallWithDeviceContext(context.TODO(), st, snapName, &RevisionOptions{Channel: channel}, userID, Flags{RequireTypeBase: requireTypeBase}, deviceCtx, "")
// something might have triggered an explicit install while
// the state was unlocked -> deal with that here by simply
// retrying the operation.
if conflErr, ok := err.(*ChangeConflictError); ok {
// conflicted with an install in the same change, just skip
if conflErr.ChangeID == t.Change().ID() {
return nil, nil
}
return nil, &state.Retry{After: prerequisitesRetryTimeout}
}
return ts, err
}
// updates a prerequisite, if it's not providing a content interface that a plug expects it to
func updatePrereqIfOutdated(t *state.Task, snapName string, contentAttrs []string, userID int, flags Flags) (*state.TaskSet, error) {
if len(contentAttrs) == 0 {
return nil, nil
}
st := t.State()
// check if the default provider has all expected content tags
if ok, err := hasAllContentAttrs(st, snapName, contentAttrs); err != nil {
return nil, err
} else if ok {
return nil, nil
}
// this is an optimization since the Update would also detect a conflict
// but only after accessing the store
if ok, err := shouldSkipToAvoidConflict(t, snapName); err != nil {
return nil, err
} else if ok {
return nil, nil
}
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return nil, err
}
// default provider is missing some content tags (likely outdated) so update it
ts, err := UpdateWithDeviceContext(st, snapName, nil, userID, flags, deviceCtx, "")
if err != nil {
if conflErr, ok := err.(*ChangeConflictError); ok {
// there's already an update for the same snap in this change,
// just skip this one
if conflErr.ChangeID == t.Change().ID() {
return nil, nil
}
return nil, &state.Retry{After: prerequisitesRetryTimeout}
}
// don't propagate error to avoid failing the main install since the
// content provider is (for now) a soft dependency
t.Logf("failed to update %q, will not have required content %q: %s", snapName, strings.Join(contentAttrs, ", "), err)
return nil, nil
}
return ts, nil
}
// Checks for conflicting tasks. Returns true if the operation should be skipped. The error
// can be a state.Retry if the operation should be retried later.
func shouldSkipToAvoidConflict(task *state.Task, snapName string) (bool, error) {
otherTask, err := findLinkSnapTaskForSnap(task.State(), snapName)
if err != nil {
return false, err
}
if otherTask == nil {
return false, nil
}
// it's in the same change, so the snap is already going to be installed
if otherTask.Change().ID() == task.Change().ID() {
return true, nil
}
// it's not in the same change, so retry to avoid conflicting changes to the snap
return true, &state.Retry{
After: prerequisitesRetryTimeout,
Reason: fmt.Sprintf("conflicting changes on snap %q by task %q", snapName, otherTask.Kind()),
}
}
// Checks if the snap has slots with "content" attributes matching the
// ones that the snap being installed requires
func hasAllContentAttrs(st *state.State, snapName string, requiredContentAttrs []string) (bool, error) {
providedContentAttrs := make(map[string]bool)
repo := ifacerepo.Get(st)
for _, slot := range repo.Slots(snapName) {
if slot.Interface != "content" {
continue
}
val, ok := slot.Lookup("content")
if !ok {
continue
}
contentAttr, ok := val.(string)
if !ok {
return false, fmt.Errorf("expected 'content' attribute of slot '%s' (snap: '%s') to be string but was %s", slot.Name, snapName, reflect.TypeOf(val))
}
providedContentAttrs[contentAttr] = true
}
for _, contentAttr := range requiredContentAttrs {
if _, ok := providedContentAttrs[contentAttr]; !ok {
return false, nil
}
}
return true, nil
}
func (m *SnapManager) installPrereqs(t *state.Task, base string, prereq map[string][]string, userID int, tm timings.Measurer, flags Flags) error {
st := t.State()
// We try to install all wanted snaps. If one snap cannot be installed
// because of change conflicts or similar we retry. Only if all snaps
// can be installed together we add the tasks to the change.
var tss []*state.TaskSet
for prereqName, contentAttrs := range prereq {
var onInFlightErr error = nil
var err error
var ts *state.TaskSet
timings.Run(tm, "install-prereq", fmt.Sprintf("install %q", prereqName), func(timings.Measurer) {
noTypeBaseCheck := false
ts, err = m.installOneBaseOrRequired(t, prereqName, contentAttrs, noTypeBaseCheck, defaultPrereqSnapsChannel(), onInFlightErr, userID, flags)
})
if err != nil {
return prereqError("prerequisite", prereqName, err)
}
if ts == nil {
continue
}
tss = append(tss, ts)
}
// for base snaps we need to wait until the change is done
// (either finished or failed)
onInFlightErr := &state.Retry{After: prerequisitesRetryTimeout}
var tsBase *state.TaskSet
var err error
if base != "none" {
timings.Run(tm, "install-prereq", fmt.Sprintf("install base %q", base), func(timings.Measurer) {
requireTypeBase := true
tsBase, err = m.installOneBaseOrRequired(t, base, nil, requireTypeBase, defaultBaseSnapsChannel(), onInFlightErr, userID, Flags{})
})
if err != nil {
return prereqError("snap base", base, err)
}
}
// on systems without core or snapd need to install snapd to
// make interfaces work - LP: 1819318
var tsSnapd *state.TaskSet
snapdSnapInstalled, err := isInstalled(st, "snapd")
if err != nil {
return err
}
coreSnapInstalled, err := isInstalled(st, "core")
if err != nil {
return err
}
if base != "core" && !snapdSnapInstalled && !coreSnapInstalled {
timings.Run(tm, "install-prereq", "install snapd", func(timings.Measurer) {
noTypeBaseCheck := false
tsSnapd, err = m.installOneBaseOrRequired(t, "snapd", nil, noTypeBaseCheck, defaultSnapdSnapsChannel(), onInFlightErr, userID, Flags{})
})
if err != nil {
return prereqError("system snap", "snapd", err)
}
}
// If transactional, use a single lane for all tasks, 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.
var joinLane func(ts *state.TaskSet)
if flags.Transaction == client.TransactionAllSnaps {
lanes := t.Lanes()
if len(lanes) != 1 {
return fmt.Errorf("internal error: more than one lane (%d) on a transactional action", len(lanes))
}
transactionLane := lanes[0]
joinLane = func(ts *state.TaskSet) { ts.JoinLane(transactionLane) }
} else {
joinLane = func(ts *state.TaskSet) { ts.JoinLane(st.NewLane()) }
}
chg := t.Change()
// add all required snaps, no ordering, this will be done in the
// auto-connect task handler
for _, ts := range tss {
joinLane(ts)
chg.AddAll(ts)
}
// add the base if needed, prereqs else must wait on this
if tsBase != nil {
joinLane(tsBase)
for _, t := range chg.Tasks() {
t.WaitAll(tsBase)
}
chg.AddAll(tsBase)
}
// add snapd if needed, everything must wait on this
if tsSnapd != nil {
joinLane(tsSnapd)
for _, t := range chg.Tasks() {
t.WaitAll(tsSnapd)
}
chg.AddAll(tsSnapd)
}
// make sure that the new change is committed to the state
// together with marking this task done
t.SetStatus(state.DoneStatus)
return nil
}
func prereqError(what, snapName string, err error) error {
if _, ok := err.(*state.Retry); ok {
return err
}
return fmt.Errorf("cannot install %s %q: %v", what, snapName, err)
}
func (m *SnapManager) doPrepareSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
if snapsup.Revision().Unset() {
// Local revisions start at -1 and go down.
revision := snapst.LocalRevision()
if revision.Unset() || revision.N > 0 {
revision = snap.R(-1)
} else {
revision.N--
}
if !revision.Local() {
panic("internal error: invalid local revision built: " + revision.String())
}
snapsup.SideInfo.Revision = revision
}
t.Set("snap-setup", snapsup)
return nil
}
func (m *SnapManager) undoPrepareSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, err := TaskSnapSetup(t)
if err != nil {
return err
}
if snapsup.SideInfo == nil || snapsup.SideInfo.RealName == "" {
return nil
}
var logMsg []string
var snapSetup string
dupSig := []string{"snap-install:"}
chg := t.Change()
logMsg = append(logMsg, fmt.Sprintf("change %q: %q", chg.Kind(), chg.Summary()))
for _, t := range chg.Tasks() {
// TODO: report only tasks in intersecting lanes?
tintro := fmt.Sprintf("%s: %s", t.Kind(), t.Status())
logMsg = append(logMsg, tintro)
dupSig = append(dupSig, tintro)
if snapsup, err := TaskSnapSetup(t); err == nil && snapsup.SideInfo != nil {
snapSetup1 := fmt.Sprintf(" snap-setup: %q (%v) %q", snapsup.SideInfo.RealName, snapsup.SideInfo.Revision, snapsup.SideInfo.Channel)
if snapSetup1 != snapSetup {
snapSetup = snapSetup1
logMsg = append(logMsg, snapSetup)
dupSig = append(dupSig, fmt.Sprintf(" snap-setup: %q", snapsup.SideInfo.RealName))
}
}
for _, l := range t.Log() {
// cut of the rfc339 timestamp to ensure duplicate
// detection works in daisy
tStampLen := strings.Index(l, " ")
if tStampLen < 0 {
continue
}
// not tStampLen+1 because the indent is nice
entry := l[tStampLen:]
logMsg = append(logMsg, entry)
dupSig = append(dupSig, entry)
}
}
var ubuntuCoreTransitionCount int
err = st.Get("ubuntu-core-transition-retry", &ubuntuCoreTransitionCount)
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
extra := map[string]string{
"Channel": snapsup.Channel,
"Revision": snapsup.SideInfo.Revision.String(),
}
if ubuntuCoreTransitionCount > 0 {
extra["UbuntuCoreTransitionCount"] = strconv.Itoa(ubuntuCoreTransitionCount)
}
// Only report and error if there is an actual error in the change,
// we could undo things because the user canceled the change.
var isErr bool
for _, tt := range t.Change().Tasks() {
if tt.Status() == state.ErrorStatus {
isErr = true
break
}
}
if isErr && !settings.ProblemReportsDisabled(st) {
st.Unlock()
oopsid, err := errtrackerReport(snapsup.SideInfo.RealName, strings.Join(logMsg, "\n"), strings.Join(dupSig, "\n"), extra)
st.Lock()
if err == nil {
logger.Noticef("Reported install problem for %q as %s", snapsup.SideInfo.RealName, oopsid)
} else {
logger.Debugf("Cannot report problem: %s", err)
}
}
return nil
}
func installInfoUnlocked(st *state.State, snapsup *SnapSetup, deviceCtx DeviceContext) (store.SnapActionResult, error) {
st.Lock()
defer st.Unlock()
opts := &RevisionOptions{Channel: snapsup.Channel, CohortKey: snapsup.CohortKey, Revision: snapsup.Revision()}
return installInfo(context.TODO(), st, snapsup.InstanceName(), opts, snapsup.UserID, Flags{}, deviceCtx)
}
// autoRefreshRateLimited returns the rate limit of auto-refreshes or 0 if
// there is no limit.
func autoRefreshRateLimited(st *state.State) (rate int64) {
tr := config.NewTransaction(st)
var rateLimit string
err := tr.Get("core", "refresh.rate-limit", &rateLimit)
if err != nil {
return 0
}
// NOTE ParseByteSize errors on negative rates
val, err := strutil.ParseByteSize(rateLimit)
if err != nil {
return 0
}
return val
}
func downloadSnapParams(st *state.State, t *state.Task) (*SnapSetup, StoreService, *auth.UserState, error) {
snapsup, err := TaskSnapSetup(t)
if err != nil {
return nil, nil, nil, err
}
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return nil, nil, nil, err
}
sto := Store(st, deviceCtx)
user, err := userFromUserID(st, snapsup.UserID)
if err != nil {
return nil, nil, nil, err
}
return snapsup, sto, user, nil
}
func (m *SnapManager) doDownloadSnap(t *state.Task, tomb *tomb.Tomb) error {
st := t.State()
var rate int64
st.Lock()
perfTimings := state.TimingsForTask(t)
snapsup, theStore, user, err := downloadSnapParams(st, t)
if snapsup != nil && snapsup.IsAutoRefresh {
// NOTE rate is never negative
rate = autoRefreshRateLimited(st)
}
st.Unlock()
if err != nil {
return err
}
meter := NewTaskProgressAdapterUnlocked(t)
targetFn := snapsup.MountFile()
dlOpts := &store.DownloadOptions{
IsAutoRefresh: snapsup.IsAutoRefresh,
RateLimit: rate,
}
if snapsup.DownloadInfo == nil {
var storeInfo store.SnapActionResult
// COMPATIBILITY - this task was created from an older version
// of snapd that did not store the DownloadInfo in the state
// yet. Therefore do not worry about DeviceContext.
storeInfo, err = installInfoUnlocked(st, snapsup, nil)
if err != nil {
return err
}
timings.Run(perfTimings, "download", fmt.Sprintf("download snap %q", snapsup.SnapName()), func(timings.Measurer) {
err = theStore.Download(tomb.Context(nil), snapsup.SnapName(), targetFn, &storeInfo.DownloadInfo, meter, user, dlOpts)
})
snapsup.SideInfo = &storeInfo.SideInfo
} else {
timings.Run(perfTimings, "download", fmt.Sprintf("download snap %q", snapsup.SnapName()), func(timings.Measurer) {
err = theStore.Download(tomb.Context(nil), snapsup.SnapName(), targetFn, snapsup.DownloadInfo, meter, user, dlOpts)
})
}
if err != nil {
return err
}
snapsup.SnapPath = targetFn
// update the snap setup for the follow up tasks
st.Lock()
t.Set("snap-setup", snapsup)
perfTimings.Save(st)
st.Unlock()
return nil
}
var (
mountPollInterval = 1 * time.Second
)
// hasOtherInstances checks whether there are other instances of the snap, be it
// instance keyed or not
func hasOtherInstances(st *state.State, instanceName string) (bool, error) {
snapName, _ := snap.SplitInstanceName(instanceName)
var all map[string]*json.RawMessage
if err := st.Get("snaps", &all); err != nil && !errors.Is(err, state.ErrNoState) {
return false, err
}
for otherName := range all {
if otherName == instanceName {
continue
}
if otherSnapName, _ := snap.SplitInstanceName(otherName); otherSnapName == snapName {
return true, nil
}
}
return false, nil
}
var ErrKernelGadgetUpdateTaskMissing = errors.New("cannot refresh kernel with change created by old snapd that is missing gadget update task")
func checkKernelHasUpdateAssetsTask(t *state.Task) error {
for _, other := range t.Change().Tasks() {
snapsup, err := TaskSnapSetup(other)
if errors.Is(err, state.ErrNoState) {
// XXX: hooks have no snapsup, is this detection okay?
continue
}
if err != nil {
return err
}
if snapsup.Type != "kernel" {
continue
}
if other.Kind() == "update-gadget-assets" {
return nil
}
}
return ErrKernelGadgetUpdateTaskMissing
}
func (m *SnapManager) doMountSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
perfTimings := state.TimingsForTask(t)
snapsup, snapst, err := snapSetupAndState(t)
st.Unlock()
if err != nil {
return err
}
curInfo, err := snapst.CurrentInfo()
if err != nil && err != ErrNoCurrent {
return err
}
m.backend.CurrentInfo(curInfo)
st.Lock()
deviceCtx, err := DeviceCtx(t.State(), t, nil)
st.Unlock()
if err != nil {
return err
}
// check that there is a "update-gadget-assets" task for kernels too,
// see https://bugs.launchpad.net/snapd/+bug/1940553
if snapsup.Type == snap.TypeKernel {
st.Lock()
err = checkKernelHasUpdateAssetsTask(t)
st.Unlock()
if err != nil {
return err
}
}
timings.Run(perfTimings, "check-snap", fmt.Sprintf("check snap %q", snapsup.InstanceName()), func(timings.Measurer) {
err = checkSnap(st, snapsup.SnapPath, snapsup.InstanceName(), snapsup.SideInfo, curInfo, snapsup.Flags, deviceCtx)
})
if err != nil {
return err
}
cleanup := func() {
st.Lock()
defer st.Unlock()
otherInstances, err := hasOtherInstances(st, snapsup.InstanceName())
if err != nil {
t.Errorf("cannot cleanup partial setup snap %q: %v", snapsup.InstanceName(), err)
return
}
// remove snap dir is idempotent so it's ok to always call it in the cleanup path
if err := m.backend.RemoveSnapDir(snapsup.placeInfo(), otherInstances); err != nil {
t.Errorf("cannot cleanup partial setup snap %q: %v", snapsup.InstanceName(), err)
}
}
setupOpts := &backend.SetupSnapOptions{
SkipKernelExtraction: snapsup.SkipKernelExtraction,
}
pb := NewTaskProgressAdapterUnlocked(t)
// TODO Use snapsup.Revision() to obtain the right info to mount
// instead of assuming the candidate is the right one.
var snapType snap.Type
var installRecord *backend.InstallRecord
timings.Run(perfTimings, "setup-snap", fmt.Sprintf("setup snap %q", snapsup.InstanceName()), func(timings.Measurer) {
snapType, installRecord, err = m.backend.SetupSnap(snapsup.SnapPath, snapsup.InstanceName(), snapsup.SideInfo, deviceCtx, setupOpts, pb)
})
if err != nil {
cleanup()
return err
}
// double check that the snap is mounted
var readInfoErr error
for i := 0; i < 10; i++ {
_, readInfoErr = readInfo(snapsup.InstanceName(), snapsup.SideInfo, errorOnBroken)
if readInfoErr == nil {
logger.Debugf("snap %q (%v) available at %q", snapsup.InstanceName(), snapsup.Revision(), snapsup.placeInfo().MountDir())
break
}
if _, ok := readInfoErr.(*snap.NotFoundError); !ok {
break
}
// snap not found, seems is not mounted yet
msg := fmt.Sprintf("expected snap %q revision %v to be mounted but is not", snapsup.InstanceName(), snapsup.Revision())
readInfoErr = fmt.Errorf("cannot proceed, %s", msg)
if i == 0 {
logger.Noticef(msg)
}
time.Sleep(mountPollInterval)
}
if readInfoErr != nil {
timings.Run(perfTimings, "undo-setup-snap", fmt.Sprintf("Undo setup of snap %q", snapsup.InstanceName()), func(timings.Measurer) {
err = m.backend.UndoSetupSnap(snapsup.placeInfo(), snapType, installRecord, deviceCtx, pb)
})
if err != nil {
st.Lock()
t.Errorf("cannot undo partial setup snap %q: %v", snapsup.InstanceName(), err)
st.Unlock()
}
cleanup()
return readInfoErr
}
st.Lock()
// set snapst type for undoMountSnap
t.Set("snap-type", snapType)
if installRecord != nil {
t.Set("install-record", installRecord)
}
st.Unlock()
if snapsup.Flags.RemoveSnapPath {
if err := os.Remove(snapsup.SnapPath); err != nil {
logger.Noticef("Failed to cleanup %s: %s", snapsup.SnapPath, err)
}
}
st.Lock()
perfTimings.Save(st)
st.Unlock()
return nil
}
func (m *SnapManager) undoMountSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
snapsup, err := TaskSnapSetup(t)
st.Unlock()
if err != nil {
return err
}
st.Lock()
deviceCtx, err := DeviceCtx(t.State(), t, nil)
st.Unlock()
if err != nil {
return err
}
st.Lock()
var typ snap.Type
err = t.Get("snap-type", &typ)
st.Unlock()
// backward compatibility
if errors.Is(err, state.ErrNoState) {
typ = "app"
} else if err != nil {
return err
}
var installRecord backend.InstallRecord
st.Lock()
// install-record is optional (e.g. not present in tasks from older snapd)
err = t.Get("install-record", &installRecord)
st.Unlock()
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
pb := NewTaskProgressAdapterUnlocked(t)
if err := m.backend.UndoSetupSnap(snapsup.placeInfo(), typ, &installRecord, deviceCtx, pb); err != nil {
return err
}
st.Lock()
defer st.Unlock()
otherInstances, err := hasOtherInstances(st, snapsup.InstanceName())
if err != nil {
return err
}
return m.backend.RemoveSnapDir(snapsup.placeInfo(), otherInstances)
}
// queryDisabledServices uses wrappers.QueryDisabledServices()
//
// Note this function takes a snap info rather than snapst because there are
// situations where we want to call this on non-current snap infos, i.e. in the
// undo handlers, see undoLinkSnap for an example.
func (m *SnapManager) queryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) {
return m.backend.QueryDisabledServices(info, pb)
}
func (m *SnapManager) doUnlinkCurrentSnap(t *state.Task, _ *tomb.Tomb) (err error) {
// called only during refresh when a new revision of a snap is being
// installed
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
oldInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
tr := config.NewTransaction(st)
experimentalRefreshAppAwareness, err := features.Flag(tr, features.RefreshAppAwareness)
if err != nil && !config.IsNoOption(err) {
return err
}
if experimentalRefreshAppAwareness && !excludeFromRefreshAppAwareness(snapsup.Type) && !snapsup.Flags.IgnoreRunning {
// Invoke the hard refresh flow. Upon success the returned lock will be
// held to prevent snap-run from advancing until UnlinkSnap, executed
// below, completes.
// XXX: should we skip it if type is snap.TypeSnapd?
lock, err := hardEnsureNothingRunningDuringRefresh(m.backend, st, snapst, oldInfo)
if err != nil {
return err
}
defer lock.Close()
}
snapst.Active = false
// snapd current symlink on the refresh path can only replaced by a
// symlink to a new revision of the snapd snap, so only do the actual
// unlink if we're not working on the snapd snap
if oldInfo.Type() != snap.TypeSnapd {
// do the final unlink
linkCtx := backend.LinkContext{
FirstInstall: false,
// This task is only used for unlinking a snap during refreshes so we
// can safely hard-code this condition here.
RunInhibitHint: runinhibit.HintInhibitedForRefresh,
}
err = m.backend.UnlinkSnap(oldInfo, linkCtx, NewTaskProgressAdapterLocked(t))
if err != nil {
return err
}
}
// mark as inactive
Set(st, snapsup.InstanceName(), snapst)
// Notify link snap participants about link changes.
notifyLinkParticipants(t, snapsup.InstanceName())
// undo migration if appropriate
if snapsup.Flags.Revert {
opts, err := getDirMigrationOpts(st, snapst, snapsup)
if err != nil {
return err
}
newInfo, err := readInfo(snapsup.InstanceName(), snapsup.SideInfo, errorOnBroken)
if err != nil {
return err
}
action := triggeredMigration(oldInfo.Base, newInfo.Base, opts)
switch action {
case full:
// we're reverting forward to a core22 based revision, so we already
// migrated previously and should use the ~/Snap sub dir as HOME again
snapsup.EnableExposedHome = true
fallthrough
case hidden:
if err := m.backend.HideSnapData(snapsup.InstanceName()); err != nil {
return err
}
snapsup.MigratedHidden = true
case home:
// we're reverting forward to a core22 based revision, so we already
// migrated previously and should use the ~/Snap sub dir as HOME again
snapsup.EnableExposedHome = true
case revertFull:
snapsup.DisableExposedHome = true
fallthrough
case revertHidden:
if err := m.backend.UndoHideSnapData(snapsup.InstanceName()); err != nil {
return err
}
snapsup.UndidHiddenMigration = true
case disableHome:
snapsup.DisableExposedHome = true
}
if err = SetTaskSnapSetup(t, snapsup); err != nil {
return err
}
}
return nil
}
func (m *SnapManager) undoUnlinkCurrentSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
oldInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return err
}
// in a revert, the migration actions were done in doUnlinkCurrentSnap so we
// revert them here and set SnapSetup flags (which will be used to set the
// state below)
if snapsup.Revert {
if snapsup.EnableExposedHome {
snapsup.DisableExposedHome = true
snapsup.EnableExposedHome = false
} else if snapsup.DisableExposedHome {
snapsup.DisableExposedHome = false
snapsup.EnableExposedHome = true
}
if snapsup.MigratedHidden {
if err := m.backend.UndoHideSnapData(snapsup.InstanceName()); err != nil {
return err
}
snapsup.UndidHiddenMigration = true
snapsup.MigratedHidden = false
} else if snapsup.UndidHiddenMigration {
if err := m.backend.HideSnapData(snapsup.InstanceName()); err != nil {
return err
}
snapsup.UndidHiddenMigration = false
snapsup.MigratedHidden = true
}
}
// undo migration-related state changes (set in doLinkSnap). The respective
// file migrations are undone either above or in undoCopySnapData. State
// should only be set in tasks that link the snap for safety and consistency.
setMigrationFlagsinState(snapst, snapsup)
if err := writeMigrationStatus(t, snapst, snapsup); err != nil {
return err
}
snapst.Active = true
opts, err := SnapServiceOptions(st, snapsup.InstanceName(), nil)
if err != nil {
return err
}
linkCtx := backend.LinkContext{
FirstInstall: false,
IsUndo: true,
ServiceOptions: opts,
}
reboot, err := m.backend.LinkSnap(oldInfo, deviceCtx, linkCtx, perfTimings)
if err != nil {
return err
}
// mark as active again
Set(st, snapsup.InstanceName(), snapst)
// Notify link snap participants about link changes.
notifyLinkParticipants(t, snapsup.InstanceName())
// if we just put back a previous a core snap, request a restart
// so that we switch executing its snapd
return m.finishTaskWithMaybeRestart(t, state.UndoneStatus, restartPossibility{info: oldInfo, RebootInfo: reboot})
}
func (m *SnapManager) doCopySnapData(t *state.Task, _ *tomb.Tomb) (err error) {
st := t.State()
st.Lock()
snapsup, snapst, err := snapSetupAndState(t)
st.Unlock()
if err != nil {
return err
}
newInfo, err := readInfo(snapsup.InstanceName(), snapsup.SideInfo, errorOnBroken)
if err != nil {
return err
}
oldInfo, err := snapst.CurrentInfo()
if err != nil && err != ErrNoCurrent {
return err
}
st.Lock()
deviceCtx, err := DeviceCtx(st, t, nil)
st.Unlock()
if err != nil {
return err
}
st.Lock()
opts, err := getDirMigrationOpts(st, snapst, snapsup)
st.Unlock()
if err != nil {
return err
}
dirOpts := opts.getSnapDirOpts()
pb := NewTaskProgressAdapterUnlocked(t)
if copyDataErr := m.backend.CopySnapData(newInfo, oldInfo, dirOpts, pb); copyDataErr != nil {
if oldInfo != nil {
// there is another revision of the snap, cannot remove
// shared data directory
return copyDataErr
}
// cleanup shared snap data directory
st.Lock()
defer st.Unlock()
otherInstances, err := hasOtherInstances(st, snapsup.InstanceName())
if err != nil {
t.Errorf("cannot undo partial snap %q data copy: %v", snapsup.InstanceName(), err)
return copyDataErr
}
// no other instances of this snap, shared data directory can be
// removed now too
if err := m.backend.RemoveSnapDataDir(newInfo, otherInstances); err != nil {
t.Errorf("cannot undo partial snap %q data copy, failed removing shared directory: %v", snapsup.InstanceName(), err)
}
return copyDataErr
}
if err := m.backend.SetupSnapSaveData(newInfo, deviceCtx, pb); err != nil {
return err
}
var oldBase string
if oldInfo != nil {
oldBase = oldInfo.Base
}
snapName := snapsup.InstanceName()
switch triggeredMigration(oldBase, newInfo.Base, opts) {
case hidden:
if err := m.backend.HideSnapData(snapName); err != nil {
return err
}
snapsup.MigratedHidden = true
case revertHidden:
if err := m.backend.UndoHideSnapData(snapName); err != nil {
return err
}
snapsup.UndidHiddenMigration = true
case full:
if err := m.backend.HideSnapData(snapName); err != nil {
return err
}
snapsup.MigratedHidden = true
fallthrough
case home:
undo, err := m.backend.InitExposedSnapHome(snapName, newInfo.Revision, opts.getSnapDirOpts())
if err != nil {
return err
}
st.Lock()
t.Set("undo-exposed-home-init", undo)
st.Unlock()
snapsup.MigratedToExposedHome = true
// no specific undo action is needed since undoing the copy will undo this
if err := m.backend.InitXDGDirs(newInfo); err != nil {
return err
}
}
st.Lock()
defer st.Unlock()
return SetTaskSnapSetup(t, snapsup)
}
type migration string
const (
// none states that no action should be taken
none migration = "none"
// hidden migrates ~/snap to ~/.snap
hidden migration = "hidden"
// revertHidden undoes the hidden migration (i.e., moves ~/.snap to ~/snap)
revertHidden migration = "revertHidden"
// home migrates the new home to ~/Snap
home migration = "home"
// full migrates ~/snap to ~/.snap and the new home to ~/Snap
full migration = "full"
// disableHome disables ~/Snap as HOME
disableHome migration = "disableHome"
// revertFull disables ~/Snap as HOME and undoes the hidden migration
revertFull migration = "revertFull"
)
func triggeredMigration(oldBase, newBase string, opts *dirMigrationOptions) migration {
if !opts.MigratedToHidden && opts.UseHidden {
// flag is set and not migrated yet
return hidden
}
if opts.MigratedToHidden && !opts.UseHidden {
// migration was done but flag was unset
return revertHidden
}
return none
}
func (m *SnapManager) undoCopySnapData(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
snapsup, snapst, err := snapSetupAndState(t)
st.Unlock()
if err != nil {
return err
}
newInfo, err := readInfo(snapsup.InstanceName(), snapsup.SideInfo, 0)
if err != nil {
return err
}
oldInfo, err := snapst.CurrentInfo()
if err != nil && err != ErrNoCurrent {
return err
}
st.Lock()
deviceCtx, err := DeviceCtx(st, t, nil)
st.Unlock()
if err != nil {
return err
}
// undo migration actions performed in doCopySnapData and set SnapSetup flags
// accordingly (they're used in undoUnlinkCurrentSnap to set SnapState)
if snapsup.MigratedToExposedHome || snapsup.MigratedHidden || snapsup.UndidHiddenMigration {
if snapsup.MigratedToExposedHome {
var undoInfo backend.UndoInfo
st.Lock()
err := t.Get("undo-exposed-home-init", &undoInfo)
st.Unlock()
if err != nil {
return err
}
if err := m.backend.UndoInitExposedSnapHome(snapsup.InstanceName(), &undoInfo); err != nil {
return err
}
snapsup.MigratedToExposedHome = false
snapsup.RemovedExposedHome = true
}
if snapsup.MigratedHidden {
if err := m.backend.UndoHideSnapData(snapsup.InstanceName()); err != nil {
return err
}
snapsup.MigratedHidden = false
snapsup.UndidHiddenMigration = true
} else if snapsup.UndidHiddenMigration {
if err := m.backend.HideSnapData(snapsup.InstanceName()); err != nil {
return err
}
snapsup.MigratedHidden = true
snapsup.UndidHiddenMigration = false
}
st.Lock()
err = SetTaskSnapSetup(t, snapsup)
st.Unlock()
if err != nil {
return err
}
}
st.Lock()
opts, err := getDirMigrationOpts(st, snapst, snapsup)
st.Unlock()
if err != nil {
return fmt.Errorf("failed to get snap dir options: %w", err)
}
dirOpts := opts.getSnapDirOpts()
pb := NewTaskProgressAdapterUnlocked(t)
if err := m.backend.UndoCopySnapData(newInfo, oldInfo, dirOpts, pb); err != nil {
return err
}
if err := m.backend.UndoSetupSnapSaveData(newInfo, oldInfo, deviceCtx, pb); err != nil {
return err
}
if oldInfo != nil {
// there is other revision of this snap, cannot remove shared
// directory anyway
return nil
}
st.Lock()
defer st.Unlock()
otherInstances, err := hasOtherInstances(st, snapsup.InstanceName())
if err != nil {
return err
}
// no other instances of this snap and no other revisions, shared data
// directory can be removed
if err := m.backend.RemoveSnapDataDir(newInfo, otherInstances); err != nil {
return err
}
return nil
}
// writeMigrationStatus writes the SnapSetup, state and sequence file (if they
// exist). This must be called after the migration undo procedure is done since
// only then do we know the actual final state of the migration. State must be
// locked by caller.
func writeMigrationStatus(t *state.Task, snapst *SnapState, snapsup *SnapSetup) error {
st := t.State()
if err := SetTaskSnapSetup(t, snapsup); err != nil {
return err
}
snapName := snapsup.InstanceName()
err := Get(st, snapName, &SnapState{})
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
if err == nil {
// migration state might've been written in the change; update it after undo
Set(st, snapName, snapst)
}
seqFile := filepath.Join(dirs.SnapSeqDir, snapName+".json")
if osutil.FileExists(seqFile) {
// might've written migration status to seq file in the change; update it
// after undo
return writeSeqFile(snapName, snapst)
}
// never got to write seq file; don't need to re-write migration status in it
return nil
}
func (m *SnapManager) cleanupCopySnapData(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
if t.Status() != state.DoneStatus {
// it failed
return nil
}
_, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
info, err := snapst.CurrentInfo()
if err != nil {
return err
}
// try to remove trashed any data in ~/snap and ~/.snap/data
m.backend.ClearTrashedData(info)
return nil
}
// writeSeqFile writes the sequence file for failover handling
func writeSeqFile(name string, snapst *SnapState) error {
p := filepath.Join(dirs.SnapSeqDir, name+".json")
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
return err
}
b, err := json.Marshal(&struct {
Sequence []*snap.SideInfo `json:"sequence"`
Current string `json:"current"`
MigratedHidden bool `json:"migrated-hidden"`
MigratedToExposedHome bool `json:"migrated-exposed-home"`
}{
Sequence: snapst.Sequence,
Current: snapst.Current.String(),
// if the snap state if empty, we're probably undoing a failed install.
// Reset the flags to false
MigratedHidden: len(snapst.Sequence) > 0 && snapst.MigratedHidden,
MigratedToExposedHome: len(snapst.Sequence) > 0 && snapst.MigratedToExposedHome,
})
if err != nil {
return err
}
return osutil.AtomicWriteFile(p, b, 0644, 0)
}
// missingDisabledServices returns a list of services that are present in
// this snap info and should be disabled as well as a list of disabled
// services that are currently missing (i.e. they were renamed).
// present in this snap info.
// the first arg is the disabled services when the snap was last active
func missingDisabledServices(svcs []string, info *snap.Info) ([]string, []string, error) {
// make a copy of all the previously disabled services that we will remove
// from, as well as an empty list to add to for the found services
missingSvcs := []string{}
foundSvcs := []string{}
// for all the previously disabled services, check if they are in the
// current snap info revision as services or not
for _, disabledSvcName := range svcs {
// check if the service is an app _and_ is a service
if app, ok := info.Apps[disabledSvcName]; ok && app.IsService() {
foundSvcs = append(foundSvcs, disabledSvcName)
} else {
missingSvcs = append(missingSvcs, disabledSvcName)
}
}
// sort the lists for easier testing
sort.Strings(missingSvcs)
sort.Strings(foundSvcs)
return foundSvcs, missingSvcs, nil
}
// LinkSnapParticipant is an interface for interacting with snap link/unlink
// operations.
//
// Unlike the interface for a task handler, only one notification method is
// used. The method notifies a participant that linkage of a snap has changed.
// This method is invoked in link-snap, unlink-snap, the undo path of those
// methods and the undo handler for link-snap.
//
// In all cases it is invoked after all other operations are completed but
// before the task completes.
type LinkSnapParticipant interface {
// SnapLinkageChanged is called when a snap is linked or unlinked.
// The error is only logged and does not stop the task it is used from.
SnapLinkageChanged(st *state.State, instanceName string) error
}
// LinkSnapParticipantFunc is an adapter from function to LinkSnapParticipant.
type LinkSnapParticipantFunc func(st *state.State, instanceName string) error
func (f LinkSnapParticipantFunc) SnapLinkageChanged(st *state.State, instanceName string) error {
return f(st, instanceName)
}
var linkSnapParticipants []LinkSnapParticipant
// AddLinkSnapParticipant adds a participant in the link/unlink operations.
func AddLinkSnapParticipant(p LinkSnapParticipant) {
linkSnapParticipants = append(linkSnapParticipants, p)
}
// MockLinkSnapParticipants replaces the list of link snap participants for testing.
func MockLinkSnapParticipants(ps []LinkSnapParticipant) (restore func()) {
old := linkSnapParticipants
linkSnapParticipants = ps
return func() {
linkSnapParticipants = old
}
}
func notifyLinkParticipants(t *state.Task, instanceName string) {
st := t.State()
for _, p := range linkSnapParticipants {
if err := p.SnapLinkageChanged(st, instanceName); err != nil {
t.Errorf("%v", err)
}
}
}
func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) (err error) {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return err
}
oldInfo, err := snapst.CurrentInfo()
if err != nil && err != ErrNoCurrent {
return err
}
// find if the snap is already installed before we modify snapst below
isInstalled := snapst.IsInstalled()
cand := snapsup.SideInfo
m.backend.Candidate(cand)
oldCandidateIndex := snapst.LastIndex(cand.Revision)
var oldRevsBeforeCand []snap.Revision
if oldCandidateIndex < 0 {
snapst.Sequence = append(snapst.Sequence, cand)
} else if !snapsup.Revert {
// save the revs before the candidate, so undoLink can account for discarded revs when putting it back
for _, si := range snapst.Sequence[:oldCandidateIndex] {
oldRevsBeforeCand = append(oldRevsBeforeCand, si.Revision)
}
// remove the old candidate from the sequence, add it at the end
copy(snapst.Sequence[oldCandidateIndex:len(snapst.Sequence)-1], snapst.Sequence[oldCandidateIndex+1:])
snapst.Sequence[len(snapst.Sequence)-1] = cand
}
oldCurrent := snapst.Current
snapst.Current = cand.Revision
snapst.Active = true
oldChannel := snapst.TrackingChannel
if snapsup.Channel != "" {
err := snapst.SetTrackingChannel(snapsup.Channel)
if err != nil {
return err
}
}
oldIgnoreValidation := snapst.IgnoreValidation
snapst.IgnoreValidation = snapsup.IgnoreValidation
oldTryMode := snapst.TryMode
snapst.TryMode = snapsup.TryMode
oldDevMode := snapst.DevMode
snapst.DevMode = snapsup.DevMode
oldJailMode := snapst.JailMode
snapst.JailMode = snapsup.JailMode
oldClassic := snapst.Classic
snapst.Classic = snapsup.Classic
oldCohortKey := snapst.CohortKey
snapst.CohortKey = snapsup.CohortKey
if snapsup.Required { // set only on install and left alone on refresh
snapst.Required = true
}
oldRefreshInhibitedTime := snapst.RefreshInhibitedTime
oldLastRefreshTime := snapst.LastRefreshTime
// only set userID if unset or logged out in snapst and if we
// actually have an associated user
if snapsup.UserID > 0 {
var user *auth.UserState
if snapst.UserID != 0 {
user, err = auth.User(st, snapst.UserID)
if err != nil && err != auth.ErrInvalidUser {
return err
}
}
if user == nil {
// if the original user installing the snap is
// no longer available transfer to user who
// triggered this change
snapst.UserID = snapsup.UserID
}
}
// keep instance key
snapst.InstanceKey = snapsup.InstanceKey
// don't keep the old state because, if we fail, we may or may not be able to
// revert the migration. We set the migration status after undoing any
// migration related ops
setMigrationFlagsinState(snapst, snapsup)
newInfo, err := readInfo(snapsup.InstanceName(), cand, 0)
if err != nil {
return err
}
// record type
snapst.SetType(newInfo.Type())
pb := NewTaskProgressAdapterLocked(t)
// Check for D-Bus service conflicts a second time to detect
// conflicts within a transaction.
if err := checkDBusServiceConflicts(st, newInfo); err != nil {
return err
}
opts, err := SnapServiceOptions(st, snapsup.InstanceName(), nil)
if err != nil {
return err
}
firstInstall := oldCurrent.Unset()
linkCtx := backend.LinkContext{
FirstInstall: firstInstall,
ServiceOptions: opts,
}
// on UC18+, snap tooling comes from the snapd snap so we need generated
// mount units to depend on the snapd snap mount units
if !deviceCtx.Classic() && deviceCtx.Model().Base() != "" {
linkCtx.RequireMountedSnapdSnap = true
}
// write sequence file for failover helpers
if err := writeSeqFile(snapsup.InstanceName(), snapst); err != nil {
return err
}
defer func() {
// if link snap fails and this is a first install, then we need to clean up
// the sequence file
if err != nil && firstInstall {
snapst.MigratedHidden = false
snapst.MigratedToExposedHome = false
if err := writeSeqFile(snapsup.InstanceName(), snapst); err != nil {
st.Warnf("cannot update sequence file after failed install of %q: %v", snapsup.InstanceName(), err)
}
}
}()
rebootInfo, err := m.backend.LinkSnap(newInfo, deviceCtx, linkCtx, perfTimings)
// defer a cleanup helper which will unlink the snap if anything fails after
// this point
defer func() {
if err == nil {
return
}
// err is not nil, we need to try and unlink the snap to cleanup after
// ourselves
var backendErr error
if newInfo.Type() == snap.TypeSnapd && !firstInstall {
// snapd snap is special in the sense that we always
// need the current symlink, so we restore the link to
// the old revision
_, backendErr = m.backend.LinkSnap(oldInfo, deviceCtx, linkCtx, perfTimings)
} else {
// snapd during first install and all other snaps
backendErr = m.backend.UnlinkSnap(newInfo, linkCtx, pb)
}
if backendErr != nil {
t.Errorf("cannot cleanup failed attempt at making snap %q available to the system: %v", snapsup.InstanceName(), backendErr)
}
notifyLinkParticipants(t, snapsup.InstanceName())
}()
if err != nil {
return err
}
// Restore configuration of the target revision (if available) on revert
if isInstalled {
// Make a copy of configuration of current snap revision
if err = config.SaveRevisionConfig(st, snapsup.InstanceName(), oldCurrent); err != nil {
return err
}
}
// Restore configuration of the target revision (if available; nothing happens if it's not).
// We only do this on reverts (and not on refreshes).
if snapsup.Revert {
if err = config.RestoreRevisionConfig(st, snapsup.InstanceName(), snapsup.Revision()); err != nil {
return err
}
}
if len(snapst.Sequence) == 1 {
if err := m.createSnapCookie(st, snapsup.InstanceName()); err != nil {
return fmt.Errorf("cannot create snap cookie: %v", err)
}
}
// save for undoLinkSnap
t.Set("old-trymode", oldTryMode)
t.Set("old-devmode", oldDevMode)
t.Set("old-jailmode", oldJailMode)
t.Set("old-classic", oldClassic)
t.Set("old-ignore-validation", oldIgnoreValidation)
t.Set("old-channel", oldChannel)
t.Set("old-current", oldCurrent)
t.Set("old-candidate-index", oldCandidateIndex)
t.Set("old-refresh-inhibited-time", oldRefreshInhibitedTime)
t.Set("old-cohort-key", oldCohortKey)
t.Set("old-last-refresh-time", oldLastRefreshTime)
t.Set("old-revs-before-cand", oldRevsBeforeCand)
if snapsup.Revert {
t.Set("old-revert-status", snapst.RevertStatus)
switch snapsup.RevertStatus {
case NotBlocked:
if snapst.RevertStatus == nil {
snapst.RevertStatus = make(map[int]RevertStatus)
}
snapst.RevertStatus[oldCurrent.N] = NotBlocked
default:
delete(snapst.RevertStatus, oldCurrent.N)
}
} else {
delete(snapst.RevertStatus, cand.Revision.N)
}
// Record the fact that the snap was refreshed successfully.
snapst.RefreshInhibitedTime = nil
if !snapsup.Revert {
now := timeNow()
snapst.LastRefreshTime = &now
}
if cand.SnapID != "" {
// write the auxiliary store info
aux := &auxStoreInfo{
Media: snapsup.Media,
Website: snapsup.Website,
}
if err := keepAuxStoreInfo(cand.SnapID, aux); err != nil {
return err
}
if len(snapst.Sequence) == 1 {
defer func() {
if err != nil {
// the install is getting undone, and there are no more of this snap
// try to remove the aux info we just created
discardAuxStoreInfo(cand.SnapID)
}
}()
}
}
// Compatibility with old snapd: check if we have auto-connect task and
// if not, inject it after self (link-snap) for snaps that are not core
if newInfo.Type() != snap.TypeOS {
var hasAutoConnect, hasSetupProfiles bool
for _, other := range t.Change().Tasks() {
// Check if this is auto-connect task for same snap and we it's part of the change with setup-profiles task
if other.Kind() == "auto-connect" || other.Kind() == "setup-profiles" {
otherSnapsup, err := TaskSnapSetup(other)
if err != nil {
return err
}
if snapsup.InstanceName() == otherSnapsup.InstanceName() {
if other.Kind() == "auto-connect" {
hasAutoConnect = true
} else {
hasSetupProfiles = true
}
}
}
}
if !hasAutoConnect && hasSetupProfiles {
InjectAutoConnect(t, snapsup)
}
}
// Do at the end so we only preserve the new state if it worked.
Set(st, snapsup.InstanceName(), snapst)
// Notify link snap participants about link changes.
notifyLinkParticipants(t, snapsup.InstanceName())
// Make sure if state commits and snapst is mutated we won't be rerun
finalStatus := state.DoneStatus
// if we just installed a core snap, request a restart
// so that we switch executing its snapd.
var canReboot bool
if rebootInfo.RebootRequired {
var cannotReboot bool
// system reboot is required, but can this task request that?
if err := t.Get("cannot-reboot", &cannotReboot); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
if !cannotReboot {
// either the task was created before that variable was
// introduced or the task can request a reboot
canReboot = true
} else {
t.Logf("reboot postponed to later tasks")
}
}
if !rebootInfo.RebootRequired || canReboot {
return m.finishTaskWithMaybeRestart(t, finalStatus, restartPossibility{info: newInfo, RebootInfo: rebootInfo})
} else {
t.SetStatus(finalStatus)
return nil
}
}
func setMigrationFlagsinState(snapst *SnapState, snapsup *SnapSetup) {
if snapsup.MigratedHidden {
snapst.MigratedHidden = true
} else if snapsup.UndidHiddenMigration {
snapst.MigratedHidden = false
}
if snapsup.MigratedToExposedHome || snapsup.EnableExposedHome {
snapst.MigratedToExposedHome = true
} else if snapsup.RemovedExposedHome || snapsup.DisableExposedHome {
snapst.MigratedToExposedHome = false
}
}
// restartPossibility carries information to decide whether a restart
// of some form is required. Non-nil pointers to values of it
// can be used in task code to signal that the task should return
// invoking finishTaskWithMaybeRestart.
type restartPossibility struct {
info *snap.Info
boot.RebootInfo
}
// finishTaskWithMaybeRestart will set the final status for the task
// and schedule a reboot or restart as needed for the just linked snap
// passed in through the restartPossibility parameter, based on the
// snap type.
func (m *SnapManager) finishTaskWithMaybeRestart(t *state.Task, status state.Status, restartPoss restartPossibility) error {
// Don't restart when preseeding - we will switch to new snapd on
// first boot.
if m.preseed {
return nil
}
st := t.State()
if restartPoss.RebootRequired {
t.Logf("Requested system restart.")
return FinishTaskWithRestart(t, status, restart.RestartSystem, &restartPoss.RebootInfo)
}
typ := restartPoss.info.Type()
// If the type of the snap requesting this start is non-trivial that either
// means we are on Ubuntu Core and the type is a base/kernel/gadget which
// requires a reboot of the system, or that the type is snapd in which case
// we just do a restart of snapd itself. In these cases restartReason will
// be non-empty and thus we will perform a restart.
// If restartReason is empty, then the snap requesting the restart was not
// a boot participant and thus we don't need to do any sort of restarts as
// a result of updating this snap.
restartReason := daemonRestartReason(st, typ)
if restartReason == "" {
// no message -> no restart
return nil
}
t.Logf(restartReason)
return FinishTaskWithRestart(t, status, restart.RestartDaemon, nil)
}
func daemonRestartReason(st *state.State, typ snap.Type) string {
if !((release.OnClassic && typ == snap.TypeOS) || typ == snap.TypeSnapd) {
// not interesting
return ""
}
if typ == snap.TypeOS {
// ignore error here as we have no way to return to caller
snapdSnapInstalled, _ := isInstalled(st, "snapd")
if snapdSnapInstalled {
// this snap is the base, but snapd is running from the snapd snap
return ""
}
return "Requested daemon restart."
}
return "Requested daemon restart (snapd snap)."
}
// maybeUndoRemodelBootChanges will check if an undo needs to update the
// bootloader. This can happen if e.g. a new kernel gets installed. This
// will switch the bootloader to the new kernel but if the change is later
// undone we need to switch back to the kernel of the old model.
// It returns a non-nil *restartPossibility if a restart should be considered.
func (m *SnapManager) maybeUndoRemodelBootChanges(t *state.Task) (*restartPossibility, error) {
// get the new and the old model
deviceCtx, err := DeviceCtx(t.State(), t, nil)
if err != nil {
return nil, err
}
// we only have an old model if we are in a remodel situation
if !deviceCtx.ForRemodeling() {
return nil, nil
}
groundDeviceCtx := deviceCtx.GroundContext()
oldModel := groundDeviceCtx.Model()
newModel := deviceCtx.Model()
// check type of the snap we are undoing, only kernel/base/core are
// relevant
snapsup, _, err := snapSetupAndState(t)
if err != nil {
return nil, err
}
var newSnapName, snapName string
switch snapsup.Type {
case snap.TypeKernel:
snapName = oldModel.Kernel()
newSnapName = newModel.Kernel()
case snap.TypeOS, snap.TypeBase:
// XXX: add support for "core"
snapName = oldModel.Base()
newSnapName = newModel.Base()
default:
return nil, nil
}
// we can stop if the kernel/base has not changed
if snapName == newSnapName {
return nil, nil
}
// we can stop if the snap we are looking at is not a kernel/base
// of the new model
if snapsup.InstanceName() != newSnapName {
return nil, nil
}
// get info for *old* kernel/base/core and see if we need to reboot
// TODO: we may need something like infoForDeviceSnap here
var snapst SnapState
if err = Get(t.State(), snapName, &snapst); err != nil {
return nil, err
}
info, err := snapst.CurrentInfo()
if err != nil && err != ErrNoCurrent {
return nil, err
}
bp := boot.Participant(info, info.Type(), groundDeviceCtx)
rebootInfo, err := bp.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
if err != nil {
return nil, err
}
// we may just have switch back to the old kernel/base/core so
// we may need to restart
return &restartPossibility{info: info, RebootInfo: rebootInfo}, nil
}
func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return err
}
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
var oldChannel string
err = t.Get("old-channel", &oldChannel)
if err != nil {
return err
}
var oldIgnoreValidation bool
err = t.Get("old-ignore-validation", &oldIgnoreValidation)
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
var oldTryMode bool
err = t.Get("old-trymode", &oldTryMode)
if err != nil {
return err
}
var oldDevMode bool
err = t.Get("old-devmode", &oldDevMode)
if err != nil {
return err
}
var oldJailMode bool
err = t.Get("old-jailmode", &oldJailMode)
if err != nil {
return err
}
var oldClassic bool
err = t.Get("old-classic", &oldClassic)
if err != nil {
return err
}
var oldCurrent snap.Revision
err = t.Get("old-current", &oldCurrent)
if err != nil {
return err
}
var oldCandidateIndex int
if err := t.Get("old-candidate-index", &oldCandidateIndex); err != nil {
return err
}
var oldRefreshInhibitedTime *time.Time
if err := t.Get("old-refresh-inhibited-time", &oldRefreshInhibitedTime); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
var oldLastRefreshTime *time.Time
if err := t.Get("old-last-refresh-time", &oldLastRefreshTime); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
var oldCohortKey string
if err := t.Get("old-cohort-key", &oldCohortKey); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
var oldRevsBeforeCand []snap.Revision
if err := t.Get("old-revs-before-cand", &oldRevsBeforeCand); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
if len(snapst.Sequence) == 1 {
// XXX: shouldn't these two just log and carry on? this is an undo handler...
timings.Run(perfTimings, "discard-snap-namespace", fmt.Sprintf("discard the namespace of snap %q", snapsup.InstanceName()), func(tm timings.Measurer) {
err = m.backend.DiscardSnapNamespace(snapsup.InstanceName())
})
if err != nil {
t.Errorf("cannot discard snap namespace %q, will retry in 3 mins: %s", snapsup.InstanceName(), err)
return &state.Retry{After: 3 * time.Minute}
}
if err := m.removeSnapCookie(st, snapsup.InstanceName()); err != nil {
return fmt.Errorf("cannot remove snap cookie: %v", err)
}
// try to remove the auxiliary store info
if err := discardAuxStoreInfo(snapsup.SideInfo.SnapID); err != nil {
return fmt.Errorf("cannot remove auxiliary store info: %v", err)
}
}
isRevert := snapsup.Revert
// relinking of the old snap is done in the undo of unlink-current-snap
currentIndex := snapst.LastIndex(snapst.Current)
if currentIndex < 0 {
return fmt.Errorf("internal error: cannot find revision %d in %v for undoing the added revision", snapsup.SideInfo.Revision, snapst.Sequence)
}
if oldCandidateIndex < 0 {
snapst.Sequence = append(snapst.Sequence[:currentIndex], snapst.Sequence[currentIndex+1:]...)
} else if !isRevert {
// account for revisions discarded before the install failed
discarded := countMissingRevs(oldRevsBeforeCand, snapst.Sequence)
oldCandidateIndex -= discarded
oldCand := snapst.Sequence[currentIndex]
copy(snapst.Sequence[oldCandidateIndex+1:], snapst.Sequence[oldCandidateIndex:])
snapst.Sequence[oldCandidateIndex] = oldCand
}
snapst.Current = oldCurrent
snapst.Active = false
snapst.TrackingChannel = oldChannel
snapst.IgnoreValidation = oldIgnoreValidation
snapst.TryMode = oldTryMode
snapst.DevMode = oldDevMode
snapst.JailMode = oldJailMode
snapst.Classic = oldClassic
snapst.RefreshInhibitedTime = oldRefreshInhibitedTime
snapst.LastRefreshTime = oldLastRefreshTime
snapst.CohortKey = oldCohortKey
if isRevert {
var oldRevertStatus map[int]RevertStatus
err := t.Get("old-revert-status", &oldRevertStatus)
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
// may be nil if not set (e.g. created by old snapd)
snapst.RevertStatus = oldRevertStatus
}
newInfo, err := readInfo(snapsup.InstanceName(), snapsup.SideInfo, 0)
if err != nil {
return err
}
// we need to undo potential changes to current snap configuration (e.g. if
// modified by post-refresh/install/configure hooks as part of failed
// refresh/install) by restoring the configuration of "old current".
// similarly, we need to re-save the disabled services if there is a
// revision for us to go back to, see comment below for full explanation
if len(snapst.Sequence) > 0 {
if err = config.RestoreRevisionConfig(st, snapsup.InstanceName(), oldCurrent); err != nil {
return err
}
} else {
// in the case of an install we need to clear any config
err = config.DeleteSnapConfig(st, snapsup.InstanceName())
if err != nil {
return err
}
}
pb := NewTaskProgressAdapterLocked(t)
firstInstall := oldCurrent.Unset()
linkCtx := backend.LinkContext{
FirstInstall: firstInstall,
IsUndo: true,
}
var backendErr error
if newInfo.Type() == snap.TypeSnapd && !firstInstall {
// snapst has been updated and now is the old revision, since
// this is not the first install of snapd, it should exist
var oldInfo *snap.Info
oldInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
// the snapd snap is special in the sense that we need to make
// sure that a sensible version is always linked as current,
// also we never reboot when updating snapd snap
_, backendErr = m.backend.LinkSnap(oldInfo, deviceCtx, linkCtx, perfTimings)
} else {
// snapd during first install and all other snaps
backendErr = m.backend.UnlinkSnap(newInfo, linkCtx, pb)
}
if backendErr != nil {
return backendErr
}
// restartPoss will be set if we should maybe schedule a restart
var restartPoss *restartPossibility
restartPoss, err = m.maybeUndoRemodelBootChanges(t)
if err != nil {
return err
}
// restart only when snapd was installed for the first time and the rest of
// the cleanup is performed by snapd from core;
// when reverting a subsequent snapd revision, the restart happens in
// undoLinkCurrentSnap() instead
if firstInstall && newInfo.Type() == snap.TypeSnapd {
restartPoss = &restartPossibility{info: newInfo, RebootInfo: boot.RebootInfo{RebootRequired: false}}
}
// write sequence file for failover helpers
if err := writeSeqFile(snapsup.InstanceName(), snapst); err != nil {
return err
}
// mark as inactive
Set(st, snapsup.InstanceName(), snapst)
// Notify link snap participants about link changes.
notifyLinkParticipants(t, snapsup.InstanceName())
// Finish task: set status, possibly restart
// Make sure if state commits and snapst is mutated we won't be rerun
finalStatus := state.UndoneStatus
t.SetStatus(finalStatus)
// should we maybe restart?
if restartPoss != nil {
return m.finishTaskWithMaybeRestart(t, finalStatus, *restartPoss)
}
// If we are on classic and have no previous version of core
// we may have restarted from a distro package into the core
// snap. We need to undo that restart here. Instead of in
// doUnlinkCurrentSnap() like we usually do when going from
// core snap -> next core snap
if release.OnClassic && newInfo.Type() == snap.TypeOS && oldCurrent.Unset() {
t.Logf("Requested daemon restart (undo classic initial core install)")
return FinishTaskWithRestart(t, finalStatus, restart.RestartDaemon, nil)
}
return nil
}
// countMissingRevs counts how many of the revisions aren't present in the sequence of sideInfos
func countMissingRevs(revisions []snap.Revision, sideInfos []*snap.SideInfo) int {
var found int
for _, rev := range revisions {
for _, si := range sideInfos {
if si.Revision == rev {
found++
}
}
}
return len(revisions) - found
}
type doSwitchFlags struct {
switchCurrentChannel bool
}
// doSwitchSnapChannel switches the snap's tracking channel and/or cohort. It
// also switches the current channel if appropriate. For use from 'Update'.
func (m *SnapManager) doSwitchSnapChannel(t *state.Task, _ *tomb.Tomb) error {
return m.genericDoSwitchSnap(t, doSwitchFlags{switchCurrentChannel: true})
}
// doSwitchSnap switches the snap's tracking channel and/or cohort, *without*
// switching the current snap channel. For use from 'Switch'.
func (m *SnapManager) doSwitchSnap(t *state.Task, _ *tomb.Tomb) error {
return m.genericDoSwitchSnap(t, doSwitchFlags{})
}
func (m *SnapManager) genericDoSwitchSnap(t *state.Task, flags doSwitchFlags) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
// switched the tracked channel
if err := snapst.SetTrackingChannel(snapsup.Channel); err != nil {
return err
}
snapst.CohortKey = snapsup.CohortKey
if flags.switchCurrentChannel {
// optionally support switching the current snap channel too, e.g.
// if a snap is in both stable and candidate with the same revision
// we can update it here and it will be displayed correctly in the UI
if snapsup.SideInfo.Channel != "" {
snapst.CurrentSideInfo().Channel = snapsup.Channel
}
}
Set(st, snapsup.InstanceName(), snapst)
return nil
}
func (m *SnapManager) doToggleSnapFlags(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
// for now we support toggling only ignore-validation
snapst.IgnoreValidation = snapsup.IgnoreValidation
Set(st, snapsup.InstanceName(), snapst)
return nil
}
// installModeDisabledServices returns what services with
// "install-mode: disabled" should be disabled. Only services
// seen for the first time are considered.
func installModeDisabledServices(st *state.State, snapst *SnapState, currentInfo *snap.Info) (svcsToDisable []string, err error) {
enabledByHookSvcs := map[string]bool{}
for _, svcName := range snapst.ServicesEnabledByHooks {
enabledByHookSvcs[svcName] = true
}
// find what servies the previous snap had
prevCurrentSvcs := map[string]bool{}
if psi := snapst.previousSideInfo(); psi != nil {
var prevCurrentInfo *snap.Info
prevCurrentInfo, err = Info(st, snapst.InstanceName(), psi.Revision)
if err != nil {
return nil, err
}
if prevCurrentInfo != nil {
for _, prevSvc := range prevCurrentInfo.Services() {
prevCurrentSvcs[prevSvc.Name] = true
}
}
}
// and deal with "install-mode: disable" for all new services
// (i.e. not present in previous snap).
//
// Services that are not new but have "install-mode: disable"
// do not need special handling. They are either still disabled
// or something has enabled them and then they should stay enabled.
for _, svc := range currentInfo.Services() {
if svc.InstallMode == "disable" && !enabledByHookSvcs[svc.Name] {
if !prevCurrentSvcs[svc.Name] {
svcsToDisable = append(svcsToDisable, svc.Name)
}
}
}
return svcsToDisable, nil
}
func (m *SnapManager) startSnapServices(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
currentInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
// check if any previously disabled services are now no longer services and
// log messages about that
for _, svc := range snapst.LastActiveDisabledServices {
app, ok := currentInfo.Apps[svc]
if !ok {
logger.Noticef("previously disabled service %s no longer exists", svc)
} else if !app.IsService() {
logger.Noticef("previously disabled service %s is now an app and not a service", svc)
}
}
// get the services which should be disabled (not started),
// as well as the services which are not present in this revision, but were
// present and disabled in a previous one and as such should be kept inside
// snapst for persistent storage
svcsToDisable, svcsToSave, err := missingDisabledServices(snapst.LastActiveDisabledServices, currentInfo)
if err != nil {
return err
}
// check what services with "InstallMode: disable" need to be disabled
svcsToDisableFromInstallMode, err := installModeDisabledServices(st, snapst, currentInfo)
if err != nil {
return err
}
svcsToDisable = append(svcsToDisable, svcsToDisableFromInstallMode...)
// append services that were disabled by hooks (they should not get re-enabled)
svcsToDisable = append(svcsToDisable, snapst.ServicesDisabledByHooks...)
// save the current last-active-disabled-services before we re-write it in case we
// need to undo this
t.Set("old-last-active-disabled-services", snapst.LastActiveDisabledServices)
// commit the missing services to state so when we unlink this revision and
// go to a different revision with potentially different service names, the
// currently missing service names will be re-disabled if they exist later
snapst.LastActiveDisabledServices = svcsToSave
// reset services tracked by operations from hooks
snapst.ServicesDisabledByHooks = nil
snapst.ServicesEnabledByHooks = nil
Set(st, snapsup.InstanceName(), snapst)
svcs := currentInfo.Services()
if len(svcs) == 0 {
return nil
}
startupOrdered, err := snap.SortServices(svcs)
if err != nil {
return err
}
pb := NewTaskProgressAdapterUnlocked(t)
st.Unlock()
err = m.backend.StartServices(startupOrdered, svcsToDisable, pb, perfTimings)
st.Lock()
return err
}
func (m *SnapManager) undoStartSnapServices(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
currentInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
var oldLastActiveDisabledServices []string
if err := t.Get("old-last-active-disabled-services", &oldLastActiveDisabledServices); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
snapst.LastActiveDisabledServices = oldLastActiveDisabledServices
Set(st, snapsup.InstanceName(), snapst)
svcs := currentInfo.Services()
if len(svcs) == 0 {
return nil
}
// XXX: stop reason not set on start task, should we have a new reason for undo?
var stopReason snap.ServiceStopReason
// stop the services
st.Unlock()
err = m.backend.StopServices(svcs, stopReason, progress.Null, perfTimings)
st.Lock()
if err != nil {
return err
}
return nil
}
func (m *SnapManager) stopSnapServices(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
currentInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
svcs := currentInfo.Services()
if len(svcs) == 0 {
return nil
}
var stopReason snap.ServiceStopReason
if err := t.Get("stop-reason", &stopReason); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
pb := NewTaskProgressAdapterUnlocked(t)
st.Unlock()
defer st.Lock()
// stop the services
err = m.backend.StopServices(svcs, stopReason, pb, perfTimings)
if err != nil {
return err
}
// get the disabled services after we stopped all the services.
// this list is not meant to save what services are disabled at any given
// time, specifically just what services are disabled while systemd loses
// track of the services. this list is also used to determine what services are enabled
// when we start services of a new revision of the snap in
// start-snap-services handler.
disabledServices, err := m.queryDisabledServices(currentInfo, pb)
if err != nil {
return err
}
st.Lock()
defer st.Unlock()
// for undo
t.Set("old-last-active-disabled-services", snapst.LastActiveDisabledServices)
// undo could queryDisabledServices, but this avoids it
t.Set("disabled-services", disabledServices)
// add to the disabled services list in snapst services which were disabled
// for usage across changes like in reverting and enabling after being
// disabled.
// we keep what's already in the list in snapst because that list is
// services which were previously present in the snap and disabled, but are
// no longer present.
snapst.LastActiveDisabledServices = append(
snapst.LastActiveDisabledServices,
disabledServices...,
)
// reset services tracked by operations from hooks
snapst.ServicesDisabledByHooks = nil
snapst.ServicesEnabledByHooks = nil
Set(st, snapsup.InstanceName(), snapst)
return nil
}
func (m *SnapManager) undoStopSnapServices(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
currentInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
svcs := currentInfo.Services()
if len(svcs) == 0 {
return nil
}
startupOrdered, err := snap.SortServices(svcs)
if err != nil {
return err
}
var lastActiveDisabled []string
if err := t.Get("old-last-active-disabled-services", &lastActiveDisabled); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
snapst.LastActiveDisabledServices = lastActiveDisabled
Set(st, snapsup.InstanceName(), snapst)
var disabledServices []string
if err := t.Get("disabled-services", &disabledServices); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
st.Unlock()
err = m.backend.StartServices(startupOrdered, disabledServices, progress.Null, perfTimings)
st.Lock()
if err != nil {
return err
}
return nil
}
func (m *SnapManager) doUnlinkSnap(t *state.Task, _ *tomb.Tomb) error {
// invoked only if snap has a current active revision, during remove or
// disable
// in case of the snapd snap, we only reach here if disabling or removal
// was deemed ok by earlier checks
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
info, err := Info(t.State(), snapsup.InstanceName(), snapsup.Revision())
if err != nil {
return err
}
// do the final unlink
unlinkCtx := backend.LinkContext{
FirstInstall: false,
}
err = m.backend.UnlinkSnap(info, unlinkCtx, NewTaskProgressAdapterLocked(t))
if err != nil {
return err
}
// mark as inactive
snapst.Active = false
Set(st, snapsup.InstanceName(), snapst)
// Notify link snap participants about link changes.
notifyLinkParticipants(t, snapsup.InstanceName())
return err
}
func (m *SnapManager) undoUnlinkSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
perfTimings := state.TimingsForTask(t)
defer perfTimings.Save(st)
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
isInstalled := snapst.IsInstalled()
if !isInstalled {
return fmt.Errorf("internal error: snap %q not installed anymore", snapsup.InstanceName())
}
info, err := snapst.CurrentInfo()
if err != nil {
return err
}
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return err
}
// undo here may be part of failed snap remove change, in which case a later
// "clear-snap" task could have been executed and some or all of the
// data of this snap could be lost. If that's the case, then we should not
// enable the snap back.
// XXX: should make an exception for snapd/core?
place := snapsup.placeInfo()
for _, dir := range []string{place.DataDir(), place.CommonDataDir()} {
if exists, _, _ := osutil.DirExists(dir); !exists {
t.Logf("cannot link snap %q back, some of its data has already been removed", snapsup.InstanceName())
// TODO: mark the snap broken at the SnapState level when we have
// such concept.
return nil
}
}
snapst.Active = true
Set(st, snapsup.InstanceName(), snapst)
opts, err := SnapServiceOptions(st, snapsup.InstanceName(), nil)
if err != nil {
return err
}
linkCtx := backend.LinkContext{
FirstInstall: false,
IsUndo: true,
ServiceOptions: opts,
}
reboot, err := m.backend.LinkSnap(info, deviceCtx, linkCtx, perfTimings)
if err != nil {
return err
}
// Notify link snap participants about link changes.
notifyLinkParticipants(t, snapsup.InstanceName())
// if we just linked back a core snap, request a restart
// so that we switch executing its snapd.
return m.finishTaskWithMaybeRestart(t, state.UndoneStatus, restartPossibility{info: info, RebootInfo: reboot})
}
func (m *SnapManager) doClearSnapData(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
snapsup, snapst, err := snapSetupAndState(t)
st.Unlock()
if err != nil {
return err
}
st.Lock()
info, err := Info(t.State(), snapsup.InstanceName(), snapsup.Revision())
st.Unlock()
if err != nil {
return err
}
st.Lock()
opts, err := getDirMigrationOpts(st, snapst, snapsup)
st.Unlock()
if err != nil {
return err
}
dirOpts := opts.getSnapDirOpts()
if err = m.backend.RemoveSnapData(info, dirOpts); err != nil {
return err
}
if len(snapst.Sequence) == 1 {
// Only remove data common between versions if this is the last version
if err = m.backend.RemoveSnapCommonData(info, dirOpts); err != nil {
return err
}
// Same for the common snap save data directory, we only remove it if this
// is the last version.
st.Lock()
deviceCtx, err := DeviceCtx(t.State(), t, nil)
st.Unlock()
if err != nil {
return err
}
if err = m.backend.RemoveSnapSaveData(info, deviceCtx); err != nil {
return err
}
st.Lock()
defer st.Unlock()
otherInstances, err := hasOtherInstances(st, snapsup.InstanceName())
if err != nil {
return err
}
// Snap data directory can be removed now too
if err := m.backend.RemoveSnapDataDir(info, otherInstances); err != nil {
return err
}
}
return nil
}
func (m *SnapManager) doDiscardSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
deviceCtx, err := DeviceCtx(st, t, nil)
if err != nil {
return err
}
if snapst.Current == snapsup.Revision() && snapst.Active {
return fmt.Errorf("internal error: cannot discard snap %q: still active", snapsup.InstanceName())
}
// drop any potential revert status for this revision
delete(snapst.RevertStatus, snapsup.Revision().N)
if len(snapst.Sequence) == 1 {
snapst.Sequence = nil
snapst.Current = snap.Revision{}
} else {
newSeq := make([]*snap.SideInfo, 0, len(snapst.Sequence))
for _, si := range snapst.Sequence {
if si.Revision == snapsup.Revision() {
// leave out
continue
}
newSeq = append(newSeq, si)
}
snapst.Sequence = newSeq
if snapst.Current == snapsup.Revision() {
snapst.Current = newSeq[len(newSeq)-1].Revision
}
}
pb := NewTaskProgressAdapterLocked(t)
typ, err := snapst.Type()
if err != nil {
return err
}
err = m.backend.RemoveSnapFiles(snapsup.placeInfo(), typ, nil, deviceCtx, pb)
if err != nil {
t.Errorf("cannot remove snap file %q, will retry in 3 mins: %s", snapsup.InstanceName(), err)
return &state.Retry{After: 3 * time.Minute}
}
if len(snapst.Sequence) == 0 {
if err = m.backend.RemoveSnapMountUnits(snapsup.placeInfo(), nil); err != nil {
return err
}
if err := pruneRefreshCandidates(st, snapsup.InstanceName()); err != nil {
return err
}
if err := pruneSnapsHold(st, snapsup.InstanceName()); err != nil {
return err
}
// Remove configuration associated with this snap.
err = config.DeleteSnapConfig(st, snapsup.InstanceName())
if err != nil {
return err
}
err = m.backend.DiscardSnapNamespace(snapsup.InstanceName())
if err != nil {
t.Errorf("cannot discard snap namespace %q, will retry in 3 mins: %s", snapsup.InstanceName(), err)
return &state.Retry{After: 3 * time.Minute}
}
err = m.backend.RemoveSnapInhibitLock(snapsup.InstanceName())
if err != nil {
return err
}
if err := m.removeSnapCookie(st, snapsup.InstanceName()); err != nil {
return fmt.Errorf("cannot remove snap cookie: %v", err)
}
otherInstances, err := hasOtherInstances(st, snapsup.InstanceName())
if err != nil {
return err
}
if err := m.backend.RemoveSnapDir(snapsup.placeInfo(), otherInstances); err != nil {
return fmt.Errorf("cannot remove snap directory: %v", err)
}
// try to remove the auxiliary store info
if err := discardAuxStoreInfo(snapsup.SideInfo.SnapID); err != nil {
logger.Noticef("Cannot remove auxiliary store info for %q: %v", snapsup.InstanceName(), err)
}
// XXX: also remove sequence files?
// remove the snap from any quota groups it may have been in, otherwise
// that quota group may get into an inconsistent state
if err := EnsureSnapAbsentFromQuotaGroup(st, snapsup.InstanceName()); err != nil {
return err
}
}
if err = config.DiscardRevisionConfig(st, snapsup.InstanceName(), snapsup.Revision()); err != nil {
return err
}
if err = SecurityProfilesRemoveLate(snapsup.InstanceName(), snapsup.Revision(), snapsup.Type); err != nil {
return err
}
Set(st, snapsup.InstanceName(), snapst)
return nil
}
/* aliases v2
aliases v2 implementation uses the following tasks:
* for install/refresh/remove/enable/disable etc
- remove-aliases: remove aliases of a snap from disk and mark them pending
- setup-aliases: (re)creates aliases from snap state, mark them as
not pending
- set-auto-aliases: updates aliases snap state based on the
snap-declaration and current revision info of the snap
* for refresh & when the snap-declaration aliases change without a
new revision
- refresh-aliases: updates aliases snap state and updates them on disk too;
its undo is used generically by other tasks as well
- prune-auto-aliases: used for the special case of automatic
aliases transferred from one snap to another to prune them from
the source snaps to avoid conflicts in later operations
* for alias/unalias/prefer:
- alias: creates a manual alias
- unalias: removes a manual alias
- disable-aliases: disable the automatic aliases of a snap and
removes all manual ones as well
- prefer-aliases: enables the automatic aliases of a snap after
disabling any other snap conflicting aliases
*/
func (m *SnapManager) doSetAutoAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
curInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
// --unaliased
if snapsup.Unaliased {
t.Set("old-auto-aliases-disabled", snapst.AutoAliasesDisabled)
snapst.AutoAliasesDisabled = true
}
curAliases := snapst.Aliases
// TODO: implement --prefer logic
newAliases, err := refreshAliases(st, curInfo, curAliases)
if err != nil {
return err
}
_, err = checkAliasesConflicts(st, snapName, snapst.AutoAliasesDisabled, newAliases, nil)
if err != nil {
return err
}
t.Set("old-aliases-v2", curAliases)
// noop, except on first install where we need to set this here
snapst.AliasesPending = true
snapst.Aliases = newAliases
Set(st, snapName, snapst)
return nil
}
func (m *SnapManager) doRemoveAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
err = m.backend.RemoveSnapAliases(snapName)
if err != nil {
return err
}
snapst.AliasesPending = true
Set(st, snapName, snapst)
return nil
}
func (m *SnapManager) doSetupAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
curAliases := snapst.Aliases
_, _, err = applyAliasesChange(snapName, autoDis, nil, snapst.AutoAliasesDisabled, curAliases, m.backend, doApply)
if err != nil {
return err
}
snapst.AliasesPending = false
Set(st, snapName, snapst)
return nil
}
func (m *SnapManager) doRefreshAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
curInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
autoDisabled := snapst.AutoAliasesDisabled
curAliases := snapst.Aliases
newAliases, err := refreshAliases(st, curInfo, curAliases)
if err != nil {
return err
}
_, err = checkAliasesConflicts(st, snapName, autoDisabled, newAliases, nil)
if err != nil {
return err
}
if !snapst.AliasesPending {
if _, _, err := applyAliasesChange(snapName, autoDisabled, curAliases, autoDisabled, newAliases, m.backend, doApply); err != nil {
return err
}
}
t.Set("old-aliases-v2", curAliases)
snapst.Aliases = newAliases
Set(st, snapName, snapst)
return nil
}
func (m *SnapManager) undoRefreshAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
var oldAliases map[string]*AliasTarget
err := t.Get("old-aliases-v2", &oldAliases)
if errors.Is(err, state.ErrNoState) {
// nothing to do
return nil
}
if err != nil {
return err
}
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
curAutoDisabled := snapst.AutoAliasesDisabled
autoDisabled := curAutoDisabled
if err = t.Get("old-auto-aliases-disabled", &autoDisabled); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
var otherSnapDisabled map[string]*otherDisabledAliases
if err = t.Get("other-disabled-aliases", &otherSnapDisabled); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
// check if the old states creates conflicts now
_, err = checkAliasesConflicts(st, snapName, autoDisabled, oldAliases, nil)
if _, ok := err.(*AliasConflictError); ok {
// best we can do is reinstate with all aliases disabled
t.Errorf("cannot reinstate alias state because of conflicts, disabling: %v", err)
oldAliases, _ = disableAliases(oldAliases)
autoDisabled = true
} else if err != nil {
return err
}
if !snapst.AliasesPending {
curAliases := snapst.Aliases
if _, _, err := applyAliasesChange(snapName, curAutoDisabled, curAliases, autoDisabled, oldAliases, m.backend, doApply); err != nil {
return err
}
}
snapst.AutoAliasesDisabled = autoDisabled
snapst.Aliases = oldAliases
newSnapStates := make(map[string]*SnapState, 1+len(otherSnapDisabled))
newSnapStates[snapName] = snapst
// if we disabled other snap aliases try to undo that
conflicting := make(map[string]bool, len(otherSnapDisabled))
otherCurSnapStates := make(map[string]*SnapState, len(otherSnapDisabled))
for otherSnap, otherDisabled := range otherSnapDisabled {
var otherSnapState SnapState
err := Get(st, otherSnap, &otherSnapState)
if err != nil {
return err
}
otherCurInfo, err := otherSnapState.CurrentInfo()
if err != nil {
return err
}
otherCurSnapStates[otherSnap] = &otherSnapState
autoDisabled := otherSnapState.AutoAliasesDisabled
if otherDisabled.Auto {
// automatic aliases of other were disabled, undo that
autoDisabled = false
}
otherAliases := reenableAliases(otherCurInfo, otherSnapState.Aliases, otherDisabled.Manual)
// check for conflicts taking into account
// re-enabled aliases
conflicts, err := checkAliasesConflicts(st, otherSnap, autoDisabled, otherAliases, newSnapStates)
if _, ok := err.(*AliasConflictError); ok {
conflicting[otherSnap] = true
for conflictSnap := range conflicts {
conflicting[conflictSnap] = true
}
} else if err != nil {
return err
}
newSnapState := otherSnapState
newSnapState.Aliases = otherAliases
newSnapState.AutoAliasesDisabled = autoDisabled
newSnapStates[otherSnap] = &newSnapState
}
// apply non-conflicting other
for otherSnap, otherSnapState := range otherCurSnapStates {
if conflicting[otherSnap] {
// keep as it was
continue
}
newSnapSt := newSnapStates[otherSnap]
if !otherSnapState.AliasesPending {
if _, _, err := applyAliasesChange(otherSnap, otherSnapState.AutoAliasesDisabled, otherSnapState.Aliases, newSnapSt.AutoAliasesDisabled, newSnapSt.Aliases, m.backend, doApply); err != nil {
return err
}
}
}
for instanceName, snapst := range newSnapStates {
if conflicting[instanceName] {
// keep as it was
continue
}
Set(st, instanceName, snapst)
}
return nil
}
func (m *SnapManager) doPruneAutoAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
var which []string
err = t.Get("aliases", &which)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
autoDisabled := snapst.AutoAliasesDisabled
curAliases := snapst.Aliases
newAliases := pruneAutoAliases(curAliases, which)
if !snapst.AliasesPending {
if _, _, err := applyAliasesChange(snapName, autoDisabled, curAliases, autoDisabled, newAliases, m.backend, doApply); err != nil {
return err
}
}
t.Set("old-aliases-v2", curAliases)
snapst.Aliases = newAliases
Set(st, snapName, snapst)
return nil
}
type changedAlias struct {
Snap string `json:"snap"`
App string `json:"app"`
Alias string `json:"alias"`
}
func aliasesTrace(t *state.Task, added, removed []*backend.Alias) error {
chg := t.Change()
var data map[string]interface{}
err := chg.Get("api-data", &data)
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
if len(data) == 0 {
data = make(map[string]interface{})
}
curAdded, _ := data["aliases-added"].([]interface{})
for _, a := range added {
snap, app := snap.SplitSnapApp(a.Target)
curAdded = append(curAdded, &changedAlias{
Snap: snap,
App: app,
Alias: a.Name,
})
}
data["aliases-added"] = curAdded
curRemoved, _ := data["aliases-removed"].([]interface{})
for _, a := range removed {
snap, app := snap.SplitSnapApp(a.Target)
curRemoved = append(curRemoved, &changedAlias{
Snap: snap,
App: app,
Alias: a.Name,
})
}
data["aliases-removed"] = curRemoved
chg.Set("api-data", data)
return nil
}
func (m *SnapManager) doAlias(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
var target, alias string
err = t.Get("target", &target)
if err != nil {
return err
}
err = t.Get("alias", &alias)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
curInfo, err := snapst.CurrentInfo()
if err != nil {
return err
}
autoDisabled := snapst.AutoAliasesDisabled
curAliases := snapst.Aliases
newAliases, err := manualAlias(curInfo, curAliases, target, alias)
if err != nil {
return err
}
_, err = checkAliasesConflicts(st, snapName, autoDisabled, newAliases, nil)
if err != nil {
return err
}
added, removed, err := applyAliasesChange(snapName, autoDisabled, curAliases, autoDisabled, newAliases, m.backend, snapst.AliasesPending)
if err != nil {
return err
}
if err := aliasesTrace(t, added, removed); err != nil {
return err
}
t.Set("old-aliases-v2", curAliases)
snapst.Aliases = newAliases
Set(st, snapName, snapst)
return nil
}
func (m *SnapManager) doDisableAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
oldAutoDisabled := snapst.AutoAliasesDisabled
oldAliases := snapst.Aliases
newAliases, _ := disableAliases(oldAliases)
added, removed, err := applyAliasesChange(snapName, oldAutoDisabled, oldAliases, autoDis, newAliases, m.backend, snapst.AliasesPending)
if err != nil {
return err
}
if err := aliasesTrace(t, added, removed); err != nil {
return err
}
t.Set("old-auto-aliases-disabled", oldAutoDisabled)
snapst.AutoAliasesDisabled = true
t.Set("old-aliases-v2", oldAliases)
snapst.Aliases = newAliases
Set(st, snapName, snapst)
return nil
}
func (m *SnapManager) doUnalias(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
var alias string
err = t.Get("alias", &alias)
if err != nil {
return err
}
snapName := snapsup.InstanceName()
autoDisabled := snapst.AutoAliasesDisabled
oldAliases := snapst.Aliases
newAliases, err := manualUnalias(oldAliases, alias)
if err != nil {
return err
}
added, removed, err := applyAliasesChange(snapName, autoDisabled, oldAliases, autoDisabled, newAliases, m.backend, snapst.AliasesPending)
if err != nil {
return err
}
if err := aliasesTrace(t, added, removed); err != nil {
return err
}
t.Set("old-aliases-v2", oldAliases)
snapst.Aliases = newAliases
Set(st, snapName, snapst)
return nil
}
// otherDisabledAliases is used to track for the benefit of undo what
// changes were made aka what aliases were disabled of another
// conflicting snap by prefer logic
type otherDisabledAliases struct {
// Auto records whether prefer had to disable automatic aliases
Auto bool `json:"auto,omitempty"`
// Manual records which manual aliases were removed by prefer
Manual map[string]string `json:"manual,omitempty"`
}
func (m *SnapManager) doPreferAliases(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snapsup, snapst, err := snapSetupAndState(t)
if err != nil {
return err
}
instanceName := snapsup.InstanceName()
if !snapst.AutoAliasesDisabled {
// already enabled, nothing to do
return nil
}
curAliases := snapst.Aliases
aliasConflicts, err := checkAliasesConflicts(st, instanceName, autoEn, curAliases, nil)
conflErr, isConflErr := err.(*AliasConflictError)
if err != nil && !isConflErr {
return err
}
if isConflErr && conflErr.Conflicts == nil {
// it's a snap command namespace conflict, we cannot remedy it
return conflErr
}
// proceed to disable conflicting aliases as needed
// before re-enabling instanceName aliases
otherSnapStates := make(map[string]*SnapState, len(aliasConflicts))
otherSnapDisabled := make(map[string]*otherDisabledAliases, len(aliasConflicts))
for otherSnap := range aliasConflicts {
var otherSnapState SnapState
err := Get(st, otherSnap, &otherSnapState)
if err != nil {
return err
}
otherAliases, disabledManual := disableAliases(otherSnapState.Aliases)
added, removed, err := applyAliasesChange(otherSnap, otherSnapState.AutoAliasesDisabled, otherSnapState.Aliases, autoDis, otherAliases, m.backend, otherSnapState.AliasesPending)
if err != nil {
return err
}
if err := aliasesTrace(t, added, removed); err != nil {
return err
}
var otherDisabled otherDisabledAliases
otherDisabled.Manual = disabledManual
otherSnapState.Aliases = otherAliases
// disable automatic aliases as needed
if !otherSnapState.AutoAliasesDisabled && len(otherAliases) != 0 {
// record that we did disable automatic aliases
otherDisabled.Auto = true
otherSnapState.AutoAliasesDisabled = true
}
otherSnapDisabled[otherSnap] = &otherDisabled
otherSnapStates[otherSnap] = &otherSnapState
}
added, removed, err := applyAliasesChange(instanceName, autoDis, curAliases, autoEn, curAliases, m.backend, snapst.AliasesPending)
if err != nil {
return err
}
if err := aliasesTrace(t, added, removed); err != nil {
return err
}
for otherSnap, otherSnapState := range otherSnapStates {
Set(st, otherSnap, otherSnapState)
}
if len(otherSnapDisabled) != 0 {
t.Set("other-disabled-aliases", otherSnapDisabled)
}
t.Set("old-auto-aliases-disabled", true)
t.Set("old-aliases-v2", curAliases)
snapst.AutoAliasesDisabled = false
Set(st, instanceName, snapst)
return nil
}
// changeReadyUpToTask returns whether all other change's tasks are Ready.
func changeReadyUpToTask(task *state.Task) bool {
me := task.ID()
change := task.Change()
for _, task := range change.Tasks() {
if me == task.ID() {
// ignore self
continue
}
if !task.Status().Ready() {
return false
}
}
return true
}
// refreshedSnaps returns the instance names of the snaps successfully refreshed
// in the last batch of refreshes before the given (re-refresh) task; failed is
// true if any of the snaps failed to refresh.
//
// It does this by advancing through the given task's change's tasks, keeping
// track of the instance names from the first SnapSetup in every lane, stopping
// when finding the given task, and resetting things when finding a different
// re-refresh task (that indicates the end of a batch that isn't the given one).
func refreshedSnaps(reTask *state.Task) (snapNames []string, failed bool) {
// NOTE nothing requires reTask to be a check-rerefresh task, nor even to be in
// a refresh-ish change, but it doesn't make much sense to call this otherwise.
tid := reTask.ID()
laneSnaps := map[int]string{}
// change.Tasks() preserves the order tasks were added, otherwise it all falls apart
for _, task := range reTask.Change().Tasks() {
if task.ID() == tid {
// we've reached ourselves; we don't care about anything beyond this
break
}
if task.Kind() == "check-rerefresh" {
// we've reached a previous check-rerefresh (but not ourselves).
// Only snaps in tasks after this point are of interest.
laneSnaps = map[int]string{}
}
lanes := task.Lanes()
if len(lanes) != 1 {
// can't happen, really
continue
}
lane := lanes[0]
if lane == 0 {
// not really a lane
continue
}
if task.Status() != state.DoneStatus {
// ignore non-successful lane (1)
laneSnaps[lane] = ""
continue
}
if _, ok := laneSnaps[lane]; ok {
// ignore lanes we've already seen (including ones explicitly ignored in (1))
continue
}
var snapsup SnapSetup
if err := task.Get("snap-setup", &snapsup); err != nil {
continue
}
laneSnaps[lane] = snapsup.InstanceName()
}
snapNames = make([]string, 0, len(laneSnaps))
for _, name := range laneSnaps {
if name == "" {
// the lane was unsuccessful
failed = true
continue
}
snapNames = append(snapNames, name)
}
return snapNames, failed
}
// reRefreshSetup holds the necessary details to re-refresh snaps that need it
type reRefreshSetup struct {
UserID int `json:"user-id,omitempty"`
*Flags
}
// reRefreshUpdateMany exists just to make testing simpler
var reRefreshUpdateMany = updateManyFiltered
// reRefreshFilter is an updateFilter that returns whether the given update
// needs a re-refresh because of further epoch transitions available.
func reRefreshFilter(update *snap.Info, snapst *SnapState) bool {
cur, err := snapst.CurrentInfo()
if err != nil {
return false
}
return !update.Epoch.Equal(&cur.Epoch)
}
var reRefreshRetryTimeout = time.Second / 2
func (m *SnapManager) doCheckReRefresh(t *state.Task, tomb *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
if numHaltTasks := t.NumHaltTasks(); numHaltTasks > 0 {
logger.Panicf("Re-refresh task has %d tasks waiting for it.", numHaltTasks)
}
if !changeReadyUpToTask(t) {
return &state.Retry{After: reRefreshRetryTimeout, Reason: "pending refreshes"}
}
snaps, failed := refreshedSnaps(t)
if len(snaps) > 0 {
if err := pruneRefreshCandidates(st, snaps...); err != nil {
return err
}
}
chg := t.Change()
// if any snap failed to refresh, reconsider validation set tracking
if failed {
tasksets, err := maybeRestoreValidationSetsAndRevertSnaps(st, snaps, chg.ID())
if err != nil {
return err
}
if len(tasksets) > 0 {
chg := t.Change()
for _, taskset := range tasksets {
chg.AddAll(taskset)
}
st.EnsureBefore(0)
t.SetStatus(state.DoneStatus)
return nil
}
// else - validation sets tracking got restored or wasn't affected, carry on
}
if len(snaps) == 0 {
// nothing to do (maybe everything failed)
return nil
}
// update validation sets stack: there are two possibilities
// - if maybeRestoreValidationSetsAndRevertSnaps restored previous tracking
// or refresh succeeded and it hasn't changed then this is a noop
// (AddCurrentTrackingToValidationSetsStack ignores tracking if identical
// to the topmost stack entry);
// - if maybeRestoreValidationSetsAndRevertSnaps kept new tracking
// because its constraints were met even after partial failure or
// refresh succeeded and tracking got updated, then
// this creates a new copy of validation-sets tracking data.
if AddCurrentTrackingToValidationSetsStack != nil {
if err := AddCurrentTrackingToValidationSetsStack(st); err != nil {
return err
}
}
var re reRefreshSetup
if err := t.Get("rerefresh-setup", &re); err != nil {
return err
}
updated, tasksets, err := reRefreshUpdateMany(tomb.Context(nil), st, snaps, nil, re.UserID, reRefreshFilter, re.Flags, chg.ID())
if err != nil {
return err
}
if len(updated) == 0 {
t.Logf("No re-refreshes found.")
} else {
t.Logf("Found re-refresh for %s.", strutil.Quoted(updated))
for _, taskset := range tasksets {
chg.AddAll(taskset)
}
st.EnsureBefore(0)
}
t.SetStatus(state.DoneStatus)
return nil
}
func (m *SnapManager) doConditionalAutoRefresh(t *state.Task, tomb *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
snaps, err := snapsToRefresh(t)
if err != nil {
return err
}
if len(snaps) == 0 {
logger.Debugf("refresh gating: no snaps to refresh")
return nil
}
tss, err := autoRefreshPhase2(context.TODO(), st, snaps, t.Change().ID())
if err != nil {
return err
}
// update the map of refreshed snaps on the task, this affects
// conflict checks (we don't want to conflict on snaps that were held and
// won't be refreshed) - see conditionalAutoRefreshAffectedSnaps().
newToUpdate := make(map[string]*refreshCandidate, len(snaps))
for _, candidate := range snaps {
newToUpdate[candidate.InstanceName()] = candidate
}
t.Set("snaps", newToUpdate)
// update original auto-refresh change
chg := t.Change()
for _, ts := range tss {
ts.WaitFor(t)
chg.AddAll(ts)
}
t.SetStatus(state.DoneStatus)
st.EnsureBefore(0)
return nil
}
func (m *SnapManager) doMigrateSnapHome(t *state.Task, tomb *tomb.Tomb) error {
st := t.State()
st.Lock()
snapsup, snapst, err := snapSetupAndState(t)
st.Unlock()
if err != nil {
return err
}
st.Lock()
opts, err := getDirMigrationOpts(st, snapst, snapsup)
st.Unlock()
if err != nil {
return err
}
dirOpts := opts.getSnapDirOpts()
undo, err := m.backend.InitExposedSnapHome(snapsup.InstanceName(), snapsup.Revision(), dirOpts)
if err != nil {
return err
}
st.Lock()
defer st.Unlock()
t.Set("undo-exposed-home-init", undo)
snapsup.MigratedToExposedHome = true
return SetTaskSnapSetup(t, snapsup)
}
func (m *SnapManager) undoMigrateSnapHome(t *state.Task, tomb *tomb.Tomb) error {
st := t.State()
st.Lock()
snapsup, snapst, err := snapSetupAndState(t)
st.Unlock()
if err != nil {
return err
}
var undo backend.UndoInfo
st.Lock()
err = t.Get("undo-exposed-home-init", &undo)
st.Unlock()
if err != nil {
return err
}
if err := m.backend.UndoInitExposedSnapHome(snapsup.InstanceName(), &undo); err != nil {
return err
}
snapsup.MigratedToExposedHome = false
snapst.MigratedToExposedHome = false
st.Lock()
defer st.Unlock()
return writeMigrationStatus(t, snapst, snapsup)
}
func (m *SnapManager) doEnforceValidationSets(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
defer st.Unlock()
var userID int
if err := t.Get("userID", &userID); err != nil {
return err
}
encodedAsserts := make(map[string][]byte)
if err := t.Get("validation-sets", &encodedAsserts); err != nil {
return err
}
decodedAsserts := make(map[string]*asserts.ValidationSet, len(encodedAsserts))
for vsStr, encAssert := range encodedAsserts {
decAssert, err := asserts.Decode(encAssert)
if err != nil {
return err
}
vsAssert, ok := decAssert.(*asserts.ValidationSet)
if !ok {
return errors.New("expected encoded assertion to be of type ValidationSet")
}
decodedAsserts[vsStr] = vsAssert
}
var pinnedSeqs map[string]int
if err := t.Get("pinned-sequence-numbers", &pinnedSeqs); err != nil {
return err
}
snaps, ignoreValidation, err := InstalledSnaps(st)
if err != nil {
return err
}
if err := EnforceValidationSets(st, decodedAsserts, pinnedSeqs, snaps, ignoreValidation, userID); err != nil {
return fmt.Errorf("cannot enforce validation sets: %v", err)
}
return nil
}
// maybeRestoreValidationSetsAndRevertSnaps restores validation-sets to their
// previous state using validation sets stack if there are any enforced
// validation sets and - if necessary - creates tasksets to revert some or all
// of the refreshed snaps to their previous revisions to satisfy the restored
// validation sets tracking.
var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSnaps []string, fromChange string) ([]*state.TaskSet, error) {
enforcedSets, err := EnforcedValidationSets(st)
if err != nil {
return nil, err
}
if enforcedSets == nil {
// no enforced validation sets, nothing to do
return nil, nil
}
installedSnaps, ignoreValidation, err := InstalledSnaps(st)
if err != nil {
return nil, fmt.Errorf("internal error: cannot get installed snaps: %v", err)
}
if err := enforcedSets.CheckInstalledSnaps(installedSnaps, ignoreValidation); err == nil {
// validation sets are still correct, nothing to do
logger.Debugf("validation sets are still correct after partial refresh")
return nil, nil
}
// restore previous validation sets tracking state
if err := RestoreValidationSetsTracking(st); err != nil {
return nil, fmt.Errorf("cannot restore validation sets: %v", err)
}
// no snaps were refreshed, after restoring validation sets tracking
// there is nothing else to do
if len(refreshedSnaps) == 0 {
logger.Debugf("validation set tracking restored, no snaps were refreshed")
return nil, nil
}
// we need to fetch enforced sets again because of RestoreValidationSetsTracking.
enforcedSets, err = EnforcedValidationSets(st)
if err != nil {
return nil, err
}
if enforcedSets == nil {
return nil, fmt.Errorf("internal error: no enforced validation sets after restoring from the stack")
}
// check installed snaps again against restored validation-sets.
// this may fail which is fine, but it tells us which snaps are
// at invalid revisions and need reverting.
err = enforcedSets.CheckInstalledSnaps(installedSnaps, ignoreValidation)
if err == nil {
// all fine after restoring validation sets: this can happen if previous
// validation sets only required a snap (regardless of its revision), then
// after update they require a specific snap revision, so after restoring
// we are back with the good state.
logger.Debugf("validation sets still valid after partial refresh, no snaps need reverting")
return nil, nil
}
verr, ok := err.(*snapasserts.ValidationSetsValidationError)
if !ok {
return nil, fmt.Errorf("internal error: %v", err)
}
if len(verr.WrongRevisionSnaps) == 0 {
// if we hit ValidationSetsValidationError but it's not about wrong revisions,
// then something is really broken (we shouldn't have invalid or missing required
// snaps at this point).
return nil, fmt.Errorf("internal error: unexpected validation error of installed snaps after unsuccesfull refresh: %v", verr)
}
wrongRevSnaps := func() []string {
snaps := make([]string, 0, len(verr.WrongRevisionSnaps))
for sn := range verr.WrongRevisionSnaps {
snaps = append(snaps, sn)
}
return snaps
}
logger.Debugf("refreshed snaps: %s, snaps at wrong revisions: %s", strutil.Quoted(refreshedSnaps), strutil.Quoted(wrongRevSnaps()))
// revert some or all snaps
var tss []*state.TaskSet
for _, snapName := range refreshedSnaps {
if verr.WrongRevisionSnaps[snapName] != nil {
// XXX: should we be extra paranoid and use RevertToRevision with
// the specific revision from verr.WrongRevisionSnaps?
ts, err := Revert(st, snapName, Flags{RevertStatus: NotBlocked}, fromChange)
if err != nil {
return nil, fmt.Errorf("cannot revert snap %q: %v", snapName, err)
}
tss = append(tss, ts)
delete(verr.WrongRevisionSnaps, snapName)
}
}
if len(verr.WrongRevisionSnaps) > 0 {
return nil, fmt.Errorf("internal error: some snaps were not refreshed but are at wrong revisions: %s", strutil.Quoted(wrongRevSnaps()))
}
return tss, nil
}
// InjectTasks makes all the halt tasks of the mainTask wait for extraTasks;
// extraTasks join the same lane and change as the mainTask.
func InjectTasks(mainTask *state.Task, extraTasks *state.TaskSet) {
lanes := mainTask.Lanes()
if len(lanes) == 1 && lanes[0] == 0 {
lanes = nil
}
for _, l := range lanes {
extraTasks.JoinLane(l)
}
chg := mainTask.Change()
// Change shouldn't normally be nil, except for cases where
// this helper is used before tasks are added to a change.
if chg != nil {
chg.AddAll(extraTasks)
}
// make all halt tasks of the mainTask wait on extraTasks
ht := mainTask.HaltTasks()
for _, t := range ht {
t.WaitAll(extraTasks)
}
// make the extra tasks wait for main task
extraTasks.WaitFor(mainTask)
}
func InjectAutoConnect(mainTask *state.Task, snapsup *SnapSetup) {
st := mainTask.State()
autoConnect := st.NewTask("auto-connect", fmt.Sprintf(i18n.G("Automatically connect eligible plugs and slots of snap %q"), snapsup.InstanceName()))
autoConnect.Set("snap-setup", snapsup)
InjectTasks(mainTask, state.NewTaskSet(autoConnect))
mainTask.Logf("added auto-connect task")
}
type dirMigrationOptions struct {
// UseHidden states whether the user has requested that the hidden data dir be used
UseHidden bool
// MigratedToHidden states whether the data has been migrated to the hidden dir
MigratedToHidden bool
// MigratedToExposedHome states whether the ~/Snap migration has been done.
MigratedToExposedHome bool
}
// GetSnapDirOpts returns the snap dir options based on the current the migration status
func (o *dirMigrationOptions) getSnapDirOpts() *dirs.SnapDirOptions {
return &dirs.SnapDirOptions{HiddenSnapDataDir: o.MigratedToHidden, MigratedToExposedHome: o.MigratedToExposedHome}
}
// GetSnapDirOpts returns the options required to get the correct snap dir.
var GetSnapDirOpts = func(st *state.State, name string) (*dirs.SnapDirOptions, error) {
var snapst SnapState
if err := Get(st, name, &snapst); err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
hiddenOpts, err := getDirMigrationOpts(st, &snapst, nil)
if err != nil {
return nil, err
}
return hiddenOpts.getSnapDirOpts(), nil
}
// getDirMigrationOpts checks if the feature flag is set and if the snap data
// has been migrated, first checking the SnapSetup (if not nil) and then
// the SnapState. The state must be locked by the caller.
var getDirMigrationOpts = func(st *state.State, snapst *SnapState, snapsup *SnapSetup) (*dirMigrationOptions, error) {
tr := config.NewTransaction(st)
hiddenDir, err := features.Flag(tr, features.HiddenSnapDataHomeDir)
if err != nil {
return nil, fmt.Errorf("cannot read feature flag %q: %w", features.HiddenSnapDataHomeDir, err)
}
opts := &dirMigrationOptions{UseHidden: hiddenDir}
if snapst != nil {
opts.MigratedToHidden = snapst.MigratedHidden
opts.MigratedToExposedHome = snapst.MigratedToExposedHome
}
// it was migrated during this change (might not be in the state yet)
if snapsup != nil {
switch {
case snapsup.MigratedHidden && snapsup.UndidHiddenMigration:
// should never happen except for programmer error
return nil, fmt.Errorf("internal error: ~/.snap migration was done and reversed in same change without updating migration flags")
case snapsup.MigratedHidden:
opts.MigratedToHidden = true
case snapsup.UndidHiddenMigration:
opts.MigratedToHidden = false
}
switch {
case (snapsup.EnableExposedHome || snapsup.MigratedToExposedHome) &&
(snapsup.DisableExposedHome || snapsup.RemovedExposedHome):
// should never happen except for programmer error
return nil, fmt.Errorf("internal error: ~/Snap migration was done and reversed in same change without updating migration flags")
case snapsup.MigratedToExposedHome:
fallthrough
case snapsup.EnableExposedHome:
opts.MigratedToExposedHome = true
case snapsup.RemovedExposedHome:
fallthrough
case snapsup.DisableExposedHome:
opts.MigratedToExposedHome = false
}
}
return opts, nil
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/mysnapcore/mysnapd.git
git@gitee.com:mysnapcore/mysnapd.git
mysnapcore
mysnapd
mysnapd
v0.1.0

Search