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 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