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

mendersoftware / mender / 1022567176

02 Oct 2023 07:50AM UTC coverage: 80.127% (+2.5%) from 77.645%
1022567176

push

gitlab-ci

kacf
chore: Centralize selection of `std::filesystem` library.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

6447 of 8046 relevant lines covered (80.13%)

9912.21 hits per line

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

85.2
/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 <regex>
21
#include <set>
22

23
#include <artifact/artifact.hpp>
24
#include <common/common.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
namespace mender {
34
namespace update {
35
namespace context {
36

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

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

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

60
const int MenderContext::standalone_data_version {1};
61

62
const MenderContextErrorCategoryClass MenderContextErrorCategory;
63

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

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

93
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
256✔
94
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
261✔
95
}
96

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

115
        return error::NoError;
291✔
116
#else
117
        return error::NoError;
118
#endif
119
}
120

121
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
995✔
122
        return mender_store_;
995✔
123
}
124

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

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

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

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

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

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

190
        return ret;
87✔
191
}
192

193
expected::ExpectedString MenderContext::GetDeviceType() {
166✔
194
        string device_type_fpath = path::Join(config_.paths.GetDataStore(), "device_type");
166✔
195
        auto ex_is = io::OpenIfstream(device_type_fpath);
166✔
196
        if (!ex_is) {
166✔
197
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
8✔
198
        }
199

200
        auto &is = ex_is.value();
162✔
201
        string line;
202
        errno = 0;
162✔
203
        getline(is, line);
162✔
204
        if (is.bad()) {
162✔
205
                int io_errno = errno;
×
206
                error::Error err {
207
                        generic_category().default_error_condition(io_errno),
×
208
                        "Failed to read device type from '" + device_type_fpath + "'"};
×
209
                return expected::ExpectedString(expected::unexpected(err));
×
210
        }
211

212
        const string::size_type eq_pos = 12;
213
        if (line.substr(0, eq_pos) != "device_type=") {
324✔
214
                auto err = MakeError(ParseError, "Failed to parse device_type data '" + line + "'");
6✔
215
                return expected::ExpectedString(expected::unexpected(err));
6✔
216
        }
217

218
        string ret = line.substr(eq_pos, string::npos);
159✔
219

220
        if (!is.eof()) {
159✔
221
                errno = 0;
158✔
222
                getline(is, line);
158✔
223
                if ((line != "") || (!is.eof())) {
158✔
224
                        auto err = MakeError(ValueError, "Trailing device_type data");
4✔
225
                        return expected::ExpectedString(expected::unexpected(err));
4✔
226
                }
227
        }
228

229
        return expected::ExpectedString(ret);
157✔
230
}
231

232
static error::Error FilterProvides(
71✔
233
        const ProvidesData &new_provides,
234
        const ClearsProvidesData &clears_provides,
235
        ProvidesData &to_modify) {
236
        // Use clears_provides to filter out unwanted provides.
237
        for (auto to_clear : clears_provides) {
207✔
238
                string escaped;
239
                // Potential to escape every character, though unlikely.
240
                escaped.reserve(to_clear.size() * 2);
136✔
241
                // Notable exception: '*', since it has special handling as a glob character.
242
                string meta_characters {".^$+()[]{}|?"};
136✔
243
                for (const auto chr : to_clear) {
2,701✔
244
                        if (chr == '*') {
2,565✔
245
                                // Turn every '*' glob wildcard into '.*' regex wildcard.
246
                                escaped.push_back('.');
70✔
247
                        } else if (any_of(meta_characters.begin(), meta_characters.end(), [chr](char c) {
2,495✔
248
                                                   return chr == c;
249
                                           })) {
250
                                // Escape every regex special character except '*'.
251
                                escaped.push_back('\\');
103✔
252
                        }
253
                        escaped.push_back(chr);
2,565✔
254
                }
255

256
                regex compiled;
272✔
257
                auto err = error::ExceptionToErrorOrAbort(
258
                        [&compiled, &escaped]() { compiled.assign(escaped, regex_constants::basic); });
272✔
259
                // Should not be possible, since the whole regex is escaped.
260
                AssertOrReturnError(err == error::NoError);
136✔
261

262
                set<string> keys;
263
                for (auto provide : to_modify) {
490✔
264
                        if (regex_match(provide.first.begin(), provide.first.end(), compiled)) {
177✔
265
                                keys.insert(provide.first);
14✔
266
                        }
267
                }
268
                for (auto key : keys) {
150✔
269
                        to_modify.erase(key);
14✔
270
                }
271
        }
272

273
        // Now add the provides from the new_provides set.
274
        for (auto provide : new_provides) {
287✔
275
                to_modify[provide.first] = provide.second;
276
        }
277

278
        return error::NoError;
71✔
279
}
280

281
error::Error MenderContext::CommitArtifactData(
78✔
282
        string artifact_name,
283
        string artifact_group,
284
        const optional<ProvidesData> &new_provides,
285
        const optional<ClearsProvidesData> &clears_provides,
286
        function<error::Error(kv_db::Transaction &)> txn_func) {
287
        return mender_store_.WriteTransaction([&](kv_db::Transaction &txn) {
78✔
288
                auto exp_existing = LoadProvides(txn);
78✔
289
                if (!exp_existing) {
78✔
290
                        return exp_existing.error();
×
291
                }
292
                auto modified_provides = exp_existing.value();
78✔
293

294
                error::Error err;
78✔
295
                if (!new_provides && !clears_provides) {
150✔
296
                        // Neither provides nor clear_provides came with the artifact. This means
297
                        // erase everything. `artifact_name` and `artifact_group` will still be
298
                        // preserved through special cases below.
299
                        modified_provides.clear();
300
                } else if (!new_provides) {
74✔
301
                        // No new provides came with the artifact. This means filter what we have,
302
                        // but don't add any new provides fields.
303
                        ProvidesData empty_provides;
304
                        err = FilterProvides(empty_provides, clears_provides.value(), modified_provides);
4✔
305
                } else if (!clears_provides) {
72✔
306
                        // Missing clears_provides is equivalent to `["*"]`, for historical reasons.
307
                        modified_provides = new_provides.value();
3✔
308
                } else {
309
                        // Standard case, filter existing provides using clears_provides, and then
310
                        // add new ones on top.
311
                        err = FilterProvides(new_provides.value(), clears_provides.value(), modified_provides);
138✔
312
                }
313
                if (err != error::NoError) {
78✔
314
                        return err;
×
315
                }
316

317
                if (artifact_name != "") {
78✔
318
                        modified_provides["artifact_name"] = artifact_name;
156✔
319
                }
320
                if (artifact_group != "") {
78✔
321
                        modified_provides["artifact_group"] = artifact_group;
8✔
322
                }
323

324
                string artifact_provides_str {"{"};
78✔
325
                for (const auto &it : modified_provides) {
269✔
326
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
191✔
327
                                artifact_provides_str +=
328
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
216✔
329
                        }
330
                }
331

332
                // if some key-value pairs were added, replace the trailing comma with the
333
                // closing '}' to make a valid JSON
334
                if (artifact_provides_str != "{") {
78✔
335
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
73✔
336
                } else {
337
                        // set to an empty value for consistency with the other two items
338
                        artifact_provides_str = "";
5✔
339
                }
340

341
                if (modified_provides["artifact_name"] != "") {
234✔
342
                        err = txn.Write(
78✔
343
                                artifact_name_key,
344
                                common::ByteVectorFromString(modified_provides["artifact_name"]));
234✔
345
                        if (err != error::NoError) {
78✔
346
                                return err;
×
347
                        }
348
                } else {
349
                        // This should not happen.
350
                        AssertOrReturnError(false);
×
351
                }
352

353
                if (modified_provides["artifact_group"] != "") {
234✔
354
                        err = txn.Write(
5✔
355
                                artifact_group_key,
356
                                common::ByteVectorFromString(modified_provides["artifact_group"]));
15✔
357
                } else {
358
                        err = txn.Remove(artifact_group_key);
146✔
359
                }
360
                if (err != error::NoError) {
78✔
361
                        return err;
×
362
                }
363

364
                if (artifact_provides_str != "") {
78✔
365
                        err = txn.Write(
73✔
366
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
73✔
367
                        if (err != error::NoError) {
73✔
368
                                return err;
×
369
                        }
370
                }
371
                return txn_func(txn);
78✔
372
        });
156✔
373
}
374

375
expected::ExpectedBool MenderContext::MatchesArtifactDepends(const artifact::HeaderView &hdr_view) {
51✔
376
        auto ex_dev_type = GetDeviceType();
51✔
377
        if (!ex_dev_type) {
51✔
378
                return expected::unexpected(ex_dev_type.error());
2✔
379
        }
380
        auto ex_provides = LoadProvides();
50✔
381
        if (!ex_provides) {
50✔
382
                return expected::unexpected(ex_provides.error());
×
383
        }
384
        auto &provides = ex_provides.value();
50✔
385
        return ArtifactMatchesContext(
386
                provides, ex_dev_type.value(), hdr_view.header_info, hdr_view.type_info);
50✔
387
}
388

389
expected::ExpectedBool ArtifactMatchesContext(
65✔
390
        const ProvidesData &provides,
391
        const string &device_type,
392
        const artifact::HeaderInfo &hdr_info,
393
        const artifact::TypeInfo &type_info) {
394
        if (!common::MapContainsStringKey(provides, "artifact_name")) {
130✔
395
                return expected::unexpected(
×
396
                        MakeError(ValueError, "Missing artifact_name value in provides"));
×
397
        }
398

399
        const auto &hdr_depends = hdr_info.depends;
400
        AssertOrReturnUnexpected(hdr_depends.device_type.size() > 0);
67✔
401
        if (!common::VectorContainsString(hdr_depends.device_type, device_type)) {
64✔
402
                log::Debug("Artifact device type doesn't match");
4✔
403
                return false;
404
        }
405

406
        if (hdr_depends.artifact_name) {
62✔
407
                AssertOrReturnUnexpected(hdr_depends.artifact_name->size() > 0);
12✔
408
                if (!common::VectorContainsString(
18✔
409
                                *hdr_depends.artifact_name, provides.at("artifact_name"))) {
18✔
410
                        log::Debug("Artifact name doesn't match");
2✔
411
                        return false;
412
                }
413
        }
414

415
        if (hdr_depends.artifact_group) {
60✔
416
                AssertOrReturnUnexpected(hdr_depends.artifact_group->size() > 0);
11✔
417
                if (!common::MapContainsStringKey(provides, "artifact_group")) {
16✔
418
                        log::Debug(
1✔
419
                                "Missing artifact_group value in provides, required by artifact header info depends");
2✔
420
                        return false;
421
                }
422
                if (!common::VectorContainsString(
14✔
423
                                *hdr_depends.artifact_group, provides.at("artifact_group"))) {
14✔
424
                        log::Debug("Artifact group doesn't match");
2✔
425
                        return false;
426
                }
427
        }
428

429
        const auto &ti_depends = type_info.artifact_depends;
430
        if (!ti_depends) {
57✔
431
                // nothing more to check
432
                return true;
433
        }
434
        for (auto it : *ti_depends) {
5✔
435
                if (!common::MapContainsStringKey(provides, it.first)) {
3✔
436
                        log::Debug(
1✔
437
                                "Missing '" + it.first + "' in provides, required by artifact type info depends");
2✔
438
                        return false;
439
                }
440
                if (provides.at(it.first) != it.second) {
2✔
441
                        log::Debug(
1✔
442
                                "'" + it.first + "' artifact type info depends value '" + it.second
2✔
443
                                + "' doesn't match provides value '" + provides.at(it.first) + "'");
2✔
444
                        return false;
445
                }
446
        }
447

448
        return true;
449
}
450

451
} // namespace context
452
} // namespace update
453
} // 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

© 2025 Coveralls, Inc