1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/
2
3/***
4  Copyright 2009 Lennart Poettering
5
6  Permission is hereby granted, free of charge, to any person
7  obtaining a copy of this software and associated documentation files
8  (the "Software"), to deal in the Software without restriction,
9  including without limitation the rights to use, copy, modify, merge,
10  publish, distribute, sublicense, and/or sell copies of the Software,
11  and to permit persons to whom the Software is furnished to do so,
12  subject to the following conditions:
13
14  The above copyright notice and this permission notice shall be
15  included in all copies or substantial portions of the Software.
16
17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24  SOFTWARE.
25***/
26
27#include <string.h>
28#include <unistd.h>
29#include <errno.h>
30#include <stdlib.h>
31#include <stdio.h>
32#include <assert.h>
33
34#include "reserve-monitor.h"
35#include "reserve.h"
36
37struct rm_monitor {
38	int ref;
39
40	char *device_name;
41	char *service_name;
42	char *match;
43
44	DBusConnection *connection;
45
46	unsigned busy:1;
47	unsigned filtering:1;
48	unsigned matching:1;
49
50	rm_change_cb_t change_cb;
51	void *userdata;
52};
53
54#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
55
56#define SERVICE_FILTER				\
57	"type='signal',"			\
58	"sender='" DBUS_SERVICE_DBUS "',"	\
59	"interface='" DBUS_INTERFACE_DBUS "',"	\
60	"member='NameOwnerChanged',"		\
61	"arg0='%s'"
62
63static unsigned get_busy(
64	DBusConnection *c,
65	const char *name_owner) {
66
67	const char *un;
68
69	if (!name_owner || !*name_owner)
70		return FALSE;
71
72	/* If we ourselves own the device, then don't consider this 'busy' */
73	if ((un = dbus_bus_get_unique_name(c)))
74		if (strcmp(name_owner, un) == 0)
75			return FALSE;
76
77	return TRUE;
78}
79
80static DBusHandlerResult filter_handler(
81	DBusConnection *c,
82	DBusMessage *s,
83	void *userdata) {
84
85	rm_monitor *m;
86	DBusError error;
87
88	dbus_error_init(&error);
89
90	m = userdata;
91	assert(m->ref >= 1);
92
93	if (dbus_message_is_signal(s, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
94		const char *name, *old, *new;
95
96		if (!dbus_message_get_args(
97			    s,
98			    &error,
99			    DBUS_TYPE_STRING, &name,
100			    DBUS_TYPE_STRING, &old,
101			    DBUS_TYPE_STRING, &new,
102			    DBUS_TYPE_INVALID))
103			goto invalid;
104
105		if (strcmp(name, m->service_name) == 0) {
106			unsigned old_busy = m->busy;
107
108			m->busy = get_busy(c, new);
109
110			if (m->busy != old_busy && m->change_cb) {
111				m->ref++;
112				m->change_cb(m);
113				rm_release(m);
114			}
115		}
116	}
117
118invalid:
119	dbus_error_free(&error);
120
121	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
122}
123
124int rm_watch(
125	rm_monitor **_m,
126	DBusConnection *connection,
127	const char *device_name,
128	rm_change_cb_t change_cb,
129	DBusError *error)  {
130
131	rm_monitor *m = NULL;
132	char *name_owner;
133	int r;
134	DBusError _error;
135
136	if (!error)
137		error = &_error;
138
139	dbus_error_init(error);
140
141	if (!_m)
142		return -EINVAL;
143
144	if (!connection)
145		return -EINVAL;
146
147	if (!device_name)
148		return -EINVAL;
149
150	if (!(m = calloc(sizeof(rm_monitor), 1)))
151		return -ENOMEM;
152
153	m->ref = 1;
154
155	if (!(m->device_name = strdup(device_name))) {
156		r = -ENOMEM;
157		goto fail;
158	}
159
160	m->connection = dbus_connection_ref(connection);
161	m->change_cb = change_cb;
162
163	if (!(m->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
164		r = -ENOMEM;
165		goto fail;
166	}
167	sprintf(m->service_name, SERVICE_PREFIX "%s", m->device_name);
168
169	if (!(dbus_connection_add_filter(m->connection, filter_handler, m, NULL))) {
170		r = -ENOMEM;
171		goto fail;
172	}
173
174	m->filtering = 1;
175
176	if (!(m->match = malloc(sizeof(SERVICE_FILTER) - 2 + strlen(m->service_name)))) {
177		r = -ENOMEM;
178		goto fail;
179	}
180
181	sprintf(m->match, SERVICE_FILTER, m->service_name);
182	dbus_bus_add_match(m->connection, m->match, error);
183
184	if (dbus_error_is_set(error)) {
185		r = -EIO;
186		goto fail;
187	}
188
189	m->matching = 1;
190
191	if ((r = rd_dbus_get_name_owner(m->connection, m->service_name, &name_owner, error)) < 0)
192		goto fail;
193
194	m->busy = get_busy(m->connection, name_owner);
195	free(name_owner);
196
197	*_m = m;
198	return 0;
199
200fail:
201	if (&_error == error)
202		dbus_error_free(&_error);
203
204	if (m)
205		rm_release(m);
206
207	return r;
208}
209
210void rm_release(rm_monitor *m) {
211	if (!m)
212		return;
213
214	assert(m->ref > 0);
215
216	if (--m->ref > 0)
217		return;
218
219	if (m->matching)
220		dbus_bus_remove_match(
221			m->connection,
222			m->match,
223			NULL);
224
225	if (m->filtering)
226		dbus_connection_remove_filter(
227			m->connection,
228			filter_handler,
229			m);
230
231	free(m->device_name);
232	free(m->service_name);
233	free(m->match);
234
235	if (m->connection)
236		dbus_connection_unref(m->connection);
237
238	free(m);
239}
240
241int rm_busy(rm_monitor *m) {
242	if (!m)
243		return -EINVAL;
244
245	assert(m->ref > 0);
246
247	return m->busy;
248}
249
250void rm_set_userdata(rm_monitor *m, void *userdata) {
251
252	if (!m)
253		return;
254
255	assert(m->ref > 0);
256	m->userdata = userdata;
257}
258
259void* rm_get_userdata(rm_monitor *m) {
260
261	if (!m)
262		return NULL;
263
264	assert(m->ref > 0);
265
266	return m->userdata;
267}
268