File: rootfs_image.go

package info (click to toggle)
golang-github-mendersoftware-mender-artifact 3.9.0%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,212 kB
  • sloc: sh: 128; makefile: 128
file content (415 lines) | stat: -rw-r--r-- 10,489 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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
// Copyright 2022 Northern.tech AS
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

package handlers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"path/filepath"

	"github.com/pkg/errors"

	"github.com/mendersoftware/mender-artifact/artifact"
)

// Rootfs handles updates of type 'rootfs-image'.
type Rootfs struct {
	version           int
	update            *DataFile
	regularHeaderRead bool

	typeInfoV3 *artifact.TypeInfoV3
	metaData   map[string]interface{}

	// If this is augmented instance: The original instance.
	original ArtifactUpdate

	installerBase
}

func NewRootfsV2(updFile string) *Rootfs {
	uf := &DataFile{
		Name: updFile,
	}
	return &Rootfs{
		update:  uf,
		version: 2,
	}
}

func NewRootfsV3(updFile string) *Rootfs {
	var uf *DataFile
	if updFile != "" {
		uf = &DataFile{
			Name: updFile,
		}
	} else {
		uf = nil
	}
	return &Rootfs{
		update:  uf,
		version: 3,
	}
}

func NewAugmentedRootfs(orig ArtifactUpdate, updFile string) *Rootfs {
	rootfs := NewRootfsV3(updFile)
	rootfs.original = orig
	return rootfs
}

// NewRootfsInstaller is used by the artifact reader to read and install
// rootfs-image update type.
func NewRootfsInstaller() *Rootfs {
	return &Rootfs{}
}

// Copy creates a new instance of Rootfs handler from the existing one.
func (rp *Rootfs) NewInstance() Installer {
	return &Rootfs{
		version:           rp.version,
		installerBase:     rp.installerBase,
		regularHeaderRead: rp.regularHeaderRead,
	}
}

func (rp *Rootfs) NewAugmentedInstance(orig ArtifactUpdate) (Installer, error) {
	if orig.GetVersion() < 3 {
		return nil, errors.New(
			"Rootfs Payload type version < 3 does not support augmented sections.",
		)
	}
	if *orig.GetUpdateType() != "rootfs-image" {
		return nil, fmt.Errorf("rootfs-image type cannot be an augmented instance of %s type.",
			*orig.GetUpdateType())
	}

	newRootfs := rp.NewInstance().(*Rootfs)
	newRootfs.original = orig
	return newRootfs, nil
}

func (rp *Rootfs) GetVersion() int {
	return rp.version
}

func (rp *Rootfs) ReadHeader(r io.Reader, path string, version int, augmented bool) error {
	rp.version = version
	switch {
	case filepath.Base(path) == "files":
		if version >= 3 {
			return errors.New("\"files\" entry found in version 3 artifact")
		}
		files, err := parseFiles(r)
		if err != nil {
			return err
		} else if len(files.FileList) != 1 {
			return errors.New("Rootfs image does not contain exactly one file")
		}
		err = rp.SetUpdateFiles([]*DataFile{{Name: files.FileList[0]}})
		if err != nil {
			return err
		}
	case filepath.Base(path) == "type-info":
		if rp.version < 3 {
			// This was ignored in pre-v3 versions, so keep ignoring it.
			break
		}
		dec := json.NewDecoder(r)
		err := dec.Decode(&rp.typeInfoV3)
		if err != nil {
			return errors.Wrap(err, "error reading type-info")
		}

	case filepath.Base(path) == "meta-data":
		dec := json.NewDecoder(r)
		var data interface{}
		err := dec.Decode(&data)
		if err == io.EOF {
			break
		} else if err != nil {
			return errors.Wrap(err, "error reading meta-data")
		}
		jsonObj, ok := data.(map[string]interface{})
		if !ok {
			return errors.New("Top level object in meta-data must be a JSON object")
		}
		if augmented {
			err = rp.setUpdateAugmentMetaData(jsonObj)
		} else {
			err = rp.setUpdateOriginalMetaData(jsonObj)
		}
		if err != nil {
			return err
		}
	case match(artifact.HeaderDirectory+"/*/signatures/*", path),
		match(artifact.HeaderDirectory+"/*/scripts/*/*", path):
		if augmented {
			return errors.New("signatures and scripts not allowed in augmented header")
		}
		// TODO: implement when needed
	case match(artifact.HeaderDirectory+"/*/checksums/*", path):
		buf := bytes.NewBuffer(nil)
		if _, err := io.Copy(buf, r); err != nil {
			return errors.Wrap(err, "update: error reading checksum")
		}
		rp.update.Checksum = buf.Bytes()
	default:
		return errors.Errorf("update: unsupported file: %v", path)
	}
	return nil
}

func (rfs *Rootfs) GetUpdateFiles() [](*DataFile) {
	if rfs.original != nil {
		return rfs.original.GetUpdateFiles()
	} else if rfs.update != nil {
		return [](*DataFile){rfs.update}
	} else {
		return [](*DataFile){}
	}
}

func (rfs *Rootfs) SetUpdateFiles(files [](*DataFile)) error {
	if rfs.original != nil {
		if len(files) > 0 && len(rfs.GetUpdateAugmentFiles()) > 0 {
			return errors.New("Rootfs: Cannot handle both augmented and non-augmented update file")
		}
		return rfs.original.SetUpdateFiles(files)
	}

	if len(files) == 0 {
		rfs.update = nil
		return nil
	} else if len(files) != 1 {
		return errors.New("Rootfs: Must provide exactly one update file")
	}

	rfs.update = files[0]
	return nil
}

func (rfs *Rootfs) GetUpdateAugmentFiles() [](*DataFile) {
	if rfs.original != nil && rfs.update != nil {
		return [](*DataFile){rfs.update}
	} else {
		return [](*DataFile){}
	}
}

func (rfs *Rootfs) SetUpdateAugmentFiles(files [](*DataFile)) error {
	if rfs.original == nil {
		if len(files) > 0 {
			return errors.New("Rootfs: Cannot set augmented data file on non-augmented instance.")
		} else {
			return nil
		}
	}

	if len(files) == 0 {
		rfs.update = nil
		return nil
	} else if len(files) != 1 {
		return errors.New("Rootfs: Must provide exactly one update file")
	}

	if len(rfs.GetUpdateFiles()) > 0 {
		return errors.New("Rootfs: Cannot handle both augmented and non-augmented update file")
	}

	rfs.update = files[0]
	return nil
}

func (rfs *Rootfs) GetUpdateAllFiles() [](*DataFile) {
	allFiles := make([]*DataFile, 0, len(rfs.GetUpdateAugmentFiles())+len(rfs.GetUpdateFiles()))
	allFiles = append(allFiles, rfs.GetUpdateFiles()...)
	allFiles = append(allFiles, rfs.GetUpdateAugmentFiles()...)
	return allFiles
}

func (rfs *Rootfs) GetUpdateType() *string {
	updateType := "rootfs-image"
	return &updateType
}

func (rfs *Rootfs) GetUpdateOriginalType() *string {
	originalType := ""
	return &originalType
}

func (rfs *Rootfs) GetUpdateDepends() (artifact.TypeInfoDepends, error) {
	return rfs.GetUpdateOriginalDepends(), nil
}

func (rfs *Rootfs) GetUpdateProvides() (artifact.TypeInfoProvides, error) {
	return rfs.GetUpdateOriginalProvides(), nil
}

func (rfs *Rootfs) GetUpdateMetaData() (map[string]interface{}, error) {
	// No metadata for rootfs update type.
	return rfs.GetUpdateOriginalMetaData(), nil
}

func (rfs *Rootfs) GetUpdateClearsProvides() []string {
	if rfs.typeInfoV3 == nil {
		return nil
	}
	if rfs.typeInfoV3.ClearsArtifactProvides == nil && rfs.original != nil {
		return rfs.original.GetUpdateOriginalClearsProvides()
	}
	return rfs.typeInfoV3.ClearsArtifactProvides
}

func (rfs *Rootfs) setUpdateOriginalMetaData(jsonObj map[string]interface{}) error {
	if rfs.original != nil {
		return errors.New("setUpdateOriginalMetaData() called on non-original instance.")
	} else {
		rfs.metaData = jsonObj
	}
	return nil
}

func (rfs *Rootfs) setUpdateAugmentMetaData(jsonObj map[string]interface{}) error {
	if rfs.original == nil {
		return errors.New("Called setUpdateAugmentMetaData() on non-augment instance")
	}
	rfs.metaData = jsonObj
	return nil
}

func (rfs *Rootfs) GetUpdateOriginalDepends() artifact.TypeInfoDepends {
	if rfs.typeInfoV3 == nil {
		return nil
	}
	return rfs.typeInfoV3.ArtifactDepends
}

func (rfs *Rootfs) GetUpdateOriginalProvides() artifact.TypeInfoProvides {
	if rfs.typeInfoV3 == nil {
		return nil
	}
	return rfs.typeInfoV3.ArtifactProvides
}

func (rfs *Rootfs) GetUpdateOriginalMetaData() map[string]interface{} {
	if rfs.original != nil {
		return rfs.original.GetUpdateOriginalMetaData()
	} else {
		return rfs.metaData
	}
}

func (rfs *Rootfs) GetUpdateOriginalClearsProvides() []string {
	if rfs.original == nil {
		if rfs.typeInfoV3 == nil {
			return nil
		}
		return rfs.typeInfoV3.ClearsArtifactProvides
	} else {
		return rfs.original.GetUpdateOriginalClearsProvides()
	}
}

func (rfs *Rootfs) GetUpdateAugmentDepends() artifact.TypeInfoDepends {
	return nil
}

func (rfs *Rootfs) GetUpdateAugmentProvides() artifact.TypeInfoProvides {
	return nil
}

func (rfs *Rootfs) GetUpdateAugmentMetaData() map[string]interface{} {
	if rfs.original == nil {
		return nil
	} else {
		return rfs.metaData
	}
}

func (rfs *Rootfs) GetUpdateAugmentClearsProvides() []string {
	if rfs.original == nil {
		return nil
	} else {
		if rfs.typeInfoV3 == nil {
			return nil
		}
		return rfs.typeInfoV3.ClearsArtifactProvides
	}
}

func (rfs *Rootfs) ComposeHeader(args *ComposeHeaderArgs) error {

	path := artifact.UpdateHeaderPath(args.No)

	switch rfs.version {
	case 1, 2:
		// first store files
		if err := writeFiles(args.TarWriter, []string{filepath.Base(rfs.update.Name)},
			path); err != nil {
			return err
		}

		if err := writeTypeInfo(args.TarWriter, "rootfs-image", path); err != nil {
			return err
		}

	case 3:
		if args.Augmented {
			// Remove the typeinfov3.provides, as this should not be written in the
			// augmented-header.
			if args.TypeInfoV3 != nil {
				args.TypeInfoV3.ArtifactProvides = nil
			}
		}

		if err := writeTypeInfoV3(&WriteInfoArgs{
			tarWriter:  args.TarWriter,
			dir:        path,
			typeinfov3: args.TypeInfoV3,
		}); err != nil {
			return errors.Wrap(err, "ComposeHeader")
		}
	default:
		return fmt.Errorf("ComposeHeader: rootfs-version %d not supported", rfs.version)

	}

	// store empty meta-data
	// the file needs to be a part of artifact even if this one is empty
	if len(args.MetaData) != 0 {
		return errors.New(
			"MetaData not empty in Rootfs.ComposeHeader. This is a bug in the application.",
		)
	}
	sw := artifact.NewTarWriterStream(args.TarWriter)
	if err := sw.Write(nil, filepath.Join(path, "meta-data")); err != nil {
		return errors.Wrap(err, "Payload: can not store meta-data")
	}

	return nil
}

func (rfs *Rootfs) GetUpdateOriginalTypeInfoWriter() io.Writer {
	// We don't use the type-info information with rootfs payloads.
	return nil
}

func (rfs *Rootfs) GetUpdateAugmentTypeInfoWriter() io.Writer {
	// We don't use the type-info information with rootfs payloads.
	return nil
}