• 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

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() {
447✔
110
#ifdef MENDER_USE_LMDB
111
        auto err = mender_store_.Open(path::Join(config_.paths.GetDataStore(), "mender-store"));
447✔
112
        if (error::NoError != err) {
447✔
113
                return err;
1✔
114
        }
115
        err = mender_store_.Remove(auth_token_name);
446✔
116
        if (error::NoError != err) {
446✔
117
                // key not existing in the DB is not treated as an error so this must be
118
                // a real error
119
                return err;
×
120
        }
121
        err = mender_store_.Remove(auth_token_cache_invalidator_name);
446✔
122
        if (error::NoError != err) {
446✔
123
                // same as above -- a real error
124
                return err;
×
125
        }
126

127
        return error::NoError;
446✔
128
#else
129
        return error::NoError;
130
#endif
131
}
132

133
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
1,744✔
134
        return mender_store_;
1,744✔
135
}
136

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

152
ExpectedProvidesData MenderContext::LoadProvides(kv_db::Transaction &txn) {
508✔
153
        string artifact_name;
154
        string artifact_group;
155
        string artifact_provides_str;
156

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

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

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

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

202
        return ret;
118✔
203
}
204

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

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

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

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

235
        return ex_system_type_str.value();
3✔
236
}
237
#endif // MENDER_USE_YAML_CPP
238

239
expected::ExpectedString MenderContext::GetDeviceType() {
297✔
240
        string device_type_fpath;
241
        if (config_.device_type_file != "") {
297✔
242
                device_type_fpath = config_.device_type_file;
243
        } else {
244
                device_type_fpath = path::Join(config_.paths.GetDataStore(), "device_type");
588✔
245
        }
246
        auto ex_is = io::OpenIfstream(device_type_fpath);
297✔
247
        if (!ex_is) {
297✔
248
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
12✔
249
        }
250

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

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

269
        string ret = line.substr(eq_pos, string::npos);
288✔
270

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

280
        return expected::ExpectedString(ret);
286✔
281
}
282

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

306
        // Regular device, always use device_type
307
        return GetDeviceType();
121✔
308
}
309

310
bool CheckClearsMatch(const string &to_match, const string &clears_string) {
275✔
311
        if (clears_string.empty()) {
275✔
312
                return to_match.empty();
10✔
313
        }
314

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

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

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

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

372
        // Now add the provides from the new_provides set.
373
        for (auto provide : new_provides) {
214✔
374
                to_modify[provide.first] = provide.second;
375
        }
121✔
376

377
        return error::NoError;
93✔
378
}
379

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

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

416
                if (artifact_name != "") {
94✔
417
                        modified_provides["artifact_name"] = artifact_name;
188✔
418
                }
419
                if (artifact_group != "") {
94✔
420
                        modified_provides["artifact_group"] = artifact_group;
8✔
421
                }
422

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

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

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

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

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

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

481
        auto ex_provides = LoadProvides();
109✔
482
        if (!ex_provides) {
109✔
483
                return expected::unexpected(ex_provides.error());
×
484
        }
485
        auto &provides = ex_provides.value();
109✔
486
        return ArtifactMatchesContext(provides, compatible_type, hdr_view);
109✔
487
}
488

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

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

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

511
        AssertOrReturnUnexpected(
142✔
512
                !MapContainsStringKey(hdr_depends, "artifact_group")
513
                || (hdr_depends["artifact_group"].size() > 0));
514

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

529
        return true;
530
}
531

532
} // namespace context
533
} // namespace update
534
} // 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