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

mendersoftware / mender / 2276543477

21 Jan 2026 01:00PM UTC coverage: 79.764%. Remained the same
2276543477

push

gitlab-ci

michalkopczan
fix: Schedule next deployment poll if current one failed early causing no handler to be called

Ticket: MEN-9144
Changelog: Fix a hang when polling for deployment failed early causing no handler of API response
to be called. Added handler call for this case, causing the deployment polling
to continue.

Signed-off-by: Michal Kopczan <michal.kopczan@northern.tech>

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

97 existing lines in 2 files now uncovered.

7919 of 9928 relevant lines covered (79.76%)

13839.69 hits per line

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

69.82
/src/common/platform/dbus/asio_libdbus/dbus.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/platform/dbus.hpp>
16

17
#include <cassert>
18
#include <functional>
19
#include <memory>
20
#include <string>
21
#include <utility>
22

23
#include <boost/asio.hpp>
24
#include <dbus/dbus.h>
25

26
#include <common/error.hpp>
27
#include <common/expected.hpp>
28
#include <common/events.hpp>
29
#include <common/log.hpp>
30
#include <common/optional.hpp>
31

32
namespace mender {
33
namespace common {
34
namespace dbus {
35

36
namespace asio = boost::asio;
37

38
namespace error = mender::common::error;
39
namespace expected = mender::common::expected;
40
namespace events = mender::common::events;
41
namespace log = mender::common::log;
42

43
using namespace std;
44

45
// DBus uses many macros with C style casts which get expanded in this file. Therefore the usual
46
// method which excludes system headers from warnings doesn't work. So just disable warnings for C
47
// style casts in this file.
48
#ifdef __clang__
49
#pragma clang diagnostic ignored "-Wold-style-cast"
50
#else
51
#pragma GCC diagnostic ignored "-Wold-style-cast"
52
#endif
53

54
// The code below integrates ASIO and libdbus. Or, more precisely, it uses
55
// asio::io_context as the main/event loop for libdbus.
56
//
57
// HandleDispatch() makes sure message dispatch is done. The *Watch() functions
58
// allow libdbus to set up and cancel watching of its connection's file
59
// descriptor(s). The *Timeout() functions do the same just for
60
// timeouts. HandleReply() is a C function we use to extract the DBus reply and
61
// pass it to a handler given to DBusClient::CallMethod().
62
// (see the individual functions below for details)
63

64
// friends can't be static (see class DBusClient)
65
void HandleDispatch(DBusConnection *conn, DBusDispatchStatus status, void *data);
66
dbus_bool_t AddDBusWatch(DBusWatch *w, void *data);
67
static void RemoveDBusWatch(DBusWatch *w, void *data);
68
static void ToggleDBusWatch(DBusWatch *w, void *data);
69
dbus_bool_t AddDBusTimeout(DBusTimeout *t, void *data);
70
static void RemoveDBusTimeout(DBusTimeout *t, void *data);
71
static void ToggleDBusTimeout(DBusTimeout *t, void *data);
72

73
template <typename ReplyType>
74
void HandleReply(DBusPendingCall *pending, void *data);
75

76
DBusHandlerResult MsgFilter(DBusConnection *connection, DBusMessage *message, void *data);
77

78
error::Error DBusPeer::InitializeConnection() {
61✔
79
        DBusError dbus_error;
80
        dbus_error_init(&dbus_error);
61✔
81
        dbus_conn_.reset(dbus_bus_get_private(DBUS_BUS_SYSTEM, &dbus_error));
61✔
82
        if (!dbus_conn_) {
61✔
83
                auto err = MakeError(
84
                        ConnectionError,
85
                        string("Failed to get connection to system bus: ") + dbus_error.message + "["
46✔
86
                                + dbus_error.name + "]");
69✔
87
                dbus_error_free(&dbus_error);
23✔
88
                return err;
23✔
89
        }
90

91
        dbus_connection_set_exit_on_disconnect(dbus_conn_.get(), FALSE);
38✔
92
        if (!dbus_connection_set_watch_functions(
38✔
93
                        dbus_conn_.get(), AddDBusWatch, RemoveDBusWatch, ToggleDBusWatch, this, NULL)) {
94
                dbus_conn_.reset();
95
                return MakeError(ConnectionError, "Failed to set watch functions");
×
96
        }
97
        if (!dbus_connection_set_timeout_functions(
38✔
98
                        dbus_conn_.get(), AddDBusTimeout, RemoveDBusTimeout, ToggleDBusTimeout, this, NULL)) {
99
                dbus_conn_.reset();
100
                return MakeError(ConnectionError, "Failed to set timeout functions");
×
101
        }
102

103
        dbus_connection_set_dispatch_status_function(dbus_conn_.get(), HandleDispatch, this, NULL);
38✔
104

105
        return error::NoError;
38✔
106
}
107

108
error::Error DBusClient::InitializeConnection() {
44✔
109
        auto err = DBusPeer::InitializeConnection();
44✔
110
        if (err != error::NoError) {
44✔
111
                return err;
23✔
112
        }
113

114
        if (!dbus_connection_add_filter(dbus_conn_.get(), MsgFilter, this, NULL)) {
21✔
115
                dbus_conn_.reset();
116
                return MakeError(ConnectionError, "Failed to set message filter");
×
117
        }
118

119
        return error::NoError;
21✔
120
}
121

122
template <typename ReplyType>
123
void FreeHandlerCopy(void *data) {
5✔
124
        auto *handler = static_cast<DBusCallReplyHandler<ReplyType> *>(data);
125
        delete handler;
10✔
126
}
5✔
127

128
template <typename ReplyType>
129
error::Error DBusClient::CallMethod(
5✔
130
        const string &destination,
131
        const string &path,
132
        const string &iface,
133
        const string &method,
134
        DBusCallReplyHandler<ReplyType> handler) {
135
        if (!dbus_conn_ || !dbus_connection_get_is_connected(dbus_conn_.get())) {
5✔
136
                auto err = InitializeConnection();
2✔
137
                if (err != error::NoError) {
2✔
138
                        return err;
×
139
                }
140
        }
141

142
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> dbus_msg {
10✔
143
                dbus_message_new_method_call(
144
                        destination.c_str(), path.c_str(), iface.c_str(), method.c_str()),
145
                dbus_message_unref};
146
        if (!dbus_msg) {
5✔
147
                return MakeError(MessageError, "Failed to create new message");
×
148
        }
149

150
        DBusPendingCall *pending;
151
        if (!dbus_connection_send_with_reply(
5✔
152
                        dbus_conn_.get(), dbus_msg.get(), &pending, DBUS_TIMEOUT_USE_DEFAULT)) {
153
                return MakeError(MessageError, "Unable to add message to the queue");
×
154
        }
155

156
        // We need to create a copy here because we need to make sure the handler,
157
        // which might be a lambda, even with captures, will live long enough for
158
        // the finished pending call to use it.
159
        unique_ptr<DBusCallReplyHandler<ReplyType>> handler_copy {
5✔
160
                new DBusCallReplyHandler<ReplyType>(handler)};
5✔
161
        if (!dbus_pending_call_set_notify(
5✔
162
                        pending, HandleReply<ReplyType>, handler_copy.get(), FreeHandlerCopy<ReplyType>)) {
163
                return MakeError(MessageError, "Failed to set reply handler");
×
164
        }
165

166
        // FreeHandlerCopy() takes care of the allocated handler copy
167
        handler_copy.release();
168

169
        return error::NoError;
5✔
170
}
171

172
template error::Error DBusClient::CallMethod(
173
        const string &destination,
174
        const string &path,
175
        const string &iface,
176
        const string &method,
177
        DBusCallReplyHandler<expected::ExpectedString> handler);
178

179
template error::Error DBusClient::CallMethod(
180
        const string &destination,
181
        const string &path,
182
        const string &iface,
183
        const string &method,
184
        DBusCallReplyHandler<dbus::ExpectedStringPair> handler);
185

186
template error::Error DBusClient::CallMethod(
187
        const string &destination,
188
        const string &path,
189
        const string &iface,
190
        const string &method,
191
        DBusCallReplyHandler<expected::ExpectedBool> handler);
192

193
template <>
194
void DBusClient::AddSignalHandler(
3✔
195
        const SignalSpec &spec, DBusSignalHandler<expected::ExpectedString> handler) {
196
        signal_handlers_string_[spec] = handler;
3✔
197
}
3✔
198

199
template <>
200
void DBusClient::AddSignalHandler(
14✔
201
        const SignalSpec &spec, DBusSignalHandler<ExpectedStringPair> handler) {
202
        signal_handlers_string_pair_[spec] = handler;
14✔
203
}
14✔
204

205
static inline string GetSignalMatchRule(const string &iface, const string &signal) {
18✔
206
        return string("type='signal'") + ",interface='" + iface + "',member='" + signal + "'";
36✔
207
}
208

209
template <typename SignalValueType>
210
error::Error DBusClient::RegisterSignalHandler(
3✔
211
        const string &iface, const string &signal, DBusSignalHandler<SignalValueType> handler) {
212
        if (!dbus_conn_ || !dbus_connection_get_is_connected(dbus_conn_.get())) {
3✔
213
                auto err = InitializeConnection();
3✔
214
                if (err != error::NoError) {
3✔
215
                        return err;
×
216
                }
217
        }
218

219
        // Registering a signal with the low-level DBus API means telling the DBus
220
        // daemon that we are interested in messages matching a rule. It could be
221
        // anything, but we are interested in (specific) signals. The MsgFilter()
222
        // function below takes care of actually invoking the right handler.
223
        const string match_rule = GetSignalMatchRule(iface, signal);
3✔
224

225
        DBusError dbus_error;
226
        dbus_error_init(&dbus_error);
3✔
227
        dbus_bus_add_match(dbus_conn_.get(), match_rule.c_str(), &dbus_error);
3✔
228
        if (dbus_error_is_set(&dbus_error)) {
3✔
229
                auto err = MakeError(
×
230
                        ConnectionError, string("Failed to register signal reception: ") + dbus_error.message);
×
231
                dbus_error_free(&dbus_error);
×
232
                return err;
×
233
        }
234
        AddSignalHandler<SignalValueType>({iface, signal}, handler);
3✔
235
        return error::NoError;
3✔
236
}
237

238
template error::Error DBusClient::RegisterSignalHandler(
239
        const string &iface, const string &signal, DBusSignalHandler<expected::ExpectedString> handler);
240

241
template error::Error DBusClient::RegisterSignalHandler(
242
        const string &iface, const string &signal, DBusSignalHandler<ExpectedStringPair> handler);
243

244
error::Error DBusClient::UnregisterSignalHandler(const string &iface, const string &signal) {
1✔
245
        // we use the match rule as a unique string for the given signal
246
        const string match_rule = GetSignalMatchRule(iface, signal);
1✔
247

248
        // should be in at most one set, but erase() is a noop if not found
249
        // Remove handlers even if subsequent dbus_bus_remove_match fails. This will result in
250
        // simply nothing being called when the signal comes. Besides, if remove_match fails, it most
251
        // probably means that the match was already not there, due to e.g. connection reestablishment.
252
        signal_handlers_string_.erase({iface, signal});
1✔
253
        signal_handlers_string_pair_.erase({iface, signal});
1✔
254

255
        DBusError dbus_error;
256
        dbus_error_init(&dbus_error);
1✔
257
        dbus_bus_remove_match(dbus_conn_.get(), match_rule.c_str(), &dbus_error);
1✔
258
        if (dbus_error_is_set(&dbus_error)) {
1✔
259
                auto err = MakeError(
260
                        ConnectionError,
261
                        string("Failed to unregister signal reception: ") + dbus_error.message);
×
262
                dbus_error_free(&dbus_error);
×
263
                return err;
×
264
        }
265

266
        return error::NoError;
1✔
267
}
268

269
void HandleDispatch(DBusConnection *conn, DBusDispatchStatus status, void *data) {
228✔
270
        DBusClient *client = static_cast<DBusClient *>(data);
271
        if (status == DBUS_DISPATCH_DATA_REMAINS) {
228✔
272
                // This must give other things in the loop a chance to run because
273
                // dbus_connection_dispatch() below can cause this to be called again.
274
                client->loop_.Post([conn]() {
265✔
275
                        while (dbus_connection_get_dispatch_status(conn) == DBUS_DISPATCH_DATA_REMAINS) {
231✔
276
                                // The data from the socket should be read by now into internal libdbus buffer,
277
                                // but just in case call dbus_connection_read_write_dispatch instead of
278
                                // dbus_connection_dispatch.
279
                                // Timeout is set to 0 to make sure we read only data that is ready to be read, we
280
                                // don't wait for anything - waiting is done in RepeatedWaitFunctor.
281
                                dbus_connection_read_write_dispatch(conn, 0);
118✔
282
                        }
283
                });
152✔
284
        }
285
}
228✔
286

287

288
struct WatchState {
38✔
289
        std::unique_ptr<asio::posix::stream_descriptor> sd_;
290
        bool finished_;
291
        DBusClient *client_;
292

293
        WatchState(std::unique_ptr<asio::posix::stream_descriptor> sd, DBusClient *client) :
38✔
294
                sd_ {std::move(sd)},
295
                finished_ {false},
296
                client_ {client} {
38✔
297
        }
298
};
299

300
dbus_bool_t AddDBusWatch(DBusWatch *w, void *data) {
76✔
301
        // libdbus adds watches in two steps -- using AddDBusWatch() with a disabled
302
        // watch which should allocate all the necessary data (and can fail)
303
        // followed by ToggleDBusWatch() to enable the watch (see below). We
304
        // simplify things for ourselves by ignoring disabled watches and only
305
        // actually adding them when ToggleDBusWatch() is called.
306
        if (!dbus_watch_get_enabled(w)) {
76✔
307
                return TRUE;
308
        }
309

310
        DBusClient *client = static_cast<DBusClient *>(data);
38✔
311
        std::unique_ptr<asio::posix::stream_descriptor> sd {
312
                new asio::posix::stream_descriptor(DBusClient::GetAsioIoContext(client->loop_))};
76✔
313
        auto watch_state = make_shared<WatchState>(std::move(sd), client);
38✔
314
        boost::system::error_code ec;
38✔
315

316
        watch_state->sd_->assign(dbus_watch_get_unix_fd(w), ec);
38✔
317
        if (ec) {
38✔
UNCOV
318
                log::Error("Failed to assign DBus FD to ASIO stream descriptor");
×
UNCOV
319
                return FALSE;
×
320
        }
321

322
        class RepeatedWaitFunctor {
38✔
323
        public:
324
                RepeatedWaitFunctor(
325
                        std::shared_ptr<WatchState> watch_state,
326
                        asio::posix::stream_descriptor::wait_type type,
327
                        DBusWatch *watch,
328
                        DBusClient *client,
329
                        unsigned int flags) :
76✔
330
                        watch_state_ {watch_state},
331
                        type_ {type},
332
                        watch_ {watch},
333
                        client_ {client},
334
                        flags_ {flags} {
76✔
335
                }
336

337
                void operator()(boost::system::error_code ec) {
59✔
338
                        if (ec == boost::asio::error::operation_aborted) {
59✔
339
                                return;
340
                        }
341
                        // RepeatedWaitFunctor is called once by asio on completion - i.e. when dbus removes a
342
                        // watch, we don't have anything more to handle, but this function is called once more.
343
                        // We mark it with finished_ flag to avoid trying to handle an invalid (removed) watch.
344
                        if (watch_state_->finished_) {
59✔
345
                                return;
346
                        }
347
                        if (!dbus_watch_handle(watch_, flags_)) {
59✔
348
                                log::Error("Failed to handle watch");
×
349
                        }
350
                        HandleDispatch(client_->dbus_conn_.get(), DBUS_DISPATCH_DATA_REMAINS, client_);
59✔
351
                        // Check again if we're finished, as dbus_watch_handle may have caused it in the
352
                        // meantime.
353
                        if (watch_state_->finished_) {
59✔
354
                                return;
355
                        }
356

357
                        // In case we return before calling async_wait with *this in all functors,
358
                        // the last instance of WatchState will be unreferenced, and thus sd will be deleted
359
                        // too.
360
                        watch_state_->sd_->async_wait(type_, *this);
59✔
361
                }
362

363
        private:
364
                std::shared_ptr<WatchState> watch_state_;
365
                asio::posix::stream_descriptor::wait_type type_;
366
                DBusWatch *watch_;
367
                DBusClient *client_;
368
                unsigned int flags_;
369
        };
370

371
        unsigned int flags {dbus_watch_get_flags(w)};
38✔
372
        if (flags & DBUS_WATCH_READABLE) {
38✔
373
                RepeatedWaitFunctor read_ftor {
374
                        watch_state, asio::posix::stream_descriptor::wait_read, w, client, flags};
76✔
375
                watch_state->sd_->async_wait(asio::posix::stream_descriptor::wait_read, read_ftor);
38✔
376
        }
377
        if (flags & DBUS_WATCH_WRITABLE) {
38✔
378
                RepeatedWaitFunctor write_ftor {
379
                        watch_state, asio::posix::stream_descriptor::wait_write, w, client, flags};
×
380
                watch_state->sd_->async_wait(asio::posix::stream_descriptor::wait_write, write_ftor);
×
381
        }
382
        // Always watch for errors.
383
        RepeatedWaitFunctor error_ftor {
384
                watch_state, asio::posix::stream_descriptor::wait_error, w, client, DBUS_WATCH_ERROR};
76✔
385
        watch_state->sd_->async_wait(asio::posix::stream_descriptor::wait_error, error_ftor);
38✔
386

387
        // Assign the watch_state so that we have access to it in
388
        // RemoveDBusWatch() and we can mark it as finished.
389
        // watch_state will exist until the watch is marked as finished. So there's no
390
        // danger of dereferencing a deleted pointer.
391
        dbus_watch_set_data(w, watch_state.get(), NULL);
38✔
392
        return TRUE;
393
}
394

395
static void RemoveDBusWatch(DBusWatch *w, void *data) {
76✔
396
        WatchState *watch_state = static_cast<WatchState *>(dbus_watch_get_data(w));
76✔
397
        dbus_watch_set_data(w, NULL, NULL);
76✔
398
        if (nullptr == watch_state) {
76✔
399
                // The watch was already removed (marked as nullptr with dbus_watch_set_data above).
400
                // dbus calls this function twice, so we need to check for it.
401
                // Nothing to do.
402
                return;
403
        }
404

405
        watch_state->finished_ = true;
38✔
406
        if (watch_state->sd_ != nullptr) {
38✔
407
                watch_state->sd_->cancel();
38✔
408
        }
409
}
410

411
static void ToggleDBusWatch(DBusWatch *w, void *data) {
×
412
        if (dbus_watch_get_enabled(w)) {
×
413
                AddDBusWatch(w, data);
×
414
        } else {
415
                RemoveDBusWatch(w, data);
×
416
        }
417
}
×
418

419
dbus_bool_t AddDBusTimeout(DBusTimeout *t, void *data) {
89✔
420
        // See AddDBusWatch() for the details about this trick.
421
        if (!dbus_timeout_get_enabled(t)) {
89✔
422
                return TRUE;
423
        }
424

425
        DBusClient *client = static_cast<DBusClient *>(data);
426
        asio::steady_timer *timer =
427
                new asio::steady_timer {DBusClient::GetAsioIoContext(client->loop_)};
89✔
428
        timer->expires_after(chrono::milliseconds {dbus_timeout_get_interval(t)});
89✔
429
        timer->async_wait([t](boost::system::error_code ec) {
161✔
430
                if (ec == boost::asio::error::operation_aborted) {
72✔
431
                        return;
432
                }
433
                if (!dbus_timeout_handle(t)) {
×
434
                        log::Error("Failed to handle timeout");
×
435
                }
436
        });
437

438
        dbus_timeout_set_data(t, timer, NULL);
89✔
439

440
        return TRUE;
89✔
441
}
442

443
static void RemoveDBusTimeout(DBusTimeout *t, void *data) {
89✔
444
        asio::steady_timer *timer = static_cast<asio::steady_timer *>(dbus_timeout_get_data(t));
89✔
445
        dbus_timeout_set_data(t, NULL, NULL);
89✔
446
        if (timer != nullptr) {
89✔
447
                timer->cancel();
89✔
448
                delete timer;
89✔
449
        }
450
}
89✔
451

452
static void ToggleDBusTimeout(DBusTimeout *t, void *data) {
×
453
        if (dbus_timeout_get_enabled(t)) {
×
454
                AddDBusTimeout(t, data);
×
455
        } else {
456
                RemoveDBusTimeout(t, data);
×
457
        }
458
}
×
459

460
template <typename ReplyType>
461
bool CheckDBusMessageSignature(const string &signature);
462

463
template <>
464
bool CheckDBusMessageSignature<expected::ExpectedString>(const string &signature) {
6✔
465
        return signature == DBUS_TYPE_STRING_AS_STRING;
6✔
466
}
467

468
template <>
469
bool CheckDBusMessageSignature<ExpectedStringPair>(const string &signature) {
21✔
470
        return signature == (string(DBUS_TYPE_STRING_AS_STRING) + DBUS_TYPE_STRING_AS_STRING);
21✔
471
}
472

473
template <>
474
bool CheckDBusMessageSignature<expected::ExpectedBool>(const string &signature) {
9✔
475
        return signature == DBUS_TYPE_BOOLEAN_AS_STRING;
9✔
476
}
477

478
template <typename ReplyType>
479
ReplyType ExtractValueFromDBusMessage(DBusMessage *message);
480

481
template <>
482
expected::ExpectedString ExtractValueFromDBusMessage(DBusMessage *message) {
6✔
483
        DBusError dbus_error;
484
        dbus_error_init(&dbus_error);
6✔
485
        const char *result;
486
        if (!dbus_message_get_args(
6✔
487
                        message, &dbus_error, DBUS_TYPE_STRING, &result, DBUS_TYPE_INVALID)) {
488
                auto err = MakeError(
489
                        ValueError,
490
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
491
                                + dbus_error.name + "]");
×
492
                dbus_error_free(&dbus_error);
×
493
                return expected::unexpected(err);
×
494
        }
495
        return string(result);
12✔
496
}
497

498
template <>
499
ExpectedStringPair ExtractValueFromDBusMessage(DBusMessage *message) {
21✔
500
        DBusError dbus_error;
501
        dbus_error_init(&dbus_error);
21✔
502
        const char *value1;
503
        const char *value2;
504
        if (!dbus_message_get_args(
21✔
505
                        message,
506
                        &dbus_error,
507
                        DBUS_TYPE_STRING,
508
                        &value1,
509
                        DBUS_TYPE_STRING,
510
                        &value2,
511
                        DBUS_TYPE_INVALID)) {
512
                auto err = MakeError(
513
                        ValueError,
514
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
515
                                + dbus_error.name + "]");
×
516
                dbus_error_free(&dbus_error);
×
517
                return expected::unexpected(err);
×
518
        }
519
        return StringPair {string(value1), string(value2)};
21✔
520
}
521

522
template <>
523
expected::ExpectedBool ExtractValueFromDBusMessage(DBusMessage *message) {
9✔
524
        DBusError dbus_error;
525
        dbus_error_init(&dbus_error);
9✔
526
        bool result;
527
        if (!dbus_message_get_args(
9✔
528
                        message, &dbus_error, DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
529
                auto err = MakeError(
530
                        ValueError,
531
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
532
                                + dbus_error.name + "]");
×
533
                dbus_error_free(&dbus_error);
×
534
                return expected::unexpected(err);
×
535
        }
536
        return result;
537
}
538

539
template <typename ReplyType>
540
void HandleReply(DBusPendingCall *pending, void *data) {
5✔
541
        auto *handler = static_cast<DBusCallReplyHandler<ReplyType> *>(data);
542

543
        // for easier resource control
544
        unique_ptr<DBusPendingCall, decltype(&dbus_pending_call_unref)> pending_ptr {
4✔
545
                pending, dbus_pending_call_unref};
546
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> reply_ptr {
9✔
547
                dbus_pending_call_steal_reply(pending), dbus_message_unref};
548

549
        if (dbus_message_get_type(reply_ptr.get()) == DBUS_MESSAGE_TYPE_ERROR) {
5✔
550
                DBusError dbus_error;
551
                dbus_error_init(&dbus_error);
1✔
552
                const char *error;
553
                if (!dbus_message_get_args(
1✔
554
                                reply_ptr.get(), &dbus_error, DBUS_TYPE_STRING, &error, DBUS_TYPE_INVALID)) {
555
                        auto err = MakeError(
×
556
                                ValueError,
557
                                string("Got error reply, but failed to extrac the error from it: ")
×
558
                                        + dbus_error.message + "[" + dbus_error.name + "]");
×
559
                        dbus_error_free(&dbus_error);
×
560
                        (*handler)(expected::unexpected(err));
×
561
                } else {
562
                        const string error_str {error};
1✔
563
                        auto err = MakeError(ReplyError, "Got error reply: " + error_str);
2✔
564
                        (*handler)(expected::unexpected(err));
2✔
565
                }
566
                return;
567
        }
568

569
        const string signature {dbus_message_get_signature(reply_ptr.get())};
4✔
570
        if (!CheckDBusMessageSignature<ReplyType>(signature)) {
4✔
571
                auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
572
                (*handler)(expected::unexpected(err));
×
573
                return;
574
        }
575

576
        auto ex_reply = ExtractValueFromDBusMessage<ReplyType>(reply_ptr.get());
4✔
577
        (*handler)(ex_reply);
8✔
578
}
579

580
template <>
581
optional<DBusSignalHandler<expected::ExpectedString>> DBusClient::GetSignalHandler(
28✔
582
        const SignalSpec &spec) {
583
        if (signal_handlers_string_.find(spec) != signal_handlers_string_.cend()) {
28✔
584
                return signal_handlers_string_[spec];
2✔
585
        } else {
586
                return nullopt;
26✔
587
        }
588
}
589

590
template <>
591
optional<DBusSignalHandler<ExpectedStringPair>> DBusClient::GetSignalHandler(
28✔
592
        const SignalSpec &spec) {
593
        if (signal_handlers_string_pair_.find(spec) != signal_handlers_string_pair_.cend()) {
28✔
594
                return signal_handlers_string_pair_[spec];
6✔
595
        } else {
596
                return nullopt;
22✔
597
        }
598
}
599

600
DBusHandlerResult MsgFilter(DBusConnection *connection, DBusMessage *message, void *data) {
28✔
601
        if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) {
28✔
602
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
603
        }
604

605
        DBusClient *client = static_cast<DBusClient *>(data);
606

607
        // first check special case: disconnected message. In this case, clean up the connection state
608
        // to mark that we're not watching any signals, so whoever wants them to be watched, needs to
609
        // register them again.
610
        if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
28✔
611
                client->CleanUp();
×
612
                return DBUS_HANDLER_RESULT_HANDLED;
×
613
        }
614

615
        const string iface = dbus_message_get_interface(message);
28✔
616
        const string signal = dbus_message_get_member(message);
28✔
617

618
        const string signature {dbus_message_get_signature(message)};
28✔
619

620
        auto opt_string_handler = client->GetSignalHandler<expected::ExpectedString>({iface, signal});
28✔
621
        auto opt_string_pair_handler = client->GetSignalHandler<ExpectedStringPair>({iface, signal});
56✔
622

623
        // either no match or only one match
624
        assert(
625
                !(static_cast<bool>(opt_string_handler) || static_cast<bool>(opt_string_pair_handler))
626
                || (static_cast<bool>(opt_string_handler) ^ static_cast<bool>(opt_string_pair_handler)));
627

628
        if (opt_string_handler) {
28✔
629
                if (!CheckDBusMessageSignature<expected::ExpectedString>(signature)) {
2✔
UNCOV
630
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
631
                        (*opt_string_handler)(expected::unexpected(err));
×
632
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
633
                }
634

635
                auto ex_value = ExtractValueFromDBusMessage<expected::ExpectedString>(message);
2✔
636
                (*opt_string_handler)(ex_value);
4✔
637
                return DBUS_HANDLER_RESULT_HANDLED;
638
        } else if (opt_string_pair_handler) {
26✔
639
                if (!CheckDBusMessageSignature<ExpectedStringPair>(signature)) {
6✔
UNCOV
640
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
641
                        (*opt_string_pair_handler)(expected::unexpected(err));
×
642
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
643
                }
644

645
                auto ex_value = ExtractValueFromDBusMessage<ExpectedStringPair>(message);
6✔
646
                (*opt_string_pair_handler)(ex_value);
12✔
647
                return DBUS_HANDLER_RESULT_HANDLED;
648
        } else {
649
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
650
        }
651
}
652

653
bool DBusClient::WatchingSignal(const string &iface, const string &signal) const {
22✔
654
        SignalSpec spec {iface, signal};
44✔
655

656
        if (signal_handlers_string_.find(spec) != signal_handlers_string_.end()) {
22✔
657
                return true;
658
        }
659

660
        if (signal_handlers_string_pair_.find(spec) != signal_handlers_string_pair_.end()) {
22✔
661
                return true;
10✔
662
        }
663

664
        return false;
665
}
666

UNCOV
667
void DBusClient::CleanUp() {
×
668
        std::vector<mender::common::dbus::SignalSpec> keys;
×
669
        for (const auto &pair : signal_handlers_string_) {
×
670
                keys.push_back(pair.first);
×
671
        }
UNCOV
672
        for (const auto &pair : signal_handlers_string_pair_) {
×
673
                keys.push_back(pair.first);
×
674
        }
675

UNCOV
676
        for (const auto &key : keys) {
×
677
                UnregisterSignalHandler(key.iface_, key.signal_);
×
678
        }
UNCOV
679
}
×
680

681
static inline string GetMethodSpec(const string &interface, const string &method) {
51✔
682
        return interface + "." + method;
102✔
683
}
684

685
template <>
686
void DBusObject::AddMethodHandler(
2✔
687
        const string &interface,
688
        const string &method,
689
        DBusMethodHandler<expected::ExpectedString> handler) {
690
        string spec = GetMethodSpec(interface, method);
2✔
691
        method_handlers_string_[spec] = handler;
2✔
692
}
2✔
693

694
template <>
695
void DBusObject::AddMethodHandler(
13✔
696
        const string &interface, const string &method, DBusMethodHandler<ExpectedStringPair> handler) {
697
        string spec = GetMethodSpec(interface, method);
13✔
698
        method_handlers_string_pair_[spec] = handler;
13✔
699
}
13✔
700

701
template <>
702
void DBusObject::AddMethodHandler(
10✔
703
        const string &interface,
704
        const string &method,
705
        DBusMethodHandler<expected::ExpectedBool> handler) {
706
        string spec = GetMethodSpec(interface, method);
10✔
707
        method_handlers_bool_[spec] = handler;
10✔
708
}
10✔
709

710
template <>
711
optional<DBusMethodHandler<expected::ExpectedString>> DBusObject::GetMethodHandler(
26✔
712
        const MethodSpec &spec) {
713
        if (method_handlers_string_.find(spec) != method_handlers_string_.cend()) {
26✔
714
                return method_handlers_string_[spec];
2✔
715
        } else {
716
                return nullopt;
24✔
717
        }
718
}
719

720
template <>
721
optional<DBusMethodHandler<ExpectedStringPair>> DBusObject::GetMethodHandler(
26✔
722
        const MethodSpec &spec) {
723
        if (method_handlers_string_pair_.find(spec) != method_handlers_string_pair_.cend()) {
26✔
724
                return method_handlers_string_pair_[spec];
15✔
725
        } else {
726
                return nullopt;
11✔
727
        }
728
}
729

730
template <>
731
optional<DBusMethodHandler<expected::ExpectedBool>> DBusObject::GetMethodHandler(
26✔
732
        const MethodSpec &spec) {
733
        if (method_handlers_bool_.find(spec) != method_handlers_bool_.cend()) {
26✔
734
                return method_handlers_bool_[spec];
9✔
735
        } else {
736
                return nullopt;
17✔
737
        }
738
}
739

740
DBusServer::~DBusServer() {
34✔
741
        if (!dbus_conn_) {
17✔
742
                // nothing to do without a DBus connection
UNCOV
743
                return;
×
744
        }
745

746
        for (auto obj : objects_) {
34✔
747
                if (!dbus_connection_unregister_object_path(dbus_conn_.get(), obj->GetPath().c_str())) {
17✔
UNCOV
748
                        log::Warning("Failed to unregister DBus object " + obj->GetPath());
×
749
                }
750
        }
751

752
        DBusError dbus_error;
753
        dbus_error_init(&dbus_error);
17✔
754
        if (dbus_bus_release_name(dbus_conn_.get(), service_name_.c_str(), &dbus_error) == -1) {
17✔
UNCOV
755
                log::Warning(
×
756
                        string("Failed to release DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
757
                        + "]");
×
758
                dbus_error_free(&dbus_error);
×
759
        }
760
}
17✔
761

762
template <typename ReturnType>
763
bool AddReturnDataToDBusMessage(DBusMessage *message, ReturnType data);
764

765
template <>
766
bool AddReturnDataToDBusMessage(DBusMessage *message, string data) {
2✔
767
        const char *data_cstr = data.c_str();
2✔
768
        return static_cast<bool>(
769
                dbus_message_append_args(message, DBUS_TYPE_STRING, &data_cstr, DBUS_TYPE_INVALID));
2✔
770
}
771

772
template <>
773
bool AddReturnDataToDBusMessage(DBusMessage *message, StringPair data) {
21✔
774
        const char *data_cstr1 = data.first.c_str();
21✔
775
        const char *data_cstr2 = data.second.c_str();
21✔
776
        return static_cast<bool>(dbus_message_append_args(
21✔
777
                message, DBUS_TYPE_STRING, &data_cstr1, DBUS_TYPE_STRING, &data_cstr2, DBUS_TYPE_INVALID));
21✔
778
}
779

780
template <>
781
bool AddReturnDataToDBusMessage(DBusMessage *message, bool data) {
9✔
782
        // (with clang) bool may be neither 0 nor 1 and libdbus has an assertion
783
        // requiring one of these two integer values.
784
        dbus_bool_t value = static_cast<dbus_bool_t>(data);
9✔
785
        return static_cast<bool>(
786
                dbus_message_append_args(message, DBUS_TYPE_BOOLEAN, &value, DBUS_TYPE_INVALID));
9✔
787
}
788

789
DBusHandlerResult HandleMethodCall(DBusConnection *connection, DBusMessage *message, void *data) {
26✔
790
        DBusObject *obj = static_cast<DBusObject *>(data);
791

792
        string spec =
793
                GetMethodSpec(dbus_message_get_interface(message), dbus_message_get_member(message));
52✔
794

795
        auto opt_string_handler = obj->GetMethodHandler<expected::ExpectedString>(spec);
26✔
796
        auto opt_string_pair_handler = obj->GetMethodHandler<ExpectedStringPair>(spec);
26✔
797
        auto opt_bool_handler = obj->GetMethodHandler<expected::ExpectedBool>(spec);
26✔
798

799
        if (!opt_string_handler && !opt_string_pair_handler && !opt_bool_handler) {
26✔
800
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
801
        }
802

803
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> reply_msg {nullptr, dbus_message_unref};
26✔
804

805
        if (opt_string_handler) {
26✔
806
                expected::ExpectedString ex_return_data = (*opt_string_handler)();
2✔
807
                if (!ex_return_data) {
2✔
808
                        auto &err = ex_return_data.error();
809
                        reply_msg.reset(
1✔
810
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
2✔
811
                        if (!reply_msg) {
1✔
UNCOV
812
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
813
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
814
                        }
815
                } else {
816
                        reply_msg.reset(dbus_message_new_method_return(message));
1✔
817
                        if (!reply_msg) {
1✔
UNCOV
818
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
819
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
820
                        }
821
                        if (!AddReturnDataToDBusMessage<string>(reply_msg.get(), ex_return_data.value())) {
2✔
UNCOV
822
                                log::Error(
×
823
                                        "Failed to add return value to reply DBus message when handling method "
UNCOV
824
                                        + spec);
×
825
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
826
                        }
827
                }
828
        } else if (opt_string_pair_handler) {
24✔
829
                ExpectedStringPair ex_return_data = (*opt_string_pair_handler)();
15✔
830
                if (!ex_return_data) {
15✔
831
                        auto &err = ex_return_data.error();
UNCOV
832
                        reply_msg.reset(
×
833
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
×
834
                        if (!reply_msg) {
×
835
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
836
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
837
                        }
838
                } else {
839
                        reply_msg.reset(dbus_message_new_method_return(message));
15✔
840
                        if (!reply_msg) {
15✔
UNCOV
841
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
842
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
843
                        }
844
                        if (!AddReturnDataToDBusMessage<StringPair>(reply_msg.get(), ex_return_data.value())) {
15✔
UNCOV
845
                                log::Error(
×
846
                                        "Failed to add return value to reply DBus message when handling method "
UNCOV
847
                                        + spec);
×
848
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
849
                        }
850
                }
851
        } else if (opt_bool_handler) {
9✔
852
                expected::ExpectedBool ex_return_data = (*opt_bool_handler)();
9✔
853
                if (!ex_return_data) {
9✔
854
                        auto &err = ex_return_data.error();
UNCOV
855
                        reply_msg.reset(
×
856
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
×
857
                        if (!reply_msg) {
×
858
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
859
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
860
                        }
861
                } else {
862
                        reply_msg.reset(dbus_message_new_method_return(message));
9✔
863
                        if (!reply_msg) {
9✔
UNCOV
864
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
865
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
866
                        }
867
                        if (!AddReturnDataToDBusMessage<bool>(reply_msg.get(), ex_return_data.value())) {
9✔
UNCOV
868
                                log::Error(
×
869
                                        "Failed to add return value to reply DBus message when handling method "
UNCOV
870
                                        + spec);
×
871
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
872
                        }
873
                }
874
        }
875

876
        if (!dbus_connection_send(connection, reply_msg.get(), NULL)) {
26✔
877
                // can only happen in case of no memory
UNCOV
878
                log::Error("Failed to send reply DBus message when handling method " + spec);
×
879
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
880
        }
881

882
        return DBUS_HANDLER_RESULT_HANDLED;
883
}
884

885
static DBusObjectPathVTable dbus_vtable = {.message_function = HandleMethodCall};
886

887
error::Error DBusServer::AdvertiseObject(DBusObjectPtr obj) {
17✔
888
        if (!dbus_conn_ || !dbus_connection_get_is_connected(dbus_conn_.get())) {
17✔
889
                auto err = InitializeConnection();
17✔
890
                if (err != error::NoError) {
17✔
UNCOV
891
                        return err;
×
892
                }
893
        }
894

895
        const string &obj_path {obj->GetPath()};
896
        DBusError dbus_error;
897
        dbus_error_init(&dbus_error);
17✔
898

899
        if (!dbus_connection_try_register_object_path(
17✔
900
                        dbus_conn_.get(), obj_path.c_str(), &dbus_vtable, obj.get(), &dbus_error)) {
901
                auto err = MakeError(
902
                        ConnectionError,
UNCOV
903
                        (string("Failed to register object ") + obj_path + ": " + dbus_error.message + " ["
×
904
                         + dbus_error.name + "]"));
×
905
                dbus_error_free(&dbus_error);
×
906
                return err;
×
907
        }
908
        objects_.push_back(obj);
17✔
909

910
        // We must register dbus name AFTER we already registered object paths.
911
        // If our server was started due to a message directed at our name configured in a .service
912
        // file, the messages aimed at the name will be delivered immediately after we successuly
913
        // register the name. If we registered the name before registering object paths, the messages
914
        // would be discared silently.
915
        auto err = RegisterDBusName();
17✔
916
        if (err != error::NoError) {
17✔
UNCOV
917
                return err;
×
918
        }
919

920
        // If at this point we already have messages in the buffer (i.e. this service was started by
921
        // dbus because a message was sent to our registered name), we must call HandleDispatch manually
922
        // once to make sure that all messages are handled. We cannot rely on
923
        // dbus_connection_set_dispatch_status_function from InitializeConnection to call it for us at
924
        // this point.
925
        HandleDispatch(dbus_conn_.get(), DBUS_DISPATCH_DATA_REMAINS, this);
17✔
926
        return error::NoError;
17✔
927
}
928

929
template <typename SignalValueType>
930
error::Error DBusServer::EmitSignal(
1✔
931
        const string &path, const string &iface, const string &signal, SignalValueType value) {
932
        if (!dbus_conn_ || !dbus_connection_get_is_connected(dbus_conn_.get())) {
1✔
UNCOV
933
                auto err = InitializeConnection();
×
934
                if (err != error::NoError) {
×
935
                        return err;
×
936
                }
937
        }
938

939
        auto err = RegisterDBusName();
1✔
940
        if (err != error::NoError) {
1✔
UNCOV
941
                return err;
×
942
        }
943

944
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> signal_msg {
2✔
945
                dbus_message_new_signal(path.c_str(), iface.c_str(), signal.c_str()), dbus_message_unref};
946
        if (!signal_msg) {
1✔
UNCOV
947
                return MakeError(MessageError, "Failed to create signal message");
×
948
        }
949

950
        if (!AddReturnDataToDBusMessage<SignalValueType>(signal_msg.get(), value)) {
2✔
UNCOV
951
                return MakeError(MessageError, "Failed to add data to the signal message");
×
952
        }
953

954
        if (!dbus_connection_send(dbus_conn_.get(), signal_msg.get(), NULL)) {
1✔
955
                // can only happen in case of no memory
UNCOV
956
                return MakeError(ConnectionError, "Failed to send signal message");
×
957
        }
958

959
        return error::NoError;
1✔
960
}
961

962
template error::Error DBusServer::EmitSignal(
963
        const string &path, const string &iface, const string &signal, string value);
964

965
template error::Error DBusServer::EmitSignal(
966
        const string &path, const string &iface, const string &signal, StringPair value);
967

968
error::Error DBusServer::RegisterDBusName() {
24✔
969
        // We could also do DBUS_NAME_FLAG_ALLOW_REPLACEMENT for cases where two of
970
        // processes request the same name, but it would require handling of the
971
        // NameLost signal and triggering termination.
972
        DBusError dbus_error;
973
        dbus_error_init(&dbus_error);
24✔
974

975
        if (dbus_bus_request_name(
24✔
976
                        dbus_conn_.get(), service_name_.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error)
977
                == -1) {
978
                dbus_conn_.reset();
979
                auto err = MakeError(
980
                        ConnectionError,
UNCOV
981
                        (string("Failed to register DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
982
                         + "]"));
×
983
                dbus_error_free(&dbus_error);
×
984
                return err;
×
985
        }
986

987
        return error::NoError;
24✔
988
}
989
} // namespace dbus
990
} // namespace common
991
} // 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