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

mendersoftware / mender / 2281564137

23 Jan 2026 10:59AM UTC coverage: 81.48% (+1.7%) from 79.764%
2281564137

push

gitlab-ci

michalkopczan
fix: Schedule next deployment poll if current one failed early causing no handler to be called

Ticket: MEN-9144
Changelog: Fix a hang when polling for deployment failed early causing no handler of API response
to be called. Added handler call for this case, causing the deployment polling
to continue.

Signed-off-by: Michal Kopczan <michal.kopczan@northern.tech>

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

327 existing lines in 44 files now uncovered.

8839 of 10848 relevant lines covered (81.48%)

20226.53 hits per line

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

77.67
/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) {
280✔
42
        if (result.err == kFallbackError or result.err == error::NoError) {
280✔
43
                result.err = update.err;
234✔
44
        } else {
45
                result.err = result.err.FollowedBy(update.err);
92✔
46
        }
47

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

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

54
        if (ResultContains(ctx.result_and_error.result, Result::Failed)) {
386✔
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)) {
386✔
59
                ctx.state_data.rolled_back = true;
23✔
60
        }
61
        if (ResultContains(ctx.result_and_error.result, Result::RollbackFailed)) {
386✔
62
                ctx.state_data.rolled_back = false;
9✔
63
        }
64

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

72
        OnEnterSaveState(ctx, poster);
386✔
UNCOV
73
}
×
74

75
void JustSaveState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
342✔
76
        // Nothing other than saving, which has already happened.
77
        poster.PostEvent(StateEvent::Success);
342✔
78
}
342✔
79

80
error::Error DoEmptyPayloadArtifact(Context &ctx) {
4✔
81
        if (ctx.options != InstallOptions::NoStdout) {
4✔
82
                cout << "Installing artifact..." << endl;
1✔
83
                cout << "Artifact with empty payload. Committing immediately." << endl;
1✔
84
        }
85

86
        auto &data = ctx.state_data;
87
        return ctx.main_context.CommitArtifactData(
8✔
88
                data.artifact_name,
4✔
89
                data.artifact_group,
4✔
90
                data.artifact_provides,
4✔
91
                data.artifact_clears_provides,
4✔
92
                [](database::Transaction &txn) { return error::NoError; });
12✔
93
}
94

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

112
                        if (!exp_resp) {
1✔
113
                                inner_err = exp_resp.error();
×
114
                                return;
×
115
                        }
116

117
                        auto resp = exp_resp.value();
1✔
118

119
                        if (resp->GetStatusCode() != http::StatusOK) {
1✔
120
                                inner_err = context::MakeError(
×
121
                                        context::UnexpectedHttpResponse,
122
                                        to_string(resp->GetStatusCode()) + ": " + resp->GetStatusMessage());
×
123
                                return;
×
124
                        }
125

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

144
        // Loop until the headers are received. Then we return and let the reader drive the
145
        // rest of the download.
146
        loop.Run();
1✔
147

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

152
        if (inner_err != error::NoError) {
1✔
153
                return expected::unexpected(inner_err);
×
154
        }
155

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

159
        return make_shared<events::io::ReaderFromAsyncReader>(loop, reader);
2✔
160
}
161

162
StateData StateDataFromPayloadHeaderView(const artifact::PayloadHeaderView &header) {
62✔
163
        StateData dst;
62✔
164
        dst.version = context::MenderContext::standalone_data_version;
62✔
165
        dst.artifact_name = header.header.artifact_name;
62✔
166
        dst.artifact_group = header.header.artifact_group;
62✔
167
        dst.artifact_provides = header.header.type_info.artifact_provides;
168
        dst.artifact_clears_provides = header.header.type_info.clears_artifact_provides;
169
        dst.payload_types.clear();
62✔
170
        dst.payload_types.push_back(header.header.payload_type);
62✔
171
        return dst;
62✔
UNCOV
172
}
×
173

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

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

204
        string art_scripts_path = main_context.GetConfig().paths.GetArtScriptsPath();
205

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

217
        artifact::config::ParserConfig config {
62✔
218
                .artifact_scripts_filesystem_path = main_context.GetConfig().paths.GetArtScriptsPath(),
219
                .artifact_scripts_version = 3,
220
                .artifact_verify_keys = main_context.GetConfig().artifact_verify_keys,
62✔
221
                .verify_signature = ctx.verify_signature,
62✔
222
        };
62✔
223

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

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

246
        ctx.state_data = StateDataFromPayloadHeaderView(header);
62✔
247

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

264
        auto exp_update_module =
265
                update_module::UpdateModule::Create(main_context, header.header.payload_type);
58✔
266
        if (!exp_update_module.has_value()) {
58✔
267
                UpdateResult(
×
268
                        ctx.result_and_error,
×
269
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
270
                         exp_update_module.error()});
271
                poster.PostEvent(StateEvent::Failure);
×
272
                return;
273
        }
274
        ctx.update_module = std::move(exp_update_module.value());
58✔
275

276
        err = ctx.update_module->CleanAndPrepareFileTree(
58✔
277
                ctx.update_module->GetUpdateModuleWorkDir(), header);
58✔
278
        if (err != error::NoError) {
58✔
279
                UpdateResult(
×
280
                        ctx.result_and_error,
×
281
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary, err});
282
                poster.PostEvent(StateEvent::Failure);
×
283
                return;
284
        }
285

286
        if (ctx.options != InstallOptions::NoStdout) {
58✔
287
                cout << "Installing artifact..." << endl;
58✔
288
        }
289

290
        auto exp_matches = main_context.MatchesArtifactDepends(header.header);
58✔
291
        if (!exp_matches) {
58✔
292
                UpdateResult(
×
293
                        ctx.result_and_error,
×
294
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
295
                         exp_matches.error()});
296
                poster.PostEvent(StateEvent::Failure);
×
297
                return;
298
        } else if (!exp_matches.value()) {
58✔
299
                // reasons already logged
UNCOV
300
                UpdateResult(
×
301
                        ctx.result_and_error,
1✔
302
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary,
303
                         error::NoError});
304
                poster.PostEvent(StateEvent::Failure);
1✔
305
                return;
306
        }
307

308
        poster.PostEvent(StateEvent::Success);
57✔
309
}
67✔
310

311
error::Error DoDownloadState(Context &ctx) {
55✔
312
        auto payload = ctx.parser->Next();
55✔
313
        if (!payload) {
55✔
314
                return payload.error();
×
315
        }
316

317
        // ProvidePayloadFileSizes
318
        auto with_sizes = ctx.update_module->ProvidePayloadFileSizes();
55✔
319
        if (!with_sizes) {
55✔
320
                log::Error("Could not query for provide file sizes: " + with_sizes.error().String());
×
321
                return with_sizes.error();
×
322
        }
323

324
        error::Error err;
55✔
325
        if (with_sizes.value()) {
55✔
326
                err = ctx.update_module->DownloadWithFileSizes(payload.value());
2✔
327
        } else {
328
                err = ctx.update_module->Download(payload.value());
108✔
329
        }
330
        if (err != error::NoError) {
55✔
331
                return err;
2✔
332
        }
333

334
        payload = ctx.parser->Next();
106✔
335
        if (payload) {
53✔
336
                err = error::Error(
×
337
                        make_error_condition(errc::not_supported),
×
338
                        "Multiple payloads are not supported in standalone mode");
×
339
        } else if (
53✔
340
                payload.error().code
341
                != artifact::parser_error::MakeError(artifact::parser_error::EOFError, "").code) {
106✔
342
                err = payload.error();
×
343
        }
344
        if (err != error::NoError) {
53✔
345
                return err;
×
346
        }
347

348
        return error::NoError;
53✔
349
}
350

351
void DownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
55✔
352
        auto err = DoDownloadState(ctx);
55✔
353
        if (err != error::NoError) {
55✔
354
                log::Error("Streaming failed: " + err.String());
2✔
355
                UpdateResult(
4✔
356
                        ctx.result_and_error,
2✔
357
                        {Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary, err});
358
                poster.PostEvent(StateEvent::Failure);
2✔
359
                return;
360
        }
361

362
        UpdateResult(ctx.result_and_error, {Result::Downloaded, error::NoError});
53✔
363
        poster.PostEvent(StateEvent::Success);
53✔
364
}
55✔
365

366
void ArtifactInstallState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
51✔
367
        auto err = ctx.update_module->ArtifactInstall();
51✔
368
        if (err != error::NoError) {
51✔
369
                log::Error("Installation failed: " + err.String());
6✔
370
                UpdateResult(ctx.result_and_error, {Result::InstallFailed | Result::Failed, err});
6✔
371
                poster.PostEvent(StateEvent::Failure);
6✔
372
                return;
373
        }
374

375
        UpdateResult(ctx.result_and_error, {Result::Installed, error::NoError});
45✔
376
        poster.PostEvent(StateEvent::Success);
45✔
377
}
51✔
378

379
RebootAndRollbackQueryState::RebootAndRollbackQueryState() :
174✔
380
        SaveState(StateData::kBeforeStateArtifactCommit_Enter) {
87✔
381
}
174✔
382

383
void RebootAndRollbackQueryState::OnEnterSaveState(
44✔
384
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
385
        auto reboot = ctx.update_module->NeedsReboot();
44✔
386
        if (!reboot) {
44✔
387
                log::Error("Could not query for reboot: " + reboot.error().String());
×
388
                UpdateResult(ctx.result_and_error, {Result::Failed, reboot.error()});
×
389
                poster.PostEvent(StateEvent::Failure);
×
390
                return;
391
        }
392

393
        if (reboot.value() != update_module::RebootAction::No) {
44✔
394
                UpdateResult(ctx.result_and_error, {Result::RebootRequired, error::NoError});
4✔
395
        }
396

397
        auto rollback_support = ctx.update_module->SupportsRollback();
44✔
398
        if (!rollback_support) {
44✔
399
                log::Error("Could not query for rollback support: " + rollback_support.error().String());
×
400
                UpdateResult(ctx.result_and_error, {Result::Failed, rollback_support.error()});
×
401
                poster.PostEvent(StateEvent::Failure);
×
402
                return;
403
        }
404

405
        if (rollback_support.value()) {
44✔
406
                poster.PostEvent(StateEvent::NeedsInteraction);
11✔
407
                return;
408
        } else {
409
                UpdateResult(ctx.result_and_error, {Result::AutoCommitWanted});
33✔
410
        }
411

412
        poster.PostEvent(StateEvent::Success);
33✔
413
}
35✔
414

415
void ArtifactCommitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
35✔
416
        auto err = ctx.update_module->ArtifactCommit();
35✔
417
        if (err != error::NoError) {
35✔
418
                log::Error("Commit failed: " + err.String());
1✔
419
                UpdateResult(ctx.result_and_error, {Result::CommitFailed | Result::Failed, err});
1✔
420
                poster.PostEvent(StateEvent::Failure);
1✔
421
                return;
422
        }
423

424
        UpdateResult(ctx.result_and_error, {Result::Committed, error::NoError});
34✔
425
        poster.PostEvent(StateEvent::Success);
34✔
426
}
35✔
427

428
void RollbackQueryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
429
        auto rollback_support = ctx.update_module->SupportsRollback();
19✔
430
        if (!rollback_support) {
19✔
431
                log::Error("Could not query for rollback support: " + rollback_support.error().String());
×
432
                UpdateResult(
×
433
                        ctx.result_and_error,
×
434
                        {Result::Failed | Result::RollbackFailed, rollback_support.error()});
435
                poster.PostEvent(StateEvent::Failure);
×
436
                return;
437
        }
438

439
        if (not rollback_support.value()) {
19✔
440
                bool already_failed = ResultContains(ctx.result_and_error.result, Result::Failed);
9✔
441
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::NoRollback, error::NoError});
9✔
442
                if (already_failed) {
9✔
443
                        poster.PostEvent(StateEvent::NothingToDo);
8✔
444
                } else {
445
                        // If it hadn't failed already, it's because the user asked for the rollback
446
                        // explicitly. In this case bail out instead of continuing.
447
                        poster.PostEvent(StateEvent::NeedsInteraction);
1✔
448
                }
449
                return;
9✔
450
        }
451

452
        poster.PostEvent(StateEvent::Success);
10✔
453
}
9✔
454

455
void ArtifactRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
9✔
456
        auto err = ctx.update_module->ArtifactRollback();
9✔
457
        if (err != error::NoError) {
9✔
458
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::RollbackFailed, err});
2✔
459
                poster.PostEvent(StateEvent::Failure);
2✔
460
                return;
461
        }
462

463
        UpdateResult(ctx.result_and_error, {Result::RolledBack, error::NoError});
7✔
464
        poster.PostEvent(StateEvent::Success);
7✔
465
}
9✔
466

467
void ArtifactFailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
17✔
468
        auto err = ctx.update_module->ArtifactFailure();
17✔
469
        if (err != error::NoError) {
17✔
470
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::RollbackFailed, err});
1✔
471
                poster.PostEvent(StateEvent::Failure);
1✔
472
                return;
473
        }
474

475
        poster.PostEvent(StateEvent::Success);
16✔
476
}
1✔
477

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

481
        auto &data = ctx.state_data;
482

483
        // If this is null, then it is simply a no-op, the update did not even get started.
484
        if (ctx.update_module != nullptr) {
55✔
485
                auto err = ctx.update_module->Cleanup();
55✔
486
                if (err != error::NoError) {
55✔
487
                        UpdateResult(ctx.result_and_error, {Result::Failed | Result::CleanupFailed, err});
1✔
488
                        final_event = StateEvent::Failure;
489
                        data.failed = true;
1✔
490
                        // Fall through so that we update the DB.
491
                }
492
        }
493

494
        error::Error err;
55✔
495
        if (data.rolled_back) {
55✔
496
                // Successful rollback.
497
                auto &db = ctx.main_context.GetMenderStoreDB();
11✔
498
                err = db.Remove(context::MenderContext::standalone_state_key);
22✔
499
        } else {
500
                if (data.failed) {
44✔
501
                        // Unsuccessful rollback or missing rollback support.
502
                        data.artifact_name += ctx.main_context.broken_artifact_name_suffix;
14✔
503
                        if (data.artifact_provides) {
14✔
504
                                data.artifact_provides.value()["artifact_name"] = data.artifact_name;
28✔
505
                        }
506
                        // Fall through to success case.
507
                }
508
                // Commit artifact data and remove state data
509
                err = ctx.main_context.CommitArtifactData(
132✔
510
                        data.artifact_name,
44✔
511
                        data.artifact_group,
44✔
512
                        data.artifact_provides,
44✔
513
                        data.artifact_clears_provides,
44✔
514
                        [](database::Transaction &txn) {
44✔
515
                                return txn.Remove(context::MenderContext::standalone_state_key);
44✔
516
                        });
44✔
517
        }
518
        if (err != error::NoError) {
55✔
519
                err = err.WithContext("Error while updating database");
×
520
                UpdateResult(ctx.result_and_error, {Result::Failed | Result::RollbackFailed, err});
×
521
                poster.PostEvent(StateEvent::Failure);
×
522
                return;
523
        }
524

525
        UpdateResult(ctx.result_and_error, {Result::Cleaned, error::NoError});
55✔
526
        poster.PostEvent(final_event);
55✔
527
}
56✔
528

529
void ScriptRunnerState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
347✔
530
        auto err = ctx.script_runner->RunScripts(state_, action_, on_error_);
347✔
531
        if (err != error::NoError) {
347✔
532
                log::Error("Error executing script: " + err.String());
24✔
533
                UpdateResult(ctx.result_and_error, {result_on_error_, err});
24✔
534
                poster.PostEvent(StateEvent::Failure);
24✔
535
                return;
536
        }
537

538
        poster.PostEvent(StateEvent::Success);
323✔
539
}
24✔
540

541
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
87✔
542
        auto err =
543
                ctx.main_context.GetMenderStoreDB().WriteTransaction([&ctx](database::Transaction &txn) {
87✔
544
                        auto exp_bytes = txn.Read(context::MenderContext::standalone_state_key);
87✔
545
                        if (!exp_bytes) {
87✔
546
                                if (exp_bytes.error().code == database::MakeError(database::KeyError, "").code) {
120✔
547
                                        // If the stata data is not saved, just do nothing here.
548
                                        return error::NoError;
60✔
549
                                } else {
550
                                        return exp_bytes.error();
×
551
                                }
552
                        }
553

554
                        // If there is state data, resave it with `failed` set to false. The rationale
555
                        // behind this is that if we have already recorded failure for this run, it will be
556
                        // returned in the error code. That does not mean that we should record error for
557
                        // the next run, which is independent. An example is rollback, if we are somewhere
558
                        // in the rollback flow, we are likely to have a failure here, because the *install*
559
                        // failed. But when we now exit, and then later resume the rollback, the rollback
560
                        // should return success, not failure.
561
                        if (ctx.state_data.failed) {
27✔
562
                                ctx.state_data.failed = false;
1✔
563
                                return SaveStateData(txn, ctx.state_data);
1✔
564
                        } else {
565
                                return error::NoError;
26✔
566
                        }
567
                });
87✔
568
        if (err != error::NoError) {
87✔
569
                UpdateResult(ctx.result_and_error, {Result::Failed, err});
×
570
                poster.PostEvent(StateEvent::Failure);
×
571
                return;
572
        }
573

574
        loop_.Stop();
87✔
UNCOV
575
}
×
576

577
} // namespace standalone
578
} // namespace update
579
} // 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

© 2026 Coveralls, Inc