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

mendersoftware / mender / 1022567176

02 Oct 2023 07:50AM UTC coverage: 80.127% (+2.5%) from 77.645%
1022567176

push

gitlab-ci

kacf
chore: Centralize selection of `std::filesystem` library.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

6447 of 8046 relevant lines covered (80.13%)

9912.21 hits per line

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

69.52
/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

43
using namespace std;
44

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

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

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

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

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

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

94
        dbus_connection_set_dispatch_status_function(dbus_conn_.get(), HandleDispatch, this, NULL);
15✔
95

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

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

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

110
        return error::NoError;
9✔
111
}
112

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

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

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

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

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

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

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

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

170
template error::Error DBusClient::CallMethod(
171
        const string &destination,
172
        const string &path,
173
        const string &iface,
174
        const string &method,
175
        DBusCallReplyHandler<dbus::ExpectedStringPair> handler);
176

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

372
        return TRUE;
25✔
373
}
374

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

537
        DBusClient *client = static_cast<DBusClient *>(data);
538

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

543
        const string signature {dbus_message_get_signature(message)};
11✔
544

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

814
        return DBUS_HANDLER_RESULT_HANDLED;
815
}
816

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

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

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

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

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

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

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

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

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

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

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

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

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