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

mendersoftware / mender / 974575668

21 Aug 2023 12:04PM UTC coverage: 78.829% (-0.05%) from 78.877%
974575668

push

gitlab-ci

kacf
chore: Implement pushing of logs to the server.

Ticket: MEN-6581

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

18 of 18 new or added lines in 2 files covered. (100.0%)

5492 of 6967 relevant lines covered (78.83%)

238.75 hits per line

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

83.77
/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:
2✔
83
                return "No update in progress";
2✔
84
        case UnexpectedHttpResponse:
×
85
                return "Unexpected HTTP response";
×
86
        case StateDataStoreCountExceededError:
1✔
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) {
211✔
94
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
422✔
95
}
96

97
error::Error MenderContext::Initialize() {
183✔
98
#if MENDER_USE_LMDB
99
        auto err = mender_store_.Open(path::Join(config_.paths.GetDataStore(), "mender-store"));
549✔
100
        if (error::NoError != err) {
183✔
101
                return err;
×
102
        }
103
        err = mender_store_.Remove(auth_token_name);
183✔
104
        if (error::NoError != err) {
183✔
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);
183✔
110
        if (error::NoError != err) {
183✔
111
                // same as above -- a real error
112
                return err;
×
113
        }
114

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

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

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

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

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

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

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

179
        auto children = ex_children.value();
126✔
180
        if (!all_of(children.cbegin(), children.cend(), [](const json::ChildrenMap::value_type &it) {
63✔
181
                        return it.second.IsString();
105✔
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()) {
166✔
187
                ret[it.first] = it.second.GetString().value();
104✔
188
        }
189

190
        return ret;
62✔
191
}
192

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

200
        auto &is = ex_is.value();
101✔
201
        string line;
202✔
202
        errno = 0;
101✔
203
        getline(is, line);
101✔
204
        if (is.bad()) {
101✔
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;
101✔
213
        if (line.substr(0, eq_pos) != "device_type=") {
101✔
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);
196✔
219

220
        if (!is.eof()) {
98✔
221
                errno = 0;
97✔
222
                getline(is, line);
97✔
223
                if ((line != "") || (!is.eof())) {
97✔
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);
96✔
230
}
231

232
static error::Error FilterProvides(
40✔
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) {
127✔
238
                string escaped;
87✔
239
                // Potential to escape every character, though unlikely.
240
                escaped.reserve(to_clear.size() * 2);
87✔
241
                // Notable exception: '*', since it has special handling as a glob character.
242
                string meta_characters {".^$+()[]{}|?"};
87✔
243
                for (const auto chr : to_clear) {
1,639✔
244
                        if (chr == '*') {
1,552✔
245
                                // Turn every '*' glob wildcard into '.*' regex wildcard.
246
                                escaped.push_back('.');
39✔
247
                        } else if (any_of(meta_characters.begin(), meta_characters.end(), [chr](char c) {
1,513✔
248
                                                   return chr == c;
17,606✔
249
                                           })) {
250
                                // Escape every regex special character except '*'.
251
                                escaped.push_back('\\');
50✔
252
                        }
253
                        escaped.push_back(chr);
1,552✔
254
                }
255

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

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

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

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

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

294
                error::Error err;
92✔
295
                if (!new_provides && !clears_provides) {
46✔
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();
3✔
300
                } else if (!new_provides) {
43✔
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;
2✔
304
                        err = FilterProvides(empty_provides, clears_provides.value(), modified_provides);
2✔
305
                } else if (!clears_provides) {
41✔
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);
38✔
312
                }
313
                if (err != error::NoError) {
46✔
314
                        return err;
×
315
                }
316

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

324
                string artifact_provides_str {"{"};
92✔
325
                for (const auto &it : modified_provides) {
165✔
326
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
119✔
327
                                artifact_provides_str +=
328
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
68✔
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 != "{") {
46✔
335
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
42✔
336
                } else {
337
                        // set to an empty value for consistency with the other two items
338
                        artifact_provides_str = "";
4✔
339
                }
340

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

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

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

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

389
expected::ExpectedBool ArtifactMatchesContext(
39✔
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")) {
39✔
395
                return expected::unexpected(
×
396
                        MakeError(ValueError, "Missing artifact_name value in provides"));
×
397
        }
398

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

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

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

429
        const auto &ti_depends = type_info.artifact_depends;
34✔
430
        if (!ti_depends) {
34✔
431
                // nothing more to check
432
                return true;
31✔
433
        }
434
        for (auto it : *ti_depends) {
4✔
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;
1✔
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) + "'");
3✔
444
                        return false;
1✔
445
                }
446
        }
447

448
        return true;
1✔
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