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

mendersoftware / mender / 1714500571

13 Mar 2025 09:05AM UTC coverage: 75.957% (-0.02%) from 75.972%
1714500571

push

gitlab-ci

jo-lund
fix: Use EscapeString when generating JSON for supplied artifact data

Ticket: MEN-7974
Changelog: Title

Signed-off-by: John Olav Lund <john.olav.lund@northern.tech>
(cherry picked from commit d74fba313)

8 of 8 new or added lines in 2 files covered. (100.0%)

2 existing lines in 1 file now uncovered.

7383 of 9720 relevant lines covered (75.96%)

11142.56 hits per line

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

82.82
/src/common/processes/platform/tiny_process_library/tiny_process_library.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 <common/processes.hpp>
16

17
#include <string>
18
#include <string_view>
19

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

25
using namespace std;
26

27
namespace mender {
28
namespace common {
29
namespace processes {
30

31
const chrono::seconds MAX_TERMINATION_TIME(10);
32

33
namespace io = mender::common::io;
34
namespace log = mender::common::log;
35
namespace path = mender::common::path;
36

37
class ProcessReaderFunctor {
10,872✔
38
public:
39
        void operator()(const char *bytes, size_t n);
40

41
        // Note: ownership is not held here, but in Process.
42
        int fd_;
43

44
        OutputCallback callback_;
45
};
46

47
Process::Process(const vector<string> &args) :
2,532✔
48
        args_ {args},
49
        max_termination_time_ {MAX_TERMINATION_TIME} {
5,064✔
50
        async_wait_data_ = make_shared<AsyncWaitData>();
2,532✔
51
}
2,532✔
52

53
Process::~Process() {
7,596✔
54
        {
55
                unique_lock lock(async_wait_data_->data_mutex);
5,064✔
56
                // DoCancel() requires being locked.
57
                DoCancel();
2,532✔
58
        }
59

60
        EnsureTerminated();
2,532✔
61

62
        if (stdout_pipe_ >= 0) {
2,532✔
63
                close(stdout_pipe_);
×
64
        }
65
        if (stderr_pipe_ >= 0) {
2,532✔
66
                close(stderr_pipe_);
×
67
        }
68
}
2,532✔
69

70
error::Error Process::Start(OutputCallback stdout_callback, OutputCallback stderr_callback) {
2,494✔
71
        if (proc_) {
2,494✔
72
                return MakeError(ProcessAlreadyStartedError, "Cannot start process");
×
73
        }
74

75
        // Tiny-process-library doesn't give a good error if the command isn't found (just returns
76
        // exit code 1). If the path is absolute, it's pretty easy to check if it exists. This won't
77
        // cover all errors (non-absolute or unset executable bit, for example), but helps a little,
78
        // at least.
79
        if (args_.size() > 0 && path::IsAbsolute(args_[0])) {
2,494✔
80
                ifstream f(args_[0]);
3,723✔
81
                if (!f.good()) {
1,862✔
82
                        int errnum = errno;
1✔
83
                        return error::Error(
84
                                generic_category().default_error_condition(errnum), "Cannot launch " + args_[0]);
2✔
85
                }
86
        }
87

88
        OutputCallback maybe_stdout_callback;
89
        if (stdout_pipe_ >= 0 || stdout_callback) {
2,493✔
90
                maybe_stdout_callback = ProcessReaderFunctor {stdout_pipe_, stdout_callback};
3,624✔
91
        }
92
        OutputCallback maybe_stderr_callback;
93
        if (stderr_pipe_ >= 0 || stderr_callback) {
2,493✔
94
                maybe_stderr_callback = ProcessReaderFunctor {stderr_pipe_, stderr_callback};
3,624✔
95
        }
96

97
        proc_ =
98
                make_unique<tpl::Process>(args_, work_dir_, maybe_stdout_callback, maybe_stderr_callback);
4,986✔
99

100
        if (proc_->get_id() == -1) {
2,493✔
101
                proc_.reset();
102
                return MakeError(
103
                        ProcessesErrorCode::SpawnError,
104
                        "Failed to spawn '" + (args_.size() >= 1 ? args_[0] : "<null>") + "'");
×
105
        }
106

107
        SetupAsyncWait();
2,493✔
108

109
        return error::NoError;
2,493✔
110
}
111

112
error::Error Process::Run() {
594✔
113
        auto err = Start();
1,188✔
114
        if (err != error::NoError) {
594✔
115
                log::Error(err.String());
×
116
        }
117
        return Wait();
1,188✔
118
}
119

120
static error::Error ErrorBasedOnExitStatus(int exit_status) {
4,502✔
121
        if (exit_status != 0) {
4,502✔
122
                return MakeError(
123
                        NonZeroExitStatusError, "Process exited with status " + to_string(exit_status));
910✔
124
        } else {
125
                return error::NoError;
4,047✔
126
        }
127
}
128

129
error::Error Process::Wait() {
2,632✔
130
        if (proc_) {
2,632✔
131
                exit_status_ = future_exit_status_.get();
2,404✔
132
                proc_.reset();
133
                if (stdout_pipe_ >= 0) {
2,404✔
134
                        close(stdout_pipe_);
2✔
135
                        stdout_pipe_ = -1;
2✔
136
                }
137
                if (stderr_pipe_ >= 0) {
2,404✔
138
                        close(stderr_pipe_);
2✔
139
                        stderr_pipe_ = -1;
2✔
140
                }
141
        }
142
        return ErrorBasedOnExitStatus(exit_status_);
2,632✔
143
}
144

145
error::Error Process::Wait(chrono::nanoseconds timeout) {
63✔
146
        if (proc_) {
63✔
147
                auto result = future_exit_status_.wait_for(timeout);
63✔
148
                if (result == future_status::timeout) {
63✔
149
                        return error::Error(
150
                                make_error_condition(errc::timed_out), "Timed out while waiting for process");
×
151
                }
152
                AssertOrReturnError(result == future_status::ready);
63✔
153
                exit_status_ = future_exit_status_.get();
63✔
154
                proc_.reset();
155
                if (stdout_pipe_ >= 0) {
63✔
156
                        close(stdout_pipe_);
×
157
                        stdout_pipe_ = -1;
×
158
                }
159
                if (stderr_pipe_ >= 0) {
63✔
160
                        close(stderr_pipe_);
×
161
                        stderr_pipe_ = -1;
×
162
                }
163
        }
164
        return ErrorBasedOnExitStatus(exit_status_);
63✔
165
}
166

167
error::Error Process::AsyncWait(events::EventLoop &loop, AsyncWaitHandler handler) {
1,815✔
168
        unique_lock lock(async_wait_data_->data_mutex);
3,630✔
169

170
        if (async_wait_data_->handler) {
1,815✔
171
                return error::Error(make_error_condition(errc::operation_in_progress), "Cannot AsyncWait");
×
172
        }
173

174
        async_wait_data_->event_loop = &loop;
1,815✔
175
        async_wait_data_->handler = handler;
1,815✔
176

177
        if (async_wait_data_->process_ended) {
1,815✔
178
                // The process has already ended. Schedule the handler immediately.
179
                auto &async_wait_data = async_wait_data_;
UNCOV
180
                async_wait_data->event_loop->Post(
×
UNCOV
181
                        [async_wait_data, this]() { AsyncWaitInternalHandler(async_wait_data); });
×
182
        }
183

184
        return error::NoError;
1,815✔
185
}
186

187
error::Error Process::AsyncWait(
1,810✔
188
        events::EventLoop &loop, AsyncWaitHandler handler, chrono::nanoseconds timeout) {
189
        timeout_timer_.reset(new events::Timer(loop));
1,810✔
190

191
        auto err = AsyncWait(loop, handler);
1,810✔
192
        if (err != error::NoError) {
1,810✔
193
                return err;
×
194
        }
195

196
        timeout_timer_->AsyncWait(timeout, [this, handler](error::Error err) {
5,435✔
197
                // Move timer here so it gets destroyed after this handler.
198
                auto timer = std::move(timeout_timer_);
5✔
199
                // Cancel normal AsyncWait() (the process part of it).
200
                {
201
                        // DoCancel() requires being locked.
202
                        unique_lock lock(async_wait_data_->data_mutex);
10✔
203
                        DoCancel();
5✔
204
                }
205
                if (err != error::NoError) {
5✔
206
                        handler(err.WithContext("Process::Timer"));
×
207
                }
208

209
                handler(error::Error(make_error_condition(errc::timed_out), "Process::Timer"));
15✔
210
        });
3,625✔
211

212
        return error::NoError;
1,810✔
213
}
214

215
void Process::Cancel() {
7✔
216
        unique_lock lock(async_wait_data_->data_mutex);
14✔
217

218
        if (async_wait_data_->handler && !async_wait_data_->process_ended) {
7✔
219
                auto &async_wait_data = async_wait_data_;
220
                auto &handler = async_wait_data->handler;
1✔
221
                async_wait_data_->event_loop->Post([handler]() {
5✔
222
                        handler(error::Error(
1✔
223
                                make_error_condition(errc::operation_canceled), "Process::AsyncWait canceled"));
2✔
224
                });
3✔
225
        }
226

227
        // DoCancel() requires being locked.
228
        DoCancel();
7✔
229
}
7✔
230

231
void Process::DoCancel() {
2,544✔
232
        // Should already be locked by caller.
233
        //   unique_lock lock(async_wait_data_->data_mutex);
234

235
        timeout_timer_.reset();
236

237
        async_wait_data_->event_loop = nullptr;
2,544✔
238
        async_wait_data_->handler = nullptr;
2,544✔
239
        async_wait_data_->process_ended = false;
2,544✔
240
}
2,544✔
241

242
void Process::SetupAsyncWait() {
2,556✔
243
        future_exit_status_ = async(launch::async, [this]() -> int {
4,363✔
244
                // This function is executed in a separate thread, but the object is guaranteed to
245
                // still exist while we're in here (because we call `future_exit_status_.get()`
246
                // during destruction.
247

248
                auto status = proc_->get_exit_status();
2,556✔
249

250
                auto &async_wait_data = async_wait_data_;
251

252
                unique_lock lock(async_wait_data->data_mutex);
2,556✔
253

254
                if (async_wait_data->handler) {
2,556✔
255
                        async_wait_data_->event_loop->Post(
3,614✔
256
                                [async_wait_data, this]() { AsyncWaitInternalHandler(async_wait_data); });
10,842✔
257
                }
258

259
                async_wait_data->process_ended = true;
2,556✔
260

261
                return status;
2,556✔
262
        });
2,556✔
263
}
2,556✔
264

265
void Process::AsyncWaitInternalHandler(shared_ptr<AsyncWaitData> async_wait_data) {
1,807✔
266
        timeout_timer_.reset();
267

268
        // This function is meant to be executed on the event loop, and the object may have been
269
        // either cancelled or destroyed before we get here. By having our own copy of the
270
        // async_wait_data pointer pointer, it survives destruction, and we can test whether we
271
        // should still proceed here. So note the use of `async_wait_data` instead of
272
        // `async_wait_data_`.
273
        unique_lock lock(async_wait_data->data_mutex);
3,614✔
274

275
        if (async_wait_data->handler) {
1,807✔
276
                auto handler = async_wait_data->handler;
1,807✔
277

278
                // For next iteration.
279
                async_wait_data->event_loop = nullptr;
1,807✔
280
                async_wait_data->handler = nullptr;
1,807✔
281
                async_wait_data->process_ended = false;
1,807✔
282

283
                auto status = GetExitStatus();
1,807✔
284

285
                // Unlock in case the handler calls back into this object.
286
                lock.unlock();
1,807✔
287
                handler(ErrorBasedOnExitStatus(status));
3,614✔
288
        }
289
}
1,807✔
290

291
static void CollectLineData(
84✔
292
        string &trailing_line, vector<string> &lines, const char *bytes, size_t len) {
293
        auto bytes_view = string_view(bytes, len);
294
        size_t line_start_idx = 0;
295
        size_t line_end_idx = bytes_view.find("\n", 0);
84✔
296
        if ((trailing_line != "") && (line_end_idx != string_view::npos)) {
84✔
297
                lines.push_back(trailing_line + string(bytes_view, 0, line_end_idx));
×
298
                line_start_idx = line_end_idx + 1;
×
299
                line_end_idx = bytes_view.find("\n", line_start_idx);
×
300
                trailing_line = "";
×
301
        }
302

303
        while ((line_start_idx < (len - 1)) && (line_end_idx != string_view::npos)) {
228✔
304
                lines.push_back(string(bytes_view, line_start_idx, (line_end_idx - line_start_idx)));
288✔
305
                line_start_idx = line_end_idx + 1;
144✔
306
                line_end_idx = bytes_view.find("\n", line_start_idx);
144✔
307
        }
308

309
        if ((line_end_idx == string_view::npos) && (line_start_idx < len)) {
84✔
310
                trailing_line += string(bytes_view, line_start_idx, (len - line_start_idx));
4✔
311
        }
312
}
84✔
313

314
ExpectedLineData Process::GenerateLineData(chrono::nanoseconds timeout) {
63✔
315
        if (proc_) {
63✔
316
                return expected::unexpected(
×
317
                        MakeError(ProcessAlreadyStartedError, "Cannot generate line data"));
×
318
        }
319

320
        if (args_.size() == 0) {
63✔
321
                return expected::unexpected(MakeError(
×
322
                        ProcessesErrorCode::SpawnError, "No arguments given, cannot spawn a process"));
×
323
        }
324

325
        // Tiny-process-library doesn't give a good error if the command isn't found (just returns
326
        // exit code 1). If the path is absolute, it's pretty easy to check if it exists. This won't
327
        // cover all errors (non-absolute or unset executable bit, for example), but helps a little,
328
        // at least.
329
        if (path::IsAbsolute(args_[0])) {
63✔
330
                ifstream f(args_[0]);
114✔
331
                if (!f.good()) {
57✔
332
                        int errnum = errno;
×
333
                        return expected::unexpected(error::Error(
×
334
                                generic_category().default_error_condition(errnum), "Cannot launch " + args_[0]));
×
335
                }
336
        }
337

338
        string trailing_line;
339
        vector<string> ret;
63✔
340
        proc_ = make_unique<tpl::Process>(
63✔
341
                args_, work_dir_, [&trailing_line, &ret](const char *bytes, size_t len) {
147✔
342
                        CollectLineData(trailing_line, ret, bytes, len);
84✔
343
                });
126✔
344

345
        if (proc_->get_id() == -1) {
63✔
346
                proc_.reset();
347
                return expected::unexpected(MakeError(
×
348
                        ProcessesErrorCode::SpawnError,
349
                        "Failed to spawn '" + (args_.size() >= 1 ? args_[0] : "<null>") + "'"));
×
350
        }
351

352
        SetupAsyncWait();
63✔
353

354
        auto err = Wait(timeout);
63✔
355
        if (err != error::NoError) {
63✔
356
                return expected::unexpected(err);
2✔
357
        }
358

359
        if (trailing_line != "") {
62✔
360
                ret.push_back(trailing_line);
2✔
361
        }
362

363
        return ExpectedLineData(ret);
62✔
364
}
365

366
io::ExpectedAsyncReaderPtr Process::GetProcessReader(events::EventLoop &loop, int &pipe_ref) {
4✔
367
        if (proc_) {
4✔
368
                return expected::unexpected(
×
369
                        MakeError(ProcessAlreadyStartedError, "Cannot get process output"));
×
370
        }
371

372
        if (pipe_ref >= 0) {
4✔
373
                close(pipe_ref);
×
374
                pipe_ref = -1;
×
375
        }
376

377
        int fds[2];
378
        int ret = pipe(fds);
4✔
379
        if (ret < 0) {
4✔
380
                int err = errno;
×
381
                return expected::unexpected(error::Error(
×
382
                        generic_category().default_error_condition(err),
×
383
                        "Could not get process stdout reader"));
×
384
        }
385

386
        pipe_ref = fds[1];
4✔
387

388
        return make_shared<events::io::AsyncFileDescriptorReader>(loop, fds[0]);
8✔
389
}
390

391
io::ExpectedAsyncReaderPtr Process::GetAsyncStdoutReader(events::EventLoop &loop) {
2✔
392
        return GetProcessReader(loop, stdout_pipe_);
2✔
393
}
394

395
io::ExpectedAsyncReaderPtr Process::GetAsyncStderrReader(events::EventLoop &loop) {
2✔
396
        return GetProcessReader(loop, stderr_pipe_);
2✔
397
}
398

399
int Process::EnsureTerminated() {
2,557✔
400
        if (!proc_) {
2,557✔
401
                return exit_status_;
2,468✔
402
        }
403

404
        log::Info("Sending SIGTERM to PID " + to_string(proc_->get_id()));
178✔
405

406
        Terminate();
89✔
407

408
        auto result = future_exit_status_.wait_for(max_termination_time_);
89✔
409
        if (result == future_status::timeout) {
89✔
410
                log::Info("Sending SIGKILL to PID " + to_string(proc_->get_id()));
2✔
411
                Kill();
1✔
412
                result = future_exit_status_.wait_for(max_termination_time_);
1✔
413
                if (result != future_status::ready) {
1✔
414
                        // This should not be possible, SIGKILL always terminates.
415
                        log::Error(
×
416
                                "PID " + to_string(proc_->get_id()) + " still not terminated after SIGKILL.");
×
417
                        return -1;
×
418
                }
419
        }
420
        assert(result == future_status::ready);
421
        exit_status_ = future_exit_status_.get();
89✔
422

423
        log::Info(
89✔
424
                "PID " + to_string(proc_->get_id()) + " exited with status " + to_string(exit_status_));
178✔
425

426
        proc_.reset();
427

428
        return exit_status_;
89✔
429
}
430

431
void Process::Terminate() {
90✔
432
        if (proc_) {
90✔
433
                // At the time of writing, tiny-process-library kills using SIGINT and SIGTERM, for
434
                // `force = false/true`, respectively. But we want to kill with SIGTERM and SIGKILL,
435
                // because:
436
                //
437
                // 1. SIGINT is not meant to kill interactive processes, whereas SIGTERM is.
438
                // 2. SIGKILL is required in order to really force, since SIGTERM can be ignored by
439
                //    the process.
440
                //
441
                // If tiny-process-library is fixed, then this can be restored and the part below
442
                // removed.
443
                // proc_->kill(false);
444

445
                ::kill(proc_->get_id(), SIGTERM);
90✔
446
                ::kill(-proc_->get_id(), SIGTERM);
90✔
447
        }
448
}
90✔
449

450
void Process::Kill() {
2✔
451
        if (proc_) {
2✔
452
                // See comment in Terminate().
453
                // proc_->kill(true);
454

455
                ::kill(proc_->get_id(), SIGKILL);
2✔
456
                ::kill(-proc_->get_id(), SIGKILL);
2✔
457
        }
458
}
2✔
459

460
void ProcessReaderFunctor::operator()(const char *bytes, size_t n) {
1,051✔
461
        if (callback_) {
1,051✔
462
                callback_(bytes, n);
1,009✔
463
        }
464

465
        if (fd_ < 0) {
1,051✔
466
                return;
467
        }
468

469
        size_t written = 0;
470
        while (written < n) {
14✔
471
                auto ret = write(fd_, bytes, n);
8✔
472
                if (ret < 0) {
8✔
473
                        int err = errno;
2✔
474
                        log::Error(
2✔
475
                                string {"Error while writing process output to main thread: "} + strerror(err));
4✔
476
                        fd_ = -1;
2✔
477
                        return;
2✔
478
                }
479

480
                written += ret;
6✔
481
        }
482
}
483

484
} // namespace processes
485
} // namespace common
486
} // 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