uint32_t mailbox_uidvalidity_next(struct mailbox_list *list, const char *path) { char buf[8+1]; uint32_t cur_value; int fd, ret; fd = open(path, O_RDWR); if (fd == -1) { if (errno != ENOENT) i_error("open(%s) failed: %m", path); return mailbox_uidvalidity_next_rescan(list, path); } ret = read_full(fd, buf, sizeof(buf)-1); if (ret < 0) { i_error("read(%s) failed: %m", path); i_close_fd(&fd); return mailbox_uidvalidity_next_rescan(list, path); } buf[sizeof(buf)-1] = 0; if (ret == 0 || str_to_uint32_hex(buf, &cur_value) < 0 || cur_value == 0) { /* broken value */ i_close_fd(&fd); return mailbox_uidvalidity_next_rescan(list, path); } /* we now have the current uidvalidity value that's hopefully correct */ if (mailbox_uidvalidity_rename(path, &cur_value, FALSE) < 0) { i_close_fd(&fd); return mailbox_uidvalidity_next_rescan(list, path); } /* fast path succeeded. write the current value to the main uidvalidity file. */ if (i_snprintf(buf, sizeof(buf), "%08x", cur_value) < 0) i_unreached(); if (pwrite_full(fd, buf, strlen(buf), 0) < 0) i_error("write(%s) failed: %m", path); if (close(fd) < 0) i_error("close(%s) failed: %m", path); return cur_value; }
static uint32_t mailbox_uidvalidity_next_rescan(struct mailbox_list *list, const char *path) { DIR *d; struct dirent *dp; const char *fname, *dir, *prefix, *tmp; unsigned int i, prefix_len; uint32_t cur_value, min_value, max_value; mode_t old_mask; int fd; fname = strrchr(path, '/'); if (fname == NULL) { dir = "."; fname = path; } else { dir = t_strdup_until(path, fname); fname++; } d = opendir(dir); if (d == NULL && errno == ENOENT) { /* FIXME: the PATH_TYPE_CONTROL should come as a parameter, but that's an API change, do it in v2.3. it's not really a problem though, since currently all backends use control dirs for the uidvalidity file. */ (void)mailbox_list_mkdir_root(list, dir, MAILBOX_LIST_PATH_TYPE_CONTROL); d = opendir(dir); } if (d == NULL) { i_error("opendir(%s) failed: %m", dir); return mailbox_uidvalidity_next_fallback(); } prefix = t_strconcat(fname, ".", NULL); prefix_len = strlen(prefix); /* just in case there happens to be multiple matching uidvalidity files, track the min/max values. use the max value and delete the min value file. */ max_value = 0; min_value = (uint32_t)-1; while ((dp = readdir(d)) != NULL) { if (strncmp(dp->d_name, prefix, prefix_len) == 0) { if (str_to_uint32_hex(dp->d_name + prefix_len, &cur_value) >= 0) { if (min_value > cur_value) min_value = cur_value; if (max_value < cur_value) max_value = cur_value; } } } if (closedir(d) < 0) i_error("closedir(%s) failed: %m", dir); if (max_value == 0) { /* no uidvalidity files. create one. */ for (i = 0; i < RETRY_COUNT; i++) { cur_value = mailbox_uidvalidity_next_fallback(); tmp = t_strdup_printf("%s.%08x", path, cur_value); /* the file is empty, don't bother with permissions */ old_mask = umask(0); fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0444); umask(old_mask); if (fd != -1 || errno != EEXIST) break; /* already exists. although it's quite unlikely we'll hit this race condition. more likely we'll create a duplicate file.. */ } if (fd == -1) { i_error("creat(%s) failed: %m", tmp); return cur_value; } i_close_fd(&fd); mailbox_uidvalidity_write(list, path, cur_value); return cur_value; } if (min_value != max_value) { /* duplicate uidvalidity files, delete the oldest */ tmp = t_strdup_printf("%s.%08x", path, min_value); i_unlink_if_exists(tmp); } cur_value = max_value; if (mailbox_uidvalidity_rename(path, &cur_value, TRUE) < 0) return mailbox_uidvalidity_next_fallback(); mailbox_uidvalidity_write(list, path, cur_value); return cur_value; }
static uint32_t mailbox_uidvalidity_next_rescan(struct mailbox_list *list, const char *path) { DIR *d; struct dirent *dp; const char *fname, *dir, *prefix, *tmp; char *endp; unsigned int i, prefix_len; uint32_t cur_value, min_value, max_value; mode_t old_mask; int fd; fname = strrchr(path, '/'); if (fname == NULL) { dir = "."; fname = path; } else { dir = t_strdup_until(path, fname); fname++; } d = opendir(dir); if (d == NULL) { i_error("opendir(%s) failed: %m", dir); return mailbox_uidvalidity_next_fallback(); } prefix = t_strconcat(fname, ".", NULL); prefix_len = strlen(prefix); /* just in case there happens to be multiple matching uidvalidity files, track the min/max values. use the max value and delete the min value file. */ max_value = 0; min_value = (uint32_t)-1; while ((dp = readdir(d)) != NULL) { if (strncmp(dp->d_name, prefix, prefix_len) == 0) { cur_value = strtoul(dp->d_name + prefix_len, &endp, 16); if (*endp == '\0') { if (min_value > cur_value) min_value = cur_value; if (max_value < cur_value) max_value = cur_value; } } } if (closedir(d) < 0) i_error("closedir(%s) failed: %m", dir); if (max_value == 0) { /* no uidvalidity files. create one. */ for (i = 0; i < RETRY_COUNT; i++) { cur_value = mailbox_uidvalidity_next_fallback(); tmp = t_strdup_printf("%s.%08x", path, cur_value); /* the file is empty, don't bother with permissions */ old_mask = umask(0); fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0444); umask(old_mask); if (fd != -1 || errno != EEXIST) break; /* already exists. although it's quite unlikely we'll hit this race condition. more likely we'll create a duplicate file.. */ } if (fd == -1) { i_error("creat(%s) failed: %m", tmp); return cur_value; } i_close_fd(&fd); mailbox_uidvalidity_write(list, path, cur_value); return cur_value; } if (min_value != max_value) { /* duplicate uidvalidity files, delete the oldest */ tmp = t_strdup_printf("%s.%08x", path, min_value); if (unlink(tmp) < 0 && errno != ENOENT) i_error("unlink(%s) failed: %m", tmp); } cur_value = max_value; if (mailbox_uidvalidity_rename(path, &cur_value, TRUE) < 0) return mailbox_uidvalidity_next_fallback(); mailbox_uidvalidity_write(list, path, cur_value); return cur_value; }