1/* 2 * Copyright © 2006-2007 Daniel Stone 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * 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 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 * Author: Daniel Stone <daniel@fooishbar.org> 24 */ 25 26#ifdef HAVE_DIX_CONFIG_H 27#include <dix-config.h> 28#endif 29 30#include <dbus/dbus.h> 31#include <string.h> 32 33#include <X11/X.h> 34 35#include "config-backends.h" 36#include "opaque.h" /* for 'display': there should be a better way. */ 37#include "input.h" 38#include "inputstr.h" 39 40#define API_VERSION 2 41 42#define MATCH_RULE "type='method_call',interface='org.x.config.input'" 43 44#define MALFORMED_MSG "[config/dbus] malformed message, dropping" 45#define MALFORMED_MESSAGE() { DebugF(MALFORMED_MSG "\n"); \ 46 ret = BadValue; \ 47 goto unwind; } 48#define MALFORMED_MESSAGE_ERROR() { DebugF(MALFORMED_MSG ": %s, %s", \ 49 error->name, error->message); \ 50 ret = BadValue; \ 51 goto unwind; } 52 53struct connection_info { 54 char busobject[32]; 55 char busname[64]; 56 DBusConnection *connection; 57}; 58 59static void 60reset_info(struct connection_info *info) 61{ 62 info->connection = NULL; 63 info->busname[0] = '\0'; 64 info->busobject[0] = '\0'; 65} 66 67static int 68add_device(DBusMessage *message, DBusMessage *reply, DBusError *error) 69{ 70 DBusMessageIter iter, reply_iter, subiter; 71 InputOption *tmpo = NULL, *options = NULL; 72 char *tmp = NULL; 73 int ret, err; 74 DeviceIntPtr dev = NULL; 75 76 dbus_message_iter_init_append(reply, &reply_iter); 77 78 if (!dbus_message_iter_init(message, &iter)) { 79 ErrorF("[config/dbus] couldn't initialise iterator\n"); 80 MALFORMED_MESSAGE(); 81 } 82 83 options = calloc(sizeof(*options), 1); 84 if (!options) { 85 ErrorF("[config/dbus] couldn't allocate option\n"); 86 return BadAlloc; 87 } 88 89 options->key = strdup("_source"); 90 options->value = strdup("client/dbus"); 91 if (!options->key || !options->value) { 92 ErrorF("[config/dbus] couldn't allocate first key/value pair\n"); 93 ret = BadAlloc; 94 goto unwind; 95 } 96 97 /* signature should be [ss][ss]... */ 98 while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) { 99 tmpo = calloc(sizeof(*tmpo), 1); 100 if (!tmpo) { 101 ErrorF("[config/dbus] couldn't allocate option\n"); 102 ret = BadAlloc; 103 goto unwind; 104 } 105 tmpo->next = options; 106 options = tmpo; 107 108 dbus_message_iter_recurse(&iter, &subiter); 109 110 if (dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_STRING) 111 MALFORMED_MESSAGE(); 112 113 dbus_message_iter_get_basic(&subiter, &tmp); 114 if (!tmp) 115 MALFORMED_MESSAGE(); 116 /* The _ prefix refers to internal settings, and may not be given by 117 * the client. */ 118 if (tmp[0] == '_') { 119 ErrorF("[config/dbus] attempted subterfuge: option name %s given\n", 120 tmp); 121 MALFORMED_MESSAGE(); 122 } 123 options->key = strdup(tmp); 124 if (!options->key) { 125 ErrorF("[config/dbus] couldn't duplicate key!\n"); 126 ret = BadAlloc; 127 goto unwind; 128 } 129 130 if (!dbus_message_iter_has_next(&subiter)) 131 MALFORMED_MESSAGE(); 132 dbus_message_iter_next(&subiter); 133 if (dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_STRING) 134 MALFORMED_MESSAGE(); 135 136 dbus_message_iter_get_basic(&subiter, &tmp); 137 if (!tmp) 138 MALFORMED_MESSAGE(); 139 options->value = strdup(tmp); 140 if (!options->value) { 141 ErrorF("[config/dbus] couldn't duplicate option!\n"); 142 ret = BadAlloc; 143 goto unwind; 144 } 145 146 dbus_message_iter_next(&iter); 147 } 148 149 ret = NewInputDeviceRequest(options, NULL, &dev); 150 if (ret != Success) { 151 DebugF("[config/dbus] NewInputDeviceRequest failed\n"); 152 goto unwind; 153 } 154 155 if (!dev) { 156 DebugF("[config/dbus] NewInputDeviceRequest provided no device\n"); 157 ret = BadImplementation; 158 goto unwind; 159 } 160 161 /* XXX: If we fail halfway through, we don't seem to have any way to 162 * empty the iterator, so you'll end up with some device IDs, 163 * plus an error. This seems to be a shortcoming in the D-Bus 164 * API. */ 165 for (; dev; dev = dev->next) { 166 if (!dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, 167 &dev->id)) { 168 ErrorF("[config/dbus] couldn't append to iterator\n"); 169 ret = BadAlloc; 170 goto unwind; 171 } 172 } 173 174unwind: 175 if (ret != Success) { 176 if (dev) 177 RemoveDevice(dev, TRUE); 178 179 err = -ret; 180 dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, &err); 181 } 182 183 while (options) { 184 tmpo = options; 185 options = options->next; 186 free(tmpo->key); 187 free(tmpo->value); 188 free(tmpo); 189 } 190 191 return ret; 192} 193 194static int 195remove_device(DBusMessage *message, DBusMessage *reply, DBusError *error) 196{ 197 int deviceid, ret, err; 198 DeviceIntPtr dev; 199 DBusMessageIter iter, reply_iter; 200 201 dbus_message_iter_init_append(reply, &reply_iter); 202 203 if (!dbus_message_iter_init(message, &iter)) { 204 ErrorF("[config/dbus] failed to init iterator\n"); 205 MALFORMED_MESSAGE(); 206 } 207 208 if (!dbus_message_get_args(message, error, DBUS_TYPE_UINT32, 209 &deviceid, DBUS_TYPE_INVALID)) { 210 MALFORMED_MESSAGE_ERROR(); 211 } 212 213 dixLookupDevice(&dev, deviceid, serverClient, DixDestroyAccess); 214 if (!dev) { 215 DebugF("[config/dbus] bogus device id %d given\n", deviceid); 216 ret = BadMatch; 217 goto unwind; 218 } 219 220 DebugF("[config/dbus] removing device %s (id %d)\n", dev->name, deviceid); 221 222 /* Call PIE here so we don't try to dereference a device that's 223 * already been removed. */ 224 OsBlockSignals(); 225 ProcessInputEvents(); 226 DeleteInputDeviceRequest(dev); 227 OsReleaseSignals(); 228 229 ret = Success; 230 231unwind: 232 err = (ret == Success) ? ret : -ret; 233 dbus_message_iter_append_basic(&reply_iter, DBUS_TYPE_INT32, &err); 234 235 return ret; 236} 237 238static int 239list_devices(DBusMessage *message, DBusMessage *reply, DBusError *error) 240{ 241 DeviceIntPtr dev; 242 DBusMessageIter iter, subiter; 243 244 dbus_message_iter_init_append(reply, &iter); 245 246 for (dev = inputInfo.devices; dev; dev = dev->next) { 247 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, 248 &subiter)) { 249 ErrorF("[config/dbus] couldn't init container\n"); 250 return BadAlloc; 251 } 252 if (!dbus_message_iter_append_basic(&subiter, DBUS_TYPE_UINT32, 253 &dev->id)) { 254 ErrorF("[config/dbus] couldn't append to iterator\n"); 255 return BadAlloc; 256 } 257 if (!dbus_message_iter_append_basic(&subiter, DBUS_TYPE_STRING, 258 &dev->name)) { 259 ErrorF("[config/dbus] couldn't append to iterator\n"); 260 return BadAlloc; 261 } 262 if (!dbus_message_iter_close_container(&iter, &subiter)) { 263 ErrorF("[config/dbus] couldn't close container\n"); 264 return BadAlloc; 265 } 266 } 267 268 return Success; 269} 270 271static int 272get_version(DBusMessage *message, DBusMessage *reply, DBusError *error) 273{ 274 DBusMessageIter iter; 275 unsigned int version = API_VERSION; 276 277 dbus_message_iter_init_append(reply, &iter); 278 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &version)) { 279 ErrorF("[config/dbus] couldn't append version\n"); 280 return BadAlloc; 281 } 282 283 return Success; 284} 285 286static DBusHandlerResult 287message_handler(DBusConnection *connection, DBusMessage *message, void *data) 288{ 289 DBusError error; 290 DBusMessage *reply; 291 struct connection_info *info = data; 292 293 /* ret is the overall D-Bus handler result, whereas err is the internal 294 * X error from our individual functions. */ 295 int ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 296 int err; 297 298 DebugF("[config/dbus] received a message for %s\n", 299 dbus_message_get_interface(message)); 300 301 dbus_error_init(&error); 302 303 reply = dbus_message_new_method_return(message); 304 if (!reply) { 305 ErrorF("[config/dbus] failed to create reply\n"); 306 ret = DBUS_HANDLER_RESULT_NEED_MEMORY; 307 goto err_start; 308 } 309 310 if (strcmp(dbus_message_get_member(message), "add") == 0) 311 err = add_device(message, reply, &error); 312 else if (strcmp(dbus_message_get_member(message), "remove") == 0) 313 err = remove_device(message, reply, &error); 314 else if (strcmp(dbus_message_get_member(message), "listDevices") == 0) 315 err = list_devices(message, reply, &error); 316 else if (strcmp(dbus_message_get_member(message), "version") == 0) 317 err = get_version(message, reply, &error); 318 else 319 goto err_reply; 320 321 /* Failure to allocate is a special case. */ 322 if (err == BadAlloc) { 323 ret = DBUS_HANDLER_RESULT_NEED_MEMORY; 324 goto err_reply; 325 } 326 327 /* While failure here is always an OOM, we don't return that, 328 * since that would result in devices being double-added/removed. */ 329 if (dbus_connection_send(info->connection, reply, NULL)) 330 dbus_connection_flush(info->connection); 331 else 332 ErrorF("[config/dbus] failed to send reply\n"); 333 334 ret = DBUS_HANDLER_RESULT_HANDLED; 335 336err_reply: 337 dbus_message_unref(reply); 338err_start: 339 dbus_error_free(&error); 340 341 return ret; 342} 343 344static void 345connect_hook(DBusConnection *connection, void *data) 346{ 347 DBusError error; 348 DBusObjectPathVTable vtable = { .message_function = message_handler, }; 349 struct connection_info *info = data; 350 351 info->connection = connection; 352 353 dbus_error_init(&error); 354 355 dbus_bus_request_name(info->connection, info->busname, 0, &error); 356 if (dbus_error_is_set(&error)) { 357 ErrorF("[config/dbus] couldn't take over org.x.config: %s (%s)\n", 358 error.name, error.message); 359 goto err_start; 360 } 361 362 /* blocks until we get a reply. */ 363 dbus_bus_add_match(info->connection, MATCH_RULE, &error); 364 if (dbus_error_is_set(&error)) { 365 ErrorF("[config/dbus] couldn't add match: %s (%s)\n", error.name, 366 error.message); 367 goto err_name; 368 } 369 370 if (!dbus_connection_register_object_path(info->connection, 371 info->busobject, &vtable, 372 info)) { 373 ErrorF("[config/dbus] couldn't register object path\n"); 374 goto err_match; 375 } 376 377 DebugF("[dbus] registered %s, %s\n", info->busname, info->busobject); 378 379 dbus_error_free(&error); 380 381 return; 382 383err_match: 384 dbus_bus_remove_match(info->connection, MATCH_RULE, &error); 385err_name: 386 dbus_bus_release_name(info->connection, info->busname, &error); 387err_start: 388 dbus_error_free(&error); 389 390 reset_info(info); 391} 392 393static void 394disconnect_hook(void *data) 395{ 396} 397 398#if 0 399void 400pre_disconnect_hook(void) 401{ 402 DBusError error; 403 404 dbus_error_init(&error); 405 dbus_connection_unregister_object_path(connection_data->connection, 406 connection_data->busobject); 407 dbus_bus_remove_match(connection_data->connection, MATCH_RULE, 408 &error); 409 dbus_bus_release_name(connection_data->connection, 410 connection_data->busname, &error); 411 dbus_error_free(&error); 412} 413#endif 414 415static struct connection_info connection_data; 416static struct config_dbus_core_hook core_hook = { 417 .connect = connect_hook, 418 .disconnect = disconnect_hook, 419 .data = &connection_data, 420}; 421 422int 423config_dbus_init(void) 424{ 425 snprintf(connection_data.busname, sizeof(connection_data.busname), 426 "org.x.config.display%d", atoi(display)); 427 snprintf(connection_data.busobject, sizeof(connection_data.busobject), 428 "/org/x/config/%d", atoi(display)); 429 430 return config_dbus_core_add_hook(&core_hook); 431} 432 433void 434config_dbus_fini(void) 435{ 436 config_dbus_core_remove_hook(&core_hook); 437 connection_data.busname[0] = '\0'; 438 connection_data.busobject[0] = '\0'; 439} 440