Compare commits

...

11 Commits

13 changed files with 314 additions and 41 deletions

View File

@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.6)
project(sdbus-c++ VERSION 0.7.1 LANGUAGES C CXX)
project(sdbus-c++ VERSION 0.7.3 LANGUAGES C CXX)
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file

View File

@ -111,3 +111,11 @@ v0.7.1
- Resolved a few clang-tidy warnings and suggestions
- Extended the tutorial with info on standard D-Bus interfaces
- Added protected non-virtual destructor in generated *_proxy/*_adaptor classes
v0.7.2
- Rewrite UnixFd implementation from plain UnixFd struct to full-ownership-semantics UnixFd class
v0.7.3
- Add ability to integrate with external event loops
- Add getSenderName() method to Message
- Skip GetMachineId integration test case when /etc/machine-id is not available

View File

@ -6,19 +6,20 @@ Using sdbus-c++ library
1. [Introduction](#introduction)
2. [Integrating sdbus-c++ into your project](#integrating-sdbus-c-into-your-project)
3. [Solving libsystemd dependency](#solving-libsystemd-dependency)
4. [Header files and namespaces](#header-files-and-namespaces)
5. [Error signalling and propagation](#error-signalling-and-propagation)
6. [Design of sdbus-c++](#design-of-sdbus-c)
7. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
8. [An example: Number concatenator](#an-example-number-concatenator)
9. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
10. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
11. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
12. [Asynchronous server-side methods](#asynchronous-server-side-methods)
13. [Asynchronous client-side methods](#asynchronous-client-side-methods)
14. [Using D-Bus properties](#using-d-bus-properties)
15. [Standard D-Bus interfaces](#standard-d-bus-interfaces)
16. [Conclusion](#conclusion)
4. [Distributing sdbus-c++](#distributing-sdbus-c)
5. [Header files and namespaces](#header-files-and-namespaces)
6. [Error signalling and propagation](#error-signalling-and-propagation)
7. [Design of sdbus-c++](#design-of-sdbus-c)
8. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
9. [An example: Number concatenator](#an-example-number-concatenator)
10. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
11. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
12. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
13. [Asynchronous server-side methods](#asynchronous-server-side-methods)
14. [Asynchronous client-side methods](#asynchronous-client-side-methods)
15. [Using D-Bus properties](#using-d-bus-properties)
16. [Standard D-Bus interfaces](#standard-d-bus-interfaces)
17. [Conclusion](#conclusion)
Introduction
------------
@ -81,10 +82,32 @@ $ ninja libsystemd.so.0.26.0 # or another version number depending which system
### Building and distributing libsystemd as part of sdbus-c++
sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download, build and integrate libsystemd as a static library into sdbus-c++ for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd enviroments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes `meson`, `ninja`, `git` programs and mainly libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Also when distributing, make sure these dependency libraries are installed on the production machine. (Contributors willing to help with bringing sdbus-c++ to popular package systems are welcome.)
sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd enviroments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes `meson`, `ninja`, `git` programs and mainly libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Also when distributing, make sure these dependency libraries are installed on the production machine.
You may additionally set the `LIBSYSTEMD_VERSION` configuration flag to fine-tune the version of systemd to be taken in. (The default value is 242).
Distributing sdbus-c++
----------------------
sdbus-c++ recipes for Yocto are available. Contributors willing to help with bringing sdbus-c++ to popular package systems are welcome.
### Yocto
There are sdbus-c++ recipes for already released Yocto versions (for versions Sumo and newer) available in the meta-oe layer of meta-openembedded fork at Kistler-Group:
* [sdbus-c++ recipes for Yocto Sumo](https://github.com/Kistler-Group/meta-openembedded/tree/sumo-with-sdbus-c%2B%2B/meta-oe/recipes-core/sdbus-c%2B%2B)
* [sdbus-c++ recipes for Yocto Thud](https://github.com/Kistler-Group/meta-openembedded/tree/thud-with-sdbus-c%2B%2B/meta-oe/recipes-core/sdbus-c%2B%2B)
* [sdbus-c++ recipes for Yocto Warrior](https://github.com/Kistler-Group/meta-openembedded/tree/warrior-with-sdbus-c%2B%2B/meta-oe/recipes-core/sdbus-c%2B%2B)
Also, there is currently a pull request pending that pushes there recipes upstream -- to the OpenEmbedded project, so they will be officially available for the upcoming Yocto release.
There are two recipes:
* One for sdbus-c++ itself. It detects whether systemd feature is turned on in the poky linux configuration. If so, it simply depends on systemd and makes use of libsystemd shared library available in the target system. Otherwise it automatically downloads and builds libsystemd static library and makes it an opaque part of sdbus-c++ shared library. The recipe also supports ptest.
* One for sdbus-c++ native tools, namely sdbus-c++ code generator to generate C++ adaptor and proxy binding classes.
Tip: If you get `ERROR: Program or command 'getent' not found or not executable` when building sdbus-c++ in Yocto, please make sure you've added `getent` to `HOSTTOOLS`. For example, you can add `HOSTTOOLS_NONFATAL += "getent"` into your local.conf file.
Header files and namespaces
---------------------------

View File

@ -46,6 +46,13 @@ namespace sdbus {
class IConnection
{
public:
struct PollData
{
int fd;
short int events;
uint64_t timeout_usec;
};
virtual ~IConnection() = default;
/*!
@ -103,6 +110,41 @@ namespace sdbus {
* @throws sdbus::Error in case of failure
*/
virtual void addObjectManager(const std::string& objectPath) = 0;
/*!
* @brief Returns parameters you can pass to poll
*
* To integrate sdbus with your app's own custom event handling system
* (without the requirement of an extra thread), you can use this
* method to query which file descriptors, poll events and timeouts you
* should add to your app's poll call in your main event loop. If these
* file descriptors signal, then you should call processPendingRequest
* to process the event. This means that all of sdbus's callbacks will
* arrive on your app's main event thread (opposed to on a thread created
* by sdbus-c++). If you are unsure what this all means then use
* enterProcessingLoop() or enterProcessingLoopAsync() instead.
*
* To integrate sdbus-c++ into a gtk app, pass the file descriptor returned
* by this method to g_main_context_add_poll.
*
* @throws sdbus::Error in case of failure
*/
virtual PollData getProcessLoopPollData() = 0;
/*!
* @brief Process a pending request
*
* Processes a single dbus event. All of sdbus-c++'s callbacks will be called
* from within this method. This method should ONLY be used in conjuction
* with getProcessLoopPollData(). enterProcessingLoop() and
* enterProcessingLoopAsync() will call this method for you, so there is no
* need to call it when using these. If you are unsure what this all means then
* don't use this method.
*
* @returns true if an event was processed
* @throws sdbus::Error in case of failure
*/
virtual bool processPendingRequest() = 0;
};
/*!

View File

@ -133,6 +133,7 @@ namespace sdbus {
std::string getInterfaceName() const;
std::string getMemberName() const;
std::string getSender() const;
void peekType(std::string& type, std::string& contents) const;
bool isValid() const;
bool isEmpty() const;

View File

@ -34,6 +34,7 @@
#include <typeinfo>
#include <memory>
#include <tuple>
#include <unistd.h>
namespace sdbus {
@ -176,25 +177,104 @@ namespace sdbus {
using std::string::operator=;
};
struct adopt_fd_t { explicit adopt_fd_t() = default; };
#ifdef __cpp_inline_variables
inline constexpr adopt_fd_t adopt_fd{};
#else
constexpr adopt_fd_t adopt_fd{};
#endif
/********************************************//**
* @struct UnixFd
*
* Representation of Unix file descriptor D-Bus type
* UnixFd is a representation of file descriptor D-Bus type that owns
* the underlying fd, provides access to it, and closes the fd when
* the UnixFd goes out of scope.
*
* UnixFd can be default constructed (owning invalid fd), or constructed from
* an explicitly provided fd by either duplicating or adopting that fd as-is.
*
***********************************************/
struct UnixFd
class UnixFd
{
int fd_ = -1;
public:
UnixFd() = default;
UnixFd(int fd)
: fd_(fd)
{}
operator int() const
explicit UnixFd(int fd)
: fd_(::dup(fd))
{
}
UnixFd(int fd, adopt_fd_t)
: fd_(fd)
{
}
UnixFd(const UnixFd& other)
{
*this = other;
}
UnixFd& operator=(const UnixFd& other)
{
close();
fd_ = ::dup(other.fd_);
return *this;
}
UnixFd(UnixFd&& other)
{
*this = std::move(other);
}
UnixFd& operator=(UnixFd&& other)
{
close();
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
~UnixFd()
{
close();
}
int get() const
{
return fd_;
}
void reset(int fd = -1)
{
*this = UnixFd{fd};
}
void reset(int fd, adopt_fd_t)
{
*this = UnixFd{fd, adopt_fd};
}
int release()
{
auto fd = fd_;
fd_ = -1;
return fd;
}
bool isValid() const
{
return fd_ >= 0;
}
private:
void close()
{
if (fd_ >= 0)
::close(fd_);
}
int fd_ = -1;
};
}

View File

@ -108,6 +108,15 @@ void Connection::leaveProcessingLoop()
joinWithProcessingLoop();
}
sdbus::IConnection::PollData Connection::getProcessLoopPollData()
{
ISdBus::PollData pollData;
auto r = iface_->sd_bus_get_poll_data(bus_.get(), &pollData);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus poll data", -r);
return {pollData.fd, pollData.events, pollData.timeout_usec};
}
const ISdBus& Connection::getSdBusInterface() const
{
return *iface_.get();
@ -328,11 +337,9 @@ void Connection::joinWithProcessingLoop()
bool Connection::processPendingRequest()
{
auto bus = bus_.get();
assert(bus != nullptr);
int r = iface_->sd_bus_process(bus, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r);
return r > 0;
@ -345,15 +352,12 @@ bool Connection::waitForNextRequest()
assert(bus != nullptr);
assert(loopExitFd_ != 0);
ISdBus::PollData sdbusPollData;
auto r = iface_->sd_bus_get_poll_data(bus, &sdbusPollData);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus poll data", -r);
auto sdbusPollData = getProcessLoopPollData();
struct pollfd fds[] = {{sdbusPollData.fd, sdbusPollData.events, 0}, {loopExitFd_, POLLIN, 0}};
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
auto timeout = sdbusPollData.timeout_usec == (uint64_t) -1 ? (uint64_t)-1 : (sdbusPollData.timeout_usec+999)/1000;
r = poll(fds, fdsCount, timeout);
auto r = poll(fds, fdsCount, timeout);
if (r < 0 && errno == EINTR)
return true; // Try again

View File

@ -57,6 +57,8 @@ namespace sdbus { namespace internal {
void enterProcessingLoop() override;
void enterProcessingLoopAsync() override;
void leaveProcessingLoop() override;
sdbus::IConnection::PollData getProcessLoopPollData() override;
bool processPendingRequest() override;
void addObjectManager(const std::string& objectPath) override;
SlotPtr addObjectManager(const std::string& objectPath, void* /*dummy*/) override;
@ -99,7 +101,6 @@ namespace sdbus { namespace internal {
void finishHandshake(sd_bus* bus);
static int createProcessingLoopExitDescriptor();
static void closeProcessingLoopExitDescriptor(int fd);
bool processPendingRequest();
bool waitForNextRequest();
static std::string composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName

View File

@ -218,7 +218,8 @@ Message& Message::operator<<(const Signature &item)
Message& Message::operator<<(const UnixFd &item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UNIX_FD, &item.fd_);
auto fd = item.get();
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UNIX_FD, &fd);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a UnixFd value", -r);
return *this;
@ -394,12 +395,15 @@ Message& Message::operator>>(Signature &item)
Message& Message::operator>>(UnixFd &item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UNIX_FD, &item.fd_);
int fd = -1;
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UNIX_FD, &fd);
if (r == 0)
ok_ = false;
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a UnixFd value", -r);
item.reset(fd);
return *this;
}
@ -586,6 +590,11 @@ std::string Message::getMemberName() const
return sd_bus_message_get_member((sd_bus_message*)msg_);
}
std::string Message::getSender() const
{
return sd_bus_message_get_sender((sd_bus_message*)msg_);
}
void Message::peekType(std::string& type, std::string& contents) const
{
char typeSig;

View File

@ -43,6 +43,8 @@
#include <fstream>
#include <future>
#include <unistd.h>
using ::testing::Eq;
using ::testing::DoubleEq;
using ::testing::Gt;
@ -213,7 +215,7 @@ TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully)
TEST_F(SdbusTestObject, CallsMethodWithUnixFdSuccesfully)
{
auto resUnixFd = m_proxy->getUnixFd();
ASSERT_THAT(resUnixFd, Gt(UNIX_FD_VALUE));
ASSERT_THAT(resUnixFd.get(), Gt(UNIX_FD_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully)
@ -431,6 +433,13 @@ TEST_F(SdbusTestObject, PingsViaPeerInterface)
TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface)
{
// If /etc/machine-id does not exist in your system (which is very likely because you have
// a non-systemd Linux), org.freedesktop.DBus.Peer.GetMachineId() will not work. To solve
// this, you can create /etc/machine-id yourself as symlink to /var/lib/dbus/machine-id,
// and then org.freedesktop.DBus.Peer.GetMachineId() will start to work.
if (::access("/etc/machine-id", F_OK) == -1)
GTEST_SKIP() << "/etc/machine-id file does not exist, GetMachineId() will not work";
ASSERT_NO_THROW(m_proxy->GetMachineId());
}
@ -468,7 +477,7 @@ TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties)
std::atomic<bool> signalReceived{false};
m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties
, const std::vector<std::string>& invalidatedProperties )
, const std::vector<std::string>& /*invalidatedProperties*/ )
{
EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME));
EXPECT_THAT(changedProperties, SizeIs(1));

View File

@ -160,7 +160,7 @@ protected:
}
sdbus::UnixFd getUnixFd() const
{
return UNIX_FD_VALUE;
return sdbus::UnixFd{UNIX_FD_VALUE};
}
ComplexType getComplex() const

View File

@ -131,7 +131,7 @@ TEST(AMessage, CanCarryAUnixFd)
{
auto msg = sdbus::createPlainMessage();
sdbus::UnixFd dataWritten = 0;
sdbus::UnixFd dataWritten{0};
msg << dataWritten;
msg.seal();
@ -139,7 +139,7 @@ TEST(AMessage, CanCarryAUnixFd)
sdbus::UnixFd dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Gt(dataWritten));
ASSERT_THAT(dataRead.get(), Gt(dataWritten.get()));
}
TEST(AMessage, CanCarryAVariant)

View File

@ -29,8 +29,10 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
#include <sys/eventfd.h>
using ::testing::Eq;
using ::testing::Gt;
using namespace std::string_literals;
namespace
@ -242,9 +244,103 @@ TEST(ASignature, CanBeConstructedFromStdString)
ASSERT_THAT(sdbus::Signature{aSignature}, Eq(aSignature));
}
TEST(AUnixFd, CanBeConstructedFromInt)
TEST(AUnixFd, DuplicatesAndOwnsFdUponStandardConstruction)
{
int fd{2};
auto fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
ASSERT_THAT((int)sdbus::UnixFd{fd}, Eq(fd));
EXPECT_THAT(sdbus::UnixFd{fd}.get(), Gt(fd));
EXPECT_THAT(::close(fd), Eq(0));
}
TEST(AUnixFd, AdoptsAndOwnsFdAsIsUponAdoptionConstruction)
{
auto fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
EXPECT_THAT(sdbus::UnixFd(fd, sdbus::adopt_fd).get(), Eq(fd));
EXPECT_THAT(::close(fd), Eq(-1));
}
TEST(AUnixFd, DuplicatesFdUponCopyConstruction)
{
sdbus::UnixFd unixFd(::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK));
sdbus::UnixFd unixFdCopy{unixFd};
EXPECT_THAT(unixFdCopy.get(), Gt(unixFd.get()));
}
TEST(AUnixFd, TakesOverFdUponMoveConstruction)
{
auto fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
sdbus::UnixFd unixFd(fd, sdbus::adopt_fd);
sdbus::UnixFd unixFdNew{std::move(unixFd)};
EXPECT_FALSE(unixFd.isValid());
EXPECT_THAT(unixFdNew.get(), Eq(fd));
}
TEST(AUnixFd, ClosesFdProperlyUponDestruction)
{
int fd, fdCopy;
{
fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
sdbus::UnixFd unixFd(fd, sdbus::adopt_fd);
auto unixFdNew = std::move(unixFd);
auto unixFdCopy = unixFdNew;
fdCopy = unixFdCopy.get();
}
EXPECT_THAT(::close(fd), Eq(-1));
EXPECT_THAT(::close(fdCopy), Eq(-1));
}
TEST(AUnixFd, DoesNotCloseReleasedFd)
{
auto fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
int fdReleased;
{
sdbus::UnixFd unixFd(fd, sdbus::adopt_fd);
fdReleased = unixFd.release();
EXPECT_FALSE(unixFd.isValid());
}
EXPECT_THAT(fd, Eq(fdReleased));
EXPECT_THAT(::close(fd), Eq(0));
}
TEST(AUnixFd, ClosesFdOnReset)
{
auto fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
sdbus::UnixFd unixFd(fd, sdbus::adopt_fd);
unixFd.reset();
EXPECT_FALSE(unixFd.isValid());
EXPECT_THAT(::close(fd), Eq(-1));
}
TEST(AUnixFd, DuplicatesNewFdAndClosesOriginalFdOnReset)
{
auto fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
sdbus::UnixFd unixFd(fd, sdbus::adopt_fd);
auto newFd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
unixFd.reset(newFd);
EXPECT_THAT(unixFd.get(), Gt(newFd));
EXPECT_THAT(::close(fd), Eq(-1));
EXPECT_THAT(::close(newFd), Eq(0));
}
TEST(AUnixFd, TakesOverNewFdAndClosesOriginalFdOnAdoptingReset)
{
auto fd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
sdbus::UnixFd unixFd(fd, sdbus::adopt_fd);
auto newFd = ::eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
unixFd.reset(newFd, sdbus::adopt_fd);
EXPECT_THAT(unixFd.get(), Eq(newFd));
EXPECT_THAT(::close(fd), Eq(-1));
}