Coverage Report

Created: 2026-10-17 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}