summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/dbox-multi/mdbox-file.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/dbox-multi/mdbox-file.c')
-rw-r--r--src/lib-storage/index/dbox-multi/mdbox-file.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/lib-storage/index/dbox-multi/mdbox-file.c b/src/lib-storage/index/dbox-multi/mdbox-file.c
new file mode 100644
index 0000000..65138bd
--- /dev/null
+++ b/src/lib-storage/index/dbox-multi/mdbox-file.c
@@ -0,0 +1,349 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "hex-dec.h"
+#include "hex-binary.h"
+#include "hostpid.h"
+#include "istream.h"
+#include "ostream.h"
+#include "file-lock.h"
+#include "file-set-size.h"
+#include "mkdir-parents.h"
+#include "fdatasync-path.h"
+#include "eacces-error.h"
+#include "str.h"
+#include "mailbox-list-private.h"
+#include "mdbox-storage.h"
+#include "mdbox-map-private.h"
+#include "mdbox-file.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+static struct mdbox_file *
+mdbox_find_and_move_open_file(struct mdbox_storage *storage, uint32_t file_id)
+{
+ struct mdbox_file *const *files;
+ unsigned int i, count;
+
+ files = array_get(&storage->open_files, &count);
+ for (i = 0; i < count; i++) {
+ if (files[i]->file_id == file_id)
+ return files[i];
+ }
+ return NULL;
+}
+
+void mdbox_files_free(struct mdbox_storage *storage)
+{
+ struct mdbox_file *const *files;
+ unsigned int i, count;
+
+ files = array_get(&storage->open_files, &count);
+ for (i = 0; i < count; i++)
+ dbox_file_free(&files[i]->file);
+ array_clear(&storage->open_files);
+}
+
+void mdbox_files_sync_input(struct mdbox_storage *storage)
+{
+ struct mdbox_file *const *files;
+ unsigned int i, count;
+
+ files = array_get(&storage->open_files, &count);
+ for (i = 0; i < count; i++) {
+ if (files[i]->file.input != NULL)
+ i_stream_sync(files[i]->file.input);
+ }
+}
+
+static void
+mdbox_close_open_files(struct mdbox_storage *storage, unsigned int close_count)
+{
+ struct mdbox_file *const *files;
+ unsigned int i, count;
+
+ files = array_get(&storage->open_files, &count);
+ for (i = 0; i < count;) {
+ if (files[i]->file.refcount == 0) {
+ dbox_file_free(&files[i]->file);
+ array_delete(&storage->open_files, i, 1);
+
+ if (--close_count == 0)
+ break;
+
+ files = array_get(&storage->open_files, &count);
+ } else {
+ i++;
+ }
+ }
+}
+
+static void
+mdbox_file_init_paths(struct mdbox_file *file, const char *fname, bool alt)
+{
+ i_free(file->file.primary_path);
+ i_free(file->file.alt_path);
+ file->file.primary_path =
+ i_strdup_printf("%s/%s", file->storage->storage_dir, fname);
+ if (file->storage->alt_storage_dir != NULL) {
+ file->file.alt_path =
+ i_strdup_printf("%s/%s", file->storage->alt_storage_dir,
+ fname);
+ }
+ file->file.cur_path = !alt ? file->file.primary_path :
+ file->file.alt_path;
+}
+
+static int mdbox_file_create(struct mdbox_file *file)
+{
+ struct dbox_file *_file = &file->file;
+ bool create_parents;
+ int ret;
+
+ create_parents = dbox_file_is_in_alt(_file);
+ _file->fd = _file->storage->v.
+ file_create_fd(_file, _file->cur_path, create_parents);
+ if (_file->fd == -1)
+ return -1;
+
+ if (file->storage->preallocate_space) {
+ ret = file_preallocate(_file->fd,
+ file->storage->set->mdbox_rotate_size);
+ if (ret < 0) {
+ switch (errno) {
+ case ENOSPC:
+ case EDQUOT:
+ /* ignore */
+ break;
+ default:
+ i_error("file_preallocate(%s) failed: %m",
+ _file->cur_path);
+ break;
+ }
+ } else if (ret == 0) {
+ /* not supported by filesystem, disable. */
+ file->storage->preallocate_space = FALSE;
+ }
+ }
+ return 0;
+}
+
+static struct dbox_file *
+mdbox_file_init_full(struct mdbox_storage *storage,
+ uint32_t file_id, bool alt_dir)
+{
+ struct mdbox_file *file;
+ const char *fname;
+ unsigned int count;
+
+ file = file_id == 0 ? NULL :
+ mdbox_find_and_move_open_file(storage, file_id);
+ if (file != NULL) {
+ file->file.refcount++;
+ return &file->file;
+ }
+
+ count = array_count(&storage->open_files);
+ if (count > MDBOX_MAX_OPEN_UNUSED_FILES) {
+ mdbox_close_open_files(storage,
+ count - MDBOX_MAX_OPEN_UNUSED_FILES);
+ }
+
+ file = i_new(struct mdbox_file, 1);
+ file->storage = storage;
+ file->file.storage = &storage->storage;
+ file->file_id = file_id;
+ fname = file_id == 0 ? dbox_generate_tmp_filename() :
+ t_strdup_printf(MDBOX_MAIL_FILE_FORMAT, file_id);
+ mdbox_file_init_paths(file, fname, FALSE);
+ dbox_file_init(&file->file);
+ if (alt_dir)
+ file->file.cur_path = file->file.alt_path;
+
+ if (file_id != 0)
+ array_push_back(&storage->open_files, &file);
+ else
+ (void)mdbox_file_create(file);
+ return &file->file;
+}
+
+struct dbox_file *
+mdbox_file_init(struct mdbox_storage *storage, uint32_t file_id)
+{
+ return mdbox_file_init_full(storage, file_id, FALSE);
+}
+
+struct dbox_file *
+mdbox_file_init_new_alt(struct mdbox_storage *storage)
+{
+ return mdbox_file_init_full(storage, 0, TRUE);
+}
+
+int mdbox_file_assign_file_id(struct mdbox_file *file, uint32_t file_id)
+{
+ struct stat st;
+ const char *old_path;
+ const char *new_dir, *new_fname, *new_path;
+
+ i_assert(file->file_id == 0);
+ i_assert(file_id != 0);
+
+ old_path = file->file.cur_path;
+ new_fname = t_strdup_printf(MDBOX_MAIL_FILE_FORMAT, file_id);
+ new_dir = !dbox_file_is_in_alt(&file->file) ?
+ file->storage->storage_dir : file->storage->alt_storage_dir;
+ new_path = t_strdup_printf("%s/%s", new_dir, new_fname);
+
+ if (stat(new_path, &st) == 0) {
+ mail_storage_set_critical(&file->file.storage->storage,
+ "mdbox: %s already exists, rebuilding index", new_path);
+ mdbox_storage_set_corrupted(file->storage);
+ return -1;
+ }
+ if (rename(old_path, new_path) < 0) {
+ mail_storage_set_critical(&file->storage->storage.storage,
+ "rename(%s, %s) failed: %m",
+ old_path, new_path);
+ return -1;
+ }
+ mdbox_file_init_paths(file, new_fname,
+ dbox_file_is_in_alt(&file->file));
+ file->file_id = file_id;
+ array_push_back(&file->storage->open_files, &file);
+ return 0;
+}
+
+static struct mdbox_file *
+mdbox_find_oldest_unused_file(struct mdbox_storage *storage,
+ unsigned int *idx_r)
+{
+ struct mdbox_file *const *files, *oldest_file = NULL;
+ unsigned int i, count;
+
+ files = array_get(&storage->open_files, &count);
+ *idx_r = count;
+ for (i = 0; i < count; i++) {
+ if (files[i]->file.refcount == 0) {
+ if (oldest_file == NULL ||
+ files[i]->close_time < oldest_file->close_time) {
+ oldest_file = files[i];
+ *idx_r = i;
+ }
+ }
+ }
+ return oldest_file;
+}
+
+static void mdbox_file_close_timeout(struct mdbox_storage *storage)
+{
+ struct mdbox_file *oldest;
+ unsigned int i;
+ time_t close_time = ioloop_time - MDBOX_CLOSE_UNUSED_FILES_TIMEOUT_SECS;
+
+ while ((oldest = mdbox_find_oldest_unused_file(storage, &i)) != NULL) {
+ if (oldest->close_time > close_time)
+ break;
+ array_delete(&storage->open_files, i, 1);
+ dbox_file_free(&oldest->file);
+ }
+
+ if (oldest == NULL)
+ timeout_remove(&storage->to_close_unused_files);
+}
+
+static void mdbox_file_close_later(struct mdbox_file *mfile)
+{
+ if (mfile->storage->to_close_unused_files == NULL) {
+ mfile->storage->to_close_unused_files =
+ timeout_add(MDBOX_CLOSE_UNUSED_FILES_TIMEOUT_SECS*1000,
+ mdbox_file_close_timeout, mfile->storage);
+ }
+}
+
+void mdbox_file_unrefed(struct dbox_file *file)
+{
+ struct mdbox_file *mfile = (struct mdbox_file *)file;
+ struct mdbox_file *oldest_file;
+ unsigned int i, count;
+
+ /* don't cache metadata seeks while file isn't being referenced */
+ file->metadata_read_offset = UOFF_T_MAX;
+ mfile->close_time = ioloop_time;
+
+ if (mfile->file_id != 0) {
+ count = array_count(&mfile->storage->open_files);
+ if (count <= MDBOX_MAX_OPEN_UNUSED_FILES) {
+ /* we can leave this file open for now */
+ mdbox_file_close_later(mfile);
+ return;
+ }
+
+ /* close the oldest file with refcount=0 */
+ oldest_file = mdbox_find_oldest_unused_file(mfile->storage, &i);
+ i_assert(oldest_file != NULL);
+ array_delete(&mfile->storage->open_files, i, 1);
+ if (oldest_file != mfile) {
+ dbox_file_free(&oldest_file->file);
+ mdbox_file_close_later(mfile);
+ return;
+ }
+ /* have to close ourself */
+ }
+ dbox_file_free(file);
+}
+
+int mdbox_file_create_fd(struct dbox_file *file, const char *path, bool parents)
+{
+ struct mdbox_file *mfile = (struct mdbox_file *)file;
+ struct mdbox_map *map = mfile->storage->map;
+ struct mailbox_permissions perm;
+ mode_t old_mask;
+ const char *p, *dir;
+ int fd;
+
+ mailbox_list_get_root_permissions(map->root_list, &perm);
+
+ old_mask = umask(0666 & ~perm.file_create_mode);
+ fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+ umask(old_mask);
+ if (fd == -1 && errno == ENOENT && parents &&
+ (p = strrchr(path, '/')) != NULL) {
+ dir = t_strdup_until(path, p);
+ if (mailbox_list_mkdir_root(map->root_list, dir,
+ path != file->alt_path ?
+ MAILBOX_LIST_PATH_TYPE_DIR :
+ MAILBOX_LIST_PATH_TYPE_ALT_DIR) < 0) {
+ mail_storage_copy_list_error(&file->storage->storage,
+ map->root_list);
+ return -1;
+ }
+ /* try again */
+ old_mask = umask(0666 & ~perm.file_create_mode);
+ fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+ umask(old_mask);
+ }
+ if (fd == -1) {
+ mail_storage_set_critical(&file->storage->storage,
+ "open(%s, O_CREAT) failed: %m", path);
+ } else if (perm.file_create_gid == (gid_t)-1) {
+ /* no group change */
+ } else if (fchown(fd, (uid_t)-1, perm.file_create_gid) < 0) {
+ if (errno == EPERM) {
+ mail_storage_set_critical(&file->storage->storage, "%s",
+ eperm_error_get_chgrp("fchown", path,
+ perm.file_create_gid,
+ perm.file_create_gid_origin));
+ } else {
+ mail_storage_set_critical(&file->storage->storage,
+ "fchown(%s, -1, %ld) failed: %m",
+ path, (long)perm.file_create_gid);
+ }
+ /* continue anyway */
+ }
+ return fd;
+}