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

mendersoftware / mender / 950534094

pending completion
950534094

push

gitlab-ci

kacf
chore: Add `StartsWith` and `EndsWith` generic utility functions.

Also use the latter in `states.cpp`.

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

1 of 1 new or added line in 1 file covered. (100.0%)

4931 of 6276 relevant lines covered (78.57%)

196.18 hits per line

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

80.11
/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 <regex>
20
#include <set>
21

22
#include <common/common.hpp>
23
#include <common/conf/paths.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/path.hpp>
30

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

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

45
const string MenderContext::broken_artifact_name_suffix {"_INCONSISTENT"};
46

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

57
const int MenderContext::standalone_data_version {1};
58

59
const MenderContextErrorCategoryClass MenderContextErrorCategory;
60

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

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

92
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
198✔
93
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
396✔
94
}
95

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

114
        return error::NoError;
157✔
115
#else
116
        return error::NoError;
117
#endif
118
}
119

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

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

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

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

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

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

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

189
        return ret;
56✔
190
}
191

192
expected::ExpectedString MenderContext::GetDeviceType() {
69✔
193
        string device_type_fpath = path::Join(config_.data_store_dir, "device_type");
138✔
194
        auto ex_is = io::OpenIfstream(device_type_fpath);
138✔
195
        if (!ex_is) {
69✔
196
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
2✔
197
        }
198

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

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

217
        string ret = line.substr(eq_pos, string::npos);
130✔
218

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

226
        return expected::ExpectedString(ret);
63✔
227
}
228

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

253
                regex compiled;
82✔
254
                auto err = error::ExceptionToErrorOrAbort(
255
                        [&compiled, &escaped]() { compiled.assign(escaped, regex_constants::basic); });
164✔
256
                // Should not be possible, since the whole regex is escaped.
257
                AssertOrReturnError(err == error::NoError);
82✔
258

259
                set<string> keys;
164✔
260
                for (auto provide : to_modify) {
145✔
261
                        if (regex_match(provide.first.begin(), provide.first.end(), compiled)) {
63✔
262
                                keys.insert(provide.first);
12✔
263
                        }
264
                }
265
                for (auto key : keys) {
94✔
266
                        to_modify.erase(key);
12✔
267
                }
268
        }
269

270
        // Now add the provides from the new_provides set.
271
        for (auto provide : new_provides) {
92✔
272
                to_modify[provide.first] = provide.second;
58✔
273
        }
274

275
        return error::NoError;
34✔
276
}
277

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

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

314
                if (artifact_name != "") {
40✔
315
                        modified_provides["artifact_name"] = artifact_name;
40✔
316
                }
317
                if (artifact_group != "") {
40✔
318
                        modified_provides["artifact_group"] = artifact_group;
4✔
319
                }
320

321
                string artifact_provides_str {"{"};
80✔
322
                for (const auto &it : modified_provides) {
148✔
323
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
108✔
324
                                artifact_provides_str +=
325
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
63✔
326
                        }
327
                }
328

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

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

350
                if (modified_provides["artifact_group"] != "") {
40✔
351
                        err = txn.Write(
5✔
352
                                artifact_group_key,
353
                                common::ByteVectorFromString(modified_provides["artifact_group"]));
10✔
354
                } else {
355
                        err = txn.Remove(artifact_group_key);
35✔
356
                }
357
                if (err != error::NoError) {
40✔
358
                        return err;
×
359
                }
360

361
                if (artifact_provides_str != "") {
40✔
362
                        err = txn.Write(
37✔
363
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
74✔
364
                        if (err != error::NoError) {
37✔
365
                                return err;
×
366
                        }
367
                }
368
                return txn_func(txn);
40✔
369
        });
40✔
370
}
371

372
} // namespace context
373
} // namespace update
374
} // 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