#include "file-set-size.h"
#include "mail-cache-private.h"
+#include <stdio.h>
#include <sys/stat.h>
struct mail_cache_copy_context {
bool new_msg;
};
+struct mail_cache_compress_lock {
+ struct dotlock *dotlock;
+};
+
static void
mail_cache_merge_bitmask(struct mail_cache_copy_context *ctx,
const struct mail_cache_iterate_field *field)
return 0;
}
+static int
+mail_cache_compress_write(struct mail_cache *cache,
+ struct mail_index_transaction *trans,
+ int fd, const char *temp_path, bool *unlock)
+{
+ struct stat st;
+ uint32_t file_seq, old_offset;
+ ARRAY_TYPE(uint32_t) ext_offsets;
+ const uint32_t *offsets;
+ unsigned int i, count;
+
+ if (mail_cache_copy(cache, trans, fd, &file_seq, &ext_offsets) < 0)
+ return -1;
+
+ if (fstat(fd, &st) < 0) {
+ mail_cache_set_syscall_error(cache, "fstat()");
+ array_free(&ext_offsets);
+ return -1;
+ }
+ if (rename(temp_path, cache->filepath) < 0) {
+ mail_cache_set_syscall_error(cache, "rename()");
+ array_free(&ext_offsets);
+ return -1;
+ }
+
+ /* once we're sure that the compression was successful,
+ update the offsets */
+ mail_index_ext_reset(trans, cache->ext_id, file_seq, TRUE);
+ offsets = array_get(&ext_offsets, &count);
+ for (i = 0; i < count; i++) {
+ if (offsets[i] != 0) {
+ mail_index_update_ext(trans, i + 1, cache->ext_id,
+ &offsets[i], &old_offset);
+ }
+ }
+ array_free(&ext_offsets);
+
+ if (*unlock) {
+ (void)mail_cache_unlock(cache);
+ *unlock = FALSE;
+ }
+
+ mail_cache_file_close(cache);
+ cache->fd = fd;
+ cache->st_ino = st.st_ino;
+ cache->st_dev = st.st_dev;
+ cache->field_header_write_pending = FALSE;
+ return 0;
+}
+
static int mail_cache_compress_has_file_changed(struct mail_cache *cache)
{
struct mail_cache_header hdr;
if (ret == 0)
return 0;
if (cache->need_compress_file_seq == 0) {
- /* previously it didn't exist */
+ /* previously it didn't exist or it
+ was unusable and was just unlinked */
return 1;
}
return hdr.file_seq != cache->need_compress_file_seq;
}
}
+static int mail_cache_compress_dotlock(struct mail_cache *cache,
+ struct dotlock **dotlock_r)
+{
+ if (file_dotlock_create(&cache->dotlock_settings, cache->filepath,
+ DOTLOCK_CREATE_FLAG_NONBLOCK, dotlock_r) <= 0) {
+ if (errno != EAGAIN)
+ mail_cache_set_syscall_error(cache, "file_dotlock_open()");
+ return -1;
+ }
+ return 0;
+}
+
static int mail_cache_compress_locked(struct mail_cache *cache,
struct mail_index_transaction *trans,
- bool *unlock)
+ bool *unlock, struct dotlock **dotlock_r)
{
- struct dotlock *dotlock;
- struct stat st;
- mode_t old_mask;
- uint32_t file_seq, old_offset;
- ARRAY_TYPE(uint32_t) ext_offsets;
- const uint32_t *offsets;
+ const char *temp_path;
const void *data;
- unsigned int i, count;
int fd, ret;
- old_mask = umask(cache->index->mode ^ 0666);
- fd = file_dotlock_open(&cache->dotlock_settings, cache->filepath,
- DOTLOCK_CREATE_FLAG_NONBLOCK, &dotlock);
- umask(old_mask);
+ /* There are two possible locking situations here:
+ a) Cache is locked against any modifications.
+ b) Cache doesn't exist or is unusable. There's no lock.
+ Because the cache lock itself is unreliable, we'll be using a
+ separate dotlock to guard against two processes compressing the
+ cache at the same time. */
- if (fd == -1) {
- if (errno != EAGAIN)
- mail_cache_set_syscall_error(cache, "file_dotlock_open()");
+ if (mail_cache_compress_dotlock(cache, dotlock_r) < 0)
return -1;
- }
-
+ /* we've locked the cache compression now. if somebody else had just
+ recreated the cache, reopen the cache and return success. */
if ((ret = mail_cache_compress_has_file_changed(cache)) != 0) {
if (ret < 0)
return -1;
/* was just compressed, forget this */
cache->need_compress_file_seq = 0;
- file_dotlock_delete(&dotlock);
+ file_dotlock_delete(dotlock_r);
if (*unlock) {
(void)mail_cache_unlock(cache);
*unlock = FALSE;
}
- return mail_cache_reopen(cache);
- }
-
- mail_index_fchown(cache->index, fd,
- file_dotlock_get_lock_path(dotlock));
-
- if (mail_cache_copy(cache, trans, fd, &file_seq, &ext_offsets) < 0) {
- /* the fields may have been updated in memory already.
- reverse those changes by re-reading them from file. */
- if (mail_cache_header_fields_read(cache) < 0)
- return -1;
- file_dotlock_delete(&dotlock);
- return -1;
+ return mail_cache_reopen(cache) < 0 ? -1 : 0;
}
- if (fstat(fd, &st) < 0) {
- mail_cache_set_syscall_error(cache, "fstat()");
- file_dotlock_delete(&dotlock);
+ /* we want to recreate the cache. write it first to a temporary file */
+ fd = mail_index_create_tmp_file(cache->index, cache->filepath, &temp_path);
+ if (fd == -1)
return -1;
- }
-
- if (file_dotlock_replace(&dotlock,
- DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) < 0) {
- mail_cache_set_syscall_error(cache,
- "file_dotlock_replace()");
+ if (mail_cache_compress_write(cache, trans, fd, temp_path, unlock) < 0) {
i_close_fd(&fd);
- array_free(&ext_offsets);
+ if (unlink(temp_path) < 0)
+ i_error("unlink(%s) failed: %m", temp_path);
return -1;
}
-
- /* once we're sure that the compression was successful,
- update the offsets */
- mail_index_ext_reset(trans, cache->ext_id, file_seq, TRUE);
- offsets = array_get(&ext_offsets, &count);
- for (i = 0; i < count; i++) {
- if (offsets[i] != 0) {
- mail_index_update_ext(trans, i + 1, cache->ext_id,
- &offsets[i], &old_offset);
- }
- }
- array_free(&ext_offsets);
-
- if (*unlock) {
- (void)mail_cache_unlock(cache);
- *unlock = FALSE;
- }
-
- mail_cache_file_close(cache);
- cache->fd = fd;
- cache->st_ino = st.st_ino;
- cache->st_dev = st.st_dev;
- cache->field_header_write_pending = FALSE;
-
if (cache->file_cache != NULL)
file_cache_set_fd(cache->file_cache, cache->fd);
}
int mail_cache_compress(struct mail_cache *cache,
- struct mail_index_transaction *trans)
+ struct mail_index_transaction *trans,
+ struct mail_cache_compress_lock **lock_r)
{
+ struct dotlock *dotlock = NULL;
bool unlock = FALSE;
int ret;
i_assert(!cache->compressing);
- if (MAIL_INDEX_IS_IN_MEMORY(cache->index) || cache->index->readonly)
+ *lock_r = NULL;
+
+ if (MAIL_INDEX_IS_IN_MEMORY(cache->index) || cache->index->readonly) {
+ *lock_r = i_new(struct mail_cache_compress_lock, 1);
return 0;
+ }
/* compression isn't very efficient with small read()s */
if (cache->map_with_read) {
} else {
switch (mail_cache_try_lock(cache)) {
case -1:
+ /* already locked or some other error */
return -1;
case 0:
- /* couldn't lock, either it's broken or doesn't exist.
+ /* cache is broken or doesn't exist.
just start creating it. */
break;
default:
}
}
cache->compressing = TRUE;
- ret = mail_cache_compress_locked(cache, trans, &unlock);
+ ret = mail_cache_compress_locked(cache, trans, &unlock, &dotlock);
cache->compressing = FALSE;
if (unlock) {
if (mail_cache_unlock(cache) < 0)
ret = -1;
}
+ if (ret < 0) {
+ if (dotlock != NULL)
+ file_dotlock_delete(&dotlock);
+ /* the fields may have been updated in memory already.
+ reverse those changes by re-reading them from file. */
+ (void)mail_cache_header_fields_read(cache);
+ } else {
+ *lock_r = i_new(struct mail_cache_compress_lock, 1);
+ (*lock_r)->dotlock = dotlock;
+ }
return ret;
}
+void mail_cache_compress_unlock(struct mail_cache_compress_lock **_lock)
+{
+ struct mail_cache_compress_lock *lock = *_lock;
+
+ *_lock = NULL;
+
+ if (lock->dotlock != NULL)
+ file_dotlock_delete(&lock->dotlock);
+ i_free(lock);
+}
+
bool mail_cache_need_compress(struct mail_cache *cache)
{
return cache->need_compress_file_seq != 0 &&