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

mendersoftware / mender / 2269834198

18 Jan 2026 11:46AM UTC coverage: 81.341% (-0.03%) from 81.369%
2269834198

push

gitlab-ci

lluiscampos
ci: Add an explicit job for testing boost download

As long as we support this option in the build system is good to be
tested in CI.

Before it was indirectly tested on `test:backward-compat`.

Ticket: MEN-8687

Signed-off-by: Lluis Campos <lluis.campos@northern.tech>

8793 of 10810 relevant lines covered (81.34%)

20323.49 hits per line

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

80.57
/src/mender-update/update_module/v3/update_module_download.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 <mender-update/progress_reader/progress_reader.hpp>
18

19
#include <common/events.hpp>
20
#include <common/events_io.hpp>
21
#include <common/log.hpp>
22
#include <common/path.hpp>
23
#include <common/processes.hpp>
24

25
namespace mender {
26
namespace update {
27
namespace update_module {
28
namespace v3 {
29

30
namespace log = mender::common::log;
31
namespace path = mender::common::path;
32
namespace processes = mender::common::processes;
33
namespace progress = mender::update::progress;
34

35

36
void UpdateModule::StartDownloadProcess() {
136✔
37
        string download_command = "Download";
136✔
38
        if (download_->downloading_with_sizes_) {
136✔
39
                download_command = "DownloadWithFileSizes";
3✔
40
        }
41
        log::Debug(
136✔
42
                "Calling Update Module with command `" + update_module_path_ + " " + download_command + " "
136✔
43
                + update_module_workdir_ + "`.");
272✔
44
        download_->proc_ = make_shared<procs::Process>(
136✔
45
                vector<string> {update_module_path_, download_command, update_module_workdir_});
680✔
46

47
        download_->proc_->SetWorkDir(update_module_workdir_);
48

49
        auto err = PrepareStreamNextPipe();
136✔
50
        if (err != error::NoError) {
136✔
51
                DownloadErrorHandler(err);
×
52
                return;
53
        }
54

55
        processes::OutputHandler stdout_handler {"Update Module output (stdout): "};
136✔
56
        processes::OutputHandler stderr_handler {"Update Module output (stderr): "};
136✔
57

58
        err = download_->proc_->Start(stdout_handler, stderr_handler);
272✔
59
        if (err != error::NoError) {
136✔
60
                DownloadErrorHandler(GetProcessError(err));
×
61
                return;
×
62
        }
63

64
        err = download_->proc_->AsyncWait(
136✔
65
                download_->event_loop_,
66
                [this](error::Error err) {
136✔
67
                        if (err.code == make_error_condition(errc::timed_out)) {
135✔
68
                                DownloadTimeoutHandler();
2✔
69
                        } else {
70
                                ProcessEndedHandler(err);
266✔
71
                        }
72
                },
135✔
73
                chrono::seconds(ctx_.GetConfig().module_timeout_seconds));
272✔
74
        if (err != error::NoError) {
136✔
75
                DownloadErrorHandler(err);
×
76
                return;
77
        }
78

79
        DownloadErrorHandler(OpenStreamNextPipe(
136✔
80
                [this](io::ExpectedAsyncWriterPtr writer) { StreamNextOpenHandler(writer); }));
290✔
81
}
136✔
82

83
void UpdateModule::StreamNextOpenHandler(io::ExpectedAsyncWriterPtr writer) {
13✔
84
        if (!writer) {
13✔
85
                DownloadErrorHandler(writer.error());
×
86
                return;
3✔
87
        }
88
        download_->stream_next_writer_ = writer.value();
13✔
89

90
        download_->module_has_started_download_ = true;
13✔
91

92
        auto reader = download_->payload_.Next();
13✔
93
        if (!reader) {
13✔
94
                if (reader.error().code
3✔
95
                        == artifact::parser_error::MakeError(
6✔
96
                                   artifact::parser_error::NoMorePayloadFilesError, "")
97
                                   .code) {
98
                        download_->module_has_finished_download_ = true;
3✔
99
                        log::Debug("Update Module finished all downloads");
3✔
100
                        EndStreamNext();
3✔
101
                } else {
102
                        DownloadErrorHandler(reader.error());
×
103
                }
104
                return;
3✔
105
        }
106
        auto payload_reader = make_shared<artifact::Reader>(std::move(reader.value()));
10✔
107

108
        auto progress_reader = make_shared<progress::Reader>(payload_reader, payload_reader->Size());
10✔
109

110
        download_->current_payload_reader_ =
111
                make_shared<events::io::AsyncReaderFromReader>(download_->event_loop_, progress_reader);
20✔
112
        download_->current_payload_name_ = payload_reader->Name();
20✔
113
        download_->current_payload_size_ = payload_reader->Size();
10✔
114

115
        auto stream_path =
116
                path::Join(update_module_workdir_, string("streams"), download_->current_payload_name_);
10✔
117
        auto exp_path_is_safe =
118
                path::IsWithinOrEqual(stream_path, path::Join(update_module_workdir_, string("streams")));
20✔
119
        if (!exp_path_is_safe.has_value()) {
10✔
120
                DownloadErrorHandler(exp_path_is_safe.error().WithContext(
×
121
                        "Error checking if path is equal to or within directory"));
122
                return;
×
123
        }
124
        if (!exp_path_is_safe.value()) {
10✔
125
                DownloadErrorHandler(error::Error(
×
126
                        make_error_condition(errc::invalid_argument),
×
127
                        "Error downloading payload: Provided payload file (" + download_->current_payload_name_
×
128
                                + ") would point outside work directory when extracted."));
×
129
                return;
×
130
        }
131

132
        auto err = PrepareAndOpenStreamPipe(
20✔
133
                stream_path, [this](io::ExpectedAsyncWriterPtr writer) { StreamOpenHandler(writer); });
22✔
134
        if (err != error::NoError) {
10✔
135
                DownloadErrorHandler(err);
×
136
                return;
137
        }
138

139
        string stream_next_string;
140
        if (download_->downloading_with_sizes_) {
10✔
141
                stream_next_string = path::Join("streams", download_->current_payload_name_) + " "
2✔
142
                                                         + to_string(download_->current_payload_size_);
3✔
143
        } else {
144
                stream_next_string = path::Join("streams", download_->current_payload_name_);
18✔
145
        }
146
        size_t entry_size = stream_next_string.size() + 1;
10✔
147
        if (entry_size > download_->buffer_.size()) {
10✔
148
                DownloadErrorHandler(error::Error(
×
149
                        make_error_condition(errc::no_buffer_space), "Payload name is too large for buffer"));
×
150
                return;
151
        }
152
        copy(stream_next_string.begin(), stream_next_string.end(), download_->buffer_.begin());
10✔
153
        download_->buffer_[entry_size - 1] = '\n';
10✔
154
        DownloadErrorHandler(download_->stream_next_writer_->AsyncWrite(
20✔
155
                download_->buffer_.begin(),
156
                download_->buffer_.begin() + entry_size,
157
                [this, entry_size](io::ExpectedSize result) {
9✔
158
                        StreamNextWriteHandler(entry_size, result);
9✔
159
                }));
9✔
160
}
161

162
void UpdateModule::StreamOpenHandler(io::ExpectedAsyncWriterPtr writer) {
6✔
163
        if (!writer) {
6✔
164
                DownloadErrorHandler(writer.error());
×
165
                return;
×
166
        }
167
        download_->current_stream_writer_ = writer.value();
6✔
168

169
        DownloadErrorHandler(download_->current_payload_reader_->AsyncRead(
6✔
170
                download_->buffer_.begin(), download_->buffer_.end(), [this](io::ExpectedSize result) {
12✔
171
                        PayloadReadHandler(result);
6✔
172
                }));
6✔
173
}
174

175
void UpdateModule::StreamNextWriteHandler(size_t expected_n, io::ExpectedSize result) {
9✔
176
        // Close stream-next writer.
177
        download_->stream_next_writer_.reset();
9✔
178
        if (!result) {
9✔
179
                DownloadErrorHandler(result.error());
×
180
        } else if (expected_n != result.value()) {
9✔
181
                DownloadErrorHandler(error::Error(
×
182
                        make_error_condition(errc::io_error),
×
183
                        "Unexpected number of written bytes to stream-next"));
×
184
        }
185
}
9✔
186

187
void UpdateModule::PayloadReadHandler(io::ExpectedSize result) {
763✔
188
        if (!result) {
763✔
189
                // Close streams.
190
                download_->current_stream_writer_.reset();
3✔
191
                download_->current_payload_reader_.reset();
3✔
192
                DownloadErrorHandler(result.error());
3✔
193
        } else if (result.value() > 0) {
760✔
194
                DownloadErrorHandler(download_->current_stream_writer_->AsyncWrite(
1,911✔
195
                        download_->buffer_.begin(),
196
                        download_->buffer_.begin() + result.value(),
637✔
197
                        [this, result](io::ExpectedSize write_result) {
2,548✔
198
                                StreamWriteHandler(0, result.value(), write_result);
637✔
199
                        }));
637✔
200
        } else {
201
                // Close streams.
202
                download_->current_stream_writer_.reset();
123✔
203
                download_->current_payload_reader_.reset();
123✔
204

205
                if (download_->downloading_to_files_) {
123✔
206
                        StartDownloadToFile();
118✔
207
                } else {
208
                        DownloadErrorHandler(OpenStreamNextPipe(
5✔
209
                                [this](io::ExpectedAsyncWriterPtr writer) { StreamNextOpenHandler(writer); }));
18✔
210
                }
211
        }
212
}
763✔
213

214
void UpdateModule::StreamWriteHandler(size_t offset, size_t expected_n, io::ExpectedSize result) {
637✔
215
        if (!result) {
637✔
216
                DownloadErrorHandler(result.error());
1✔
217
        } else if (result.value() == 0 || result.value() > expected_n) {
636✔
218
                DownloadErrorHandler(error::Error(
×
219
                        make_error_condition(errc::io_error),
×
220
                        "Unexpected number of written bytes to download stream"));
×
221
        } else if (result.value() < expected_n) {
636✔
222
                auto new_offset = offset + result.value();
×
223
                auto new_expected = expected_n - result.value();
×
224
                DownloadErrorHandler(download_->current_stream_writer_->AsyncWrite(
×
225
                        download_->buffer_.begin() + new_offset,
226
                        download_->buffer_.begin() + new_offset + new_expected,
227
                        [this, new_offset, new_expected](io::ExpectedSize write_result) {
×
228
                                StreamWriteHandler(new_offset, new_expected, write_result);
×
229
                        }));
×
230
        } else {
231
                download_->written_ += result.value();
636✔
232
                log::Trace("Wrote " + to_string(download_->written_) + " bytes to Update Module");
1,272✔
233
                DownloadErrorHandler(download_->current_payload_reader_->AsyncRead(
636✔
234
                        download_->buffer_.begin(), download_->buffer_.end(), [this](io::ExpectedSize result) {
1,272✔
235
                                PayloadReadHandler(result);
636✔
236
                        }));
636✔
237
        }
238
}
637✔
239

240
void UpdateModule::EndStreamNext() {
3✔
241
        // Empty write.
242
        DownloadErrorHandler(download_->stream_next_writer_->AsyncWrite(
6✔
243
                download_->buffer_.begin(), download_->buffer_.begin(), [this](io::ExpectedSize result) {
6✔
244
                        if (!result) {
3✔
245
                                DownloadErrorHandler(result.error());
×
246
                        } else {
247
                                DownloadErrorHandler(error::NoError);
3✔
248
                        }
249
                        // Close writer.
250
                        download_->stream_next_writer_.reset();
3✔
251
                        // No further action necessary. Now we just need to wait for the process to finish.
252
                }));
3✔
253
}
3✔
254

255
void UpdateModule::DownloadErrorHandler(const error::Error &err) {
1,574✔
256
        if (err != error::NoError) {
1,574✔
257
                EndDownloadLoop(err);
17✔
258
        }
259
}
1,574✔
260

261
void UpdateModule::EndDownloadLoop(const error::Error &err) {
136✔
262
        download_->download_finished_handler_(err);
136✔
263
}
136✔
264

265
void UpdateModule::DownloadTimeoutHandler() {
2✔
266
        download_->proc_->EnsureTerminated();
2✔
267
        EndDownloadLoop(error::Error(
2✔
268
                make_error_condition(errc::timed_out), "Update Module Download process timed out"));
4✔
269
}
2✔
270

271
void UpdateModule::ProcessEndedHandler(error::Error err) {
133✔
272
        if (err != error::NoError) {
133✔
273
                err = GetProcessError(err);
5✔
274
                DownloadErrorHandler(error::Error(
5✔
275
                        err.code, "Download: Update Module returned non-zero status: " + err.message));
10✔
276
        } else if (download_->module_has_finished_download_) {
128✔
277
                EndDownloadLoop(error::NoError);
3✔
278
        } else if (download_->module_has_started_download_) {
125✔
279
                DownloadErrorHandler(error::Error(
3✔
280
                        make_error_condition(errc::broken_pipe),
6✔
281
                        "Update Module started downloading, but did not finish"));
6✔
282
        } else {
283
                download_->downloading_to_files_ = true;
122✔
284
                download_->stream_next_opener_.reset();
122✔
285
                download_->current_stream_opener_.reset();
122✔
286
                err = DeleteStreamsFiles();
122✔
287
                if (err != error::NoError) {
122✔
288
                        DownloadErrorHandler(err);
×
289
                } else {
290
                        StartDownloadToFile();
122✔
291
                }
292
        }
293
}
133✔
294

295
void UpdateModule::StartDownloadToFile() {
240✔
296
        auto reader = download_->payload_.Next();
240✔
297
        if (!reader) {
240✔
298
                if (reader.error().code
118✔
299
                        == artifact::parser_error::MakeError(
236✔
300
                                   artifact::parser_error::NoMorePayloadFilesError, "")
301
                                   .code) {
302
                        log::Debug("Downloaded all files to `files` directory.");
114✔
303
                        EndDownloadLoop(error::NoError);
114✔
304
                } else {
305
                        DownloadErrorHandler(reader.error());
4✔
306
                }
307
                return;
118✔
308
        }
309
        auto payload_reader = make_shared<artifact::Reader>(std::move(reader.value()));
122✔
310
        download_->current_payload_reader_ =
311
                make_shared<events::io::AsyncReaderFromReader>(download_->event_loop_, payload_reader);
244✔
312
        download_->current_payload_name_ = payload_reader->Name();
122✔
313

314
        auto stream_path = path::Join(update_module_workdir_, string("files"));
122✔
315
        auto err = PrepareDownloadDirectory(stream_path);
122✔
316
        if (err != error::NoError) {
122✔
317
                DownloadErrorHandler(err);
×
318
                return;
319
        }
320

321
        stream_path = path::Join(stream_path, download_->current_payload_name_);
122✔
322
        auto exp_path_is_safe =
323
                path::IsWithinOrEqual(stream_path, path::Join(update_module_workdir_, string("files")));
244✔
324
        if (!exp_path_is_safe.has_value()) {
122✔
325
                DownloadErrorHandler(exp_path_is_safe.error().WithContext(
×
326
                        "Error checking if path is equal to or within directory"));
327
                return;
×
328
        }
329
        if (!exp_path_is_safe.value()) {
122✔
330
                DownloadErrorHandler(error::Error(
×
331
                        make_error_condition(errc::invalid_argument),
×
332
                        "Error downloading payload: Provided payload file (" + download_->current_payload_name_
×
333
                                + ") would point outside work directory when extracted."));
×
334
                return;
×
335
        }
336

337
        auto current_stream_writer =
338
                make_shared<events::io::AsyncFileDescriptorWriter>(download_->event_loop_);
122✔
339
        err = current_stream_writer->Open(stream_path);
122✔
340
        if (err != error::NoError) {
122✔
341
                DownloadErrorHandler(err);
1✔
342
                return;
343
        }
344
        download_->current_stream_writer_ = current_stream_writer;
345

346
        DownloadErrorHandler(download_->current_payload_reader_->AsyncRead(
121✔
347
                download_->buffer_.begin(), download_->buffer_.end(), [this](io::ExpectedSize result) {
242✔
348
                        PayloadReadHandler(result);
121✔
349
                }));
121✔
350
}
351

352
} // namespace v3
353
} // namespace update_module
354
} // namespace update
355
} // 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

© 2026 Coveralls, Inc