Make the bus launcher abort if an instance is already running
[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   "</node>";
66 static GDBusNodeInfo *introspection_data = NULL;
67
68 static void
69 setup_bus_child (gpointer data)
70 {
71   A11yBusLauncher *app = data;
72   (void) app;
73
74   close (app->pipefd[0]);
75   dup2 (app->pipefd[1], 3);
76   close (app->pipefd[1]);
77
78   /* On Linux, tell the bus process to exit if this process goes away */
79 #ifdef __linux
80 #include <sys/prctl.h>
81   prctl (PR_SET_PDEATHSIG, 15);
82 #endif  
83 }
84
85 /**
86  * unix_read_all_fd_to_string:
87  *
88  * Read all data from a file descriptor to a C string buffer.
89  */
90 static gboolean
91 unix_read_all_fd_to_string (int      fd,
92                             char    *buf,
93                             ssize_t  max_bytes)
94 {
95   ssize_t bytes_read;
96
97   while (max_bytes > 1 && (bytes_read = read (fd, buf, MAX (4096, max_bytes - 1))))
98     {
99       if (bytes_read < 0)
100         return FALSE;
101       buf += bytes_read;
102       max_bytes -= bytes_read;
103     }
104   *buf = '\0';
105   return TRUE;
106 }
107
108 static void
109 on_bus_exited (GPid     pid,
110                gint     status,
111                gpointer data)
112 {
113   A11yBusLauncher *app = data;
114   
115   app->a11y_bus_pid = -1;
116   app->state = A11Y_BUS_STATE_ERROR;
117   if (app->a11y_launch_error_message == NULL)
118     {
119       if (WIFEXITED (status))
120         app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
121       else if (WIFSIGNALED (status))
122         app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
123       else if (WIFSTOPPED (status))
124         app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
125     }
126   g_main_loop_quit (app->loop);
127
128
129 static void
130 ensure_a11y_bus (A11yBusLauncher *app)
131 {
132   GPid pid;
133   char *argv[] = { DBUS_DAEMON, NULL, "--nofork", "--print-address", "3", NULL };
134   char addr_buf[2048];
135   GError *error = NULL;
136
137   if (app->a11y_bus_pid != 0)
138     return;
139   
140   argv[1] = g_strdup_printf ("--config-file=%s/at-spi2/accessibility.conf", SYSCONFDIR);
141
142   if (pipe (app->pipefd) < 0)
143     g_error ("Failed to create pipe: %s", strerror (errno));
144   
145   if (!g_spawn_async (NULL,
146                       argv,
147                       NULL,
148                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
149                       setup_bus_child,
150                       app,
151                       &pid,
152                       &error))
153     {
154       app->a11y_bus_pid = -1;
155       app->a11y_launch_error_message = g_strdup (error->message);
156       g_clear_error (&error);
157       goto error;
158     }
159
160   close (app->pipefd[1]);
161   app->pipefd[1] = -1;
162
163   g_child_watch_add (pid, on_bus_exited, app);
164
165   app->state = A11Y_BUS_STATE_READING_ADDRESS;
166   app->a11y_bus_pid = pid;
167   g_debug ("Launched a11y bus, child is %ld", (long) pid);
168   if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
169     {
170       app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
171       kill (app->a11y_bus_pid, SIGTERM);
172       goto error;
173     }
174   close (app->pipefd[0]);
175   app->pipefd[0] = -1;
176   app->state = A11Y_BUS_STATE_RUNNING;
177
178   /* Trim the trailing newline */
179   app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
180   g_debug ("a11y bus address: %s", app->a11y_bus_address);
181
182   {
183     Display *display = XOpenDisplay (NULL);
184     if (display)
185       {
186         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
187         XChangeProperty (display,
188                          XDefaultRootWindow (display),
189                          bus_address_atom,
190                          XA_STRING, 8, PropModeReplace,
191                          (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
192       }
193     XFlush (display);
194     XCloseDisplay (display);
195   }
196
197   return;
198   
199  error:
200   close (app->pipefd[0]);
201   close (app->pipefd[1]);
202   app->state = A11Y_BUS_STATE_ERROR;
203 }
204
205 static void
206 handle_method_call (GDBusConnection       *connection,
207                     const gchar           *sender,
208                     const gchar           *object_path,
209                     const gchar           *interface_name,
210                     const gchar           *method_name,
211                     GVariant              *parameters,
212                     GDBusMethodInvocation *invocation,
213                     gpointer               user_data)
214 {
215   A11yBusLauncher *app = user_data;
216
217   if (g_strcmp0 (method_name, "GetAddress") == 0)
218     {
219       ensure_a11y_bus (app);
220       if (app->a11y_bus_pid > 0)
221         g_dbus_method_invocation_return_value (invocation,
222                                                g_variant_new ("(s)", app->a11y_bus_address));
223       else
224         g_dbus_method_invocation_return_dbus_error (invocation,
225                                                     "org.a11y.Bus.Error",
226                                                     app->a11y_launch_error_message);
227     }
228 }
229
230 static const GDBusInterfaceVTable interface_vtable =
231 {
232   handle_method_call,
233   NULL,
234   NULL  /* handle_set_property */
235 };
236
237 static void
238 on_bus_acquired (GDBusConnection *connection,
239                  const gchar     *name,
240                  gpointer         user_data)
241 {
242   A11yBusLauncher *app = user_data;
243   GError *error;
244   guint registration_id;
245   
246   if (connection == NULL)
247     {
248       g_main_loop_quit (app->loop);
249       return;
250     }
251   app->session_bus = connection;
252
253   if (app->launch_immediately)
254     {
255       ensure_a11y_bus (app);
256       if (app->state == A11Y_BUS_STATE_ERROR)
257         {
258           g_main_loop_quit (app->loop);
259           return;
260         }
261     }
262
263   error = NULL;
264   registration_id = g_dbus_connection_register_object (connection,
265                                                        "/org/a11y/bus",
266                                                        introspection_data->interfaces[0],
267                                                        &interface_vtable,
268                                                        _global_app,
269                                                        NULL,
270                                                        &error);
271   if (registration_id == 0)
272     g_error ("%s", error->message);
273 }
274
275 static void
276 on_name_lost (GDBusConnection *connection,
277               const gchar     *name,
278               gpointer         user_data)
279 {
280   A11yBusLauncher *app = user_data;
281   if (app->session_bus == NULL
282       && connection == NULL
283       && app->a11y_launch_error_message == NULL)
284     app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
285   g_main_loop_quit (app->loop);
286 }
287
288 static void
289 on_name_acquired (GDBusConnection *connection,
290                   const gchar     *name,
291                   gpointer         user_data)
292 {
293   A11yBusLauncher *app = user_data;
294   (void) app;
295 }
296
297 static int sigterm_pipefd[2];
298
299 static void
300 sigterm_handler (int signum)
301 {
302   write (sigterm_pipefd[1], "X", 1);
303 }
304
305 static gboolean
306 on_sigterm_pipe (GIOChannel  *channel,
307                  GIOCondition condition,
308                  gpointer     data)
309 {
310   A11yBusLauncher *app = data;
311   
312   g_main_loop_quit (app->loop);
313
314   return FALSE;
315 }
316
317 static void
318 init_sigterm_handling (A11yBusLauncher *app)
319 {
320   GIOChannel *sigterm_channel;
321
322   if (pipe (sigterm_pipefd) < 0)
323     g_error ("Failed to create pipe: %s", strerror (errno));
324   signal (SIGTERM, sigterm_handler);
325
326   sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
327   g_io_add_watch (sigterm_channel,
328                   G_IO_IN | G_IO_ERR | G_IO_HUP,
329                   on_sigterm_pipe,
330                   app);
331 }
332
333 static gboolean
334 is_a11y_using_corba (void)
335 {
336   char *gconf_argv[] = { "gconftool-2", "--get", "/desktop/gnome/interface/at-spi-corba", NULL };
337   char *stdout = NULL;
338   int estatus;
339   gboolean result = FALSE;
340
341   if (!g_spawn_sync (NULL, gconf_argv, NULL,
342                      G_SPAWN_SEARCH_PATH, NULL, NULL, &stdout, NULL, &estatus, NULL))
343     goto out;
344   if (estatus != 0)
345     goto out;
346   if (g_str_has_prefix (stdout, "true"))
347     result = TRUE;
348  out:
349   g_free (stdout);
350   return result;
351 }
352
353 static gboolean
354 already_running ()
355 {
356   Atom AT_SPI_BUS;
357   Atom actual_type;
358   Display *bridge_display;
359   int actual_format;
360   unsigned char *data = NULL;
361   unsigned long nitems;
362   unsigned long leftover;
363   gboolean result = FALSE;
364
365   bridge_display = XOpenDisplay (NULL);
366   if (!bridge_display)
367               return FALSE;
368       
369   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
370   XGetWindowProperty (bridge_display,
371                       XDefaultRootWindow (bridge_display),
372                       AT_SPI_BUS, 0L,
373                       (long) BUFSIZ, False,
374                       (Atom) 31, &actual_type, &actual_format,
375                       &nitems, &leftover, &data);
376
377   if (data)
378   {
379     GDBusConnection *bus;
380     GError *error = NULL;
381     const gchar *old_session = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
382     /* TODO: Is there a better way to connect? This is really hacky */
383     g_setenv ("DBUS_SESSION_BUS_ADDRESS", data, TRUE);
384     bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
385     g_setenv ("DBUS_SESSION_BUS_ADDRESS", old_session, TRUE);
386     if (bus != NULL)
387       result = TRUE;
388     g_object_unref (bus);
389   }
390
391   XCloseDisplay (bridge_display);
392   return result;
393 }
394
395
396 int
397 main (int    argc,
398       char **argv)
399 {
400   GError *error = NULL;
401   GMainLoop *loop;
402   GDBusConnection *session_bus;
403   int name_owner_id;
404
405   g_type_init ();
406
407   if (is_a11y_using_corba ())
408     return 0;
409
410   if (already_running ())
411     return 0;
412
413   _global_app = g_slice_new0 (A11yBusLauncher);
414   _global_app->loop = g_main_loop_new (NULL, FALSE);
415   _global_app->launch_immediately = (argc == 2 && strcmp (argv[1], "--launch-immediately") == 0);
416
417   init_sigterm_handling (_global_app);
418
419   introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
420   g_assert (introspection_data != NULL);
421
422   name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
423                                   "org.a11y.Bus",
424                                   G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
425                                   on_bus_acquired,
426                                   on_name_acquired,
427                                   on_name_lost,
428                                   _global_app,
429                                   NULL);
430
431   g_main_loop_run (_global_app->loop);
432
433   if (_global_app->a11y_bus_pid > 0)
434     kill (_global_app->a11y_bus_pid, SIGTERM);
435
436   /* Clear the X property if our bus is gone; in the case where e.g. 
437    * GDM is launching a login on an X server it was using before,
438    * we don't want early login processes to pick up the stale address.
439    */
440   {
441     Display *display = XOpenDisplay (NULL);
442     if (display)
443       {
444         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
445         XDeleteProperty (display,
446                          XDefaultRootWindow (display),
447                          bus_address_atom);
448
449         XFlush (display);
450         XCloseDisplay (display);
451       }
452   }
453
454   if (_global_app->a11y_launch_error_message)
455     {
456       g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
457       return 1;
458     }
459   return 0;
460 }