Fix for BGO#657585: The bus launcher should not flush a NULL display
[platform/upstream/at-spi2-core.git] / bus / at-spi-bus-launcher.c
1 /* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * 
3  * at-spi-bus-launcher: Manage the a11y bus as a child process 
4  *
5  * Copyright 2011 Red Hat, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include "config.h"
24
25 #include <unistd.h>
26 #include <string.h>
27 #include <signal.h>
28 #include <sys/wait.h>
29 #include <errno.h>
30 #include <stdio.h>
31
32 #include <gio/gio.h>
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35
36 typedef enum {
37   A11Y_BUS_STATE_IDLE = 0,
38   A11Y_BUS_STATE_READING_ADDRESS,
39   A11Y_BUS_STATE_RUNNING,
40   A11Y_BUS_STATE_ERROR
41 } A11yBusState;
42
43 typedef struct {
44   GMainLoop *loop;
45   gboolean launch_immediately;
46   GDBusConnection *session_bus;
47
48   A11yBusState state;
49   /* -1 == error, 0 == pending, > 0 == running */
50   int a11y_bus_pid;
51   char *a11y_bus_address;
52   int pipefd[2];
53   char *a11y_launch_error_message;
54 } A11yBusLauncher;
55
56 static A11yBusLauncher *_global_app = NULL;
57
58 static const gchar introspection_xml[] =
59   "<node>"
60   "  <interface name='org.a11y.Bus'>"
61   "    <method name='GetAddress'>"
62   "      <arg type='s' name='address' direction='out'/>"
63   "    </method>"
64   "  </interface>"
65   "<interface name='org.a11y.Status'>"
66   "<property name='IsEnabled' type='b' access='read'/>"
67   "</interface>"
68   "</node>";
69 static GDBusNodeInfo *introspection_data = NULL;
70
71 static void
72 setup_bus_child (gpointer data)
73 {
74   A11yBusLauncher *app = data;
75   (void) app;
76
77   close (app->pipefd[0]);
78   dup2 (app->pipefd[1], 3);
79   close (app->pipefd[1]);
80
81   /* On Linux, tell the bus process to exit if this process goes away */
82 #ifdef __linux
83 #include <sys/prctl.h>
84   prctl (PR_SET_PDEATHSIG, 15);
85 #endif  
86 }
87
88 /**
89  * unix_read_all_fd_to_string:
90  *
91  * Read all data from a file descriptor to a C string buffer.
92  */
93 static gboolean
94 unix_read_all_fd_to_string (int      fd,
95                             char    *buf,
96                             ssize_t  max_bytes)
97 {
98   ssize_t bytes_read;
99
100   while (max_bytes > 1 && (bytes_read = read (fd, buf, MAX (4096, max_bytes - 1))))
101     {
102       if (bytes_read < 0)
103         return FALSE;
104       buf += bytes_read;
105       max_bytes -= bytes_read;
106     }
107   *buf = '\0';
108   return TRUE;
109 }
110
111 static void
112 on_bus_exited (GPid     pid,
113                gint     status,
114                gpointer data)
115 {
116   A11yBusLauncher *app = data;
117   
118   app->a11y_bus_pid = -1;
119   app->state = A11Y_BUS_STATE_ERROR;
120   if (app->a11y_launch_error_message == NULL)
121     {
122       if (WIFEXITED (status))
123         app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
124       else if (WIFSIGNALED (status))
125         app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
126       else if (WIFSTOPPED (status))
127         app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
128     }
129   g_main_loop_quit (app->loop);
130
131
132 static void
133 ensure_a11y_bus (A11yBusLauncher *app)
134 {
135   GPid pid;
136   char *argv[] = { DBUS_DAEMON, NULL, "--nofork", "--print-address", "3", NULL };
137   char addr_buf[2048];
138   GError *error = NULL;
139
140   if (app->a11y_bus_pid != 0)
141     return;
142   
143   argv[1] = g_strdup_printf ("--config-file=%s/at-spi2/accessibility.conf", SYSCONFDIR);
144
145   if (pipe (app->pipefd) < 0)
146     g_error ("Failed to create pipe: %s", strerror (errno));
147   
148   if (!g_spawn_async (NULL,
149                       argv,
150                       NULL,
151                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
152                       setup_bus_child,
153                       app,
154                       &pid,
155                       &error))
156     {
157       app->a11y_bus_pid = -1;
158       app->a11y_launch_error_message = g_strdup (error->message);
159       g_clear_error (&error);
160       goto error;
161     }
162
163   close (app->pipefd[1]);
164   app->pipefd[1] = -1;
165
166   g_child_watch_add (pid, on_bus_exited, app);
167
168   app->state = A11Y_BUS_STATE_READING_ADDRESS;
169   app->a11y_bus_pid = pid;
170   g_debug ("Launched a11y bus, child is %ld", (long) pid);
171   if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
172     {
173       app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
174       kill (app->a11y_bus_pid, SIGTERM);
175       goto error;
176     }
177   close (app->pipefd[0]);
178   app->pipefd[0] = -1;
179   app->state = A11Y_BUS_STATE_RUNNING;
180
181   /* Trim the trailing newline */
182   app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
183   g_debug ("a11y bus address: %s", app->a11y_bus_address);
184
185   {
186     Display *display = XOpenDisplay (NULL);
187     if (display)
188       {
189         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
190         XChangeProperty (display,
191                          XDefaultRootWindow (display),
192                          bus_address_atom,
193                          XA_STRING, 8, PropModeReplace,
194                          (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
195         XFlush (display);
196         XCloseDisplay (display);
197       }
198   }
199
200   return;
201   
202  error:
203   close (app->pipefd[0]);
204   close (app->pipefd[1]);
205   app->state = A11Y_BUS_STATE_ERROR;
206 }
207
208 static void
209 handle_method_call (GDBusConnection       *connection,
210                     const gchar           *sender,
211                     const gchar           *object_path,
212                     const gchar           *interface_name,
213                     const gchar           *method_name,
214                     GVariant              *parameters,
215                     GDBusMethodInvocation *invocation,
216                     gpointer               user_data)
217 {
218   A11yBusLauncher *app = user_data;
219
220   if (g_strcmp0 (method_name, "GetAddress") == 0)
221     {
222       ensure_a11y_bus (app);
223       if (app->a11y_bus_pid > 0)
224         g_dbus_method_invocation_return_value (invocation,
225                                                g_variant_new ("(s)", app->a11y_bus_address));
226       else
227         g_dbus_method_invocation_return_dbus_error (invocation,
228                                                     "org.a11y.Bus.Error",
229                                                     app->a11y_launch_error_message);
230     }
231 }
232
233 static GVariant *
234 handle_get_property  (GDBusConnection       *connection,
235                       const gchar           *sender,
236                       const gchar           *object_path,
237                       const gchar           *interface_name,
238                       const gchar           *property_name,
239                     GError **error,
240                     gpointer               user_data)
241 {
242   A11yBusLauncher *app = user_data;
243
244   if (g_strcmp0 (property_name, "IsEnabled") == 0)
245     {
246       gboolean result = (app->a11y_bus_pid > 0);
247       return g_variant_new ("(b)", result);
248     }
249   else
250     return NULL;
251 }
252
253 static const GDBusInterfaceVTable bus_vtable =
254 {
255   handle_method_call,
256   NULL, /* handle_get_property, */
257   NULL  /* handle_set_property */
258 };
259
260 static const GDBusInterfaceVTable status_vtable =
261 {
262   NULL, /* handle_method_call */
263   handle_get_property,
264   NULL  /* handle_set_property */
265 };
266
267 static void
268 on_bus_acquired (GDBusConnection *connection,
269                  const gchar     *name,
270                  gpointer         user_data)
271 {
272   A11yBusLauncher *app = user_data;
273   GError *error;
274   guint registration_id;
275   
276   if (connection == NULL)
277     {
278       g_main_loop_quit (app->loop);
279       return;
280     }
281   app->session_bus = connection;
282
283   if (app->launch_immediately)
284     {
285       ensure_a11y_bus (app);
286       if (app->state == A11Y_BUS_STATE_ERROR)
287         {
288           g_main_loop_quit (app->loop);
289           return;
290         }
291     }
292
293   error = NULL;
294   registration_id = g_dbus_connection_register_object (connection,
295                                                        "/org/a11y/bus",
296                                                        introspection_data->interfaces[0],
297                                                        &bus_vtable,
298                                                        _global_app,
299                                                        NULL,
300                                                        &error);
301   if (registration_id == 0)
302     g_error ("%s", error->message);
303
304   g_dbus_connection_register_object (connection,
305                                                        "/org/a11y/bus",
306                                                        introspection_data->interfaces[1],
307                                                        &status_vtable,
308                                                        _global_app,
309                                                        NULL,
310                                                        &error);
311 }
312
313 static void
314 on_name_lost (GDBusConnection *connection,
315               const gchar     *name,
316               gpointer         user_data)
317 {
318   A11yBusLauncher *app = user_data;
319   if (app->session_bus == NULL
320       && connection == NULL
321       && app->a11y_launch_error_message == NULL)
322     app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
323   g_main_loop_quit (app->loop);
324 }
325
326 static void
327 on_name_acquired (GDBusConnection *connection,
328                   const gchar     *name,
329                   gpointer         user_data)
330 {
331   A11yBusLauncher *app = user_data;
332   (void) app;
333 }
334
335 static int sigterm_pipefd[2];
336
337 static void
338 sigterm_handler (int signum)
339 {
340   write (sigterm_pipefd[1], "X", 1);
341 }
342
343 static gboolean
344 on_sigterm_pipe (GIOChannel  *channel,
345                  GIOCondition condition,
346                  gpointer     data)
347 {
348   A11yBusLauncher *app = data;
349   
350   g_main_loop_quit (app->loop);
351
352   return FALSE;
353 }
354
355 static void
356 init_sigterm_handling (A11yBusLauncher *app)
357 {
358   GIOChannel *sigterm_channel;
359
360   if (pipe (sigterm_pipefd) < 0)
361     g_error ("Failed to create pipe: %s", strerror (errno));
362   signal (SIGTERM, sigterm_handler);
363
364   sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
365   g_io_add_watch (sigterm_channel,
366                   G_IO_IN | G_IO_ERR | G_IO_HUP,
367                   on_sigterm_pipe,
368                   app);
369 }
370
371 static gboolean
372 already_running ()
373 {
374   Atom AT_SPI_BUS;
375   Atom actual_type;
376   Display *bridge_display;
377   int actual_format;
378   unsigned char *data = NULL;
379   unsigned long nitems;
380   unsigned long leftover;
381   gboolean result = FALSE;
382
383   bridge_display = XOpenDisplay (NULL);
384   if (!bridge_display)
385               return FALSE;
386       
387   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
388   XGetWindowProperty (bridge_display,
389                       XDefaultRootWindow (bridge_display),
390                       AT_SPI_BUS, 0L,
391                       (long) BUFSIZ, False,
392                       (Atom) 31, &actual_type, &actual_format,
393                       &nitems, &leftover, &data);
394
395   if (data)
396   {
397     GDBusConnection *bus;
398     GError *error = NULL;
399     const gchar *old_session = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
400     /* TODO: Is there a better way to connect? This is really hacky */
401     g_setenv ("DBUS_SESSION_BUS_ADDRESS", data, TRUE);
402     bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
403     g_setenv ("DBUS_SESSION_BUS_ADDRESS", old_session, TRUE);
404     if (bus != NULL)
405       result = TRUE;
406     g_object_unref (bus);
407   }
408
409   XCloseDisplay (bridge_display);
410   return result;
411 }
412
413
414 int
415 main (int    argc,
416       char **argv)
417 {
418   GError *error = NULL;
419   GMainLoop *loop;
420   GDBusConnection *session_bus;
421   int name_owner_id;
422
423   g_type_init ();
424
425   if (already_running ())
426     return 0;
427
428   _global_app = g_slice_new0 (A11yBusLauncher);
429   _global_app->loop = g_main_loop_new (NULL, FALSE);
430   _global_app->launch_immediately = (argc == 2 && strcmp (argv[1], "--launch-immediately") == 0);
431
432   init_sigterm_handling (_global_app);
433
434   introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
435   g_assert (introspection_data != NULL);
436
437   name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
438                                   "org.a11y.Bus",
439                                   G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
440                                   on_bus_acquired,
441                                   on_name_acquired,
442                                   on_name_lost,
443                                   _global_app,
444                                   NULL);
445
446   g_main_loop_run (_global_app->loop);
447
448   if (_global_app->a11y_bus_pid > 0)
449     kill (_global_app->a11y_bus_pid, SIGTERM);
450
451   /* Clear the X property if our bus is gone; in the case where e.g. 
452    * GDM is launching a login on an X server it was using before,
453    * we don't want early login processes to pick up the stale address.
454    */
455   {
456     Display *display = XOpenDisplay (NULL);
457     if (display)
458       {
459         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
460         XDeleteProperty (display,
461                          XDefaultRootWindow (display),
462                          bus_address_atom);
463
464         XFlush (display);
465         XCloseDisplay (display);
466       }
467   }
468
469   if (_global_app->a11y_launch_error_message)
470     {
471       g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
472       return 1;
473     }
474   return 0;
475 }