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

mendersoftware / mender / 1018307341

27 Sep 2023 12:04PM UTC coverage: 77.682% (-0.4%) from 78.076%
1018307341

push

gitlab-ci

vpodzime
fix: Wait repeatedly for I/O events on DBus file descriptors

ASIO's `stream_descriptor::async_read/wait/error()` all register
one-off handlers. We actually need to watch the given file
descriptors for all events until we cancel the watch.

We can do that by using functors re-registering themselves
whenever called.

Also, add a tip and commented code to DBus tests helping debug
DBus issues.

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

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

6474 of 8334 relevant lines covered (77.68%)

11035.56 hits per line

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

62.29
/common/dbus/platform/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/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
namespace optional = mender::common::optional;
43

44
using namespace std;
45

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

56
// friends can't be static (see class DBusClient)
57
void HandleDispatch(DBusConnection *conn, DBusDispatchStatus status, void *data);
58
dbus_bool_t AddDBusWatch(DBusWatch *w, void *data);
59
static void RemoveDBusWatch(DBusWatch *w, void *data);
60
static void ToggleDBusWatch(DBusWatch *w, void *data);
61
dbus_bool_t AddDBusTimeout(DBusTimeout *t, void *data);
62
static void RemoveDBusTimeout(DBusTimeout *t, void *data);
63
static void ToggleDBusTimeout(DBusTimeout *t, void *data);
64

65
template <typename ReplyType>
66
void HandleReply(DBusPendingCall *pending, void *data);
67

68
DBusHandlerResult MsgFilter(DBusConnection *connection, DBusMessage *message, void *data);
69

70
error::Error DBusPeer::InitializeConnection() {
9✔
71
        DBusError dbus_error;
72
        dbus_error_init(&dbus_error);
9✔
73
        dbus_conn_.reset(dbus_bus_get_private(DBUS_BUS_SYSTEM, &dbus_error));
9✔
74
        if (!dbus_conn_) {
9✔
75
                auto err = MakeError(
76
                        ConnectionError,
77
                        string("Failed to get connection to system bus: ") + dbus_error.message + "["
×
78
                                + dbus_error.name + "]");
×
79
                dbus_error_free(&dbus_error);
×
80
                return err;
×
81
        }
82

83
        dbus_connection_set_exit_on_disconnect(dbus_conn_.get(), FALSE);
9✔
84
        if (!dbus_connection_set_watch_functions(
9✔
85
                        dbus_conn_.get(), AddDBusWatch, RemoveDBusWatch, ToggleDBusWatch, this, NULL)) {
86
                dbus_conn_.reset();
×
87
                return MakeError(ConnectionError, "Failed to set watch functions");
×
88
        }
89
        if (!dbus_connection_set_timeout_functions(
9✔
90
                        dbus_conn_.get(), AddDBusTimeout, RemoveDBusTimeout, ToggleDBusTimeout, this, NULL)) {
91
                dbus_conn_.reset();
×
92
                return MakeError(ConnectionError, "Failed to set timeout functions");
×
93
        }
94

95
        dbus_connection_set_dispatch_status_function(dbus_conn_.get(), HandleDispatch, this, NULL);
9✔
96

97
        return error::NoError;
9✔
98
}
99

100
error::Error DBusClient::InitializeConnection() {
6✔
101
        auto err = DBusPeer::InitializeConnection();
12✔
102
        if (err != error::NoError) {
6✔
103
                return err;
×
104
        }
105

106
        if (!dbus_connection_add_filter(dbus_conn_.get(), MsgFilter, this, NULL)) {
6✔
107
                dbus_conn_.reset();
×
108
                return MakeError(ConnectionError, "Failed to set message filter");
×
109
        }
110

111
        return error::NoError;
6✔
112
}
113

114
template <typename ReplyType>
115
void FreeHandlerCopy(void *data) {
5✔
116
        auto *handler = static_cast<DBusCallReplyHandler<ReplyType> *>(data);
5✔
117
        delete handler;
5✔
118
}
5✔
119

120
template <typename ReplyType>
121
error::Error DBusClient::CallMethod(
5✔
122
        const string &destination,
123
        const string &path,
124
        const string &iface,
125
        const string &method,
126
        DBusCallReplyHandler<ReplyType> handler) {
127
        if (!dbus_conn_) {
5✔
128
                auto err = InitializeConnection();
2✔
129
                if (err != error::NoError) {
2✔
130
                        return err;
×
131
                }
132
        }
133

134
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> dbus_msg {
10✔
135
                dbus_message_new_method_call(
136
                        destination.c_str(), path.c_str(), iface.c_str(), method.c_str()),
137
                dbus_message_unref};
5✔
138
        if (!dbus_msg) {
5✔
139
                return MakeError(MessageError, "Failed to create new message");
×
140
        }
141

142
        DBusPendingCall *pending;
143
        if (!dbus_connection_send_with_reply(
5✔
144
                        dbus_conn_.get(), dbus_msg.get(), &pending, DBUS_TIMEOUT_USE_DEFAULT)) {
145
                return MakeError(MessageError, "Unable to add message to the queue");
×
146
        }
147

148
        // We need to create a copy here because we need to make sure the handler,
149
        // which might be a lambda, even with captures, will live long enough for
150
        // the finished pending call to use it.
151
        unique_ptr<DBusCallReplyHandler<ReplyType>> handler_copy {
10✔
152
                new DBusCallReplyHandler<ReplyType>(handler)};
5✔
153
        if (!dbus_pending_call_set_notify(
5✔
154
                        pending, HandleReply<ReplyType>, handler_copy.get(), FreeHandlerCopy<ReplyType>)) {
5✔
155
                return MakeError(MessageError, "Failed to set reply handler");
×
156
        }
157

158
        // FreeHandlerCopy() takes care of the allocated handler copy
159
        handler_copy.release();
5✔
160

161
        return error::NoError;
5✔
162
}
163

164
template error::Error DBusClient::CallMethod(
165
        const string &destination,
166
        const string &path,
167
        const string &iface,
168
        const string &method,
169
        DBusCallReplyHandler<expected::ExpectedString> handler);
170

171
template <>
172
void DBusClient::AddSignalHandler(
3✔
173
        const SignalSpec &spec, DBusSignalHandler<expected::ExpectedString> handler) {
174
        signal_handlers_string_[spec] = handler;
3✔
175
}
3✔
176

177
template <>
178
void DBusClient::AddSignalHandler(
1✔
179
        const SignalSpec &spec, DBusSignalHandler<ExpectedStringPair> handler) {
180
        signal_handlers_string_pair_[spec] = handler;
1✔
181
}
1✔
182

183
static inline string GetSignalMatchRule(const string &iface, const string &signal) {
12✔
184
        return string("type='signal'") + ",interface='" + iface + "',member='" + signal + "'";
36✔
185
}
186

187
template <typename SignalValueType>
188
error::Error DBusClient::RegisterSignalHandler(
3✔
189
        const string &iface, const string &signal, DBusSignalHandler<SignalValueType> handler) {
190
        if (!dbus_conn_) {
3✔
191
                auto err = InitializeConnection();
3✔
192
                if (err != error::NoError) {
3✔
193
                        return err;
×
194
                }
195
        }
196

197
        // Registering a signal with the low-level DBus API means telling the DBus
198
        // daemon that we are interested in messages matching a rule. It could be
199
        // anything, but we are interested in (specific) signals. The MsgFilter()
200
        // function below takes care of actually invoking the right handler.
201
        const string match_rule = GetSignalMatchRule(iface, signal);
6✔
202

203
        DBusError dbus_error;
204
        dbus_error_init(&dbus_error);
3✔
205
        dbus_bus_add_match(dbus_conn_.get(), match_rule.c_str(), &dbus_error);
3✔
206
        if (dbus_error_is_set(&dbus_error)) {
3✔
207
                auto err = MakeError(
×
208
                        ConnectionError, string("Failed to register signal reception: ") + dbus_error.message);
×
209
                dbus_error_free(&dbus_error);
×
210
                return err;
×
211
        }
212
        AddSignalHandler<SignalValueType>(match_rule, handler);
3✔
213
        return error::NoError;
3✔
214
}
215

216
template error::Error DBusClient::RegisterSignalHandler(
217
        const string &iface, const string &signal, DBusSignalHandler<expected::ExpectedString> handler);
218

219
template error::Error DBusClient::RegisterSignalHandler(
220
        const string &iface, const string &signal, DBusSignalHandler<ExpectedStringPair> handler);
221

222
void DBusClient::UnregisterSignalHandler(const string &iface, const string &signal) {
1✔
223
        // we use the match rule as a unique string for the given signal
224
        const string spec = GetSignalMatchRule(iface, signal);
2✔
225

226
        // should be in at most one set, but erase() is a noop if not found
227
        signal_handlers_string_.erase(spec);
1✔
228
        signal_handlers_string_pair_.erase(spec);
1✔
229
}
1✔
230

231
void HandleDispatch(DBusConnection *conn, DBusDispatchStatus status, void *data) {
24✔
232
        DBusClient *client = static_cast<DBusClient *>(data);
24✔
233
        if (status == DBUS_DISPATCH_DATA_REMAINS) {
24✔
234
                // This must give other things in the loop a chance to run because
235
                // dbus_connection_dispatch() below can cause this to be called again.
236
                client->loop_.Post([conn]() {
16✔
237
                        while (dbus_connection_get_dispatch_status(conn) == DBUS_DISPATCH_DATA_REMAINS) {
26✔
238
                                dbus_connection_dispatch(conn);
18✔
239
                        }
240
                });
32✔
241
        }
242
}
24✔
243

244
dbus_bool_t AddDBusWatch(DBusWatch *w, void *data) {
18✔
245
        // libdbus adds watches in two steps -- using AddDBusWatch() with a disabled
246
        // watch which should allocate all the necessary data (and can fail)
247
        // followed by ToggleDBusWatch() to enable the watch (see below). We
248
        // simplify things for ourselves by ignoring disabled watches and only
249
        // actually adding them when ToggleDBusWatch() is called.
250
        if (!dbus_watch_get_enabled(w)) {
18✔
251
                return TRUE;
9✔
252
        }
253

254
        DBusClient *client = static_cast<DBusClient *>(data);
9✔
255
        unique_ptr<asio::posix::stream_descriptor> sd {
256
                new asio::posix::stream_descriptor(DBusClient::GetAsioIoContext(client->loop_))};
18✔
257
        boost::system::error_code ec;
9✔
258
        sd->assign(dbus_watch_get_unix_fd(w), ec);
9✔
259
        if (ec) {
9✔
260
                log::Error("Failed to assign DBus FD to ASIO stream descriptor");
×
261
                return FALSE;
×
262
        }
263

264
        class RepeatedWaitFunctor {
265
        public:
266
                RepeatedWaitFunctor(
18✔
267
                        asio::posix::stream_descriptor *sd,
268
                        asio::posix::stream_descriptor::wait_type type,
269
                        DBusWatch *watch,
270
                        DBusClient *client,
271
                        unsigned int flags) :
18✔
272
                        sd_ {sd},
273
                        type_ {type},
274
                        watch_ {watch},
275
                        client_ {client},
276
                        flags_ {flags} {
18✔
277
                }
18✔
278

279
                void operator()(boost::system::error_code ec) {
8✔
280
                        if (ec == boost::asio::error::operation_aborted) {
8✔
281
                                return;
×
282
                        }
283
                        if (!dbus_watch_handle(watch_, flags_)) {
8✔
284
                                log::Error("Failed to handle watch");
×
285
                        }
286
                        HandleDispatch(client_->dbus_conn_.get(), DBUS_DISPATCH_DATA_REMAINS, client_);
8✔
287
                        sd_->async_wait(type_, *this);
8✔
288
                }
289

290
        private:
291
                asio::posix::stream_descriptor *sd_;
292
                asio::posix::stream_descriptor::wait_type type_;
293
                DBusWatch *watch_;
294
                DBusClient *client_;
295
                unsigned int flags_;
296
        };
297

298
        unsigned int flags {dbus_watch_get_flags(w)};
9✔
299
        if (flags & DBUS_WATCH_READABLE) {
9✔
300
                RepeatedWaitFunctor read_ftor {
301
                        sd.get(), asio::posix::stream_descriptor::wait_read, w, client, flags};
9✔
302
                sd->async_wait(asio::posix::stream_descriptor::wait_read, read_ftor);
9✔
303
        }
304
        if (flags & DBUS_WATCH_WRITABLE) {
9✔
305
                RepeatedWaitFunctor write_ftor {
306
                        sd.get(), asio::posix::stream_descriptor::wait_write, w, client, flags};
×
307
                sd->async_wait(asio::posix::stream_descriptor::wait_write, write_ftor);
×
308
        }
309
        // Always watch for errors.
310
        RepeatedWaitFunctor error_ftor {
311
                sd.get(), asio::posix::stream_descriptor::wait_error, w, client, flags};
9✔
312
        sd->async_wait(asio::posix::stream_descriptor::wait_error, error_ftor);
9✔
313

314
        // Assign the stream_descriptor so that we have access to it in
315
        // RemoveDBusWatch() and we can delete it.
316
        dbus_watch_set_data(w, sd.release(), NULL);
9✔
317
        return TRUE;
9✔
318
}
319

320
static void RemoveDBusWatch(DBusWatch *w, void *data) {
18✔
321
        asio::posix::stream_descriptor *sd =
322
                static_cast<asio::posix::stream_descriptor *>(dbus_watch_get_data(w));
18✔
323
        dbus_watch_set_data(w, NULL, NULL);
18✔
324
        if (sd != nullptr) {
18✔
325
                sd->cancel();
9✔
326
                delete sd;
9✔
327
        }
328
}
18✔
329

330
static void ToggleDBusWatch(DBusWatch *w, void *data) {
×
331
        if (dbus_watch_get_enabled(w)) {
×
332
                AddDBusWatch(w, data);
×
333
        } else {
334
                RemoveDBusWatch(w, data);
×
335
        }
336
}
×
337

338
dbus_bool_t AddDBusTimeout(DBusTimeout *t, void *data) {
15✔
339
        // See AddDBusWatch() for the details about this trick.
340
        if (!dbus_timeout_get_enabled(t)) {
15✔
341
                return TRUE;
×
342
        }
343

344
        DBusClient *client = static_cast<DBusClient *>(data);
15✔
345
        asio::steady_timer *timer =
346
                new asio::steady_timer {DBusClient::GetAsioIoContext(client->loop_)};
15✔
347
        timer->expires_after(chrono::milliseconds {dbus_timeout_get_interval(t)});
15✔
348
        timer->async_wait([t](boost::system::error_code ec) {
15✔
349
                if (ec == boost::asio::error::operation_aborted) {
12✔
350
                        return;
12✔
351
                }
352
                if (!dbus_timeout_handle(t)) {
×
353
                        log::Error("Failed to handle timeout");
×
354
                }
355
        });
356

357
        dbus_timeout_set_data(t, timer, NULL);
15✔
358

359
        return TRUE;
15✔
360
}
361

362
static void RemoveDBusTimeout(DBusTimeout *t, void *data) {
15✔
363
        asio::steady_timer *timer = static_cast<asio::steady_timer *>(dbus_timeout_get_data(t));
15✔
364
        dbus_timeout_set_data(t, NULL, NULL);
15✔
365
        if (timer != nullptr) {
15✔
366
                timer->cancel();
15✔
367
                delete timer;
15✔
368
        }
369
}
15✔
370

371
static void ToggleDBusTimeout(DBusTimeout *t, void *data) {
×
372
        if (dbus_timeout_get_enabled(t)) {
×
373
                AddDBusTimeout(t, data);
×
374
        } else {
375
                RemoveDBusTimeout(t, data);
×
376
        }
377
}
×
378

379
template <typename ReplyType>
380
bool CheckDBusMessageSignature(const string &signature);
381

382
template <>
383
bool CheckDBusMessageSignature<expected::ExpectedString>(const string &signature) {
6✔
384
        return signature == DBUS_TYPE_STRING_AS_STRING;
6✔
385
}
386

387
template <>
388
bool CheckDBusMessageSignature<ExpectedStringPair>(const string &signature) {
×
389
        return signature == (string(DBUS_TYPE_STRING_AS_STRING) + DBUS_TYPE_STRING_AS_STRING);
×
390
}
391

392
template <typename ReplyType>
393
ReplyType ExtractValueFromDBusMessage(DBusMessage *message);
394

395
template <>
396
expected::ExpectedString ExtractValueFromDBusMessage(DBusMessage *message) {
6✔
397
        DBusError dbus_error;
398
        dbus_error_init(&dbus_error);
6✔
399
        const char *result;
400
        if (!dbus_message_get_args(
6✔
401
                        message, &dbus_error, DBUS_TYPE_STRING, &result, DBUS_TYPE_INVALID)) {
402
                auto err = MakeError(
403
                        ValueError,
404
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
405
                                + dbus_error.name + "]");
×
406
                dbus_error_free(&dbus_error);
×
407
                return expected::unexpected(err);
×
408
        }
409
        return string(result);
12✔
410
}
411

412
template <>
413
ExpectedStringPair ExtractValueFromDBusMessage(DBusMessage *message) {
×
414
        DBusError dbus_error;
415
        dbus_error_init(&dbus_error);
×
416
        const char *value1;
417
        const char *value2;
418
        if (!dbus_message_get_args(
×
419
                        message,
420
                        &dbus_error,
421
                        DBUS_TYPE_STRING,
422
                        &value1,
423
                        DBUS_TYPE_STRING,
424
                        &value2,
425
                        DBUS_TYPE_INVALID)) {
426
                auto err = MakeError(
427
                        ValueError,
428
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
429
                                + dbus_error.name + "]");
×
430
                dbus_error_free(&dbus_error);
×
431
                return expected::unexpected(err);
×
432
        }
433
        return StringPair {string(value1), string(value2)};
×
434
}
435

436
template <typename ReplyType>
437
void HandleReply(DBusPendingCall *pending, void *data) {
5✔
438
        auto *handler = static_cast<DBusCallReplyHandler<ReplyType> *>(data);
5✔
439

440
        // for easier resource control
441
        unique_ptr<DBusPendingCall, decltype(&dbus_pending_call_unref)> pending_ptr {
×
442
                pending, dbus_pending_call_unref};
5✔
443
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> reply_ptr {
5✔
444
                dbus_pending_call_steal_reply(pending), dbus_message_unref};
5✔
445

446
        if (dbus_message_get_type(reply_ptr.get()) == DBUS_MESSAGE_TYPE_ERROR) {
5✔
447
                DBusError dbus_error;
448
                dbus_error_init(&dbus_error);
1✔
449
                const char *error;
450
                if (!dbus_message_get_args(
1✔
451
                                reply_ptr.get(), &dbus_error, DBUS_TYPE_STRING, &error, DBUS_TYPE_INVALID)) {
452
                        auto err = MakeError(
×
453
                                ValueError,
454
                                string("Got error reply, but failed to extrac the error from it: ")
×
455
                                        + dbus_error.message + "[" + dbus_error.name + "]");
×
456
                        dbus_error_free(&dbus_error);
×
457
                        (*handler)(expected::unexpected(err));
×
458
                } else {
459
                        const string error_str {error};
2✔
460
                        auto err = MakeError(ReplyError, "Got error reply: " + error_str);
1✔
461
                        (*handler)(expected::unexpected(err));
1✔
462
                }
463
                return;
1✔
464
        }
465

466
        const string signature {dbus_message_get_signature(reply_ptr.get())};
4✔
467
        if (!CheckDBusMessageSignature<ReplyType>(signature)) {
4✔
468
                auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
469
                (*handler)(expected::unexpected(err));
×
470
                return;
×
471
        }
472

473
        auto ex_reply = ExtractValueFromDBusMessage<ReplyType>(reply_ptr.get());
4✔
474
        (*handler)(ex_reply);
4✔
475
}
476

477
template <>
478
optional::optional<DBusSignalHandler<expected::ExpectedString>> DBusClient::GetSignalHandler(
7✔
479
        const SignalSpec &spec) {
480
        if (signal_handlers_string_.find(spec) != signal_handlers_string_.cend()) {
7✔
481
                return signal_handlers_string_[spec];
2✔
482
        } else {
483
                return optional::nullopt;
5✔
484
        }
485
}
486

487
template <>
488
optional::optional<DBusSignalHandler<ExpectedStringPair>> DBusClient::GetSignalHandler(
7✔
489
        const SignalSpec &spec) {
490
        if (signal_handlers_string_pair_.find(spec) != signal_handlers_string_pair_.cend()) {
7✔
491
                return signal_handlers_string_pair_[spec];
×
492
        } else {
493
                return optional::nullopt;
7✔
494
        }
495
}
496

497
DBusHandlerResult MsgFilter(DBusConnection *connection, DBusMessage *message, void *data) {
7✔
498
        if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) {
7✔
499
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
500
        }
501

502
        DBusClient *client = static_cast<DBusClient *>(data);
7✔
503

504
        // we use the match rule as a unique string for the given signal
505
        const string spec =
506
                GetSignalMatchRule(dbus_message_get_interface(message), dbus_message_get_member(message));
28✔
507

508
        const string signature {dbus_message_get_signature(message)};
14✔
509

510
        auto opt_string_handler = client->GetSignalHandler<expected::ExpectedString>(spec);
14✔
511
        auto opt_string_pair_handler = client->GetSignalHandler<ExpectedStringPair>(spec);
14✔
512

513
        // either no match or only one match
514
        assert(
7✔
515
                !(static_cast<bool>(opt_string_handler) || static_cast<bool>(opt_string_pair_handler))
516
                || (static_cast<bool>(opt_string_handler) ^ static_cast<bool>(opt_string_pair_handler)));
517

518
        if (opt_string_handler) {
7✔
519
                if (!CheckDBusMessageSignature<expected::ExpectedString>(signature)) {
2✔
520
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
521
                        (*opt_string_handler)(expected::unexpected(err));
×
522
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
523
                }
524

525
                auto ex_value = ExtractValueFromDBusMessage<expected::ExpectedString>(message);
2✔
526
                (*opt_string_handler)(ex_value);
2✔
527
                return DBUS_HANDLER_RESULT_HANDLED;
2✔
528
        } else if (opt_string_pair_handler) {
5✔
529
                if (!CheckDBusMessageSignature<ExpectedStringPair>(signature)) {
×
530
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
531
                        (*opt_string_pair_handler)(expected::unexpected(err));
×
532
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
533
                }
534

535
                auto ex_value = ExtractValueFromDBusMessage<ExpectedStringPair>(message);
×
536
                (*opt_string_pair_handler)(ex_value);
×
537
                return DBUS_HANDLER_RESULT_HANDLED;
×
538
        } else {
539
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
5✔
540
        }
541
}
542

543
static inline string GetMethodSpec(
4✔
544
        const string &service, const string &interface, const string &method) {
545
        return service + "::" + interface + "." + method;
8✔
546
}
547

548
template <>
549
void DBusObject::AddMethodHandler(
2✔
550
        const string &service,
551
        const string &interface,
552
        const string &method,
553
        DBusMethodHandler<expected::ExpectedString> handler) {
554
        string spec = GetMethodSpec(service, interface, method);
4✔
555
        method_handlers_string_[spec] = handler;
2✔
556
}
2✔
557

558
template <>
559
void DBusObject::AddMethodHandler(
×
560
        const string &service,
561
        const string &interface,
562
        const string &method,
563
        DBusMethodHandler<ExpectedStringPair> handler) {
564
        string spec = GetMethodSpec(service, interface, method);
×
565
        method_handlers_string_pair_[spec] = handler;
×
566
}
×
567

568
template <>
569
optional::optional<DBusMethodHandler<expected::ExpectedString>> DBusObject::GetMethodHandler(
2✔
570
        const MethodSpec &spec) {
571
        if (method_handlers_string_.find(spec) != method_handlers_string_.cend()) {
2✔
572
                return method_handlers_string_[spec];
2✔
573
        } else {
574
                return optional::nullopt;
×
575
        }
576
}
577

578
template <>
579
optional::optional<DBusMethodHandler<ExpectedStringPair>> DBusObject::GetMethodHandler(
2✔
580
        const MethodSpec &spec) {
581
        if (method_handlers_string_pair_.find(spec) != method_handlers_string_pair_.cend()) {
2✔
582
                return method_handlers_string_pair_[spec];
×
583
        } else {
584
                return optional::nullopt;
2✔
585
        }
586
}
587

588
error::Error DBusServer::InitializeConnection() {
3✔
589
        auto err = DBusPeer::InitializeConnection();
6✔
590
        if (err != error::NoError) {
3✔
591
                return err;
×
592
        }
593

594
        DBusError dbus_error;
595
        dbus_error_init(&dbus_error);
3✔
596

597
        // We could also do DBUS_NAME_FLAG_ALLOW_REPLACEMENT for cases where two of
598
        // processes request the same name, but it would require handling of the
599
        // NameLost signal and triggering termination.
600
        if (dbus_bus_request_name(
3✔
601
                        dbus_conn_.get(), service_name_.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error)
602
                == -1) {
3✔
603
                dbus_conn_.reset();
×
604
                auto err = MakeError(
605
                        ConnectionError,
606
                        (string("Failed to register DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
607
                         + "]"));
×
608
                dbus_error_free(&dbus_error);
×
609
                return err;
×
610
        }
611

612
        return error::NoError;
3✔
613
}
614

615
DBusServer::~DBusServer() {
3✔
616
        if (!dbus_conn_) {
3✔
617
                // nothing to do without a DBus connection
618
                return;
×
619
        }
620

621
        for (auto obj : objects_) {
6✔
622
                if (!dbus_connection_unregister_object_path(dbus_conn_.get(), obj->GetPath().c_str())) {
3✔
623
                        log::Warning("Failed to unregister DBus object " + obj->GetPath());
×
624
                }
625
        }
626

627
        DBusError dbus_error;
628
        dbus_error_init(&dbus_error);
3✔
629
        if (dbus_bus_release_name(dbus_conn_.get(), service_name_.c_str(), &dbus_error) == -1) {
3✔
630
                log::Warning(
×
631
                        string("Failed to release DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
632
                        + "]");
×
633
                dbus_error_free(&dbus_error);
×
634
        }
635
}
3✔
636

637
template <typename ReturnType>
638
bool AddReturnDataToDBusMessage(DBusMessage *message, ReturnType data);
639

640
template <>
641
bool AddReturnDataToDBusMessage(DBusMessage *message, string data) {
2✔
642
        const char *data_cstr = data.c_str();
2✔
643
        return static_cast<bool>(
644
                dbus_message_append_args(message, DBUS_TYPE_STRING, &data_cstr, DBUS_TYPE_INVALID));
2✔
645
}
646

647
template <>
648
bool AddReturnDataToDBusMessage(DBusMessage *message, StringPair data) {
×
649
        const char *data_cstr1 = data.first.c_str();
×
650
        const char *data_cstr2 = data.second.c_str();
×
651
        return static_cast<bool>(dbus_message_append_args(
×
652
                message, DBUS_TYPE_STRING, &data_cstr1, DBUS_TYPE_STRING, &data_cstr2, DBUS_TYPE_INVALID));
×
653
}
654

655
DBusHandlerResult HandleMethodCall(DBusConnection *connection, DBusMessage *message, void *data) {
2✔
656
        DBusObject *obj = static_cast<DBusObject *>(data);
2✔
657

658
        string spec = GetMethodSpec(
659
                dbus_message_get_destination(message),
660
                dbus_message_get_interface(message),
661
                dbus_message_get_member(message));
10✔
662

663
        auto opt_string_handler = obj->GetMethodHandler<expected::ExpectedString>(spec);
4✔
664
        auto opt_string_pair_handler = obj->GetMethodHandler<ExpectedStringPair>(spec);
4✔
665

666
        if (!opt_string_handler && !opt_string_pair_handler) {
2✔
667
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
668
        }
669

670
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> reply_msg {nullptr, dbus_message_unref};
4✔
671

672
        if (opt_string_handler) {
2✔
673
                expected::ExpectedString ex_return_data = (*opt_string_handler)();
2✔
674
                if (!ex_return_data) {
2✔
675
                        auto &err = ex_return_data.error();
1✔
676
                        reply_msg.reset(
1✔
677
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
2✔
678
                        if (!reply_msg) {
1✔
679
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
680
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
681
                        }
682
                } else {
683
                        reply_msg.reset(dbus_message_new_method_return(message));
1✔
684
                        if (!reply_msg) {
1✔
685
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
686
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
687
                        }
688
                        if (!AddReturnDataToDBusMessage<string>(reply_msg.get(), ex_return_data.value())) {
1✔
689
                                log::Error(
×
690
                                        "Failed to add return value to reply DBus message when handling method "
691
                                        + spec);
×
692
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
693
                        }
694
                }
695
        } else if (opt_string_pair_handler) {
×
696
                ExpectedStringPair ex_return_data = (*opt_string_pair_handler)();
×
697
                if (!ex_return_data) {
×
698
                        auto &err = ex_return_data.error();
×
699
                        reply_msg.reset(
×
700
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
×
701
                        if (!reply_msg) {
×
702
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
703
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
704
                        }
705
                } else {
706
                        reply_msg.reset(dbus_message_new_method_return(message));
×
707
                        if (!reply_msg) {
×
708
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
709
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
710
                        }
711
                        if (!AddReturnDataToDBusMessage<StringPair>(reply_msg.get(), ex_return_data.value())) {
×
712
                                log::Error(
×
713
                                        "Failed to add return value to reply DBus message when handling method "
714
                                        + spec);
×
715
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
716
                        }
717
                }
718
        }
719

720
        if (!dbus_connection_send(connection, reply_msg.get(), NULL)) {
2✔
721
                // can only happen in case of no memory
722
                log::Error("Failed to send reply DBus message when handling method " + spec);
×
723
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
724
        }
725

726
        return DBUS_HANDLER_RESULT_HANDLED;
2✔
727
}
728

729
static DBusObjectPathVTable dbus_vtable = {.message_function = HandleMethodCall};
730

731
error::Error DBusServer::AdvertiseObject(DBusObjectPtr obj) {
3✔
732
        if (!dbus_conn_) {
3✔
733
                auto err = InitializeConnection();
3✔
734
                if (err != error::NoError) {
3✔
735
                        return err;
×
736
                }
737
        }
738

739
        const string &obj_path {obj->GetPath()};
3✔
740
        DBusError dbus_error;
741
        dbus_error_init(&dbus_error);
3✔
742

743
        if (!dbus_connection_try_register_object_path(
3✔
744
                        dbus_conn_.get(), obj_path.c_str(), &dbus_vtable, obj.get(), &dbus_error)) {
3✔
745
                auto err = MakeError(
746
                        ConnectionError,
747
                        (string("Failed to register object ") + obj_path + ": " + dbus_error.message + " ["
×
748
                         + dbus_error.name + "]"));
×
749
                dbus_error_free(&dbus_error);
×
750
                return err;
×
751
        }
752

753
        objects_.push_back(obj);
3✔
754
        return error::NoError;
3✔
755
}
756

757
template <typename SignalValueType>
758
error::Error DBusServer::EmitSignal(
1✔
759
        const string &path, const string &iface, const string &signal, SignalValueType value) {
760
        if (!dbus_conn_) {
1✔
761
                auto err = InitializeConnection();
×
762
                if (err != error::NoError) {
×
763
                        return err;
×
764
                }
765
        }
766

767
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> signal_msg {
2✔
768
                dbus_message_new_signal(path.c_str(), iface.c_str(), signal.c_str()), dbus_message_unref};
1✔
769
        if (!signal_msg) {
1✔
770
                return MakeError(MessageError, "Failed to create signal message");
×
771
        }
772

773
        if (!AddReturnDataToDBusMessage<SignalValueType>(signal_msg.get(), value)) {
1✔
774
                return MakeError(MessageError, "Failed to add data to the signal message");
×
775
        }
776

777
        if (!dbus_connection_send(dbus_conn_.get(), signal_msg.get(), NULL)) {
1✔
778
                // can only happen in case of no memory
779
                return MakeError(ConnectionError, "Failed to send signal message");
×
780
        }
781

782
        return error::NoError;
1✔
783
}
784

785
template error::Error DBusServer::EmitSignal(
786
        const string &path, const string &iface, const string &signal, string value);
787

788
template error::Error DBusServer::EmitSignal(
789
        const string &path, const string &iface, const string &signal, StringPair value);
790

791
} // namespace dbus
792
} // namespace common
793
} // 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