2010-02-25 10:43:11 +00:00
|
|
|
/* fdleak.c -- detect file descriptor leaks
|
2025-01-04 11:43:53 +01:00
|
|
|
Copyright (C) 2010-2025 Free Software Foundation, Inc.
|
2010-02-25 10:43:11 +00:00
|
|
|
|
|
|
|
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
|
all: prefer https:// URLs where possible
Change from http:// to https:// URLs for the following:
www.gnu.org, gnu.org, savannah.gnu.org, git.sv.gnu.org, lists.gnu.org,
translationproject.org, cve.mitre.org, cwe.mitre.org, xkcd.com,
standards.ieee.org, and gcc.gnu.org.
* COPYING: Do the above replacement.
* ChangeLog-2013: Likewise.
* NEWS: Likewise.
* README: Likewise.
* README-hacking: Likewise.
* build-aux/Makefile.am: Likewise.
* build-aux/check-testfiles.sh: Likewise.
* build-aux/gen-changelog.sh: Likewise.
* build-aux/man-lint.sh: Likewise.
* build-aux/src-sniff.py: Likewise.
* cfg.mk: Likewise.
* configure.ac: Likewise.
* doc/Makefile.am: Likewise.
* doc/find-maint.texi: Likewise.
* doc/find.texi: Likewise.
* find/defs.h: Likewise.
* find/exec.c: Likewise.
* find/find.1: Likewise.
* find/finddata.c: Likewise.
* find/fstype.c: Likewise.
* find/ftsfind.c: Likewise.
* find/oldfind.c: Likewise.
* find/parser.c: Likewise.
* find/pred.c: Likewise.
* find/print.c: Likewise.
* find/print.h: Likewise.
* find/sharefile.c: Likewise.
* find/sharefile.h: Likewise.
* find/testsuite/Makefile.am: Likewise.
* find/testsuite/binary_locations.sh: Likewise.
* find/testsuite/checklists.py: Likewise.
* find/testsuite/config/unix.exp: Likewise.
* find/testsuite/find.gnu/name-period.exp: Likewise.
* find/testsuite/find.posix/depth1.exp: Likewise.
* find/testsuite/sv-34079.sh: Likewise.
* find/testsuite/sv-34976-execdir-fd-leak.sh: Likewise.
* find/testsuite/sv-48030-exec-plus-bug.sh: Likewise.
* find/testsuite/sv-48180-refuse-noop.sh: Likewise.
* find/testsuite/sv-52220.sh: Likewise.
* find/testsuite/sv-bug-32043.sh: Likewise.
* find/testsuite/test_escape_c.sh: Likewise.
* find/testsuite/test_escapechars.sh: Likewise.
* find/testsuite/test_inode.sh: Likewise.
* find/testsuite/test_type-list.sh: Likewise.
* find/tree.c: Likewise.
* find/util.c: Likewise.
* gnulib-local/lib/gcc-function-attributes.h: Likewise.
* lib/bugreports.c: Likewise.
* lib/bugreports.h: Likewise.
* lib/buildcmd.c: Likewise.
* lib/buildcmd.h: Likewise.
* lib/check-regexprops.sh: Likewise.
* lib/dircallback.c: Likewise.
* lib/dircallback.h: Likewise.
* lib/extendbuf.c: Likewise.
* lib/extendbuf.h: Likewise.
* lib/fdleak.c: Likewise.
* lib/fdleak.h: Likewise.
* lib/findutils-version.c: Likewise.
* lib/findutils-version.h: Likewise.
* lib/listfile.c: Likewise.
* lib/listfile.h: Likewise.
* lib/printquoted.c: Likewise.
* lib/printquoted.h: Likewise.
* lib/qmark.c: Likewise.
* lib/regexprops.c: Likewise.
* lib/regextype.c: Likewise.
* lib/regextype.h: Likewise.
* lib/safe-atoi.c: Likewise.
* lib/safe-atoi.h: Likewise.
* lib/splitstring.c: Likewise.
* lib/splitstring.h: Likewise.
* lib/test_splitstring.c: Likewise.
* lib/unused-result.h: Likewise.
* locate/frcode.c: Likewise.
* locate/locate.1: Likewise.
* locate/locate.c: Likewise.
* locate/locatedb.5: Likewise.
* locate/locatedb.h: Likewise.
* locate/testsuite/Makefile.am: Likewise.
* locate/testsuite/config/unix.exp: Likewise.
* locate/updatedb.1: Likewise.
* locate/updatedb.sh: Likewise.
* locate/word_io.c: Likewise.
* po/fetch-po-files: Likewise.
* xargs/testsuite/Makefile.am: Likewise.
* xargs/testsuite/config/unix.exp: Likewise.
* xargs/xargs.1: Likewise.
* xargs/xargs.c: Likewise.
2017-10-23 00:19:34 +02:00
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2010-02-25 10:43:11 +00:00
|
|
|
*/
|
2011-06-20 01:30:22 +01:00
|
|
|
/* config.h must be included first. */
|
2010-02-25 10:43:11 +00:00
|
|
|
#include <config.h>
|
2011-06-20 01:30:22 +01:00
|
|
|
|
|
|
|
/* system headers. */
|
|
|
|
#include <assert.h>
|
2016-11-02 07:32:01 +01:00
|
|
|
#include <errno.h>
|
2010-02-25 10:43:11 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <limits.h>
|
2011-06-20 01:30:22 +01:00
|
|
|
#include <poll.h>
|
2010-03-30 20:34:13 +01:00
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdbool.h>
|
2011-06-20 01:30:22 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2015-12-28 02:22:43 +01:00
|
|
|
#if HAVE_GETRLIMIT
|
2011-06-20 01:30:22 +01:00
|
|
|
# include <sys/resource.h>
|
|
|
|
#endif
|
|
|
|
#include <unistd.h>
|
2010-02-25 10:43:11 +00:00
|
|
|
|
2011-06-20 01:30:22 +01:00
|
|
|
/* gnulib headers. */
|
2010-03-30 20:34:13 +01:00
|
|
|
#include "cloexec.h"
|
2020-12-02 02:00:01 +01:00
|
|
|
#include "dirent--.h"
|
2011-06-20 01:30:22 +01:00
|
|
|
#include "fcntl--.h"
|
|
|
|
|
|
|
|
/* find headers. */
|
2018-02-02 22:40:42 +01:00
|
|
|
#include "system.h"
|
2011-06-20 01:30:22 +01:00
|
|
|
#include "extendbuf.h"
|
2010-02-25 10:43:11 +00:00
|
|
|
#include "fdleak.h"
|
2011-06-20 01:30:22 +01:00
|
|
|
#include "safe-atoi.h"
|
2010-02-25 10:43:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
/* 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
|
2011-06-03 01:11:57 +01:00
|
|
|
get_proc_max_fd (void)
|
2010-02-25 10:43:11 +00:00
|
|
|
{
|
|
|
|
const char *path = "/proc/self/fd";
|
|
|
|
int maxfd = -1;
|
2010-03-30 01:23:03 +01:00
|
|
|
/* We don't use readdir_r, because we cannot trust pathconf
|
2010-02-25 10:43:11 +00:00
|
|
|
* 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). */
|
2020-12-02 02:00:01 +01:00
|
|
|
DIR *dir = opendir (path);
|
2010-02-25 10:43:11 +00:00
|
|
|
if (dir)
|
|
|
|
{
|
|
|
|
int good = 0;
|
|
|
|
struct dirent *dent;
|
|
|
|
|
2016-11-02 07:32:01 +01:00
|
|
|
while (1)
|
|
|
|
{
|
2024-05-26 11:12:59 +01:00
|
|
|
errno = 0;
|
|
|
|
dent = readdir (dir);
|
|
|
|
if (NULL == dent)
|
|
|
|
{
|
|
|
|
if (errno)
|
|
|
|
{
|
|
|
|
error (0, errno, "%s", quotearg_n_style (0, locale_quoting_style, path));
|
|
|
|
good = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dent->d_name[0] != '.'
|
|
|
|
|| (dent->d_name[1] != 0
|
|
|
|
&& (dent->d_name[1] != '.' || dent->d_name[2] != 0)))
|
|
|
|
{
|
|
|
|
const int fd = safe_atoi (dent->d_name, literal_quoting_style);
|
|
|
|
if (fd > maxfd)
|
|
|
|
maxfd = fd;
|
|
|
|
good = 1;
|
|
|
|
}
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
closedir (dir);
|
|
|
|
if (good)
|
2024-05-26 11:12:59 +01:00
|
|
|
return maxfd;
|
2010-02-25 10:43:11 +00:00
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Estimate the value of the largest possible file descriptor */
|
|
|
|
static int
|
|
|
|
get_max_fd (void)
|
|
|
|
{
|
|
|
|
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)
|
2024-05-26 11:12:59 +01:00
|
|
|
open_max = _POSIX_OPEN_MAX; /* underestimate */
|
2010-02-25 10:43:11 +00:00
|
|
|
|
2010-03-30 01:23:03 +01:00
|
|
|
/* We assume if RLIMIT_NOFILE is defined, all the related macros are, too. */
|
2015-12-28 02:22:43 +01:00
|
|
|
#if defined HAVE_GETRLIMIT && defined RLIMIT_NOFILE
|
2018-08-08 09:39:42 +02:00
|
|
|
{
|
|
|
|
struct rlimit fd_limit;
|
|
|
|
if (0 == getrlimit (RLIMIT_NOFILE, &fd_limit))
|
|
|
|
{
|
2024-05-26 11:12:59 +01:00
|
|
|
if (fd_limit.rlim_cur == RLIM_INFINITY)
|
|
|
|
return open_max;
|
|
|
|
else
|
|
|
|
return (int) fd_limit.rlim_cur;
|
2018-08-08 09:39:42 +02:00
|
|
|
}
|
|
|
|
}
|
2010-03-30 01:23:03 +01:00
|
|
|
#endif
|
|
|
|
/* cannot determine the limit's value */
|
|
|
|
return open_max;
|
2010-02-25 10:43:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2010-04-01 00:06:23 +01:00
|
|
|
visit_open_fds (int fd_min, int fd_max,
|
2024-05-26 11:12:59 +01:00
|
|
|
int (*callback)(int, void*), void *cb_context)
|
2010-02-25 10:43:11 +00:00
|
|
|
{
|
|
|
|
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)
|
2024-05-26 11:12:59 +01:00
|
|
|
limit = MAX_POLL;
|
2010-02-25 10:43:11 +00:00
|
|
|
|
|
|
|
for (i=0; i<limit; i++)
|
2024-05-26 11:12:59 +01:00
|
|
|
{
|
|
|
|
pf[i].events = POLLIN|POLLOUT;
|
|
|
|
pf[i].revents = 0;
|
|
|
|
pf[i].fd = fd_min + i;
|
|
|
|
}
|
2010-04-01 00:06:23 +01:00
|
|
|
rv = poll (pf, limit, 0);
|
2010-02-25 10:43:11 +00:00
|
|
|
if (-1 == rv)
|
2024-05-26 11:12:59 +01:00
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
else
|
2024-05-26 11:12:59 +01:00
|
|
|
{
|
|
|
|
int j;
|
|
|
|
for (j=0; j<limit; j++)
|
|
|
|
{
|
|
|
|
if (pf[j].revents != POLLNVAL)
|
|
|
|
{
|
|
|
|
if (0 != (rv = callback (pf[j].fd, cb_context)))
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
fd_min += limit;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
fd_is_cloexec (int fd)
|
|
|
|
{
|
|
|
|
const int flags = fcntl (fd, F_GETFD);
|
|
|
|
return flags & FD_CLOEXEC;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Faking closures in C is a bit of a pain. */
|
|
|
|
struct remember_fd_context
|
|
|
|
{
|
|
|
|
int *buf;
|
|
|
|
size_t used;
|
|
|
|
size_t allocated;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Record FD is it's not FD_CLOEXEC. */
|
|
|
|
static int
|
|
|
|
remember_fd_if_non_cloexec (int fd, void *context)
|
|
|
|
{
|
|
|
|
if (fd_is_cloexec (fd))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
struct remember_fd_context * const p = context;
|
|
|
|
void *newbuf = extendbuf (p->buf,
|
2024-05-26 11:12:59 +01:00
|
|
|
sizeof (p->buf[0])*(p->used+1),
|
|
|
|
&(p->allocated));
|
2010-02-25 10:43:11 +00:00
|
|
|
if (newbuf)
|
2024-05-26 11:12:59 +01:00
|
|
|
{
|
|
|
|
p->buf = newbuf;
|
|
|
|
p->buf[p->used] = fd;
|
|
|
|
++p->used;
|
|
|
|
return 0;
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
else
|
2024-05-26 11:12:59 +01:00
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2010-03-30 01:23:03 +01:00
|
|
|
visit_open_fds (0, max_fd, remember_fd_if_non_cloexec, &cb_data);
|
2010-02-25 10:43:11 +00:00
|
|
|
|
|
|
|
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)
|
2024-05-26 11:12:59 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
/* We come here if p->prev_buf[p->lookup_pos] > fd, or
|
2024-05-26 11:12:59 +01:00
|
|
|
if we ran out of items in the lookup table.
|
|
|
|
Either way, this is a leak. */
|
2010-02-25 10:43:11 +00:00
|
|
|
p->leaked_fd = fd;
|
2024-05-26 11:12:59 +01:00
|
|
|
return -1; /* No more callbacks needed. */
|
2010-02-25 10:43:11 +00:00
|
|
|
}
|
|
|
|
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;
|
2010-03-30 01:23:03 +01:00
|
|
|
visit_open_fds (0, max_fd, find_first_leak_callback, &context);
|
2010-02-25 10:43:11 +00:00
|
|
|
return context.leaked_fd;
|
|
|
|
}
|
|
|
|
|
2010-04-04 01:07:51 +01:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-02-25 10:43:11 +00:00
|
|
|
int
|
2010-03-30 20:34:13 +01:00
|
|
|
open_cloexec (const char *path, int flags, ...)
|
2010-02-25 10:43:11 +00:00
|
|
|
{
|
|
|
|
int fd;
|
2010-03-30 20:34:13 +01:00
|
|
|
mode_t mode = 0;
|
2010-04-04 01:07:51 +01:00
|
|
|
static bool cloexec_works = false;
|
|
|
|
static bool cloexec_status_known = false;
|
2010-02-25 10:43:11 +00:00
|
|
|
|
2010-03-30 20:34:13 +01:00
|
|
|
if (flags & O_CREAT)
|
|
|
|
{
|
|
|
|
/* this code is copied from gnulib's open-safer.c. */
|
|
|
|
va_list ap;
|
|
|
|
va_start (ap, flags);
|
2010-03-30 01:23:03 +01:00
|
|
|
|
2010-03-30 20:34:13 +01:00
|
|
|
/* 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);
|
2010-02-25 10:43:11 +00:00
|
|
|
|
2010-03-30 20:34:13 +01:00
|
|
|
va_end (ap);
|
|
|
|
}
|
|
|
|
|
2010-04-04 01:07:51 +01:00
|
|
|
/* 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;
|
|
|
|
}
|
2010-03-30 20:34:13 +01:00
|
|
|
fd = open (path, flags|O_CLOEXEC, mode);
|
2010-04-04 01:07:51 +01:00
|
|
|
if ((fd >= 0) && !(O_CLOEXEC && cloexec_works))
|
2010-03-30 20:34:13 +01:00
|
|
|
{
|
|
|
|
set_cloexec_flag (fd, true);
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
forget_non_cloexec_fds (void)
|
|
|
|
{
|
|
|
|
free (non_cloexec_fds);
|
|
|
|
non_cloexec_fds = NULL;
|
|
|
|
num_cloexec_fds = 0;
|
|
|
|
}
|
|
|
|
|
2019-04-26 09:12:18 +02:00
|
|
|
/* Return nonzero if file descriptor leak-checking is enabled.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
fd_leak_check_is_enabled (void)
|
|
|
|
{
|
|
|
|
if (getenv ("GNU_FINDUTILS_FD_LEAK_CHECK"))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
2010-02-25 10:43:11 +00:00
|
|
|
|
|
|
|
void
|
|
|
|
complain_about_leaky_fds (void)
|
|
|
|
{
|
2010-03-29 23:56:56 +01:00
|
|
|
int no_leaks = 1;
|
2010-02-25 10:43:11 +00:00
|
|
|
const int leaking_fd = find_first_leaked_fd (non_cloexec_fds, num_cloexec_fds);
|
|
|
|
|
|
|
|
if (leaking_fd >= 0)
|
|
|
|
{
|
2010-03-29 23:56:56 +01:00
|
|
|
no_leaks = 0;
|
2010-02-25 10:43:11 +00:00
|
|
|
error (0, 0,
|
2024-05-26 11:12:59 +01:00
|
|
|
_("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);
|
2010-02-25 10:43:11 +00:00
|
|
|
}
|
2010-03-29 23:56:56 +01:00
|
|
|
assert (no_leaks);
|
2010-02-25 10:43:11 +00:00
|
|
|
}
|