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

mendersoftware / mender / 1022753986

02 Oct 2023 10:37AM UTC coverage: 78.168% (-2.0%) from 80.127%
1022753986

push

gitlab-ci

oleorhagen
feat: Run the authentication loop once upon bootstrap

Ticket: MEN-6658
Changelog: None

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

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

6996 of 8950 relevant lines covered (78.17%)

10353.4 hits per line

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

73.11
/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();
2,826✔
64
        auto ex_os = io::OpenOfstream(fpath);
1,884✔
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);
1,506✔
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();
198✔
102
        if (!ex_provides) {
99✔
103
                return ex_provides.error();
×
104
        }
105

106
        auto ex_device_type = ctx_.GetDeviceType();
198✔
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
        CreateDirectories(tmp_subdir_path);
99✔
115

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

124
        auto err = CreateDataFile(file_tree_path, "version", "3\n");
396✔
125
        if (err != error::NoError) {
99✔
126
                return err;
×
127
        }
128

129
        err = write_provides_into_file("artifact_name");
99✔
130
        if (err != error::NoError) {
99✔
131
                return err;
×
132
        }
133
        err = write_provides_into_file("artifact_group");
99✔
134
        if (err != error::NoError) {
99✔
135
                return err;
×
136
        }
137

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

144
        return error::NoError;
99✔
145
}
146

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

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

158
        auto err = PrepareFileTreeDeviceParts(path);
182✔
159
        if (err != error::NoError) {
91✔
160
                return err;
×
161
        }
162

163
        //
164
        // Header
165
        //
166

167
        const fs::path header_subdir_path = file_tree_path / "header";
182✔
168
        CreateDirectories(header_subdir_path);
91✔
169

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

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

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

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

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

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

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

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

232
        return PrepareFileTreeDeviceParts(path);
8✔
233
}
234

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

244
        return error::NoError;
1✔
245
}
246

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

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

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

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

282
        return ret;
4✔
283
}
284

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

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

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

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

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

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

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

333
        return error::NoError;
43✔
334
}
335

336
error::Error UpdateModule::DeleteStreamsFiles() {
88✔
337
        std::error_code ec;
88✔
338

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

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

351
        return error::NoError;
88✔
352
}
353

354
AsyncFifoOpener::AsyncFifoOpener(events::EventLoop &loop) :
×
355
        event_loop_ {loop},
356
        cancelled_ {make_shared<bool>(true)},
×
357
        destroying_ {make_shared<bool>(false)} {
×
358
}
×
359

360
AsyncFifoOpener::~AsyncFifoOpener() {
×
361
        *destroying_ = true;
×
362
        Cancel();
×
363
}
×
364

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

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

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

397
                io::ExpectedAsyncWriterPtr exp_writer;
×
398
                if (err != error::NoError) {
119✔
399
                        exp_writer = expected::unexpected(err);
1✔
400
                } else {
401
                        exp_writer = writer;
118✔
402
                }
403

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

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

417
                        handler(exp_writer);
21✔
418
                });
238✔
419
        });
357✔
420

421
        return error::NoError;
119✔
422
}
423

424
void AsyncFifoOpener::Cancel() {
120✔
425
        if (*cancelled_) {
120✔
426
                return;
1✔
427
        }
428

429
        *cancelled_ = true;
119✔
430

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

438
        thread_.join();
119✔
439

440
        ::close(fd);
119✔
441
}
442

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