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

mendersoftware / mender / 1054569109

30 Oct 2023 09:47AM UTC coverage: 80.205% (+0.07%) from 80.137%
1054569109

push

gitlab-ci

lluiscampos
fix: Omit service/destination from method handling spec

If the method call arrives to the particular `DBusServer`
instance, it must have the matching destination set.

Ticket: MEN-6808
Changelog: none
Signed-off-by: Vratislav Podzimek <v.podzimek@mykolab.com>

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

6872 of 8568 relevant lines covered (80.21%)

9389.02 hits per line

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

82.35
/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::common::processes {
28

29
const chrono::seconds MAX_TERMINATION_TIME(10);
30

31
namespace io = mender::common::io;
32
namespace log = mender::common::log;
33
namespace path = mender::common::path;
34

35
class ProcessReaderFunctor {
10,050✔
36
public:
37
        void operator()(const char *bytes, size_t n);
38

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

42
        OutputCallback callback_;
43
};
44

45
Process::Process(vector<string> args) :
2,170✔
46
        args_ {args},
47
        max_termination_time_ {MAX_TERMINATION_TIME} {
4,340✔
48
        async_wait_data_ = make_shared<AsyncWaitData>();
2,170✔
49
}
2,170✔
50

51
Process::~Process() {
6,510✔
52
        {
53
                unique_lock lock(async_wait_data_->data_mutex);
4,340✔
54
                // DoCancel() requires being locked.
55
                DoCancel();
2,170✔
56
        }
57

58
        EnsureTerminated();
2,170✔
59

60
        if (stdout_pipe_ >= 0) {
2,170✔
61
                close(stdout_pipe_);
×
62
        }
63
        if (stderr_pipe_ >= 0) {
2,170✔
64
                close(stderr_pipe_);
×
65
        }
66
}
2,170✔
67

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

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

86
        OutputCallback maybe_stdout_callback;
87
        if (stdout_pipe_ >= 0 || stdout_callback) {
2,146✔
88
                maybe_stdout_callback = ProcessReaderFunctor {stdout_pipe_, stdout_callback};
3,350✔
89
        }
90
        OutputCallback maybe_stderr_callback;
91
        if (stderr_pipe_ >= 0 || stderr_callback) {
2,146✔
92
                maybe_stderr_callback = ProcessReaderFunctor {stderr_pipe_, stderr_callback};
3,350✔
93
        }
94

95
        proc_ =
96
                make_unique<tpl::Process>(args_, work_dir_, maybe_stdout_callback, maybe_stderr_callback);
4,292✔
97

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

105
        SetupAsyncWait();
2,146✔
106

107
        return error::NoError;
2,146✔
108
}
109

110
error::Error Process::Run() {
392✔
111
        auto err = Start();
784✔
112
        if (err != error::NoError) {
392✔
113
                log::Error(err.String());
×
114
        }
115
        return Wait();
784✔
116
}
117

118
static error::Error ErrorBasedOnExitStatus(int exit_status) {
4,011✔
119
        if (exit_status != 0) {
4,011✔
120
                return MakeError(
121
                        NonZeroExitStatusError, "Process exited with status " + to_string(exit_status));
914✔
122
        } else {
123
                return error::NoError;
3,554✔
124
        }
125
}
126

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

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

165
error::Error Process::AsyncWait(events::EventLoop &loop, AsyncWaitHandler handler) {
1,676✔
166
        unique_lock lock(async_wait_data_->data_mutex);
3,352✔
167

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

172
        async_wait_data_->event_loop = &loop;
1,676✔
173
        async_wait_data_->handler = handler;
1,676✔
174

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

182
        return error::NoError;
1,676✔
183
}
184

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

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

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

207
                handler(error::Error(make_error_condition(errc::timed_out), "Process::Timer"));
15✔
208
        });
3,351✔
209

210
        return error::NoError;
1,673✔
211
}
212

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

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

225
        // DoCancel() requires being locked.
226
        DoCancel();
7✔
227
}
7✔
228

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

233
        timeout_timer_.reset();
234

235
        async_wait_data_->event_loop = nullptr;
2,182✔
236
        async_wait_data_->handler = nullptr;
2,182✔
237
        async_wait_data_->process_ended = false;
2,182✔
238
}
2,182✔
239

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

246
                auto status = proc_->get_exit_status();
2,201✔
247

248
                auto &async_wait_data = async_wait_data_;
249

250
                unique_lock lock(async_wait_data->data_mutex);
2,201✔
251

252
                if (async_wait_data->handler) {
2,201✔
253
                        async_wait_data_->event_loop->Post(
3,336✔
254
                                [async_wait_data, this]() { AsyncWaitInternalHandler(async_wait_data); });
10,008✔
255
                }
256

257
                async_wait_data->process_ended = true;
2,201✔
258

259
                return status;
2,201✔
260
        });
2,201✔
261
}
2,201✔
262

263
void Process::AsyncWaitInternalHandler(shared_ptr<AsyncWaitData> async_wait_data) {
1,668✔
264
        timeout_timer_.reset();
265

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

273
        if (async_wait_data->handler) {
1,668✔
274
                auto handler = async_wait_data->handler;
1,668✔
275

276
                // For next iteration.
277
                async_wait_data->event_loop = nullptr;
1,668✔
278
                async_wait_data->handler = nullptr;
1,668✔
279
                async_wait_data->process_ended = false;
1,668✔
280

281
                auto status = GetExitStatus();
1,668✔
282

283
                // Unlock in case the handler calls back into this object.
284
                lock.unlock();
1,668✔
285
                handler(ErrorBasedOnExitStatus(status));
3,336✔
286
        }
287
}
1,668✔
288

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

301
        while ((line_start_idx < (len - 1)) && (line_end_idx != string_view::npos)) {
184✔
302
                lines.push_back(string(bytes_view, line_start_idx, (line_end_idx - line_start_idx)));
232✔
303
                line_start_idx = line_end_idx + 1;
116✔
304
                line_end_idx = bytes_view.find("\n", line_start_idx);
116✔
305
        }
306

307
        if ((line_end_idx == string_view::npos) && (line_start_idx != (len - 1))) {
68✔
308
                trailing_line += string(bytes_view, line_start_idx, (len - line_start_idx));
136✔
309
        }
310
}
68✔
311

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

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

323
        string trailing_line;
324
        vector<string> ret;
55✔
325
        proc_ = make_unique<tpl::Process>(
55✔
326
                args_, work_dir_, [&trailing_line, &ret](const char *bytes, size_t len) {
123✔
327
                        CollectLineData(trailing_line, ret, bytes, len);
68✔
328
                });
110✔
329

330
        if (proc_->get_id() == -1) {
55✔
331
                proc_.reset();
332
                return expected::unexpected(MakeError(
×
333
                        ProcessesErrorCode::SpawnError,
334
                        "Failed to spawn '" + (args_.size() >= 1 ? args_[0] : "<null>") + "'"));
×
335
        }
336

337
        SetupAsyncWait();
55✔
338

339
        auto err = Wait(timeout);
55✔
340
        if (err != error::NoError && err.code != MakeError(NonZeroExitStatusError, "").code) {
67✔
341
                return expected::unexpected(err);
×
342
        }
343

344
        if (trailing_line != "") {
55✔
345
                ret.push_back(trailing_line);
2✔
346
        }
347

348
        return ExpectedLineData(ret);
55✔
349
}
350

351
io::ExpectedAsyncReaderPtr Process::GetProcessReader(events::EventLoop &loop, int &pipe_ref) {
4✔
352
        if (proc_) {
4✔
353
                return expected::unexpected(
×
354
                        MakeError(ProcessAlreadyStartedError, "Cannot get process output"));
×
355
        }
356

357
        if (pipe_ref >= 0) {
4✔
358
                close(pipe_ref);
×
359
                pipe_ref = -1;
×
360
        }
361

362
        int fds[2];
363
        int ret = pipe(fds);
4✔
364
        if (ret < 0) {
4✔
365
                int err = errno;
×
366
                return expected::unexpected(error::Error(
×
367
                        generic_category().default_error_condition(err),
×
368
                        "Could not get process stdout reader"));
×
369
        }
370

371
        pipe_ref = fds[1];
4✔
372

373
        return make_shared<events::io::AsyncFileDescriptorReader>(loop, fds[0]);
8✔
374
}
375

376
io::ExpectedAsyncReaderPtr Process::GetAsyncStdoutReader(events::EventLoop &loop) {
2✔
377
        return GetProcessReader(loop, stdout_pipe_);
2✔
378
}
379

380
io::ExpectedAsyncReaderPtr Process::GetAsyncStderrReader(events::EventLoop &loop) {
2✔
381
        return GetProcessReader(loop, stderr_pipe_);
2✔
382
}
383

384
int Process::EnsureTerminated() {
2,195✔
385
        if (!proc_) {
2,195✔
386
                return exit_status_;
2,112✔
387
        }
388

389
        log::Info("Sending SIGTERM to PID " + to_string(proc_->get_id()));
166✔
390

391
        Terminate();
83✔
392

393
        auto result = future_exit_status_.wait_for(max_termination_time_);
83✔
394
        if (result == future_status::timeout) {
83✔
395
                log::Info("Sending SIGKILL to PID " + to_string(proc_->get_id()));
2✔
396
                Kill();
1✔
397
                result = future_exit_status_.wait_for(max_termination_time_);
1✔
398
                if (result != future_status::ready) {
1✔
399
                        // This should not be possible, SIGKILL always terminates.
400
                        log::Error(
×
401
                                "PID " + to_string(proc_->get_id()) + " still not terminated after SIGKILL.");
×
402
                        return -1;
×
403
                }
404
        }
405
        assert(result == future_status::ready);
406
        exit_status_ = future_exit_status_.get();
83✔
407

408
        log::Info(
83✔
409
                "PID " + to_string(proc_->get_id()) + " exited with status " + to_string(exit_status_));
166✔
410

411
        proc_.reset();
412

413
        return exit_status_;
83✔
414
}
415

416
void Process::Terminate() {
84✔
417
        if (proc_) {
84✔
418
                // At the time of writing, tiny-process-library kills using SIGINT and SIGTERM, for
419
                // `force = false/true`, respectively. But we want to kill with SIGTERM and SIGKILL,
420
                // because:
421
                //
422
                // 1. SIGINT is not meant to kill interactive processes, whereas SIGTERM is.
423
                // 2. SIGKILL is required in order to really force, since SIGTERM can be ignored by
424
                //    the process.
425
                //
426
                // If tiny-process-library is fixed, then this can be restored and the part below
427
                // removed.
428
                // proc_->kill(false);
429

430
                ::kill(proc_->get_id(), SIGTERM);
84✔
431
                ::kill(-proc_->get_id(), SIGTERM);
84✔
432
        }
433
}
84✔
434

435
void Process::Kill() {
2✔
436
        if (proc_) {
2✔
437
                // See comment in Terminate().
438
                // proc_->kill(true);
439

440
                ::kill(proc_->get_id(), SIGKILL);
2✔
441
                ::kill(-proc_->get_id(), SIGKILL);
2✔
442
        }
443
}
2✔
444

445
void ProcessReaderFunctor::operator()(const char *bytes, size_t n) {
1,013✔
446
        if (callback_) {
1,013✔
447
                callback_(bytes, n);
971✔
448
        }
449

450
        if (fd_ < 0) {
1,013✔
451
                return;
452
        }
453

454
        size_t written = 0;
455
        while (written < n) {
14✔
456
                auto ret = write(fd_, bytes, n);
8✔
457
                if (ret < 0) {
8✔
458
                        int err = errno;
2✔
459
                        log::Error(
2✔
460
                                string {"Error while writing process output to main thread: "} + strerror(err));
4✔
461
                        fd_ = -1;
2✔
462
                        return;
2✔
463
                }
464

465
                written += ret;
6✔
466
        }
467
}
468

469
} // namespace mender::common::processes
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