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

mendersoftware / mender / 2271300743

19 Jan 2026 11:42AM UTC coverage: 81.376% (+1.7%) from 79.701%
2271300743

push

gitlab-ci

web-flow
Merge pull request #1879 from lluiscampos/MEN-8687-ci-debian-updates

MEN-8687: Update Debian base images for CI jobs

8791 of 10803 relevant lines covered (81.38%)

20310.08 hits per line

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

84.35
/src/mender-update/daemon/context.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/daemon/context.hpp>
16

17
#include <common/common.hpp>
18
#include <client_shared/conf.hpp>
19
#include <common/log.hpp>
20
#include <common/http_resumer.hpp>
21

22
namespace mender {
23
namespace update {
24
namespace daemon {
25

26
namespace common = mender::common;
27
namespace conf = mender::client_shared::conf;
28
namespace log = mender::common::log;
29
namespace http_resumer = mender::common::http::resumer;
30

31
namespace main_context = mender::update::context;
32

33
const int kStateDataVersion = 2;
34

35
// The maximum times we are allowed to move through update states. If this is exceeded then the
36
// update will be forcefully aborted. This can happen if we are in a reboot loop, for example.
37
const int kMaxStateDataStoreCount = 28;
38

39
ExpectedStateData ApiResponseJsonToStateData(const json::Json &json) {
57✔
40
        StateData data;
57✔
41

42
        expected::ExpectedString str = json.Get("id").and_then(json::ToString);
114✔
43
        if (!str) {
57✔
44
                return expected::unexpected(str.error().WithContext("Could not get deployment ID"));
×
45
        }
46
        data.update_info.id = str.value();
57✔
47

48
        str = json.Get("artifact")
57✔
49
                          .and_then([](const json::Json &json) { return json.Get("source"); })
114✔
50
                          .and_then([](const json::Json &json) { return json.Get("uri"); })
114✔
51
                          .and_then(json::ToString);
57✔
52
        if (!str) {
57✔
53
                return expected::unexpected(
×
54
                        str.error().WithContext("Could not get artifact URI for deployment"));
×
55
        }
56
        data.update_info.artifact.source.uri = str.value();
57✔
57
        log::Debug("Artifact Download URL: " + data.update_info.artifact.source.uri);
57✔
58

59
        str = json.Get("artifact")
57✔
60
                          .and_then([](const json::Json &json) { return json.Get("source"); })
114✔
61
                          .and_then([](const json::Json &json) { return json.Get("expire"); })
114✔
62
                          .and_then(json::ToString);
57✔
63
        if (str) {
57✔
64
                data.update_info.artifact.source.expire = str.value();
57✔
65
                // If it's not available, we don't care.
66
        }
67

68
        // For later: Update Control Maps should be handled here.
69

70
        // Note: There is more information available in the response than we collect here, but we
71
        // prefer to get the information from the artifact instead, since it is the authoritative
72
        // source. And it's also signed, unlike the response.
73

74
        return data;
57✔
75
}
57✔
76

77
// Database keys
78
const string Context::kRollbackNotSupported = "rollback-not-supported";
79
const string Context::kRollbackSupported = "rollback-supported";
80

81
string SupportsRollbackToDbString(bool support) {
44✔
82
        return support ? Context::kRollbackSupported : Context::kRollbackNotSupported;
50✔
83
}
84

85
expected::ExpectedBool DbStringToSupportsRollback(const string &str) {
14✔
86
        if (str == Context::kRollbackSupported) {
14✔
87
                return true;
88
        } else if (str == Context::kRollbackNotSupported) {
2✔
89
                return false;
90
        } else {
91
                return expected::unexpected(main_context::MakeError(
×
92
                        main_context::DatabaseValueError,
93
                        "\"" + str + "\" is not a valid value for SupportsRollback"));
×
94
        }
95
}
96

97
// Database keys
98
const string Context::kRebootTypeNone = "";
99
const string Context::kRebootTypeCustom = "reboot-type-custom";
100
const string Context::kRebootTypeAutomatic = "reboot-type-automatic";
101

102
string NeedsRebootToDbString(update_module::RebootAction action) {
71✔
103
        switch (action) {
71✔
104
        case update_module::RebootAction::No:
8✔
105
                return Context::kRebootTypeNone;
8✔
106
        case update_module::RebootAction::Automatic:
×
107
                return Context::kRebootTypeAutomatic;
×
108
        case update_module::RebootAction::Yes:
63✔
109
                return Context::kRebootTypeCustom;
63✔
110
        default:
×
111
                // Should not happen.
112
                assert(false);
113
                return Context::kRebootTypeNone;
×
114
        }
115
}
116

117
update_module::ExpectedRebootAction DbStringToNeedsReboot(const string &str) {
106✔
118
        if (str == Context::kRebootTypeNone) {
106✔
119
                return update_module::RebootAction::No;
120
        } else if (str == Context::kRebootTypeAutomatic) {
106✔
121
                return update_module::RebootAction::Automatic;
122
        } else if (str == Context::kRebootTypeCustom) {
106✔
123
                return update_module::RebootAction::Yes;
124
        } else {
125
                return expected::unexpected(main_context::MakeError(
×
126
                        main_context::DatabaseValueError,
127
                        "\"" + str + "\" is not a valid value for RebootRequested"));
×
128
        }
129
}
130

131
void StateData::FillUpdateDataFromArtifact(artifact::PayloadHeaderView &view) {
50✔
132
        auto &artifact = update_info.artifact;
133
        auto &header = view.header;
134
        artifact.compatible_devices = header.header_info.depends.device_type;
50✔
135
        artifact.payload_types = {header.payload_type};
150✔
136
        artifact.artifact_name = header.artifact_name;
50✔
137
        artifact.artifact_group = header.artifact_group;
50✔
138
        if (header.type_info.artifact_provides) {
50✔
139
                artifact.type_info_provides = header.type_info.artifact_provides.value();
49✔
140
        } else {
141
                artifact.type_info_provides.clear();
142
        }
143
        if (header.type_info.clears_artifact_provides) {
50✔
144
                artifact.clears_artifact_provides = header.type_info.clears_artifact_provides.value();
49✔
145
        } else {
146
                artifact.clears_artifact_provides.clear();
1✔
147
        }
148
}
100✔
149

150
Context::Context(
96✔
151
        mender::update::context::MenderContext &mender_context, events::EventLoop &event_loop) :
96✔
152
        mender_context(mender_context),
96✔
153
        event_loop(event_loop),
96✔
154
#ifdef MENDER_USE_DBUS
155
        authenticator(event_loop),
96✔
156
#elif defined(MENDER_EMBED_MENDER_AUTH)
157
        authenticator(event_loop, mender_context.GetConfig()),
158
#endif
159
        http_client(mender_context.GetConfig().GetHttpClientConfig(), event_loop, authenticator),
192✔
160
        download_client(make_shared<http_resumer::DownloadResumerClient>(
96✔
161
                mender_context.GetConfig().GetHttpClientConfig(), event_loop)),
162
        deployment_client(make_shared<deployments::DeploymentClient>()),
96✔
163
        inventory_client(make_shared<inventory::InventoryClient>()),
96✔
164
        deployment_timer(event_loop),
96✔
165
        inventory_timer(event_loop) {
96✔
166
}
96✔
167

168
///////////////////////////////////////////////////////////////////////////////////////////////////
169
// Values for various states in the database.
170
///////////////////////////////////////////////////////////////////////////////////////////////////
171

172
// In use by current client. Some of the variable names have been updated from the Golang version,
173
// but the database strings are the same. Some naming is inconsistent, this is for historical
174
// reasons, and it's better to look at the names for the variables.
175
const string Context::kUpdateStateDownload = "update-store";
176
const string Context::kUpdateStateArtifactInstall = "update-install";
177
const string Context::kUpdateStateArtifactReboot = "reboot";
178
const string Context::kUpdateStateArtifactVerifyReboot = "after-reboot";
179
const string Context::kUpdateStateArtifactCommit = "update-commit";
180
const string Context::kUpdateStateAfterArtifactCommit = "update-after-commit";
181
const string Context::kUpdateStateArtifactRollback = "rollback";
182
const string Context::kUpdateStateArtifactRollbackReboot = "rollback-reboot";
183
const string Context::kUpdateStateArtifactVerifyRollbackReboot = "after-rollback-reboot";
184
const string Context::kUpdateStateArtifactFailure = "update-error";
185
const string Context::kUpdateStateCleanup = "cleanup";
186
const string Context::kUpdateStateStatusReportRetry = "update-retry-report";
187

188
///////////////////////////////////////////////////////////////////////////////////////////////////
189
// Not in use by current client, but were in use by Golang client, and still important to handle
190
// correctly in recovery scenarios.
191
///////////////////////////////////////////////////////////////////////////////////////////////////
192

193
// This client doesn't use it, but it's essentially equivalent to "update-after-commit".
194
const string Context::kUpdateStateUpdateAfterFirstCommit = "update-after-first-commit";
195
// This client doesn't use it, but it's essentially equivalent to "after-rollback-reboot".
196
const string Context::kUpdateStateVerifyRollbackReboot = "verify-rollback-reboot";
197
// No longer used. Since this used to be at the very end of an update, if we encounter it in the
198
// database during startup, we just go back to Idle.
199
const string UpdateStateReportStatusError = "status-report-error";
200

201
///////////////////////////////////////////////////////////////////////////////////////////////////
202
// Not in use. All of these, as well as unknown values, will cause a rollback.
203
///////////////////////////////////////////////////////////////////////////////////////////////////
204

205
// Disable, but distinguish from comments.
206
#if false
207
// These were never actually saved due to not being update states.
208
const string Context::kUpdateStateInit = "init";
209
const string Context::kUpdateStateIdle = "idle";
210
const string Context::kUpdateStateAuthorize = "authorize";
211
const string Context::kUpdateStateAuthorizeWait = "authorize-wait";
212
const string Context::kUpdateStateInventoryUpdate = "inventory-update";
213
const string Context::kUpdateStateInventoryUpdateRetryWait = "inventory-update-retry-wait";
214

215
const string Context::kUpdateStateCheckWait = "check-wait";
216
const string Context::kUpdateStateUpdateCheck = "update-check";
217
const string Context::kUpdateStateUpdateFetch = "update-fetch";
218
const string Context::kUpdateStateUpdateAfterStore = "update-after-store";
219
const string Context::kUpdateStateFetchStoreRetryWait = "fetch-install-retry-wait";
220
const string Context::kUpdateStateUpdateVerify = "update-verify";
221
const string Context::kUpdateStateUpdatePreCommitStatusReportRetry = "update-pre-commit-status-report-retry";
222
const string Context::kUpdateStateUpdateStatusReport = "update-status-report";
223
// Would have been used, but a copy/paste error in the Golang client means that it was never
224
// saved. "after-reboot" is stored twice instead.
225
const string Context::kUpdateStateVerifyReboot = "verify-reboot";
226
const string Context::kUpdateStateError = "error";
227
const string Context::kUpdateStateDone = "finished";
228
const string Context::kUpdateStateUpdateControl = "mender-update-control";
229
const string Context::kUpdateStateUpdateControlPause = "mender-update-control-pause";
230
const string Context::kUpdateStateFetchUpdateControl = "mender-update-control-refresh-maps";
231
const string Context::kUpdateStateFetchRetryUpdateControl = "mender-update-control-retry-refresh-maps";
232
#endif
233

234
///////////////////////////////////////////////////////////////////////////////////////////////////
235
// End of database values.
236
///////////////////////////////////////////////////////////////////////////////////////////////////
237

238
static string GenerateStateDataJson(const StateData &state_data) {
648✔
239
        stringstream content;
648✔
240

241
        auto append_vector = [&content](const vector<string> &data) {
2,592✔
242
                for (auto entry = data.begin(); entry != data.end(); entry++) {
4,770✔
243
                        if (entry != data.begin()) {
2,178✔
244
                                content << ",";
×
245
                        }
246
                        content << R"(")" << json::EscapeString(*entry) << R"(")";
4,356✔
247
                }
248
        };
2,592✔
249

250
        auto append_map = [&content](const unordered_map<string, string> &data) {
648✔
251
                for (auto entry = data.begin(); entry != data.end(); entry++) {
1,229✔
252
                        if (entry != data.begin()) {
581✔
253
                                content << ",";
×
254
                        }
255
                        content << R"(")" << json::EscapeString(entry->first) << R"(":")"
1,162✔
256
                                        << json::EscapeString(entry->second) << R"(")";
1,743✔
257
                }
258
        };
648✔
259

260
        content << "{";
648✔
261
        {
262
                content << R"("Version":)" << to_string(state_data.version) << ",";
648✔
263
                content << R"("Name":")" << json::EscapeString(state_data.state) << R"(",)";
648✔
264
                content << R"("UpdateInfo":{)";
648✔
265
                {
266
                        auto &update_info = state_data.update_info;
267
                        content << R"("Artifact":{)";
648✔
268
                        {
269
                                auto &artifact = update_info.artifact;
270
                                content << R"("Source":{)";
648✔
271
                                {
272
                                        content << R"("URI":")" << json::EscapeString(artifact.source.uri) << R"(",)";
648✔
273
                                        content << R"("Expire":")" << json::EscapeString(artifact.source.expire)
648✔
274
                                                        << R"(")";
1,296✔
275
                                }
276
                                content << "},";
648✔
277

278
                                content << R"("device_types_compatible":[)";
648✔
279
                                append_vector(artifact.compatible_devices);
648✔
280
                                content << "],";
648✔
281

282
                                content << R"("PayloadTypes":[)";
648✔
283
                                append_vector(artifact.payload_types);
648✔
284
                                content << "],";
648✔
285

286
                                content << R"("artifact_name":")" << json::EscapeString(artifact.artifact_name)
648✔
287
                                                << R"(",)";
1,296✔
288
                                content << R"("artifact_group":")" << json::EscapeString(artifact.artifact_group)
648✔
289
                                                << R"(",)";
1,296✔
290

291
                                content << R"("artifact_provides":{)";
648✔
292
                                append_map(artifact.type_info_provides);
648✔
293
                                content << "},";
648✔
294

295
                                content << R"("clears_artifact_provides":[)";
648✔
296
                                append_vector(artifact.clears_artifact_provides);
648✔
297
                                content << "]";
648✔
298
                        }
299
                        content << "},";
648✔
300

301
                        content << R"("ID":")" << json::EscapeString(update_info.id) << R"(",)";
648✔
302

303
                        content << R"("RebootRequested":[)";
648✔
304
                        append_vector(update_info.reboot_requested);
648✔
305
                        content << R"(],)";
648✔
306

307
                        content << R"("SupportsRollback":")"
308
                                        << json::EscapeString(update_info.supports_rollback) << R"(",)";
648✔
309
                        content << R"("StateDataStoreCount":)" << to_string(update_info.state_data_store_count)
648✔
310
                                        << R"(,)";
1,296✔
311
                        content << R"("HasDBSchemaUpdate":)"
312
                                        << string(update_info.has_db_schema_update ? "true," : "false,");
1,295✔
313
                        content << R"("AllRollbacksSuccessful":)"
314
                                        << string(update_info.all_rollbacks_successful ? "true" : "false");
1,148✔
315
                }
316
                content << "}";
648✔
317
        }
318
        content << "}";
648✔
319

320
        return std::move(*content.rdbuf()).str();
1,296✔
321
}
648✔
322

323
error::Error Context::SaveDeploymentStateData(kv_db::Transaction &txn, StateData &state_data) {
650✔
324
        if (state_data.update_info.state_data_store_count++ >= kMaxStateDataStoreCount) {
650✔
325
                return main_context::MakeError(
4✔
326
                        main_context::StateDataStoreCountExceededError,
327
                        "State looping detected, breaking out of loop");
2✔
328
        }
329

330
        string content = GenerateStateDataJson(state_data);
648✔
331

332
        string store_key;
333
        if (state_data.update_info.has_db_schema_update) {
648✔
334
                store_key = mender_context.state_data_key_uncommitted;
335

336
                // Leave state_data_key alone.
337
        } else {
338
                store_key = mender_context.state_data_key;
339

340
                auto err = txn.Remove(mender_context.state_data_key_uncommitted);
647✔
341
                if (err != error::NoError) {
647✔
342
                        return err.WithContext("Could not remove uncommitted state data");
×
343
                }
344
        }
345

346
        auto err = txn.Write(store_key, common::ByteVectorFromString(content));
648✔
347
        if (err != error::NoError) {
648✔
348
                return err.WithContext("Could not write state data");
×
349
        }
350

351
        return error::NoError;
648✔
352
}
353

354
error::Error Context::SaveDeploymentStateData(StateData &state_data) {
585✔
355
        auto &db = mender_context.GetMenderStoreDB();
585✔
356
        return db.WriteTransaction([this, &state_data](kv_db::Transaction &txn) {
585✔
357
                return SaveDeploymentStateData(txn, state_data);
576✔
358
        });
1,170✔
359
}
360

361
#define SetOrReturnIfError(dst, expr) \
362
        if (!expr) {                      \
363
                return expr.error();          \
364
        }                                 \
365
        dst = expr.value()
366

367
#define DefaultOrSetOrReturnIfError(dst, expr, def)                          \
368
        if (!expr) {                                                             \
369
                if (expr.error().code == json::MakeError(json::KeyError, "").code) { \
370
                        dst = def;                                                       \
371
                } else {                                                             \
372
                        return expr.error();                                             \
373
                }                                                                    \
374
        } else {                                                                 \
375
                dst = expr.value();                                                  \
376
        }
377

378
static error::Error UnmarshalJsonStateDataVersion1(const json::Json &json, StateData &state_data) {
1✔
379
        auto exp_int = json.Get("Version").and_then(json::To<int>);
2✔
380
        SetOrReturnIfError(state_data.version, exp_int);
1✔
381

382
        if (state_data.version != 1) {
1✔
383
                return error::MakeError(
×
384
                        error::ProgrammingError, "Only able to unmarshal version 1 of the state data format");
×
385
        }
386

387
        auto exp_string = json.Get("Name").and_then(json::ToString);
2✔
388
        SetOrReturnIfError(state_data.state, exp_string);
1✔
389

390
        const auto &exp_json_update_info = json.Get("UpdateInfo");
1✔
391
        SetOrReturnIfError(const auto &json_update_info, exp_json_update_info);
1✔
392

393
        auto &update_info = state_data.update_info;
394

395
        exp_string = json_update_info.Get("ID").and_then(json::ToString);
2✔
396
        SetOrReturnIfError(update_info.id, exp_string);
1✔
397

398
        const auto &exp_json_artifact = json_update_info.Get("Artifact");
1✔
399
        SetOrReturnIfError(const auto &json_artifact, exp_json_artifact);
1✔
400

401
        auto &artifact = update_info.artifact;
402

403
        const auto &exp_json_source = json_artifact.Get("Source");
1✔
404
        SetOrReturnIfError(const auto &json_source, exp_json_source);
1✔
405

406
        auto &source = artifact.source;
407

408
        exp_string = json_source.Get("URI").and_then(json::ToString);
2✔
409
        SetOrReturnIfError(source.uri, exp_string);
1✔
410

411
        exp_string = json_source.Get("Expire").and_then(json::ToString);
2✔
412
        SetOrReturnIfError(source.expire, exp_string);
1✔
413

414
        auto exp_string_vector =
415
                json_artifact.Get("device_types_compatible").and_then(json::ToStringVector);
2✔
416
        SetOrReturnIfError(artifact.compatible_devices, exp_string_vector);
1✔
417

418
        exp_string = json_artifact.Get("artifact_name").and_then(json::ToString);
2✔
419
        SetOrReturnIfError(artifact.artifact_name, exp_string);
1✔
420

421
        return error::NoError;
1✔
422
}
423

424
static error::Error UnmarshalJsonStateData(const json::Json &json, StateData &state_data) {
38✔
425
        auto exp_int = json.Get("Version").and_then(json::To<int>);
76✔
426
        SetOrReturnIfError(state_data.version, exp_int);
38✔
427

428
        if (state_data.version != kStateDataVersion && state_data.version != 1) {
38✔
429
                return error::Error(
430
                        make_error_condition(errc::not_supported),
2✔
431
                        "State Data version not supported by this client (" + to_string(state_data.version)
1✔
432
                                + ")");
2✔
433
        }
434

435
        if (state_data.version == 1) {
37✔
436
                return UnmarshalJsonStateDataVersion1(json, state_data);
1✔
437
        }
438

439
        auto exp_string = json.Get("Name").and_then(json::ToString);
72✔
440
        SetOrReturnIfError(state_data.state, exp_string);
36✔
441

442
        const auto &exp_json_update_info = json.Get("UpdateInfo");
36✔
443
        SetOrReturnIfError(const auto &json_update_info, exp_json_update_info);
36✔
444

445
        auto &update_info = state_data.update_info;
446

447
        exp_string = json_update_info.Get("ID").and_then(json::ToString);
72✔
448
        SetOrReturnIfError(update_info.id, exp_string);
36✔
449

450
        const auto &exp_json_artifact = json_update_info.Get("Artifact");
36✔
451
        SetOrReturnIfError(const auto &json_artifact, exp_json_artifact);
36✔
452

453
        auto &artifact = update_info.artifact;
454

455
        const auto &exp_json_source = json_artifact.Get("Source");
36✔
456
        SetOrReturnIfError(const auto &json_source, exp_json_source);
36✔
457

458
        auto &source = artifact.source;
459

460
        exp_string = json_source.Get("URI").and_then(json::ToString);
72✔
461
        SetOrReturnIfError(source.uri, exp_string);
36✔
462

463
        exp_string = json_source.Get("Expire").and_then(json::ToString);
72✔
464
        SetOrReturnIfError(source.expire, exp_string);
36✔
465

466
        auto exp_string_vector =
467
                json_artifact.Get("device_types_compatible").and_then(json::ToStringVector);
72✔
468
        SetOrReturnIfError(artifact.compatible_devices, exp_string_vector);
36✔
469

470
        exp_string = json_artifact.Get("artifact_name").and_then(json::ToString);
72✔
471
        SetOrReturnIfError(artifact.artifact_name, exp_string);
36✔
472

473
        exp_string_vector = json_artifact.Get("PayloadTypes").and_then(json::ToStringVector);
72✔
474
        SetOrReturnIfError(artifact.payload_types, exp_string_vector);
36✔
475
        // It's possible for there not to be an initialized update,
476
        // if the deployment failed before we could successfully parse the artifact.
477
        if (artifact.payload_types.size() == 0 and artifact.artifact_name == "") {
36✔
478
                return error::NoError;
1✔
479
        }
480
        if (artifact.payload_types.size() != 1) {
35✔
481
                return error::Error(
482
                        make_error_condition(errc::not_supported),
×
483
                        "Only exactly one payload type is supported. Got: "
484
                                + to_string(artifact.payload_types.size()));
×
485
        }
486

487
        exp_string = json_artifact.Get("artifact_group").and_then(json::ToString);
70✔
488
        SetOrReturnIfError(artifact.artifact_group, exp_string);
35✔
489

490
        auto exp_string_map = json_artifact.Get("artifact_provides").and_then(json::ToKeyValueMap);
70✔
491
        DefaultOrSetOrReturnIfError(artifact.type_info_provides, exp_string_map, {});
35✔
492

493
        exp_string_vector =
494
                json_artifact.Get("clears_artifact_provides").and_then(json::ToStringVector);
70✔
495
        DefaultOrSetOrReturnIfError(artifact.clears_artifact_provides, exp_string_vector, {});
35✔
496

497
        exp_string_vector = json_update_info.Get("RebootRequested").and_then(json::ToStringVector);
70✔
498
        SetOrReturnIfError(update_info.reboot_requested, exp_string_vector);
35✔
499
        // Check that it's valid strings.
500
        for (const auto &reboot_requested : update_info.reboot_requested) {
61✔
501
                if (reboot_requested != "") {
26✔
502
                        auto exp_needs_reboot = DbStringToNeedsReboot(reboot_requested);
23✔
503
                        if (!exp_needs_reboot) {
23✔
504
                                return exp_needs_reboot.error();
×
505
                        }
506
                }
507
        }
508

509
        exp_string = json_update_info.Get("SupportsRollback").and_then(json::ToString);
70✔
510
        SetOrReturnIfError(update_info.supports_rollback, exp_string);
35✔
511
        // Check that it's a valid string.
512
        if (update_info.supports_rollback != "") {
35✔
513
                auto exp_supports_rollback = DbStringToSupportsRollback(update_info.supports_rollback);
14✔
514
                if (!exp_supports_rollback) {
14✔
515
                        return exp_supports_rollback.error();
×
516
                }
517
        }
518

519
        auto exp_int64 = json_update_info.Get("StateDataStoreCount").and_then(json::ToInt64);
70✔
520
        SetOrReturnIfError(update_info.state_data_store_count, exp_int64);
35✔
521

522
        auto exp_bool = json_update_info.Get("HasDBSchemaUpdate").and_then(json::ToBool);
70✔
523
        SetOrReturnIfError(update_info.has_db_schema_update, exp_bool);
35✔
524

525
        exp_bool = json_update_info.Get("AllRollbacksSuccessful").and_then(json::ToBool);
70✔
526
        DefaultOrSetOrReturnIfError(update_info.all_rollbacks_successful, exp_bool, false);
35✔
527

528
        return error::NoError;
35✔
529
}
530

531
#undef SetOrReturnIfError
532
#undef EmptyOrSetOrReturnIfError
533

534
expected::ExpectedBool Context::LoadDeploymentStateData(StateData &state_data) {
93✔
535
        log::Trace("Loading the deployment state data");
93✔
536

537
        auto &db = mender_context.GetMenderStoreDB();
93✔
538
        auto err = db.WriteTransaction([this, &state_data](kv_db::Transaction &txn) {
93✔
539
                auto exp_content = txn.Read(mender_context.state_data_key);
92✔
540
                if (!exp_content) {
92✔
541
                        return exp_content.error().WithContext("Could not load state data");
110✔
542
                }
543
                auto content = common::StringFromByteVector(exp_content.value());
37✔
544

545
                auto exp_json = json::Load(content);
74✔
546
                if (!exp_json) {
37✔
547
                        return exp_json.error().WithContext("Could not load state data");
×
548
                }
549

550
                log::Trace("Got database state data content: " + content);
37✔
551

552
                auto err = UnmarshalJsonStateData(exp_json.value(), state_data);
37✔
553
                if (err != error::NoError) {
37✔
554
                        if (err.code == make_error_condition(errc::not_supported)) {
1✔
555
                                //
556
                                // Try and Roll back
557
                                //
558
                                // Try and load the uncommitted data, in case we are rolling back from an
559
                                // unsupported version
560
                                exp_content = txn.Read(mender_context.state_data_key_uncommitted);
2✔
561
                                if (!exp_content) {
1✔
562
                                        if (exp_content.error().code != kv_db::MakeError(kv_db::KeyError, "").code) {
×
563
                                                return exp_content.error().WithContext("Could not load state data");
×
564
                                        }
565
                                        return exp_content.error().WithContext("Could not load state data");
×
566
                                }
567

568
                                auto content = common::StringFromByteVector(exp_content.value());
1✔
569
                                log::Trace("Got database state data content from the uncommitted key: " + content);
1✔
570
                                auto exp_json = json::Load(content);
2✔
571
                                if (!exp_json) {
1✔
572
                                        return exp_json.error().WithContext("Could not load state data");
×
573
                                }
574
                                StateData state_data_uncommitted {};
1✔
575

576
                                err = UnmarshalJsonStateData(exp_json.value(), state_data_uncommitted);
1✔
577
                                if (err != error::NoError) {
1✔
578
                                        return err.WithContext(
×
579
                                                "Could not unmarshal the uncommited state data. This means we failed to roll back the state data");
×
580
                                }
581
                                state_data = state_data_uncommitted;
1✔
582
                        } else {
1✔
583
                                return err.WithContext("Failed to unmarshal the state data");
×
584
                        }
585
                }
586

587
                switch (state_data.version) {
37✔
588
                case 1: {
589
                        //
590
                        // Roll forwards
591
                        //
592
                        log::Debug("Got old state data version 1. Migrating it to version 2");
1✔
593

594
                        // We need to upgrade the schema. Check if we have
595
                        // already written an updated one.
596
                        exp_content = txn.Read(mender_context.state_data_key_uncommitted);
2✔
597
                        if (!exp_content) {
1✔
598
                                if (exp_content.error().code != kv_db::MakeError(kv_db::KeyError, "").code) {
2✔
599
                                        return exp_content.error().WithContext("Could not load state data");
×
600
                                }
601
                                log::Debug(
1✔
602
                                        "Got read error reading the uncommitted state data: "
603
                                        + exp_content.error().String());
2✔
604
                        } else {
605
                                auto content = common::StringFromByteVector(exp_content.value());
×
606
                                log::Trace("Loaded the uncommitted state data: " + content);
×
607

608
                                exp_json = json::Load(content);
×
609
                                if (!exp_json) {
×
610
                                        return exp_json.error().WithContext("Could not load state data");
×
611
                                }
612

613
                                StateData state_data_uncommitted {};
×
614

615
                                auto inner_err = UnmarshalJsonStateData(exp_json.value(), state_data_uncommitted);
×
616
                                if (inner_err != error::NoError) {
×
617
                                        return inner_err.WithContext("Could not load the uncommited state data");
×
618
                                }
619

620
                                // Verify that the update IDs are equal
621
                                if (state_data.update_info.id == state_data_uncommitted.update_info.id) {
×
622
                                        state_data = state_data_uncommitted;
×
623
                                }
624
                        }
×
625

626
                        // If we are upgrading the schema, we know for a fact
627
                        // that we came from a rootfs-image update, because it
628
                        // was the only thing that was supported there. Store
629
                        // this, since this information will be missing in
630
                        // databases before version 2.
631
                        state_data.update_info.artifact.payload_types = {"rootfs-image"};
2✔
632
                        state_data.update_info.reboot_requested = {"reboot-type-custom"};
2✔
633
                        state_data.update_info.supports_rollback = "rollback-supported";
1✔
634

635
                        log::Info("Storing new version of the state data");
1✔
636

637
                        // Since we loaded from the uncommitted key, set this.
638
                        state_data.update_info.has_db_schema_update = true;
1✔
639

640
                        break;
1✔
641
                }
642
                case 2:
36✔
643
                        state_data.update_info.has_db_schema_update = false;
36✔
644
                        break;
36✔
645
                default:
×
646
                        log::Fatal(
×
647
                                "ProgrammingError. Unsupported state data versions should already be handled, got: "
648
                                + to_string(state_data.version));
×
649
                }
650

651
                state_data.version = 2;
37✔
652
                log::Trace("Finished loading the state data");
37✔
653

654
                // Every load also saves, which increments the state_data_store_count.
655
                return SaveDeploymentStateData(txn, state_data);
37✔
656
        });
95✔
657

658
        if (err == error::NoError) {
93✔
659
                return true;
660
        } else if (err.code == kv_db::MakeError(kv_db::KeyError, "").code) {
114✔
661
                return false;
662
        } else {
663
                return expected::unexpected(err);
4✔
664
        }
665
}
666

667
void Context::BeginDeploymentLogging() {
93✔
668
        deployment.logger.reset(new deployments::DeploymentLog(
669
                mender_context.GetConfig().paths.GetUpdateLogPath(),
93✔
670
                deployment.state_data->update_info.id));
93✔
671
        auto err = deployment.logger->BeginLogging();
93✔
672
        if (err != error::NoError) {
93✔
673
                log::Error(
×
674
                        "Was not able to set up deployment log for deployment ID "
675
                        + deployment.state_data->update_info.id + ": " + err.String());
×
676
                // It's not a fatal error, so continue.
677
        }
678
}
93✔
679

680
void Context::FinishDeploymentLogging() {
93✔
681
        auto err = deployment.logger->FinishLogging();
93✔
682
        if (err != error::NoError) {
93✔
683
                log::Error(
×
684
                        "Was not able to stop deployment log for deployment ID "
685
                        + deployment.state_data->update_info.id + ": " + err.String());
×
686
                // We need to continue regardless
687
        }
688
}
93✔
689

690
} // namespace daemon
691
} // namespace update
692
} // 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