File: sequence.go

package info (click to toggle)
snapd 2.72-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 80,412 kB
  • sloc: sh: 16,506; ansic: 16,211; python: 11,213; makefile: 1,919; exp: 190; awk: 58; xml: 22
file content (295 lines) | stat: -rw-r--r-- 9,458 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2016-2024 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 sequence contains types representing a sequence of snap
// revisions (with components) that describe current and past states
// of the snap in the system.
package sequence

import (
	"encoding/json"
	"errors"

	"github.com/snapcore/snapd/snap"
	"github.com/snapcore/snapd/snap/naming"
)

// ComponentState contains information about an installed component.
type ComponentState struct {
	SideInfo *snap.ComponentSideInfo `json:"side-info"`
	CompType snap.ComponentType      `json:"type"`
}

// NewComponentState creates a ComponentState from components side information and type.
func NewComponentState(si *snap.ComponentSideInfo, tp snap.ComponentType) *ComponentState {
	return &ComponentState{SideInfo: si, CompType: tp}
}

// RevisionSideState contains the side information for a snap and related components
// installed in the system.
type RevisionSideState struct {
	Snap       *snap.SideInfo
	Components []*ComponentState
}

// revisionSideInfoMarshal is an ancillary structure used exclusively to
// help marshaling of RevisionSideInfo.
type revisionSideInfoMarshal struct {
	// SideInfo is included for compatibility with older snapd state files
	*snap.SideInfo
	Components []*ComponentState `json:"components,omitempty"`
}

// MarshalJSON implements the json.Marshaler interface
func (bsi RevisionSideState) MarshalJSON() ([]byte, error) {
	return json.Marshal(&revisionSideInfoMarshal{bsi.Snap, bsi.Components})
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (bsi *RevisionSideState) UnmarshalJSON(in []byte) error {
	var aux revisionSideInfoMarshal
	if err := json.Unmarshal(in, &aux); err != nil {
		return err
	}
	bsi.Snap = aux.SideInfo
	bsi.Components = aux.Components
	return nil
}

// FindComponent returns the ComponentState if cref is found in the sequence point.
func (rss *RevisionSideState) FindComponent(cref naming.ComponentRef) *ComponentState {
	for _, csi := range rss.Components {
		if csi.SideInfo.Component == cref {
			return csi
		}
	}
	return nil
}

// NewRevisionSideState creates a RevisionSideInfo from snap and
// related components side information.
func NewRevisionSideState(snapSideInfo *snap.SideInfo, compSideInfo []*ComponentState) *RevisionSideState {
	return &RevisionSideState{Snap: snapSideInfo, Components: compSideInfo}
}

// SnapSequence is a container for a slice containing revisions of
// snaps plus related components.
// TODO add methods to access Revisions (length, copy, append) and
// use them in handlers.go and snapstate.go.
type SnapSequence struct {
	// Revisions contains information for a snap revision and
	// components SideInfo.
	Revisions []*RevisionSideState
}

// MarshalJSON implements the json.Marshaler interface. We override the default
// so serialization of the SnapState.Sequence field is compatible to what was
// produced when it was defined as a []*snap.SideInfo. This is also the reason
// to have SnapSequence.UnmarshalJSON and MarshalJSON/UnmarshalJSON for
// RevisionSideState.
func (snapSeq SnapSequence) MarshalJSON() ([]byte, error) {
	return json.Marshal(snapSeq.Revisions)
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (snapSeq *SnapSequence) UnmarshalJSON(in []byte) error {
	aux := []*RevisionSideState{}
	if err := json.Unmarshal(in, &aux); err != nil {
		return err
	}
	snapSeq.Revisions = aux
	return nil
}

// SideInfos returns a slice with all the SideInfos for the snap sequence.
func (snapSeq SnapSequence) SideInfos() []*snap.SideInfo {
	sis := make([]*snap.SideInfo, len(snapSeq.Revisions))
	for i, rev := range snapSeq.Revisions {
		sis[i] = rev.Snap
	}
	return sis
}

// LastIndex returns the last index of the given revision in snapSeq,
// or -1 if the revision was not found.
func (snapSeq *SnapSequence) LastIndex(revision snap.Revision) int {
	for i := len(snapSeq.Revisions) - 1; i >= 0; i-- {
		if snapSeq.Revisions[i].Snap.Revision == revision {
			return i
		}
	}
	return -1
}

var ErrSnapRevNotInSequence = errors.New("snap is not in the sequence")

// AddComponentForRevision adds a component to the last instance of snapRev in
// the sequence.
func (snapSeq *SnapSequence) AddComponentForRevision(snapRev snap.Revision, cs *ComponentState) error {
	snapIdx := snapSeq.LastIndex(snapRev)
	if snapIdx == -1 {
		return ErrSnapRevNotInSequence
	}
	revSt := snapSeq.Revisions[snapIdx]

	if currentCompSt := revSt.FindComponent(cs.SideInfo.Component); currentCompSt != nil {
		// Component already present, replace revision
		*currentCompSt = *cs
		return nil
	}

	// Append new component to components of the current snap
	revSt.Components = append(revSt.Components, cs)
	return nil
}

// RemoveComponentForRevision removes the cref component for the last instance
// of snapRev in the sequence and returns a pointer to it, which might be nil
// if not found.
func (snapSeq *SnapSequence) RemoveComponentForRevision(snapRev snap.Revision, cref naming.ComponentRef) (unlinkedComp *ComponentState) {
	snapIdx := snapSeq.LastIndex(snapRev)
	if snapIdx == -1 {
		return nil
	}

	revSt := snapSeq.Revisions[snapIdx]
	var leftComp []*ComponentState
	for _, csi := range revSt.Components {
		if csi.SideInfo.Component == cref {
			unlinkedComp = csi
			continue
		}
		leftComp = append(leftComp, csi)
	}
	revSt.Components = leftComp
	// might be nil
	return unlinkedComp
}

// ComponentStateForRev returns cref's component side info for the revision
// (sequence point) indicated by revIdx if there is one.
func (snapSeq *SnapSequence) ComponentStateForRev(revIdx int, cref naming.ComponentRef) *ComponentState {
	for _, comp := range snapSeq.Revisions[revIdx].Components {
		if comp.SideInfo.Component == cref {
			return comp
		}
	}
	// component not found
	return nil
}

// IsComponentRevPresent tells us if a given component revision is
// present in the system for this snap.
func (snapSeq *SnapSequence) IsComponentRevPresent(compSi *snap.ComponentSideInfo) bool {
	for _, rev := range snapSeq.Revisions {
		for _, cs := range rev.Components {
			if cs.SideInfo.Equal(compSi) {
				return true
			}
		}
	}
	return false
}

func (snapSeq *SnapSequence) ComponentsForRevision(rev snap.Revision) []*ComponentState {
	for _, rss := range snapSeq.Revisions {
		if rss.Snap.Revision == rev {
			return rss.Components
		}
	}
	return nil
}

func (snapSeq *SnapSequence) ComponentsWithTypeForRev(rev snap.Revision, compType snap.ComponentType) []*snap.ComponentSideInfo {
	comps := snapSeq.ComponentsForRevision(rev)
	kmodComps := make([]*snap.ComponentSideInfo, 0, len(comps))
	for _, comp := range comps {
		if comp.CompType != snap.KernelModulesComponent {
			continue
		}
		kmodComps = append(kmodComps, comp.SideInfo)
	}
	return kmodComps
}

// IsComponentRevInRefSeqPtInAnyOtherSeqPt tells us if the component cref in
// the sequence point defined by refIdx is used in another sequence point too.
func (snapSeq *SnapSequence) IsComponentRevInRefSeqPtInAnyOtherSeqPt(cref naming.ComponentRef, refIdx int) bool {
	// Find component in reference sequence point
	refSeqPt := snapSeq.Revisions[refIdx]
	refSeqPtComp := refSeqPt.FindComponent(cref)
	if refSeqPtComp == nil {
		return false
	}

	// Find if the reference component revision is used elsewhere
	for idx, seqPt := range snapSeq.Revisions {
		if idx == refIdx {
			continue
		}
		compInSeqPt := seqPt.FindComponent(cref)
		if compInSeqPt == nil {
			continue
		}
		if compInSeqPt.SideInfo.Revision == refSeqPtComp.SideInfo.Revision {
			return true
		}
	}

	return false
}

// MinimumLocalRevision returns the the smallest local revision for the
// sequence. Local revisions start at -1 and are counted down. 0 will be
// returned if no local revision for the snap is found.
func (snapSeq *SnapSequence) MinimumLocalRevision() snap.Revision {
	var local snap.Revision
	for _, rev := range snapSeq.Revisions {
		if rev.Snap.Revision.N < local.N {
			local = rev.Snap.Revision
		}
	}
	return local
}

// MinimumLocalComponentRevision returns the smallest local revision for the
// compName component in the sequence. Local revisions start at -1 and are
// counted down. 0 will be returned if no local revision for the component is
// found.
func (snapSeq *SnapSequence) MinimumLocalComponentRevision(compName string) snap.Revision {
	var local snap.Revision
	for _, revSt := range snapSeq.Revisions {
		for _, compSt := range revSt.Components {
			if compSt.SideInfo.Component.ComponentName != compName {
				continue
			}
			if compSt.SideInfo.Revision.N >= local.N {
				continue
			}
			local = compSt.SideInfo.Revision
		}
	}
	return local
}

// HasComponents returns true if the revision at the given index has
// any components installed with it.
func (snapSeq *SnapSequence) HasComponents(revIdx int) bool {
	return len(snapSeq.Revisions[revIdx].Components) > 0
}