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

mendersoftware / mender / 2460319284

17 Apr 2026 11:02AM UTC coverage: 81.435% (-0.3%) from 81.695%
2460319284

push

gitlab-ci

web-flow
Merge pull request #1938 from michalkopczan/MEN-9075-mender-marks-download-as-imcomplete-when-update-module-returns-too-fast

chore: Document stream tree flow synchronization

9181 of 11274 relevant lines covered (81.44%)

20188.59 hits per line

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

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

17
#include <cctype>
18

19
#include <algorithm>
20
#include <set>
21

22
#include <artifact/artifact.hpp>
23
#include <common/common.hpp>
24
#include <common/device_tier.hpp>
25
#include <common/error.hpp>
26
#include <common/expected.hpp>
27
#include <common/io.hpp>
28
#include <common/json.hpp>
29
#include <common/key_value_database.hpp>
30
#include <common/log.hpp>
31
#include <common/path.hpp>
32

33
#ifdef MENDER_USE_YAML_CPP
34
#include <common/yaml.hpp>
35
#endif // MENDER_USE_YAML_CPP
36

37
namespace mender {
38
namespace update {
39
namespace context {
40

41
using namespace std;
42
namespace artifact = mender::artifact;
43
namespace common = mender::common;
44
namespace error = mender::common::error;
45
namespace expected = mender::common::expected;
46
namespace io = mender::common::io;
47
namespace json = mender::common::json;
48
namespace kv_db = mender::common::key_value_database;
49
namespace log = mender::common::log;
50
namespace path = mender::common::path;
51
namespace device_tier = mender::common::device_tier;
52

53
#ifdef MENDER_USE_YAML_CPP
54
namespace yaml = mender::common::yaml;
55
#endif // MENDER_USE_YAML_CPP
56

57
const string MenderContext::broken_artifact_name_suffix {"_INCONSISTENT"};
58
const string MenderContext::orchestrator_manifest_payload_type {"mender-orchestrator-manifest"};
59

60
const string MenderContext::artifact_name_key {"artifact-name"};
61
const string MenderContext::artifact_group_key {"artifact-group"};
62
const string MenderContext::artifact_provides_key {"artifact-provides"};
63
const string MenderContext::standalone_state_key {"standalone-state"};
64
const string MenderContext::state_data_key {"state"};
65
const string MenderContext::state_data_key_uncommitted {"state-uncommitted"};
66
const string MenderContext::update_control_maps {"update-control-maps"};
67
const string MenderContext::auth_token_name {"authtoken"};
68
const string MenderContext::auth_token_cache_invalidator_name {"auth-token-cache-invalidator"};
69

70
const int MenderContext::standalone_data_version {2};
71

72
const MenderContextErrorCategoryClass MenderContextErrorCategory;
73

74
const char *MenderContextErrorCategoryClass::name() const noexcept {
×
75
        return "MenderContextErrorCategory";
×
76
}
77

78
string MenderContextErrorCategoryClass::message(int code) const {
3✔
79
        switch (code) {
3✔
80
        case NoError:
81
                return "Success";
×
82
        case ParseError:
83
                return "Parse error";
×
84
        case ValueError:
85
                return "Value error";
×
86
        case NoSuchUpdateModuleError:
87
                return "Update Module not found for given artifact type";
×
88
        case DatabaseValueError:
89
                return "Value in database is invalid or corrupted";
×
90
        case RebootRequiredError:
91
                return "Reboot required";
×
92
        case NoUpdateInProgressError:
93
                return "No update in progress";
2✔
94
        case UnexpectedHttpResponse:
95
                return "Unexpected HTTP response";
×
96
        case StateDataStoreCountExceededError:
97
                return "State data store count exceeded";
1✔
98
        case WrongOperationError:
99
                return "Operation cannot be done in this state";
×
100
        }
101
        assert(false);
102
        return "Unknown";
×
103
}
104

105
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
377✔
106
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
383✔
107
}
108

109
error::Error MenderContext::Initialize() {
456✔
110
        auto err = mender_store_.Open(path::Join(config_.paths.GetDataStore(), "mender-store"));
456✔
111
        if (error::NoError != err) {
456✔
112
                return err;
1✔
113
        }
114
        err = mender_store_.Remove(auth_token_name);
455✔
115
        if (error::NoError != err) {
455✔
116
                // key not existing in the DB is not treated as an error so this must be
117
                // a real error
118
                return err;
×
119
        }
120
        err = mender_store_.Remove(auth_token_cache_invalidator_name);
455✔
121
        if (error::NoError != err) {
455✔
122
                // same as above -- a real error
123
                return err;
×
124
        }
125

126
        return error::NoError;
455✔
127
}
128

129
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
1,744✔
130
        return mender_store_;
1,744✔
131
}
132

133
ExpectedProvidesData MenderContext::LoadProvides() {
415✔
134
        ExpectedProvidesData data;
135
        auto err = mender_store_.ReadTransaction([this, &data](kv_db::Transaction &txn) {
×
136
                data = LoadProvides(txn);
414✔
137
                if (!data) {
414✔
138
                        return data.error();
2✔
139
                }
140
                return error::NoError;
412✔
141
        });
415✔
142
        if (err != error::NoError) {
415✔
143
                return expected::unexpected(err);
6✔
144
        }
145
        return data;
146
}
147

148
ExpectedProvidesData MenderContext::LoadProvides(kv_db::Transaction &txn) {
508✔
149
        string artifact_name;
150
        string artifact_group;
151
        string artifact_provides_str;
152

153
        auto err = kv_db::ReadString(txn, artifact_name_key, artifact_name, true);
508✔
154
        if (err != error::NoError) {
508✔
155
                return expected::unexpected(err);
×
156
        }
157
        err = kv_db::ReadString(txn, artifact_group_key, artifact_group, true);
508✔
158
        if (err != error::NoError) {
508✔
159
                return expected::unexpected(err);
×
160
        }
161
        err = kv_db::ReadString(txn, artifact_provides_key, artifact_provides_str, true);
508✔
162
        if (err != error::NoError) {
508✔
163
                return expected::unexpected(err);
×
164
        }
165

166
        ProvidesData ret {};
508✔
167
        if (artifact_name != "") {
508✔
168
                ret["artifact_name"] = artifact_name;
1,000✔
169
        }
170
        if (artifact_group != "") {
508✔
171
                ret["artifact_group"] = artifact_group;
20✔
172
        }
173
        if (artifact_provides_str == "") {
508✔
174
                // nothing more to do
175
                return ret;
388✔
176
        }
177

178
        auto ex_j = json::Load(artifact_provides_str);
240✔
179
        if (!ex_j) {
120✔
180
                return expected::unexpected(ex_j.error());
2✔
181
        }
182
        auto ex_children = ex_j.value().GetChildren();
119✔
183
        if (!ex_children) {
119✔
184
                return expected::unexpected(ex_children.error());
×
185
        }
186

187
        auto children = ex_children.value();
119✔
188
        if (!all_of(children.cbegin(), children.cend(), [](const json::ChildrenMap::value_type &it) {
119✔
189
                        return it.second.IsString();
213✔
190
                })) {
191
                auto err = json::MakeError(json::TypeError, "Unexpected non-string data in provides");
2✔
192
                return expected::unexpected(err);
2✔
193
        }
194
        for (const auto &it : ex_children.value()) {
330✔
195
                ret[it.first] = it.second.GetString().value();
424✔
196
        }
197

198
        return ret;
118✔
199
}
200

201
#ifdef MENDER_USE_YAML_CPP
202
expected::ExpectedString MenderContext::GetSystemType() {
4✔
203
        string topology_dir =
204
                conf::GetEnv("MENDER_ORCHESTRATOR_TOPOLOGY_DIR", "/var/lib/mender-orchestrator/");
8✔
205
        string topology_file_path = path::Join(topology_dir, "topology.yaml");
4✔
206

207
        auto ex_topology = yaml::LoadFromFile(topology_file_path);
8✔
208
        if (!ex_topology) {
4✔
209
                return expected::unexpected(MakeError(
1✔
210
                        ParseError,
211
                        "Failed to load topology file '" + topology_file_path
1✔
212
                                + "': " + ex_topology.error().message));
3✔
213
        }
214

215
        auto ex_system_type = ex_topology.value().Get("system_type");
3✔
216
        if (!ex_system_type) {
3✔
217
                return expected::unexpected(MakeError(
×
218
                        ParseError,
219
                        "Failed to find 'system_type' in topology file '" + topology_file_path
×
220
                                + "': " + ex_system_type.error().message));
×
221
        }
222

223
        auto ex_system_type_str = ex_system_type.value().Get<string>();
3✔
224
        if (!ex_system_type_str) {
3✔
225
                return expected::unexpected(MakeError(
×
226
                        ParseError,
227
                        "Failed to parse 'system_type' as string in topology file '" + topology_file_path
×
228
                                + "': " + ex_system_type_str.error().message));
×
229
        }
230

231
        return ex_system_type_str.value();
3✔
232
}
233
#endif // MENDER_USE_YAML_CPP
234

235
expected::ExpectedString MenderContext::GetDeviceType() {
300✔
236
        string device_type_fpath;
237
        if (config_.device_type_file != "") {
300✔
238
                device_type_fpath = config_.device_type_file;
239
        } else {
240
                device_type_fpath = path::Join(config_.paths.GetDataStore(), "device_type");
594✔
241
        }
242
        auto ex_is = io::OpenIfstream(device_type_fpath);
300✔
243
        if (!ex_is) {
300✔
244
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
18✔
245
        }
246

247
        auto &is = ex_is.value();
291✔
248
        string line;
249
        errno = 0;
291✔
250
        getline(is, line);
291✔
251
        if (is.bad()) {
291✔
252
                int io_errno = errno;
×
253
                error::Error err {
254
                        generic_category().default_error_condition(io_errno),
×
255
                        "Failed to read device type from '" + device_type_fpath + "'"};
×
256
                return expected::ExpectedString(expected::unexpected(err));
×
257
        }
258

259
        const string::size_type eq_pos = 12;
260
        if (line.substr(0, eq_pos) != "device_type=") {
291✔
261
                auto err = MakeError(ParseError, "Failed to parse device_type data '" + line + "'");
6✔
262
                return expected::ExpectedString(expected::unexpected(err));
6✔
263
        }
264

265
        string ret = line.substr(eq_pos, string::npos);
288✔
266

267
        if (!is.eof()) {
288✔
268
                errno = 0;
287✔
269
                getline(is, line);
287✔
270
                if ((line != "") || (!is.eof())) {
287✔
271
                        auto err = MakeError(ValueError, "Trailing device_type data");
4✔
272
                        return expected::ExpectedString(expected::unexpected(err));
4✔
273
                }
274
        }
275

276
        return expected::ExpectedString(ret);
286✔
277
}
278

279
// This function determines whether we return the system_type from the topology
280
// or the device_type. The system_type should be used rather than the device_type in two places:
281
//   1) When we poll for a deployment and the device_tier is set to `system`
282
//   2) When matching the artifact context when installing a mender-orchestrator manifest
283
expected::ExpectedString MenderContext::GetCompatibleType(const string &payload_type) {
128✔
284
        if (config_.device_tier == device_tier::kSystem) {
128✔
285
#ifdef MENDER_USE_YAML_CPP
286
                if (payload_type == "") {
4✔
287
                        // Deployment polling -> use system_type
288
                        return GetSystemType();
2✔
289
                } else if (payload_type == orchestrator_manifest_payload_type) {
2✔
290
                        // Manifest installtion -> use system_type
291
                        return GetSystemType();
1✔
292
                } else {
293
                        // Regular artifact processing (updated in standalone mode) -> use device_type
294
                        return GetDeviceType();
1✔
295
                }
296
#else
297
                return expected::unexpected(MakeError(
298
                        ValueError, "DeviceTier is 'system', but MENDER_USE_YAML_CPP is not enabled"));
299
#endif // MENDER_USE_YAML_CPP
300
        }
301

302
        // Regular device, always use device_type
303
        return GetDeviceType();
124✔
304
}
305

306
bool CheckClearsMatch(const string &to_match, const string &clears_string) {
275✔
307
        if (clears_string.empty()) {
275✔
308
                return to_match.empty();
10✔
309
        }
310

311
        vector<std::string> sub_strings;
312
        string escaped;
313
        for (const auto chr : clears_string) {
4,342✔
314
                if (chr == '*') {
4,077✔
315
                        sub_strings.push_back(escaped);
144✔
316
                        escaped.clear();
317
                } else {
318
                        escaped.push_back(chr);
3,933✔
319
                }
320
        }
321
        sub_strings.push_back(escaped);
265✔
322

323
        // Make sure that that front of vector starts at index 0
324
        if (sub_strings.front() != ""
265✔
325
                && to_match.compare(0, sub_strings.front().size(), sub_strings.front()) != 0) {
265✔
326
                return false;
327
        }
328
        // Checks if no trailing wildcard
329
        if (sub_strings.back() != ""
58✔
330
                && to_match.compare(
58✔
331
                           to_match.size() - sub_strings.back().size(), to_match.size(), sub_strings.back())
332
                           != 0) {
333
                return false;
334
        }
335

336
        // Iterate over substrings, set boundary if found to avoid
337
        // matching same substring twice
338
        size_t boundary = 0;
339
        for (const auto &str : sub_strings) {
156✔
340
                if (!str.empty()) {
109✔
341
                        size_t find = to_match.find(str, boundary);
57✔
342
                        if (find == string::npos) {
57✔
343
                                return false;
344
                        }
345
                        boundary = find + str.size();
55✔
346
                }
347
        }
348
        return true;
349
}
265✔
350

351
error::Error FilterProvides(
93✔
352
        const ProvidesData &new_provides,
353
        const ClearsProvidesData &clears_provides,
354
        ProvidesData &to_modify) {
355
        // Use clears_provides to filter out unwanted provides.
356
        for (auto to_clear : clears_provides) {
257✔
357
                set<string> keys;
358
                for (auto provide : to_modify) {
411✔
359
                        if (CheckClearsMatch(provide.first, to_clear)) {
247✔
360
                                keys.insert(provide.first);
32✔
361
                        }
362
                }
247✔
363
                for (auto key : keys) {
196✔
364
                        to_modify.erase(key);
32✔
365
                }
366
        }
367

368
        // Now add the provides from the new_provides set.
369
        for (auto provide : new_provides) {
214✔
370
                to_modify[provide.first] = provide.second;
371
        }
121✔
372

373
        return error::NoError;
93✔
374
}
375

376
error::Error MenderContext::CommitArtifactData(
94✔
377
        string artifact_name,
378
        string artifact_group,
379
        const optional<ProvidesData> &new_provides,
380
        const optional<ClearsProvidesData> &clears_provides,
381
        function<error::Error(kv_db::Transaction &)> txn_func) {
382
        return mender_store_.WriteTransaction([&](kv_db::Transaction &txn) {
282✔
383
                auto exp_existing = LoadProvides(txn);
94✔
384
                if (!exp_existing) {
94✔
385
                        return exp_existing.error();
×
386
                }
387
                auto modified_provides = exp_existing.value();
94✔
388

389
                error::Error err;
94✔
390
                if (!new_provides && !clears_provides) {
94✔
391
                        // Neither provides nor clear_provides came with the artifact. This means
392
                        // erase everything. `artifact_name` and `artifact_group` will still be
393
                        // preserved through special cases below.
394
                        modified_provides.clear();
395
                } else if (!new_provides) {
88✔
396
                        // No new provides came with the artifact. This means filter what we have,
397
                        // but don't add any new provides fields.
398
                        ProvidesData empty_provides;
399
                        err = FilterProvides(empty_provides, clears_provides.value(), modified_provides);
42✔
400
                } else if (!clears_provides) {
67✔
401
                        // Missing clears_provides is equivalent to `["*"]`, for historical reasons.
402
                        modified_provides = new_provides.value();
3✔
403
                } else {
404
                        // Standard case, filter existing provides using clears_provides, and then
405
                        // add new ones on top.
406
                        err = FilterProvides(new_provides.value(), clears_provides.value(), modified_provides);
128✔
407
                }
408
                if (err != error::NoError) {
94✔
409
                        return err;
×
410
                }
411

412
                if (artifact_name != "") {
94✔
413
                        modified_provides["artifact_name"] = artifact_name;
188✔
414
                }
415
                if (artifact_group != "") {
94✔
416
                        modified_provides["artifact_group"] = artifact_group;
8✔
417
                }
418

419
                string artifact_provides_str {"{"};
94✔
420
                for (const auto &it : modified_provides) {
308✔
421
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
214✔
422
                                artifact_provides_str += "\"" + json::EscapeString(it.first) + "\":" + "\""
230✔
423
                                                                                 + json::EscapeString(it.second) + "\",";
345✔
424
                        }
425
                }
426

427
                // if some key-value pairs were added, replace the trailing comma with the
428
                // closing '}' to make a valid JSON
429
                if (artifact_provides_str != "{") {
94✔
430
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
68✔
431
                } else {
432
                        // set to an empty value for consistency with the other two items
433
                        artifact_provides_str = "";
26✔
434
                }
435

436
                if (modified_provides["artifact_name"] != "") {
188✔
437
                        err = txn.Write(
94✔
438
                                artifact_name_key,
439
                                common::ByteVectorFromString(modified_provides["artifact_name"]));
282✔
440
                        if (err != error::NoError) {
94✔
441
                                return err;
×
442
                        }
443
                } else {
444
                        // This should not happen.
445
                        AssertOrReturnError(false);
×
446
                }
447

448
                if (modified_provides["artifact_group"] != "") {
188✔
449
                        err = txn.Write(
5✔
450
                                artifact_group_key,
451
                                common::ByteVectorFromString(modified_provides["artifact_group"]));
20✔
452
                } else {
453
                        err = txn.Remove(artifact_group_key);
178✔
454
                }
455
                if (err != error::NoError) {
94✔
456
                        return err;
×
457
                }
458

459
                if (artifact_provides_str != "") {
94✔
460
                        err = txn.Write(
68✔
461
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
136✔
462
                        if (err != error::NoError) {
68✔
463
                                return err;
×
464
                        }
465
                }
466
                return txn_func(txn);
94✔
467
        });
188✔
468
}
469

470
expected::ExpectedBool MenderContext::MatchesArtifactDepends(const artifact::HeaderView &hdr_view) {
111✔
471
        auto ex_compatible_type = GetCompatibleType(hdr_view.type_info.type);
111✔
472
        if (!ex_compatible_type) {
111✔
473
                return expected::unexpected(ex_compatible_type.error());
4✔
474
        }
475
        auto &compatible_type = ex_compatible_type.value();
109✔
476

477
        auto ex_provides = LoadProvides();
109✔
478
        if (!ex_provides) {
109✔
479
                return expected::unexpected(ex_provides.error());
×
480
        }
481
        auto &provides = ex_provides.value();
109✔
482
        return ArtifactMatchesContext(provides, compatible_type, hdr_view);
109✔
483
}
484

485
expected::ExpectedBool ArtifactMatchesContext(
124✔
486
        const ProvidesData &provides,
487
        const string &compatible_type,
488
        const artifact::HeaderView &hdr_view) {
489
        using common::MapContainsStringKey;
490
        if (!MapContainsStringKey(provides, "artifact_name")) {
248✔
491
                return expected::unexpected(
×
492
                        MakeError(ValueError, "Missing artifact_name value in provides"));
×
493
        }
494

495
        auto hdr_depends = hdr_view.GetDepends();
124✔
496
        AssertOrReturnUnexpected(hdr_depends["device_type"].size() > 0);
250✔
497
        if (!common::VectorContainsString(hdr_depends["device_type"], compatible_type)) {
246✔
498
                log::Error("Artifact device type doesn't match");
4✔
499
                return false;
500
        }
501
        hdr_depends.erase("device_type");
121✔
502

503
        AssertOrReturnUnexpected(
145✔
504
                !MapContainsStringKey(hdr_depends, "artifact_name")
505
                || (hdr_depends["artifact_name"].size() > 0));
506

507
        AssertOrReturnUnexpected(
142✔
508
                !MapContainsStringKey(hdr_depends, "artifact_group")
509
                || (hdr_depends["artifact_group"].size() > 0));
510

511
        for (auto it : hdr_depends) {
130✔
512
                if (!common::MapContainsStringKey(provides, it.first)) {
17✔
513
                        log::Error("Missing '" + it.first + "' in provides, required by artifact depends");
4✔
514
                        return false;
515
                }
516
                if (!common::VectorContainsString(hdr_depends[it.first], provides.at(it.first))) {
15✔
517
                        log::Error(
4✔
518
                                "Provides value '" + provides.at(it.first) + "' doesn't match any of the '"
4✔
519
                                + it.first + "' artifact depends ("
8✔
520
                                + common::StringVectorToString(hdr_depends[it.first]) + ")");
16✔
521
                        return false;
522
                }
523
        }
17✔
524

525
        return true;
526
}
527

528
} // namespace context
529
} // namespace update
530
} // 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