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

mendersoftware / mender / 950534094

pending completion
950534094

push

gitlab-ci

kacf
chore: Add `StartsWith` and `EndsWith` generic utility functions.

Also use the latter in `states.cpp`.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

1 of 1 new or added line in 1 file covered. (100.0%)

4931 of 6276 relevant lines covered (78.57%)

196.18 hits per line

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

82.01
/mender-update/standalone/standalone.cpp
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
#include <mender-update/standalone.hpp>
16

17
#include <common/common.hpp>
18
#include <common/events_io.hpp>
19
#include <common/http.hpp>
20
#include <common/log.hpp>
21
#include <common/conf/paths.hpp>
22

23
namespace mender {
24
namespace update {
25
namespace standalone {
26

27
namespace common = mender::common;
28
namespace events = mender::common::events;
29
namespace http = mender::http;
30
namespace io = mender::common::io;
31
namespace log = mender::common::log;
32
namespace paths = mender::common::conf::paths;
33

34
const string StateDataKeys::version {"Version"};
35
const string StateDataKeys::artifact_name {"ArtifactName"};
36
const string StateDataKeys::artifact_group {"ArtifactGroup"};
37
const string StateDataKeys::artifact_provides {"ArtifactTypeInfoProvides"};
38
const string StateDataKeys::artifact_clears_provides {"ArtifactClearsProvides"};
39
const string StateDataKeys::payload_types {"PayloadTypes"};
40

41
ExpectedOptionalStateData LoadStateData(database::KeyValueDatabase &db) {
40✔
42
        StateDataKeys keys;
43
        StateData dst;
80✔
44

45
        auto exp_bytes = db.Read(context::MenderContext::standalone_state_key);
80✔
46
        if (!exp_bytes) {
40✔
47
                auto &err = exp_bytes.error();
32✔
48
                if (err.code == database::MakeError(database::KeyError, "").code) {
32✔
49
                        return optional::optional<StateData>();
64✔
50
                } else {
51
                        return expected::unexpected(err);
×
52
                }
53
        }
54

55
        auto exp_json = json::Load(common::StringFromByteVector(exp_bytes.value()));
16✔
56
        if (!exp_json) {
8✔
57
                return expected::unexpected(exp_json.error());
×
58
        }
59
        auto &json = exp_json.value();
8✔
60

61
        auto exp_int = json::Get<int64_t>(json, keys.version, json::MissingOk::No);
16✔
62
        if (!exp_int) {
8✔
63
                return expected::unexpected(exp_int.error());
×
64
        }
65
        dst.version = exp_int.value();
8✔
66

67
        auto exp_string = json::Get<string>(json, keys.artifact_name, json::MissingOk::No);
16✔
68
        if (!exp_string) {
8✔
69
                return expected::unexpected(exp_string.error());
×
70
        }
71
        dst.artifact_name = exp_string.value();
8✔
72

73
        exp_string = json::Get<string>(json, keys.artifact_group, json::MissingOk::Yes);
8✔
74
        if (!exp_string) {
8✔
75
                return expected::unexpected(exp_string.error());
×
76
        }
77
        dst.artifact_group = exp_string.value();
8✔
78

79
        auto exp_map = json::Get<json::KeyValueMap>(json, keys.artifact_provides, json::MissingOk::No);
16✔
80
        if (exp_map) {
8✔
81
                dst.artifact_provides = exp_map.value();
7✔
82
        } else {
83
                dst.artifact_provides.reset();
1✔
84
        }
85

86
        auto exp_array =
87
                json::Get<vector<string>>(json, keys.artifact_clears_provides, json::MissingOk::No);
16✔
88
        if (exp_array) {
8✔
89
                dst.artifact_clears_provides = exp_array.value();
7✔
90
        } else {
91
                dst.artifact_clears_provides.reset();
1✔
92
        }
93

94
        exp_array = json::Get<vector<string>>(json, keys.payload_types, json::MissingOk::No);
8✔
95
        if (!exp_array) {
8✔
96
                return expected::unexpected(exp_array.error());
×
97
        }
98
        dst.payload_types = exp_array.value();
8✔
99

100
        if (dst.version != context::MenderContext::standalone_data_version) {
8✔
101
                return expected::unexpected(error::Error(
×
102
                        make_error_condition(errc::not_supported),
×
103
                        "State data has a version which is not supported by this client"));
×
104
        }
105

106
        if (dst.artifact_name == "") {
8✔
107
                return expected::unexpected(context::MakeError(
×
108
                        context::DatabaseValueError, "`" + keys.artifact_name + "` is empty"));
×
109
        }
110

111
        if (dst.payload_types.size() == 0) {
8✔
112
                return expected::unexpected(context::MakeError(
×
113
                        context::DatabaseValueError, "`" + keys.payload_types + "` is empty"));
×
114
        }
115
        if (dst.payload_types.size() >= 2) {
8✔
116
                return expected::unexpected(error::Error(
×
117
                        make_error_condition(errc::not_supported),
×
118
                        "`" + keys.payload_types + "` contains multiple payloads"));
×
119
        }
120

121
        return dst;
8✔
122
}
123

124
StateData StateDataFromPayloadHeaderView(const artifact::PayloadHeaderView &header) {
30✔
125
        StateData dst;
30✔
126
        dst.version = context::MenderContext::standalone_data_version;
30✔
127
        dst.artifact_name = header.header.artifact_name;
30✔
128
        dst.artifact_group = header.header.artifact_group;
30✔
129
        dst.artifact_provides = header.header.type_info.artifact_provides;
30✔
130
        dst.artifact_clears_provides = header.header.type_info.clears_artifact_provides;
30✔
131
        dst.payload_types.clear();
30✔
132
        dst.payload_types.push_back(header.header.payload_type);
30✔
133
        return dst;
30✔
134
}
135

136
error::Error SaveStateData(database::KeyValueDatabase &db, const StateData &data) {
29✔
137
        StateDataKeys keys;
138
        stringstream ss;
58✔
139
        ss << "{";
29✔
140
        ss << "\"" << keys.version << "\":" << data.version;
29✔
141

142
        ss << ",";
29✔
143
        ss << "\"" << keys.artifact_name << "\":\"" << data.artifact_name << "\"";
29✔
144

145
        ss << ",";
29✔
146
        ss << "\"" << keys.artifact_group << "\":\"" << data.artifact_group << "\"";
29✔
147

148
        ss << ",";
29✔
149
        ss << "\"" << keys.payload_types << "\": [";
29✔
150
        bool first = true;
29✔
151
        for (auto elem : data.payload_types) {
58✔
152
                if (!first) {
29✔
153
                        ss << ",";
×
154
                }
155
                ss << "\"" << elem << "\"";
29✔
156
                first = false;
29✔
157
        }
158
        ss << "]";
29✔
159

160
        if (data.artifact_provides) {
29✔
161
                ss << ",";
28✔
162
                ss << "\"" << keys.artifact_provides << "\": {";
28✔
163
                bool first = true;
28✔
164
                for (auto elem : data.artifact_provides.value()) {
84✔
165
                        if (!first) {
56✔
166
                                ss << ",";
28✔
167
                        }
168
                        ss << "\"" << elem.first << "\":\"" << elem.second << "\"";
56✔
169
                        first = false;
56✔
170
                }
171
                ss << "}";
28✔
172
        }
173

174
        if (data.artifact_clears_provides) {
29✔
175
                ss << ",";
28✔
176
                ss << "\"" << keys.artifact_clears_provides << "\": [";
28✔
177
                bool first = true;
28✔
178
                for (auto elem : data.artifact_clears_provides.value()) {
112✔
179
                        if (!first) {
84✔
180
                                ss << ",";
56✔
181
                        }
182
                        ss << "\"" << elem << "\"";
84✔
183
                        first = false;
84✔
184
                }
185
                ss << "]";
28✔
186
        }
187

188
        ss << "}";
29✔
189

190
        string strdata = ss.str();
58✔
191
        vector<uint8_t> bytedata(common::ByteVectorFromString(strdata));
58✔
192

193
        return db.Write(context::MenderContext::standalone_state_key, bytedata);
58✔
194
}
195

196
error::Error RemoveStateData(database::KeyValueDatabase &db) {
4✔
197
        return db.Remove(context::MenderContext::standalone_state_key);
4✔
198
}
199

200
static io::ExpectedReaderPtr ReaderFromUrl(
1✔
201
        events::EventLoop &loop, http::Client &http_client, const string &src) {
202
        auto req = make_shared<http::OutgoingRequest>();
2✔
203
        req->SetMethod(http::Method::GET);
1✔
204
        auto err = req->SetAddress(src);
2✔
205
        if (err != error::NoError) {
1✔
206
                return expected::unexpected(err);
×
207
        }
208
        error::Error inner_err;
2✔
209
        io::AsyncReaderPtr reader;
1✔
210
        err = http_client.AsyncCall(
1✔
211
                req,
212
                [&loop, &inner_err, &reader](http::ExpectedIncomingResponsePtr exp_resp) {
2✔
213
                        // No matter what happens, we will want to stop the loop after the headers
214
                        // are received.
215
                        loop.Stop();
1✔
216

217
                        if (!exp_resp) {
1✔
218
                                inner_err = exp_resp.error();
×
219
                                return;
×
220
                        }
221

222
                        auto resp = exp_resp.value();
1✔
223

224
                        if (resp->GetStatusCode() != http::StatusOK) {
1✔
225
                                inner_err = context::MakeError(
×
226
                                        context::UnexpectedHttpResponse,
227
                                        to_string(resp->GetStatusCode()) + ": " + resp->GetStatusMessage());
×
228
                                return;
×
229
                        }
230

231
                        reader = resp->MakeBodyAsyncReader();
1✔
232
                },
233
                [](http::ExpectedIncomingResponsePtr exp_resp) {
1✔
234
                        if (!exp_resp) {
1✔
235
                                log::Warning("While reading HTTP body: " + exp_resp.error().String());
×
236
                        }
237
                });
3✔
238

239
        // Loop until the headers are received. Then we return and let the reader drive the
240
        // rest of the download.
241
        loop.Run();
1✔
242

243
        if (err != error::NoError) {
1✔
244
                return expected::unexpected(err);
×
245
        }
246

247
        if (inner_err != error::NoError) {
1✔
248
                return expected::unexpected(inner_err);
×
249
        }
250

251
        return make_shared<events::io::ReaderFromAsyncReader>(loop, reader);
2✔
252
}
253

254
ResultAndError Install(context::MenderContext &main_context, const string &src) {
31✔
255
        auto exp_in_progress = LoadStateData(main_context.GetMenderStoreDB());
62✔
256
        if (!exp_in_progress) {
31✔
257
                return {Result::FailedNothingDone, exp_in_progress.error()};
×
258
        }
259
        auto &in_progress = exp_in_progress.value();
31✔
260

261
        if (in_progress) {
31✔
262
                return {
263
                        Result::FailedNothingDone,
264
                        error::Error(
265
                                make_error_condition(errc::operation_in_progress),
1✔
266
                                "Update already in progress. Please commit or roll back first")};
3✔
267
        }
268

269
        io::ReaderPtr artifact_reader;
30✔
270

271
        shared_ptr<events::EventLoop> event_loop;
30✔
272
        http::ClientPtr http_client;
30✔
273

274
        if (src.find("http://") == 0 || src.find("https://") == 0) {
30✔
275
                event_loop = make_shared<events::EventLoop>();
1✔
276
                http::ClientConfig conf;
1✔
277
                http_client = make_shared<http::Client>(conf, *event_loop);
1✔
278
                auto reader = ReaderFromUrl(*event_loop, *http_client, src);
1✔
279
                if (!reader) {
1✔
280
                        return {Result::FailedNothingDone, reader.error()};
×
281
                }
282
                artifact_reader = reader.value();
1✔
283
        } else {
284
                auto stream = io::OpenIfstream(src);
29✔
285
                if (!stream) {
29✔
286
                        return {Result::FailedNothingDone, stream.error()};
×
287
                }
288
                auto file_stream = make_shared<ifstream>(std::move(stream.value()));
29✔
289
                artifact_reader = make_shared<io::StreamReader>(file_stream);
29✔
290
        }
291

292
        artifact::config::ParserConfig config {
293
                paths::DefaultArtScriptsPath,
294
        };
60✔
295
        auto exp_parser = artifact::Parse(*artifact_reader, config);
60✔
296
        if (!exp_parser) {
30✔
297
                return {Result::FailedNothingDone, exp_parser.error()};
×
298
        }
299
        auto &parser = exp_parser.value();
30✔
300

301
        auto exp_header = artifact::View(parser, 0);
60✔
302
        if (!exp_header) {
30✔
303
                return {Result::FailedNothingDone, exp_header.error()};
×
304
        }
305
        auto &header = exp_header.value();
30✔
306

307
        cout << "Installing artifact..." << endl;
30✔
308

309
        if (header.header.payload_type == "") {
30✔
310
                auto data = StateDataFromPayloadHeaderView(header);
2✔
311
                return DoEmptyPayloadArtifact(main_context, data);
1✔
312
        }
313

314
        update_module::UpdateModule update_module(main_context, header.header.payload_type);
58✔
315

316
        auto err =
317
                update_module.CleanAndPrepareFileTree(update_module.GetUpdateModuleWorkDir(), header);
58✔
318
        if (err != error::NoError) {
29✔
319
                err = err.FollowedBy(update_module.Cleanup());
×
320
                return {Result::FailedNothingDone, err};
×
321
        }
322

323
        StateData data = StateDataFromPayloadHeaderView(header);
58✔
324
        err = SaveStateData(main_context.GetMenderStoreDB(), data);
29✔
325
        if (err != error::NoError) {
29✔
326
                err = err.FollowedBy(update_module.Cleanup());
×
327
                return {Result::FailedNothingDone, err};
×
328
        }
329

330
        return DoInstallStates(main_context, data, parser, update_module);
29✔
331
}
332

333
ResultAndError Commit(context::MenderContext &main_context) {
4✔
334
        auto exp_in_progress = LoadStateData(main_context.GetMenderStoreDB());
8✔
335
        if (!exp_in_progress) {
4✔
336
                return {Result::FailedNothingDone, exp_in_progress.error()};
×
337
        }
338
        auto &in_progress = exp_in_progress.value();
4✔
339

340
        if (!in_progress) {
4✔
341
                return {
342
                        Result::NoUpdateInProgress,
343
                        context::MakeError(context::NoUpdateInProgressError, "Cannot commit")};
2✔
344
        }
345
        auto &data = in_progress.value();
3✔
346

347
        update_module::UpdateModule update_module(main_context, data.payload_types[0]);
6✔
348

349
        if (data.payload_types[0] == "rootfs-image") {
3✔
350
                // Special case for rootfs-image upgrades. See comments inside the function.
351
                auto err = update_module.EnsureRootfsImageFileTree(update_module.GetUpdateModuleWorkDir());
3✔
352
                if (err != error::NoError) {
3✔
353
                        return {Result::FailedNothingDone, err};
×
354
                }
355
        }
356

357
        return DoCommit(main_context, data, update_module);
3✔
358
}
359

360
ResultAndError Rollback(context::MenderContext &main_context) {
5✔
361
        auto exp_in_progress = LoadStateData(main_context.GetMenderStoreDB());
10✔
362
        if (!exp_in_progress) {
5✔
363
                return {Result::FailedNothingDone, exp_in_progress.error()};
×
364
        }
365
        auto &in_progress = exp_in_progress.value();
5✔
366

367
        if (!in_progress) {
5✔
368
                return {
369
                        Result::NoUpdateInProgress,
370
                        context::MakeError(context::NoUpdateInProgressError, "Cannot roll back")};
2✔
371
        }
372
        auto &data = in_progress.value();
4✔
373

374
        update_module::UpdateModule update_module(main_context, data.payload_types[0]);
8✔
375

376
        if (data.payload_types[0] == "rootfs-image") {
4✔
377
                // Special case for rootfs-image upgrades. See comments inside the function.
378
                auto err = update_module.EnsureRootfsImageFileTree(update_module.GetUpdateModuleWorkDir());
4✔
379
                if (err != error::NoError) {
4✔
380
                        return {Result::FailedNothingDone, err};
×
381
                }
382
        }
383

384
        auto result = DoRollback(main_context, data, update_module);
8✔
385

386
        if (result.result == Result::NoRollback) {
4✔
387
                // No support for rollback. Return instead of clearing update data. It should be
388
                // cleared by calling commit or restoring the rollback capability.
389
                return result;
1✔
390
        }
391

392
        auto err = update_module.Cleanup();
6✔
393
        if (err != error::NoError) {
3✔
394
                result.result = Result::FailedAndRollbackFailed;
×
395
                result.err = result.err.FollowedBy(err);
×
396
        }
397

398
        if (result.result == Result::RolledBack) {
3✔
399
                err = RemoveStateData(main_context.GetMenderStoreDB());
2✔
400
        } else {
401
                err = CommitBrokenArtifact(main_context, data);
1✔
402
        }
403
        if (err != error::NoError) {
3✔
404
                result.result = Result::RollbackFailed;
×
405
                result.err = result.err.FollowedBy(err);
×
406
        }
407

408
        return result;
3✔
409
}
410

411
ResultAndError DoInstallStates(
29✔
412
        context::MenderContext &main_context,
413
        StateData &data,
414
        artifact::Artifact &artifact,
415
        update_module::UpdateModule &update_module) {
416
        auto payload = artifact.Next();
58✔
417
        if (!payload) {
29✔
418
                return {Result::FailedNothingDone, payload.error()};
×
419
        }
420

421
        auto err = update_module.Download(payload.value());
58✔
422
        if (err != error::NoError) {
29✔
423
                err = err.FollowedBy(update_module.Cleanup());
1✔
424
                err = err.FollowedBy(RemoveStateData(main_context.GetMenderStoreDB()));
1✔
425
                return {Result::FailedNothingDone, err};
1✔
426
        }
427

428
        err = update_module.ArtifactInstall();
28✔
429
        if (err != error::NoError) {
28✔
430
                log::Error("Installation failed: " + err.String());
4✔
431
                return InstallationFailureHandler(main_context, data, update_module);
4✔
432
        }
433

434
        auto reboot = update_module.NeedsReboot();
48✔
435
        if (!reboot) {
24✔
436
                log::Error("Could not query for reboot: " + reboot.error().String());
×
437
                return InstallationFailureHandler(main_context, data, update_module);
×
438
        }
439

440
        auto rollback_support = update_module.SupportsRollback();
48✔
441
        if (!rollback_support) {
24✔
442
                log::Error("Could not query for rollback support: " + rollback_support.error().String());
×
443
                return InstallationFailureHandler(main_context, data, update_module);
×
444
        }
445

446
        if (rollback_support.value()) {
24✔
447
                if (reboot.value() != update_module::RebootAction::No) {
8✔
448
                        return {Result::InstalledRebootRequired, error::NoError};
×
449
                } else {
450
                        return {Result::Installed, error::NoError};
8✔
451
                }
452
        }
453

454
        cout << "Update Module doesn't support rollback. Committing immediately." << endl;
16✔
455

456
        auto result = DoCommit(main_context, data, update_module);
32✔
457
        if (result.result == Result::Committed) {
16✔
458
                if (reboot.value() != update_module::RebootAction::No) {
15✔
459
                        result.result = Result::InstalledAndCommittedRebootRequired;
2✔
460
                } else {
461
                        result.result = Result::InstalledAndCommitted;
13✔
462
                }
463
        }
464
        return result;
16✔
465
}
466

467
ResultAndError DoCommit(
19✔
468
        context::MenderContext &main_context,
469
        StateData &data,
470
        update_module::UpdateModule &update_module) {
471
        auto err = update_module.ArtifactCommit();
38✔
472
        if (err != error::NoError) {
19✔
473
                log::Error("Commit failed: " + err.String());
×
474
                return InstallationFailureHandler(main_context, data, update_module);
×
475
        }
476

477
        auto result = Result::Committed;
19✔
478
        error::Error return_err;
38✔
479

480
        err = update_module.Cleanup();
19✔
481
        if (err != error::NoError) {
19✔
482
                result = Result::InstalledButFailedInPostCommit;
1✔
483
                return_err = return_err.FollowedBy(err);
1✔
484
        }
485

486
        err = main_context.CommitArtifactData(
19✔
487
                data.artifact_name,
19✔
488
                data.artifact_group,
19✔
489
                data.artifact_provides,
19✔
490
                data.artifact_clears_provides,
19✔
491
                [](database::Transaction &txn) {
19✔
492
                        return txn.Remove(context::MenderContext::standalone_state_key);
19✔
493
                });
38✔
494
        if (err != error::NoError) {
19✔
495
                result = Result::InstalledButFailedInPostCommit;
×
496
                return_err = return_err.FollowedBy(err);
×
497
        }
498

499
        return {result, return_err};
19✔
500
}
501

502
ResultAndError DoRollback(
8✔
503
        context::MenderContext &main_context,
504
        StateData &data,
505
        update_module::UpdateModule &update_module) {
506
        auto exp_rollback_support = update_module.SupportsRollback();
16✔
507
        if (!exp_rollback_support) {
8✔
508
                return {Result::NoRollback, exp_rollback_support.error()};
×
509
        }
510

511
        if (exp_rollback_support.value()) {
8✔
512
                auto err = update_module.ArtifactRollback();
12✔
513
                if (err != error::NoError) {
6✔
514
                        return {Result::RollbackFailed, err};
2✔
515
                }
516

517
                return {Result::RolledBack, error::NoError};
4✔
518
        } else {
519
                return {Result::NoRollback, error::NoError};
2✔
520
        }
521
}
522

523
ResultAndError DoEmptyPayloadArtifact(context::MenderContext &main_context, StateData &data) {
1✔
524
        cout << "Artifact with empty payload. Committing immediately." << endl;
1✔
525

526
        auto err = main_context.CommitArtifactData(
527
                data.artifact_name,
1✔
528
                data.artifact_group,
1✔
529
                data.artifact_provides,
1✔
530
                data.artifact_clears_provides,
1✔
531
                [](database::Transaction &txn) { return error::NoError; });
4✔
532
        if (err != error::NoError) {
1✔
533
                return {Result::InstalledButFailedInPostCommit, err};
×
534
        }
535
        return {Result::InstalledAndCommitted, err};
1✔
536
}
537

538
ResultAndError InstallationFailureHandler(
4✔
539
        context::MenderContext &main_context,
540
        StateData &data,
541
        update_module::UpdateModule &update_module) {
542
        error::Error err;
8✔
543

544
        auto result = DoRollback(main_context, data, update_module);
8✔
545
        switch (result.result) {
4✔
546
        case Result::RolledBack:
2✔
547
                result.result = Result::FailedAndRolledBack;
2✔
548
                break;
2✔
549
        case Result::NoRollback:
1✔
550
                result.result = Result::FailedAndNoRollback;
1✔
551
                break;
1✔
552
        case Result::RollbackFailed:
1✔
553
                result.result = Result::FailedAndRollbackFailed;
1✔
554
                break;
1✔
555
        default:
×
556
                // Should not happen.
557
                assert(false);
×
558
                return {
559
                        Result::FailedAndRollbackFailed,
560
                        error::MakeError(
561
                                error::ProgrammingError,
562
                                "Unexpected result in InstallationFailureHandler. This is a bug.")};
563
        }
564

565
        err = update_module.ArtifactFailure();
4✔
566
        if (err != error::NoError) {
4✔
567
                result.result = Result::FailedAndRollbackFailed;
1✔
568
                result.err = result.err.FollowedBy(err);
1✔
569
        }
570

571
        err = update_module.Cleanup();
4✔
572
        if (err != error::NoError) {
4✔
573
                result.result = Result::FailedAndRollbackFailed;
×
574
                result.err = result.err.FollowedBy(err);
×
575
        }
576

577
        if (result.result == Result::FailedAndRolledBack) {
4✔
578
                err = RemoveStateData(main_context.GetMenderStoreDB());
1✔
579
        } else {
580
                err = CommitBrokenArtifact(main_context, data);
3✔
581
        }
582
        if (err != error::NoError) {
4✔
583
                result.result = Result::FailedAndRollbackFailed;
×
584
                result.err = result.err.FollowedBy(err);
×
585
        }
586

587
        return result;
4✔
588
}
589

590
error::Error CommitBrokenArtifact(context::MenderContext &main_context, StateData &data) {
4✔
591
        data.artifact_name += main_context.broken_artifact_name_suffix;
4✔
592
        if (data.artifact_provides) {
4✔
593
                data.artifact_provides.value()["artifact_name"] = data.artifact_name;
4✔
594
        }
595
        return main_context.CommitArtifactData(
596
                data.artifact_name,
4✔
597
                data.artifact_group,
4✔
598
                data.artifact_provides,
4✔
599
                data.artifact_clears_provides,
4✔
600
                [](database::Transaction &txn) {
4✔
601
                        return txn.Remove(context::MenderContext::standalone_state_key);
4✔
602
                });
4✔
603
}
604

605
} // namespace standalone
606
} // namespace update
607
} // namespace mender
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