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

mendersoftware / mender / 951615729

pending completion
951615729

push

gitlab-ci

larsewi
fix: Add 'build/' to '.gitignore'

Ticket: None
Changelog: None
Signed-off-by: Lars Erik Wik <lars.erik.wik@northern.tech>

4199 of 5286 relevant lines covered (79.44%)

166.43 hits per line

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

79.89
/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 {
2✔
66
        switch (code) {
2✔
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
        }
86
        assert(false);
×
87
        return "Unknown";
88
}
89

90
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
194✔
91
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
388✔
92
}
93

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

112
        return error::NoError;
93✔
113
#else
114
        return error::NoError;
115
#endif
116
}
117

118
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
89✔
119
        return mender_store_;
89✔
120
}
121

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

137
ExpectedProvidesData MenderContext::LoadProvides(kv_db::Transaction &txn) {
108✔
138
        string artifact_name;
216✔
139
        string artifact_group;
216✔
140
        string artifact_provides_str;
216✔
141

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

155
        ProvidesData ret {};
216✔
156
        if (artifact_name != "") {
108✔
157
                ret["artifact_name"] = artifact_name;
57✔
158
        }
159
        if (artifact_group != "") {
108✔
160
                ret["artifact_group"] = artifact_group;
10✔
161
        }
162
        if (artifact_provides_str == "") {
108✔
163
                // nothing more to do
164
                return ret;
59✔
165
        }
166

167
        auto ex_j = json::Load(artifact_provides_str);
98✔
168
        if (!ex_j) {
49✔
169
                return expected::unexpected(ex_j.error());
2✔
170
        }
171
        auto ex_children = ex_j.value().GetChildren();
96✔
172
        if (!ex_children) {
48✔
173
                return expected::unexpected(ex_children.error());
×
174
        }
175

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

187
        return ret;
47✔
188
}
189

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

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

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

215
        string ret = line.substr(eq_pos, string::npos);
92✔
216

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

224
        return expected::ExpectedString(ret);
44✔
225
}
226

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

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

257
                set<string> keys;
146✔
258
                for (auto provide : to_modify) {
127✔
259
                        if (regex_match(provide.first.begin(), provide.first.end(), compiled)) {
54✔
260
                                keys.insert(provide.first);
12✔
261
                        }
262
                }
263
                for (auto key : keys) {
85✔
264
                        to_modify.erase(key);
12✔
265
                }
266
        }
267

268
        // Now add the provides from the new_provides set.
269
        for (auto provide : new_provides) {
74✔
270
                to_modify[provide.first] = provide.second;
49✔
271
        }
272

273
        return error::NoError;
25✔
274
}
275

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

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

312
                if (artifact_name != "") {
31✔
313
                        modified_provides["artifact_name"] = artifact_name;
31✔
314
                }
315
                if (artifact_group != "") {
31✔
316
                        modified_provides["artifact_group"] = artifact_group;
4✔
317
                }
318

319
                string artifact_provides_str {"{"};
62✔
320
                for (const auto &it : modified_provides) {
121✔
321
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
90✔
322
                                artifact_provides_str +=
323
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
54✔
324
                        }
325
                }
326

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

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

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

359
                if (artifact_provides_str != "") {
31✔
360
                        err = txn.Write(
28✔
361
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
56✔
362
                        if (err != error::NoError) {
28✔
363
                                return err;
×
364
                        }
365
                }
366
                return txn_func(txn);
31✔
367
        });
31✔
368
}
369

370
} // namespace context
371
} // namespace update
372
} // 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