• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-artifact / 1886872200

24 Jun 2025 02:27PM UTC coverage: 76.671% (+0.07%) from 76.598%
1886872200

Pull #717

gitlab-ci

elkoniu
chore: Look for binaries in brew specific path

When brew installs binary it will create symlink directory
in location like `/usr/local/opt/<binary>/bin`. This location
is not part of the system PATH by default.

When mender-artifact evaluates commands location it need to check
this brew specific directories apart of default ones.

ticket: MEN-8471
changelog: Extend tools search locations with brew specific ones
Signed-off-by: Paweł Poławski <pawel.polawski@northern.tech>
Pull Request #717: chore: Look for binaries in brew specific path

11 of 15 new or added lines in 1 file covered. (73.33%)

249 existing lines in 2 files now uncovered.

5932 of 7737 relevant lines covered (76.67%)

132.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.24
/cli/write.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package cli
16

17
import (
18
        "bufio"
19
        "context"
20
        "encoding/json"
21
        "fmt"
22
        "os"
23
        "os/exec"
24
        "os/signal"
25
        "regexp"
26
        "strings"
27
        "syscall"
28
        "time"
29

30
        "io"
31

32
        "github.com/pkg/errors"
33
        "github.com/urfave/cli"
34

35
        "github.com/mendersoftware/mender-artifact/artifact"
36
        "github.com/mendersoftware/mender-artifact/artifact/stage"
37
        "github.com/mendersoftware/mender-artifact/awriter"
38
        "github.com/mendersoftware/mender-artifact/cli/util"
39
        "github.com/mendersoftware/mender-artifact/handlers"
40
        "github.com/mendersoftware/mender-artifact/utils"
41
)
42

43
func writeRootfsImageChecksum(rootfsFilename string,
44
        typeInfo *artifact.TypeInfoV3, legacy bool) (err error) {
187✔
45
        chk := artifact.NewWriterChecksum(io.Discard)
187✔
46
        payload, err := os.Open(rootfsFilename)
187✔
47
        if err != nil {
189✔
48
                return cli.NewExitError(
2✔
49
                        fmt.Sprintf("Failed to open the payload file: %q", rootfsFilename),
2✔
50
                        1,
2✔
51
                )
2✔
52
        }
2✔
53
        if _, err = io.Copy(chk, payload); err != nil {
185✔
UNCOV
54
                return cli.NewExitError("Failed to generate the checksum for the payload", 1)
×
UNCOV
55
        }
×
56
        checksum := string(chk.Checksum())
185✔
57

185✔
58
        checksumKey := "rootfs-image.checksum"
185✔
59
        if legacy {
187✔
60
                checksumKey = "rootfs_image_checksum"
2✔
61
        }
2✔
62

63
        Log.Debugf("Adding the `%s`: %q to Artifact provides", checksumKey, checksum)
185✔
64
        if typeInfo == nil {
185✔
UNCOV
65
                return errors.New("Type-info is unitialized")
×
66
        }
×
67
        if typeInfo.ArtifactProvides == nil {
187✔
68
                t, err := artifact.NewTypeInfoProvides(map[string]string{checksumKey: checksum})
2✔
69
                if err != nil {
2✔
UNCOV
70
                        return errors.Wrapf(err, "%s", "Failed to write the "+"`"+checksumKey+"` provides")
×
UNCOV
71
                }
×
72
                typeInfo.ArtifactProvides = t
2✔
73
        } else {
183✔
74
                typeInfo.ArtifactProvides[checksumKey] = checksum
183✔
75
        }
183✔
76
        return nil
185✔
77
}
78

79
func validateInput(c *cli.Context) error {
82✔
80
        // Version 2 and 3 validation.
82✔
81
        fileMissing := false
82✔
82
        if c.Command.Name != "bootstrap-artifact" {
160✔
83
                if len(c.String("file")) == 0 {
78✔
UNCOV
84
                        fileMissing = true
×
85
                }
×
86
        }
87
        if len(c.StringSlice("device-type")) == 0 ||
82✔
88
                len(c.String("artifact-name")) == 0 || fileMissing {
82✔
89
                return cli.NewExitError(
×
UNCOV
90
                        "must provide `device-type`, `artifact-name` and `file`",
×
UNCOV
91
                        errArtifactInvalidParameters,
×
UNCOV
92
                )
×
UNCOV
93
        }
×
94
        if len(strings.Fields(c.String("artifact-name"))) > 1 {
84✔
95
                // check for whitespace in artifact-name
2✔
96
                return cli.NewExitError(
2✔
97
                        "whitespace is not allowed in the artifact-name",
2✔
98
                        errArtifactInvalidParameters,
2✔
99
                )
2✔
100
        }
2✔
101
        return nil
80✔
102
}
103

104
func createRootfsFromSSH(c *cli.Context) (string, error) {
×
105
        _, err := utils.GetBinaryPath("blkid")
×
106
        if err != nil {
×
107
                Log.Warnf("Skipping running fsck on the Artifact: %v", errBlkidNotFound)
×
108
        }
×
UNCOV
109
        rootfsFilename, err := getDeviceSnapshot(c)
×
UNCOV
110
        if err != nil {
×
111
                return rootfsFilename, cli.NewExitError("SSH error: "+err.Error(), 1)
×
112
        }
×
113

114
        // check for blkid and get filesystem type
115
        fstype, err := imgFilesystemType(rootfsFilename)
×
116
        if err != nil {
×
117
                if err == errBlkidNotFound {
×
118
                        return rootfsFilename, nil
×
119
                }
×
UNCOV
120
                return rootfsFilename, cli.NewExitError(
×
UNCOV
121
                        "imgFilesystemType error: "+err.Error(),
×
UNCOV
122
                        errArtifactCreate,
×
123
                )
×
124
        }
125

126
        // run fsck
127
        switch fstype {
×
128
        case fat:
×
129
                err = runFsck(rootfsFilename, "vfat")
×
UNCOV
130
        case ext:
×
UNCOV
131
                err = runFsck(rootfsFilename, "ext4")
×
132
        case unsupported:
×
133
                err = errors.New("createRootfsFromSSH: unsupported filesystem")
×
134

135
        }
136
        if err != nil {
×
UNCOV
137
                return rootfsFilename, cli.NewExitError("runFsck error: "+err.Error(), errArtifactCreate)
×
UNCOV
138
        }
×
139

UNCOV
140
        return rootfsFilename, nil
×
141
}
142

143
func makeEmptyUpdates(ctx *cli.Context) (*awriter.Updates, error) {
4✔
144
        handler := handlers.NewBootstrapArtifact()
4✔
145

4✔
146
        dataFiles := make([](*handlers.DataFile), 0)
4✔
147
        if err := handler.SetUpdateFiles(dataFiles); err != nil {
4✔
148
                return nil, cli.NewExitError(
×
UNCOV
149
                        err,
×
UNCOV
150
                        1,
×
UNCOV
151
                )
×
UNCOV
152
        }
×
153

154
        upd := &awriter.Updates{
4✔
155
                Updates: []handlers.Composer{handler},
4✔
156
        }
4✔
157
        return upd, nil
4✔
158
}
159

160
func writeBootstrapArtifact(c *cli.Context) error {
4✔
161
        comp, err := artifact.NewCompressorFromId(c.GlobalString("compression"))
4✔
162
        if err != nil {
4✔
163
                return cli.NewExitError(
×
UNCOV
164
                        "compressor '"+c.GlobalString("compression")+"' is not supported: "+err.Error(),
×
UNCOV
165
                        1,
×
166
                )
×
167
        }
×
168

169
        if err := validateInput(c); err != nil {
4✔
UNCOV
170
                Log.Error(err.Error())
×
UNCOV
171
                return err
×
UNCOV
172
        }
×
173

174
        // set the default name
175
        name := "artifact.mender"
4✔
176
        if len(c.String("output-path")) > 0 {
8✔
177
                name = c.String("output-path")
4✔
178
        }
4✔
179
        version := c.Int("version")
4✔
180

4✔
181
        Log.Debugf("creating bootstrap artifact [%s], version: %d", name, version)
4✔
182

4✔
183
        var w io.Writer
4✔
184
        if name == "-" {
4✔
185
                w = os.Stdout
×
186
        } else {
4✔
187
                f, err := os.Create(name)
4✔
188
                if err != nil {
4✔
189
                        return cli.NewExitError(
×
UNCOV
190
                                "can not create bootstrap artifact file: "+err.Error(),
×
UNCOV
191
                                errArtifactCreate,
×
UNCOV
192
                        )
×
UNCOV
193
                }
×
194
                defer f.Close()
4✔
195
                w = f
4✔
196
        }
197

198
        aw, err := artifactWriter(c, comp, w, version)
4✔
199
        if err != nil {
4✔
UNCOV
200
                return cli.NewExitError(err.Error(), 1)
×
UNCOV
201
        }
×
202

203
        depends := artifact.ArtifactDepends{
4✔
204
                ArtifactName:      c.StringSlice("artifact-name-depends"),
4✔
205
                CompatibleDevices: c.StringSlice("device-type"),
4✔
206
                ArtifactGroup:     c.StringSlice("depends-groups"),
4✔
207
        }
4✔
208

4✔
209
        provides := artifact.ArtifactProvides{
4✔
210
                ArtifactName:  c.String("artifact-name"),
4✔
211
                ArtifactGroup: c.String("provides-group"),
4✔
212
        }
4✔
213

4✔
214
        upd, err := makeEmptyUpdates(c)
4✔
215
        if err != nil {
4✔
UNCOV
216
                return err
×
217
        }
×
218

219
        typeInfoV3, _, err := makeTypeInfo(c)
4✔
220
        if err != nil {
4✔
UNCOV
221
                return err
×
UNCOV
222
        }
×
223

224
        if !c.Bool("no-progress") {
8✔
225
                ctx, cancel := context.WithCancel(context.Background())
4✔
226
                go reportProgress(ctx, aw.State)
4✔
227
                defer cancel()
4✔
228
                aw.ProgressWriter = utils.NewProgressWriter()
4✔
229
        }
4✔
230

231
        err = aw.WriteArtifact(
4✔
232
                &awriter.WriteArtifactArgs{
4✔
233
                        Format:     "mender",
4✔
234
                        Version:    version,
4✔
235
                        Devices:    c.StringSlice("device-type"),
4✔
236
                        Name:       c.String("artifact-name"),
4✔
237
                        Updates:    upd,
4✔
238
                        Scripts:    nil,
4✔
239
                        Depends:    &depends,
4✔
240
                        Provides:   &provides,
4✔
241
                        TypeInfoV3: typeInfoV3,
4✔
242
                        Bootstrap:  true,
4✔
243
                })
4✔
244
        if err != nil {
4✔
UNCOV
245
                return cli.NewExitError(err.Error(), 1)
×
UNCOV
246
        }
×
247
        return nil
4✔
248
}
249

250
func writeRootfs(c *cli.Context) error {
78✔
251
        comp, err := artifact.NewCompressorFromId(c.GlobalString("compression"))
78✔
252
        if err != nil {
78✔
253
                return cli.NewExitError(
×
UNCOV
254
                        "compressor '"+c.GlobalString("compression")+"' is not supported: "+err.Error(),
×
UNCOV
255
                        1,
×
UNCOV
256
                )
×
UNCOV
257
        }
×
258

259
        if err := validateInput(c); err != nil {
80✔
260
                Log.Error(err.Error())
2✔
261
                return err
2✔
262
        }
2✔
263

264
        // set the default name
265
        name := "artifact.mender"
76✔
266
        if len(c.String("output-path")) > 0 {
152✔
267
                name = c.String("output-path")
76✔
268
        }
76✔
269
        version := c.Int("version")
76✔
270
        var showprovides map[string]string
76✔
271

76✔
272
        Log.Debugf("creating artifact [%s], version: %d", name, version)
76✔
273
        rootfsFilename := c.String("file")
76✔
274
        if strings.HasPrefix(rootfsFilename, "ssh://") {
76✔
275
                rootfsFilename, err = createRootfsFromSSH(c)
×
276
                defer os.Remove(rootfsFilename)
×
277
                if err != nil {
×
278
                        return cli.NewExitError(err.Error(), errArtifactCreate)
×
279
                }
×
UNCOV
280
                showprovides, err = showProvides(c)
×
UNCOV
281
                if err != nil {
×
UNCOV
282
                        return cli.NewExitError(err.Error(), errArtifactCreate)
×
UNCOV
283
                }
×
284
        }
285

286
        var h handlers.Composer
76✔
287
        switch version {
76✔
288
        case 2:
2✔
289
                h = handlers.NewRootfsV2(rootfsFilename)
2✔
290
        case 3:
68✔
291
                h = handlers.NewRootfsV3(rootfsFilename)
68✔
292
        default:
6✔
293
                return cli.NewExitError(
6✔
294
                        fmt.Sprintf("Artifact version %d is not supported", version),
6✔
295
                        errArtifactUnsupportedVersion,
6✔
296
                )
6✔
297
        }
298

299
        upd := &awriter.Updates{
70✔
300
                Updates: []handlers.Composer{h},
70✔
301
        }
70✔
302

70✔
303
        var w io.Writer
70✔
304
        if name == "-" {
70✔
305
                w = os.Stdout
×
306
        } else {
70✔
307
                f, err := os.Create(name)
70✔
308
                if err != nil {
70✔
309
                        return cli.NewExitError(
×
UNCOV
310
                                "can not create artifact file: "+err.Error(),
×
UNCOV
311
                                errArtifactCreate,
×
UNCOV
312
                        )
×
UNCOV
313
                }
×
314
                defer f.Close()
70✔
315
                w = f
70✔
316
        }
317

318
        aw, err := artifactWriter(c, comp, w, version)
70✔
319
        if err != nil {
72✔
320
                return cli.NewExitError(err.Error(), 1)
2✔
321
        }
2✔
322

323
        scr, err := scripts(c.StringSlice("script"))
68✔
324
        if err != nil {
70✔
325
                return cli.NewExitError(err.Error(), 1)
2✔
326
        }
2✔
327

328
        depends := artifact.ArtifactDepends{
66✔
329
                ArtifactName:      c.StringSlice("artifact-name-depends"),
66✔
330
                CompatibleDevices: c.StringSlice("device-type"),
66✔
331
                ArtifactGroup:     c.StringSlice("depends-groups"),
66✔
332
        }
66✔
333

66✔
334
        provides := artifact.ArtifactProvides{
66✔
335
                ArtifactName:  c.String("artifact-name"),
66✔
336
                ArtifactGroup: c.String("provides-group"),
66✔
337
        }
66✔
338

66✔
339
        typeInfoV3, _, err := makeTypeInfo(c)
66✔
340
        if err != nil {
66✔
341
                return err
×
342
        }
×
343
        for k, v := range showprovides {
66✔
UNCOV
344
                _, exist := typeInfoV3.ArtifactProvides[k]
×
UNCOV
345
                if !exist {
×
UNCOV
346
                        typeInfoV3.ArtifactProvides[k] = v
×
UNCOV
347
                }
×
348
        }
349

350
        if !c.Bool("no-checksum-provide") {
128✔
351
                legacy := c.Bool("legacy-rootfs-image-checksum")
62✔
352
                if err = writeRootfsImageChecksum(rootfsFilename, typeInfoV3, legacy); err != nil {
62✔
353
                        return cli.NewExitError(
×
UNCOV
354
                                errors.Wrap(err, "Failed to write the `rootfs-image.checksum` to the artifact"),
×
UNCOV
355
                                1,
×
UNCOV
356
                        )
×
UNCOV
357
                }
×
358
        }
359
        if !c.Bool("no-progress") {
132✔
360
                ctx, cancel := context.WithCancel(context.Background())
66✔
361
                go reportProgress(ctx, aw.State)
66✔
362
                defer cancel()
66✔
363
                aw.ProgressWriter = utils.NewProgressWriter()
66✔
364
        }
66✔
365

366
        err = aw.WriteArtifact(
66✔
367
                &awriter.WriteArtifactArgs{
66✔
368
                        Format:     "mender",
66✔
369
                        Version:    version,
66✔
370
                        Devices:    c.StringSlice("device-type"),
66✔
371
                        Name:       c.String("artifact-name"),
66✔
372
                        Updates:    upd,
66✔
373
                        Scripts:    scr,
66✔
374
                        Depends:    &depends,
66✔
375
                        Provides:   &provides,
66✔
376
                        TypeInfoV3: typeInfoV3,
66✔
377
                })
66✔
378
        if err != nil {
66✔
UNCOV
379
                return cli.NewExitError(err.Error(), 1)
×
UNCOV
380
        }
×
381
        return nil
66✔
382
}
383

384
func reportProgress(c context.Context, state chan string) {
121✔
385
        fmt.Fprintln(os.Stderr, "Writing Artifact...")
121✔
386
        str := fmt.Sprintf("%-20s\t", <-state)
121✔
387
        fmt.Fprint(os.Stderr, str)
121✔
388
        for {
724✔
389
                select {
603✔
390
                case str = <-state:
483✔
391
                        if str == stage.Data {
604✔
392
                                fmt.Fprintf(os.Stderr, "\033[1;32m\u2713\033[0m\n")
121✔
393
                                fmt.Fprintln(os.Stderr, "Payload")
121✔
394
                        } else {
484✔
395
                                fmt.Fprintf(os.Stderr, "\033[1;32m\u2713\033[0m\n")
363✔
396
                                str = fmt.Sprintf("%-20s\t", str)
363✔
397
                                fmt.Fprint(os.Stderr, str)
363✔
398
                        }
363✔
399
                case <-c.Done():
121✔
400
                        return
121✔
401
                }
402
        }
403
}
404

405
func artifactWriter(c *cli.Context, comp artifact.Compressor, w io.Writer,
406
        ver int) (*awriter.Writer, error) {
125✔
407
        privateKey, err := getKey(c)
125✔
408
        if err != nil {
127✔
409
                return nil, err
2✔
410
        }
2✔
411
        if privateKey != nil {
133✔
412
                if ver == 0 {
10✔
UNCOV
413
                        // check if we are having correct version
×
UNCOV
414
                        return nil, errors.New("can not use signed artifact with version 0")
×
UNCOV
415
                }
×
416
                return awriter.NewWriterSigned(w, comp, privateKey), nil
10✔
417
        }
418
        return awriter.NewWriter(w, comp), nil
113✔
419
}
420

421
func makeUpdates(ctx *cli.Context) (*awriter.Updates, error) {
51✔
422
        version := ctx.Int("version")
51✔
423

51✔
424
        var handler, augmentHandler handlers.Composer
51✔
425
        switch version {
51✔
UNCOV
426
        case 2:
×
UNCOV
427
                return nil, cli.NewExitError(
×
428
                        "Module images need at least artifact format version 3",
×
429
                        errArtifactInvalidParameters)
×
430
        case 3:
51✔
431
                handler = handlers.NewModuleImage(ctx.String("type"))
51✔
432
        default:
×
UNCOV
433
                return nil, cli.NewExitError(
×
UNCOV
434
                        fmt.Sprintf("unsupported artifact version: %v", version),
×
UNCOV
435
                        errArtifactUnsupportedVersion,
×
UNCOV
436
                )
×
437
        }
438

439
        dataFiles := make([](*handlers.DataFile), 0, len(ctx.StringSlice("file")))
51✔
440
        for _, file := range ctx.StringSlice("file") {
105✔
441
                dataFiles = append(dataFiles, &handlers.DataFile{Name: file})
54✔
442
        }
54✔
443
        if err := handler.SetUpdateFiles(dataFiles); err != nil {
51✔
444
                return nil, cli.NewExitError(
×
UNCOV
445
                        err,
×
UNCOV
446
                        1,
×
UNCOV
447
                )
×
UNCOV
448
        }
×
449

450
        upd := &awriter.Updates{
51✔
451
                Updates: []handlers.Composer{handler},
51✔
452
        }
51✔
453

51✔
454
        if ctx.String("augment-type") != "" {
55✔
455
                augmentHandler = handlers.NewAugmentedModuleImage(handler, ctx.String("augment-type"))
4✔
456
                dataFiles = make([](*handlers.DataFile), 0, len(ctx.StringSlice("augment-file")))
4✔
457
                for _, file := range ctx.StringSlice("augment-file") {
8✔
458
                        dataFiles = append(dataFiles, &handlers.DataFile{Name: file})
4✔
459
                }
4✔
460
                if err := augmentHandler.SetUpdateAugmentFiles(dataFiles); err != nil {
4✔
461
                        return nil, cli.NewExitError(
×
UNCOV
462
                                err,
×
UNCOV
463
                                1,
×
UNCOV
464
                        )
×
UNCOV
465
                }
×
466
                upd.Augments = []handlers.Composer{augmentHandler}
4✔
467
        }
468

469
        return upd, nil
51✔
470
}
471

472
// makeTypeInfo returns the type-info provides and depends and the augmented
473
// type-info provides and depends, or nil.
474
func makeTypeInfo(ctx *cli.Context) (*artifact.TypeInfoV3, *artifact.TypeInfoV3, error) {
121✔
475
        // Make key value pairs from the type-info fields supplied on command
121✔
476
        // line.
121✔
477
        var keyValues *map[string]string
121✔
478

121✔
479
        var typeInfoDepends artifact.TypeInfoDepends
121✔
480
        keyValues, err := extractKeyValues(ctx.StringSlice("depends"))
121✔
481
        if err != nil {
121✔
482
                return nil, nil, err
×
483
        } else if keyValues != nil {
151✔
484
                if typeInfoDepends, err = artifact.NewTypeInfoDepends(*keyValues); err != nil {
30✔
UNCOV
485
                        return nil, nil, err
×
UNCOV
486
                }
×
487
        }
488

489
        var typeInfoProvides artifact.TypeInfoProvides
121✔
490
        keyValues, err = extractKeyValues(ctx.StringSlice("provides"))
121✔
491
        if err != nil {
121✔
492
                return nil, nil, err
×
493
        } else if keyValues != nil {
153✔
494
                if typeInfoProvides, err = artifact.NewTypeInfoProvides(*keyValues); err != nil {
32✔
UNCOV
495
                        return nil, nil, err
×
UNCOV
496
                }
×
497
        }
498
        typeInfoProvides = applySoftwareVersionToTypeInfoProvides(ctx, typeInfoProvides)
121✔
499

121✔
500
        var augmentTypeInfoDepends artifact.TypeInfoDepends
121✔
501
        keyValues, err = extractKeyValues(ctx.StringSlice("augment-depends"))
121✔
502
        if err != nil {
121✔
503
                return nil, nil, err
×
504
        } else if keyValues != nil {
125✔
505
                if augmentTypeInfoDepends, err = artifact.NewTypeInfoDepends(*keyValues); err != nil {
4✔
UNCOV
506
                        return nil, nil, err
×
UNCOV
507
                }
×
508
        }
509

510
        var augmentTypeInfoProvides artifact.TypeInfoProvides
121✔
511
        keyValues, err = extractKeyValues(ctx.StringSlice("augment-provides"))
121✔
512
        if err != nil {
121✔
513
                return nil, nil, err
×
514
        } else if keyValues != nil {
125✔
515
                if augmentTypeInfoProvides, err = artifact.NewTypeInfoProvides(*keyValues); err != nil {
4✔
UNCOV
516
                        return nil, nil, err
×
UNCOV
517
                }
×
518
        }
519

520
        clearsArtifactProvides, err := makeClearsArtifactProvides(ctx)
121✔
521
        if err != nil {
121✔
UNCOV
522
                return nil, nil, err
×
UNCOV
523
        }
×
524

525
        var typeInfo *string
121✔
526
        if ctx.Command.Name != "bootstrap-artifact" {
238✔
527
                typeFlag := ctx.String("type")
117✔
528
                typeInfo = &typeFlag
117✔
529
        }
117✔
530
        typeInfoV3 := &artifact.TypeInfoV3{
121✔
531
                Type:                   typeInfo,
121✔
532
                ArtifactDepends:        typeInfoDepends,
121✔
533
                ArtifactProvides:       typeInfoProvides,
121✔
534
                ClearsArtifactProvides: clearsArtifactProvides,
121✔
535
        }
121✔
536

121✔
537
        if ctx.String("augment-type") == "" {
238✔
538
                // Non-augmented artifact
117✔
539
                if len(ctx.StringSlice("augment-file")) != 0 ||
117✔
540
                        len(ctx.StringSlice("augment-depends")) != 0 ||
117✔
541
                        len(ctx.StringSlice("augment-provides")) != 0 ||
117✔
542
                        ctx.String("augment-meta-data") != "" {
117✔
543

×
UNCOV
544
                        err = errors.New("Must give --augment-type argument if making augmented artifact")
×
UNCOV
545
                        fmt.Println(err.Error())
×
UNCOV
546
                        return nil, nil, err
×
UNCOV
547
                }
×
548
                return typeInfoV3, nil, nil
117✔
549
        }
550

551
        augmentType := ctx.String("augment-type")
4✔
552
        augmentTypeInfoV3 := &artifact.TypeInfoV3{
4✔
553
                Type:             &augmentType,
4✔
554
                ArtifactDepends:  augmentTypeInfoDepends,
4✔
555
                ArtifactProvides: augmentTypeInfoProvides,
4✔
556
        }
4✔
557

4✔
558
        return typeInfoV3, augmentTypeInfoV3, nil
4✔
559
}
560

561
func getSoftwareVersion(
562
        artifactName,
563
        softwareFilesystem,
564
        softwareName,
565
        softwareNameDefault,
566
        softwareVersion string,
567
        noDefaultSoftwareVersion bool,
568
) map[string]string {
135✔
569
        result := map[string]string{}
135✔
570
        softwareVersionName := "rootfs-image"
135✔
571
        if softwareFilesystem != "" {
151✔
572
                softwareVersionName = softwareFilesystem
16✔
573
        }
16✔
574
        if !noDefaultSoftwareVersion {
242✔
575
                if softwareName == "" {
202✔
576
                        softwareName = softwareNameDefault
95✔
577
                }
95✔
578
                if softwareVersion == "" {
202✔
579
                        softwareVersion = artifactName
95✔
580
                }
95✔
581
        }
582
        if softwareName != "" {
186✔
583
                softwareVersionName += fmt.Sprintf(".%s", softwareName)
51✔
584
        }
51✔
585
        if softwareVersionName != "" && softwareVersion != "" {
246✔
586
                result[softwareVersionName+".version"] = softwareVersion
111✔
587
        }
111✔
588
        return result
135✔
589
}
590

591
// applySoftwareVersionToTypeInfoProvides returns a new mapping, enriched with provides
592
// for the software version; the mapping provided as argument is not modified
593
func applySoftwareVersionToTypeInfoProvides(
594
        ctx *cli.Context,
595
        typeInfoProvides artifact.TypeInfoProvides,
596
) artifact.TypeInfoProvides {
121✔
597
        result := make(map[string]string)
121✔
598
        for key, value := range typeInfoProvides {
185✔
599
                result[key] = value
64✔
600
        }
64✔
601
        artifactName := ctx.String("artifact-name")
121✔
602
        softwareFilesystem := ctx.String(softwareFilesystemFlag)
121✔
603
        softwareName := ctx.String(softwareNameFlag)
121✔
604
        softwareNameDefault := ""
121✔
605
        if ctx.Command.Name == "module-image" {
172✔
606
                softwareNameDefault = ctx.String("type")
51✔
607
        }
51✔
608
        if ctx.Command.Name == "bootstrap-artifact" {
125✔
609
                return result
4✔
610
        }
4✔
611
        softwareVersion := ctx.String(softwareVersionFlag)
117✔
612
        noDefaultSoftwareVersion := ctx.Bool(noDefaultSoftwareVersionFlag)
117✔
613
        if softwareVersionMapping := getSoftwareVersion(
117✔
614
                artifactName,
117✔
615
                softwareFilesystem,
117✔
616
                softwareName,
117✔
617
                softwareNameDefault,
117✔
618
                softwareVersion,
117✔
619
                noDefaultSoftwareVersion,
117✔
620
        ); len(softwareVersionMapping) > 0 {
214✔
621
                for key, value := range softwareVersionMapping {
194✔
622
                        if result[key] == "" || softwareVersionOverridesProvides(ctx, key) {
190✔
623
                                result[key] = value
93✔
624
                        }
93✔
625
                }
626
        }
627
        return result
117✔
628
}
629

630
func softwareVersionOverridesProvides(ctx *cli.Context, key string) bool {
6✔
631
        mainCtx := ctx.Parent().Parent()
6✔
632
        cmdLine := strings.Join(mainCtx.Args(), " ")
6✔
633

6✔
634
        var providesVersion string = `(-p|--provides)(\s+|=)` + regexp.QuoteMeta(key) + ":"
6✔
635
        reProvidesVersion := regexp.MustCompile(providesVersion)
6✔
636
        providesIndexes := reProvidesVersion.FindAllStringIndex(cmdLine, -1)
6✔
637

6✔
638
        var softareVersion string = "--software-(name|version|filesystem)"
6✔
639
        reSoftwareVersion := regexp.MustCompile(softareVersion)
6✔
640
        softwareIndexes := reSoftwareVersion.FindAllStringIndex(cmdLine, -1)
6✔
641

6✔
642
        if len(providesIndexes) == 0 {
6✔
UNCOV
643
                return true
×
644
        } else if len(softwareIndexes) == 0 {
8✔
645
                return false
2✔
646
        } else {
6✔
647
                return softwareIndexes[len(softwareIndexes)-1][0] >
4✔
648
                        providesIndexes[len(providesIndexes)-1][0]
4✔
649
        }
4✔
650
}
651

652
func makeClearsArtifactProvides(ctx *cli.Context) ([]string, error) {
121✔
653
        list := ctx.StringSlice(clearsProvidesFlag)
121✔
654

121✔
655
        if ctx.Bool(noDefaultClearsProvidesFlag) ||
121✔
656
                ctx.Bool(noDefaultSoftwareVersionFlag) ||
121✔
657
                ctx.Command.Name == "bootstrap-artifact" {
153✔
658
                return list, nil
32✔
659
        }
32✔
660

661
        var softwareFilesystem string
89✔
662
        if ctx.IsSet("software-filesystem") {
97✔
663
                softwareFilesystem = ctx.String("software-filesystem")
8✔
664
        } else {
89✔
665
                softwareFilesystem = "rootfs-image"
81✔
666
        }
81✔
667

668
        var softwareName string
89✔
669
        if len(ctx.String("software-name")) > 0 {
97✔
670
                softwareName = ctx.String("software-name") + "."
8✔
671
        } else if ctx.Command.Name == "rootfs-image" {
141✔
672
                softwareName = ""
52✔
673
                // "rootfs_image_checksum" is included for legacy
52✔
674
                // reasons. Previously, "rootfs_image_checksum" was the name
52✔
675
                // given to the checksum, but new artifacts follow the new dot
52✔
676
                // separated scheme, "rootfs-image.checksum", which also has the
52✔
677
                // correct dash instead of the incorrect underscore.
52✔
678
                //
52✔
679
                // "artifact_group" is included as a sane default for
52✔
680
                // rootfs-image updates. A standard rootfs-image update should
52✔
681
                // clear the group if it does not have one.
52✔
682
                if softwareFilesystem == "rootfs-image" {
102✔
683
                        list = append(list, "artifact_group", "rootfs_image_checksum")
50✔
684
                }
50✔
685
        } else if ctx.Command.Name == "module-image" {
58✔
686
                softwareName = ctx.String("type") + "."
29✔
687
        } else {
29✔
UNCOV
688
                return nil, errors.New(
×
UNCOV
689
                        "Unknown write command in makeClearsArtifactProvides(), this is a bug.",
×
UNCOV
690
                )
×
UNCOV
691
        }
×
692

693
        defaultCap := fmt.Sprintf("%s.%s*", softwareFilesystem, softwareName)
89✔
694
        for _, cap := range list {
195✔
695
                if defaultCap == cap {
108✔
696
                        // Avoid adding it twice if the default is the same as a
2✔
697
                        // specified provide.
2✔
698
                        goto dontAdd
2✔
699
                }
700
        }
701
        list = append(list, defaultCap)
87✔
702

87✔
703
dontAdd:
87✔
704
        return list, nil
89✔
705
}
706

707
func makeMetaData(ctx *cli.Context) (map[string]interface{}, map[string]interface{}, error) {
92✔
708
        var metaData map[string]interface{}
92✔
709
        var augmentMetaData map[string]interface{}
92✔
710

92✔
711
        if len(ctx.String("meta-data")) > 0 {
113✔
712
                file, err := os.Open(ctx.String("meta-data"))
21✔
713
                if err != nil {
21✔
UNCOV
714
                        return metaData, augmentMetaData, cli.NewExitError(err, errArtifactInvalidParameters)
×
UNCOV
715
                }
×
716
                defer file.Close()
21✔
717
                dec := json.NewDecoder(file)
21✔
718
                err = dec.Decode(&metaData)
21✔
719
                if err != nil {
21✔
UNCOV
720
                        return metaData, augmentMetaData, cli.NewExitError(err, errArtifactInvalidParameters)
×
UNCOV
721
                }
×
722
        }
723

724
        if len(ctx.String("augment-meta-data")) > 0 {
96✔
725
                file, err := os.Open(ctx.String("augment-meta-data"))
4✔
726
                if err != nil {
4✔
UNCOV
727
                        return metaData, augmentMetaData, cli.NewExitError(err, errArtifactInvalidParameters)
×
UNCOV
728
                }
×
729
                defer file.Close()
4✔
730
                dec := json.NewDecoder(file)
4✔
731
                err = dec.Decode(&augmentMetaData)
4✔
732
                if err != nil {
4✔
UNCOV
733
                        return metaData, augmentMetaData, cli.NewExitError(err, errArtifactInvalidParameters)
×
UNCOV
734
                }
×
735
        }
736

737
        return metaData, augmentMetaData, nil
92✔
738
}
739

740
func writeModuleImage(ctx *cli.Context) error {
51✔
741
        comp, err := artifact.NewCompressorFromId(ctx.GlobalString("compression"))
51✔
742
        if err != nil {
51✔
743
                return cli.NewExitError(
×
UNCOV
744
                        "compressor '"+ctx.GlobalString("compression")+"' is not supported: "+err.Error(),
×
UNCOV
745
                        1,
×
UNCOV
746
                )
×
UNCOV
747
        }
×
748

749
        // set the default name
750
        name := "artifact.mender"
51✔
751
        if len(ctx.String("output-path")) > 0 {
102✔
752
                name = ctx.String("output-path")
51✔
753
        }
51✔
754
        version := ctx.Int("version")
51✔
755

51✔
756
        if version == 1 {
51✔
UNCOV
757
                return cli.NewExitError("Mender-Artifact version 1 is not supported", 1)
×
758
        }
×
759

760
        // The device-type flag is required
761
        if len(ctx.StringSlice("device-type")) == 0 {
51✔
UNCOV
762
                return cli.NewExitError("The `device-type` flag is required", 1)
×
763
        }
×
764

765
        upd, err := makeUpdates(ctx)
51✔
766
        if err != nil {
51✔
UNCOV
767
                return err
×
768
        }
×
769

770
        var w io.Writer
51✔
771
        if name == "-" {
51✔
772
                w = os.Stdout
×
773
        } else {
51✔
774
                f, err := os.Create(name)
51✔
775
                if err != nil {
51✔
776
                        return cli.NewExitError(
×
UNCOV
777
                                "can not create artifact file: "+err.Error(),
×
UNCOV
778
                                errArtifactCreate,
×
UNCOV
779
                        )
×
UNCOV
780
                }
×
781
                defer f.Close()
51✔
782
                w = f
51✔
783
        }
784

785
        aw, err := artifactWriter(ctx, comp, w, version)
51✔
786
        if err != nil {
51✔
UNCOV
787
                return cli.NewExitError(err.Error(), 1)
×
788
        }
×
789

790
        scr, err := scripts(ctx.StringSlice("script"))
51✔
791
        if err != nil {
51✔
UNCOV
792
                return cli.NewExitError(err.Error(), 1)
×
UNCOV
793
        }
×
794

795
        depends := artifact.ArtifactDepends{
51✔
796
                ArtifactName:      ctx.StringSlice("artifact-name-depends"),
51✔
797
                CompatibleDevices: ctx.StringSlice("device-type"),
51✔
798
                ArtifactGroup:     ctx.StringSlice("depends-groups"),
51✔
799
        }
51✔
800

51✔
801
        provides := artifact.ArtifactProvides{
51✔
802
                ArtifactName:  ctx.String("artifact-name"),
51✔
803
                ArtifactGroup: ctx.String("provides-group"),
51✔
804
        }
51✔
805

51✔
806
        typeInfoV3, augmentTypeInfoV3, err := makeTypeInfo(ctx)
51✔
807
        if err != nil {
51✔
UNCOV
808
                return err
×
809
        }
×
810

811
        metaData, augmentMetaData, err := makeMetaData(ctx)
51✔
812
        if err != nil {
51✔
UNCOV
813
                return err
×
UNCOV
814
        }
×
815

816
        if !ctx.Bool("no-progress") {
102✔
817
                ctx, cancel := context.WithCancel(context.Background())
51✔
818
                go reportProgress(ctx, aw.State)
51✔
819
                defer cancel()
51✔
820
                aw.ProgressWriter = utils.NewProgressWriter()
51✔
821
        }
51✔
822

823
        err = aw.WriteArtifact(
51✔
824
                &awriter.WriteArtifactArgs{
51✔
825
                        Format:            "mender",
51✔
826
                        Version:           version,
51✔
827
                        Devices:           ctx.StringSlice("device-type"),
51✔
828
                        Name:              ctx.String("artifact-name"),
51✔
829
                        Updates:           upd,
51✔
830
                        Scripts:           scr,
51✔
831
                        Depends:           &depends,
51✔
832
                        Provides:          &provides,
51✔
833
                        TypeInfoV3:        typeInfoV3,
51✔
834
                        MetaData:          metaData,
51✔
835
                        AugmentTypeInfoV3: augmentTypeInfoV3,
51✔
836
                        AugmentMetaData:   augmentMetaData,
51✔
837
                })
51✔
838
        if err != nil {
51✔
UNCOV
839
                return cli.NewExitError(err.Error(), 1)
×
UNCOV
840
        }
×
841
        return nil
51✔
842
}
843

844
func extractKeyValues(params []string) (*map[string]string, error) {
648✔
845
        var keyValues *map[string]string
648✔
846
        if len(params) > 0 {
724✔
847
                keyValues = &map[string]string{}
76✔
848
                for _, arg := range params {
232✔
849
                        split := strings.SplitN(arg, ":", 2)
156✔
850
                        if len(split) != 2 {
156✔
UNCOV
851
                                return nil, cli.NewExitError(
×
UNCOV
852
                                        fmt.Sprintf("argument must have a delimiting colon: %s", arg),
×
UNCOV
853
                                        errArtifactInvalidParameters)
×
UNCOV
854
                        }
×
855
                        (*keyValues)[split[0]] = split[1]
156✔
856
                }
857
        }
858
        return keyValues, nil
648✔
859
}
860

861
// SSH to remote host and dump rootfs snapshot to a local temporary file.
862
func getDeviceSnapshot(c *cli.Context) (string, error) {
×
863

×
864
        const sshInitMagic = "Initializing snapshot..."
×
865
        var userAtHost string
×
866
        var sigChan chan os.Signal
×
867
        var errChan chan error
×
868
        ctx, cancel := context.WithCancel(context.Background())
×
869
        defer cancel()
×
870
        port := "22"
×
871
        host := strings.TrimPrefix(c.String("file"), "ssh://")
×
872

×
873
        if remotePort := strings.Split(host, ":"); len(remotePort) == 2 {
×
874
                port = remotePort[1]
×
875
                userAtHost = remotePort[0]
×
876
        } else {
×
877
                userAtHost = host
×
878
        }
×
879

880
        // Prepare command-line arguments
881
        args := c.StringSlice("ssh-args")
×
882
        // Check if port is specified explicitly with the --ssh-args flag
×
883
        addPort := true
×
884
        for _, arg := range args {
×
885
                if strings.Contains(arg, "-p") {
×
886
                        addPort = false
×
887
                        break
×
888
                }
889
        }
890
        if addPort {
×
891
                args = append(args, "-p", port)
×
892
        }
×
893
        args = append(args, userAtHost)
×
UNCOV
894
        // First echo to stdout such that we know when ssh connection is
×
895
        // established (password prompt is written to /dev/tty directly,
×
896
        // and hence impossible to detect).
×
897
        // When user id is 0 do not bother with sudo.
×
898
        args = append(
×
899
                args,
×
UNCOV
900
                "/bin/sh",
×
901
                "-c",
×
902
                `'[ $(id -u) -eq 0 ] || sudo_cmd="sudo -S"`+
×
903
                        `; if which mender-snapshot 1> /dev/null`+
×
904
                        `; then $sudo_cmd /bin/sh -c "echo `+sshInitMagic+`; mender-snapshot dump" | cat`+
×
UNCOV
905
                        `; elif which mender 1> /dev/null`+
×
906
                        `; then $sudo_cmd /bin/sh -c "echo `+sshInitMagic+`; mender snapshot dump" | cat`+
×
UNCOV
907
                        `; else echo "Mender not found: Please check that Mender is installed" >&2 &&`+
×
UNCOV
908
                        `exit 1; fi'`,
×
909
        )
×
910

×
911
        cmd := exec.Command("ssh", args...)
×
912

×
913
        // Simply connect stdin/stderr
×
914
        cmd.Stdin = os.Stdin
×
915
        cmd.Stderr = os.Stderr
×
916
        stdout, err := cmd.StdoutPipe()
×
917
        if err != nil {
×
918
                return "", errors.New("Error redirecting stdout on exec")
×
919
        }
×
920

921
        // Create tempfile for storing the snapshot
922
        f, err := os.CreateTemp("", "rootfs.tmp")
×
923
        if err != nil {
×
924
                return "", err
×
925
        }
×
926
        filePath := f.Name()
×
927

×
928
        defer removeOnPanic(filePath)
×
929
        defer f.Close()
×
930

×
931
        // Disable tty echo before starting
×
UNCOV
932
        term, err := util.DisableEcho(int(os.Stdin.Fd()))
×
933
        if err == nil {
×
934
                sigChan = make(chan os.Signal, 1)
×
935
                errChan = make(chan error, 1)
×
936
                // Make sure that echo is enabled if the process gets
×
937
                // interrupted
×
938
                signal.Notify(sigChan)
×
939
                go util.EchoSigHandler(ctx, sigChan, errChan, term)
×
940
        } else if err != syscall.ENOTTY {
×
UNCOV
941
                return "", err
×
UNCOV
942
        }
×
943

944
        if err := cmd.Start(); err != nil {
×
945
                return "", err
×
946
        }
×
947

948
        // Wait for 60 seconds for ssh to establish connection
949
        err = waitForBufferSignal(stdout, os.Stdout, sshInitMagic, 2*time.Minute)
×
UNCOV
950
        if err != nil {
×
UNCOV
951
                _ = cmd.Process.Kill()
×
UNCOV
952
                return "", errors.Wrap(err,
×
UNCOV
953
                        "Error waiting for ssh session to be established.")
×
954
        }
×
955

956
        _, err = recvSnapshot(f, stdout)
×
957
        if err != nil {
×
958
                _ = cmd.Process.Kill()
×
959
                return "", err
×
960
        }
×
961

962
        if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
×
963
                return "", errors.New("SSH session closed unexpectedly")
×
964
        }
×
965

966
        if err = cmd.Wait(); err != nil {
×
967
                return "", errors.Wrap(err,
×
968
                        "SSH session closed with error")
×
969
        }
×
970

971
        if sigChan != nil {
×
972
                // Wait for signal handler to execute
×
973
                signal.Stop(sigChan)
×
UNCOV
974
                cancel()
×
975
                err = <-errChan
×
UNCOV
976
        }
×
977

978
        return filePath, err
×
979
}
980
func showProvides(c *cli.Context) (map[string]string, error) {
×
981
        var userAtHost string
×
982
        providesMap := make(map[string]string)
×
983
        port := "22"
×
984
        host := strings.TrimPrefix(c.String("file"), "ssh://")
×
985

×
986
        if remotePort := strings.Split(host, ":"); len(remotePort) == 2 {
×
987
                port = remotePort[1]
×
988
                userAtHost = remotePort[0]
×
989
        } else {
×
990
                userAtHost = host
×
UNCOV
991
        }
×
992

993
        args := c.StringSlice("ssh-args")
×
UNCOV
994
        // Check if port is specified explicitly with the --ssh-args flag
×
UNCOV
995
        addPort := true
×
UNCOV
996
        for _, arg := range args {
×
UNCOV
997
                if strings.Contains(arg, "-p") {
×
UNCOV
998
                        addPort = false
×
UNCOV
999
                        break
×
1000
                }
1001
        }
UNCOV
1002
        if addPort {
×
UNCOV
1003
                args = append(args, "-p", port)
×
UNCOV
1004
        }
×
UNCOV
1005
        args = append(args, userAtHost)
×
UNCOV
1006

×
UNCOV
1007
        providesArgs := ` 'if which mender-update 1> /dev/null` +
×
UNCOV
1008
                `; then mender-update show-provides` +
×
UNCOV
1009
                `; elif which mender 1> /dev/null` +
×
UNCOV
1010
                `; then mender show-provides` +
×
UNCOV
1011
                `; else echo "Mender not found: Please check that Mender is installed" >&2 &&` +
×
UNCOV
1012
                ` exit 1; fi'`
×
UNCOV
1013

×
UNCOV
1014
        args = append(
×
UNCOV
1015
                args,
×
UNCOV
1016
                "/bin/sh",
×
UNCOV
1017
                "-c",
×
UNCOV
1018
                providesArgs)
×
UNCOV
1019

×
UNCOV
1020
        cmd := exec.Command("ssh", args...)
×
UNCOV
1021

×
UNCOV
1022
        stdout, err := cmd.CombinedOutput()
×
UNCOV
1023

×
UNCOV
1024
        if err != nil {
×
UNCOV
1025
                return nil, err
×
UNCOV
1026
        }
×
UNCOV
1027
        if len(stdout) == 0 {
×
UNCOV
1028
                return nil, nil
×
UNCOV
1029
        }
×
UNCOV
1030
        provides := strings.Split(string(stdout), "\n")
×
UNCOV
1031

×
UNCOV
1032
        for _, p := range provides {
×
UNCOV
1033
                if p == "" || !strings.HasPrefix(p, "rootfs-image.") {
×
UNCOV
1034
                        continue
×
1035
                }
UNCOV
1036
                info := strings.Split(p, "=")
×
UNCOV
1037
                if len(info) != 2 {
×
UNCOV
1038
                        continue
×
1039
                }
UNCOV
1040
                providesMap[info[0]] = info[1]
×
1041
        }
UNCOV
1042
        return providesMap, nil
×
1043
}
1044

1045
// Reads from src waiting for the string specified by signal, writing all other
1046
// output appearing at src to sink. The function returns an error if occurs
1047
// reading from the stream or the deadline exceeds.
1048
func waitForBufferSignal(src io.Reader, sink io.Writer,
UNCOV
1049
        signal string, deadline time.Duration) error {
×
UNCOV
1050

×
UNCOV
1051
        var err error
×
UNCOV
1052
        errChan := make(chan error)
×
UNCOV
1053

×
UNCOV
1054
        go func() {
×
UNCOV
1055
                stdoutRdr := bufio.NewReader(src)
×
UNCOV
1056
                for {
×
UNCOV
1057
                        line, err := stdoutRdr.ReadString('\n')
×
UNCOV
1058
                        if err != nil {
×
UNCOV
1059
                                errChan <- err
×
UNCOV
1060
                                break
×
1061
                        }
UNCOV
1062
                        if strings.Contains(line, signal) {
×
UNCOV
1063
                                errChan <- nil
×
UNCOV
1064
                                break
×
1065
                        }
UNCOV
1066
                        _, err = sink.Write([]byte(line + "\n"))
×
UNCOV
1067
                        if err != nil {
×
UNCOV
1068
                                errChan <- err
×
UNCOV
1069
                                break
×
1070
                        }
1071
                }
1072
        }()
1073

UNCOV
1074
        select {
×
UNCOV
1075
        case err = <-errChan:
×
1076
                // Error from goroutine
UNCOV
1077
        case <-time.After(deadline):
×
UNCOV
1078
                err = errors.New("Input deadline exceeded")
×
1079
        }
UNCOV
1080
        return err
×
1081
}
1082

1083
// Performs the same operation as io.Copy while at the same time prining
1084
// the number of bytes written at any time.
UNCOV
1085
func recvSnapshot(dst io.Writer, src io.Reader) (int64, error) {
×
UNCOV
1086
        buf := make([]byte, 1024*1024*32)
×
UNCOV
1087
        var written int64
×
UNCOV
1088
        for {
×
UNCOV
1089
                nr, err := src.Read(buf)
×
UNCOV
1090
                if err == io.EOF {
×
UNCOV
1091
                        fmt.Println()
×
UNCOV
1092
                        break
×
UNCOV
1093
                } else if err != nil {
×
UNCOV
1094
                        return written, errors.Wrap(err,
×
UNCOV
1095
                                "Error receiving snapshot from device")
×
UNCOV
1096
                }
×
UNCOV
1097
                nw, err := dst.Write(buf[:nr])
×
UNCOV
1098
                if err != nil {
×
UNCOV
1099
                        return written, errors.Wrap(err,
×
UNCOV
1100
                                "Error storing snapshot locally")
×
UNCOV
1101
                } else if nw < nr {
×
UNCOV
1102
                        return written, io.ErrShortWrite
×
UNCOV
1103
                }
×
UNCOV
1104
                written += int64(nw)
×
1105
        }
UNCOV
1106
        return written, nil
×
1107
}
1108

UNCOV
1109
func removeOnPanic(filename string) {
×
UNCOV
1110
        if r := recover(); r != nil {
×
UNCOV
1111
                err := os.Remove(filename)
×
UNCOV
1112
                if err != nil {
×
UNCOV
1113
                        switch v := r.(type) {
×
UNCOV
1114
                        case string:
×
UNCOV
1115
                                err = errors.Wrap(errors.New(v), err.Error())
×
UNCOV
1116
                                panic(err)
×
UNCOV
1117
                        case error:
×
UNCOV
1118
                                err = errors.Wrap(v, err.Error())
×
UNCOV
1119
                                panic(err)
×
UNCOV
1120
                        default:
×
UNCOV
1121
                                panic(r)
×
1122
                        }
1123
                }
UNCOV
1124
                panic(r)
×
1125
        }
1126
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc