1 #pragma once
2
3 #include <systemd/sd-bus.h>
4
5 #include <sdbusplus/exception.hpp>
6 #include <sdbusplus/message/append.hpp>
7 #include <sdbusplus/message/native_types.hpp>
8 #include <sdbusplus/message/read.hpp>
9 #include <sdbusplus/sdbus.hpp>
10 #include <sdbusplus/slot.hpp>
11
12 #include <exception>
13 #include <memory>
14 #include <optional>
15 #include <tuple>
16 #include <type_traits>
17 #include <utility>
18
19 #ifdef __clang__
20 #pragma clang diagnostic push
21 #pragma clang diagnostic ignored "-Wc99-extensions"
22 #endif
23
24 namespace sdbusplus
25 {
26
27 // Forward declare sdbusplus::bus::bus for 'friend'ship.
28 namespace bus
29 {
30 struct bus;
31 }
32
33 namespace message
34 {
35
36 using msgp_t = sd_bus_message*;
37 class message;
38
39 namespace details
40 {
41
42 /** @brief unique_ptr functor to release a msg reference. */
43 // TODO(venture): Consider using template <SdBusInterfaceType> for this so that
44 // it doesn't require creating a specific instance of it, unless that's ok --
45 struct MsgDeleter
46 {
operator ()sdbusplus::message::details::MsgDeleter47 void operator()(msgp_t ptr) const
48 {
49 sd_bus_message_unref(ptr);
50 }
51 };
52
53 /* @brief Alias 'msg' to a unique_ptr type for auto-release. */
54 using msg = std::unique_ptr<sd_bus_message, MsgDeleter>;
55
56 template <typename CbT>
57 int call_async_cb(sd_bus_message* m, void* userdata, sd_bus_error*) noexcept;
58
59 template <typename CbT>
call_async_del(void * userdata)60 void call_async_del(void* userdata) noexcept
61 {
62 delete reinterpret_cast<CbT*>(userdata);
63 }
64
65 } // namespace details
66
67 /** @class message
68 * @brief Provides C++ bindings to the sd_bus_message_* class functions.
69 */
70 class message : private sdbusplus::slot::details::slot_friend
71 {
72 /* Define all of the basic class operations:
73 * Allowed:
74 * - Default constructor
75 * - Copy operations.
76 * - Move operations.
77 * - Destructor.
78 */
79 public:
80 message(message&&) = default;
81 message& operator=(message&&) = default;
82 ~message() = default;
83
message(msgp_t m,sdbusplus::SdBusInterface * intf)84 message(msgp_t m, sdbusplus::SdBusInterface* intf) :
85 _intf(std::move(intf)), _msg(_intf->sd_bus_message_ref(m))
86 {}
87
88 /** @brief Conversion constructor for 'msgp_t'.
89 *
90 * Takes increment ref-count of the msg-pointer and release when
91 * destructed.
92 */
message(msgp_t m=nullptr)93 explicit message(msgp_t m = nullptr) : message(m, &sdbus_impl) {}
94
message(msgp_t m,sdbusplus::SdBusInterface * intf,std::false_type)95 message(msgp_t m, sdbusplus::SdBusInterface* intf, std::false_type) :
96 _intf(intf), _msg(m)
97 {}
98
99 /** @brief Constructor for 'msgp_t'.
100 *
101 * Takes ownership of the msg-pointer and releases it when done.
102 */
message(msgp_t m,std::false_type)103 message(msgp_t m, std::false_type) : _intf(&sdbus_impl), _msg(m) {}
104
105 /** @brief Copy constructor for 'message'.
106 *
107 * Copies the message class and increments the ref on _msg
108 */
message(const message & other)109 message(const message& other) :
110 _intf(other._intf), _msg(sd_bus_message_ref(other._msg.get()))
111 {}
112
113 /** @brief Assignment operator for 'message'.
114 *
115 * Copies the message class and increments the ref on _msg
116 */
operator =(const message & other)117 message& operator=(const message& other)
118 {
119 _msg.reset(sd_bus_message_ref(other._msg.get()));
120 _intf = other._intf;
121 return *this;
122 }
123
124 /** @brief Release ownership of the stored msg-pointer. */
release()125 msgp_t release()
126 {
127 return _msg.release();
128 }
129
130 /** @brief Check if message contains a real pointer. (non-nullptr). */
operator bool() const131 explicit operator bool() const
132 {
133 return bool(_msg);
134 }
135
136 /** @brief Perform sd_bus_message_append, with automatic type deduction.
137 *
138 * @tparam Args - Type of items to append to message.
139 * @param[in] args - Items to append to message.
140 */
141 template <typename... Args>
append(Args &&...args)142 void append(Args&&... args)
143 {
144 sdbusplus::message::append(_intf, _msg.get(),
145 std::forward<Args>(args)...);
146 }
147
148 /** @brief Perform sd_bus_message_read, with automatic type deduction.
149 *
150 * @tparam Args - Type of items to read from message.
151 * @param[out] args - Items to read from message.
152 */
153 template <typename... Args>
read(Args &&...args)154 void read(Args&&... args)
155 {
156 sdbusplus::message::read(_intf, _msg.get(),
157 std::forward<Args>(args)...);
158 }
159
160 /** @brief Perform sd_bus_message_read with results returned.
161 *
162 * @tparam Args - Type of items to read from the message.
163 * @return One of { void, Args, std::tuple<Args...> }.
164 */
165 template <typename... Args>
unpack()166 auto unpack()
167 {
168 if constexpr (sizeof...(Args) == 0)
169 {
170 // No args
171 return;
172 }
173 else if constexpr (sizeof...(Args) == 1 &&
174 std::is_void_v<
175 std::tuple_element_t<0, std::tuple<Args...>>>)
176 {
177 // Args is void
178 return;
179 }
180 else if constexpr (sizeof...(Args) == 1)
181 {
182 std::tuple_element_t<0, std::tuple<Args...>> r{};
183 read(r);
184 return r;
185 }
186 else
187 {
188 std::tuple<Args...> r{};
189 std::apply([this](auto&&... v) { this->read(v...); }, r);
190 return r;
191 }
192 }
193
194 /** @brief Get the dbus bus from the message. */
195 // Forward declare.
196 bus::bus get_bus() const;
197
198 /** @brief Get the signature of a message.
199 *
200 * @return A [weak] pointer to the signature of the message.
201 */
get_signature() const202 const char* get_signature() const
203 {
204 return _intf->sd_bus_message_get_signature(_msg.get(), true);
205 }
206
207 /** @brief Get the path of a message.
208 *
209 * @return A [weak] pointer to the path of the message.
210 */
get_path() const211 const char* get_path() const
212 {
213 return _intf->sd_bus_message_get_path(_msg.get());
214 }
215
216 /** @brief Get the interface of a message.
217 *
218 * @return A [weak] pointer to the interface of the message.
219 */
get_interface() const220 const char* get_interface() const
221 {
222 return _intf->sd_bus_message_get_interface(_msg.get());
223 }
224
225 /** @brief Get the member of a message.
226 *
227 * @return A [weak] pointer to the member of the message.
228 */
get_member() const229 const char* get_member() const
230 {
231 return _intf->sd_bus_message_get_member(_msg.get());
232 }
233
234 /** @brief Get the destination of a message.
235 *
236 * @return A [weak] pointer to the destination of the message.
237 */
get_destination() const238 const char* get_destination() const
239 {
240 return _intf->sd_bus_message_get_destination(_msg.get());
241 }
242
243 /** @brief Get the sender of a message.
244 *
245 * @return A [weak] pointer to the sender of the message.
246 */
get_sender() const247 const char* get_sender() const
248 {
249 return _intf->sd_bus_message_get_sender(_msg.get());
250 }
251
252 /** @brief Check if message is a method error.
253 *
254 * @return True - if message is a method error.
255 */
is_method_error() const256 bool is_method_error() const
257 {
258 return _intf->sd_bus_message_is_method_error(_msg.get(), nullptr);
259 }
260
261 /** @brief Get the errno from the message.
262 *
263 * @return The errno of the message.
264 */
get_errno() const265 int get_errno() const
266 {
267 return _intf->sd_bus_message_get_errno(_msg.get());
268 }
269
270 /** @brief Get the error from the message.
271 *
272 * @return The error of the message.
273 */
get_error() const274 const sd_bus_error* get_error() const
275 {
276 return _intf->sd_bus_message_get_error(_msg.get());
277 }
278
279 /** @brief Get the type of a message.
280 *
281 * @return The type of message.
282 */
get_type() const283 auto get_type() const
284 {
285 uint8_t type;
286 int r = _intf->sd_bus_message_get_type(_msg.get(), &type);
287 if (r < 0)
288 {
289 throw exception::SdBusError(-r, "sd_bus_message_get_type");
290 }
291 return type;
292 }
293
294 /** @brief Get the transaction cookie of a message.
295 *
296 * @return The transaction cookie of a message.
297 */
get_cookie() const298 auto get_cookie() const
299 {
300 uint64_t cookie;
301 int r = _intf->sd_bus_message_get_cookie(_msg.get(), &cookie);
302 if (r < 0)
303 {
304 throw exception::SdBusError(-r, "sd_bus_message_get_cookie");
305 }
306 return cookie;
307 }
308
309 /** @brief Get the reply cookie of a message.
310 *
311 * @return The reply cookie of a message.
312 */
get_reply_cookie() const313 auto get_reply_cookie() const
314 {
315 uint64_t cookie;
316 int r = _intf->sd_bus_message_get_reply_cookie(_msg.get(), &cookie);
317 if (r < 0)
318 {
319 throw exception::SdBusError(-r, "sd_bus_message_get_reply_cookie");
320 }
321 return cookie;
322 }
323
324 /** @brief Check if message is a method call for an interface/method.
325 *
326 * @param[in] interface - The interface to match.
327 * @param[in] method - The method to match.
328 *
329 * @return True - if message is a method call for interface/method.
330 */
is_method_call(const char * interface,const char * method) const331 bool is_method_call(const char* interface, const char* method) const
332 {
333 return _intf->sd_bus_message_is_method_call(_msg.get(), interface,
334 method);
335 }
336
337 /** @brief Check if message is a signal for an interface/member.
338 *
339 * @param[in] interface - The interface to match.
340 * @param[in] member - The member to match.
341 */
is_signal(const char * interface,const char * member) const342 bool is_signal(const char* interface, const char* member) const
343 {
344 return _intf->sd_bus_message_is_signal(_msg.get(), interface, member);
345 }
346
347 /** @brief Create a 'method_return' type message from an existing message.
348 *
349 * @return method-return message.
350 */
new_method_return()351 message new_method_return()
352 {
353 msgp_t reply = nullptr;
354 int r = _intf->sd_bus_message_new_method_return(this->get(), &reply);
355 if (r < 0)
356 {
357 throw exception::SdBusError(-r, "sd_bus_message_new_method_return");
358 }
359
360 return message(reply, _intf, std::false_type());
361 }
362
363 /** @brief Create a 'method_error' type message from an existing message.
364 *
365 * @param[in] e - The exception we are returning
366 * @return method-return message.
367 */
new_method_error(const sdbusplus::exception::exception & e)368 message new_method_error(const sdbusplus::exception::exception& e)
369 {
370 msgp_t reply = nullptr;
371 sd_bus_error error = SD_BUS_ERROR_NULL;
372 e.set_error(_intf, &error);
373 int r =
374 _intf->sd_bus_message_new_method_error(this->get(), &reply, &error);
375 sd_bus_error_free(&error);
376 if (r < 0)
377 {
378 throw exception::SdBusError(-r, "sd_bus_message_new_method_error");
379 }
380
381 return message(reply, _intf, std::false_type());
382 }
383
384 /** @brief Create a 'method_error' type message from an existing message.
385 *
386 * @param[in] error - integer error number
387 * @param[in] e - optional pointer to preformatted sd_bus_error
388 * @return method-error message.
389 */
new_method_errno(int error,const sd_bus_error * e=nullptr)390 message new_method_errno(int error, const sd_bus_error* e = nullptr)
391 {
392 msgp_t reply = nullptr;
393 int r = _intf->sd_bus_message_new_method_errno(this->get(), &reply,
394 error, e);
395 if (r < 0)
396 {
397 throw exception::SdBusError(-r, "sd_bus_message_new_method_errno");
398 }
399
400 return message(reply, _intf, std::false_type());
401 }
402
403 /** @brief Perform a 'method-return' response call. */
method_return()404 void method_return()
405 {
406 auto b = _intf->sd_bus_message_get_bus(this->get());
407 int r = _intf->sd_bus_send(b, this->get(), nullptr);
408 if (r < 0)
409 {
410 throw exception::SdBusError(-r, "sd_bus_send");
411 }
412 }
413
414 /** @brief Perform a 'signal-send' call. */
signal_send()415 void signal_send()
416 {
417 method_return();
418 }
419
420 /** @brief Perform a message call.
421 * Errors generated by this call come from underlying dbus
422 * related errors *AND* from any method call that results
423 * in a METHOD_ERROR. This means you do not need to check
424 * is_method_error() on the returned message.
425 *
426 * @param[in] timeout - The timeout for the method call.
427 *
428 * @return The response message.
429 */
call(std::optional<SdBusDuration> timeout=std::nullopt)430 auto call(std::optional<SdBusDuration> timeout = std::nullopt)
431 {
432 sd_bus_error error = SD_BUS_ERROR_NULL;
433 sd_bus_message* reply = nullptr;
434 auto timeout_us = timeout ? timeout->count() : 0;
435 int r = _intf->sd_bus_call(nullptr, get(), timeout_us, &error, &reply);
436 if (r < 0)
437 {
438 throw exception::SdBusError(&error, "sd_bus_call");
439 }
440
441 return message(reply, _intf, std::false_type());
442 }
443
444 /** @brief Perform an async message call.
445 *
446 * @param[in] cb - The callback to run when the response is available.
447 * @param[in] timeout - The timeout for the method call.
448 *
449 * @return The slot handle that manages the lifetime of the call object.
450 */
451 template <typename Cb>
452 [[nodiscard]] slot_t
call_async(Cb && cb,std::optional<SdBusDuration> timeout=std::nullopt)453 call_async(Cb&& cb, std::optional<SdBusDuration> timeout = std::nullopt)
454 {
455 sd_bus_slot* slot;
456 auto timeout_us = timeout ? timeout->count() : 0;
457 using CbT = std::remove_cv_t<std::remove_reference_t<Cb>>;
458 int r = _intf->sd_bus_call_async(nullptr, &slot, get(),
459 details::call_async_cb<CbT>, nullptr,
460 timeout_us);
461 if (r < 0)
462 {
463 throw exception::SdBusError(-r, "sd_bus_call_async");
464 }
465 slot_t ret(slot, _intf);
466
467 if constexpr (std::is_pointer_v<CbT>)
468 {
469 _intf->sd_bus_slot_set_userdata(get_slotp(ret),
470 reinterpret_cast<void*>(cb));
471 }
472 else if constexpr (std::is_function_v<CbT>)
473 {
474 _intf->sd_bus_slot_set_userdata(get_slotp(ret),
475 reinterpret_cast<void*>(&cb));
476 }
477 else
478 {
479 r = _intf->sd_bus_slot_set_destroy_callback(
480 get_slotp(ret), details::call_async_del<CbT>);
481 if (r < 0)
482 {
483 throw exception::SdBusError(-r,
484 "sd_bus_slot_set_destroy_callback");
485 }
486 _intf->sd_bus_slot_set_userdata(get_slotp(ret),
487 new CbT(std::forward<Cb>(cb)));
488 }
489 return ret;
490 }
491
492 friend struct sdbusplus::bus::bus;
493
494 /** @brief Get a pointer to the owned 'msgp_t'.
495 * This api should be used sparingly and carefully, as it opens a number of
496 * possibilities for race conditions, RAII destruction issues, and runtime
497 * problems when using the sd-bus c api. Here be dragons. */
get()498 msgp_t get()
499 {
500 return _msg.get();
501 }
502
503 private:
504 sdbusplus::SdBusInterface* _intf;
505 details::msg _msg;
506 };
507
508 namespace details
509 {
510
511 template <typename CbT>
call_async_cb(sd_bus_message * m,void * userdata,sd_bus_error *)512 int call_async_cb(sd_bus_message* m, void* userdata, sd_bus_error*) noexcept
513 {
514 try
515 {
516 if constexpr (std::is_pointer_v<CbT>)
517 {
518 (*reinterpret_cast<CbT>(userdata))(message(m));
519 }
520 else
521 {
522 (*reinterpret_cast<CbT*>(userdata))(message(m));
523 }
524 }
525 catch (...)
526 {
527 std::terminate();
528 }
529 return 1;
530 }
531
532 } // namespace details
533
534 } // namespace message
535
536 using message_t = message::message;
537
538 } // namespace sdbusplus
539
540 #ifdef __clang__
541 #pragma clang diagnostic pop
542 #endif
543