Coverage Report

Created: 2026-01-09 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/lib.c
Line
Count
Source
1
/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "dovecot-version.h"
5
#include "array.h"
6
#include "event-filter.h"
7
#include "env-util.h"
8
#include "hostpid.h"
9
#include "ipwd.h"
10
#include "process-title.h"
11
#include "restrict-access.h"
12
#include "randgen.h"
13
14
#include <fcntl.h>
15
#include <unistd.h>
16
#include <sys/time.h>
17
#ifdef HAVE_FACCESSAT2
18
#  include <asm/unistd.h>
19
#endif
20
21
/* Mainly for including the full version information in core dumps.
22
   NOTE: Don't set this const - otherwise it won't end up in core dumps. */
23
char dovecot_build_info[] = DOVECOT_BUILD_INFO;
24
25
static bool lib_initialized = FALSE;
26
int dev_null_fd = -1;
27
28
struct atexit_callback {
29
  int priority;
30
  lib_atexit_callback_t *callback;
31
};
32
33
static ARRAY(struct atexit_callback) atexit_callbacks = ARRAY_INIT;
34
static bool lib_clean_exit;
35
36
/* The original faccessat() syscall didn't handle the flags parameter.  glibc
37
   v2.33's faccessat() started using the new Linux faccessat2() syscall for that
38
   reason.  However, we can still use the faccessat2() syscall directly in some
39
   Linux distros to avoid this problem, so just do it here when possible. */
40
int i_faccessat2(int dirfd, const char *pathname, int mode, int flags)
41
0
{
42
#ifdef HAVE_FACCESSAT2
43
  static bool faccessat2_unavailable = FALSE;
44
  if (!faccessat2_unavailable) {
45
    /* On bullseye the syscall is available,
46
       but the glibc wrapping function is not. */
47
    long ret = syscall(__NR_faccessat2, dirfd, pathname, mode, flags);
48
    faccessat2_unavailable = ret == -1 && errno == ENOSYS;
49
    if (!faccessat2_unavailable)
50
      return (int)ret;
51
  }
52
#endif
53
0
  return faccessat(dirfd, pathname, mode, flags);
54
0
}
55
56
#undef i_unlink
57
int i_unlink(const char *path, const char *source_fname,
58
       unsigned int source_linenum)
59
0
{
60
0
  if (unlink(path) < 0) {
61
0
    i_error("unlink(%s) failed: %m (in %s:%u)",
62
0
      path, source_fname, source_linenum);
63
0
    return -1;
64
0
  }
65
0
  return 0;
66
0
}
67
68
#undef i_unlink_if_exists
69
int i_unlink_if_exists(const char *path, const char *source_fname,
70
           unsigned int source_linenum)
71
0
{
72
0
  if (unlink(path) == 0)
73
0
    return 1;
74
0
  else if (errno == ENOENT)
75
0
    return 0;
76
0
  else {
77
0
    i_error("unlink(%s) failed: %m (in %s:%u)",
78
0
      path, source_fname, source_linenum);
79
0
    return -1;
80
0
  }
81
0
}
82
83
void i_getopt_reset(void)
84
0
{
85
0
#ifdef __GLIBC__
86
  /* a) for subcommands allow -options anywhere in command line
87
     b) this is actually required for the reset to work (glibc bug?) */
88
0
  optind = 0;
89
#else
90
  optind = 1;
91
#endif
92
0
}
93
94
void lib_atexit(lib_atexit_callback_t *callback)
95
0
{
96
0
  lib_atexit_priority(callback, 0);
97
0
}
98
99
void lib_atexit_priority(lib_atexit_callback_t *callback, int priority)
100
20
{
101
20
  struct atexit_callback *cb;
102
20
  const struct atexit_callback *callbacks;
103
20
  unsigned int i, count;
104
105
20
  if (!array_is_created(&atexit_callbacks))
106
20
    i_array_init(&atexit_callbacks, 8);
107
10
  else {
108
    /* skip if it's already added */
109
10
    callbacks = array_get(&atexit_callbacks, &count);
110
20
    for (i = count; i > 0; i--) {
111
10
      if (callbacks[i-1].callback == callback) {
112
0
        i_assert(callbacks[i-1].priority == priority);
113
0
        return;
114
0
      }
115
10
    }
116
10
  }
117
20
  cb = array_append_space(&atexit_callbacks);
118
20
  cb->priority = priority;
119
20
  cb->callback = callback;
120
20
}
121
122
static int atexit_callback_priority_cmp(const struct atexit_callback *cb1,
123
          const struct atexit_callback *cb2)
124
0
{
125
0
  return cb1->priority - cb2->priority;
126
0
}
127
128
void lib_atexit_run(void)
129
0
{
130
0
  const struct atexit_callback *cb;
131
132
0
  if (array_is_created(&atexit_callbacks)) {
133
0
    array_sort(&atexit_callbacks, atexit_callback_priority_cmp);
134
0
    array_foreach(&atexit_callbacks, cb)
135
0
      (*cb->callback)();
136
0
    array_free(&atexit_callbacks);
137
0
  }
138
0
}
139
140
static void lib_open_non_stdio_dev_null(void)
141
10
{
142
10
  dev_null_fd = open("/dev/null", O_WRONLY);
143
10
  if (dev_null_fd == -1)
144
0
    i_fatal("open(/dev/null) failed: %m");
145
  /* Make sure stdin, stdout and stderr fds exist. We especially rely on
146
     stderr being available and a lot of code doesn't like fd being 0.
147
     We'll open /dev/null as write-only also for stdin, since if any
148
     reads are attempted from it we'll want them to fail. */
149
10
  while (dev_null_fd < STDERR_FILENO) {
150
0
    dev_null_fd = dup(dev_null_fd);
151
0
    if (dev_null_fd == -1)
152
0
      i_fatal("dup(/dev/null) failed: %m");
153
0
  }
154
  /* close the actual /dev/null fd on exec*(), but keep it in stdio fds */
155
10
  fd_close_on_exec(dev_null_fd, TRUE);
156
10
}
157
158
void lib_set_clean_exit(bool set)
159
10
{
160
10
  lib_clean_exit = set;
161
10
}
162
163
void lib_exit(int status)
164
0
{
165
0
  lib_set_clean_exit(TRUE);
166
0
  exit(status);
167
0
}
168
169
static void lib_atexit_handler(void)
170
10
{
171
  /* We're already in exit code path. Avoid using any functions that
172
     might cause strange breakage. Especially anything that could call
173
     exit() again could cause infinite looping in some OSes. */
174
10
  if (!lib_clean_exit) {
175
0
    const char *error = "Unexpected exit - converting to abort\n";
176
0
    if (write(STDERR_FILENO, error, strlen(error)) < 0) {
177
      /* ignore */
178
0
    }
179
0
    abort();
180
0
  }
181
10
}
182
183
void lib_init(void)
184
10
{
185
10
  i_assert(!lib_initialized);
186
10
  random_init();
187
10
  data_stack_init();
188
10
  hostpid_init();
189
10
  lib_open_non_stdio_dev_null();
190
10
  lib_event_init();
191
10
  event_filter_init();
192
193
  /* Default to clean exit. Otherwise there would be too many accidents
194
     with e.g. command line parsing errors that try to return instead
195
     of using lib_exit(). master_service_init_finish() will change this
196
     again to be FALSE. */
197
10
  lib_set_clean_exit(TRUE);
198
10
  atexit(lib_atexit_handler);
199
200
10
  lib_initialized = TRUE;
201
10
}
202
203
bool lib_is_initialized(void)
204
42.2k
{
205
42.2k
  return lib_initialized;
206
42.2k
}
207
208
void lib_deinit(void)
209
0
{
210
0
  i_assert(lib_initialized);
211
0
  lib_initialized = FALSE;
212
0
  lib_atexit_run();
213
0
  ipwd_deinit();
214
0
  hostpid_deinit();
215
0
  event_filter_deinit();
216
0
  data_stack_deinit_event();
217
0
  lib_event_deinit();
218
0
  restrict_access_deinit();
219
0
  i_close_fd(&dev_null_fd);
220
0
  data_stack_deinit();
221
0
  failures_deinit();
222
0
  process_title_deinit();
223
0
  random_deinit();
224
225
0
  lib_clean_exit = TRUE;
226
0
}