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

mendersoftware / mender / 1421288278

21 Aug 2024 09:30AM UTC coverage: 76.154%. First build
1421288278

push

gitlab-ci

kacf
feat: Expand `--stop-after` functionality.

Changelog: Adjust states allowed for the `--stop-after` flag.
These are the allowed states:
* `Download_Leave`
* `ArtifactInstall_Leave`
* `ArtifactInstall_Error`
* `ArtifactCommit` (exits before `ArtifactCommit_Leave`)
* `ArtifactCommit_Error`
* `ArtifactRollback_Leave`
* `ArtifactFailure_Leave`

Changelog: Add `--stop-after` flag to `commit` and `rollback`
commands.

Changelog: Allow `--stop-after` flag to be specified multiple times.

Ticket: MEN-7115

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

145 of 167 new or added lines in 4 files covered. (86.83%)

7377 of 9687 relevant lines covered (76.15%)

11284.59 hits per line

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

78.45
/src/mender-update/standalone/states.cpp
1
// Copyright 2024 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/states.hpp>
16

17
#include <common/http.hpp>
18
#include <common/events_io.hpp>
19
#include <common/io.hpp>
20
#include <common/key_value_database.hpp>
21
#include <common/log.hpp>
22
#include <common/path.hpp>
23

24
#include <mender-update/standalone.hpp>
25

26
namespace mender {
27
namespace update {
28
namespace standalone {
29

30
namespace database = mender::common::key_value_database;
31
namespace events = mender::common::events;
32
namespace http = mender::common::http;
33
namespace io = mender::common::io;
34
namespace log = mender::common::log;
35
namespace path = mender::common::path;
36

37
// This is used to catch mistakes where we don't set the error before exiting the state machine.
38
static const error::Error kFallbackError = error::MakeError(
39
        error::ProgrammingError, "Returned from standalone operation without setting error code.");
40

41
static void UpdateResult(ResultAndError &result, const ResultAndError &update) {
247✔
42
        if (result.err == kFallbackError or result.err == error::NoError) {
247✔
43
                result.err = update.err;
201✔
44
        } else {
45
                result.err = result.err.FollowedBy(update.err);
92✔
46
        }
47

48
        result.result = result.result | update.result;
247✔
49
}
247✔
50

51
void SaveState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
245✔
52
        ctx.state_data.in_state = state_;
245✔
53

54
        if (ResultContains(ctx.result_and_error.result, Result::Failed)) {
245✔
55
                ctx.state_data.failed = true;
35✔
56
        }
57
        if (ResultContains(ctx.result_and_error.result, Result::RolledBack)
58
                or ResultContains(ctx.result_and_error.result, Result::NoRollbackNecessary)) {
245✔
59
                ctx.state_data.rolled_back = true;
19✔
60
        }
61
        if (ResultContains(ctx.result_and_error.result, Result::RollbackFailed)) {
245✔
62
                ctx.state_data.rolled_back = false;
9✔
63
        }
64

65
        auto err = SaveStateData(ctx.main_context.GetMenderStoreDB(), ctx.state_data);
245✔
66
        if (err != error::NoError) {
245✔
67
                UpdateResult(ctx.result_and_error, {Result::Failed, err});
×
68
                poster.PostEvent(StateEvent::Failure);
×
69
                return;
70
        }
71

72
        poster.PostEvent(StateEvent::Success);
245✔
73
}
74

75
error::Error DoEmptyPayloadArtifact(Context &ctx) {
4✔
76
        if (ctx.options != InstallOptions::NoStdout) {
4✔
77
                cout << "Installing artifact..." << endl;
1✔
78
                cout << "Artifact with empty payload. Committing immediately." << endl;
1✔
79
        }
80

81
        auto &data = ctx.state_data;
82
        return ctx.main_context.CommitArtifactData(
4✔
83
                data.artifact_name,
4✔
84
                data.artifact_group,
4✔
85
                data.artifact_provides,
4✔
86
                data.artifact_clears_provides,
4✔
87
                [](database::Transaction &txn) { return error::NoError; });
12✔
88
}
89

90
static io::ExpectedReaderPtr ReaderFromUrl(
1✔
91
        events::EventLoop &loop, http::Client &http_client, const string &src) {
92
        auto req = make_shared<http::OutgoingRequest>();
1✔
93
        req->SetMethod(http::Method::GET);
1✔
94
        auto err = req->SetAddress(src);
1✔
95
        if (err != error::NoError) {
1✔
96
                return expected::unexpected(err);
×
97
        }
98
        error::Error inner_err;
1✔
99
        io::AsyncReaderPtr reader;
1✔
100
        err = http_client.AsyncCall(
1✔
101
                req,
102
                [&loop, &inner_err, &reader](http::ExpectedIncomingResponsePtr exp_resp) {
1✔
103
                        // No matter what happens, we will want to stop the loop after the headers
104
                        // are received.
105
                        loop.Stop();
1✔
106

107
                        if (!exp_resp) {
1✔
108
                                inner_err = exp_resp.error();
×
109
                                return;
×
110
                        }
111

112
                        auto resp = exp_resp.value();
1✔
113

114
                        if (resp->GetStatusCode() != http::StatusOK) {
1✔
115
                                inner_err = context::MakeError(
×
116
                                        context::UnexpectedHttpResponse,
117
                                        to_string(resp->GetStatusCode()) + ": " + resp->GetStatusMessage());
×
118
                                return;
×
119
                        }
120

121
                        auto exp_reader = resp->MakeBodyAsyncReader();
1✔
122
                        if (!exp_reader) {
1✔
123
                                inner_err = exp_reader.error();
×
124
                                return;
125
                        }
126
                        reader = exp_reader.value();
1✔
127
                },
128
                [](http::ExpectedIncomingResponsePtr exp_resp) {
1✔
129
                        // Note: Since we stop the event loop above, this handler will not be called
130
                        // while we are inside the `ReaderFromUrl` stack frame. It will be called
131
                        // later though, when the reader that we return has finished reading
132
                        // everything (which includes resuming the loop). So be careful with
133
                        // captures in this handler.
134
                        if (!exp_resp) {
1✔
135
                                log::Warning("While reading HTTP body: " + exp_resp.error().String());
×
136
                        }
137
                });
4✔
138

139
        // Loop until the headers are received. Then we return and let the reader drive the
140
        // rest of the download.
141
        loop.Run();
1✔
142

143
        if (err != error::NoError) {
1✔
144
                return expected::unexpected(err);
×
145
        }
146

147
        if (inner_err != error::NoError) {
1✔
148
                return expected::unexpected(inner_err);
×
149
        }
150

151
        // Should not happen since we have checked both `err` and `inner_err`, but just to be safe.
152
        AssertOrReturnUnexpected(reader != nullptr);
1✔
153

154
        return make_shared<events::io::ReaderFromAsyncReader>(loop, reader);
2✔
155
}
156

157
StateData StateDataFromPayloadHeaderView(const artifact::PayloadHeaderView &header) {
62✔
158
        StateData dst;
62✔
159
        dst.version = context::MenderContext::standalone_data_version;
62✔
160
        dst.artifact_name = header.header.artifact_name;
62✔
161
        dst.artifact_group = header.header.artifact_group;
62✔
162
        dst.artifact_provides = header.header.type_info.artifact_provides;
163
        dst.artifact_clears_provides = header.header.type_info.clears_artifact_provides;
164
        dst.payload_types.clear();
62✔
165
        dst.payload_types.push_back(header.header.payload_type);
62✔
166
        return dst;
62✔
167
}
168

169
void PrepareDownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
62✔
170
        auto &main_context = ctx.main_context;
62✔
171

172
        if (ctx.artifact_src.find("http://") == 0 || ctx.artifact_src.find("https://") == 0) {
62✔
173
                ctx.http_client =
174
                        make_shared<http::Client>(main_context.GetConfig().GetHttpClientConfig(), ctx.loop);
2✔
175
                auto reader = ReaderFromUrl(ctx.loop, *ctx.http_client, ctx.artifact_src);
1✔
176
                if (!reader) {
1✔
177
                        UpdateResult(
×
178
                                ctx.result_and_error,
×
179
                                {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
180
                                 reader.error()});
181
                        poster.PostEvent(StateEvent::Failure);
×
182
                        return;
183
                }
184
                ctx.artifact_reader = reader.value();
1✔
185
        } else {
186
                auto stream = io::OpenIfstream(ctx.artifact_src);
61✔
187
                if (!stream) {
61✔
188
                        UpdateResult(
×
189
                                ctx.result_and_error,
×
190
                                {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
191
                                 stream.error()});
192
                        poster.PostEvent(StateEvent::Failure);
×
193
                        return;
194
                }
195
                auto file_stream = make_shared<ifstream>(std::move(stream.value()));
61✔
196
                ctx.artifact_reader = make_shared<io::StreamReader>(file_stream);
122✔
197
        }
198

199
        string art_scripts_path = main_context.GetConfig().paths.GetArtScriptsPath();
200

201
        // Clear the artifact scripts directory so we don't risk old scripts lingering.
202
        auto err = path::DeleteRecursively(art_scripts_path);
62✔
203
        if (err != error::NoError) {
62✔
204
                UpdateResult(
×
205
                        ctx.result_and_error,
×
206
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
207
                         err.WithContext("When preparing to parse artifact")});
×
208
                poster.PostEvent(StateEvent::Failure);
×
209
                return;
210
        }
211

212
        artifact::config::ParserConfig config {
62✔
213
                .artifact_scripts_filesystem_path = main_context.GetConfig().paths.GetArtScriptsPath(),
214
                .artifact_scripts_version = 3,
215
                .artifact_verify_keys = main_context.GetConfig().artifact_verify_keys,
62✔
216
                .verify_signature = ctx.verify_signature,
62✔
217
        };
119✔
218

219
        auto exp_parser = artifact::Parse(*ctx.artifact_reader, config);
124✔
220
        if (!exp_parser) {
62✔
221
                UpdateResult(
×
222
                        ctx.result_and_error,
×
223
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
224
                         exp_parser.error()});
225
                poster.PostEvent(StateEvent::Failure);
×
226
                return;
227
        }
228
        ctx.parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
62✔
229

230
        auto exp_header = artifact::View(*ctx.parser, 0);
62✔
231
        if (!exp_header) {
62✔
232
                UpdateResult(
×
233
                        ctx.result_and_error,
×
234
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
235
                         exp_header.error()});
236
                poster.PostEvent(StateEvent::Failure);
×
237
                return;
238
        }
239
        auto &header = exp_header.value();
62✔
240

241
        ctx.state_data = StateDataFromPayloadHeaderView(header);
62✔
242

243
        if (header.header.payload_type == "") {
62✔
244
                err = DoEmptyPayloadArtifact(ctx);
4✔
245
                if (err != error::NoError) {
4✔
246
                        UpdateResult(
×
247
                                ctx.result_and_error,
×
248
                                {Result::DownloadFailed | Result::Failed | Result::FailedInPostCommit, err});
249
                        poster.PostEvent(StateEvent::Failure);
×
250
                        return;
251
                }
252
                UpdateResult(
4✔
253
                        ctx.result_and_error,
4✔
254
                        {Result::Downloaded | Result::Installed | Result::Committed, error::NoError});
255
                poster.PostEvent(StateEvent::EmptyPayloadArtifact);
4✔
256
                return;
257
        }
258

259
        ctx.update_module.reset(
260
                new update_module::UpdateModule(main_context, header.header.payload_type));
58✔
261

262
        err = ctx.update_module->CleanAndPrepareFileTree(
58✔
263
                ctx.update_module->GetUpdateModuleWorkDir(), header);
58✔
264
        if (err != error::NoError) {
58✔
265
                UpdateResult(
×
266
                        ctx.result_and_error,
×
267
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary, err});
268
                poster.PostEvent(StateEvent::Failure);
×
269
                return;
270
        }
271

272
        if (ctx.options != InstallOptions::NoStdout) {
58✔
273
                cout << "Installing artifact..." << endl;
58✔
274
        }
275

276
        auto exp_matches = main_context.MatchesArtifactDepends(header.header);
58✔
277
        if (!exp_matches) {
58✔
278
                UpdateResult(
×
279
                        ctx.result_and_error,
×
280
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
281
                         exp_matches.error()});
282
                poster.PostEvent(StateEvent::Failure);
×
283
                return;
284
        } else if (!exp_matches.value()) {
58✔
285
                // reasons already logged
286
                UpdateResult(
1✔
287
                        ctx.result_and_error,
1✔
288
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
289
                         error::NoError});
290
                poster.PostEvent(StateEvent::Failure);
1✔
291
                return;
292
        }
293

294
        poster.PostEvent(StateEvent::Success);
57✔
295
}
296

297
error::Error DoDownloadState(Context &ctx) {
55✔
298
        auto payload = ctx.parser->Next();
55✔
299
        if (!payload) {
55✔
300
                return payload.error();
×
301
        }
302

303
        // ProvidePayloadFileSizes
304
        auto with_sizes = ctx.update_module->ProvidePayloadFileSizes();
55✔
305
        if (!with_sizes) {
55✔
306
                log::Error("Could not query for provide file sizes: " + with_sizes.error().String());
×
307
                return with_sizes.error();
×
308
        }
309

310
        error::Error err;
55✔
311
        if (with_sizes.value()) {
55✔
312
                err = ctx.update_module->DownloadWithFileSizes(payload.value());
2✔
313
        } else {
314
                err = ctx.update_module->Download(payload.value());
108✔
315
        }
316
        if (err != error::NoError) {
55✔
317
                return err;
2✔
318
        }
319

320
        payload = ctx.parser->Next();
106✔
321
        if (payload) {
53✔
322
                err = error::Error(
×
323
                        make_error_condition(errc::not_supported),
×
324
                        "Multiple payloads are not supported in standalone mode");
×
325
        } else if (
53✔
326
                payload.error().code
327
                != artifact::parser_error::MakeError(artifact::parser_error::EOFError, "").code) {
53✔
328
                err = payload.error();
×
329
        }
330
        if (err != error::NoError) {
53✔
331
                return err;
×
332
        }
333

334
        return error::NoError;
53✔
335
}
336

337
void DownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
55✔
338
        auto err = DoDownloadState(ctx);
55✔
339
        if (err != error::NoError) {
55✔
340
                log::Error("Streaming failed: " + err.String());
4✔
341
                UpdateResult(
2✔
342
                        ctx.result_and_error,
2✔
343
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary, err});
344
                poster.PostEvent(StateEvent::Failure);
2✔
345
                return;
346
        }
347

348
        UpdateResult(ctx.result_and_error, {Result::Downloaded, error::NoError});
53✔
349
        poster.PostEvent(StateEvent::Success);
53✔
350
}
351

352
void ArtifactInstallState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
51✔
353
        auto err = ctx.update_module->ArtifactInstall();
51✔
354
        if (err != error::NoError) {
51✔
355
                log::Error("Installation failed: " + err.String());
12✔
356
                UpdateResult(ctx.result_and_error, {Result::InstallFailed | Result::Failed, err});
6✔
357
                poster.PostEvent(StateEvent::Failure);
6✔
358
                return;
359
        }
360

361
        UpdateResult(ctx.result_and_error, {Result::Installed, error::NoError});
45✔
362
        poster.PostEvent(StateEvent::Success);
45✔
363
}
364

365
void RebootAndRollbackQueryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
42✔
366
        auto reboot = ctx.update_module->NeedsReboot();
42✔
367
        if (!reboot) {
42✔
368
                log::Error("Could not query for reboot: " + reboot.error().String());
×
369
                UpdateResult(ctx.result_and_error, {Result::Failed, reboot.error()});
×
370
                poster.PostEvent(StateEvent::Failure);
×
371
                return;
372
        }
373

374
        if (reboot.value() != update_module::RebootAction::No) {
42✔
375
                UpdateResult(ctx.result_and_error, {Result::RebootRequired, error::NoError});
4✔
376
        }
377

378
        auto rollback_support = ctx.update_module->SupportsRollback();
42✔
379
        if (!rollback_support) {
42✔
380
                log::Error("Could not query for rollback support: " + rollback_support.error().String());
×
381
                UpdateResult(ctx.result_and_error, {Result::Failed, rollback_support.error()});
×
382
                poster.PostEvent(StateEvent::Failure);
×
383
                return;
384
        }
385

386
        if (rollback_support.value()) {
42✔
387
                poster.PostEvent(StateEvent::NeedsInteraction);
10✔
388
                return;
389
        }
390

391
        cout << "Update Module doesn't support rollback. Committing immediately." << endl;
32✔
392
        poster.PostEvent(StateEvent::Success);
32✔
393
}
394

395
void ArtifactCommitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
35✔
396
        auto err = ctx.update_module->ArtifactCommit();
35✔
397
        if (err != error::NoError) {
35✔
398
                log::Error("Commit failed: " + err.String());
2✔
399
                UpdateResult(ctx.result_and_error, {Result::CommitFailed | Result::Failed, err});
1✔
400
                poster.PostEvent(StateEvent::Failure);
1✔
401
                return;
402
        }
403

404
        UpdateResult(ctx.result_and_error, {Result::Committed, error::NoError});
34✔
405
        poster.PostEvent(StateEvent::Success);
34✔
406
}
407

408
void RollbackQueryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
409
        auto rollback_support = ctx.update_module->SupportsRollback();
19✔
410
        if (!rollback_support) {
19✔
411
                log::Error("Could not query for rollback support: " + rollback_support.error().String());
×
412
                UpdateResult(
×
413
                        ctx.result_and_error,
×
414
                        {Result::Failed | Result::RollbackFailed, rollback_support.error()});
415
                poster.PostEvent(StateEvent::Failure);
×
416
                return;
417
        }
418

419
        if (not rollback_support.value()) {
19✔
420
                bool already_failed = ResultContains(ctx.result_and_error.result, Result::Failed);
9✔
421
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::NoRollback, error::NoError});
9✔
422
                if (already_failed) {
9✔
423
                        poster.PostEvent(StateEvent::NothingToDo);
8✔
424
                } else {
425
                        // If it hadn't failed already, it's because the user asked for the rollback
426
                        // explicitly. In this case bail out instead of continuing.
427
                        poster.PostEvent(StateEvent::NeedsInteraction);
1✔
428
                }
429
                return;
9✔
430
        }
431

432
        poster.PostEvent(StateEvent::Success);
10✔
433
}
434

435
void ArtifactRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
9✔
436
        auto err = ctx.update_module->ArtifactRollback();
9✔
437
        if (err != error::NoError) {
9✔
438
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::RollbackFailed, err});
2✔
439
                poster.PostEvent(StateEvent::Failure);
2✔
440
                return;
441
        }
442

443
        UpdateResult(ctx.result_and_error, {Result::RolledBack, error::NoError});
7✔
444
        poster.PostEvent(StateEvent::Success);
7✔
445
}
446

447
void ArtifactFailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
17✔
448
        auto err = ctx.update_module->ArtifactFailure();
17✔
449
        if (err != error::NoError) {
17✔
450
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::RollbackFailed, err});
1✔
451
                poster.PostEvent(StateEvent::Failure);
1✔
452
                return;
453
        }
454

455
        poster.PostEvent(StateEvent::Success);
16✔
456
}
457

458
void CleanupState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
55✔
459
        auto final_event = StateEvent::Success;
460

461
        auto &data = ctx.state_data;
462

463
        // If this is null, then it is simply a no-op, the update did not even get started.
464
        if (ctx.update_module != nullptr) {
55✔
465
                auto err = ctx.update_module->Cleanup();
55✔
466
                if (err != error::NoError) {
55✔
467
                        UpdateResult(ctx.result_and_error, {Result::Failed | Result::CleanupFailed, err});
1✔
468
                        final_event = StateEvent::Failure;
469
                        data.failed = true;
1✔
470
                        // Fall through so that we update the DB.
471
                }
472
        }
473

474
        error::Error err;
55✔
475
        if (data.rolled_back) {
55✔
476
                // Successful rollback.
477
                auto &db = ctx.main_context.GetMenderStoreDB();
11✔
478
                err = db.Remove(context::MenderContext::standalone_state_key);
22✔
479
        } else {
480
                if (data.failed) {
44✔
481
                        // Unsuccessful rollback or missing rollback support.
482
                        data.artifact_name += ctx.main_context.broken_artifact_name_suffix;
14✔
483
                        if (data.artifact_provides) {
14✔
484
                                data.artifact_provides.value()["artifact_name"] = data.artifact_name;
28✔
485
                        }
486
                        // Fall through to success case.
487
                }
488
                // Commit artifact data and remove state data
489
                err = ctx.main_context.CommitArtifactData(
88✔
490
                        data.artifact_name,
44✔
491
                        data.artifact_group,
44✔
492
                        data.artifact_provides,
44✔
493
                        data.artifact_clears_provides,
44✔
494
                        [](database::Transaction &txn) {
44✔
495
                                return txn.Remove(context::MenderContext::standalone_state_key);
44✔
496
                        });
88✔
497
        }
498
        if (err != error::NoError) {
55✔
499
                err = err.WithContext("Error while updating database");
×
500
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::RollbackFailed, err});
×
501
                poster.PostEvent(StateEvent::Failure);
×
502
                return;
503
        }
504

505
        UpdateResult(ctx.result_and_error, {Result::Cleaned, error::NoError});
55✔
506
        poster.PostEvent(final_event);
55✔
507
}
508

509
void ScriptRunnerState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
347✔
510
        auto err = ctx.script_runner->RunScripts(state_, action_, on_error_);
347✔
511
        if (err != error::NoError) {
347✔
512
                log::Error("Error executing script: " + err.String());
48✔
513
                UpdateResult(ctx.result_and_error, {result_on_error_, err});
24✔
514
                poster.PostEvent(StateEvent::Failure);
24✔
515
                return;
516
        }
517

518
        poster.PostEvent(StateEvent::Success);
323✔
519
}
520

521
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
80✔
522
        auto err =
523
                ctx.main_context.GetMenderStoreDB().WriteTransaction([&ctx](database::Transaction &txn) {
80✔
524
                        auto exp_bytes = txn.Read(context::MenderContext::standalone_state_key);
80✔
525
                        if (!exp_bytes) {
80✔
526
                                if (exp_bytes.error().code == database::MakeError(database::KeyError, "").code) {
60✔
527
                                        // If the stata data is not saved, just do nothing here.
528
                                        return error::NoError;
60✔
529
                                } else {
NEW
530
                                        return exp_bytes.error();
×
531
                                }
532
                        }
533

534
                        // If there is state data, resave it with `failed` set to false. The rationale
535
                        // behind this is that if we have already recorded failure for this run, it will be
536
                        // returned in the error code. That does not mean that we should record error for
537
                        // the next run, which is independent. An example is rollback, if we are somewhere
538
                        // in the rollback flow, we are likely to have a failure here, because the *install*
539
                        // failed. But when we now exit, and then later resume the rollback, the rollback
540
                        // should return success, not failure.
541
                        ctx.state_data.failed = false;
20✔
542
                        return SaveStateData(txn, ctx.state_data);
20✔
543
                });
80✔
544
        if (err != error::NoError) {
80✔
NEW
545
                UpdateResult(ctx.result_and_error, {Result::Failed, err});
×
NEW
546
                poster.PostEvent(StateEvent::Failure);
×
547
                return;
548
        }
549

550
        loop_.Stop();
80✔
551
}
552

553
} // namespace standalone
554
} // namespace update
555
} // 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