├── .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 |
--------------------------------------------------------------------------------