1/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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
50static void*
51xmalloc (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
70static void*
71xrealloc (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
95typedef enum
96  {
97    SOCKET_UNKNOWN,
98    SOCKET_FAILED_TO_HANDLE,
99    SOCKET_DEAD,
100    SOCKET_ALIVE,
101    SOCKET_UNLINKED
102  } SocketStatus;
103
104static int alive_count = 0;
105static int cleaned_count = 0;
106static int unhandled_count = 0;
107
108typedef struct
109{
110  char *name;
111  int   fd;
112  SocketStatus status;
113  int   n_retries;
114} SocketEntry;
115
116static SocketEntry*
117socket_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
142static void
143free_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
152static void
153read_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
200static SocketStatus
201open_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
261static int
262handle_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
334static void
335clean_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
357static void
358usage (int ecode)
359{
360  fprintf (stderr, "dbus-cleanup-sockets [--version] [--help] <socketdir>\n");
361  exit (ecode);
362}
363
364static void
365version (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
376int
377main (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