* Fix BUILD_DATE in rrdtool help output <c72578>
* acinclude.m4: Include <stdlib.h> when using exit <ryandesign>
* rrdtool-release: Create NUMVERS from VERSION file <c72578>
-
+* Avoids leaking of file descriptors in multi threaded programs by @ensc
Features
--------
* Remove autogenerated files from git repo (configure, Makefile.in, conftools, rrd_config.h.in) <c72578>
-
RRDtool 1.8.0 - 2022-03-13
==========================
AC_CHECK_SIZEOF([long int])
+AC_CHECK_DECLS([O_CLOEXEC], [], [], [#include <fcntl.h>])
+AC_CHECK_DECLS([SOCK_CLOEXEC], [], [], [#include <sys/socket.h>])
+
+AC_CACHE_CHECK([whether fopen() supports the "e" flag],[rd_cv_fopen_e],[
+ AC_RUN_IFELSE([
+ AC_LANG_PROGRAM([
+ #include <stdio.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ ], [
+ FILE *f = fopen("/dev/null", "re");
+ int fd;
+ long flags;
+
+ if (!f)
+ /* "e" causes fopen() to fail */
+ return 1;
+ fd = fileno(f);
+ if (fd < 0)
+ return 2;
+
+ flags = fcntl(fileno(f), F_GETFD);
+ if (flags < 0 || (flags & FD_CLOEXEC) == 0)
+ /* "e" is accepted but has no effect */
+ return 3;
+ ])
+ ],
+ [rd_cv_fopen_e=yes],
+ [rd_cv_fopen_e=no],[
+ dnl cross-compiling; assume yes
+ rd_cv_fopen_e=yes
+ ])
+])
+
+AS_IF([test x"$rd_cv_fopen_e" = xyes],[
+ AC_DEFINE([RRD_HAVE_WORKING_FOPEN_E], [1], [indicates whether fopen(..., "e") is working])
+])
+
+AM_CONDITIONAL([NEED_COMPAT_CLOEXEC],[test x"$rd_cv_fopen_e" != xyes])
+
CONFIGURE_PART(Find 3rd-Party Libraries)
have_libdbi=no
unused.h \
gettext.h \
mutex.h \
+ compat-cloexec.h \
rrd_strtod.h \
rrd_snprintf.h \
rrd_parsetime.h \
noinst_HEADERS += ../win32/win32-glob.h strftime.h
endif
+if NEED_COMPAT_CLOEXEC
+UPD_C_FILES += compat-cloexec.c
+endif
+
noinst_LTLIBRARIES = librrdupd.la
lib_LTLIBRARIES = librrd.la
--- /dev/null
+#include "compat-cloexec.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef _POSIX_C_SOURCE
+# include <fcntl.h>
+# include <unistd.h>
+#else
+# define O_CREAT 0
+# define O_WRONLY 0
+# define O_RDONLY 0
+# define O_APPEND 0
+# define O_RDWR 0
+#endif
+
+inline static bool have_decl_o_cloexec(void)
+{
+#ifdef HAVE_DECL_O_CLOEXEC
+ return true;
+#else
+ return false;
+#endif
+}
+
+FILE *_rrd_fopen(const char *restrict pathname, const char *restrict mode_raw)
+{
+ char mode[20];
+ const char *in = mode_raw;
+ char *out = mode;
+ int flags = 0;
+ int rw_flags = 0;
+ bool is_cloexec = false;
+
+ /* We are the only caller and never use mode strings with more than 20
+ chars... But just to be sure... */
+ if (strlen(mode_raw) >= sizeof mode)
+ abort();
+
+ /* parse the mode string and strip away the 'e' flag */
+ while (*in) {
+ char c = *in++;
+
+ switch (c) {
+ case 'w':
+ flags |= O_CREAT;
+ rw_flags = O_WRONLY;
+ break;
+ case 'r':
+ rw_flags = O_RDONLY;
+ break;
+ case 'a':
+ flags |= O_CREAT | O_APPEND;
+ rw_flags = O_WRONLY;
+ break;
+ case '+':
+ rw_flags = O_RDWR;
+ break;
+ case 'e':
+ is_cloexec = true;
+ /* continue loop and do not copy mode char */
+ continue;
+ case 'b':
+ break;
+ default:
+ /* we are the only caller and should not set any
+ unknown flag */
+ abort();
+ }
+
+ *out++ = c;
+ }
+
+ *out = '\0';
+
+#ifndef _POSIX_C_SOURCE
+ (void)flags;
+ (void)rw_flags;
+ (void)is_cloexec;
+ /* TODO: do we have to care about O_CLOEXEC behavior on non-POSIX
+ systems? */
+#else
+ if (have_decl_o_cloexec() && is_cloexec) {
+ int fd;
+ FILE *res;
+
+ fd = open(pathname, flags | rw_flags | O_CLOEXEC, 0666);
+ if (fd < 0)
+ return NULL;
+
+ res = fdopen(fd, mode);
+ if (!res)
+ close(fd);
+
+ return res;
+ }
+#endif
+
+ return fopen(pathname, mode);
+}
--- /dev/null
+#ifndef H_RRDTOOL_SRC_COMPAT_CLOEXEC_H
+#define H_RRDTOOL_SRC_COMPAT_CLOEXEC_H
+
+#include <rrd_config.h>
+
+#ifndef HAVE_DECL_O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
+
+#ifndef HAVE_DECL_SOCK_CLOEXEC
+# define SOCK_CLOEXEC 0
+#endif
+
+#include <stdio.h>
+
+FILE *_rrd_fopen(const char *restrict pathname, const char *restrict mode_raw);
+
+#ifdef RRD_HAVE_WORKING_FOPEN_E
+# define rrd_fopen(_pathname, _mode) fopen(_pathname, _mode)
+#else
+
+inline static
+FILE *rrd_fopen(const char *restrict pathname, const char *restrict mode)
+{
+ return _rrd_fopen(pathname, mode);
+}
+
+#endif /* RD_HAVE_WORKING_FOPEN_E */
+
+#endif /* H_RRDTOOL_SRC_COMPAT_CLOEXEC_H */
#include <stdlib.h>
#endif
+#include "compat-cloexec.h"
+
#define MEMBLK 1024
/*#define DEBUG_PARSER
#define DEBUG_VARS*/
if ((strcmp("-", file_name) == 0)) {
input = stdin;
} else {
- if ((input = fopen(file_name, "rb")) == NULL) {
+ if ((input = rrd_fopen(file_name, "rbe")) == NULL) {
rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
return (-1);
}
#include <sys/types.h>
#include <limits.h>
+#include "compat-cloexec.h"
+
struct rrdc_response_s {
int status;
char *message;
assert(path != NULL);
assert(client->sd == -1);
- client->sd = socket(PF_UNIX, SOCK_STREAM, /* protocol = */ 0);
+ client->sd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, /* protocol = */ 0);
if (client->sd < 0) {
status = errno;
return (status);
#endif /* HAVE_LIBWRAP */
#include "rrd_strtod.h"
+#include "compat-cloexec.h"
+
#include <glib.h>
/* }}} */
}
}
- fh = fopen(file, "r");
+ fh = rrd_fopen(file, "re");
if (fh == NULL) {
if (errno != ENOENT)
RRDD_LOG(LOG_ERR,
listen_fds = temp;
memcpy(listen_fds + listen_fds_num, sock, sizeof(listen_fds[0]));
- fd = socket(PF_UNIX, SOCK_STREAM, /* protocol = */ 0);
+ fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, /* protocol = */ 0);
if (fd < 0) {
fprintf(stderr, "rrdcached: unix socket(2) failed: %s\n",
rrd_strerror(errno));
{
if (log_fh)
fclose(log_fh);
- log_fh = fopen(options.optarg, "a");
+ log_fh = rrd_fopen(options.optarg, "ae");
if (!log_fh) {
fprintf(stderr, "Failed to open log file '%s': %s\n",
options.optarg, rrd_strerror(errno));
#include "rrd_client.h"
#include "rrd_snprintf.h"
+#include "compat-cloexec.h"
+
#if !(defined(NETWARE) || defined(_WIN32))
extern char *tzname[2];
out_file = NULL;
if (outname) {
- if (!(out_file = fopen(outname, "w"))) {
+ if (!(out_file = rrd_fopen(outname, "we"))) {
return (-1);
}
} else {
#include "rrd_strtod.h"
#include "rrd_tool.h"
+#include "compat-cloexec.h"
/* MinGW and MinGW-w64 use the format codes from msvcrt.dll,
* which does not support e.g. %F, %T or %V. Here we need %V for "Week %V".
change here ... */
if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
return 0;
- if ((fd = fopen(im->graphfile, "rb")) == NULL)
+ if ((fd = rrd_fopen(im->graphfile, "rbe")) == NULL)
return 0; /* the file does not exist */
switch (im->imgformat) {
case IF_PNG:
#endif /* WIN32 */
#include "rrd_tool.h"
+#include "compat-cloexec.h"
#include "unused.h"
#ifdef HAVE_BROKEN_MS_ASYNC
goto out_free;
}
#else
- if ((rrd_simple_file->fd = open(file_name, flags, 0666)) < 0) {
+ if ((rrd_simple_file->fd = open(file_name, flags | O_CLOEXEC, 0666)) < 0) {
rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
goto out_free;
}
#endif
#include "rrd_snprintf.h"
+#include "compat-cloexec.h"
static int rrd_xport_fn(
image_desc_t *,
/* if we write a file, then open it */
if (im->graphfile) {
- buffer.file = fopen(im->graphfile, "w");
+ buffer.file = rrd_fopen(im->graphfile, "we");
}
/* do the data processing */
/modify5-testa*-mod.dump*
/*.log
/*.trs
+/compat-cloexec
TESTS = modify1 modify2 modify3 modify4 modify5 \
tune1 tune2 graph1 graph2 rpn1 rpn2 \
rrdcreate \
+ compat-cloexec \
dump-restore \
create-with-source-1 create-with-source-2 create-with-source-3 \
create-with-source-4 create-with-source-and-mapping-1 \
modify5-testa1-mod.dump modify5-testa2-mod.dump \
modify5-testa1-mod.dump.tmp modify5-testa2-mod.dump.tmp \
rpn1.out rpn1.output.out
+
+check_PROGRAMS = \
+ compat-cloexec
+
+compat_cloexec_SOURCES = \
+ test_compat-cloexec.c \
+ ${top_srcdir}/src/compat-cloexec.c \
+ ${top_srcdir}/src/compat-cloexec.h
--- /dev/null
+#include <compat-cloexec.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#ifdef _POSIX_C_SOURCE
+# include <fcntl.h>
+#else
+# define O_RDONLY 0
+# define O_RDWR 0
+# define O_APPEND 0
+#endif
+
+static void fail(char *msg, int line)
+{
+ fprintf(stderr, "%s:%u %s\n", __FILE__, line, msg);
+ abort();
+}
+
+static void check_file(FILE *f, int exp_flags, bool is_cloexec, int line)
+{
+ int flags;
+ int fd;
+
+ if (!f)
+ fail("fopen() failed", line);
+
+#ifndef _POSIX_C_SOURCE
+ (void)flags;
+ (void)fd;
+ (void)exp_flags;
+ (void)is_cloexec;
+#else
+ fd = fileno(f);
+ if (fd < 0)
+ fail("failed to get fd", line);
+
+ flags = fcntl(fd, F_GETFD);
+
+ if (O_CLOEXEC != 0) {
+ if (is_cloexec != (((flags & FD_CLOEXEC) != 0)))
+ fail("O_CLOEXEC mismatch", line);
+ }
+
+ flags = fcntl(fd, F_GETFL);
+ flags &= (O_RDONLY | O_WRONLY | O_RDWR | O_APPEND);
+ if (flags != exp_flags)
+ fail("flag mismatch", line);
+#endif
+
+ fclose(f);
+}
+
+int main(void) {
+ FILE *f;
+
+ f = _rrd_fopen("/dev/null", "r");
+ check_file(f, O_RDONLY, false, __LINE__);
+
+ f = _rrd_fopen("/dev/null", "re");
+ check_file(f, O_RDONLY, true, __LINE__);
+
+ f = _rrd_fopen("/dev/null", "w+be");
+ check_file(f, O_RDWR, true, __LINE__);
+
+ f = _rrd_fopen("/dev/null", "a+be");
+ check_file(f, O_RDWR | O_APPEND, true, __LINE__);
+}
Ws2_32.lib zdll.lib gthread-2.0.lib\r
\r
RRD_LIB_OBJ_LIST = \\r
+ $(TOP)/src/compat-cloexec.obj \\r
$(TOP)/src/hash_32.obj \\r
$(TOP)/src/mkstemp.obj \\r
$(TOP)/src/mutex.obj \\r
Ws2_32.lib zlib.lib\r
\r
RRD_LIB_OBJ_LIST = \\r
+ $(TOP)/src/compat-cloexec.obj \\r
$(TOP)/src/hash_32.obj \\r
$(TOP)/src/mkstemp.obj \\r
$(TOP)/src/mutex.obj \\r
rrdc_stats_free
rrdc_stats_get
rrdc_update
+_rrd_fopen
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\src\compat-cloexec.c" />
<ClCompile Include="..\src\hash_32.c" />
<ClCompile Include="..\src\mkstemp.c" />
<ClCompile Include="..\src\mutex.c" />