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

mendersoftware / mender / 1430702423

28 Aug 2024 02:32PM UTC coverage: 76.323% (+0.6%) from 75.74%
1430702423

push

gitlab-ci

web-flow
Merge pull request #1642 from kacf/standalone_state_machine

refac: Add --stop-after flag and make state machine for standalone.

547 of 664 new or added lines in 9 files covered. (82.38%)

1 existing line in 1 file now uncovered.

7369 of 9655 relevant lines covered (76.32%)

11318.18 hits per line

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

84.69
/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/error.hpp>
25
#include <common/expected.hpp>
26
#include <common/io.hpp>
27
#include <common/json.hpp>
28
#include <common/key_value_database.hpp>
29
#include <common/log.hpp>
30
#include <common/path.hpp>
31

32
namespace mender {
33
namespace update {
34
namespace context {
35

36
using namespace std;
37
namespace artifact = mender::artifact;
38
namespace common = mender::common;
39
namespace error = mender::common::error;
40
namespace expected = mender::common::expected;
41
namespace io = mender::common::io;
42
namespace json = mender::common::json;
43
namespace kv_db = mender::common::key_value_database;
44
namespace log = mender::common::log;
45
namespace path = mender::common::path;
46

47
const string MenderContext::broken_artifact_name_suffix {"_INCONSISTENT"};
48

49
const string MenderContext::artifact_name_key {"artifact-name"};
50
const string MenderContext::artifact_group_key {"artifact-group"};
51
const string MenderContext::artifact_provides_key {"artifact-provides"};
52
const string MenderContext::standalone_state_key {"standalone-state"};
53
const string MenderContext::state_data_key {"state"};
54
const string MenderContext::state_data_key_uncommitted {"state-uncommitted"};
55
const string MenderContext::update_control_maps {"update-control-maps"};
56
const string MenderContext::auth_token_name {"authtoken"};
57
const string MenderContext::auth_token_cache_invalidator_name {"auth-token-cache-invalidator"};
58

59
const int MenderContext::standalone_data_version {2};
60

61
const MenderContextErrorCategoryClass MenderContextErrorCategory;
62

63
const char *MenderContextErrorCategoryClass::name() const noexcept {
×
64
        return "MenderContextErrorCategory";
×
65
}
66

67
string MenderContextErrorCategoryClass::message(int code) const {
3✔
68
        switch (code) {
3✔
69
        case NoError:
70
                return "Success";
×
71
        case ParseError:
72
                return "Parse error";
×
73
        case ValueError:
74
                return "Value error";
×
75
        case NoSuchUpdateModuleError:
76
                return "Update Module not found for given artifact type";
×
77
        case DatabaseValueError:
78
                return "Value in database is invalid or corrupted";
×
79
        case RebootRequiredError:
80
                return "Reboot required";
×
81
        case NoUpdateInProgressError:
82
                return "No update in progress";
2✔
83
        case UnexpectedHttpResponse:
84
                return "Unexpected HTTP response";
×
85
        case StateDataStoreCountExceededError:
86
                return "State data store count exceeded";
1✔
87
        case WrongOperationError:
NEW
88
                return "Operation cannot be done in this state";
×
89
        }
90
        assert(false);
91
        return "Unknown";
×
92
}
93

94
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
354✔
95
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
359✔
96
}
97

98
error::Error MenderContext::Initialize() {
424✔
99
#ifdef MENDER_USE_LMDB
100
        auto err = mender_store_.Open(path::Join(config_.paths.GetDataStore(), "mender-store"));
848✔
101
        if (error::NoError != err) {
424✔
102
                return err;
1✔
103
        }
104
        err = mender_store_.Remove(auth_token_name);
423✔
105
        if (error::NoError != err) {
423✔
106
                // key not existing in the DB is not treated as an error so this must be
107
                // a real error
108
                return err;
×
109
        }
110
        err = mender_store_.Remove(auth_token_cache_invalidator_name);
423✔
111
        if (error::NoError != err) {
423✔
112
                // same as above -- a real error
113
                return err;
×
114
        }
115

116
        return error::NoError;
423✔
117
#else
118
        return error::NoError;
119
#endif
120
}
121

122
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
1,571✔
123
        return mender_store_;
1,571✔
124
}
125

126
ExpectedProvidesData MenderContext::LoadProvides() {
405✔
127
        ExpectedProvidesData data;
128
        auto err = mender_store_.ReadTransaction([this, &data](kv_db::Transaction &txn) {
404✔
129
                data = LoadProvides(txn);
808✔
130
                if (!data) {
404✔
131
                        return data.error();
2✔
132
                }
133
                return error::NoError;
402✔
134
        });
405✔
135
        if (err != error::NoError) {
405✔
136
                return expected::unexpected(err);
6✔
137
        }
138
        return data;
139
}
140

141
ExpectedProvidesData MenderContext::LoadProvides(kv_db::Transaction &txn) {
498✔
142
        string artifact_name;
143
        string artifact_group;
144
        string artifact_provides_str;
145

146
        auto err = kv_db::ReadString(txn, artifact_name_key, artifact_name, true);
498✔
147
        if (err != error::NoError) {
498✔
148
                return expected::unexpected(err);
×
149
        }
150
        err = kv_db::ReadString(txn, artifact_group_key, artifact_group, true);
498✔
151
        if (err != error::NoError) {
498✔
152
                return expected::unexpected(err);
×
153
        }
154
        err = kv_db::ReadString(txn, artifact_provides_key, artifact_provides_str, true);
498✔
155
        if (err != error::NoError) {
498✔
156
                return expected::unexpected(err);
×
157
        }
158

159
        ProvidesData ret {};
498✔
160
        if (artifact_name != "") {
498✔
161
                ret["artifact_name"] = artifact_name;
980✔
162
        }
163
        if (artifact_group != "") {
498✔
164
                ret["artifact_group"] = artifact_group;
20✔
165
        }
166
        if (artifact_provides_str == "") {
498✔
167
                // nothing more to do
168
                return ret;
385✔
169
        }
170

171
        auto ex_j = json::Load(artifact_provides_str);
226✔
172
        if (!ex_j) {
113✔
173
                return expected::unexpected(ex_j.error());
2✔
174
        }
175
        auto ex_children = ex_j.value().GetChildren();
112✔
176
        if (!ex_children) {
112✔
177
                return expected::unexpected(ex_children.error());
×
178
        }
179

180
        auto children = ex_children.value();
112✔
181
        if (!all_of(children.cbegin(), children.cend(), [](const json::ChildrenMap::value_type &it) {
112✔
182
                        return it.second.IsString();
199✔
183
                })) {
184
                auto err = json::MakeError(json::TypeError, "Unexpected non-string data in provides");
2✔
185
                return expected::unexpected(err);
2✔
186
        }
187
        for (const auto &it : ex_children.value()) {
309✔
188
                ret[it.first] = it.second.GetString().value();
396✔
189
        }
190

191
        return ret;
111✔
192
}
193

194
expected::ExpectedString MenderContext::GetDeviceType() {
285✔
195
        string device_type_fpath;
196
        if (config_.device_type_file != "") {
285✔
197
                device_type_fpath = config_.device_type_file;
3✔
198
        } else {
199
                device_type_fpath = path::Join(config_.paths.GetDataStore(), "device_type");
564✔
200
        }
201
        auto ex_is = io::OpenIfstream(device_type_fpath);
285✔
202
        if (!ex_is) {
285✔
203
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
12✔
204
        }
205

206
        auto &is = ex_is.value();
279✔
207
        string line;
208
        errno = 0;
279✔
209
        getline(is, line);
279✔
210
        if (is.bad()) {
279✔
211
                int io_errno = errno;
×
212
                error::Error err {
213
                        generic_category().default_error_condition(io_errno),
×
214
                        "Failed to read device type from '" + device_type_fpath + "'"};
×
215
                return expected::ExpectedString(expected::unexpected(err));
×
216
        }
217

218
        const string::size_type eq_pos = 12;
219
        if (line.substr(0, eq_pos) != "device_type=") {
558✔
220
                auto err = MakeError(ParseError, "Failed to parse device_type data '" + line + "'");
6✔
221
                return expected::ExpectedString(expected::unexpected(err));
6✔
222
        }
223

224
        string ret = line.substr(eq_pos, string::npos);
276✔
225

226
        if (!is.eof()) {
276✔
227
                errno = 0;
275✔
228
                getline(is, line);
275✔
229
                if ((line != "") || (!is.eof())) {
275✔
230
                        auto err = MakeError(ValueError, "Trailing device_type data");
4✔
231
                        return expected::ExpectedString(expected::unexpected(err));
4✔
232
                }
233
        }
234

235
        return expected::ExpectedString(ret);
274✔
236
}
237

238
bool CheckClearsMatch(const string &to_match, const string &clears_string) {
275✔
239
        if (clears_string.empty()) {
275✔
240
                return to_match.empty();
10✔
241
        }
242

243
        vector<std::string> sub_strings;
265✔
244
        string escaped;
245
        for (const auto chr : clears_string) {
4,342✔
246
                if (chr == '*') {
4,077✔
247
                        sub_strings.push_back(escaped);
144✔
248
                        escaped.clear();
249
                } else {
250
                        escaped.push_back(chr);
3,933✔
251
                }
252
        }
253
        sub_strings.push_back(escaped);
265✔
254

255
        // Make sure that that front of vector starts at index 0
256
        if (sub_strings.front() != ""
265✔
257
                && to_match.compare(0, sub_strings.front().size(), sub_strings.front()) != 0) {
265✔
258
                return false;
259
        }
260
        // Checks if no trailing wildcard
261
        if (sub_strings.back() != ""
58✔
262
                && to_match.compare(
58✔
263
                           to_match.size() - sub_strings.back().size(), to_match.size(), sub_strings.back())
264
                           != 0) {
265
                return false;
266
        }
267

268
        // Iterate over substrings, set boundary if found to avoid
269
        // matching same substring twice
270
        size_t boundary = 0;
271
        for (const auto &str : sub_strings) {
156✔
272
                if (!str.empty()) {
109✔
273
                        size_t find = to_match.find(str, boundary);
57✔
274
                        if (find == string::npos) {
57✔
275
                                return false;
276
                        }
277
                        boundary = find + str.size();
55✔
278
                }
279
        }
280
        return true;
281
}
282

283
error::Error FilterProvides(
93✔
284
        const ProvidesData &new_provides,
285
        const ClearsProvidesData &clears_provides,
286
        ProvidesData &to_modify) {
287
        // Use clears_provides to filter out unwanted provides.
288
        for (auto to_clear : clears_provides) {
257✔
289
                set<string> keys;
290
                for (auto provide : to_modify) {
658✔
291
                        if (CheckClearsMatch(provide.first, to_clear)) {
247✔
292
                                keys.insert(provide.first);
32✔
293
                        }
294
                }
295
                for (auto key : keys) {
196✔
296
                        to_modify.erase(key);
32✔
297
                }
298
        }
299

300
        // Now add the provides from the new_provides set.
301
        for (auto provide : new_provides) {
335✔
302
                to_modify[provide.first] = provide.second;
303
        }
304

305
        return error::NoError;
93✔
306
}
307

308
error::Error MenderContext::CommitArtifactData(
94✔
309
        string artifact_name,
310
        string artifact_group,
311
        const optional<ProvidesData> &new_provides,
312
        const optional<ClearsProvidesData> &clears_provides,
313
        function<error::Error(kv_db::Transaction &)> txn_func) {
314
        return mender_store_.WriteTransaction([&](kv_db::Transaction &txn) {
94✔
315
                auto exp_existing = LoadProvides(txn);
94✔
316
                if (!exp_existing) {
94✔
317
                        return exp_existing.error();
×
318
                }
319
                auto modified_provides = exp_existing.value();
94✔
320

321
                error::Error err;
94✔
322
                if (!new_provides && !clears_provides) {
161✔
323
                        // Neither provides nor clear_provides came with the artifact. This means
324
                        // erase everything. `artifact_name` and `artifact_group` will still be
325
                        // preserved through special cases below.
326
                        modified_provides.clear();
327
                } else if (!new_provides) {
88✔
328
                        // No new provides came with the artifact. This means filter what we have,
329
                        // but don't add any new provides fields.
330
                        ProvidesData empty_provides;
331
                        err = FilterProvides(empty_provides, clears_provides.value(), modified_provides);
42✔
332
                } else if (!clears_provides) {
67✔
333
                        // Missing clears_provides is equivalent to `["*"]`, for historical reasons.
334
                        modified_provides = new_provides.value();
3✔
335
                } else {
336
                        // Standard case, filter existing provides using clears_provides, and then
337
                        // add new ones on top.
338
                        err = FilterProvides(new_provides.value(), clears_provides.value(), modified_provides);
128✔
339
                }
340
                if (err != error::NoError) {
94✔
341
                        return err;
×
342
                }
343

344
                if (artifact_name != "") {
94✔
345
                        modified_provides["artifact_name"] = artifact_name;
188✔
346
                }
347
                if (artifact_group != "") {
94✔
348
                        modified_provides["artifact_group"] = artifact_group;
8✔
349
                }
350

351
                string artifact_provides_str {"{"};
94✔
352
                for (const auto &it : modified_provides) {
308✔
353
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
214✔
354
                                artifact_provides_str +=
355
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
230✔
356
                        }
357
                }
358

359
                // if some key-value pairs were added, replace the trailing comma with the
360
                // closing '}' to make a valid JSON
361
                if (artifact_provides_str != "{") {
94✔
362
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
68✔
363
                } else {
364
                        // set to an empty value for consistency with the other two items
365
                        artifact_provides_str = "";
26✔
366
                }
367

368
                if (modified_provides["artifact_name"] != "") {
282✔
369
                        err = txn.Write(
94✔
370
                                artifact_name_key,
371
                                common::ByteVectorFromString(modified_provides["artifact_name"]));
282✔
372
                        if (err != error::NoError) {
94✔
373
                                return err;
×
374
                        }
375
                } else {
376
                        // This should not happen.
377
                        AssertOrReturnError(false);
×
378
                }
379

380
                if (modified_provides["artifact_group"] != "") {
282✔
381
                        err = txn.Write(
5✔
382
                                artifact_group_key,
383
                                common::ByteVectorFromString(modified_provides["artifact_group"]));
15✔
384
                } else {
385
                        err = txn.Remove(artifact_group_key);
178✔
386
                }
387
                if (err != error::NoError) {
94✔
388
                        return err;
×
389
                }
390

391
                if (artifact_provides_str != "") {
94✔
392
                        err = txn.Write(
68✔
393
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
68✔
394
                        if (err != error::NoError) {
68✔
395
                                return err;
×
396
                        }
397
                }
398
                return txn_func(txn);
94✔
399
        });
188✔
400
}
401

402
expected::ExpectedBool MenderContext::MatchesArtifactDepends(const artifact::HeaderView &hdr_view) {
110✔
403
        auto ex_dev_type = GetDeviceType();
110✔
404
        if (!ex_dev_type) {
110✔
405
                return expected::unexpected(ex_dev_type.error());
4✔
406
        }
407
        auto ex_provides = LoadProvides();
108✔
408
        if (!ex_provides) {
108✔
409
                return expected::unexpected(ex_provides.error());
×
410
        }
411
        auto &provides = ex_provides.value();
108✔
412
        return ArtifactMatchesContext(provides, ex_dev_type.value(), hdr_view);
108✔
413
}
414

415
expected::ExpectedBool ArtifactMatchesContext(
123✔
416
        const ProvidesData &provides, const string &device_type, const artifact::HeaderView &hdr_view) {
417
        using common::MapContainsStringKey;
418
        if (!MapContainsStringKey(provides, "artifact_name")) {
246✔
419
                return expected::unexpected(
×
420
                        MakeError(ValueError, "Missing artifact_name value in provides"));
×
421
        }
422

423
        auto hdr_depends = hdr_view.GetDepends();
123✔
424
        AssertOrReturnUnexpected(hdr_depends["device_type"].size() > 0);
248✔
425
        if (!common::VectorContainsString(hdr_depends["device_type"], device_type)) {
366✔
426
                log::Error("Artifact device type doesn't match");
4✔
427
                return false;
428
        }
429
        hdr_depends.erase("device_type");
240✔
430

431
        AssertOrReturnUnexpected(
154✔
432
                !MapContainsStringKey(hdr_depends, "artifact_name")
433
                || (hdr_depends["artifact_name"].size() > 0));
434

435
        AssertOrReturnUnexpected(
150✔
436
                !MapContainsStringKey(hdr_depends, "artifact_group")
437
                || (hdr_depends["artifact_group"].size() > 0));
438

439
        for (auto it : hdr_depends) {
140✔
440
                if (!common::MapContainsStringKey(provides, it.first)) {
17✔
441
                        log::Error("Missing '" + it.first + "' in provides, required by artifact depends");
4✔
442
                        return false;
443
                }
444
                if (!common::VectorContainsString(hdr_depends[it.first], provides.at(it.first))) {
15✔
445
                        log::Error(
4✔
446
                                "Provides value '" + provides.at(it.first) + "' doesn't match any of the '"
8✔
447
                                + it.first + "' artifact depends ("
8✔
448
                                + common::StringVectorToString(hdr_depends[it.first]) + ")");
20✔
449
                        return false;
450
                }
451
        }
452

453
        return true;
454
}
455

456
} // namespace context
457
} // namespace update
458
} // 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