/src/dovecot/src/lib/fd-util.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "net.h" |
5 | | #include "path-util.h" |
6 | | |
7 | | #include <unistd.h> |
8 | | #include <fcntl.h> |
9 | | #include <poll.h> |
10 | | #include <sys/stat.h> |
11 | | #include <sys/un.h> |
12 | | |
13 | | void fd_close_on_exec(int fd, bool set) |
14 | 2.50k | { |
15 | 2.50k | int flags; |
16 | | |
17 | 2.50k | flags = fcntl(fd, F_GETFD, 0); |
18 | 2.50k | if (flags < 0) |
19 | 0 | i_fatal("fcntl(F_GETFD, %d) failed: %m", fd); |
20 | | |
21 | 2.50k | flags = set ? (flags | FD_CLOEXEC) : (flags & ~FD_CLOEXEC); |
22 | 2.50k | if (fcntl(fd, F_SETFD, flags) < 0) |
23 | 0 | i_fatal("fcntl(F_SETFD, %d) failed: %m", fd); |
24 | 2.50k | } |
25 | | |
26 | | void fd_debug_verify_leaks(int first_fd, int last_fd) |
27 | 0 | { |
28 | 0 | struct ip_addr addr, raddr; |
29 | 0 | in_port_t port, rport; |
30 | 0 | struct stat st; |
31 | 0 | int old_errno; |
32 | 0 | bool leaks = FALSE; |
33 | |
|
34 | 0 | for (int fd = first_fd; fd <= last_fd; ++fd) { |
35 | 0 | if (fcntl(fd, F_GETFD, 0) == -1 && errno == EBADF) |
36 | 0 | continue; |
37 | | |
38 | 0 | old_errno = errno; |
39 | |
|
40 | 0 | if (net_getsockname(fd, &addr, &port) == 0) { |
41 | 0 | if (addr.family == AF_UNIX) { |
42 | 0 | struct sockaddr_un sa; |
43 | |
|
44 | 0 | socklen_t socklen = sizeof(sa); |
45 | |
|
46 | 0 | if (getsockname(fd, (void *)&sa, |
47 | 0 | &socklen) < 0) |
48 | 0 | sa.sun_path[0] = '\0'; |
49 | |
|
50 | 0 | i_error("Leaked UNIX socket fd %d: %s", |
51 | 0 | fd, sa.sun_path); |
52 | 0 | leaks = TRUE; |
53 | 0 | continue; |
54 | 0 | } |
55 | | |
56 | 0 | if (net_getpeername(fd, &raddr, &rport) < 0) { |
57 | 0 | i_zero(&raddr); |
58 | 0 | rport = 0; |
59 | 0 | } |
60 | 0 | i_error("Leaked socket fd %d: %s:%u -> %s:%u", |
61 | 0 | fd, net_ip2addr(&addr), port, |
62 | 0 | net_ip2addr(&raddr), rport); |
63 | 0 | leaks = TRUE; |
64 | 0 | continue; |
65 | 0 | } |
66 | | |
67 | 0 | if (fstat(fd, &st) == 0) { |
68 | 0 | const char *error; |
69 | 0 | const char *fname; |
70 | 0 | if (t_readlink(t_strdup_printf("/proc/self/fd/%d", fd), |
71 | 0 | &fname, &error) < 0) |
72 | 0 | fname = t_strdup_printf("<error: %s>", error); |
73 | |
|
74 | | #ifdef __APPLE__ |
75 | | /* OSX workaround: gettimeofday() calls shm_open() |
76 | | internally and the fd won't get closed on exec. |
77 | | We'll just skip all ino/dev=0 files and hope they |
78 | | weren't anything else. */ |
79 | | if (st.st_ino == 0 && st.st_dev == 0) |
80 | | continue; |
81 | | #endif |
82 | 0 | #ifdef HAVE_SYS_SYSMACROS_H |
83 | 0 | i_error("Leaked file %s: fd %d dev %s.%s inode %s", |
84 | 0 | fname, fd, dec2str(major(st.st_dev)), |
85 | 0 | dec2str(minor(st.st_dev)), dec2str(st.st_ino)); |
86 | 0 | leaks = TRUE; |
87 | 0 | continue; |
88 | | #else |
89 | | i_error("Leaked file %s: fd %d dev %s inode %s", |
90 | | fname, fd, dec2str(st.st_dev), |
91 | | dec2str(st.st_ino)); |
92 | | leaks = TRUE; |
93 | | continue; |
94 | | #endif |
95 | 0 | } |
96 | | |
97 | 0 | i_error("Leaked unknown fd %d (errno = %s)", |
98 | 0 | fd, strerror(old_errno)); |
99 | 0 | leaks = TRUE; |
100 | 0 | continue; |
101 | 0 | } |
102 | 0 | if (leaks) |
103 | 0 | i_fatal("fd leak found"); |
104 | 0 | } |
105 | | |
106 | | void fd_set_nonblock(int fd, bool nonblock) |
107 | 0 | { |
108 | 0 | int flags; |
109 | |
|
110 | 0 | i_assert(fd > -1); |
111 | | |
112 | 0 | flags = fcntl(fd, F_GETFL, 0); |
113 | 0 | if (flags < 0) |
114 | 0 | i_fatal("fcntl(%d, F_GETFL) failed: %m", fd); |
115 | | |
116 | 0 | if (nonblock) |
117 | 0 | flags |= O_NONBLOCK; |
118 | 0 | else |
119 | 0 | flags &= ENUM_NEGATE(O_NONBLOCK); |
120 | |
|
121 | 0 | if (fcntl(fd, F_SETFL, flags) < 0) |
122 | 0 | i_fatal("fcntl(%d, F_SETFL) failed: %m", fd); |
123 | 0 | } |
124 | | |
125 | | bool fd_is_readable_now(int fd) |
126 | 0 | { |
127 | 0 | struct pollfd pfd = { |
128 | 0 | .fd = fd, |
129 | 0 | .events = POLLIN, |
130 | 0 | }; |
131 | 0 | int ret; |
132 | |
|
133 | 0 | i_assert(fd > -1); |
134 | | |
135 | 0 | ret = poll(&pfd, 1, 0); |
136 | 0 | if (ret < 0) { |
137 | 0 | if (errno != EINTR) |
138 | 0 | i_error("poll(%d) failed: %m", fd); |
139 | 0 | return FALSE; |
140 | 0 | } |
141 | 0 | return ret > 0; |
142 | 0 | } |
143 | | |
144 | | void fd_close_maybe_stdio(int *fd_in, int *fd_out) |
145 | 0 | { |
146 | 0 | int *fdp[2] = { fd_in, fd_out }; |
147 | |
|
148 | 0 | if (*fd_in == *fd_out) |
149 | 0 | *fd_in = -1; |
150 | |
|
151 | 0 | for (unsigned int i = 0; i < N_ELEMENTS(fdp); i++) { |
152 | 0 | if (*fdp[i] == -1) |
153 | 0 | ; |
154 | 0 | else if (*fdp[i] > 1) |
155 | 0 | i_close_fd(fdp[i]); |
156 | 0 | else if (dup2(dev_null_fd, *fdp[i]) == *fdp[i]) |
157 | 0 | *fdp[i] = -1; |
158 | 0 | else |
159 | 0 | i_fatal("dup2(/dev/null, %d) failed: %m", *fdp[i]); |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | #undef i_close_fd_path |
164 | | void i_close_fd_path(int *fd, const char *path, const char *arg, |
165 | | const char *func, const char *file, int line) |
166 | 5.00k | { |
167 | 5.00k | int saved_errno; |
168 | | |
169 | 5.00k | if (*fd == -1) |
170 | 2.50k | return; |
171 | | |
172 | 2.50k | if (unlikely(*fd <= 0)) { |
173 | 0 | i_panic("%s: close(%s%s%s) @ %s:%d attempted with fd=%d", |
174 | 0 | func, arg, |
175 | 0 | (path == NULL) ? "" : " = ", |
176 | 0 | (path == NULL) ? "" : path, |
177 | 0 | file, line, *fd); |
178 | 0 | } |
179 | | |
180 | 2.50k | saved_errno = errno; |
181 | | /* Ignore ECONNRESET because we don't really care about it here, |
182 | | as we are closing the socket down in any case. There might be |
183 | | unsent data but nothing we can do about that. */ |
184 | 2.50k | if (unlikely(close(*fd) < 0 && errno != ECONNRESET)) |
185 | 0 | i_error("%s: close(%s%s%s) @ %s:%d failed (fd=%d): %m", |
186 | 0 | func, arg, |
187 | 0 | (path == NULL) ? "" : " = ", |
188 | 0 | (path == NULL) ? "" : path, |
189 | 0 | file, line, *fd); |
190 | 2.50k | errno = saved_errno; |
191 | | |
192 | 2.50k | *fd = -1; |
193 | 2.50k | } |