Coverage Report

Created: 2026-04-12 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-storage/mailbox-uidvalidity.c
Line
Count
Source
1
/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "ioloop.h"
5
#include "str.h"
6
#include "read-full.h"
7
#include "write-full.h"
8
#include "eacces-error.h"
9
#include "mail-user.h"
10
#include "mailbox-list.h"
11
#include "mailbox-uidvalidity.h"
12
13
#include <stdio.h>
14
#include <unistd.h>
15
#include <dirent.h>
16
#include <fcntl.h>
17
#include <sys/stat.h>
18
19
0
#define RETRY_COUNT 10
20
21
static uint32_t mailbox_uidvalidity_next_fallback(void)
22
0
{
23
0
  static uint32_t uid_validity = 0;
24
25
  /* we failed to use the uidvalidity file. don't fail the mailbox
26
     creation because of it though, most of the time it's safe enough
27
     to use the current time as the uidvalidity value. */
28
0
  if (uid_validity < ioloop_time32)
29
0
    uid_validity = ioloop_time32;
30
0
  else
31
0
    uid_validity++;
32
0
  if (uid_validity == 0)
33
0
    uid_validity = 1;
34
0
  return uid_validity;
35
0
}
36
37
static void mailbox_uidvalidity_write(struct mailbox_list *list,
38
              const char *path, uint32_t uid_validity)
39
0
{
40
0
  struct mail_user *user = mailbox_list_get_user(list);
41
0
  char buf[8+1];
42
0
  int fd;
43
0
  struct mailbox_permissions perm;
44
0
  mode_t old_mask;
45
46
0
  mailbox_list_get_root_permissions(list, &perm);
47
48
0
  old_mask = umask(0666 & ~perm.file_create_mode);
49
0
  fd = open(path, O_RDWR | O_CREAT | O_NOFOLLOW, 0666);
50
0
  umask(old_mask);
51
0
  if (fd == -1) {
52
0
    e_error(user->event, "open(%s) failed: %m", path);
53
0
    return;
54
0
  }
55
0
  if (perm.file_create_gid != (gid_t)-1 &&
56
0
      fchown(fd, (uid_t)-1, perm.file_create_gid) < 0) {
57
0
    if (errno == EPERM) {
58
0
      e_error(user->event, "%s",
59
0
        eperm_error_get_chgrp("fchown", path,
60
0
            perm.file_create_gid,
61
0
            perm.file_create_gid_origin));
62
0
    } else {
63
0
      e_error(mailbox_list_get_user(list)->event,
64
0
        "fchown(%s, -1, %ld) failed: %m",
65
0
        path, (long)perm.file_create_gid);
66
0
    }
67
0
  }
68
69
0
  if (i_snprintf(buf, sizeof(buf), "%08x", uid_validity) < 0)
70
0
    i_unreached();
71
0
  if (pwrite_full(fd, buf, strlen(buf), 0) < 0)
72
0
    e_error(user->event, "write(%s) failed: %m", path);
73
0
  if (close(fd) < 0)
74
0
    e_error(user->event, "close(%s) failed: %m", path);
75
0
}
76
77
static int
78
mailbox_uidvalidity_rename(struct mailbox_list *list, const char *path,
79
         uint32_t *uid_validity, bool log_enoent)
80
0
{
81
0
  string_t *src, *dest;
82
0
  unsigned int i;
83
0
  size_t prefix_len;
84
0
  int ret;
85
86
0
  src = t_str_new(256);
87
0
  str_append(src, path);
88
0
  dest = t_str_new(256);
89
0
  str_append(dest, path);
90
0
  prefix_len = str_len(src);
91
92
0
  for (i = 0; i < RETRY_COUNT; i++) {
93
0
    str_truncate(src, prefix_len);
94
0
    str_truncate(dest, prefix_len);
95
96
0
    str_printfa(src, ".%08x", *uid_validity);
97
0
    *uid_validity += 1;
98
0
    if (*uid_validity == 0)
99
0
      *uid_validity += 1;
100
0
    str_printfa(dest, ".%08x", *uid_validity);
101
102
0
    if ((ret = rename(str_c(src), str_c(dest))) == 0 ||
103
0
        errno != ENOENT)
104
0
      break;
105
106
    /* possibly a race condition. try the next value. */
107
0
  }
108
0
  if (ret < 0 && (errno != ENOENT || log_enoent))
109
0
    e_error(mailbox_list_get_user(list)->event,
110
0
      "rename(%s, %s) failed: %m", str_c(src), str_c(dest));
111
0
  return ret;
112
0
}
113
114
static uint32_t
115
mailbox_uidvalidity_next_rescan(struct mailbox_list *list, const char *path)
116
0
{
117
0
  DIR *d;
118
0
  struct dirent *dp;
119
0
  const char *fname, *dir, *prefix, *tmp;
120
0
  unsigned int i;
121
0
  size_t prefix_len;
122
0
  uint32_t cur_value, min_value, max_value;
123
0
  mode_t old_mask;
124
0
  int fd;
125
126
0
  fname = strrchr(path, '/');
127
0
  if (fname == NULL) {
128
0
    dir = ".";
129
0
    fname = path;
130
0
  } else {
131
0
    dir = t_strdup_until(path, fname);
132
0
    fname++;
133
0
  }
134
135
0
  d = opendir(dir);
136
0
  if (d == NULL && errno == ENOENT) {
137
    /* FIXME: the PATH_TYPE_CONTROL should come as a parameter, but
138
       that's an API change, do it in v2.3. it's not really a
139
       problem though, since currently all backends use control
140
       dirs for the uidvalidity file. */
141
0
    (void)mailbox_list_mkdir_root(list, dir, MAILBOX_LIST_PATH_TYPE_CONTROL);
142
0
    d = opendir(dir);
143
0
  }
144
0
  if (d == NULL) {
145
0
    e_error(mailbox_list_get_user(list)->event,
146
0
      "opendir(%s) failed: %m", dir);
147
0
    return mailbox_uidvalidity_next_fallback();
148
0
  }
149
0
  prefix = t_strconcat(fname, ".", NULL);
150
0
  prefix_len = strlen(prefix);
151
152
  /* just in case there happens to be multiple matching uidvalidity
153
     files, track the min/max values. use the max value and delete the
154
     min value file. */
155
0
  max_value = 0; min_value = (uint32_t)-1;
156
0
  while ((dp = readdir(d)) != NULL) {
157
0
    if (strncmp(dp->d_name, prefix, prefix_len) == 0) {
158
0
      if (str_to_uint32_hex(dp->d_name + prefix_len, &cur_value) >= 0) {
159
0
        if (min_value > cur_value)
160
0
          min_value = cur_value;
161
0
        if (max_value < cur_value)
162
0
          max_value = cur_value;
163
0
      }
164
0
    }
165
0
  }
166
0
  if (closedir(d) < 0)
167
0
    e_error(mailbox_list_get_user(list)->event,
168
0
      "closedir(%s) failed: %m", dir);
169
170
0
  if (max_value == 0) {
171
    /* no uidvalidity files. create one. */
172
0
    for (i = 0; i < RETRY_COUNT; i++) {
173
0
      cur_value = mailbox_uidvalidity_next_fallback();
174
0
      tmp = t_strdup_printf("%s.%08x", path, cur_value);
175
      /* the file is empty, don't bother with permissions */
176
0
      old_mask = umask(0);
177
0
      fd = open(tmp, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0444);
178
0
      umask(old_mask);
179
0
      if (fd != -1 || errno != EEXIST)
180
0
        break;
181
      /* already exists. although it's quite unlikely we'll
182
         hit this race condition. more likely we'll create
183
         a duplicate file.. */
184
0
    }
185
0
    if (fd == -1) {
186
0
      e_error(mailbox_list_get_user(list)->event,
187
0
        "creat(%s) failed: %m", tmp);
188
0
      return cur_value;
189
0
    }
190
0
    i_close_fd(&fd);
191
0
    mailbox_uidvalidity_write(list, path, cur_value);
192
0
    return cur_value;
193
0
  }
194
0
  if (min_value != max_value) {
195
    /* duplicate uidvalidity files, delete the oldest */
196
0
    tmp = t_strdup_printf("%s.%08x", path, min_value);
197
0
    i_unlink_if_exists(tmp);
198
0
  }
199
200
0
  cur_value = max_value;
201
0
  if (mailbox_uidvalidity_rename(list, path, &cur_value, TRUE) < 0)
202
0
    return mailbox_uidvalidity_next_fallback();
203
0
  mailbox_uidvalidity_write(list, path, cur_value);
204
0
  return cur_value;
205
0
}
206
207
uint32_t mailbox_uidvalidity_next(struct mailbox_list *list, const char *path)
208
0
{
209
0
  struct mail_user *user = mailbox_list_get_user(list);
210
0
  char buf[8+1];
211
0
  uint32_t cur_value;
212
0
  int fd, ret;
213
214
0
  fd = open(path, O_RDWR);
215
0
  if (fd == -1) {
216
0
    if (errno != ENOENT)
217
0
      e_error(user->event, "open(%s) failed: %m", path);
218
0
    return mailbox_uidvalidity_next_rescan(list, path);
219
0
  }
220
0
  ret = read_full(fd, buf, sizeof(buf)-1);
221
0
  if (ret < 0) {
222
0
    e_error(user->event, "read(%s) failed: %m", path);
223
0
    i_close_fd(&fd);
224
0
    return mailbox_uidvalidity_next_rescan(list, path);
225
0
  }
226
0
  buf[sizeof(buf)-1] = 0;
227
0
  if (ret == 0 || str_to_uint32_hex(buf, &cur_value) < 0 ||
228
0
      cur_value == 0) {
229
    /* broken value */
230
0
    i_close_fd(&fd);
231
0
    return mailbox_uidvalidity_next_rescan(list, path);
232
0
  }
233
234
  /* we now have the current uidvalidity value that's hopefully correct */
235
0
  if (mailbox_uidvalidity_rename(list, path, &cur_value, FALSE) < 0) {
236
0
    i_close_fd(&fd);
237
0
    return mailbox_uidvalidity_next_rescan(list, path);
238
0
  }
239
240
  /* fast path succeeded. write the current value to the main
241
     uidvalidity file. */
242
0
  if (i_snprintf(buf, sizeof(buf), "%08x", cur_value) < 0)
243
0
    i_unreached();
244
0
  if (pwrite_full(fd, buf, strlen(buf), 0) < 0)
245
0
    e_error(user->event, "write(%s) failed: %m", path);
246
0
  if (close(fd) < 0)
247
0
    e_error(user->event, "close(%s) failed: %m", path);
248
0
  return cur_value;
249
0
}