• 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

69.57
/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
class AsyncFifoOpener : virtual public io::Canceller {
50
public:
51
        AsyncFifoOpener(events::EventLoop &loop);
52
        ~AsyncFifoOpener();
53

54
        error::Error AsyncOpen(const string &path, ExpectedWriterHandler handler);
55

56
        void Cancel() override;
57

58
private:
59
        events::EventLoop &event_loop_;
60
        string path_;
61
        ExpectedWriterHandler handler_;
62
        thread thread_;
63
        shared_ptr<atomic<bool>> cancelled_;
64
};
65

66
error::Error CreateDirectories(const fs::path &dir) {
115✔
67
        try {
68
                fs::create_directories(dir);
115✔
69
        } catch (const fs::filesystem_error &e) {
×
70
                return error::Error(
71
                        e.code().default_error_condition(),
×
72
                        "Failed to create directory '" + dir.string() + "': " + e.what());
×
73
        }
74
        return error::NoError;
115✔
75
}
76

77

78
error::Error CreateDataFile(
568✔
79
        const fs::path &file_tree_path, const string &file_name, const string &data) {
80
        string fpath = (file_tree_path / file_name).string();
1,704✔
81
        auto ex_os = io::OpenOfstream(fpath);
1,136✔
82
        if (!ex_os) {
568✔
83
                return ex_os.error();
×
84
        }
85
        ofstream &os = ex_os.value();
568✔
86
        if (data != "") {
568✔
87
                auto err = io::WriteStringIntoOfstream(os, data);
868✔
88
                return err;
434✔
89
        }
90
        return error::NoError;
134✔
91
}
92

93
static error::Error SyncFs(const string &path) {
54✔
94
        int fd = ::open(path.c_str(), O_RDONLY);
54✔
95
        if (fd < 0) {
54✔
96
                int errnum = errno;
×
97
                return error::Error(
98
                        generic_category().default_error_condition(errnum), "Could not open " + path);
×
99
        }
100

101
        int result = syncfs(fd);
54✔
102

103
        ::close(fd);
54✔
104

105
        if (result != 0) {
54✔
106
                int errnum = errno;
×
107
                return error::Error(
108
                        generic_category().default_error_condition(errnum),
×
109
                        "Could not sync filesystem at " + path);
×
110
        }
111

112
        return error::NoError;
54✔
113
};
114

115
error::Error UpdateModule::PrepareFileTreeDeviceParts(const string &path) {
61✔
116
        // make sure all the required data can be gathered first before creating
117
        // directories and files
118
        auto ex_provides = ctx_.LoadProvides();
122✔
119
        if (!ex_provides) {
61✔
120
                return ex_provides.error();
×
121
        }
122

123
        auto ex_device_type = ctx_.GetDeviceType();
122✔
124
        if (!ex_device_type) {
61✔
125
                return ex_device_type.error();
×
126
        }
127

128
        const fs::path file_tree_path {path};
122✔
129

130
        const fs::path tmp_subdir_path = file_tree_path / "tmp";
122✔
131
        CreateDirectories(tmp_subdir_path);
61✔
132

133
        auto provides = ex_provides.value();
122✔
134
        auto write_provides_into_file = [&](const string &key) {
122✔
135
                return CreateDataFile(
136
                        file_tree_path,
122✔
137
                        "current_" + key,
244✔
138
                        (provides.count(key) != 0) ? provides[key] + "\n" : "");
488✔
139
        };
61✔
140

141
        auto err = CreateDataFile(file_tree_path, "version", "3\n");
244✔
142
        if (err != error::NoError) {
61✔
143
                return err;
×
144
        }
145

146
        err = write_provides_into_file("artifact_name");
61✔
147
        if (err != error::NoError) {
61✔
148
                return err;
×
149
        }
150
        err = write_provides_into_file("artifact_group");
61✔
151
        if (err != error::NoError) {
61✔
152
                return err;
×
153
        }
154

155
        auto device_type = ex_device_type.value();
122✔
156
        err = CreateDataFile(file_tree_path, "current_device_type", device_type + "\n");
61✔
157
        if (err != error::NoError) {
61✔
158
                return err;
×
159
        }
160

161
        return error::NoError;
61✔
162
}
163

164
error::Error UpdateModule::CleanAndPrepareFileTree(
54✔
165
        const string &path, artifact::PayloadHeaderView &payload_meta_data) {
166
        const fs::path file_tree_path {path};
108✔
167

168
        std::error_code ec;
54✔
169
        fs::remove_all(file_tree_path, ec);
54✔
170
        if (ec) {
54✔
171
                return error::Error(
172
                        ec.default_error_condition(), "Could not clean File Tree for Update Module");
×
173
        }
174

175
        auto err = PrepareFileTreeDeviceParts(path);
108✔
176
        if (err != error::NoError) {
54✔
177
                return err;
×
178
        }
179

180
        //
181
        // Header
182
        //
183

184
        const fs::path header_subdir_path = file_tree_path / "header";
108✔
185
        CreateDirectories(header_subdir_path);
54✔
186

187
        err = CreateDataFile(
54✔
188
                header_subdir_path, "artifact_group", payload_meta_data.header.artifact_group);
108✔
189
        if (err != error::NoError) {
54✔
190
                return err;
×
191
        }
192

193
        err =
194
                CreateDataFile(header_subdir_path, "artifact_name", payload_meta_data.header.artifact_name);
54✔
195
        if (err != error::NoError) {
54✔
196
                return err;
×
197
        }
198

199
        err = CreateDataFile(header_subdir_path, "payload_type", payload_meta_data.header.payload_type);
54✔
200
        if (err != error::NoError) {
54✔
201
                return err;
×
202
        }
203

204
        err = CreateDataFile(
54✔
205
                header_subdir_path, "header-info", payload_meta_data.header.header_info.verbatim.Dump());
108✔
206
        if (err != error::NoError) {
54✔
207
                return err;
×
208
        }
209

210
        err = CreateDataFile(
54✔
211
                header_subdir_path, "type-info", payload_meta_data.header.type_info.verbatim.Dump());
108✔
212
        if (err != error::NoError) {
54✔
213
                return err;
×
214
        }
215

216
        err =
217
                CreateDataFile(header_subdir_path, "meta-data", payload_meta_data.header.meta_data.Dump());
54✔
218
        if (err != error::NoError) {
54✔
219
                return err;
×
220
        }
221

222
        // Make sure all changes are permanent, even across spontaneous reboots. We don't want to
223
        // have half a tree when trying to recover from that.
224
        return SyncFs(path);
54✔
225
}
226

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

249
        return PrepareFileTreeDeviceParts(path);
7✔
250
}
251

252
error::Error UpdateModule::DeleteFileTree(const string &path) {
1✔
253
        try {
254
                fs::remove_all(fs::path {path});
1✔
255
        } catch (const fs::filesystem_error &e) {
×
256
                return error::Error(
257
                        e.code().default_error_condition(),
×
258
                        "Failed to recursively remove directory '" + path + "': " + e.what());
×
259
        }
260

261
        return error::NoError;
1✔
262
}
263

264
expected::ExpectedStringVector DiscoverUpdateModules(const conf::MenderConfig &config) {
4✔
265
        vector<string> ret {};
8✔
266
        fs::path file_tree_path = fs::path(config.paths.GetDataStore()) / "modules/v3";
12✔
267

268
        try {
269
                for (auto entry : fs::directory_iterator(file_tree_path)) {
18✔
270
                        const fs::path file_path = entry.path();
6✔
271
                        const string file_path_str = file_path.string();
6✔
272
                        if (!fs::is_regular_file(file_path)) {
6✔
273
                                log::Warning("'" + file_path_str + "' is not a regular file");
×
274
                                continue;
×
275
                        }
276

277
                        const fs::perms perms = entry.status().permissions();
6✔
278
                        if ((perms & (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec))
6✔
279
                                == fs::perms::none) {
6✔
280
                                log::Warning("'" + file_path_str + "' is not executable");
4✔
281
                                continue;
4✔
282
                        }
283

284
                        // TODO: should check access(X_OK)?
285
                        ret.push_back(file_path_str);
2✔
286
                }
287
        } catch (const fs::filesystem_error &e) {
×
288
                auto code = e.code();
×
289
                if (code.value() == ENOENT) {
×
290
                        // directory not found is not an error, just return an empty vector
291
                        return ret;
×
292
                }
293
                // everything (?) else is an error
294
                return expected::unexpected(error::Error(
×
295
                        code.default_error_condition(),
×
296
                        "Failed to discover update modules in '" + file_tree_path.string() + "': " + e.what()));
×
297
        }
298

299
        return ret;
4✔
300
}
301

302
error::Error UpdateModule::PrepareStreamNextPipe() {
65✔
303
        download_->stream_next_path_ = path::Join(update_module_workdir_, "stream-next");
65✔
304

305
        if (::mkfifo(download_->stream_next_path_.c_str(), 0600) != 0) {
65✔
306
                int err = errno;
×
307
                return error::Error(
308
                        generic_category().default_error_condition(err),
×
309
                        "Unable to create `stream-next` at " + download_->stream_next_path_);
×
310
        }
311
        return error::NoError;
65✔
312
}
313

314
error::Error UpdateModule::OpenStreamNextPipe(ExpectedWriterHandler open_handler) {
69✔
315
        auto opener = make_shared<AsyncFifoOpener>(download_->event_loop_);
69✔
316
        download_->stream_next_opener_ = opener;
69✔
317
        return opener->AsyncOpen(download_->stream_next_path_, open_handler);
138✔
318
}
319

320
error::Error UpdateModule::PrepareAndOpenStreamPipe(
9✔
321
        const string &path, ExpectedWriterHandler open_handler) {
322
        auto fs_path = fs::path(path);
18✔
323
        std::error_code ec;
9✔
324
        if (!fs::create_directories(fs_path.parent_path(), ec) && ec) {
9✔
325
                return error::Error(
326
                        ec.default_error_condition(),
×
327
                        "Could not create stream directory at " + fs_path.parent_path().string());
×
328
        }
329

330
        if (::mkfifo(path.c_str(), 0600) != 0) {
9✔
331
                int err = errno;
×
332
                return error::Error(
333
                        generic_category().default_error_condition(err),
×
334
                        "Could not create stream FIFO at " + path);
×
335
        }
336

337
        auto opener = make_shared<AsyncFifoOpener>(download_->event_loop_);
9✔
338
        download_->current_stream_opener_ = opener;
9✔
339
        return opener->AsyncOpen(path, open_handler);
9✔
340
}
341

342
error::Error UpdateModule::PrepareDownloadDirectory(const string &path) {
32✔
343
        auto fs_path = fs::path(path);
64✔
344
        std::error_code ec;
32✔
345
        if (!fs::create_directories(fs_path, ec) && ec) {
32✔
346
                return error::Error(
347
                        ec.default_error_condition(), "Could not create `files` directory at " + path);
×
348
        }
349

350
        return error::NoError;
32✔
351
}
352

353
error::Error UpdateModule::DeleteStreamsFiles() {
53✔
354
        std::error_code ec;
53✔
355

356
        fs::path p {download_->stream_next_path_};
106✔
357
        fs::remove_all(p, ec);
53✔
358
        if (ec) {
53✔
359
                return error::Error(ec.default_error_condition(), "Could not remove " + p.string());
×
360
        }
361

362
        p = fs::path(update_module_workdir_) / "streams";
53✔
363
        fs::remove_all(p, ec);
53✔
364
        if (ec) {
53✔
365
                return error::Error(ec.default_error_condition(), "Could not remove " + p.string());
×
366
        }
367

368
        return error::NoError;
53✔
369
}
370

371
AsyncFifoOpener::AsyncFifoOpener(events::EventLoop &loop) :
×
372
        event_loop_ {loop},
373
        cancelled_ {make_shared<atomic<bool>>(true)} {
×
374
}
×
375

376
AsyncFifoOpener::~AsyncFifoOpener() {
×
377
        Cancel();
×
378
}
×
379

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

400
        if (!*cancelled_) {
78✔
401
                return error::Error(
402
                        make_error_condition(errc::operation_in_progress), "Already running AsyncFifoOpener");
×
403
        }
404

405
        *cancelled_ = false;
78✔
406
        path_ = path;
78✔
407
        thread_ = thread([this, handler]() {
78✔
408
                auto writer = make_shared<events::io::AsyncFileDescriptorWriter>(event_loop_);
156✔
409
                // This will block for as long as there are no FIFO readers.
410
                auto err = writer->Open(path_);
156✔
411

412
                auto cancelled = cancelled_;
156✔
413
                if (err != error::NoError) {
78✔
414
                        event_loop_.Post([handler, err, cancelled]() {
×
415
                                if (!*cancelled) {
×
416
                                        // Needed because capture is always const, and `unexpected`
417
                                        // wants to `move`.
418
                                        auto err_copy = err;
×
419
                                        handler(expected::unexpected(err_copy));
×
420
                                }
421
                        });
×
422
                } else {
423
                        event_loop_.Post([handler, writer, cancelled]() {
78✔
424
                                if (!*cancelled) {
70✔
425
                                        handler(writer);
16✔
426
                                }
427
                        });
226✔
428
                }
429
        });
234✔
430

431
        return error::NoError;
78✔
432
}
433

434
void AsyncFifoOpener::Cancel() {
78✔
435
        if (*cancelled_) {
78✔
436
                return;
×
437
        }
438

439
        *cancelled_ = true;
78✔
440

441
        // Open the other end of the pipe to jerk the first end loose.
442
        int fd = ::open(path_.c_str(), O_RDONLY | O_NONBLOCK);
78✔
443
        if (fd < 0) {
78✔
444
                // Should not happen.
445
                int errnum = errno;
×
446
                log::Error(string("Cancel::open() returned error: ") + strerror(errnum));
×
447
                assert(fd >= 0);
×
448
        }
449

450
        thread_.join();
78✔
451

452
        ::close(fd);
78✔
453
}
454

455
} // namespace v3
456
} // namespace update_module
457
} // namespace update
458
} // 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