Coverage Report

Created: 2025-08-29 07:17

/src/dovecot/src/lib/lib.c
Line
Count
Source (jump to first uncovered line)
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
2
{
101
2
  struct atexit_callback *cb;
102
2
  const struct atexit_callback *callbacks;
103
2
  unsigned int i, count;
104
105
2
  if (!array_is_created(&atexit_callbacks))
106
2
    i_array_init(&atexit_callbacks, 8);
107
1
  else {
108
    /* skip if it's already added */
109
1
    callbacks = array_get(&atexit_callbacks, &count);
110
2
    for (i = count; i > 0; i--) {
111
1
      if (callbacks[i-1].callback == callback) {
112
0
        i_assert(callbacks[i-1].priority == priority);
113
0
        return;
114
0
      }
115
1
    }
116
1
  }
117
2
  cb = array_append_space(&atexit_callbacks);
118
2
  cb->priority = priority;
119
2
  cb->callback = callback;
120
2
}
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
1
{
142
1
  dev_null_fd = open("/dev/null", O_WRONLY);
143
1
  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
1
  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
1
  fd_close_on_exec(dev_null_fd, TRUE);
156
1
}
157
158
void lib_set_clean_exit(bool set)
159
1
{
160
1
  lib_clean_exit = set;
161
1
}
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
1
{
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
1
  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
1
}
182
183
void lib_init(void)
184
1
{
185
1
  i_assert(!lib_initialized);
186
1
  random_init();
187
1
  data_stack_init();
188
1
  hostpid_init();
189
1
  lib_open_non_stdio_dev_null();
190
1
  lib_event_init();
191
1
  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
1
  lib_set_clean_exit(TRUE);
198
1
  atexit(lib_atexit_handler);
199
200
1
  lib_initialized = TRUE;
201
1
}
202
203
bool lib_is_initialized(void)
204
3.29k
{
205
3.29k
  return lib_initialized;
206
3.29k
}
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
}