/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* dbus-cleanup-sockets.c dbus-cleanup-sockets utility
*
* Copyright (C) 2003 Red Hat, Inc.
* Copyright (C) 2002 Michael Meeks
*
* Note that this file is NOT licensed under the Academic Free License,
* as it is based on linc-cleanup-sockets which is LGPL.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifndef TRUE
#define TRUE (1)
#endif
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef NULL
#define NULL ((void*) 0)
#endif
static void*
xmalloc (size_t bytes)
{
void *mem;
if (bytes == 0)
return NULL;
mem = malloc (bytes);
if (mem == NULL)
{
fprintf (stderr, "Allocation of %d bytes failed\n",
(int) bytes);
exit (1);
}
return mem;
}
static void*
xrealloc (void *old, size_t bytes)
{
void *mem;
if (bytes == 0)
{
free (old);
return NULL;
}
mem = realloc (old, bytes);
if (mem == NULL)
{
fprintf (stderr, "Reallocation of %d bytes failed\n",
(int) bytes);
exit (1);
}
return mem;
}
#ifdef AF_UNIX
typedef enum
{
SOCKET_UNKNOWN,
SOCKET_FAILED_TO_HANDLE,
SOCKET_DEAD,
SOCKET_ALIVE,
SOCKET_UNLINKED
} SocketStatus;
static int alive_count = 0;
static int cleaned_count = 0;
static int unhandled_count = 0;
typedef struct
{
char *name;
int fd;
SocketStatus status;
int n_retries;
} SocketEntry;
static SocketEntry*
socket_entry_new (const char *dir,
const char *fname)
{
SocketEntry *se;
int len;
se = xmalloc (sizeof (SocketEntry));
len = strlen (dir) + strlen (fname) + 2; /* 2 = nul and '/' */
se->name = xmalloc (len);
strcpy (se->name, dir);
strcat (se->name, "/");
strcat (se->name, fname);
se->fd = -1;
se->status = SOCKET_UNKNOWN;
se->n_retries = 0;
return se;
}
static void
free_socket_entry (SocketEntry *se)
{
if (se)
{
free (se->name);
if (se->fd >= 0)
close (se->fd);
free (se);
}
}
static void
free_socket_entries (SocketEntry** entries,
int n_entries)
{
int i;
if (entries)
{
for (i = 0; i < n_entries; ++i)
free_socket_entry (entries[i]);
free (entries);
}
}
static void
read_sockets (const char *dir,
SocketEntry ***entries_p,
int *n_entries_p)
{
DIR *dirh;
struct dirent *dent;
SocketEntry **entries;
int n_entries;
int allocated;
n_entries = 0;
allocated = 2;
entries = xmalloc (sizeof (SocketEntry*) * allocated);
dirh = opendir (dir);
if (dirh == NULL)
{
fprintf (stderr, "Failed to open directory %s: %s\n",
dir, strerror (errno));
exit (1);
}
while ((dent = readdir (dirh)))
{
SocketEntry *se;
if (strncmp (dent->d_name, "dbus-", 5) != 0)
continue;
se = socket_entry_new (dir, dent->d_name);
if (n_entries == allocated)
{
allocated *= 2;
entries = xrealloc (entries, sizeof (SocketEntry*) * allocated);
}
entries[n_entries] = se;
n_entries += 1;
}
closedir (dirh);
*entries_p = entries;
*n_entries_p = n_entries;
}
static SocketStatus
open_socket (SocketEntry *se)
{
int ret;
struct sockaddr_un saddr;
if (se->n_retries > 5)
{
fprintf (stderr, "Warning: giving up on socket %s after several retries; unable to determine socket's status\n",
se->name);
return SOCKET_FAILED_TO_HANDLE;
}
se->n_retries += 1;
se->fd = socket (AF_UNIX, SOCK_STREAM, 0);
if (se->fd < 0)
{
fprintf (stderr, "Warning: failed to open a socket to use for connecting: %s\n",
strerror (errno));
return SOCKET_UNKNOWN;
}
if (fcntl (se->fd, F_SETFL, O_NONBLOCK) < 0)
{
fprintf (stderr, "Warning: failed set socket %s nonblocking: %s\n",
se->name, strerror (errno));
return SOCKET_UNKNOWN;
}
memset (&saddr, '\0', sizeof (saddr)); /* nul-terminates the sun_path */
saddr.sun_family = AF_UNIX;
strncpy (saddr.sun_path, se->name, sizeof (saddr.sun_path) - 1);
do
{
ret = connect (se->fd, (struct sockaddr*) &saddr, sizeof (saddr));
}
while (ret < 0 && errno == EINTR);
if (ret >= 0)
return SOCKET_ALIVE;
else
{
switch (errno)
{
case EINPROGRESS:
case EAGAIN:
return SOCKET_UNKNOWN;
case ECONNREFUSED:
return SOCKET_DEAD;
default:
fprintf (stderr, "Warning: unexpected error connecting to socket %s: %s\n",
se->name, strerror (errno));
return SOCKET_FAILED_TO_HANDLE;
}
}
}
static int
handle_sockets (SocketEntry **entries,
int n_entries)
{
int i;
int n_unknown;
n_unknown = 0;
i = 0;
while (i < n_entries)
{
SocketEntry *se;
SocketStatus status;
se = entries[i];
++i;
if (se->fd >= 0)
{
fprintf (stderr, "Internal error, socket has fd kept open while status = %d\n",
se->status);
exit (1);
}
if (se->status != SOCKET_UNKNOWN)
continue;
status = open_socket (se);
switch (status)
{
case SOCKET_DEAD:
cleaned_count += 1;
if (unlink (se->name) < 0)
{
fprintf (stderr, "Warning: Failed to delete %s: %s\n",
se->name, strerror (errno));
se->status = SOCKET_FAILED_TO_HANDLE;
}
else
se->status = SOCKET_UNLINKED;
break;
case SOCKET_ALIVE:
alive_count += 1;
/* FALL THRU */
case SOCKET_FAILED_TO_HANDLE:
case SOCKET_UNKNOWN:
se->status = status;
break;
case SOCKET_UNLINKED:
fprintf (stderr, "Bad status from open_socket(), should not happen\n");
exit (1);
break;
}
if (se->fd >= 0)
{
close (se->fd);
se->fd = -1;
}
if (se->status == SOCKET_UNKNOWN)
n_unknown += 1;
}
return n_unknown == 0;
}
static void
clean_dir (const char *dir)
{
SocketEntry **entries;
int n_entries;
read_sockets (dir, &entries, &n_entries);
/* open_socket() will fail conclusively after
* several retries, so this loop is guaranteed
* to terminate eventually
*/
while (!handle_sockets (entries, n_entries))
{
fprintf (stderr, "Unable to determine state of some sockets, retrying in 2 seconds\n");
sleep (2);
}
unhandled_count += (n_entries - alive_count - cleaned_count);
free_socket_entries (entries, n_entries);
}
#endif /* AF_UNIX */
static void
usage (int ecode)
{
fprintf (stderr, "dbus-cleanup-sockets [--version] [--help] <socketdir>\n");
exit (ecode);
}
static void
version (void)
{
printf ("D-Bus Socket Cleanup Utility %s\n"
"Copyright (C) 2003 Red Hat, Inc.\n"
"Copyright (C) 2002 Michael Meeks\n"
"This is free software; see the source for copying conditions.\n"
"There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
VERSION);
exit (0);
}
int
main (int argc, char **argv)
{
int i;
int saw_doubledash;
const char *dirname;
saw_doubledash = FALSE;
dirname = NULL;
i = 1;
while (i < argc)
{
const char *arg = argv[i];
if (strcmp (arg, "--help") == 0 ||
strcmp (arg, "-h") == 0 ||
strcmp (arg, "-?") == 0)
usage (0);
else if (strcmp (arg, "--version") == 0)
version ();
else if (!saw_doubledash)
{
if (strcmp (arg, "--") == 0)
saw_doubledash = TRUE;
else if (*arg == '-')
usage (1);
}
else
{
if (dirname != NULL)
{
fprintf (stderr, "dbus-cleanup-sockets only supports a single directory name\n");
exit (1);
}
dirname = arg;
}
++i;
}
/* Default to session socket dir, usually /tmp */
if (dirname == NULL)
dirname = DBUS_SESSION_SOCKET_DIR;
#ifdef AF_UNIX
clean_dir (dirname);
printf ("Cleaned up %d sockets in %s; %d sockets are still in use; %d in unknown state\n",
cleaned_count, dirname, alive_count, unhandled_count);
#else
printf ("This system does not support UNIX domain sockets, so dbus-cleanup-sockets does nothing\n");
#endif
return 0;
}