blob: 7e834c0269f198eae1c42699dbf43461c9144431 [file] [log] [blame]
#pragma once
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include <sdbusplus/exception.hpp>
#include <sdbusplus/message.hpp>
#include <sdbusplus/sdbus.hpp>
#include <algorithm>
#include <climits>
#include <exception>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
#endif
namespace sdbusplus
{
namespace bus
{
using busp_t = sd_bus*;
struct bus;
/* Methods for getting a new bus connection.
*
* There are two different bus types:
* - system
* - session ("user")
*
* If you call either `new_default` or `new_bus` you end up with a connection
* to the default bus based on the current user; system if root, session
* otherwise.
*
* sd-bus uses the word "default" to refer to a shared bus connection that it
* saves in thread-local-storage. The `new_default*` functions give a
* connection to this thread-local-storage bus connection and not neceesarily a
* new, unique bus connection. This can be a very important distinction,
* such as when writing test-cases that might require having both a server
* and client connection.
*
* If you only expect to have a single bus connection in your process with a
* single thread interacting with that connection, `new_default` is a fine
* option. Otherwise, you likely want `new_bus`.
*/
/** @brief Get the shared instance of the 'default' bus. */
bus new_default();
/** @brief Get a new instance of a bus. */
bus new_bus();
/** @brief Get a new instance of the 'user' session bus. */
bus new_user();
/** @brief Get a new instance of the 'system' bus. */
bus new_system();
/** @brief Get the shared instance of the 'user' session bus. */
bus new_default_user();
/** @brief Get the shared instance of the 'system' bus. */
bus new_default_system();
namespace details
{
/** @brief unique_ptr functor to release a bus reference. */
struct BusDeleter
{
BusDeleter() = delete;
explicit BusDeleter(SdBusInterface* interface) : m_interface(interface) {}
void operator()(sd_bus* ptr) const
{
(m_interface->*deleter)(ptr);
}
SdBusInterface* m_interface;
decltype(&SdBusInterface::sd_bus_unref) deleter =
&SdBusInterface::sd_bus_unref;
};
/** @brief Convert a vector of strings to c-style char** array. */
class Strv
{
public:
~Strv() = default;
Strv() = delete;
Strv(const Strv&) = delete;
Strv& operator=(const Strv&) = delete;
Strv(Strv&&) = default;
Strv& operator=(Strv&&) = default;
explicit Strv(const std::vector<std::string>& v)
{
std::transform(v.begin(), v.end(), std::back_inserter(ptrs),
[](const auto& i) { return i.c_str(); });
ptrs.push_back(nullptr);
}
explicit operator char**()
{
return const_cast<char**>(&ptrs.front());
}
private:
std::vector<const char*> ptrs;
};
/* @brief Alias 'bus' to a unique_ptr type for auto-release. */
using bus = std::unique_ptr<sd_bus, BusDeleter>;
struct bus_friend;
} // namespace details
/** @class bus
* @brief Provides C++ bindings to the sd_bus_* class functions.
*/
struct bus
{
/* Define all of the basic class operations:
* Not allowed:
* - Default constructor to avoid nullptrs.
* - Copy operations due to internal unique_ptr.
* Allowed:
* - Move operations.
* - Destructor.
*/
bus() = delete;
bus(const bus&) = delete;
bus& operator=(const bus&) = delete;
bus(bus&&) = default;
bus& operator=(bus&&) = default;
~bus() = default;
bus(busp_t b, sdbusplus::SdBusInterface* intf);
/** @brief Conversion constructor from 'busp_t'.
*
* Increments ref-count of the bus-pointer and releases it when done.
*/
explicit bus(busp_t b);
/** @brief Constructor for 'bus'.
*
* Takes ownership of the bus-pointer and releases it when done.
*/
bus(busp_t b, std::false_type);
/** @brief Sets the bus to be closed when this handle is destroyed. */
void set_should_close(bool shouldClose)
{
if (shouldClose)
{
_bus.get_deleter().deleter =
&SdBusInterface::sd_bus_flush_close_unref;
}
else
{
_bus.get_deleter().deleter = &SdBusInterface::sd_bus_unref;
}
}
/** @brief Release ownership of the stored bus-pointer. */
busp_t release()
{
return _bus.release();
}
/** @brief Flush all of the outstanding work on the bus. */
void flush()
{
int r = _intf->sd_bus_flush(_bus.get());
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_flush");
}
}
/** @brief Close the connection to the dbus daemon. */
void close()
{
_intf->sd_bus_close(_bus.get());
}
/** @brief Determine if the bus is open to the broker */
bool is_open()
{
int r = _intf->sd_bus_is_open(_bus.get());
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_is_open");
}
return r;
}
/** @brief Wait for new dbus messages or signals.
*
* @param[in] timeout_us - Timeout in usec.
*/
int wait(uint64_t timeout_us)
{
int r = _intf->sd_bus_wait(_bus.get(), timeout_us);
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_wait");
}
return r;
}
int wait(std::optional<SdBusDuration> timeout = std::nullopt)
{
return wait(timeout ? timeout->count() : UINT64_MAX);
}
/** @brief Process waiting dbus messages or signals. */
auto process()
{
sd_bus_message* m = nullptr;
int r = _intf->sd_bus_process(_bus.get(), &m);
if (current_exception)
{
auto ex = std::exchange(current_exception, nullptr);
std::rethrow_exception(ex);
}
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_process");
}
return message_t(m, _intf, std::false_type());
}
/** @brief Process waiting dbus messages or signals, discarding unhandled.
*/
auto process_discard()
{
int r = _intf->sd_bus_process(_bus.get(), nullptr);
if (current_exception)
{
auto ex = std::exchange(current_exception, nullptr);
std::rethrow_exception(ex);
}
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_process discard");
}
return r > 0;
}
/** @brief Process waiting dbus messages or signals forever, discarding
* unhandled.
*/
[[noreturn]] void process_loop()
{
while (true)
{
process_discard();
wait();
}
}
/** @brief Claim a service name on the dbus.
*
* @param[in] service - The service name to claim.
*/
void request_name(const char* service)
{
int r = _intf->sd_bus_request_name(
_bus.get(), service,
(SD_BUS_NAME_ALLOW_REPLACEMENT | SD_BUS_NAME_REPLACE_EXISTING));
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_request_name");
}
}
/** @brief Create a method_call message.
*
* @param[in] service - The service to call.
* @param[in] objpath - The object's path for the call.
* @param[in] interf - The object's interface to call.
* @param[in] method - The object's method to call.
*
* @return A newly constructed message.
*/
auto new_method_call(const char* service, const char* objpath,
const char* interf, const char* method)
{
sd_bus_message* m = nullptr;
int r = _intf->sd_bus_message_new_method_call(_bus.get(), &m, service,
objpath, interf, method);
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_message_new_method_call");
}
return message_t(m, _intf, std::false_type());
}
/** @brief Create a signal message.
*
* @param[in] objpath - The object's path for the signal.
* @param[in] interf - The object's interface for the signal.
* @param[in] member - The signal name.
*
* @return A newly constructed message.
*/
auto new_signal(const char* objpath, const char* interf, const char* member)
{
sd_bus_message* m = nullptr;
int r = _intf->sd_bus_message_new_signal(_bus.get(), &m, objpath,
interf, member);
if (r < 0)
{
throw exception::SdBusError(-r, "sd_bus_message_new_signal");
}
return message_t(m, _intf, std::false_type());
}
/** @brief Perform a message call.
* Errors generated by this call come from underlying dbus
* related errors *AND* from any method call that results
* in a METHOD_ERROR. This means you do not need to check
* is_method_error() on the returned message.
*
* @param[in] m - The method_call message.
* @param[in] timeout_us - The timeout for the method call.
*
* @return The response message.
*/
auto call(message_t& m, uint64_t timeout_us)
{
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message* reply = nullptr;
int r = _intf->sd_bus_call(_bus.get(), m.get(), timeout_us, &error,
&reply);
if (r < 0)
{
throw exception::SdBusError(&error, "sd_bus_call");
}
return message_t(reply, _intf, std::false_type());
}
auto call(message_t& m, std::optional<SdBusDuration> timeout = std::nullopt)
{
return call(m, timeout ? timeout->count() : 0);
}
/** @brief Perform a message call, ignoring the reply.
*
* @param[in] m - The method_call message.
* @param[in] timeout_us - The timeout for the method call.
*/
void call_noreply(message_t& m, uint64_t timeout_us)
{
sd_bus_error error = SD_BUS_ERROR_NULL;
int r = _intf->sd_bus_call(_bus.get(), m.get(), timeout_us, &error,
nullptr);
if (r < 0)
{
throw exception::SdBusError(&error, "sd_bus_call noreply");
}
}
auto call_noreply(message_t& m,
std::optional<SdBusDuration> timeout = std::nullopt)
{
return call_noreply(m, timeout ? timeout->count() : 0);
}
/** @brief Perform a message call, ignoring the reply and any errors
* in the dbus stack.
*
* @param[in] m - The method_call message.
* @param[in] timeout_us - The timeout for the method call.
*/
void call_noreply_noerror(message_t& m, uint64_t timeout_us)
{
try
{
call_noreply(m, timeout_us);
}
catch (const exception::SdBusError&)
{
// Intentionally ignoring these sd_bus errors
}
}
auto call_noreply_noerror(
message_t& m, std::optional<SdBusDuration> timeout = std::nullopt)
{
return call_noreply_noerror(m, timeout ? timeout->count() : 0);
}
/** @brief Get the bus unique name. Ex: ":1.11".
*
* @return The bus unique name.
*/
auto get_unique_name()
{
const char* unique = nullptr;
_intf->sd_bus_get_unique_name(_bus.get(), &unique);
return std::string(unique);
}
auto get_fd()
{
return _intf->sd_bus_get_fd(_bus.get());
}
/** @brief Attach the bus with a sd-event event loop object.
*
* @param[in] event - sd_event object.
* @param[in] priority - priority of bus event source.
*/
void attach_event(sd_event* event, int priority)
{
_intf->sd_bus_attach_event(_bus.get(), event, priority);
}
/** @brief Detach the bus from its sd-event event loop object */
void detach_event()
{
_intf->sd_bus_detach_event(_bus.get());
}
/** @brief Get the sd-event event loop object of the bus */
auto get_event()
{
return _intf->sd_bus_get_event(_bus.get());
}
/** @brief Wrapper for sd_bus_emit_interfaces_added_strv
*
* In general the similarly named server::object::object API should
* be used to manage emission of ObjectManager signals in favor
* of this one. Provided here for complex usage scenarios.
*
* @param[in] path - The path to forward.
* @param[in] ifaces - The interfaces to forward.
*/
void emit_interfaces_added(const char* path,
const std::vector<std::string>& ifaces);
/** @brief Wrapper for sd_bus_emit_interfaces_removed_strv
*
* In general the similarly named server::object::object API should
* be used to manage emission of ObjectManager signals in favor
* of this one. Provided here for complex usage scenarios.
*
* @param[in] path - The path to forward.
* @param[in] ifaces - The interfaces to forward.
*/
void emit_interfaces_removed(const char* path,
const std::vector<std::string>& ifaces);
/** @brief Wrapper for sd_bus_emit_object_added
*
* In general the similarly named server::object::object API should
* be used to manage emission of ObjectManager signals in favor
* of this one. Provided here for complex usage scenarios.
*
* @param[in] path - The path to forward to sd_bus_emit_object_added
*/
void emit_object_added(const char* path)
{
_intf->sd_bus_emit_object_added(_bus.get(), path);
}
/** @brief Wrapper for sd_bus_emit_object_removed
*
* In general the similarly named server::object::object API should
* be used to manage emission of ObjectManager signals in favor
* of this one. Provided here for complex usage scenarios.
*
* @param[in] path - The path to forward to sd_bus_emit_object_removed
*/
void emit_object_removed(const char* path)
{
_intf->sd_bus_emit_object_removed(_bus.get(), path);
}
/** @brief Wrapper for sd_bus_list_names.
*
* @return A vector of strings containing the 'acquired' names from
* sd_bus_list_names.
*/
auto list_names_acquired()
{
char** names = nullptr;
_intf->sd_bus_list_names(_bus.get(), &names, nullptr);
std::vector<std::string> result;
for (auto ptr = names; ptr && *ptr; ++ptr)
{
result.push_back(*ptr);
free(*ptr);
}
free(names);
return result;
}
/** @brief Get the SdBusInterface used by this bus.
*
* @return A pointer to the SdBusInterface used by this bus.
*/
sdbusplus::SdBusInterface* getInterface()
{
return _intf;
}
void set_current_exception(std::exception_ptr exception)
{
current_exception = exception;
}
friend struct details::bus_friend;
protected:
busp_t get() noexcept
{
return _bus.get();
}
sdbusplus::SdBusInterface* _intf;
details::bus _bus;
private:
std::exception_ptr current_exception;
};
inline bus::bus(busp_t b, sdbusplus::SdBusInterface* intf) :
_intf(intf), _bus(_intf->sd_bus_ref(b), details::BusDeleter(intf))
{
// Emitting object added causes a message to get the properties
// which can trigger a 'transaction' in the server bindings. If
// the bus isn't up far enough, this causes an assert deep in
// sd-bus code. Get the 'unique_name' to ensure the bus is up far
// enough to avoid the assert.
if (b != nullptr)
{
get_unique_name();
}
}
inline bus::bus(busp_t b) :
_intf(&sdbus_impl),
_bus(_intf->sd_bus_ref(b), details::BusDeleter(&sdbus_impl))
{
// Emitting object added causes a message to get the properties
// which can trigger a 'transaction' in the server bindings. If
// the bus isn't up far enough, this causes an assert deep in
// sd-bus code. Get the 'unique_name' to ensure the bus is up far
// enough to avoid the assert.
if (b != nullptr)
{
get_unique_name();
}
}
inline bus::bus(busp_t b, std::false_type) :
_intf(&sdbus_impl), _bus(b, details::BusDeleter(&sdbus_impl))
{
// Emitting object added causes a message to get the properties
// which can trigger a 'transaction' in the server bindings. If
// the bus isn't up far enough, this causes an assert deep in
// sd-bus code. Get the 'unique_name' to ensure the bus is up far
// enough to avoid the assert.
if (b != nullptr)
{
get_unique_name();
}
}
/* Create a new default connection: system bus if root, session bus if user. */
inline bus new_default()
{
sd_bus* b = nullptr;
sd_bus_default(&b);
return bus(b, std::false_type());
}
/* Create a new default connection to the session bus. */
inline bus new_default_user()
{
sd_bus* b = nullptr;
sd_bus_default_user(&b);
return bus(b, std::false_type());
}
/* Create a new default connection to the system bus. */
inline bus new_default_system()
{
sd_bus* b = nullptr;
sd_bus_default_system(&b);
return bus(b, std::false_type());
}
/* Create a new connection: system bus if root, session bus if user. */
inline bus new_bus()
{
sd_bus* b = nullptr;
sd_bus_open(&b);
bus bus(b, std::false_type());
bus.set_should_close(true);
return bus;
}
/* Create a new connection to the session bus. */
inline bus new_user()
{
sd_bus* b = nullptr;
sd_bus_open_user(&b);
bus bus(b, std::false_type());
bus.set_should_close(true);
return bus;
}
/* Create a new connection to the system bus. */
inline bus new_system()
{
sd_bus* b = nullptr;
sd_bus_open_system(&b);
bus bus(b, std::false_type());
bus.set_should_close(true);
return bus;
}
namespace details
{
// Some sdbusplus classes need to be able to pass the underlying bus pointer
// along to sd_bus calls, but we don't want to make it available for everyone.
// Define a class which can be inherited explicitly (intended for internal users
// only) to get the underlying bus pointer.
struct bus_friend
{
static busp_t get_busp(sdbusplus::bus::bus& b) noexcept
{
return b.get();
}
};
} // namespace details
} // namespace bus
using bus_t = bus::bus;
/** @brief Get the dbus bus from the message.
*
* @return The dbus bus.
*/
inline bus_t message_t::get_bus() const
{
sd_bus* b = nullptr;
b = _intf->sd_bus_message_get_bus(_msg.get());
return bus_t(b, _intf);
}
} // namespace sdbusplus
#ifdef __clang__
#pragma clang diagnostic pop
#endif