Coverage Report

Created: 2025-08-26 07:01

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