/* -*- 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; } #if 0 static void free_socket_entry (SocketEntry *se) { free (se->name); if (se->fd >= 0) close (se->fd); free (se); } #endif 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); } #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; }