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

mendersoftware / mender / 1019238693

28 Sep 2023 06:41AM UTC coverage: 77.563% (-0.5%) from 78.091%
1019238693

push

gitlab-ci

kacf
chore: Send 501 to client when server tries unsupported protocol switch.

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

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

6696 of 8633 relevant lines covered (77.56%)

10658.93 hits per line

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

60.0
/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
        unsigned int flags {dbus_watch_get_flags(w)};
9✔
265
        if (flags & DBUS_WATCH_READABLE) {
9✔
266
                sd->async_wait(
9✔
267
                        asio::posix::stream_descriptor::wait_read,
268
                        [w, client, flags](boost::system::error_code ec) {
24✔
269
                                if (ec == boost::asio::error::operation_aborted) {
8✔
270
                                        return;
×
271
                                }
272
                                if (!dbus_watch_handle(w, flags)) {
8✔
273
                                        log::Error("Failed to handle readable watch");
×
274
                                }
275
                                HandleDispatch(client->dbus_conn_.get(), DBUS_DISPATCH_DATA_REMAINS, client);
8✔
276
                        });
277
        }
278
        if (flags & DBUS_WATCH_WRITABLE) {
9✔
279
                sd->async_wait(
×
280
                        asio::posix::stream_descriptor::wait_write,
281
                        [w, client, flags](boost::system::error_code ec) {
×
282
                                if (ec == boost::asio::error::operation_aborted) {
×
283
                                        return;
×
284
                                }
285
                                if (!dbus_watch_handle(w, flags)) {
×
286
                                        log::Error("Failed to handle writable watch");
×
287
                                }
288
                                HandleDispatch(client->dbus_conn_.get(), DBUS_DISPATCH_DATA_REMAINS, client);
×
289
                        });
290
        }
291
        // Always watch for errors.
292
        sd->async_wait(asio::posix::stream_descriptor::wait_error, [w](boost::system::error_code ec) {
9✔
293
                if (ec == boost::asio::error::operation_aborted) {
×
294
                        return;
×
295
                }
296
                if (!dbus_watch_handle(w, DBUS_WATCH_ERROR)) {
×
297
                        log::Error("Failed to handle error watch");
×
298
                }
299
        });
300

301
        // Assign the stream_descriptor so that we have access to it in
302
        // RemoveDBusWatch() and we can delete it.
303
        dbus_watch_set_data(w, sd.release(), NULL);
9✔
304
        return TRUE;
9✔
305
}
306

307
static void RemoveDBusWatch(DBusWatch *w, void *data) {
18✔
308
        asio::posix::stream_descriptor *sd =
309
                static_cast<asio::posix::stream_descriptor *>(dbus_watch_get_data(w));
18✔
310
        dbus_watch_set_data(w, NULL, NULL);
18✔
311
        if (sd != nullptr) {
18✔
312
                sd->cancel();
9✔
313
                delete sd;
9✔
314
        }
315
}
18✔
316

317
static void ToggleDBusWatch(DBusWatch *w, void *data) {
×
318
        if (dbus_watch_get_enabled(w)) {
×
319
                AddDBusWatch(w, data);
×
320
        } else {
321
                RemoveDBusWatch(w, data);
×
322
        }
323
}
×
324

325
dbus_bool_t AddDBusTimeout(DBusTimeout *t, void *data) {
15✔
326
        // See AddDBusWatch() for the details about this trick.
327
        if (!dbus_timeout_get_enabled(t)) {
15✔
328
                return TRUE;
×
329
        }
330

331
        DBusClient *client = static_cast<DBusClient *>(data);
15✔
332
        asio::steady_timer *timer =
333
                new asio::steady_timer {DBusClient::GetAsioIoContext(client->loop_)};
15✔
334
        timer->expires_after(chrono::milliseconds {dbus_timeout_get_interval(t)});
15✔
335
        timer->async_wait([t](boost::system::error_code ec) {
15✔
336
                if (ec == boost::asio::error::operation_aborted) {
12✔
337
                        return;
12✔
338
                }
339
                if (!dbus_timeout_handle(t)) {
×
340
                        log::Error("Failed to handle timeout");
×
341
                }
342
        });
343

344
        dbus_timeout_set_data(t, timer, NULL);
15✔
345

346
        return TRUE;
15✔
347
}
348

349
static void RemoveDBusTimeout(DBusTimeout *t, void *data) {
15✔
350
        asio::steady_timer *timer = static_cast<asio::steady_timer *>(dbus_timeout_get_data(t));
15✔
351
        dbus_timeout_set_data(t, NULL, NULL);
15✔
352
        if (timer != nullptr) {
15✔
353
                timer->cancel();
15✔
354
                delete timer;
15✔
355
        }
356
}
15✔
357

358
static void ToggleDBusTimeout(DBusTimeout *t, void *data) {
×
359
        if (dbus_timeout_get_enabled(t)) {
×
360
                AddDBusTimeout(t, data);
×
361
        } else {
362
                RemoveDBusTimeout(t, data);
×
363
        }
364
}
×
365

366
template <typename ReplyType>
367
bool CheckDBusMessageSignature(const string &signature);
368

369
template <>
370
bool CheckDBusMessageSignature<expected::ExpectedString>(const string &signature) {
6✔
371
        return signature == DBUS_TYPE_STRING_AS_STRING;
6✔
372
}
373

374
template <>
375
bool CheckDBusMessageSignature<ExpectedStringPair>(const string &signature) {
×
376
        return signature == (string(DBUS_TYPE_STRING_AS_STRING) + DBUS_TYPE_STRING_AS_STRING);
×
377
}
378

379
template <typename ReplyType>
380
ReplyType ExtractValueFromDBusMessage(DBusMessage *message);
381

382
template <>
383
expected::ExpectedString ExtractValueFromDBusMessage(DBusMessage *message) {
6✔
384
        DBusError dbus_error;
385
        dbus_error_init(&dbus_error);
6✔
386
        const char *result;
387
        if (!dbus_message_get_args(
6✔
388
                        message, &dbus_error, DBUS_TYPE_STRING, &result, DBUS_TYPE_INVALID)) {
389
                auto err = MakeError(
390
                        ValueError,
391
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
392
                                + dbus_error.name + "]");
×
393
                dbus_error_free(&dbus_error);
×
394
                return expected::unexpected(err);
×
395
        }
396
        return string(result);
12✔
397
}
398

399
template <>
400
ExpectedStringPair ExtractValueFromDBusMessage(DBusMessage *message) {
×
401
        DBusError dbus_error;
402
        dbus_error_init(&dbus_error);
×
403
        const char *value1;
404
        const char *value2;
405
        if (!dbus_message_get_args(
×
406
                        message,
407
                        &dbus_error,
408
                        DBUS_TYPE_STRING,
409
                        &value1,
410
                        DBUS_TYPE_STRING,
411
                        &value2,
412
                        DBUS_TYPE_INVALID)) {
413
                auto err = MakeError(
414
                        ValueError,
415
                        string("Failed to extract reply data from reply message: ") + dbus_error.message + " ["
×
416
                                + dbus_error.name + "]");
×
417
                dbus_error_free(&dbus_error);
×
418
                return expected::unexpected(err);
×
419
        }
420
        return std::pair<string, string> {string(value1), string(value1)};
×
421
}
422

423
template <typename ReplyType>
424
void HandleReply(DBusPendingCall *pending, void *data) {
5✔
425
        auto *handler = static_cast<DBusCallReplyHandler<ReplyType> *>(data);
5✔
426

427
        // for easier resource control
428
        unique_ptr<DBusPendingCall, decltype(&dbus_pending_call_unref)> pending_ptr {
×
429
                pending, dbus_pending_call_unref};
5✔
430
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> reply_ptr {
5✔
431
                dbus_pending_call_steal_reply(pending), dbus_message_unref};
5✔
432

433
        if (dbus_message_get_type(reply_ptr.get()) == DBUS_MESSAGE_TYPE_ERROR) {
5✔
434
                DBusError dbus_error;
435
                dbus_error_init(&dbus_error);
1✔
436
                const char *error;
437
                if (!dbus_message_get_args(
1✔
438
                                reply_ptr.get(), &dbus_error, DBUS_TYPE_STRING, &error, DBUS_TYPE_INVALID)) {
439
                        auto err = MakeError(
×
440
                                ValueError,
441
                                string("Got error reply, but failed to extrac the error from it: ")
×
442
                                        + dbus_error.message + "[" + dbus_error.name + "]");
×
443
                        dbus_error_free(&dbus_error);
×
444
                        (*handler)(expected::unexpected(err));
×
445
                } else {
446
                        const string error_str {error};
2✔
447
                        auto err = MakeError(ReplyError, "Got error reply: " + error_str);
1✔
448
                        (*handler)(expected::unexpected(err));
1✔
449
                }
450
                return;
1✔
451
        }
452

453
        const string signature {dbus_message_get_signature(reply_ptr.get())};
4✔
454
        if (!CheckDBusMessageSignature<ReplyType>(signature)) {
4✔
455
                auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
456
                (*handler)(expected::unexpected(err));
×
457
                return;
×
458
        }
459

460
        auto ex_reply = ExtractValueFromDBusMessage<ReplyType>(reply_ptr.get());
4✔
461
        (*handler)(ex_reply);
4✔
462
}
463

464
template <>
465
optional::optional<DBusSignalHandler<expected::ExpectedString>> DBusClient::GetSignalHandler(
7✔
466
        const SignalSpec &spec) {
467
        if (signal_handlers_string_.find(spec) != signal_handlers_string_.cend()) {
7✔
468
                return signal_handlers_string_[spec];
2✔
469
        } else {
470
                return optional::nullopt;
5✔
471
        }
472
}
473

474
template <>
475
optional::optional<DBusSignalHandler<ExpectedStringPair>> DBusClient::GetSignalHandler(
7✔
476
        const SignalSpec &spec) {
477
        if (signal_handlers_string_pair_.find(spec) != signal_handlers_string_pair_.cend()) {
7✔
478
                return signal_handlers_string_pair_[spec];
×
479
        } else {
480
                return optional::nullopt;
7✔
481
        }
482
}
483

484
DBusHandlerResult MsgFilter(DBusConnection *connection, DBusMessage *message, void *data) {
7✔
485
        if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) {
7✔
486
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
487
        }
488

489
        DBusClient *client = static_cast<DBusClient *>(data);
7✔
490

491
        // we use the match rule as a unique string for the given signal
492
        const string spec =
493
                GetSignalMatchRule(dbus_message_get_interface(message), dbus_message_get_member(message));
28✔
494

495
        const string signature {dbus_message_get_signature(message)};
14✔
496

497
        auto opt_string_handler = client->GetSignalHandler<expected::ExpectedString>(spec);
14✔
498
        auto opt_string_pair_handler = client->GetSignalHandler<ExpectedStringPair>(spec);
14✔
499

500
        // either no match or only one match
501
        assert(
7✔
502
                !(static_cast<bool>(opt_string_handler) || static_cast<bool>(opt_string_pair_handler))
503
                || (static_cast<bool>(opt_string_handler) ^ static_cast<bool>(opt_string_pair_handler)));
504

505
        if (opt_string_handler) {
7✔
506
                if (!CheckDBusMessageSignature<expected::ExpectedString>(signature)) {
2✔
507
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
508
                        (*opt_string_handler)(expected::unexpected(err));
×
509
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
510
                }
511

512
                auto ex_value = ExtractValueFromDBusMessage<expected::ExpectedString>(message);
2✔
513
                (*opt_string_handler)(ex_value);
2✔
514
                return DBUS_HANDLER_RESULT_HANDLED;
2✔
515
        } else if (opt_string_pair_handler) {
5✔
516
                if (!CheckDBusMessageSignature<ExpectedStringPair>(signature)) {
×
517
                        auto err = MakeError(ValueError, "Unexpected reply signature: " + signature);
×
518
                        (*opt_string_pair_handler)(expected::unexpected(err));
×
519
                        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
520
                }
521

522
                auto ex_value = ExtractValueFromDBusMessage<ExpectedStringPair>(message);
×
523
                (*opt_string_pair_handler)(ex_value);
×
524
                return DBUS_HANDLER_RESULT_HANDLED;
×
525
        } else {
526
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
5✔
527
        }
528
}
529

530
static inline string GetMethodSpec(
4✔
531
        const string &service, const string &interface, const string &method) {
532
        return service + "::" + interface + "." + method;
8✔
533
}
534

535
template <>
536
void DBusObject::AddMethodHandler(
2✔
537
        const string &service,
538
        const string &interface,
539
        const string &method,
540
        DBusMethodHandler<expected::ExpectedString> handler) {
541
        string spec = GetMethodSpec(service, interface, method);
4✔
542
        method_handlers_string_[spec] = handler;
2✔
543
}
2✔
544

545
template <>
546
void DBusObject::AddMethodHandler(
×
547
        const string &service,
548
        const string &interface,
549
        const string &method,
550
        DBusMethodHandler<ExpectedStringPair> handler) {
551
        string spec = GetMethodSpec(service, interface, method);
×
552
        method_handlers_string_pair_[spec] = handler;
×
553
}
×
554

555
template <>
556
optional::optional<DBusMethodHandler<expected::ExpectedString>> DBusObject::GetMethodHandler(
2✔
557
        const MethodSpec &spec) {
558
        if (method_handlers_string_.find(spec) != method_handlers_string_.cend()) {
2✔
559
                return method_handlers_string_[spec];
2✔
560
        } else {
561
                return optional::nullopt;
×
562
        }
563
}
564

565
template <>
566
optional::optional<DBusMethodHandler<ExpectedStringPair>> DBusObject::GetMethodHandler(
2✔
567
        const MethodSpec &spec) {
568
        if (method_handlers_string_pair_.find(spec) != method_handlers_string_pair_.cend()) {
2✔
569
                return method_handlers_string_pair_[spec];
×
570
        } else {
571
                return optional::nullopt;
2✔
572
        }
573
}
574

575
error::Error DBusServer::InitializeConnection() {
3✔
576
        auto err = DBusPeer::InitializeConnection();
6✔
577
        if (err != error::NoError) {
3✔
578
                return err;
×
579
        }
580

581
        DBusError dbus_error;
582
        dbus_error_init(&dbus_error);
3✔
583

584
        // We could also do DBUS_NAME_FLAG_ALLOW_REPLACEMENT for cases where two of
585
        // processes request the same name, but it would require handling of the
586
        // NameLost signal and triggering termination.
587
        if (dbus_bus_request_name(
3✔
588
                        dbus_conn_.get(), service_name_.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE, &dbus_error)
589
                == -1) {
3✔
590
                dbus_conn_.reset();
×
591
                auto err = MakeError(
592
                        ConnectionError,
593
                        (string("Failed to register DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
594
                         + "]"));
×
595
                dbus_error_free(&dbus_error);
×
596
                return err;
×
597
        }
598

599
        return error::NoError;
3✔
600
}
601

602
DBusServer::~DBusServer() {
3✔
603
        if (!dbus_conn_) {
3✔
604
                // nothing to do without a DBus connection
605
                return;
×
606
        }
607

608
        for (auto obj : objects_) {
6✔
609
                if (!dbus_connection_unregister_object_path(dbus_conn_.get(), obj->GetPath().c_str())) {
3✔
610
                        log::Warning("Failed to unregister DBus object " + obj->GetPath());
×
611
                }
612
        }
613

614
        DBusError dbus_error;
615
        dbus_error_init(&dbus_error);
3✔
616
        if (dbus_bus_release_name(dbus_conn_.get(), service_name_.c_str(), &dbus_error) == -1) {
3✔
617
                log::Warning(
×
618
                        string("Failed to release DBus name: ") + dbus_error.message + " [" + dbus_error.name
×
619
                        + "]");
×
620
                dbus_error_free(&dbus_error);
×
621
        }
622
}
3✔
623

624
template <typename ReturnType>
625
bool AddReturnDataToDBusMessage(DBusMessage *message, ReturnType data);
626

627
template <>
628
bool AddReturnDataToDBusMessage(DBusMessage *message, string data) {
2✔
629
        const char *data_cstr = data.c_str();
2✔
630
        return static_cast<bool>(
631
                dbus_message_append_args(message, DBUS_TYPE_STRING, &data_cstr, DBUS_TYPE_INVALID));
2✔
632
}
633

634
template <>
635
bool AddReturnDataToDBusMessage(DBusMessage *message, StringPair data) {
×
636
        const char *data_cstr1 = data.first.c_str();
×
637
        const char *data_cstr2 = data.second.c_str();
×
638
        return static_cast<bool>(dbus_message_append_args(
×
639
                message, DBUS_TYPE_STRING, &data_cstr1, DBUS_TYPE_STRING, &data_cstr2, DBUS_TYPE_INVALID));
×
640
}
641

642
DBusHandlerResult HandleMethodCall(DBusConnection *connection, DBusMessage *message, void *data) {
2✔
643
        DBusObject *obj = static_cast<DBusObject *>(data);
2✔
644

645
        string spec = GetMethodSpec(
646
                dbus_message_get_destination(message),
647
                dbus_message_get_interface(message),
648
                dbus_message_get_member(message));
10✔
649

650
        auto opt_string_handler = obj->GetMethodHandler<expected::ExpectedString>(spec);
4✔
651
        auto opt_string_pair_handler = obj->GetMethodHandler<ExpectedStringPair>(spec);
4✔
652

653
        if (!opt_string_handler && !opt_string_pair_handler) {
2✔
654
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
655
        }
656

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

659
        if (opt_string_handler) {
2✔
660
                expected::ExpectedString ex_return_data = (*opt_string_handler)();
2✔
661
                if (!ex_return_data) {
2✔
662
                        auto &err = ex_return_data.error();
1✔
663
                        reply_msg.reset(
1✔
664
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
2✔
665
                        if (!reply_msg) {
1✔
666
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
667
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
668
                        }
669
                } else {
670
                        reply_msg.reset(dbus_message_new_method_return(message));
1✔
671
                        if (!reply_msg) {
1✔
672
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
673
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
674
                        }
675
                        if (!AddReturnDataToDBusMessage<string>(reply_msg.get(), ex_return_data.value())) {
1✔
676
                                log::Error(
×
677
                                        "Failed to add return value to reply DBus message when handling method "
678
                                        + spec);
×
679
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
680
                        }
681
                }
682
        } else if (opt_string_pair_handler) {
×
683
                ExpectedStringPair ex_return_data = (*opt_string_pair_handler)();
×
684
                if (!ex_return_data) {
×
685
                        auto &err = ex_return_data.error();
×
686
                        reply_msg.reset(
×
687
                                dbus_message_new_error(message, DBUS_ERROR_FAILED, err.String().c_str()));
×
688
                        if (!reply_msg) {
×
689
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
690
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
691
                        }
692
                } else {
693
                        reply_msg.reset(dbus_message_new_method_return(message));
×
694
                        if (!reply_msg) {
×
695
                                log::Error("Failed to create new DBus message when handling method " + spec);
×
696
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
697
                        }
698
                        if (!AddReturnDataToDBusMessage<StringPair>(reply_msg.get(), ex_return_data.value())) {
×
699
                                log::Error(
×
700
                                        "Failed to add return value to reply DBus message when handling method "
701
                                        + spec);
×
702
                                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
703
                        }
704
                }
705
        }
706

707
        if (!dbus_connection_send(connection, reply_msg.get(), NULL)) {
2✔
708
                // can only happen in case of no memory
709
                log::Error("Failed to send reply DBus message when handling method " + spec);
×
710
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
×
711
        }
712

713
        return DBUS_HANDLER_RESULT_HANDLED;
2✔
714
}
715

716
static DBusObjectPathVTable dbus_vtable = {.message_function = HandleMethodCall};
717

718
error::Error DBusServer::AdvertiseObject(DBusObjectPtr obj) {
3✔
719
        if (!dbus_conn_) {
3✔
720
                auto err = InitializeConnection();
3✔
721
                if (err != error::NoError) {
3✔
722
                        return err;
×
723
                }
724
        }
725

726
        const string &obj_path {obj->GetPath()};
3✔
727
        DBusError dbus_error;
728
        dbus_error_init(&dbus_error);
3✔
729

730
        if (!dbus_connection_try_register_object_path(
3✔
731
                        dbus_conn_.get(), obj_path.c_str(), &dbus_vtable, obj.get(), &dbus_error)) {
3✔
732
                auto err = MakeError(
733
                        ConnectionError,
734
                        (string("Failed to register object ") + obj_path + ": " + dbus_error.message + " ["
×
735
                         + dbus_error.name + "]"));
×
736
                dbus_error_free(&dbus_error);
×
737
                return err;
×
738
        }
739

740
        objects_.push_back(obj);
3✔
741
        return error::NoError;
3✔
742
}
743

744
template <typename SignalValueType>
745
error::Error DBusServer::EmitSignal(
1✔
746
        const string &path, const string &iface, const string &signal, SignalValueType value) {
747
        if (!dbus_conn_) {
1✔
748
                auto err = InitializeConnection();
×
749
                if (err != error::NoError) {
×
750
                        return err;
×
751
                }
752
        }
753

754
        unique_ptr<DBusMessage, decltype(&dbus_message_unref)> signal_msg {
2✔
755
                dbus_message_new_signal(path.c_str(), iface.c_str(), signal.c_str()), dbus_message_unref};
1✔
756
        if (!signal_msg) {
1✔
757
                return MakeError(MessageError, "Failed to create signal message");
×
758
        }
759

760
        if (!AddReturnDataToDBusMessage<SignalValueType>(signal_msg.get(), value)) {
1✔
761
                return MakeError(MessageError, "Failed to add data to the signal message");
×
762
        }
763

764
        if (!dbus_connection_send(dbus_conn_.get(), signal_msg.get(), NULL)) {
1✔
765
                // can only happen in case of no memory
766
                return MakeError(ConnectionError, "Failed to send signal message");
×
767
        }
768

769
        return error::NoError;
1✔
770
}
771

772
template error::Error DBusServer::EmitSignal(
773
        const string &path, const string &iface, const string &signal, string value);
774

775
template error::Error DBusServer::EmitSignal(
776
        const string &path, const string &iface, const string &signal, StringPair value);
777

778
} // namespace dbus
779
} // namespace common
780
} // 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