Home | History | Annotate | Download | only in tools
      1 /* -*- mode: C; c-file-style: "gnu" -*- */
      2 /* dbus-cleanup-sockets.c  dbus-cleanup-sockets utility
      3  *
      4  * Copyright (C) 2003 Red Hat, Inc.
      5  * Copyright (C) 2002 Michael Meeks
      6  *
      7  * Note that this file is NOT licensed under the Academic Free License,
      8  * as it is based on linc-cleanup-sockets which is LGPL.
      9  *
     10  * This program is free software; you can redistribute it and/or modify
     11  * it under the terms of the GNU General Public License as published by
     12  * the Free Software Foundation; either version 2 of the License, or
     13  * (at your option) any later version.
     14  *
     15  * This program is distributed in the hope that it will be useful,
     16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18  * GNU General Public License for more details.
     19  *
     20  * You should have received a copy of the GNU General Public License
     21  * along with this program; if not, write to the Free Software
     22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     23  *
     24  */
     25 #include <config.h>
     26 #include <sys/types.h>
     27 #include <sys/stat.h>
     28 #include <stdio.h>
     29 #include <fcntl.h>
     30 #include <unistd.h>
     31 #include <dirent.h>
     32 #include <sys/socket.h>
     33 #include <sys/un.h>
     34 #include <errno.h>
     35 #include <stdlib.h>
     36 #include <string.h>
     37 
     38 #ifndef TRUE
     39 #define TRUE (1)
     40 #endif
     41 
     42 #ifndef FALSE
     43 #define FALSE (0)
     44 #endif
     45 
     46 #ifndef NULL
     47 #define NULL ((void*) 0)
     48 #endif
     49 
     50 static void*
     51 xmalloc (size_t bytes)
     52 {
     53   void *mem;
     54 
     55   if (bytes == 0)
     56     return NULL;
     57 
     58   mem = malloc (bytes);
     59 
     60   if (mem == NULL)
     61     {
     62       fprintf (stderr, "Allocation of %d bytes failed\n",
     63                (int) bytes);
     64       exit (1);
     65     }
     66 
     67   return mem;
     68 }
     69 
     70 static void*
     71 xrealloc (void *old, size_t bytes)
     72 {
     73   void *mem;
     74 
     75   if (bytes == 0)
     76     {
     77       free (old);
     78       return NULL;
     79     }
     80 
     81   mem = realloc (old, bytes);
     82 
     83   if (mem == NULL)
     84     {
     85       fprintf (stderr, "Reallocation of %d bytes failed\n",
     86                (int) bytes);
     87       exit (1);
     88     }
     89 
     90   return mem;
     91 }
     92 
     93 #ifdef AF_UNIX
     94 
     95 typedef enum
     96   {
     97     SOCKET_UNKNOWN,
     98     SOCKET_FAILED_TO_HANDLE,
     99     SOCKET_DEAD,
    100     SOCKET_ALIVE,
    101     SOCKET_UNLINKED
    102   } SocketStatus;
    103 
    104 static int alive_count = 0;
    105 static int cleaned_count = 0;
    106 static int unhandled_count = 0;
    107 
    108 typedef struct
    109 {
    110   char *name;
    111   int   fd;
    112   SocketStatus status;
    113   int   n_retries;
    114 } SocketEntry;
    115 
    116 static SocketEntry*
    117 socket_entry_new (const char *dir,
    118                   const char *fname)
    119 {
    120   SocketEntry *se;
    121   int len;
    122 
    123   se = xmalloc (sizeof (SocketEntry));
    124 
    125   len = strlen (dir) + strlen (fname) + 2; /* 2 = nul and '/' */
    126   se->name = xmalloc (len);
    127 
    128   strcpy (se->name, dir);
    129   strcat (se->name, "/");
    130   strcat (se->name, fname);
    131 
    132   se->fd = -1;
    133 
    134   se->status = SOCKET_UNKNOWN;
    135 
    136   se->n_retries = 0;
    137 
    138   return se;
    139 }
    140 
    141 #if 0
    142 static void
    143 free_socket_entry (SocketEntry *se)
    144 {
    145   free (se->name);
    146   if (se->fd >= 0)
    147     close (se->fd);
    148   free (se);
    149 }
    150 #endif
    151 
    152 static void
    153 read_sockets (const char    *dir,
    154               SocketEntry ***entries_p,
    155               int           *n_entries_p)
    156 {
    157   DIR   *dirh;
    158   struct dirent *dent;
    159   SocketEntry **entries;
    160   int n_entries;
    161   int allocated;
    162 
    163   n_entries = 0;
    164   allocated = 2;
    165   entries = xmalloc (sizeof (SocketEntry*) * allocated);
    166 
    167   dirh = opendir (dir);
    168   if (dirh == NULL)
    169     {
    170       fprintf (stderr, "Failed to open directory %s: %s\n",
    171                dir, strerror (errno));
    172       exit (1);
    173     }
    174 
    175   while ((dent = readdir (dirh)))
    176     {
    177       SocketEntry *se;
    178 
    179       if (strncmp (dent->d_name, "dbus-", 5) != 0)
    180         continue;
    181 
    182       se = socket_entry_new (dir, dent->d_name);
    183 
    184       if (n_entries == allocated)
    185         {
    186           allocated *= 2;
    187           entries = xrealloc (entries, sizeof (SocketEntry*) * allocated);
    188         }
    189 
    190       entries[n_entries] = se;
    191       n_entries += 1;
    192     }
    193 
    194   closedir (dirh);
    195 
    196   *entries_p = entries;
    197   *n_entries_p = n_entries;
    198 }
    199 
    200 static SocketStatus
    201 open_socket (SocketEntry *se)
    202 {
    203   int ret;
    204   struct sockaddr_un saddr;
    205 
    206   if (se->n_retries > 5)
    207     {
    208       fprintf (stderr, "Warning: giving up on socket %s after several retries; unable to determine socket's status\n",
    209                se->name);
    210       return SOCKET_FAILED_TO_HANDLE;
    211     }
    212 
    213   se->n_retries += 1;
    214 
    215   se->fd = socket (AF_UNIX, SOCK_STREAM, 0);
    216   if (se->fd < 0)
    217     {
    218       fprintf (stderr, "Warning: failed to open a socket to use for connecting: %s\n",
    219                strerror (errno));
    220       return SOCKET_UNKNOWN;
    221     }
    222 
    223   if (fcntl (se->fd, F_SETFL, O_NONBLOCK) < 0)
    224     {
    225       fprintf (stderr, "Warning: failed set socket %s nonblocking: %s\n",
    226                se->name, strerror (errno));
    227       return SOCKET_UNKNOWN;
    228     }
    229 
    230 
    231   memset (&saddr, '\0', sizeof (saddr)); /* nul-terminates the sun_path */
    232 
    233   saddr.sun_family = AF_UNIX;
    234   strncpy (saddr.sun_path, se->name, sizeof (saddr.sun_path) - 1);
    235 
    236   do
    237     {
    238       ret = connect (se->fd, (struct sockaddr*) &saddr, sizeof (saddr));
    239     }
    240   while (ret < 0 && errno == EINTR);
    241 
    242   if (ret >= 0)
    243     return SOCKET_ALIVE;
    244   else
    245     {
    246       switch (errno)
    247         {
    248         case EINPROGRESS:
    249         case EAGAIN:
    250           return SOCKET_UNKNOWN;
    251         case ECONNREFUSED:
    252           return SOCKET_DEAD;
    253         default:
    254           fprintf (stderr, "Warning: unexpected error connecting to socket %s: %s\n",
    255                    se->name, strerror (errno));
    256           return SOCKET_FAILED_TO_HANDLE;
    257         }
    258     }
    259 }
    260 
    261 static int
    262 handle_sockets (SocketEntry **entries,
    263                 int           n_entries)
    264 {
    265   int i;
    266   int n_unknown;
    267 
    268   n_unknown = 0;
    269 
    270   i = 0;
    271   while (i < n_entries)
    272     {
    273       SocketEntry *se;
    274       SocketStatus status;
    275 
    276       se = entries[i];
    277       ++i;
    278 
    279       if (se->fd >= 0)
    280         {
    281           fprintf (stderr, "Internal error, socket has fd  kept open while status = %d\n",
    282                    se->status);
    283           exit (1);
    284         }
    285 
    286       if (se->status != SOCKET_UNKNOWN)
    287         continue;
    288 
    289       status = open_socket (se);
    290 
    291       switch (status)
    292         {
    293         case SOCKET_DEAD:
    294           cleaned_count += 1;
    295           if (unlink (se->name) < 0)
    296             {
    297               fprintf (stderr, "Warning: Failed to delete %s: %s\n",
    298                        se->name, strerror (errno));
    299 
    300               se->status = SOCKET_FAILED_TO_HANDLE;
    301             }
    302           else
    303             se->status = SOCKET_UNLINKED;
    304           break;
    305 
    306         case SOCKET_ALIVE:
    307           alive_count += 1;
    308           /* FALL THRU */
    309 
    310         case SOCKET_FAILED_TO_HANDLE:
    311         case SOCKET_UNKNOWN:
    312           se->status = status;
    313           break;
    314 
    315         case SOCKET_UNLINKED:
    316           fprintf (stderr, "Bad status from open_socket(), should not happen\n");
    317           exit (1);
    318           break;
    319         }
    320 
    321       if (se->fd >= 0)
    322         {
    323           close (se->fd);
    324           se->fd = -1;
    325         }
    326 
    327       if (se->status == SOCKET_UNKNOWN)
    328         n_unknown += 1;
    329     }
    330 
    331   return n_unknown == 0;
    332 }
    333 
    334 static void
    335 clean_dir (const char *dir)
    336 {
    337   SocketEntry **entries;
    338   int n_entries;
    339 
    340   read_sockets (dir, &entries, &n_entries);
    341 
    342   /* open_socket() will fail conclusively after
    343    * several retries, so this loop is guaranteed
    344    * to terminate eventually
    345    */
    346   while (!handle_sockets (entries, n_entries))
    347     {
    348       fprintf (stderr, "Unable to determine state of some sockets, retrying in 2 seconds\n");
    349       sleep (2);
    350     }
    351 
    352   unhandled_count += (n_entries - alive_count - cleaned_count);
    353 }
    354 
    355 #endif /* AF_UNIX */
    356 
    357 static void
    358 usage (int ecode)
    359 {
    360   fprintf (stderr, "dbus-cleanup-sockets [--version] [--help] <socketdir>\n");
    361   exit (ecode);
    362 }
    363 
    364 static void
    365 version (void)
    366 {
    367   printf ("D-Bus Socket Cleanup Utility %s\n"
    368           "Copyright (C) 2003 Red Hat, Inc.\n"
    369           "Copyright (C) 2002 Michael Meeks\n"
    370           "This is free software; see the source for copying conditions.\n"
    371           "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
    372           VERSION);
    373   exit (0);
    374 }
    375 
    376 int
    377 main (int argc, char **argv)
    378 {
    379   int i;
    380   int saw_doubledash;
    381   const char *dirname;
    382 
    383   saw_doubledash = FALSE;
    384   dirname = NULL;
    385   i = 1;
    386   while (i < argc)
    387     {
    388       const char *arg = argv[i];
    389 
    390       if (strcmp (arg, "--help") == 0 ||
    391           strcmp (arg, "-h") == 0 ||
    392           strcmp (arg, "-?") == 0)
    393         usage (0);
    394       else if (strcmp (arg, "--version") == 0)
    395         version ();
    396       else if (!saw_doubledash)
    397 	{
    398           if (strcmp (arg, "--") == 0)
    399             saw_doubledash = TRUE;
    400           else if (*arg == '-')
    401             usage (1);
    402 	}
    403       else
    404         {
    405           if (dirname != NULL)
    406             {
    407               fprintf (stderr, "dbus-cleanup-sockets only supports a single directory name\n");
    408               exit (1);
    409             }
    410 
    411           dirname = arg;
    412         }
    413 
    414       ++i;
    415     }
    416 
    417   /* Default to session socket dir, usually /tmp */
    418   if (dirname == NULL)
    419     dirname = DBUS_SESSION_SOCKET_DIR;
    420 
    421 #ifdef AF_UNIX
    422   clean_dir (dirname);
    423 
    424   printf ("Cleaned up %d sockets in %s; %d sockets are still in use; %d in unknown state\n",
    425           cleaned_count, dirname, alive_count, unhandled_count);
    426 #else
    427   printf ("This system does not support UNIX domain sockets, so dbus-cleanup-sockets does nothing\n");
    428 #endif
    429 
    430   return 0;
    431 }
    432