I'm trying to write a program that uses DBus to interact with BlueZ. From the information I have been able to gather from the DBus docs, BlueZ DBus API docs, and various forums, my thought process was as follows:
hci0
)org.freedesktop.DBus.ObjectManager.GetManagedObjects()
to find available devices.So I have wrote the following code:
#include <...>
#include <dbus-1.0/dbus/dbus.h>
struct DBusDeleter
{
void operator()(DBusConnection *connection) {dbus_connection_unref(connection);}
void operator()(DBusMessage *msg) {dbus_message_unref(msg);}
void operator()(DBusError *err) {dbus_error_free(err);}
};
void print_managed_objects(std::unique_ptr<DBusMessage, DBusDeleter> message)
{
DBusMessageIter message_iter;
dbus_message_iter_init(message.get(), &message_iter);
std::cout << (char) dbus_message_iter_get_arg_type(&message_iter) << '\n';
DBusMessageIter dict_iter;
dbus_message_iter_recurse(&message_iter, &dict_iter);
std::cout << (char) dbus_message_iter_get_arg_type(&dict_iter) << '\n';
while(dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY)
{
DBusMessageIter entry_iter;
dbus_message_iter_recurse(&dict_iter, &entry_iter);
char * object_path;
dbus_message_iter_get_basic(&entry_iter, object_path);
std::cout << object_path << "\n";
std::cout << (char) dbus_message_iter_get_arg_type(&entry_iter) << '\n';
dbus_message_iter_next(&dict_iter);
}
std::cout << std::flush;
}
int main()
{
std::unique_ptr<DBusError, DBusDeleter> dbus_error(new DBusError);
dbus_error_init(dbus_error.get());
std::shared_ptr<DBusConnection> conn(dbus_bus_get(DBUS_BUS_SYSTEM, dbus_error.get()), DBusDeleter());
if (dbus_error_is_set(dbus_error.get()))
{
std::cout << "DBus Error: " << dbus_error->message << std::endl;
return 1;
}
std::string service_name = "org.bluez";
std::unique_ptr<DBusMessage, DBusDeleter> msg, reply;
// Start Device Discovery
msg.reset(dbus_message_new_method_call(service_name.c_str(), "/org/bluez/hci0", "org.bluez.Adapter1", "StartDiscovery"));
reply.reset(nullptr);
uint32_t serial = 1;
dbus_connection_send(conn.get(), msg.get(), &serial);
if (dbus_error_is_set(dbus_error.get()))
{
std::cout << "BlueZ Error (Failed to start discovery): " << dbus_error->message << std::endl;
return 1;
}
std::chrono::duration<int64_t> scan_duration = std::chrono::seconds(10);
std::chrono::_V2::system_clock::time_point start_time = std::chrono::system_clock::now();
msg.reset(dbus_message_new_method_call(service_name.c_str(), "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"));
while (start_time + scan_duration >= std::chrono::system_clock::now())
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
reply = std::unique_ptr<DBusMessage, DBusDeleter>(dbus_connection_send_with_reply_and_block(conn.get(), msg.get(), -1, dbus_error.get()));
if (dbus_error_is_set(dbus_error.get()))
{
std::cout << "error with reply: " << dbus_error->message << "\n";
break;
}
print_managed_objects(std::move(reply));
if (reply == nullptr) {
std::cout << "All Good!\n";
continue;
}
std::cout << "Something went wrong!\n";
break;
}
// more code...
}
The issue happens right around the print_managed_objects
function call. When the function returns, I would expect that since the print_managed_objects
function has already consumed the message as I had gave it ownership of the message, that everything would be okay, but the check for nullptr
fails on the original unique_ptr
that was moved from.
This means that the reply
pointer still has a reference to the message which was destroyed after print_managed_objects
returned, and when resetting the pointer in the next iteration of the loop, the program would crash because DBus would spit out this error:
dbus[15920]: arguments to dbus_message_unref() were incorrect, assertion "message->generation == _dbus_current_generation" failed in file dbus-message.c line 1728
My question is why is reply
's internal pointer not set to nullptr
after the move? According to another Stack Overflow question, calling std::move()
on a unique pointer will set that pointer's internal pointer to nullptr
.
Edit: I had left out lines in the print_managed_objects
function that I thought I could leave out for a minimal reproducible example, but those lines were actually necessary for the error to occur. Specifically, these lines:
char * object_path;
dbus_message_iter_get_basic(&entry_iter, object_path);
std::cout << *object_path << "\n";
Here is an issue.
char * object_path;
dbus_message_iter_get_basic(&entry_iter, object_path);
std::cout << *object_path << "\n";
You pass the uninitialized pointer to the function. This is undefined behavior. You seem to get corrupted stack data in your case.
std::cout << *object_path << "\n";
I don't know what you expect there. As for the 3rd line, you likely expect a single char data. The code should be
char object_path;
dbus_message_iter_get_basic(&entry_iter, &object_path);
std::cout << object_path << "\n";
&object_path
passes the address of the char storage.
The top code is showing
std::cout << object_path << "\n"
So, I am confused by your code.
A similar example is shown in the manual. You could easy follow it instead of trials. dbus_message_iter_get_basic().