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

mendersoftware / mender / 1054591953

30 Oct 2023 10:02AM UTC coverage: 80.194% (+0.06%) from 80.137%
1054591953

push

gitlab-ci

kacf
fix: Make sure `device_type` is submitted with inventory data.

Changelog: None
Ticket: None

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

6871 of 8568 relevant lines covered (80.19%)

9388.93 hits per line

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

76.1
/mender-update/update_module/v3/platform/c++17/fs_operations.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/update_module/v3/update_module.hpp>
16

17
#include <cerrno>
18
#include <fstream>
19

20
#include <unistd.h>
21

22
#include <filesystem>
23

24
#include <common/conf.hpp>
25
#include <common/events_io.hpp>
26
#include <common/io.hpp>
27
#include <common/log.hpp>
28
#include <common/path.hpp>
29
#include <mender-update/context.hpp>
30

31
namespace mender {
32
namespace update {
33
namespace update_module {
34
namespace v3 {
35

36
using namespace std;
37

38
namespace error = mender::common::error;
39
namespace events = mender::common::events;
40
namespace expected = mender::common::expected;
41
namespace conf = mender::common::conf;
42
namespace context = mender::update::context;
43
namespace io = mender::common::io;
44
namespace log = mender::common::log;
45
namespace path = mender::common::path;
46

47
namespace fs = std::filesystem;
48

49
error::Error CreateDirectories(const fs::path &dir) {
190✔
50
        try {
51
                fs::create_directories(dir);
190✔
52
        } catch (const fs::filesystem_error &e) {
×
53
                return error::Error(
54
                        e.code().default_error_condition(),
×
55
                        "Failed to create directory '" + dir.string() + "': " + e.what());
×
56
        }
57
        return error::NoError;
190✔
58
}
59

60

61
error::Error CreateDataFile(
942✔
62
        const fs::path &file_tree_path, const string &file_name, const string &data) {
63
        string fpath = (file_tree_path / file_name).string();
1,884✔
64
        auto ex_os = io::OpenOfstream(fpath);
942✔
65
        if (!ex_os) {
942✔
66
                return ex_os.error();
×
67
        }
68
        ofstream &os = ex_os.value();
942✔
69
        if (data != "") {
942✔
70
                auto err = io::WriteStringIntoOfstream(os, data);
753✔
71
                return err;
753✔
72
        }
73
        return error::NoError;
189✔
74
}
75

76
static error::Error SyncFs(const string &path) {
91✔
77
        int fd = ::open(path.c_str(), O_RDONLY);
91✔
78
        if (fd < 0) {
91✔
79
                int errnum = errno;
×
80
                return error::Error(
81
                        generic_category().default_error_condition(errnum), "Could not open " + path);
×
82
        }
83

84
        int result = syncfs(fd);
91✔
85

86
        ::close(fd);
91✔
87

88
        if (result != 0) {
91✔
89
                int errnum = errno;
×
90
                return error::Error(
91
                        generic_category().default_error_condition(errnum),
×
92
                        "Could not sync filesystem at " + path);
×
93
        }
94

95
        return error::NoError;
91✔
96
};
97

98
error::Error UpdateModule::PrepareFileTreeDeviceParts(const string &path) {
99✔
99
        // make sure all the required data can be gathered first before creating
100
        // directories and files
101
        auto ex_provides = ctx_.LoadProvides();
99✔
102
        if (!ex_provides) {
99✔
103
                return ex_provides.error();
×
104
        }
105

106
        auto ex_device_type = ctx_.GetDeviceType();
99✔
107
        if (!ex_device_type) {
99✔
108
                return ex_device_type.error();
×
109
        }
110

111
        const fs::path file_tree_path {path};
198✔
112

113
        const fs::path tmp_subdir_path = file_tree_path / "tmp";
198✔
114
        auto err = CreateDirectories(tmp_subdir_path);
99✔
115
        if (err != error::NoError) {
99✔
116
                return err;
×
117
        }
118

119
        auto provides = ex_provides.value();
99✔
120
        auto write_provides_into_file = [&](const string &key) {
198✔
121
                return CreateDataFile(
122
                        file_tree_path,
198✔
123
                        "current_" + key,
396✔
124
                        (provides.count(key) != 0) ? provides[key] + "\n" : "");
594✔
125
        };
99✔
126

127
        err = CreateDataFile(file_tree_path, "version", "3\n");
198✔
128
        if (err != error::NoError) {
99✔
129
                return err;
×
130
        }
131

132
        err = write_provides_into_file("artifact_name");
198✔
133
        if (err != error::NoError) {
99✔
134
                return err;
×
135
        }
136
        err = write_provides_into_file("artifact_group");
198✔
137
        if (err != error::NoError) {
99✔
138
                return err;
×
139
        }
140

141
        auto device_type = ex_device_type.value();
99✔
142
        err = CreateDataFile(file_tree_path, "current_device_type", device_type + "\n");
198✔
143
        if (err != error::NoError) {
99✔
144
                return err;
×
145
        }
146

147
        return error::NoError;
99✔
148
}
149

150
error::Error UpdateModule::CleanAndPrepareFileTree(
91✔
151
        const string &path, artifact::PayloadHeaderView &payload_meta_data) {
152
        const fs::path file_tree_path {path};
182✔
153

154
        std::error_code ec;
91✔
155
        fs::remove_all(file_tree_path, ec);
91✔
156
        if (ec) {
91✔
157
                return error::Error(
158
                        ec.default_error_condition(), "Could not clean File Tree for Update Module");
×
159
        }
160

161
        auto err = PrepareFileTreeDeviceParts(path);
91✔
162
        if (err != error::NoError) {
91✔
163
                return err;
×
164
        }
165

166
        //
167
        // Header
168
        //
169

170
        const fs::path header_subdir_path = file_tree_path / "header";
182✔
171
        CreateDirectories(header_subdir_path);
91✔
172

173
        err = CreateDataFile(
91✔
174
                header_subdir_path, "artifact_group", payload_meta_data.header.artifact_group);
182✔
175
        if (err != error::NoError) {
91✔
176
                return err;
×
177
        }
178

179
        err =
180
                CreateDataFile(header_subdir_path, "artifact_name", payload_meta_data.header.artifact_name);
182✔
181
        if (err != error::NoError) {
91✔
182
                return err;
×
183
        }
184

185
        err = CreateDataFile(header_subdir_path, "payload_type", payload_meta_data.header.payload_type);
182✔
186
        if (err != error::NoError) {
91✔
187
                return err;
×
188
        }
189

190
        err = CreateDataFile(
91✔
191
                header_subdir_path, "header-info", payload_meta_data.header.header_info.verbatim.Dump());
182✔
192
        if (err != error::NoError) {
91✔
193
                return err;
×
194
        }
195

196
        err = CreateDataFile(
91✔
197
                header_subdir_path, "type-info", payload_meta_data.header.type_info.verbatim.Dump());
182✔
198
        if (err != error::NoError) {
91✔
199
                return err;
×
200
        }
201

202
        err =
203
                CreateDataFile(header_subdir_path, "meta-data", payload_meta_data.header.meta_data.Dump());
182✔
204
        if (err != error::NoError) {
91✔
205
                return err;
×
206
        }
207

208
        // Make sure all changes are permanent, even across spontaneous reboots. We don't want to
209
        // have half a tree when trying to recover from that.
210
        return SyncFs(path);
91✔
211
}
212

213
error::Error UpdateModule::EnsureRootfsImageFileTree(const string &path) {
8✔
214
        // Historical note: Versions of the client prior to 4.0 had the rootfs-image module built
215
        // in. Because of this it has no Update Module File Tree. So if we are upgrading, we might
216
        // hit an on-going upgrade without a File Tree. It's too late to create a complete one with
217
        // all the artifact content by the time we get here, but at least we can create one which
218
        // has the current Provides information, as well as a folder for the Update Module to run
219
        // in.
220
        ifstream payload_type(path::Join(path, "header", "payload_type"));
24✔
221
        if (payload_type.good()) {
8✔
222
                string type;
223
                payload_type >> type;
6✔
224
                if (payload_type.good() && type == "rootfs-image") {
6✔
225
                        // If we have a File Tree with the rootfs-image type, we assume we are
226
                        // fine. This is actually not completely safe in an upgrade situation,
227
                        // because the old <4.0 client will not have cleaned the tree, and it could
228
                        // be old. However, this will *only* happen in an upgrade situation from
229
                        // <4.0 to >=4.0, and I can't think of a way it could be exploited. Also,
230
                        // the rootfs-image module does not use any of the information ATM.
231
                        return error::NoError;
×
232
                }
233
        }
234

235
        return PrepareFileTreeDeviceParts(path);
8✔
236
}
237

238
error::Error UpdateModule::DeleteFileTree(const string &path) {
1✔
239
        try {
240
                fs::remove_all(fs::path {path});
1✔
241
        } catch (const fs::filesystem_error &e) {
×
242
                return error::Error(
243
                        e.code().default_error_condition(),
×
244
                        "Failed to recursively remove directory '" + path + "': " + e.what());
×
245
        }
246

247
        return error::NoError;
1✔
248
}
249

250
expected::ExpectedStringVector DiscoverUpdateModules(const conf::MenderConfig &config) {
4✔
251
        vector<string> ret {};
4✔
252
        fs::path file_tree_path = fs::path(config.paths.GetDataStore()) / "modules/v3";
16✔
253

254
        try {
255
                for (auto entry : fs::directory_iterator(file_tree_path)) {
18✔
256
                        const fs::path file_path = entry.path();
8✔
257
                        const string file_path_str = file_path.string();
6✔
258
                        if (!fs::is_regular_file(file_path)) {
6✔
259
                                log::Warning("'" + file_path_str + "' is not a regular file");
×
260
                                continue;
×
261
                        }
262

263
                        const fs::perms perms = entry.status().permissions();
264
                        if ((perms & (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec))
6✔
265
                                == fs::perms::none) {
266
                                log::Warning("'" + file_path_str + "' is not executable");
8✔
267
                                continue;
4✔
268
                        }
269

270
                        ret.push_back(file_path_str);
2✔
271
                }
272
        } catch (const fs::filesystem_error &e) {
×
273
                auto code = e.code();
×
274
                if (code.value() == ENOENT) {
×
275
                        // directory not found is not an error, just return an empty vector
276
                        return ret;
×
277
                }
278
                // everything (?) else is an error
279
                return expected::unexpected(error::Error(
×
280
                        code.default_error_condition(),
×
281
                        "Failed to discover update modules in '" + file_tree_path.string() + "': " + e.what()));
×
282
        }
283

284
        return ret;
4✔
285
}
286

287
error::Error UpdateModule::PrepareStreamNextPipe() {
101✔
288
        download_->stream_next_path_ = path::Join(update_module_workdir_, "stream-next");
202✔
289

290
        if (::mkfifo(download_->stream_next_path_.c_str(), 0600) != 0) {
101✔
291
                int err = errno;
×
292
                return error::Error(
293
                        generic_category().default_error_condition(err),
×
294
                        "Unable to create `stream-next` at " + download_->stream_next_path_);
×
295
        }
296
        return error::NoError;
101✔
297
}
298

299
error::Error UpdateModule::OpenStreamNextPipe(ExpectedWriterHandler open_handler) {
106✔
300
        auto opener = make_shared<AsyncFifoOpener>(download_->event_loop_);
106✔
301
        download_->stream_next_opener_ = opener;
302
        return opener->AsyncOpen(download_->stream_next_path_, open_handler);
212✔
303
}
304

305
error::Error UpdateModule::PrepareAndOpenStreamPipe(
10✔
306
        const string &path, ExpectedWriterHandler open_handler) {
307
        auto fs_path = fs::path(path);
20✔
308
        std::error_code ec;
10✔
309
        if (!fs::create_directories(fs_path.parent_path(), ec) && ec) {
11✔
310
                return error::Error(
311
                        ec.default_error_condition(),
×
312
                        "Could not create stream directory at " + fs_path.parent_path().string());
×
313
        }
314

315
        if (::mkfifo(path.c_str(), 0600) != 0) {
10✔
316
                int err = errno;
×
317
                return error::Error(
318
                        generic_category().default_error_condition(err),
×
319
                        "Could not create stream FIFO at " + path);
×
320
        }
321

322
        auto opener = make_shared<AsyncFifoOpener>(download_->event_loop_);
10✔
323
        download_->current_stream_opener_ = opener;
324
        return opener->AsyncOpen(path, open_handler);
20✔
325
}
326

327
error::Error UpdateModule::PrepareDownloadDirectory(const string &path) {
43✔
328
        auto fs_path = fs::path(path);
86✔
329
        std::error_code ec;
43✔
330
        if (!fs::create_directories(fs_path, ec) && ec) {
43✔
331
                return error::Error(
332
                        ec.default_error_condition(), "Could not create `files` directory at " + path);
×
333
        }
334

335
        return error::NoError;
43✔
336
}
337

338
error::Error UpdateModule::DeleteStreamsFiles() {
88✔
339
        std::error_code ec;
88✔
340

341
        fs::path p {download_->stream_next_path_};
176✔
342
        fs::remove_all(p, ec);
88✔
343
        if (ec) {
88✔
344
                return error::Error(ec.default_error_condition(), "Could not remove " + p.string());
×
345
        }
346

347
        p = fs::path(update_module_workdir_) / "streams";
88✔
348
        fs::remove_all(p, ec);
88✔
349
        if (ec) {
88✔
350
                return error::Error(ec.default_error_condition(), "Could not remove " + p.string());
×
351
        }
352

353
        return error::NoError;
88✔
354
}
355

356
AsyncFifoOpener::AsyncFifoOpener(events::EventLoop &loop) :
119✔
357
        event_loop_ {loop},
358
        cancelled_ {make_shared<bool>(true)},
119✔
359
        destroying_ {make_shared<bool>(false)} {
238✔
360
}
119✔
361

362
AsyncFifoOpener::~AsyncFifoOpener() {
357✔
363
        *destroying_ = true;
119✔
364
        Cancel();
119✔
365
}
119✔
366

367
error::Error AsyncFifoOpener::AsyncOpen(const string &path, ExpectedWriterHandler handler) {
119✔
368
        // Excerpt from fifo(7) man page:
369
        // ------------------------------
370
        // The FIFO must be opened on both ends (reading and writing) before data can be passed.
371
        // Normally, opening the FIFO blocks until the other end is opened also.
372
        //
373
        // A process can open a FIFO in nonblocking mode. In this case, opening for read-only
374
        // succeeds even if no one has opened on the write side yet and opening for write-only fails
375
        // with ENXIO (no such device or address) unless the other end has already been opened.
376
        //
377
        // Under Linux, opening a FIFO for read and write will succeed both in blocking and
378
        // nonblocking mode. POSIX leaves this behavior undefined.  This can be used to open a FIFO
379
        // for writing while there are no readers available.
380
        // ------------------------------
381
        //
382
        // We want to open the pipe to check if a process is going to read from it. But we cannot do
383
        // this in a blocking fashion, because we are also waiting for the process to terminate,
384
        // which happens for Update Modules that want the client to download the files for them. So
385
        // we need this AsyncFifoOpener class, which does the work in a thread.
386

387
        if (!*cancelled_) {
119✔
388
                return error::Error(
389
                        make_error_condition(errc::operation_in_progress), "Already running AsyncFifoOpener");
×
390
        }
391

392
        *cancelled_ = false;
119✔
393
        path_ = path;
119✔
394
        thread_ = thread([this, handler]() {
119✔
395
                auto writer = make_shared<events::io::AsyncFileDescriptorWriter>(event_loop_);
119✔
396
                // This will block for as long as there are no FIFO readers.
397
                auto err = writer->Open(path_);
119✔
398

399
                io::ExpectedAsyncWriterPtr exp_writer;
119✔
400
                if (err != error::NoError) {
119✔
401
                        exp_writer = expected::unexpected(err);
2✔
402
                } else {
403
                        exp_writer = writer;
236✔
404
                }
405

406
                auto &cancelled = cancelled_;
407
                auto &destroying = destroying_;
408
                event_loop_.Post([handler, exp_writer, cancelled, destroying]() {
230✔
409
                        if (*destroying) {
111✔
410
                                return;
411
                        }
412

413
                        if (*cancelled) {
22✔
414
                                handler(expected::unexpected(error::Error(
2✔
415
                                        make_error_condition(errc::operation_canceled), "AsyncFifoOpener cancelled")));
4✔
416
                                return;
1✔
417
                        }
418

419
                        handler(exp_writer);
42✔
420
                });
357✔
421
        });
238✔
422

423
        return error::NoError;
119✔
424
}
425

426
void AsyncFifoOpener::Cancel() {
120✔
427
        if (*cancelled_) {
120✔
428
                return;
429
        }
430

431
        *cancelled_ = true;
119✔
432

433
        // Open the other end of the pipe to jerk the first end loose.
434
        int fd = ::open(path_.c_str(), O_RDONLY | O_NONBLOCK);
119✔
435
        if (fd < 0) {
119✔
436
                int errnum = errno;
1✔
437
                log::Error(string("Cancel::open() returned error: ") + strerror(errnum));
2✔
438
        }
439

440
        thread_.join();
119✔
441

442
        ::close(fd);
119✔
443
}
444

445
} // namespace v3
446
} // namespace update_module
447
} // namespace update
448
} // 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