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

mendersoftware / mender / 947394036

pending completion
947394036

push

gitlab-ci

kacf
chore: Treat events with no state transitions as fatal.

This was discussed with the team members. Since an unhandled event is
almost guaranteed to hang the state machine, then it's better to
terminate and let systemd try to restart us, in the hopes that
recovery will still work.

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

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

4268 of 5997 relevant lines covered (71.17%)

148.52 hits per line

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

79.01
/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
        case StateDataStoreCountExceededError:
×
86
                return "State data store count exceeded";
×
87
        }
88
        assert(false);
×
89
        return "Unknown";
90
}
91

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

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

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

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

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

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

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

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

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

178
        auto children = ex_children.value();
96✔
179
        if (!all_of(children.cbegin(), children.cend(), [](const json::ChildrenMap::value_type &it) {
48✔
180
                        return it.second.IsString();
90✔
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()) {
136✔
186
                ret[it.first] = it.second.GetString().value();
89✔
187
        }
188

189
        return ret;
47✔
190
}
191

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

199
        auto &is = ex_is.value();
49✔
200
        string line;
98✔
201
        errno = 0;
49✔
202
        getline(is, line);
49✔
203
        if (is.bad()) {
49✔
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;
49✔
212
        if (line.substr(0, eq_pos) != "device_type=") {
49✔
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);
92✔
218

219
        errno = 0;
46✔
220
        getline(is, line);
46✔
221
        if ((line != "") || (!is.eof())) {
46✔
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);
44✔
227
}
228

229
static error::Error FilterProvides(
25✔
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) {
98✔
235
                string escaped;
73✔
236
                // Potential to escape every character, though unlikely.
237
                escaped.reserve(to_clear.size() * 2);
73✔
238
                // Notable exception: '*', since it has special handling as a glob character.
239
                string meta_characters {".^$+()[]{}|?"};
73✔
240
                for (const auto chr : to_clear) {
1,261✔
241
                        if (chr == '*') {
1,188✔
242
                                // Turn every '*' glob wildcard into '.*' regex wildcard.
243
                                escaped.push_back('.');
25✔
244
                        } else if (any_of(meta_characters.begin(), meta_characters.end(), [chr](char c) {
1,163✔
245
                                                   return chr == c;
13,714✔
246
                                           })) {
247
                                // Escape every regex special character except '*'.
248
                                escaped.push_back('\\');
22✔
249
                        }
250
                        escaped.push_back(chr);
1,188✔
251
                }
252

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

259
                set<string> keys;
146✔
260
                for (auto provide : to_modify) {
127✔
261
                        if (regex_match(provide.first.begin(), provide.first.end(), compiled)) {
54✔
262
                                keys.insert(provide.first);
12✔
263
                        }
264
                }
265
                for (auto key : keys) {
85✔
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) {
74✔
272
                to_modify[provide.first] = provide.second;
49✔
273
        }
274

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

278
error::Error MenderContext::CommitArtifactData(
31✔
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) {
31✔
285
                auto exp_existing = LoadProvides(txn);
62✔
286
                if (!exp_existing) {
31✔
287
                        return exp_existing.error();
×
288
                }
289
                auto modified_provides = exp_existing.value();
62✔
290

291
                error::Error err;
62✔
292
                if (!new_provides && !clears_provides) {
31✔
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) {
28✔
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) {
26✔
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);
23✔
309
                }
310
                if (err != error::NoError) {
31✔
311
                        return err;
×
312
                }
313

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

321
                string artifact_provides_str {"{"};
62✔
322
                for (const auto &it : modified_provides) {
121✔
323
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
90✔
324
                                artifact_provides_str +=
325
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
54✔
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 != "{") {
31✔
332
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
28✔
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"] != "") {
31✔
339
                        err = txn.Write(
31✔
340
                                artifact_name_key,
341
                                common::ByteVectorFromString(modified_provides["artifact_name"]));
62✔
342
                        if (err != error::NoError) {
31✔
343
                                return err;
×
344
                        }
345
                } else {
346
                        // This should not happen.
347
                        AssertOrReturnError(false);
×
348
                }
349

350
                if (modified_provides["artifact_group"] != "") {
31✔
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);
26✔
356
                }
357
                if (err != error::NoError) {
31✔
358
                        return err;
×
359
                }
360

361
                if (artifact_provides_str != "") {
31✔
362
                        err = txn.Write(
28✔
363
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
56✔
364
                        if (err != error::NoError) {
28✔
365
                                return err;
×
366
                        }
367
                }
368
                return txn_func(txn);
31✔
369
        });
31✔
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