Home | History | Annotate | Download | only in tools
      1 /* -*- mode: C; c-file-style: "gnu" -*- */
      2 /* dbus-launch.h  dbus-launch utility
      3  *
      4  * Copyright (C) 2006 Thiago Macieira <thiago (at) kde.org>
      5  *
      6  * Licensed under the Academic Free License version 2.1
      7  *
      8  * This program is free software; you can redistribute it and/or modify
      9  * it under the terms of the GNU General Public License as published by
     10  * the Free Software Foundation; either version 2 of the License, or
     11  * (at your option) any later version.
     12  *
     13  * This program is distributed in the hope that it will be useful,
     14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  * GNU General Public License for more details.
     17  *
     18  * You should have received a copy of the GNU General Public License
     19  * along with this program; if not, write to the Free Software
     20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     21  *
     22  */
     23 #include "dbus-launch.h"
     24 
     25 #ifdef DBUS_BUILD_X11
     26 #include <stdlib.h>
     27 #include <sys/types.h>
     28 #include <sys/stat.h>
     29 #include <unistd.h>
     30 #include <fcntl.h>
     31 #include <errno.h>
     32 #include <stdio.h>
     33 #include <string.h>
     34 #include <pwd.h>
     35 #include <X11/Xlib.h>
     36 #include <X11/Xatom.h>
     37 
     38 Display *xdisplay = NULL;
     39 static Atom selection_atom;
     40 static Atom address_atom;
     41 static Atom pid_atom;
     42 
     43 static int
     44 x_io_error_handler (Display *xdisplay)
     45 {
     46   verbose ("X IO error\n");
     47   kill_bus_and_exit (0);
     48   return 0;
     49 }
     50 
     51 static void
     52 remove_prefix (char *s,
     53                char *prefix)
     54 {
     55   int plen;
     56 
     57   plen = strlen (prefix);
     58 
     59   if (strncmp (s, prefix, plen) == 0)
     60     {
     61       memmove (s, s + plen, strlen (s) - plen + 1);
     62     }
     63 }
     64 
     65 static const char*
     66 get_homedir (void)
     67 {
     68   const char *home;
     69 
     70   home = getenv ("HOME");
     71   if (home == NULL)
     72     {
     73       /* try from the user database */
     74       struct passwd *user = getpwuid (getuid());
     75       if (user != NULL)
     76         home = user->pw_dir;
     77     }
     78 
     79   if (home == NULL)
     80     {
     81       fprintf (stderr, "Can't get user home directory\n");
     82       exit (1);
     83     }
     84 
     85   return home;
     86 }
     87 
     88 #define DBUS_DIR ".dbus"
     89 #define DBUS_SESSION_BUS_DIR "session-bus"
     90 
     91 static char *
     92 get_session_file (void)
     93 {
     94   static const char prefix[] = "/" DBUS_DIR "/" DBUS_SESSION_BUS_DIR "/";
     95   const char *machine;
     96   const char *home;
     97   char *display;
     98   char *result;
     99   char *p;
    100 
    101   machine = get_machine_uuid ();
    102   if (machine == NULL)
    103     return NULL;
    104 
    105   display = xstrdup (getenv ("DISPLAY"));
    106   if (display == NULL)
    107     {
    108       verbose ("X11 integration disabled because X11 is not running\n");
    109       return NULL;
    110     }
    111 
    112   /* remove the screen part of the display name */
    113   p = strrchr (display, ':');
    114   if (p != NULL)
    115     {
    116       for ( ; *p; ++p)
    117         {
    118           if (*p == '.')
    119             {
    120               *p = '\0';
    121               break;
    122             }
    123         }
    124     }
    125 
    126   /* Note that we leave the hostname in the display most of the
    127    * time. The idea is that we want to be per-(machine,display,user)
    128    * triplet to be extra-sure we get a bus we can connect to. Ideally
    129    * we'd recognize when the hostname matches the machine we're on in
    130    * all cases; we do try to drop localhost and localhost.localdomain
    131    * as a special common case so that alternate spellings of DISPLAY
    132    * don't result in extra bus instances.
    133    *
    134    * We also kill the ":" if there's nothing in front of it. This
    135    * avoids an ugly double underscore in the filename.
    136    */
    137   remove_prefix (display, "localhost.localdomain:");
    138   remove_prefix (display, "localhost:");
    139   remove_prefix (display, ":");
    140 
    141   /* replace the : in the display with _ if the : is still there.
    142    * use _ instead of - since it can't be in hostnames.
    143    */
    144   for (p = display; *p; ++p)
    145     {
    146       if (*p == ':')
    147         *p = '_';
    148     }
    149 
    150   home = get_homedir ();
    151 
    152   result = malloc (strlen (home) + strlen (prefix) + strlen (machine) +
    153                    strlen (display) + 2);
    154   if (result == NULL)
    155     {
    156       /* out of memory */
    157       free (display);
    158       return NULL;
    159     }
    160 
    161   strcpy (result, home);
    162   strcat (result, prefix);
    163   strcat (result, machine);
    164   strcat (result, "-");
    165   strcat (result, display);
    166   free (display);
    167 
    168   verbose ("session file: %s\n", result);
    169   return result;
    170 }
    171 
    172 static void
    173 ensure_session_directory (void)
    174 {
    175   const char *home;
    176   char *dir;
    177 
    178   home = get_homedir ();
    179 
    180   /* be sure we have space for / and nul */
    181   dir = malloc (strlen (home) + strlen (DBUS_DIR) + strlen (DBUS_SESSION_BUS_DIR) + 3);
    182   if (dir == NULL)
    183     {
    184       fprintf (stderr, "no memory\n");
    185       exit (1);
    186     }
    187 
    188   strcpy (dir, home);
    189   strcat (dir, "/");
    190   strcat (dir, DBUS_DIR);
    191 
    192   if (mkdir (dir, 0700) < 0)
    193     {
    194       /* only print a warning here, writing the session file itself will fail later */
    195       if (errno != EEXIST)
    196         fprintf (stderr, "Unable to create %s\n", dir);
    197     }
    198 
    199   strcat (dir, "/");
    200   strcat (dir, DBUS_SESSION_BUS_DIR);
    201 
    202   if (mkdir (dir, 0700) < 0)
    203     {
    204       /* only print a warning here, writing the session file itself will fail later */
    205       if (errno != EEXIST)
    206         fprintf (stderr, "Unable to create %s\n", dir);
    207     }
    208 
    209   free (dir);
    210 }
    211 
    212 static Display *
    213 open_x11 (void)
    214 {
    215   if (xdisplay != NULL)
    216     return xdisplay;
    217 
    218   xdisplay = XOpenDisplay (NULL);
    219   if (xdisplay != NULL)
    220     {
    221       verbose ("Connected to X11 display '%s'\n", DisplayString (xdisplay));
    222       XSetIOErrorHandler (x_io_error_handler);
    223     }
    224   return xdisplay;
    225 }
    226 
    227 static int
    228 init_x_atoms (Display *display)
    229 {
    230   static const char selection_prefix[] = "_DBUS_SESSION_BUS_SELECTION_";
    231   static const char address_prefix[] = "_DBUS_SESSION_BUS_ADDRESS";
    232   static const char pid_prefix[] = "_DBUS_SESSION_BUS_PID";
    233   static int init = FALSE;
    234   char *atom_name;
    235   const char *machine;
    236   char *user_name;
    237   struct passwd *user;
    238 
    239   if (init)
    240     return TRUE;
    241 
    242   machine = get_machine_uuid ();
    243   if (machine == NULL)
    244     return FALSE;
    245 
    246   user = getpwuid (getuid ());
    247   if (user == NULL)
    248     {
    249       verbose ("Could not determine the user informations; aborting X11 integration.\n");
    250       return FALSE;
    251     }
    252   user_name = xstrdup(user->pw_name);
    253 
    254   atom_name = malloc (strlen (machine) + strlen (user_name) + 2 +
    255                       MAX (strlen (selection_prefix),
    256                            MAX (strlen (address_prefix),
    257                                 strlen (pid_prefix))));
    258   if (atom_name == NULL)
    259     {
    260       verbose ("Could not create X11 atoms; aborting X11 integration.\n");
    261       free (user_name);
    262       return FALSE;
    263     }
    264 
    265   /* create the selection atom */
    266   strcpy (atom_name, selection_prefix);
    267   strcat (atom_name, user_name);
    268   strcat (atom_name, "_");
    269   strcat (atom_name, machine);
    270   selection_atom = XInternAtom (display, atom_name, FALSE);
    271 
    272   /* create the address property atom */
    273   strcpy (atom_name, address_prefix);
    274   address_atom = XInternAtom (display, atom_name, FALSE);
    275 
    276   /* create the PID property atom */
    277   strcpy (atom_name, pid_prefix);
    278   pid_atom = XInternAtom (display, atom_name, FALSE);
    279 
    280   free (atom_name);
    281   free (user_name);
    282   init = TRUE;
    283   return TRUE;
    284 }
    285 
    286 /*
    287  * Gets the daemon address from the X11 display.
    288  * Returns FALSE if there was an error. Returning
    289  * TRUE does not mean the address exists.
    290  */
    291 int
    292 x11_get_address (char **paddress, pid_t *pid, long *wid)
    293 {
    294   Atom type;
    295   Window owner;
    296   int format;
    297   unsigned long items;
    298   unsigned long after;
    299   char *data;
    300 
    301   *paddress = NULL;
    302 
    303   /* locate the selection owner */
    304   owner = XGetSelectionOwner (xdisplay, selection_atom);
    305   if (owner == None)
    306     return TRUE;                /* no owner */
    307   if (wid != NULL)
    308     *wid = (long) owner;
    309 
    310   /* get the bus address */
    311   XGetWindowProperty (xdisplay, owner, address_atom, 0, 1024, False,
    312                       XA_STRING, &type, &format, &items, &after,
    313                       (unsigned char **) &data);
    314   if (type == None || after != 0 || data == NULL || format != 8)
    315     return FALSE;               /* error */
    316 
    317   *paddress = xstrdup (data);
    318   XFree (data);
    319 
    320   /* get the PID */
    321   if (pid != NULL)
    322     {
    323       *pid = 0;
    324       XGetWindowProperty (xdisplay, owner, pid_atom, 0, sizeof pid, False,
    325                           XA_CARDINAL, &type, &format, &items, &after,
    326                           (unsigned char **) &data);
    327       if (type != None && after == 0 && data != NULL && format == 32)
    328         *pid = (pid_t) *(long*) data;
    329       XFree (data);
    330     }
    331 
    332   return TRUE;                  /* success */
    333 }
    334 
    335 /*
    336  * Saves the address in the X11 display. Returns 0 on success.
    337  * If an error occurs, returns -1. If the selection already exists,
    338  * returns 1. (i.e. another daemon is already running)
    339  */
    340 static Window
    341 set_address_in_x11(char *address, pid_t pid)
    342 {
    343   char *current_address;
    344   Window wid;
    345   int pid32;
    346 
    347   /* lock the X11 display to make sure we're doing this atomically */
    348   XGrabServer (xdisplay);
    349 
    350   if (!x11_get_address (&current_address, NULL, NULL))
    351     {
    352       /* error! */
    353       XUngrabServer (xdisplay);
    354       return None;
    355     }
    356 
    357   if (current_address != NULL)
    358     {
    359       /* someone saved the address in the meantime */
    360       XUngrabServer (xdisplay);
    361       free (current_address);
    362       return None;
    363     }
    364 
    365   /* Create our window */
    366   wid = XCreateSimpleWindow (xdisplay, RootWindow (xdisplay, 0), -20, -20, 10, 10,
    367                              0, WhitePixel (xdisplay, 0),
    368                              BlackPixel (xdisplay, 0));
    369   verbose ("Created window %d\n", wid);
    370 
    371   /* Save the property in the window */
    372   XChangeProperty (xdisplay, wid, address_atom, XA_STRING, 8, PropModeReplace,
    373                    (unsigned char *)address, strlen (address));
    374   pid32 = pid;
    375   if (sizeof(pid32) != 4)
    376     {
    377       fprintf (stderr, "int is not 32 bits!\n");
    378       exit (1);
    379     }
    380   XChangeProperty (xdisplay, wid, pid_atom, XA_CARDINAL, 32, PropModeReplace,
    381                    (unsigned char *)&pid32, 1);
    382 
    383   /* Now grab the selection */
    384   XSetSelectionOwner (xdisplay, selection_atom, wid, CurrentTime);
    385 
    386   /* Ungrab the server to let other people use it too */
    387   XUngrabServer (xdisplay);
    388 
    389   XFlush (xdisplay);
    390 
    391   return wid;
    392 }
    393 
    394 /*
    395  * Saves the session address in session file. Returns TRUE on
    396  * success, FALSE if an error occurs.
    397  */
    398 static int
    399 set_address_in_file (char *address, pid_t pid, Window wid)
    400 {
    401   char *session_file;
    402   FILE *f;
    403 
    404   ensure_session_directory ();
    405   session_file = get_session_file();
    406   if (session_file == NULL)
    407     return FALSE;
    408 
    409   f = fopen (session_file, "w");
    410   if (f == NULL)
    411     return FALSE;               /* some kind of error */
    412   fprintf (f,
    413            "# This file allows processes on the machine with id %s using \n"
    414            "# display %s to find the D-Bus session bus with the below address.\n"
    415            "# If the DBUS_SESSION_BUS_ADDRESS environment variable is set, it will\n"
    416            "# be used rather than this file.\n"
    417            "# See \"man dbus-launch\" for more details.\n"
    418            "DBUS_SESSION_BUS_ADDRESS=%s\n"
    419            "DBUS_SESSION_BUS_PID=%ld\n"
    420            "DBUS_SESSION_BUS_WINDOWID=%ld\n",
    421            get_machine_uuid (),
    422            getenv ("DISPLAY"),
    423            address, (long)pid, (long)wid);
    424 
    425   fclose (f);
    426   free (session_file);
    427 
    428   return TRUE;
    429 }
    430 
    431 int
    432 x11_save_address (char *address, pid_t pid, long *wid)
    433 {
    434   Window id = set_address_in_x11 (address, pid);
    435   if (id != None)
    436     {
    437       if (!set_address_in_file (address, pid, id))
    438         return FALSE;
    439 
    440       if (wid != NULL)
    441         *wid = (long) id;
    442       return TRUE;
    443     }
    444   return FALSE;
    445 }
    446 
    447 int
    448 x11_init (void)
    449 {
    450   return open_x11 () != NULL && init_x_atoms (xdisplay);
    451 }
    452 
    453 void
    454 x11_handle_event (void)
    455 {
    456   if (xdisplay != NULL)
    457     {
    458       while (XPending (xdisplay))
    459         {
    460           XEvent ignored;
    461           XNextEvent (xdisplay, &ignored);
    462         }
    463     }
    464 }
    465 
    466 #else
    467 void dummy_dbus_launch_x11 (void) { }
    468 #endif
    469