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

mendersoftware / mender / 1022753986

02 Oct 2023 10:37AM UTC coverage: 78.168% (-2.0%) from 80.127%
1022753986

push

gitlab-ci

oleorhagen
feat: Run the authentication loop once upon bootstrap

Ticket: MEN-6658
Changelog: None

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

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

6996 of 8950 relevant lines covered (78.17%)

10353.4 hits per line

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

69.27
/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() {
15✔
71
        DBusError dbus_error;
72
        dbus_error_init(&dbus_error);
15✔
73
        dbus_conn_.reset(dbus_bus_get_private(DBUS_BUS_SYSTEM, &dbus_error));
15✔
74
        if (!dbus_conn_) {
15✔
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);
15✔
84
        if (!dbus_connection_set_watch_functions(
15✔
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(
15✔
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);
15✔
96

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

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

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

111
        return error::NoError;
9✔
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 error::Error DBusClient::CallMethod(
172
        const string &destination,
173
        const string &path,
174
        const string &iface,
175
        const string &method,
176
        DBusCallReplyHandler<dbus::ExpectedStringPair> handler);
177

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

185
template <>
186
void DBusClient::AddSignalHandler(
3✔
187
        const SignalSpec &spec, DBusSignalHandler<expected::ExpectedString> handler) {
188
        signal_handlers_string_[spec] = handler;
3✔
189
}
3✔
190

191
template <>
192
void DBusClient::AddSignalHandler(
2✔
193
        const SignalSpec &spec, DBusSignalHandler<ExpectedStringPair> handler) {
194
        signal_handlers_string_pair_[spec] = handler;
2✔
195
}
2✔
196

197
static inline string GetSignalMatchRule(const string &iface, const string &signal) {
17✔
198
        return string("type='signal'") + ",interface='" + iface + "',member='" + signal + "'";
51✔
199
}
200

201
template <typename SignalValueType>
202
error::Error DBusClient::RegisterSignalHandler(
3✔
203
        const string &iface, const string &signal, DBusSignalHandler<SignalValueType> handler) {
204
        if (!dbus_conn_) {
3✔
205
                auto err = InitializeConnection();
3✔
206
                if (err != error::NoError) {
3✔
207
                        return err;
×
208
                }
209
        }
210

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

217
        DBusError dbus_error;
218
        dbus_error_init(&dbus_error);
3✔
219
        dbus_bus_add_match(dbus_conn_.get(), match_rule.c_str(), &dbus_error);
3✔
220
        if (dbus_error_is_set(&dbus_error)) {
3✔
221
                auto err = MakeError(
×
222
                        ConnectionError, string("Failed to register signal reception: ") + dbus_error.message);
×
223
                dbus_error_free(&dbus_error);
×
224
                return err;
×
225
        }
226
        AddSignalHandler<SignalValueType>(match_rule, handler);
3✔
227
        return error::NoError;
3✔
228
}
229

230
template error::Error DBusClient::RegisterSignalHandler(
231
        const string &iface, const string &signal, DBusSignalHandler<expected::ExpectedString> handler);
232

233
template error::Error DBusClient::RegisterSignalHandler(
234
        const string &iface, const string &signal, DBusSignalHandler<ExpectedStringPair> handler);
235

236
void DBusClient::UnregisterSignalHandler(const string &iface, const string &signal) {
1✔
237
        // we use the match rule as a unique string for the given signal
238
        const string spec = GetSignalMatchRule(iface, signal);
2✔
239

240
        // should be in at most one set, but erase() is a noop if not found
241
        signal_handlers_string_.erase(spec);
1✔
242
        signal_handlers_string_pair_.erase(spec);
1✔
243
}
1✔
244

245
void HandleDispatch(DBusConnection *conn, DBusDispatchStatus status, void *data) {
49✔
246
        DBusClient *client = static_cast<DBusClient *>(data);
49✔
247
        if (status == DBUS_DISPATCH_DATA_REMAINS) {
49✔
248
                // This must give other things in the loop a chance to run because
249
                // dbus_connection_dispatch() below can cause this to be called again.
250
                client->loop_.Post([conn]() {
33✔
251
                        while (dbus_connection_get_dispatch_status(conn) == DBUS_DISPATCH_DATA_REMAINS) {
51✔
252
                                dbus_connection_dispatch(conn);
34✔
253
                        }
254
                });
66✔
255
        }
256
}
49✔
257

258
dbus_bool_t AddDBusWatch(DBusWatch *w, void *data) {
30✔
259
        // libdbus adds watches in two steps -- using AddDBusWatch() with a disabled
260
        // watch which should allocate all the necessary data (and can fail)
261
        // followed by ToggleDBusWatch() to enable the watch (see below). We
262
        // simplify things for ourselves by ignoring disabled watches and only
263
        // actually adding them when ToggleDBusWatch() is called.
264
        if (!dbus_watch_get_enabled(w)) {
30✔
265
                return TRUE;
15✔
266
        }
267

268
        DBusClient *client = static_cast<DBusClient *>(data);
15✔
269
        unique_ptr<asio::posix::stream_descriptor> sd {
270
                new asio::posix::stream_descriptor(DBusClient::GetAsioIoContext(client->loop_))};
30✔
271
        boost::system::error_code ec;
15✔
272
        sd->assign(dbus_watch_get_unix_fd(w), ec);
15✔
273
        if (ec) {
15✔
274
                log::Error("Failed to assign DBus FD to ASIO stream descriptor");
×
275
                return FALSE;
×
276
        }
277

278
        class RepeatedWaitFunctor {
279
        public:
280
                RepeatedWaitFunctor(
30✔
281
                        asio::posix::stream_descriptor *sd,
282
                        asio::posix::stream_descriptor::wait_type type,
283
                        DBusWatch *watch,
284
                        DBusClient *client,
285
                        unsigned int flags) :
30✔
286
                        sd_ {sd},
287
                        type_ {type},
288
                        watch_ {watch},
289
                        client_ {client},
290
                        flags_ {flags} {
30✔
291
                }
30✔
292

293
                void operator()(boost::system::error_code ec) {
16✔
294
                        if (ec == boost::asio::error::operation_aborted) {
16✔
295
                                return;
×
296
                        }
297
                        if (!dbus_watch_handle(watch_, flags_)) {
16✔
298
                                log::Error("Failed to handle watch");
×
299
                        }
300
                        HandleDispatch(client_->dbus_conn_.get(), DBUS_DISPATCH_DATA_REMAINS, client_);
16✔
301
                        sd_->async_wait(type_, *this);
16✔
302
                }
303

304
        private:
305
                asio::posix::stream_descriptor *sd_;
306
                asio::posix::stream_descriptor::wait_type type_;
307
                DBusWatch *watch_;
308
                DBusClient *client_;
309
                unsigned int flags_;
310
        };
311

312
        unsigned int flags {dbus_watch_get_flags(w)};
15✔
313
        if (flags & DBUS_WATCH_READABLE) {
15✔
314
                RepeatedWaitFunctor read_ftor {
315
                        sd.get(), asio::posix::stream_descriptor::wait_read, w, client, flags};
15✔
316
                sd->async_wait(asio::posix::stream_descriptor::wait_read, read_ftor);
15✔
317
        }
318
        if (flags & DBUS_WATCH_WRITABLE) {
15✔
319
                RepeatedWaitFunctor write_ftor {
320
                        sd.get(), asio::posix::stream_descriptor::wait_write, w, client, flags};
×
321
                sd->async_wait(asio::posix::stream_descriptor::wait_write, write_ftor);
×
322
        }
323
        // Always watch for errors.
324
        RepeatedWaitFunctor error_ftor {
325
                sd.get(), asio::posix::stream_descriptor::wait_error, w, client, DBUS_WATCH_ERROR};
15✔
326
        sd->async_wait(asio::posix::stream_descriptor::wait_error, error_ftor);
15✔
327

328
        // Assign the stream_descriptor so that we have access to it in
329
        // RemoveDBusWatch() and we can delete it.
330
        dbus_watch_set_data(w, sd.release(), NULL);
15✔
331
        return TRUE;
15✔
332
}
333

334
static void RemoveDBusWatch(DBusWatch *w, void *data) {
30✔
335
        asio::posix::stream_descriptor *sd =
336
                static_cast<asio::posix::stream_descriptor *>(dbus_watch_get_data(w));
30✔
337
        dbus_watch_set_data(w, NULL, NULL);
30✔
338
        if (sd != nullptr) {
30✔
339
                sd->cancel();
15✔
340
                delete sd;
15✔
341
        }
342
}
30✔
343

344
static void ToggleDBusWatch(DBusWatch *w, void *data) {
×
345
        if (dbus_watch_get_enabled(w)) {
×
346
                AddDBusWatch(w, data);
×
347
        } else {
348
                RemoveDBusWatch(w, data);
×
349
        }
350
}
×
351

352
dbus_bool_t AddDBusTimeout(DBusTimeout *t, void *data) {
25✔
353
        // See AddDBusWatch() for the details about this trick.
354
        if (!dbus_timeout_get_enabled(t)) {
25✔
355
                return TRUE;
×
356
        }
357

358
        DBusClient *client = static_cast<DBusClient *>(data);
25✔
359
        asio::steady_timer *timer =
360
                new asio::steady_timer {DBusClient::GetAsioIoContext(client->loop_)};
25✔
361
        timer->expires_after(chrono::milliseconds {dbus_timeout_get_interval(t)});
25✔
362
        timer->async_wait([t](boost::system::error_code ec) {
25✔
363
                if (ec == boost::asio::error::operation_aborted) {
19✔
364
                        return;
19✔
365
                }
366
                if (!dbus_timeout_handle(t)) {
×
367
                        log::Error("Failed to handle timeout");
×
368
                }
369
        });
370

371
        dbus_timeout_set_data(t, timer, NULL);
25✔
372

373
        return TRUE;
25✔
374
}
375

376
static void RemoveDBusTimeout(DBusTimeout *t, void *data) {
25✔
377
        asio::steady_timer *timer = static_cast<asio::steady_timer *>(dbus_timeout_get_data(t));
25✔
378
        dbus_timeout_set_data(t, NULL, NULL);
25✔
379
        if (timer != nullptr) {
25✔
380
                timer->cancel();
25✔
381
                delete timer;
25✔
382
        }
383
}
25✔
384

385
static void ToggleDBusTimeout(DBusTimeout *t, void *data) {
×
386
        if (dbus_timeout_get_enabled(t)) {
×
387
                AddDBusTimeout(t, data);
×
388
        } else {
389
                RemoveDBusTimeout(t, data);
×
390
        }
391
}
×
392

393
template <typename ReplyType>
394
bool CheckDBusMessageSignature(const string &signature);
395

396
template <>
397
bool CheckDBusMessageSignature<expected::ExpectedString>(const string &signature) {
6✔
398
        return signature == DBUS_TYPE_STRING_AS_STRING;
6✔
399
}
400

401
template <>
402
bool CheckDBusMessageSignature<ExpectedStringPair>(const string &signature) {
2✔
403
        return signature == (string(DBUS_TYPE_STRING_AS_STRING) + DBUS_TYPE_STRING_AS_STRING);
4✔
404
}
405

406
template <>
407
bool CheckDBusMessageSignature<expected::ExpectedBool>(const string &signature) {
2✔
408
        return signature == DBUS_TYPE_BOOLEAN_AS_STRING;
2✔
409
}
410

411
template <typename ReplyType>
412
ReplyType ExtractValueFromDBusMessage(DBusMessage *message);
413

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

431
template <>
432
ExpectedStringPair ExtractValueFromDBusMessage(DBusMessage *message) {
2✔
433
        DBusError dbus_error;
434
        dbus_error_init(&dbus_error);
2✔
435
        const char *value1;
436
        const char *value2;
437
        if (!dbus_message_get_args(
2✔
438
                        message,
439
                        &dbus_error,
440
                        DBUS_TYPE_STRING,
441
                        &value1,
442
                        DBUS_TYPE_STRING,
443
                        &value2,
444
                        DBUS_TYPE_INVALID)) {
445
                auto err = MakeError(
446
                        ValueError,
447
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
448
                                + dbus_error.name + "]");
×
449
                dbus_error_free(&dbus_error);
×
450
                return expected::unexpected(err);
×
451
        }
452
        return StringPair {string(value1), string(value2)};
4✔
453
}
454

455
template <>
456
expected::ExpectedBool ExtractValueFromDBusMessage(DBusMessage *message) {
2✔
457
        DBusError dbus_error;
458
        dbus_error_init(&dbus_error);
2✔
459
        bool result;
460
        if (!dbus_message_get_args(
2✔
461
                        message, &dbus_error, DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
462
                auto err = MakeError(
463
                        ValueError,
464
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
465
                                + dbus_error.name + "]");
×
466
                dbus_error_free(&dbus_error);
×
467
                return expected::unexpected(err);
×
468
        }
469
        return result;
2✔
470
}
471

472
template <typename ReplyType>
473
void HandleReply(DBusPendingCall *pending, void *data) {
5✔
474
        auto *handler = static_cast<DBusCallReplyHandler<ReplyType> *>(data);
5✔
475

476
        // for easier resource control
477
        unique_ptr<DBusPendingCall, decltype(&dbus_pending_call_unref)> pending_ptr {
×
478
                pending, dbus_pending_call_unref};
5✔
479
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> reply_ptr {
5✔
480
                dbus_pending_call_steal_reply(pending), dbus_message_unref};
5✔
481

482
        if (dbus_message_get_type(reply_ptr.get()) == DBUS_MESSAGE_TYPE_ERROR) {
5✔
483
                DBusError dbus_error;
484
                dbus_error_init(&dbus_error);
1✔
485
                const char *error;
486
                if (!dbus_message_get_args(
1✔
487
                                reply_ptr.get(), &dbus_error, DBUS_TYPE_STRING, &error, DBUS_TYPE_INVALID)) {
488
                        auto err = MakeError(
×
489
                                ValueError,
490
                                string("Got error reply, but failed to extrac the error from it: ")
×
491
                                        + dbus_error.message + "[" + dbus_error.name + "]");
×
492
                        dbus_error_free(&dbus_error);
×
493
                        (*handler)(expected::unexpected(err));
×
494
                } else {
495
                        const string error_str {error};
2✔
496
                        auto err = MakeError(ReplyError, "Got error reply: " + error_str);
1✔
497
                        (*handler)(expected::unexpected(err));
1✔
498
                }
499
                return;
1✔
500
        }
501

502
        const string signature {dbus_message_get_signature(reply_ptr.get())};
4✔
503
        if (!CheckDBusMessageSignature<ReplyType>(signature)) {
4✔
504
                auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
505
                (*handler)(expected::unexpected(err));
×
506
                return;
×
507
        }
508

509
        auto ex_reply = ExtractValueFromDBusMessage<ReplyType>(reply_ptr.get());
4✔
510
        (*handler)(ex_reply);
4✔
511
}
512

513
template <>
514
optional::optional<DBusSignalHandler<expected::ExpectedString>> DBusClient::GetSignalHandler(
11✔
515
        const SignalSpec &spec) {
516
        if (signal_handlers_string_.find(spec) != signal_handlers_string_.cend()) {
11✔
517
                return signal_handlers_string_[spec];
2✔
518
        } else {
519
                return optional::nullopt;
9✔
520
        }
521
}
522

523
template <>
524
optional::optional<DBusSignalHandler<ExpectedStringPair>> DBusClient::GetSignalHandler(
11✔
525
        const SignalSpec &spec) {
526
        if (signal_handlers_string_pair_.find(spec) != signal_handlers_string_pair_.cend()) {
11✔
527
                return signal_handlers_string_pair_[spec];
1✔
528
        } else {
529
                return optional::nullopt;
10✔
530
        }
531
}
532

533
DBusHandlerResult MsgFilter(DBusConnection *connection, DBusMessage *message, void *data) {
11✔
534
        if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) {
11✔
535
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
536
        }
537

538
        DBusClient *client = static_cast<DBusClient *>(data);
11✔
539

540
        // we use the match rule as a unique string for the given signal
541
        const string spec =
542
                GetSignalMatchRule(dbus_message_get_interface(message), dbus_message_get_member(message));
44✔
543

544
        const string signature {dbus_message_get_signature(message)};
22✔
545

546
        auto opt_string_handler = client->GetSignalHandler<expected::ExpectedString>(spec);
22✔
547
        auto opt_string_pair_handler = client->GetSignalHandler<ExpectedStringPair>(spec);
22✔
548

549
        // either no match or only one match
550
        assert(
11✔
551
                !(static_cast<bool>(opt_string_handler) || static_cast<bool>(opt_string_pair_handler))
552
                || (static_cast<bool>(opt_string_handler) ^ static_cast<bool>(opt_string_pair_handler)));
553

554
        if (opt_string_handler) {
11✔
555
                if (!CheckDBusMessageSignature<expected::ExpectedString>(signature)) {
2✔
556
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
557
                        (*opt_string_handler)(expected::unexpected(err));
×
558
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
559
                }
560

561
                auto ex_value = ExtractValueFromDBusMessage<expected::ExpectedString>(message);
2✔
562
                (*opt_string_handler)(ex_value);
2✔
563
                return DBUS_HANDLER_RESULT_HANDLED;
2✔
564
        } else if (opt_string_pair_handler) {
9✔
565
                if (!CheckDBusMessageSignature<ExpectedStringPair>(signature)) {
1✔
566
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
567
                        (*opt_string_pair_handler)(expected::unexpected(err));
×
568
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
569
                }
570

571
                auto ex_value = ExtractValueFromDBusMessage<ExpectedStringPair>(message);
1✔
572
                (*opt_string_pair_handler)(ex_value);
1✔
573
                return DBUS_HANDLER_RESULT_HANDLED;
1✔
574
        } else {
575
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
8✔
576
        }
577
}
578

579
static inline string GetMethodSpec(
12✔
580
        const string &service, const string &interface, const string &method) {
581
        return service + "::" + interface + "." + method;
24✔
582
}
583

584
template <>
585
void DBusObject::AddMethodHandler(
2✔
586
        const string &service,
587
        const string &interface,
588
        const string &method,
589
        DBusMethodHandler<expected::ExpectedString> handler) {
590
        string spec = GetMethodSpec(service, interface, method);
4✔
591
        method_handlers_string_[spec] = handler;
2✔
592
}
2✔
593

594
template <>
595
void DBusObject::AddMethodHandler(
2✔
596
        const string &service,
597
        const string &interface,
598
        const string &method,
599
        DBusMethodHandler<ExpectedStringPair> handler) {
600
        string spec = GetMethodSpec(service, interface, method);
4✔
601
        method_handlers_string_pair_[spec] = handler;
2✔
602
}
2✔
603

604
template <>
605
void DBusObject::AddMethodHandler(
3✔
606
        const string &service,
607
        const string &interface,
608
        const string &method,
609
        DBusMethodHandler<expected::ExpectedBool> handler) {
610
        string spec = GetMethodSpec(service, interface, method);
6✔
611
        method_handlers_bool_[spec] = handler;
3✔
612
}
3✔
613

614
template <>
615
optional::optional<DBusMethodHandler<expected::ExpectedString>> DBusObject::GetMethodHandler(
5✔
616
        const MethodSpec &spec) {
617
        if (method_handlers_string_.find(spec) != method_handlers_string_.cend()) {
5✔
618
                return method_handlers_string_[spec];
2✔
619
        } else {
620
                return optional::nullopt;
3✔
621
        }
622
}
623

624
template <>
625
optional::optional<DBusMethodHandler<ExpectedStringPair>> DBusObject::GetMethodHandler(
5✔
626
        const MethodSpec &spec) {
627
        if (method_handlers_string_pair_.find(spec) != method_handlers_string_pair_.cend()) {
5✔
628
                return method_handlers_string_pair_[spec];
1✔
629
        } else {
630
                return optional::nullopt;
4✔
631
        }
632
}
633

634
template <>
635
optional::optional<DBusMethodHandler<expected::ExpectedBool>> DBusObject::GetMethodHandler(
5✔
636
        const MethodSpec &spec) {
637
        if (method_handlers_bool_.find(spec) != method_handlers_bool_.cend()) {
5✔
638
                return method_handlers_bool_[spec];
2✔
639
        } else {
640
                return optional::nullopt;
3✔
641
        }
642
}
643

644
error::Error DBusServer::InitializeConnection() {
6✔
645
        auto err = DBusPeer::InitializeConnection();
12✔
646
        if (err != error::NoError) {
6✔
647
                return err;
×
648
        }
649

650
        DBusError dbus_error;
651
        dbus_error_init(&dbus_error);
6✔
652

653
        // We could also do DBUS_NAME_FLAG_ALLOW_REPLACEMENT for cases where two of
654
        // processes request the same name, but it would require handling of the
655
        // NameLost signal and triggering termination.
656
        if (dbus_bus_request_name(
6✔
657
                        dbus_conn_.get(), service_name_.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error)
658
                == -1) {
6✔
659
                dbus_conn_.reset();
×
660
                auto err = MakeError(
661
                        ConnectionError,
662
                        (string("Failed to register DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
663
                         + "]"));
×
664
                dbus_error_free(&dbus_error);
×
665
                return err;
×
666
        }
667

668
        return error::NoError;
6✔
669
}
670

671
DBusServer::~DBusServer() {
6✔
672
        if (!dbus_conn_) {
6✔
673
                // nothing to do without a DBus connection
674
                return;
×
675
        }
676

677
        for (auto obj : objects_) {
12✔
678
                if (!dbus_connection_unregister_object_path(dbus_conn_.get(), obj->GetPath().c_str())) {
6✔
679
                        log::Warning("Failed to unregister DBus object " + obj->GetPath());
×
680
                }
681
        }
682

683
        DBusError dbus_error;
684
        dbus_error_init(&dbus_error);
6✔
685
        if (dbus_bus_release_name(dbus_conn_.get(), service_name_.c_str(), &dbus_error) == -1) {
6✔
686
                log::Warning(
×
687
                        string("Failed to release DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
688
                        + "]");
×
689
                dbus_error_free(&dbus_error);
×
690
        }
691
}
6✔
692

693
template <typename ReturnType>
694
bool AddReturnDataToDBusMessage(DBusMessage *message, ReturnType data);
695

696
template <>
697
bool AddReturnDataToDBusMessage(DBusMessage *message, string data) {
2✔
698
        const char *data_cstr = data.c_str();
2✔
699
        return static_cast<bool>(
700
                dbus_message_append_args(message, DBUS_TYPE_STRING, &data_cstr, DBUS_TYPE_INVALID));
2✔
701
}
702

703
template <>
704
bool AddReturnDataToDBusMessage(DBusMessage *message, StringPair data) {
2✔
705
        const char *data_cstr1 = data.first.c_str();
2✔
706
        const char *data_cstr2 = data.second.c_str();
2✔
707
        return static_cast<bool>(dbus_message_append_args(
2✔
708
                message, DBUS_TYPE_STRING, &data_cstr1, DBUS_TYPE_STRING, &data_cstr2, DBUS_TYPE_INVALID));
2✔
709
}
710

711
template <>
712
bool AddReturnDataToDBusMessage(DBusMessage *message, bool data) {
2✔
713
        // (with clang) bool may be neither 0 nor 1 and libdbus has an assertion
714
        // requiring one of these two integer values.
715
        dbus_bool_t value = static_cast<dbus_bool_t>(data);
2✔
716
        return static_cast<bool>(
717
                dbus_message_append_args(message, DBUS_TYPE_BOOLEAN, &value, DBUS_TYPE_INVALID));
2✔
718
}
719

720
DBusHandlerResult HandleMethodCall(DBusConnection *connection, DBusMessage *message, void *data) {
5✔
721
        DBusObject *obj = static_cast<DBusObject *>(data);
5✔
722

723
        string spec = GetMethodSpec(
724
                dbus_message_get_destination(message),
725
                dbus_message_get_interface(message),
726
                dbus_message_get_member(message));
25✔
727

728
        auto opt_string_handler = obj->GetMethodHandler<expected::ExpectedString>(spec);
10✔
729
        auto opt_string_pair_handler = obj->GetMethodHandler<ExpectedStringPair>(spec);
10✔
730
        auto opt_bool_handler = obj->GetMethodHandler<expected::ExpectedBool>(spec);
10✔
731

732
        if (!opt_string_handler && !opt_string_pair_handler && !opt_bool_handler) {
5✔
733
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
734
        }
735

736
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> reply_msg {nullptr, dbus_message_unref};
10✔
737

738
        if (opt_string_handler) {
5✔
739
                expected::ExpectedString ex_return_data = (*opt_string_handler)();
2✔
740
                if (!ex_return_data) {
2✔
741
                        auto &err = ex_return_data.error();
1✔
742
                        reply_msg.reset(
1✔
743
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
2✔
744
                        if (!reply_msg) {
1✔
745
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
746
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
747
                        }
748
                } else {
749
                        reply_msg.reset(dbus_message_new_method_return(message));
1✔
750
                        if (!reply_msg) {
1✔
751
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
752
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
753
                        }
754
                        if (!AddReturnDataToDBusMessage<string>(reply_msg.get(), ex_return_data.value())) {
1✔
755
                                log::Error(
×
756
                                        "Failed to add return value to reply DBus message when handling method "
757
                                        + spec);
×
758
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
759
                        }
760
                }
761
        } else if (opt_string_pair_handler) {
3✔
762
                ExpectedStringPair ex_return_data = (*opt_string_pair_handler)();
1✔
763
                if (!ex_return_data) {
1✔
764
                        auto &err = ex_return_data.error();
×
765
                        reply_msg.reset(
×
766
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
×
767
                        if (!reply_msg) {
×
768
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
769
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
770
                        }
771
                } else {
772
                        reply_msg.reset(dbus_message_new_method_return(message));
1✔
773
                        if (!reply_msg) {
1✔
774
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
775
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
776
                        }
777
                        if (!AddReturnDataToDBusMessage<StringPair>(reply_msg.get(), ex_return_data.value())) {
1✔
778
                                log::Error(
×
779
                                        "Failed to add return value to reply DBus message when handling method "
780
                                        + spec);
×
781
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
782
                        }
783
                }
784
        } else if (opt_bool_handler) {
2✔
785
                expected::ExpectedBool ex_return_data = (*opt_bool_handler)();
2✔
786
                if (!ex_return_data) {
2✔
787
                        auto &err = ex_return_data.error();
×
788
                        reply_msg.reset(
×
789
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
×
790
                        if (!reply_msg) {
×
791
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
792
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
793
                        }
794
                } else {
795
                        reply_msg.reset(dbus_message_new_method_return(message));
2✔
796
                        if (!reply_msg) {
2✔
797
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
798
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
799
                        }
800
                        if (!AddReturnDataToDBusMessage<bool>(reply_msg.get(), ex_return_data.value())) {
2✔
801
                                log::Error(
×
802
                                        "Failed to add return value to reply DBus message when handling method "
803
                                        + spec);
×
804
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
805
                        }
806
                }
807
        }
808

809
        if (!dbus_connection_send(connection, reply_msg.get(), NULL)) {
5✔
810
                // can only happen in case of no memory
811
                log::Error("Failed to send reply DBus message when handling method " + spec);
×
812
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
813
        }
814

815
        return DBUS_HANDLER_RESULT_HANDLED;
5✔
816
}
817

818
static DBusObjectPathVTable dbus_vtable = {.message_function = HandleMethodCall};
819

820
error::Error DBusServer::AdvertiseObject(DBusObjectPtr obj) {
6✔
821
        if (!dbus_conn_) {
6✔
822
                auto err = InitializeConnection();
6✔
823
                if (err != error::NoError) {
6✔
824
                        return err;
×
825
                }
826
        }
827

828
        const string &obj_path {obj->GetPath()};
6✔
829
        DBusError dbus_error;
830
        dbus_error_init(&dbus_error);
6✔
831

832
        if (!dbus_connection_try_register_object_path(
6✔
833
                        dbus_conn_.get(), obj_path.c_str(), &dbus_vtable, obj.get(), &dbus_error)) {
6✔
834
                auto err = MakeError(
835
                        ConnectionError,
836
                        (string("Failed to register object ") + obj_path + ": " + dbus_error.message + " ["
×
837
                         + dbus_error.name + "]"));
×
838
                dbus_error_free(&dbus_error);
×
839
                return err;
×
840
        }
841

842
        objects_.push_back(obj);
6✔
843
        return error::NoError;
6✔
844
}
845

846
template <typename SignalValueType>
847
error::Error DBusServer::EmitSignal(
1✔
848
        const string &path, const string &iface, const string &signal, SignalValueType value) {
849
        if (!dbus_conn_) {
1✔
850
                auto err = InitializeConnection();
×
851
                if (err != error::NoError) {
×
852
                        return err;
×
853
                }
854
        }
855

856
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> signal_msg {
2✔
857
                dbus_message_new_signal(path.c_str(), iface.c_str(), signal.c_str()), dbus_message_unref};
1✔
858
        if (!signal_msg) {
1✔
859
                return MakeError(MessageError, "Failed to create signal message");
×
860
        }
861

862
        if (!AddReturnDataToDBusMessage<SignalValueType>(signal_msg.get(), value)) {
1✔
863
                return MakeError(MessageError, "Failed to add data to the signal message");
×
864
        }
865

866
        if (!dbus_connection_send(dbus_conn_.get(), signal_msg.get(), NULL)) {
1✔
867
                // can only happen in case of no memory
868
                return MakeError(ConnectionError, "Failed to send signal message");
×
869
        }
870

871
        return error::NoError;
1✔
872
}
873

874
template error::Error DBusServer::EmitSignal(
875
        const string &path, const string &iface, const string &signal, string value);
876

877
template error::Error DBusServer::EmitSignal(
878
        const string &path, const string &iface, const string &signal, StringPair value);
879

880
} // namespace dbus
881
} // namespace common
882
} // 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