summaryrefslogtreecommitdiffstats
path: root/src/lib/fd-util.c
blob: 01315bc417222352ffadcbd287a9f8e7f34b4023 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/* Copyright (c) 1999-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "net.h"

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/un.h>

void fd_close_on_exec(int fd, bool set)
{
	int flags;

	flags = fcntl(fd, F_GETFD, 0);
	if (flags < 0)
		i_fatal("fcntl(F_GETFD, %d) failed: %m", fd);

	flags = set ? (flags | FD_CLOEXEC) : (flags & ~FD_CLOEXEC);
	if (fcntl(fd, F_SETFD, flags) < 0)
		i_fatal("fcntl(F_SETFD, %d) failed: %m", fd);
}

void fd_debug_verify_leaks(int first_fd, int last_fd)
{
	struct ip_addr addr, raddr;
	in_port_t port, rport;
	struct stat st;
	int old_errno;
	bool leaks = FALSE;

	for (int fd = first_fd; fd <= last_fd; ++fd) {
		if (fcntl(fd, F_GETFD, 0) == -1 && errno == EBADF)
			continue;

		old_errno = errno;

		if (net_getsockname(fd, &addr, &port) == 0) {
			if (addr.family == AF_UNIX) {
				struct sockaddr_un sa;

				socklen_t socklen = sizeof(sa);

				if (getsockname(fd, (void *)&sa,
						&socklen) < 0)
					sa.sun_path[0] = '\0';

				i_error("Leaked UNIX socket fd %d: %s",
					fd, sa.sun_path);
				leaks = TRUE;
				continue;
			}

			if (net_getpeername(fd, &raddr, &rport) < 0) {
				i_zero(&raddr);
				rport = 0;
			}
			i_error("Leaked socket fd %d: %s:%u -> %s:%u",
				fd, net_ip2addr(&addr), port,
				net_ip2addr(&raddr), rport);
			leaks = TRUE;
			continue;
		}

		if (fstat(fd, &st) == 0) {
#ifdef __APPLE__
			/* OSX workaround: gettimeofday() calls shm_open()
			   internally and the fd won't get closed on exec.
			   We'll just skip all ino/dev=0 files and hope they
			   weren't anything else. */
			if (st.st_ino == 0 && st.st_dev == 0)
				continue;
#endif
#ifdef HAVE_SYS_SYSMACROS_H
			i_error("Leaked file fd %d: dev %s.%s inode %s",
				fd, dec2str(major(st.st_dev)),
				dec2str(minor(st.st_dev)), dec2str(st.st_ino));
			leaks = TRUE;
			continue;
#else
			i_error("Leaked file fd %d: dev %s inode %s",
				fd, dec2str(st.st_dev),
				dec2str(st.st_ino));
			leaks = TRUE;
			continue;
#endif
		}

		i_error("Leaked unknown fd %d (errno = %s)",
			fd, strerror(old_errno));
		leaks = TRUE;
		continue;
	}
	if (leaks)
		i_fatal("fd leak found");
}

void fd_set_nonblock(int fd, bool nonblock)
{
	int flags;

	i_assert(fd > -1);

	flags = fcntl(fd, F_GETFL, 0);
	if (flags < 0)
		i_fatal("fcntl(%d, F_GETFL) failed: %m", fd);

	if (nonblock)
		flags |= O_NONBLOCK;
	else
		flags &= ENUM_NEGATE(O_NONBLOCK);

	if (fcntl(fd, F_SETFL, flags) < 0)
		i_fatal("fcntl(%d, F_SETFL) failed: %m", fd);
}

void fd_close_maybe_stdio(int *fd_in, int *fd_out)
{
	int *fdp[2] = { fd_in, fd_out };

	if (*fd_in == *fd_out)
		*fd_in = -1;

	for (unsigned int i = 0; i < N_ELEMENTS(fdp); i++) {
		if (*fdp[i] == -1)
			;
		else if (*fdp[i] > 1)
			i_close_fd(fdp[i]);
		else if (dup2(dev_null_fd, *fdp[i]) == *fdp[i])
			*fdp[i] = -1;
		else
			i_fatal("dup2(/dev/null, %d) failed: %m", *fdp[i]);
	}
}

#undef i_close_fd_path
void i_close_fd_path(int *fd, const char *path, const char *arg,
		     const char *func, const char *file, int line)
{
	int saved_errno;

	if (*fd == -1)
		return;

	if (unlikely(*fd <= 0)) {
		i_panic("%s: close(%s%s%s) @ %s:%d attempted with fd=%d",
			func, arg,
			(path == NULL) ? "" : " = ",
			(path == NULL) ? "" : path,
			file, line, *fd);
	}

	saved_errno = errno;
	/* Ignore ECONNRESET because we don't really care about it here,
	   as we are closing the socket down in any case. There might be
	   unsent data but nothing we can do about that. */
	if (unlikely(close(*fd) < 0 && errno != ECONNRESET))
		i_error("%s: close(%s%s%s) @ %s:%d failed (fd=%d): %m",
			func, arg,
			(path == NULL) ? "" : " = ",
			(path == NULL) ? "" : path,
			file, line, *fd);
	errno = saved_errno;

	*fd = -1;
}