├── .gitignore ├── Dockerfile ├── LICENSE.mit ├── README.md └── dbus.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ######### 2 | # Created and copyrighted by Zachary J. Fields. Offered as open source under the MIT License (MIT). 3 | # 4 | # Related Blog Post: https://makercrew.com/404 5 | ######### 6 | 7 | # Base Image 8 | FROM alpine:latest 9 | 10 | WORKDIR /root/ 11 | 12 | # Add Required Packages [Layer 1] 13 | RUN apk update && \ 14 | apk add \ 15 | ca-certificates \ 16 | dbus \ 17 | dbus-dev \ 18 | g++ \ 19 | git 20 | 21 | # Download Sources [Layer 2] 22 | RUN cd ~ && \ 23 | git clone https://github.com/makercrew/dbus-sample.git --recursive && \ 24 | cd dbus-sample/ && \ 25 | g++ dbus.cpp -std=c++0x $(pkg-config dbus-1 --cflags) -ldbus-1 -Werror -Wall -Wextra 26 | 27 | # Verify Build and Install 28 | CMD dbus-daemon --system --nofork 29 | -------------------------------------------------------------------------------- /LICENSE.mit: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-09-12 Zachary J. Fields 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Investigating D-Bus on GNU/Linux 2 | ================================ 3 | 4 | D-Bus is an integral part of the Linux operating system and in true Linux fashion is heavily used and poorly documented. After hours of scouring the internet, I have pulled together some resources that start to paint a more complete picture of D-Bus, the problem D-Bus solves and how to interact with it programatically (in C/C++). 5 | 6 | D-Bus at its essence is a message passing system that facilitates interprocess communication and generically abstracts a service's functionality. This allows a service to provide functionality and information in such a way that it can be accessed across process boundaries, language boundaries, licensing restrictions and even network boundaries! 7 | 8 | Hop on the bus, Gus 9 | ------------------- 10 | 11 | As you may have guessed, D-Bus behaves like a bus. As we all know, a bus is of no use, unless you are connected. In order to get connected, you will need to execute the following code. 12 | 13 | ```c++ 14 | DBusConnection * dbus_conn = nullptr; 15 | DBusError dbus_error; 16 | 17 | // Initialize D-Bus error 18 | ::dbus_error_init(&dbus_error); 19 | 20 | // Connect to D-Bus 21 | dbus_conn = ::dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error); 22 | std::cout << "Connected to D-Bus as \"" << ::dbus_bus_get_unique_name(dbus_conn) << "\"." << std::endl; 23 | ``` 24 | 25 | Bango, now you're on the bus! Now you have access to every service connected to D-Bus (i.e. Bluetooth, network manager, system power information, etc...). The services on D-Bus are exposed via interfaces, which describe methods, events and properties. If you are familiar with object-oriented programming this should all be very intuitive. 26 | 27 | > ***PROTIP:** If you are an Ubuntu user, and you would like to see the D-Bus mechanism in action, then download [D-Feet](https://apps.ubuntu.com/cat/applications/d-feet/) from the Ubuntu App Store and you can easily navigate through the services exposed via D-Bus.* 28 | 29 | #### See Also 30 | 31 | - [D-Bus Bus API](https://dbus.freedesktop.org/doc/api/html/group__DBusBus.html#ga8a9024c78c4ea89b6271f19dbc7861b2) 32 | 33 | Operator, well could you help me place this call? 34 | ------------------------------------------------- 35 | 36 | All D-Bus services should provide an interface called `Introspectable` with a single method called `Introspect` (*if you are familiar with **DCOM** on Windows, then you should feel right at home*). This allows you to recurse through each service made available via D-Bus. You are able to discover all methods, properties and events, by using the interfaces defined in the XML string resulting from the call to `Introspect`. 37 | 38 | The easiest place to start is to query D-Bus itself. First, you will need to formulate a message for the D-Bus daemon (the operator) to pass on to the D-Bus service. You can accomplish this call, using the following code. 39 | 40 | ```c++ 41 | DBusMessage * dbus_msg = nullptr; 42 | DBusMessage * dbus_reply = nullptr; 43 | 44 | // Compose remote procedure call 45 | dbus_msg = ::dbus_message_new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus.Introspectable", "Introspect"); 46 | 47 | // Invoke remote procedure call, block for response 48 | dbus_reply = ::dbus_connection_send_with_reply_and_block(dbus_conn, dbus_msg, DBUS_TIMEOUT_USE_DEFAULT, &dbus_error); 49 | ``` 50 | 51 | Eureka! You've just communcated with the system via D-Bus. Take a step back and think about what you've accomplished so far. You have just queried the system! The same pattern will give to access to nearly every service on the system (i.e. bluetooth). 52 | 53 | #### See Also 54 | 55 | - [D-Bus Connection API](https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html#ga8d6431f17a9e53c9446d87c2ba8409f0) 56 | - [D-Bus Message API](https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html#gad8953f53ceea7de81cde792e3edd0230) 57 | 58 | My baby, just-a wrote me a letter 59 | --------------------------------- 60 | 61 | So far we have connected to the D-Bus (daemon), we have queried the D-Bus service and we have received a response. *HOWEVER*, we have a message that we can't quite understand. That's because it was *marshalled* into a binary format that is easier and more efficient to send as a message (but more difficult for humans to read). 62 | 63 | Now it's time to decode the response message and view the data encoded within. Luckily, the D-Bus developers have provided all the tools necessary to break open the message and get the contents. Use the following code to make sense of the response. 64 | 65 | ```c++ 66 | const char * dbus_result = nullptr; 67 | 68 | // Parse response 69 | ::dbus_message_get_args(dbus_reply, &dbus_error, DBUS_TYPE_STRING, &dbus_result, DBUS_TYPE_INVALID); 70 | 71 | // Work with the results of the remote procedure call 72 | std::cout << "Introspection Result:" << std::endl << std::endl; 73 | std::cout << dbus_result << std::endl; 74 | ``` 75 | 76 | Now we've pulled the string out of the message. The string is XML detailing the interfaces available via the D-Bus. It describes interfaces, methods and their parameters, properties and signals. Below is a snippet of the response from `Introspect`. 77 | 78 | ```xml 79 | ... 80 | 81 | 82 | 83 | 84 | 85 | ... 86 | ``` 87 | 88 | The snippet above is the `Introspectable` interface we used to get this information. As you can see, it details any available methods (i.e. `Introspect`) along with the parameters and data-types they require (none in this case). Notice that even though we didn't have any parameters to the call, we see the result described as an `out` argument of type `s` (or string). 89 | 90 | #### See Also 91 | 92 | - [D-Bus Message API](https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html#gad8953f53ceea7de81cde792e3edd0230) 93 | 94 | Take out the papers and the trash 95 | --------------------------------- 96 | 97 | Well the "hard part" is behind us, and now it's time to clean up. Thinking back on the resources we have allocated, we made a connection, we created a message, we received a message and pulled a string from that message. Let see how we clean up our mess. 98 | 99 | At some level, everything involving D-Bus is shared, and at the very least, a message must be shared between two process. Fortunately, we are only responsible for our reference to the memory (even if we created it), which greatly simplifies things. We no longer have to be concerned about when the memory comes or goes. We only need worry about letting the system know when we are finished using the memory (think `std::shared_pointer`). The following code lets the system know we have finished using the messages. 100 | 101 | ```c++ 102 | ::dbus_message_unref(dbus_msg); 103 | ::dbus_message_unref(dbus_reply); 104 | ``` 105 | 106 | So what about the string we pulled out of the message? Apparently, it gets cleaned up with the message. 107 | 108 | D-Bus documentation states: 109 | > "Except for string arrays, the returned values are constant; do not free them. They point into the DBusMessage." 110 | 111 | Fair enough, but what about the connection to D-Bus itself? Well, we are attaching to a "shared connection", therefore we are not allowed to close it. In fact, if you attempt to close the connection with the `dbus_connection_close` api, then the library throws you a nice error message (_shown below_); to slap your hand. 112 | 113 | process nnnn: Applications must not close shared connections - see dbus_connection_close() docs. This is a bug in the application. 114 | 115 | Instead of closing the connection, we will simply _unreference_ the connection as we did with the messages. 116 | 117 | ```c++ 118 | ::dbus_connection_unref(dbus_conn); 119 | ``` 120 | 121 | #### See Also 122 | 123 | - [D-Bus Message API](https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html#gad8953f53ceea7de81cde792e3edd0230) 124 | 125 | We are family 126 | ------------- 127 | 128 | When I was learning about D-Bus, I kept running into the following similarly named libraries. They are all in the same domain (D-Bus), and they really muddy the waters when you're trying to learn the basics. 129 | 130 | - [`gdbus`](https://www.freedesktop.org/software/gstreamer-sdk/data/docs/2012.5/gio/gdbus.html) - GNOME's D-Bus wrapper/helper library 131 | - [`qtdbus`](http://doc.qt.io/qt-5/qtdbus-index.html) - QT D-Bus wrapper/helper library 132 | - [`kdbus`](https://www.freedesktop.org/wiki/Software/systemd/kdbus/) - A kernel side D-Bus transport layer 133 | 134 | The takeaway here, is to learn about D-Bus first. Once you have a firm grasp about the function D-Bus performs, then you can learn about these additional layers that sit atop and beneath D-Bus to make it "easier to use" or "faster" as the case may be. 135 | 136 | Summary 137 | ------- 138 | 139 | At this point, you should have some insight into what D-Bus is, a basic understanding of how to interact with it and even a little sample code to help get you started! 140 | 141 | D-Bus in an amazing tool! Not only are you able to access a wealth of system services and resources, but there are also several less obvious benefits. There are bindings in several different high-level languages (i.e. python, javascript, etc...), allowing you to break through language boundaries. Not to mention, the fact that you are invoking a service as a binary, which also has the neat side-effect of allowing you to bypass GNU licensing restrictions! 142 | 143 | With this foundation, you now possess the building blocks necessary to interact with BlueZ via D-Bus. Continue on, by reading my follow up post [GNU/Linux BLE via BlueZ](https://makercrew.com/404). It will guide you through the BlueZ D-Bus interface, and you will be well on your way to programmatically interacting with all your bluetooth devices! 144 | 145 | _________________________________ 146 | 147 | D-Bus API 148 | --------- 149 | 150 | - [Bus API](https://dbus.freedesktop.org/doc/api/html/group__DBusBus.html#ga8a9024c78c4ea89b6271f19dbc7861b2) 151 | - [Connection API](https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html#ga8d6431f17a9e53c9446d87c2ba8409f0) 152 | - [Error API](https://dbus.freedesktop.org/doc/api/html/group__DBusErrors.html#ga8937f0b7cdf8554fa6305158ce453fbe) 153 | - [Message API](https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html#gad8953f53ceea7de81cde792e3edd0230) 154 | 155 | More Info 156 | --------- 157 | 158 | - [D-Bus Tutorial](https://dbus.freedesktop.org/doc/dbus-tutorial.html) 159 | - [D-Bus Specification](https://dbus.freedesktop.org/doc/dbus-specification.html) 160 | - [Standard Interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces) 161 | 162 | Other Examples 163 | -------------- 164 | 165 | - [Using the D-Bus C API](http://www.matthew.ath.cx/misc/dbus) 166 | - [Compiling D-Bus Programs](https://stackoverflow.com/questions/14263390/how-to-compile-a-basic-d-bus-glib-example) 167 | 168 | Applications 169 | ------------ 170 | 171 | - [D-Feet](https://apps.ubuntu.com/cat/applications/d-feet/) - A D-Bus exploration application 172 | 173 | Code 174 | ---- 175 | 176 | ```c++ 177 | /* Created and copyrighted by Zachary J. Fields. Offered as open source under the MIT License (MIT). */ 178 | 179 | /* 180 | * Filename: dbus.cpp 181 | * 182 | * Purpose: A simple sample of a D-Bus interaction. 183 | */ 184 | 185 | #include 186 | 187 | #include 188 | #include 189 | #include 190 | 191 | int 192 | main ( 193 | int argc, 194 | char * argv[] 195 | ) { 196 | (void)argc; 197 | (void)argv; 198 | DBusError dbus_error; 199 | DBusConnection * dbus_conn = nullptr; 200 | DBusMessage * dbus_msg = nullptr; 201 | DBusMessage * dbus_reply = nullptr; 202 | const char * dbus_result = nullptr; 203 | 204 | // Initialize D-Bus error 205 | ::dbus_error_init(&dbus_error); 206 | 207 | // Connect to D-Bus 208 | if ( nullptr == (dbus_conn = ::dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error)) ) { 209 | ::perror(dbus_error.name); 210 | ::perror(dbus_error.message); 211 | 212 | // Compose remote procedure call 213 | } else if ( nullptr == (dbus_msg = ::dbus_message_new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus.Introspectable", "Introspect")) ) { 214 | ::dbus_connection_unref(dbus_conn); 215 | ::perror("ERROR: ::dbus_message_new_method_call - Unable to allocate memory for the message!"); 216 | 217 | // Invoke remote procedure call, block for response 218 | } else if ( nullptr == (dbus_reply = ::dbus_connection_send_with_reply_and_block(dbus_conn, dbus_msg, DBUS_TIMEOUT_USE_DEFAULT, &dbus_error)) ) { 219 | ::dbus_message_unref(dbus_msg); 220 | ::dbus_connection_unref(dbus_conn); 221 | ::perror(dbus_error.name); 222 | ::perror(dbus_error.message); 223 | 224 | // Parse response 225 | } else if ( !::dbus_message_get_args(dbus_reply, &dbus_error, DBUS_TYPE_STRING, &dbus_result, DBUS_TYPE_INVALID) ) { 226 | ::dbus_message_unref(dbus_msg); 227 | ::dbus_message_unref(dbus_reply); 228 | ::dbus_connection_unref(dbus_conn); 229 | ::perror(dbus_error.name); 230 | ::perror(dbus_error.message); 231 | 232 | // Work with the results of the remote procedure call 233 | } else { 234 | std::cout << "Connected to D-Bus as \"" << ::dbus_bus_get_unique_name(dbus_conn) << "\"." << std::endl; 235 | std::cout << "Introspection Result:" << std::endl << std::endl; 236 | std::cout << dbus_result << std::endl; 237 | ::dbus_message_unref(dbus_msg); 238 | ::dbus_message_unref(dbus_reply); 239 | 240 | /* 241 | * Applications must not close shared connections - 242 | * see dbus_connection_close() docs. This is a bug in the application. 243 | */ 244 | //::dbus_connection_close(dbus_conn); 245 | 246 | // When using the System Bus, unreference 247 | // the connection instead of closing it 248 | ::dbus_connection_unref(dbus_conn); 249 | } 250 | 251 | return 0; 252 | } 253 | 254 | /* Created and copyrighted by Zachary J. Fields. Offered as open source under the MIT License (MIT). */ 255 | ``` 256 | 257 | #### Compile Command 258 | 259 | ```bash 260 | $ g++ dbus.cpp -std=c++0x $(pkg-config dbus-1 --cflags) -ldbus-1 -Werror -Wall -Wextra 261 | ``` 262 | 263 | > ***NOTE:** You may have noticed the funky `$(pkg-config dbus-1 --cflags)` string in the compile arguments. This allows D-Bus to support different configurations on different systems. I ran into this exact scenario while creating the Docker container for an Alpine system, after developing on Ubuntu.* 264 | 265 | #### Docker Environment 266 | 267 | If you have Docker installed, then I have provided a container. The container houses all the necessary dependencies (including the D-Bus daemon), and allows you to play with D-Bus and the sample, without having to make any modifications to your host machine. 268 | 269 | ##### Create D-Bus Environment 270 | 271 | ```bash 272 | $ docker run -d --name dbus-sample --rm makercrew/dbus-sample 273 | ``` 274 | 275 | For those of you who are unfamiliar with Docker, I would like to assure you this is one of the safest bits of sample code you can run. The container provides a sandbox that traps the application. We have not specified any flags that would break the sandbox, thus providing access to your machine (_i.e. `--privileged`, `--volume`, `--network host`, etc...). 276 | 277 | The specified flags provide the following behavior: 278 | 279 | - `-d` - the process associated with this container will run as a background ("detached") process on the host (_your machine_) 280 | - `--name` - the name specified for the process (_if not supplied, Docker will create one for you_) 281 | - `--rm` - when this process is stopped, remove the container associated with the process 282 | 283 | Congratulations, you have now launched the D-Bus deamon as a detached process inside a Docker container (_a fully sandboxed process_)! 284 | 285 | ##### Play Around 286 | 287 | ```bash 288 | $ docker exec --interactive --tty dbus-sample /bin/ash 289 | # cd dbus-sample/ 290 | # ls 291 | ``` 292 | 293 | The specified flags provide the following behavior: 294 | 295 | - `--interactive` - the user (_you_) is requesting interactive shell access to the application 296 | - `--tty` - allocate a pseudo-TTY for the container process 297 | 298 | The `docker exec` command allows you to attach to the container hosting the D-Bus daemon (_launched in the previous step_). The subsequent commands, with the `#` prefix, are commands that will be executed within the context of the container. 299 | Once you have executed the preceding commands, you will see the contents of this repository presented to you. You can now execute the sample above, or modify and experiment; even on Windows and Mac! 300 | 301 | ##### Clean Environment 302 | 303 | ```bash 304 | $ docker stop dbus-sample 305 | ``` 306 | 307 | Once you have finished playing, be sure to kill the background (_detached_) process. In doing so, the supporting container will be cleaned up by the Docker daemon, courtesy of the `--rm` flag we passed to the original `docker run` command. 308 | 309 | > ***NOTE:** This is an Alpine based container (to save you space), and there are a couple of things you may not be familiar with and are worth calling out. This container uses the Almquist shell, `ash`, and the Alpine Linux Package Manager, `apk`.* 310 | 311 | If you aren't familiar with Docker, then you're missing out! Be sure to watch [Kevin Sidwar's vlog about Docker](https://makercrew.com/404). 312 | -------------------------------------------------------------------------------- /dbus.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int 8 | main ( 9 | int argc, 10 | char * argv[] 11 | ) { 12 | (void)argc; 13 | (void)argv; 14 | DBusError dbus_error; 15 | DBusConnection * dbus_conn = nullptr; 16 | DBusMessage * dbus_msg = nullptr; 17 | DBusMessage * dbus_reply = nullptr; 18 | const char * dbus_result = nullptr; 19 | 20 | // Initialize D-Bus error 21 | ::dbus_error_init(&dbus_error); 22 | 23 | // Connect to D-Bus 24 | if ( nullptr == (dbus_conn = ::dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error)) ) { 25 | ::perror(dbus_error.name); 26 | ::perror(dbus_error.message); 27 | 28 | // Compose remote procedure call 29 | } else if ( nullptr == (dbus_msg = ::dbus_message_new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus.Introspectable", "Introspect")) ) { 30 | ::dbus_connection_unref(dbus_conn); 31 | ::perror("ERROR: ::dbus_message_new_method_call - Unable to allocate memory for the message!"); 32 | 33 | // Invoke remote procedure call, block for response 34 | } else if ( nullptr == (dbus_reply = ::dbus_connection_send_with_reply_and_block(dbus_conn, dbus_msg, DBUS_TIMEOUT_USE_DEFAULT, &dbus_error)) ) { 35 | ::dbus_message_unref(dbus_msg); 36 | ::dbus_connection_unref(dbus_conn); 37 | ::perror(dbus_error.name); 38 | ::perror(dbus_error.message); 39 | 40 | // Parse response 41 | } else if ( !::dbus_message_get_args(dbus_reply, &dbus_error, DBUS_TYPE_STRING, &dbus_result, DBUS_TYPE_INVALID) ) { 42 | ::dbus_message_unref(dbus_msg); 43 | ::dbus_message_unref(dbus_reply); 44 | ::dbus_connection_unref(dbus_conn); 45 | ::perror(dbus_error.name); 46 | ::perror(dbus_error.message); 47 | 48 | // Work with the results of the remote procedure call 49 | } else { 50 | std::cout << "Connected to D-Bus as \"" << ::dbus_bus_get_unique_name(dbus_conn) << "\"." << std::endl; 51 | std::cout << "Introspection Result:" << std::endl; 52 | std::cout << std::endl << dbus_result << std::endl << std::endl; 53 | ::dbus_message_unref(dbus_msg); 54 | ::dbus_message_unref(dbus_reply); 55 | 56 | /* 57 | * Applications must not close shared connections - 58 | * see dbus_connection_close() docs. This is a bug in the application. 59 | */ 60 | //::dbus_connection_close(dbus_conn); 61 | 62 | // When using the System Bus, unreference 63 | // the connection instead of closing it 64 | ::dbus_connection_unref(dbus_conn); 65 | } 66 | 67 | return 0; 68 | } 69 | --------------------------------------------------------------------------------