/* fdleak.c -- detect file descriptor leaks Copyright (C) 2010, 2011 Free Software Foundation, Inc. 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 3 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, see . */ /* config.h must be included first. */ #include /* system headers. */ #include #include #include #include #include #include #include #include #if HAVE_GETRLIMIT # include #endif #include /* gnulib headers. */ #include "cloexec.h" #include "dirent-safer.h" #include "error.h" #include "fcntl--.h" #include "gettext.h" /* find headers. */ #include "extendbuf.h" #include "fdleak.h" #include "safe-atoi.h" #if ENABLE_NLS # include # define _(Text) gettext (Text) #else # define _(Text) Text #endif #ifdef gettext_noop # define N_(String) gettext_noop (String) #else /* See locate.c for explanation as to why not use (String) */ # define N_(String) String #endif /* In order to detect FD leaks, we take a snapshot of the open * file descriptors which are not FD_CLOEXEC when the program starts. * When the program exits, we discover if there are any new * file descriptors which aren't FD_CLOEXEC. */ static int *non_cloexec_fds; static size_t num_cloexec_fds; /* Determine the value of the largest open fd, on systems that * offer /proc/self/fd. */ static int get_proc_max_fd (void) { const char *path = "/proc/self/fd"; int maxfd = -1; /* We don't use readdir_r, because we cannot trust pathconf * to tell us the maximum possible length of a path in * a given directory (the manpage for readdir_r claims this * is the approved method, but the manpage for pathconf indicates * that _PC_NAME_MAX is not an upper limit). */ DIR *dir = opendir_safer (path); if (dir) { int good = 0; struct dirent *dent; while ((dent=readdir (dir)) != NULL) { if (dent->d_name[0] != '.' || (dent->d_name[0] != 0 && dent->d_name[1] != 0 && dent->d_name[1] != '.')) { const int fd = safe_atoi (dent->d_name, literal_quoting_style); if (fd > maxfd) maxfd = fd; good = 1; } } closedir (dir); if (good) return maxfd; } return -1; } /* Estimate the value of the largest possible file descriptor */ static int get_max_fd (void) { struct rlimit fd_limit; long open_max; open_max = get_proc_max_fd (); if (open_max >= 0) return open_max; open_max = sysconf (_SC_OPEN_MAX); if (open_max == -1) open_max = _POSIX_OPEN_MAX; /* underestimate */ /* We assume if RLIMIT_NOFILE is defined, all the related macros are, too. */ #if defined HAVE_GETRLIMIT && defined RLIMIT_NOFILE if (0 == getrlimit (RLIMIT_NOFILE, &fd_limit)) { if (fd_limit.rlim_cur == RLIM_INFINITY) return open_max; else return (int) fd_limit.rlim_cur; } #endif /* cannot determine the limit's value */ return open_max; } static int visit_open_fds (int fd_min, int fd_max, int (*callback)(int, void*), void *cb_context) { enum { MAX_POLL = 64 }; struct pollfd pf[MAX_POLL]; int rv = 0; while (fd_min < fd_max) { int i; int limit = fd_max - fd_min; if (limit > MAX_POLL) limit = MAX_POLL; for (i=0; ibuf, sizeof (p->buf[0])*(p->used+1), &(p->allocated)); if (newbuf) { p->buf = newbuf; p->buf[p->used] = fd; ++p->used; return 0; } else { return -1; } } } void remember_non_cloexec_fds (void) { int max_fd = get_max_fd (); struct remember_fd_context cb_data; cb_data.buf = NULL; cb_data.used = cb_data.allocated = 0; if (max_fd < INT_MAX) ++max_fd; visit_open_fds (0, max_fd, remember_fd_if_non_cloexec, &cb_data); non_cloexec_fds = cb_data.buf; num_cloexec_fds = cb_data.used; } struct fd_leak_context { const int *prev_buf; size_t used; size_t lookup_pos; int leaked_fd; }; /* FD is open and not close-on-exec. * If it's not in the list of non-cloexec file descriptors we saw before, it's a leak. */ static int find_first_leak_callback (int fd, void *context) { if (!fd_is_cloexec (fd)) { struct fd_leak_context *p = context; while (p->lookup_pos < p->used) { if (p->prev_buf[p->lookup_pos] < fd) { ++p->lookup_pos; } else if (p->prev_buf[p->lookup_pos] == fd) { /* FD was open and still is, it's not a leak. */ return 0; } else { break; } } /* We come here if p->prev_buf[p->lookup_pos] > fd, or if we ran out of items in the lookup table. Either way, this is a leak. */ p->leaked_fd = fd; return -1; /* No more callbacks needed. */ } return 0; } static int find_first_leaked_fd (const int* prev_non_cloexec_fds, size_t n) { struct fd_leak_context context; int max_fd = get_max_fd (); if (max_fd < INT_MAX) ++max_fd; context.prev_buf = prev_non_cloexec_fds; context.used = n; context.lookup_pos = 0; context.leaked_fd = -1; visit_open_fds (0, max_fd, find_first_leak_callback, &context); return context.leaked_fd; } /* Determine if O_CLOEXEC actually works (Savannah bug #29435: fd_is_cloexec () does not work on Fedora buildhosts). */ static bool o_cloexec_works (void) { bool result = false; int fd = open ("/", O_RDONLY|O_CLOEXEC); if (fd >= 0) { result = fd_is_cloexec (fd); close (fd); } return result; } int open_cloexec (const char *path, int flags, ...) { int fd; mode_t mode = 0; static bool cloexec_works = false; static bool cloexec_status_known = false; if (flags & O_CREAT) { /* this code is copied from gnulib's open-safer.c. */ va_list ap; va_start (ap, flags); /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4 creates crashing code when 'mode_t' is smaller than 'int'. */ mode = va_arg (ap, PROMOTED_MODE_T); va_end (ap); } /* Kernels usually ignore open flags they don't recognise, so it * is possible this program was built against a library which * defines O_CLOEXEC, but is running on a kernel that (silently) * does not recognise it. We figure this out by just trying it, * once. */ if (!cloexec_status_known) { cloexec_works = o_cloexec_works (); cloexec_status_known = true; } fd = open (path, flags|O_CLOEXEC, mode); if ((fd >= 0) && !(O_CLOEXEC && cloexec_works)) { set_cloexec_flag (fd, true); } return fd; } void forget_non_cloexec_fds (void) { free (non_cloexec_fds); non_cloexec_fds = NULL; num_cloexec_fds = 0; } void complain_about_leaky_fds (void) { int no_leaks = 1; const int leaking_fd = find_first_leaked_fd (non_cloexec_fds, num_cloexec_fds); if (leaking_fd >= 0) { no_leaks = 0; error (0, 0, _("File descriptor %d will leak; please report this as a bug, " "remembering to include a detailed description of the simplest " "way to reproduce this problem."), leaking_fd); } assert (no_leaks); }