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

mendersoftware / mender / 1054626264

30 Oct 2023 10:27AM UTC coverage: 80.137% (-0.06%) from 80.194%
1054626264

push

gitlab-ci

kacf
chore: Add many missing error checks and exception harnesses.

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

100 of 100 new or added lines in 7 files covered. (100.0%)

6887 of 8594 relevant lines covered (80.14%)

9361.04 hits per line

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

76.26
/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 CreateDataFile(
942✔
50
        const fs::path &file_tree_path, const string &file_name, const string &data) {
51
        string fpath = (file_tree_path / file_name).string();
1,884✔
52
        auto ex_os = io::OpenOfstream(fpath);
942✔
53
        if (!ex_os) {
942✔
54
                return ex_os.error();
×
55
        }
56
        ofstream &os = ex_os.value();
942✔
57
        if (data != "") {
942✔
58
                auto err = io::WriteStringIntoOfstream(os, data);
753✔
59
                return err;
753✔
60
        }
61
        return error::NoError;
189✔
62
}
63

64
static error::Error SyncFs(const string &path) {
91✔
65
        int fd = ::open(path.c_str(), O_RDONLY);
91✔
66
        if (fd < 0) {
91✔
67
                int errnum = errno;
×
68
                return error::Error(
69
                        generic_category().default_error_condition(errnum), "Could not open " + path);
×
70
        }
71

72
        int result = syncfs(fd);
91✔
73

74
        ::close(fd);
91✔
75

76
        if (result != 0) {
91✔
77
                int errnum = errno;
×
78
                return error::Error(
79
                        generic_category().default_error_condition(errnum),
×
80
                        "Could not sync filesystem at " + path);
×
81
        }
82

83
        return error::NoError;
91✔
84
};
85

86
error::Error UpdateModule::PrepareFileTreeDeviceParts(const string &path) {
99✔
87
        // make sure all the required data can be gathered first before creating
88
        // directories and files
89
        auto ex_provides = ctx_.LoadProvides();
99✔
90
        if (!ex_provides) {
99✔
91
                return ex_provides.error();
×
92
        }
93

94
        auto ex_device_type = ctx_.GetDeviceType();
99✔
95
        if (!ex_device_type) {
99✔
96
                return ex_device_type.error();
×
97
        }
98

99
        const fs::path file_tree_path {path};
198✔
100

101
        const fs::path tmp_subdir_path = file_tree_path / "tmp";
198✔
102
        auto err = path::CreateDirectories(tmp_subdir_path.string());
99✔
103
        if (err != error::NoError) {
99✔
104
                return err;
×
105
        }
106

107
        auto provides = ex_provides.value();
99✔
108
        auto write_provides_into_file = [&](const string &key) {
198✔
109
                return CreateDataFile(
110
                        file_tree_path,
198✔
111
                        "current_" + key,
396✔
112
                        (provides.count(key) != 0) ? provides[key] + "\n" : "");
594✔
113
        };
99✔
114

115
        err = CreateDataFile(file_tree_path, "version", "3\n");
198✔
116
        if (err != error::NoError) {
99✔
117
                return err;
×
118
        }
119

120
        err = write_provides_into_file("artifact_name");
198✔
121
        if (err != error::NoError) {
99✔
122
                return err;
×
123
        }
124
        err = write_provides_into_file("artifact_group");
198✔
125
        if (err != error::NoError) {
99✔
126
                return err;
×
127
        }
128

129
        auto device_type = ex_device_type.value();
99✔
130
        err = CreateDataFile(file_tree_path, "current_device_type", device_type + "\n");
198✔
131
        if (err != error::NoError) {
99✔
132
                return err;
×
133
        }
134

135
        return error::NoError;
99✔
136
}
137

138
error::Error UpdateModule::CleanAndPrepareFileTree(
91✔
139
        const string &path, artifact::PayloadHeaderView &payload_meta_data) {
140
        const fs::path file_tree_path {path};
182✔
141

142
        std::error_code ec;
91✔
143
        fs::remove_all(file_tree_path, ec);
91✔
144
        if (ec) {
91✔
145
                return error::Error(
146
                        ec.default_error_condition(), "Could not clean File Tree for Update Module");
×
147
        }
148

149
        auto err = PrepareFileTreeDeviceParts(path);
91✔
150
        if (err != error::NoError) {
91✔
151
                return err;
×
152
        }
153

154
        //
155
        // Header
156
        //
157

158
        const fs::path header_subdir_path = file_tree_path / "header";
182✔
159
        err = path::CreateDirectories(header_subdir_path.string());
182✔
160
        if (err != error::NoError) {
91✔
161
                return err;
×
162
        }
163

164
        err = CreateDataFile(
91✔
165
                header_subdir_path, "artifact_group", payload_meta_data.header.artifact_group);
182✔
166
        if (err != error::NoError) {
91✔
167
                return err;
×
168
        }
169

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

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

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

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

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

199
        // Make sure all changes are permanent, even across spontaneous reboots. We don't want to
200
        // have half a tree when trying to recover from that.
201
        return SyncFs(path);
91✔
202
}
203

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

226
        return PrepareFileTreeDeviceParts(path);
8✔
227
}
228

229
error::Error UpdateModule::DeleteFileTree(const string &path) {
1✔
230
        try {
231
                fs::remove_all(fs::path {path});
1✔
232
        } catch (const fs::filesystem_error &e) {
×
233
                return error::Error(
234
                        e.code().default_error_condition(),
×
235
                        "Failed to recursively remove directory '" + path + "': " + e.what());
×
236
        }
237

238
        return error::NoError;
1✔
239
}
240

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

245
        try {
246
                for (auto entry : fs::directory_iterator(file_tree_path)) {
18✔
247
                        const fs::path file_path = entry.path();
8✔
248
                        const string file_path_str = file_path.string();
6✔
249
                        if (!fs::is_regular_file(file_path)) {
6✔
250
                                log::Warning("'" + file_path_str + "' is not a regular file");
×
251
                                continue;
×
252
                        }
253

254
                        const fs::perms perms = entry.status().permissions();
255
                        if ((perms & (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec))
6✔
256
                                == fs::perms::none) {
257
                                log::Warning("'" + file_path_str + "' is not executable");
8✔
258
                                continue;
4✔
259
                        }
260

261
                        ret.push_back(file_path_str);
2✔
262
                }
263
        } catch (const fs::filesystem_error &e) {
×
264
                auto code = e.code();
×
265
                if (code.value() == ENOENT) {
×
266
                        // directory not found is not an error, just return an empty vector
267
                        return ret;
×
268
                }
269
                // everything (?) else is an error
270
                return expected::unexpected(error::Error(
×
271
                        code.default_error_condition(),
×
272
                        "Failed to discover update modules in '" + file_tree_path.string() + "': " + e.what()));
×
273
        }
274

275
        return ret;
4✔
276
}
277

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

281
        if (::mkfifo(download_->stream_next_path_.c_str(), 0600) != 0) {
101✔
282
                int err = errno;
×
283
                return error::Error(
284
                        generic_category().default_error_condition(err),
×
285
                        "Unable to create `stream-next` at " + download_->stream_next_path_);
×
286
        }
287
        return error::NoError;
101✔
288
}
289

290
error::Error UpdateModule::OpenStreamNextPipe(ExpectedWriterHandler open_handler) {
106✔
291
        auto opener = make_shared<AsyncFifoOpener>(download_->event_loop_);
106✔
292
        download_->stream_next_opener_ = opener;
293
        return opener->AsyncOpen(download_->stream_next_path_, open_handler);
212✔
294
}
295

296
error::Error UpdateModule::PrepareAndOpenStreamPipe(
10✔
297
        const string &path, ExpectedWriterHandler open_handler) {
298
        auto fs_path = fs::path(path);
20✔
299
        std::error_code ec;
10✔
300
        if (!fs::create_directories(fs_path.parent_path(), ec) && ec) {
11✔
301
                return error::Error(
302
                        ec.default_error_condition(),
×
303
                        "Could not create stream directory at " + fs_path.parent_path().string());
×
304
        }
305

306
        if (::mkfifo(path.c_str(), 0600) != 0) {
10✔
307
                int err = errno;
×
308
                return error::Error(
309
                        generic_category().default_error_condition(err),
×
310
                        "Could not create stream FIFO at " + path);
×
311
        }
312

313
        auto opener = make_shared<AsyncFifoOpener>(download_->event_loop_);
10✔
314
        download_->current_stream_opener_ = opener;
315
        return opener->AsyncOpen(path, open_handler);
20✔
316
}
317

318
error::Error UpdateModule::PrepareDownloadDirectory(const string &path) {
43✔
319
        auto fs_path = fs::path(path);
86✔
320
        std::error_code ec;
43✔
321
        if (!fs::create_directories(fs_path, ec) && ec) {
43✔
322
                return error::Error(
323
                        ec.default_error_condition(), "Could not create `files` directory at " + path);
×
324
        }
325

326
        return error::NoError;
43✔
327
}
328

329
error::Error UpdateModule::DeleteStreamsFiles() {
88✔
330
        try {
331
                fs::path p {download_->stream_next_path_};
176✔
332
                fs::remove_all(p);
88✔
333

334
                p = fs::path(update_module_workdir_) / "streams";
88✔
335
                fs::remove_all(p);
88✔
336
        } catch (fs::filesystem_error &e) {
×
337
                return error::Error(
338
                        e.code().default_error_condition(), "Could not remove " + download_->stream_next_path_);
×
339
        }
340

341
        return error::NoError;
88✔
342
}
343

344
AsyncFifoOpener::AsyncFifoOpener(events::EventLoop &loop) :
119✔
345
        event_loop_ {loop},
346
        cancelled_ {make_shared<bool>(true)},
119✔
347
        destroying_ {make_shared<bool>(false)} {
238✔
348
}
119✔
349

350
AsyncFifoOpener::~AsyncFifoOpener() {
357✔
351
        *destroying_ = true;
119✔
352
        Cancel();
119✔
353
}
119✔
354

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

375
        if (!*cancelled_) {
119✔
376
                return error::Error(
377
                        make_error_condition(errc::operation_in_progress), "Already running AsyncFifoOpener");
×
378
        }
379

380
        *cancelled_ = false;
119✔
381
        path_ = path;
119✔
382
        thread_ = thread([this, handler]() {
119✔
383
                auto writer = make_shared<events::io::AsyncFileDescriptorWriter>(event_loop_);
119✔
384
                // This will block for as long as there are no FIFO readers.
385
                auto err = writer->Open(path_);
119✔
386

387
                io::ExpectedAsyncWriterPtr exp_writer;
119✔
388
                if (err != error::NoError) {
119✔
389
                        exp_writer = expected::unexpected(err);
2✔
390
                } else {
391
                        exp_writer = writer;
236✔
392
                }
393

394
                auto &cancelled = cancelled_;
395
                auto &destroying = destroying_;
396
                event_loop_.Post([handler, exp_writer, cancelled, destroying]() {
230✔
397
                        if (*destroying) {
111✔
398
                                return;
399
                        }
400

401
                        if (*cancelled) {
22✔
402
                                handler(expected::unexpected(error::Error(
2✔
403
                                        make_error_condition(errc::operation_canceled), "AsyncFifoOpener cancelled")));
4✔
404
                                return;
1✔
405
                        }
406

407
                        handler(exp_writer);
42✔
408
                });
357✔
409
        });
238✔
410

411
        return error::NoError;
119✔
412
}
413

414
void AsyncFifoOpener::Cancel() {
120✔
415
        if (*cancelled_) {
120✔
416
                return;
417
        }
418

419
        *cancelled_ = true;
119✔
420

421
        // Open the other end of the pipe to jerk the first end loose.
422
        int fd = ::open(path_.c_str(), O_RDONLY | O_NONBLOCK);
119✔
423
        if (fd < 0) {
119✔
424
                int errnum = errno;
1✔
425
                log::Error(string("Cancel::open() returned error: ") + strerror(errnum));
2✔
426
        }
427

428
        thread_.join();
119✔
429

430
        ::close(fd);
119✔
431
}
432

433
} // namespace v3
434
} // namespace update_module
435
} // namespace update
436
} // 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