Files
sdbus-cpp/src/Connection.cpp

374 lines
10 KiB
C++
Raw Normal View History

2017-11-27 14:13:55 +01:00
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Connection.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Connection.h"
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
namespace sdbus { namespace internal {
Connection::Connection(Connection::BusType type)
: busType_(type)
{
2018-05-25 20:48:20 +02:00
auto bus = openBus(busType_);
2017-11-27 14:13:55 +01:00
bus_.reset(bus);
2018-05-25 20:48:20 +02:00
finishHandshake(bus);
2017-11-27 14:13:55 +01:00
notificationFd_ = createLoopNotificationDescriptor();
2017-11-27 14:13:55 +01:00
}
Connection::~Connection()
{
leaveProcessingLoop();
closeLoopNotificationDescriptor(notificationFd_);
2017-11-27 14:13:55 +01:00
}
void Connection::requestName(const std::string& name)
{
auto r = sd_bus_request_name(bus_.get(), name.c_str(), 0);
2018-05-25 20:48:20 +02:00
SDBUS_THROW_ERROR_IF(r < 0, "Failed to request bus name", -r);
2017-11-27 14:13:55 +01:00
}
void Connection::releaseName(const std::string& name)
{
auto r = sd_bus_release_name(bus_.get(), name.c_str());
2018-05-25 20:48:20 +02:00
SDBUS_THROW_ERROR_IF(r < 0, "Failed to release bus name", -r);
2017-11-27 14:13:55 +01:00
}
void Connection::enterProcessingLoop()
{
while (true)
{
auto processed = processPendingRequest();
2018-05-25 20:48:20 +02:00
if (processed)
continue; // Process next one
auto success = waitForNextRequest();
2018-05-25 20:48:20 +02:00
if (!success)
break; // Exit processing loop
if (success.asyncMsgsToProcess)
processAsynchronousMessages();
2017-11-27 14:13:55 +01:00
}
}
void Connection::enterProcessingLoopAsync()
{
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
}
void Connection::leaveProcessingLoop()
{
2018-05-25 20:48:20 +02:00
notifyProcessingLoopToExit();
joinWithProcessingLoop();
2017-11-27 14:13:55 +01:00
}
void* Connection::addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData )
{
sd_bus_slot *slot{};
2018-05-25 20:48:20 +02:00
2017-11-27 14:13:55 +01:00
auto r = sd_bus_add_object_vtable( bus_.get()
, &slot
, objectPath.c_str()
, interfaceName.c_str()
, static_cast<const sd_bus_vtable*>(vtable)
, userData );
2018-05-25 20:48:20 +02:00
SDBUS_THROW_ERROR_IF(r < 0, "Failed to register object vtable", -r);
2017-11-27 14:13:55 +01:00
return slot;
}
void Connection::removeObjectVTable(void* vtableHandle)
{
sd_bus_slot_unref((sd_bus_slot *)vtableHandle);
}
sdbus::MethodCall Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
2017-11-27 14:13:55 +01:00
{
sd_bus_message *sdbusMsg{};
2018-05-25 20:48:20 +02:00
// Returned message will become an owner of sdbusMsg
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); };
2017-11-27 14:13:55 +01:00
auto r = sd_bus_message_new_method_call( bus_.get()
, &sdbusMsg
, destination.c_str()
, objectPath.c_str()
, interfaceName.c_str()
, methodName.c_str() );
2018-05-25 20:48:20 +02:00
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method call", -r);
2017-11-27 14:13:55 +01:00
return MethodCall(sdbusMsg);
2017-11-27 14:13:55 +01:00
}
sdbus::Signal Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
2017-11-27 14:13:55 +01:00
{
sd_bus_message *sdbusSignal{};
2018-05-25 20:48:20 +02:00
// Returned message will become an owner of sdbusSignal
SCOPE_EXIT{ sd_bus_message_unref(sdbusSignal); };
2017-11-27 14:13:55 +01:00
auto r = sd_bus_message_new_signal( bus_.get()
, &sdbusSignal
, objectPath.c_str()
, interfaceName.c_str()
, signalName.c_str() );
2018-05-25 20:48:20 +02:00
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create signal", -r);
2017-11-27 14:13:55 +01:00
return Signal(sdbusSignal);
2017-11-27 14:13:55 +01:00
}
void* Connection::registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData )
{
sd_bus_slot *slot{};
2018-05-25 20:48:20 +02:00
2017-11-27 14:13:55 +01:00
auto filter = composeSignalMatchFilter(objectPath, interfaceName, signalName);
auto r = sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData);
2018-05-25 20:48:20 +02:00
SDBUS_THROW_ERROR_IF(r < 0, "Failed to register signal handler", -r);
2017-11-27 14:13:55 +01:00
return slot;
}
void Connection::unregisterSignalHandler(void* handlerCookie)
{
sd_bus_slot_unref((sd_bus_slot *)handlerCookie);
}
void Connection::sendReplyAsynchronously(const sdbus::MethodReply& reply)
{
std::lock_guard<std::mutex> guard(mutex_);
asyncReplies_.push(reply);
notifyProcessingLoop();
}
2017-11-27 14:13:55 +01:00
std::unique_ptr<sdbus::internal::IConnection> Connection::clone() const
{
return std::make_unique<sdbus::internal::Connection>(busType_);
}
2018-05-25 20:48:20 +02:00
sd_bus* Connection::openBus(Connection::BusType type)
{
static std::map<sdbus::internal::Connection::BusType, int(*)(sd_bus **)> busTypeToFactory
{
{sdbus::internal::Connection::BusType::eSystem, &sd_bus_open_system},
{sdbus::internal::Connection::BusType::eSession, &sd_bus_open_user}
};
sd_bus* bus{};
auto r = busTypeToFactory[type](&bus);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open bus", -r);
assert(bus != nullptr);
return bus;
}
void Connection::finishHandshake(sd_bus* bus)
{
// Process all requests that are part of the initial handshake,
// like processing the Hello message response, authentication etc.,
// to avoid connection authentication timeout in dbus daemon.
assert(bus != nullptr);
auto r = sd_bus_flush(bus);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to flush bus on opening", -r);
}
int Connection::createLoopNotificationDescriptor()
2018-05-25 20:48:20 +02:00
{
auto r = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create event object", -errno);
return r;
}
void Connection::closeLoopNotificationDescriptor(int fd)
2018-05-25 20:48:20 +02:00
{
close(fd);
}
void Connection::notifyProcessingLoop()
2018-05-25 20:48:20 +02:00
{
assert(notificationFd_ >= 0);
2018-05-25 20:48:20 +02:00
uint64_t value = 1;
write(notificationFd_, &value, sizeof(value));
}
void Connection::notifyProcessingLoopToExit()
{
exitLoopThread_ = true;
notifyProcessingLoop();
2018-05-25 20:48:20 +02:00
}
void Connection::joinWithProcessingLoop()
{
if (asyncLoopThread_.joinable())
asyncLoopThread_.join();
}
bool Connection::processPendingRequest()
2018-05-25 20:48:20 +02:00
{
auto bus = bus_.get();
2018-05-25 20:48:20 +02:00
assert(bus != nullptr);
int r = sd_bus_process(bus, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r);
return r > 0;
}
void Connection::processAsynchronousMessages()
{
std::lock_guard<std::mutex> guard(mutex_);
while (!asyncReplies_.empty())
{
auto reply = asyncReplies_.front();
asyncReplies_.pop();
reply.send();
}
}
Connection::WaitResult Connection::waitForNextRequest()
2018-05-25 20:48:20 +02:00
{
auto bus = bus_.get();
2018-05-25 20:48:20 +02:00
assert(bus != nullptr);
assert(notificationFd_ != 0);
2018-05-25 20:48:20 +02:00
auto r = sd_bus_get_fd(bus);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r);
auto sdbusFd = r;
r = sd_bus_get_events(bus);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus events", -r);
short int sdbusEvents = r;
uint64_t usec;
sd_bus_get_timeout(bus, &usec);
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {notificationFd_, POLLIN, 0}};
2018-05-25 20:48:20 +02:00
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
r = poll(fds, fdsCount, usec == (uint64_t) -1 ? -1 : (usec+999)/1000);
if (r < 0 && errno == EINTR)
return {true, false}; // Try again
2018-05-25 20:48:20 +02:00
SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno);
if (fds[1].revents & POLLIN)
{
if (exitLoopThread_)
return {false, false}; // Got exit notification
// Otherwise we have some async messages to process
return {false, true};
}
2018-05-25 20:48:20 +02:00
return {true, false};
2018-05-25 20:48:20 +02:00
}
2017-11-27 14:13:55 +01:00
std::string Connection::composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName )
{
std::string filter;
2018-05-25 20:48:20 +02:00
2017-11-27 14:13:55 +01:00
filter += "type='signal',";
filter += "interface='" + interfaceName + "',";
filter += "member='" + signalName + "',";
filter += "path='" + objectPath + "'";
2018-05-25 20:48:20 +02:00
2017-11-27 14:13:55 +01:00
return filter;
}
}}
namespace sdbus {
std::unique_ptr<sdbus::IConnection> createConnection()
{
return createSystemBusConnection();
}
std::unique_ptr<sdbus::IConnection> createConnection(const std::string& name)
{
return createSystemBusConnection(name);
}
std::unique_ptr<sdbus::IConnection> createSystemBusConnection()
{
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSystem);
}
std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name)
{
auto conn = createSystemBusConnection();
conn->requestName(name);
return conn;
}
std::unique_ptr<sdbus::IConnection> createSessionBusConnection()
{
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSession);
}
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name)
{
auto conn = createSessionBusConnection();
conn->requestName(name);
return conn;
}
}