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*
xmalloc(size_t bytes)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*
xrealloc(void * old,size_t bytes)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*
socket_entry_new(const char * dir,const char * fname)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 static void
free_socket_entry(SocketEntry * se)142 free_socket_entry (SocketEntry *se)
143 {
144 if (se)
145 {
146 free (se->name);
147 if (se->fd >= 0)
148 close (se->fd);
149 free (se);
150 }
151 }
152
153 static void
free_socket_entries(SocketEntry ** entries,int n_entries)154 free_socket_entries (SocketEntry** entries,
155 int n_entries)
156 {
157 int i;
158
159 if (entries)
160 {
161 for (i = 0; i < n_entries; ++i)
162 free_socket_entry (entries[i]);
163 free (entries);
164 }
165 }
166
167 static void
read_sockets(const char * dir,SocketEntry *** entries_p,int * n_entries_p)168 read_sockets (const char *dir,
169 SocketEntry ***entries_p,
170 int *n_entries_p)
171 {
172 DIR *dirh;
173 struct dirent *dent;
174 SocketEntry **entries;
175 int n_entries;
176 int allocated;
177
178 n_entries = 0;
179 allocated = 2;
180 entries = xmalloc (sizeof (SocketEntry*) * allocated);
181
182 dirh = opendir (dir);
183 if (dirh == NULL)
184 {
185 fprintf (stderr, "Failed to open directory %s: %s\n",
186 dir, strerror (errno));
187 exit (1);
188 }
189
190 while ((dent = readdir (dirh)))
191 {
192 SocketEntry *se;
193
194 if (strncmp (dent->d_name, "dbus-", 5) != 0)
195 continue;
196
197 se = socket_entry_new (dir, dent->d_name);
198
199 if (n_entries == allocated)
200 {
201 allocated *= 2;
202 entries = xrealloc (entries, sizeof (SocketEntry*) * allocated);
203 }
204
205 entries[n_entries] = se;
206 n_entries += 1;
207 }
208
209 closedir (dirh);
210
211 *entries_p = entries;
212 *n_entries_p = n_entries;
213 }
214
215 static SocketStatus
open_socket(SocketEntry * se)216 open_socket (SocketEntry *se)
217 {
218 int ret;
219 struct sockaddr_un saddr;
220
221 if (se->n_retries > 5)
222 {
223 fprintf (stderr, "Warning: giving up on socket %s after several retries; unable to determine socket's status\n",
224 se->name);
225 return SOCKET_FAILED_TO_HANDLE;
226 }
227
228 se->n_retries += 1;
229
230 se->fd = socket (AF_UNIX, SOCK_STREAM, 0);
231 if (se->fd < 0)
232 {
233 fprintf (stderr, "Warning: failed to open a socket to use for connecting: %s\n",
234 strerror (errno));
235 return SOCKET_UNKNOWN;
236 }
237
238 if (fcntl (se->fd, F_SETFL, O_NONBLOCK) < 0)
239 {
240 fprintf (stderr, "Warning: failed set socket %s nonblocking: %s\n",
241 se->name, strerror (errno));
242 return SOCKET_UNKNOWN;
243 }
244
245
246 memset (&saddr, '\0', sizeof (saddr)); /* nul-terminates the sun_path */
247
248 saddr.sun_family = AF_UNIX;
249 strncpy (saddr.sun_path, se->name, sizeof (saddr.sun_path) - 1);
250
251 do
252 {
253 ret = connect (se->fd, (struct sockaddr*) &saddr, sizeof (saddr));
254 }
255 while (ret < 0 && errno == EINTR);
256
257 if (ret >= 0)
258 return SOCKET_ALIVE;
259 else
260 {
261 switch (errno)
262 {
263 case EINPROGRESS:
264 case EAGAIN:
265 return SOCKET_UNKNOWN;
266 case ECONNREFUSED:
267 return SOCKET_DEAD;
268 default:
269 fprintf (stderr, "Warning: unexpected error connecting to socket %s: %s\n",
270 se->name, strerror (errno));
271 return SOCKET_FAILED_TO_HANDLE;
272 }
273 }
274 }
275
276 static int
handle_sockets(SocketEntry ** entries,int n_entries)277 handle_sockets (SocketEntry **entries,
278 int n_entries)
279 {
280 int i;
281 int n_unknown;
282
283 n_unknown = 0;
284
285 i = 0;
286 while (i < n_entries)
287 {
288 SocketEntry *se;
289 SocketStatus status;
290
291 se = entries[i];
292 ++i;
293
294 if (se->fd >= 0)
295 {
296 fprintf (stderr, "Internal error, socket has fd kept open while status = %d\n",
297 se->status);
298 exit (1);
299 }
300
301 if (se->status != SOCKET_UNKNOWN)
302 continue;
303
304 status = open_socket (se);
305
306 switch (status)
307 {
308 case SOCKET_DEAD:
309 cleaned_count += 1;
310 if (unlink (se->name) < 0)
311 {
312 fprintf (stderr, "Warning: Failed to delete %s: %s\n",
313 se->name, strerror (errno));
314
315 se->status = SOCKET_FAILED_TO_HANDLE;
316 }
317 else
318 se->status = SOCKET_UNLINKED;
319 break;
320
321 case SOCKET_ALIVE:
322 alive_count += 1;
323 /* FALL THRU */
324
325 case SOCKET_FAILED_TO_HANDLE:
326 case SOCKET_UNKNOWN:
327 se->status = status;
328 break;
329
330 case SOCKET_UNLINKED:
331 fprintf (stderr, "Bad status from open_socket(), should not happen\n");
332 exit (1);
333 break;
334 }
335
336 if (se->fd >= 0)
337 {
338 close (se->fd);
339 se->fd = -1;
340 }
341
342 if (se->status == SOCKET_UNKNOWN)
343 n_unknown += 1;
344 }
345
346 return n_unknown == 0;
347 }
348
349 static void
clean_dir(const char * dir)350 clean_dir (const char *dir)
351 {
352 SocketEntry **entries;
353 int n_entries;
354
355 read_sockets (dir, &entries, &n_entries);
356
357 /* open_socket() will fail conclusively after
358 * several retries, so this loop is guaranteed
359 * to terminate eventually
360 */
361 while (!handle_sockets (entries, n_entries))
362 {
363 fprintf (stderr, "Unable to determine state of some sockets, retrying in 2 seconds\n");
364 sleep (2);
365 }
366
367 unhandled_count += (n_entries - alive_count - cleaned_count);
368
369 free_socket_entries (entries, n_entries);
370 }
371
372 #endif /* AF_UNIX */
373
374 static void
usage(int ecode)375 usage (int ecode)
376 {
377 fprintf (stderr, "dbus-cleanup-sockets [--version] [--help] <socketdir>\n");
378 exit (ecode);
379 }
380
381 static void
version(void)382 version (void)
383 {
384 printf ("D-Bus Socket Cleanup Utility %s\n"
385 "Copyright (C) 2003 Red Hat, Inc.\n"
386 "Copyright (C) 2002 Michael Meeks\n"
387 "This is free software; see the source for copying conditions.\n"
388 "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
389 VERSION);
390 exit (0);
391 }
392
393 int
main(int argc,char ** argv)394 main (int argc, char **argv)
395 {
396 int i;
397 int saw_doubledash;
398 const char *dirname;
399
400 saw_doubledash = FALSE;
401 dirname = NULL;
402 i = 1;
403 while (i < argc)
404 {
405 const char *arg = argv[i];
406
407 if (strcmp (arg, "--help") == 0 ||
408 strcmp (arg, "-h") == 0 ||
409 strcmp (arg, "-?") == 0)
410 usage (0);
411 else if (strcmp (arg, "--version") == 0)
412 version ();
413 else if (!saw_doubledash)
414 {
415 if (strcmp (arg, "--") == 0)
416 saw_doubledash = TRUE;
417 else if (*arg == '-')
418 usage (1);
419 }
420 else
421 {
422 if (dirname != NULL)
423 {
424 fprintf (stderr, "dbus-cleanup-sockets only supports a single directory name\n");
425 exit (1);
426 }
427
428 dirname = arg;
429 }
430
431 ++i;
432 }
433
434 /* Default to session socket dir, usually /tmp */
435 if (dirname == NULL)
436 dirname = DBUS_SESSION_SOCKET_DIR;
437
438 #ifdef AF_UNIX
439 clean_dir (dirname);
440
441 printf ("Cleaned up %d sockets in %s; %d sockets are still in use; %d in unknown state\n",
442 cleaned_count, dirname, alive_count, unhandled_count);
443 #else
444 printf ("This system does not support UNIX domain sockets, so dbus-cleanup-sockets does nothing\n");
445 #endif
446
447 return 0;
448 }
449