Mercurial > dovecot > core-2.2
changeset 12309:e3fe87b855ef
Added lib-fs for simple filesystem accessing. Implemented POSIX, SiS and SiS-queue backends.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 19 Oct 2010 18:16:10 +0100 |
parents | 22689f4ceecb |
children | 21c70ff6b344 |
files | configure.in src/Makefile.am src/lib-dovecot/Makefile.am src/lib-fs/Makefile.am src/lib-fs/fs-api-private.h src/lib-fs/fs-api.c src/lib-fs/fs-api.h src/lib-fs/fs-posix.c src/lib-fs/fs-sis-common.c src/lib-fs/fs-sis-common.h src/lib-fs/fs-sis-queue.c src/lib-fs/fs-sis.c src/lib-fs/ostream-cmp.c src/lib-fs/ostream-cmp.h |
diffstat | 14 files changed, 1871 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.in Tue Oct 19 18:10:34 2010 +0100 +++ b/configure.in Tue Oct 19 18:16:10 2010 +0100 @@ -2459,7 +2459,7 @@ LIBDOVECOT_LOGIN='$(top_builddir)/src/login-common/libdovecot-login.la' LIBDOVECOT_LDA='$(top_builddir)/src/lib-lda/libdovecot-lda.la' else - LIBDOVECOT_DEPS='$(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib/liblib.la' + LIBDOVECOT_DEPS='$(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib/liblib.la' LIBDOVECOT="$LIBDOVECOT_DEPS \$(LIBICONV)" LIBDOVECOT_STORAGE_LAST='$(top_builddir)/src/lib-storage/list/libstorage_list.la $(top_builddir)/src/lib-storage/index/libstorage_index.la $(top_builddir)/src/lib-storage/libstorage.la $(top_builddir)/src/lib-index/libindex.la' LIBDOVECOT_STORAGE_FIRST='$(top_builddir)/src/lib-storage/libstorage_service.la $(top_builddir)/src/lib-storage/register/libstorage_register.la' @@ -2643,6 +2643,7 @@ src/lib-charset/Makefile src/lib-dict/Makefile src/lib-dns/Makefile +src/lib-fs/Makefile src/lib-imap/Makefile src/lib-index/Makefile src/lib-lda/Makefile
--- a/src/Makefile.am Tue Oct 19 18:10:34 2010 +0100 +++ b/src/Makefile.am Tue Oct 19 18:16:10 2010 +0100 @@ -3,6 +3,7 @@ lib-auth \ lib-charset \ lib-dns \ + lib-fs \ lib-mail \ lib-imap \ lib-master \
--- a/src/lib-dovecot/Makefile.am Tue Oct 19 18:10:34 2010 +0100 +++ b/src/lib-dovecot/Makefile.am Tue Oct 19 18:16:10 2010 +0100 @@ -5,6 +5,7 @@ ../lib-mail/libmail.la \ ../lib-auth/libauth.la \ ../lib-dns/libdns.la \ + ../lib-fs/libfs.la \ ../lib-charset/libcharset.la \ ../lib-master/libmaster.la \ ../lib/liblib.la
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/Makefile.am Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,21 @@ +noinst_LTLIBRARIES = libfs.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib + +libfs_la_SOURCES = \ + fs-api.c \ + fs-posix.c \ + fs-sis.c \ + fs-sis-common.c \ + fs-sis-queue.c \ + ostream-cmp.c + +headers = \ + fs-api.h \ + fs-api-private.h \ + fs-sis-common.h \ + ostream-cmp.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-api-private.h Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,62 @@ +#ifndef FS_API_PRIVATE_H +#define FS_API_PRIVATE_H + +#include "fs-api.h" + +struct fs_vfuncs { + struct fs *(*init)(const char *args, const struct fs_settings *set); + void (*deinit)(struct fs *fs); + + int (*open)(struct fs *fs, const char *path, enum fs_open_mode mode, + enum fs_open_flags flags, struct fs_file **file_r); + void (*close)(struct fs_file *file); + + ssize_t (*read)(struct fs_file *file, void *buf, size_t size); + struct istream *(*read_stream)(struct fs_file *file, + size_t max_buffer_size); + + int (*write)(struct fs_file *file, const void *data, size_t size); + void (*write_stream)(struct fs_file *file); + int (*write_stream_finish)(struct fs_file *file, bool success); + + int (*lock)(struct fs_file *file, unsigned int secs, + struct fs_lock **lock_r); + void (*unlock)(struct fs_lock *lock); + int (*fdatasync)(struct fs_file *file); + + int (*exists)(struct fs *fs, const char *path); + int (*stat)(struct fs *fs, const char *path, struct stat *st_r); + int (*link)(struct fs *fs, const char *src, const char *dest); + int (*rename)(struct fs *fs, const char *src, const char *dest); + int (*unlink)(struct fs *fs, const char *path); + int (*rmdir)(struct fs *fs, const char *path); +}; + +struct fs { + const char *name; + struct fs_vfuncs v; + + struct fs_settings set; + string_t *last_error; + + unsigned int files_open_count; +}; + +struct fs_file { + struct fs *fs; + struct ostream *output; + char *path; +}; + +struct fs_lock { + struct fs_file *file; +}; + +extern struct fs fs_class_posix; +extern struct fs fs_class_sis; +extern struct fs fs_class_sis_queue; + +void fs_set_error(struct fs *fs, const char *fmt, ...) ATTR_FORMAT(2, 3); +void fs_set_critical(struct fs *fs, const char *fmt, ...) ATTR_FORMAT(2, 3); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-api.c Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,199 @@ +/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "fs-api-private.h" + +static struct fs *fs_classes[] = { + &fs_class_posix, + &fs_class_sis, + &fs_class_sis_queue +}; + +static struct fs * +fs_alloc(const struct fs *fs_class, const char *args, + const struct fs_settings *set) +{ + struct fs *fs; + + fs = fs_class->v.init(args, set); + fs->last_error = str_new(default_pool, 64); + return fs; +} + +struct fs *fs_init(const char *driver, const char *args, + const struct fs_settings *set) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(fs_classes); i++) { + if (strcmp(fs_classes[i]->name, driver) == 0) + return fs_alloc(fs_classes[i], args, set); + } + i_fatal("Unknown fs driver: %s", driver); +} + +void fs_deinit(struct fs **_fs) +{ + struct fs *fs = *_fs; + + *_fs = NULL; + + if (fs->files_open_count > 0) { + i_panic("fs-%s: %u files still open", + fs->name, fs->files_open_count); + } + + str_free(&fs->last_error); + fs->v.deinit(fs); +} + +int fs_open(struct fs *fs, const char *path, int mode_flags, + struct fs_file **file_r) +{ + int ret; + + T_BEGIN { + ret = fs->v.open(fs, path, mode_flags & FS_OPEN_MODE_MASK, + mode_flags & ~FS_OPEN_MODE_MASK, file_r); + } T_END; + if (ret == 0) + fs->files_open_count++; + return ret; +} + +void fs_close(struct fs_file **_file) +{ + struct fs_file *file = *_file; + + i_assert(file->fs->files_open_count > 0); + + *_file = NULL; + + file->fs->files_open_count--; + file->fs->v.close(file); +} + +const char *fs_file_path(struct fs_file *file) +{ + return file->path; +} + +const char *fs_last_error(struct fs *fs) +{ + if (str_len(fs->last_error) == 0) + return "BUG: Unknown fs error"; + return str_c(fs->last_error); +} + +const char *fs_file_last_error(struct fs_file *file) +{ + return fs_last_error(file->fs); +} + +ssize_t fs_read(struct fs_file *file, void *buf, size_t size) +{ + return file->fs->v.read(file, buf, size); +} + +struct istream *fs_read_stream(struct fs_file *file, size_t max_buffer_size) +{ + return file->fs->v.read_stream(file, max_buffer_size); +} + +int fs_write(struct fs_file *file, const void *data, size_t size) +{ + return file->fs->v.write(file, data, size); +} + +struct ostream *fs_write_stream(struct fs_file *file) +{ + file->fs->v.write_stream(file); + i_assert(file->output != NULL); + return file->output; +} + +int fs_write_stream_finish(struct fs_file *file, struct ostream **output) +{ + i_assert(*output == file->output); + + *output = NULL; + return file->fs->v.write_stream_finish(file, TRUE); +} + +void fs_write_stream_abort(struct fs_file *file, struct ostream **output) +{ + i_assert(*output == file->output); + + *output = NULL; + (void)file->fs->v.write_stream_finish(file, FALSE); +} + +int fs_lock(struct fs_file *file, unsigned int secs, struct fs_lock **lock_r) +{ + return file->fs->v.lock(file, secs, lock_r); +} + +void fs_unlock(struct fs_lock **_lock) +{ + struct fs_lock *lock = *_lock; + + *_lock = NULL; + lock->file->fs->v.unlock(lock); +} + +int fs_fdatasync(struct fs_file *file) +{ + return file->fs->v.fdatasync(file); +} + +int fs_exists(struct fs *fs, const char *path) +{ + return fs->v.exists(fs, path); +} + +int fs_stat(struct fs *fs, const char *path, struct stat *st_r) +{ + return fs->v.stat(fs, path, st_r); +} + +int fs_link(struct fs *fs, const char *src, const char *dest) +{ + return fs->v.link(fs, src, dest); +} + +int fs_rename(struct fs *fs, const char *src, const char *dest) +{ + return fs->v.rename(fs, src, dest); +} + +int fs_unlink(struct fs *fs, const char *path) +{ + return fs->v.unlink(fs, path); +} + +int fs_rmdir(struct fs *fs, const char *path) +{ + return fs->v.rmdir(fs, path); +} + +void fs_set_error(struct fs *fs, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + str_truncate(fs->last_error, 0); + str_vprintfa(fs->last_error, fmt, args); + va_end(args); +} + +void fs_set_critical(struct fs *fs, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + str_truncate(fs->last_error, 0); + str_vprintfa(fs->last_error, fmt, args); + i_error("fs-%s: %s", fs->name, str_c(fs->last_error)); + va_end(args); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-api.h Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,101 @@ +#ifndef FS_API_H +#define FS_API_H + +struct stat; +struct fs_file; +struct fs_lock; + +enum fs_open_mode { + /* Open only for reading, or fail with ENOENT if it doesn't exist */ + FS_OPEN_MODE_RDONLY, + /* Create a new file, fail with EEXIST if it already exists */ + FS_OPEN_MODE_CREATE, + /* Create or replace a file */ + FS_OPEN_MODE_REPLACE, + /* Append to existing file, fail with ENOENT if it doesn't exist */ + FS_OPEN_MODE_APPEND + +#define FS_OPEN_MODE_MASK 0x0f +}; + +enum fs_open_flags { + /* Call fdatasync() on files after writes */ + FS_OPEN_FLAG_FDATASYNC = 0x10, + /* Create any missing parent directories for new files */ + FS_OPEN_FLAG_MKDIR = 0x20 +}; + +struct fs_settings { + /* When creating temporary files, use this prefix + (to avoid conflicts with existing files). */ + const char *temp_file_prefix; +}; + +struct fs *fs_init(const char *driver, const char *args, + const struct fs_settings *set); +void fs_deinit(struct fs **fs); + +/* Returns 0 if opened, -1 if error (errno is set). */ +int fs_open(struct fs *fs, const char *path, int mode_flags, + struct fs_file **file_r); +void fs_close(struct fs_file **file); + +/* Returns the path given to fs_open(). */ +const char *fs_file_path(struct fs_file *file); + +/* Return the error message for the last failed operation. */ +const char *fs_last_error(struct fs *fs); +/* Convenience function for the above. Errors aren't preserved across files. */ +const char *fs_file_last_error(struct fs_file *file); + +/* Returns >0 if something was read, -1 if error (errno is set). */ +ssize_t fs_read(struct fs_file *file, void *buf, size_t size); +/* Returns a stream for reading from file. Multiple streams can be opened, + and caller must destroy the streams before closing the file. */ +struct istream *fs_read_stream(struct fs_file *file, size_t max_buffer_size); + +/* Returns 0 if ok, -1 if error (errno is set). Note: With CREATE/REPLACE mode + files you can call fs_write() only once, the file creation is finished by it. + CREATE can return EEXIST here, if the destination file was already created. + With APPEND mode each fs_write() atomically appends the given data to + file. */ +int fs_write(struct fs_file *file, const void *data, size_t size); + +/* Write to file via output stream. The stream will be destroyed by + fs_write_stream_finish/abort. */ +struct ostream *fs_write_stream(struct fs_file *file); +/* Finish writing via stream. The file will be created/replaced/appended only + after this call, same as with fs_write(). Anything written to the stream + won't be visible earlier. */ +int fs_write_stream_finish(struct fs_file *file, struct ostream **output); +/* Abort writing via stream. Anything written to the stream is discarded. */ +void fs_write_stream_abort(struct fs_file *file, struct ostream **output); + +/* Exclusively lock a file. If file is already locked, wait for it for given + number of seconds (0 = fail immediately). Returns 1 if locked, 0 if wait + timed out, -1 if error. */ +int fs_lock(struct fs_file *file, unsigned int secs, struct fs_lock **lock_r); +void fs_unlock(struct fs_lock **lock); + +/* Make sure all written data is flushed to disk. */ +int fs_fdatasync(struct fs_file *file); + +/* Returns 1 if file exists, 0 if not, -1 if error occurred. */ +int fs_exists(struct fs *fs, const char *path); +/* Returns 0 if ok, -1 if error occurred (e.g. errno=ENOENT). + All fs backends may not support all stat fields. */ +int fs_stat(struct fs *fs, const char *path, struct stat *st_r); +/* Create a hard link. Destination parent directories are created + automatically. Returns 0 if ok, -1 if error occurred + (errno=EXDEV if hard links not supported by backend). */ +int fs_link(struct fs *fs, const char *src, const char *dest); +/* Atomically rename a file. Destination parent directories are created + automatically. Returns 0 if ok, -1 if error occurred + (errno=EXDEV if hard links not supported by backend). */ +int fs_rename(struct fs *fs, const char *src, const char *dest); +/* Unlink a file. */ +int fs_unlink(struct fs *fs, const char *path); +/* Delete a directory. Returns 0 if ok, -1 if error occurred. */ +int fs_rmdir(struct fs *fs, const char *path); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-posix.c Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,529 @@ +/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "safe-mkstemp.h" +#include "mkdir-parents.h" +#include "write-full.h" +#include "file-lock.h" +#include "file-dotlock.h" +#include "fs-api-private.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#define FS_POSIX_DOTLOCK_STALE_TIMEOUT_SECS (60*10) + +enum fs_posix_lock_method { + FS_POSIX_LOCK_METHOD_FLOCK, + FS_POSIX_LOCK_METHOD_DOTLOCK +}; + +struct posix_fs { + struct fs fs; + char *temp_file_prefix; + enum fs_posix_lock_method lock_method; +}; + +struct posix_fs_file { + struct fs_file file; + char *temp_path; + int fd; + enum fs_open_mode open_mode; + enum fs_open_flags open_flags; + + buffer_t *write_buf; + + bool seek_to_beginning; + bool success; +}; + +struct posix_fs_lock { + struct fs_lock lock; + struct file_lock *file_lock; + struct dotlock *dotlock; +}; + +static struct fs * +fs_posix_init(const char *args, const struct fs_settings *set) +{ + struct posix_fs *fs; + + fs = i_new(struct posix_fs, 1); + fs->fs = fs_class_posix; + fs->temp_file_prefix = set->temp_file_prefix != NULL ? + i_strdup(set->temp_file_prefix) : i_strdup("temp.dovecot."); + fs->fs.set.temp_file_prefix = fs->temp_file_prefix; + + if (*args == '\0') + fs->lock_method = FS_POSIX_LOCK_METHOD_FLOCK; + else if (strcmp(args, "lock=flock") == 0) + fs->lock_method = FS_POSIX_LOCK_METHOD_FLOCK; + else if (strcmp(args, "lock=dotlock") == 0) + fs->lock_method = FS_POSIX_LOCK_METHOD_DOTLOCK; + else + i_fatal("fs-posix: Unknown args '%s'", args); + return &fs->fs; +} + +static void fs_posix_deinit(struct fs *_fs) +{ + struct posix_fs *fs = (struct posix_fs *)_fs; + + i_free(fs->temp_file_prefix); + i_free(fs); +} + +static int fs_posix_create_parent_dir(struct fs *fs, const char *path) +{ + const char *dir, *fname; + + fname = strrchr(path, '/'); + if (fname == NULL) + return 0; + dir = t_strdup_until(path, fname); + if (mkdir_parents(dir, 0700) < 0 && errno != EEXIST) { + fs_set_error(fs, "mkdir_parents(%s) failed: %m", dir); + return -1; + } + return 0; +} + +static int +fs_posix_create(struct posix_fs *fs, const char *path, enum fs_open_flags flags, + char **temp_path_r) +{ + struct fs *_fs = &fs->fs; + string_t *str = t_str_new(256); + const char *slash = strrchr(path, '/'); + int fd; + + if (slash != NULL) + str_append_n(str, path, slash-path + 1); + str_append(str, fs->temp_file_prefix); + + fd = safe_mkstemp_hostpid(str, 0600, (uid_t)-1, (gid_t)-1); + if (fd == -1 && errno == ENOENT && (flags & FS_OPEN_FLAG_MKDIR) != 0) { + if (fs_posix_create_parent_dir(_fs, path) < 0) + return -1; + fd = safe_mkstemp_hostpid(str, 0600, (uid_t)-1, (gid_t)-1); + } + if (fd == -1) { + fs_set_error(_fs, "safe_mkstemp(%s) failed: %m", str_c(str)); + return -1; + } + *temp_path_r = i_strdup(str_c(str)); + return fd; +} + +static int +fs_posix_open(struct fs *_fs, const char *path, enum fs_open_mode mode, + enum fs_open_flags flags, struct fs_file **file_r) +{ + struct posix_fs *fs = (struct posix_fs *)_fs; + struct posix_fs_file *file; + char *temp_path = NULL; + int fd = -1; + + switch (mode) { + case FS_OPEN_MODE_RDONLY: + fd = open(path, O_RDONLY); + if (fd == -1) + fs_set_error(_fs, "open(%s) failed: %m", path); + break; + case FS_OPEN_MODE_APPEND: + fd = open(path, O_RDWR | O_APPEND); + if (fd == -1) + fs_set_error(_fs, "open(%s) failed: %m", path); + break; + case FS_OPEN_MODE_CREATE: + case FS_OPEN_MODE_REPLACE: + T_BEGIN { + fd = fs_posix_create(fs, path, flags, &temp_path); + } T_END; + break; + } + if (fd == -1) + return -1; + + file = i_new(struct posix_fs_file, 1); + file->file.fs = _fs; + file->file.path = i_strdup(path); + file->open_mode = mode; + file->open_flags = flags; + file->temp_path = temp_path; + file->fd = fd; + + *file_r = &file->file; + return 0; +} + +static void fs_posix_close(struct fs_file *_file) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + + i_assert(_file->output == NULL); + + switch (file->open_mode) { + case FS_OPEN_MODE_RDONLY: + case FS_OPEN_MODE_APPEND: + break; + case FS_OPEN_MODE_CREATE: + case FS_OPEN_MODE_REPLACE: + if (file->success) + break; + /* failed to create/replace this. delete the temp file */ + if (unlink(file->temp_path) < 0) { + fs_set_critical(_file->fs, "unlink(%s) failed: %m", + file->temp_path); + } + break; + } + + if (file->fd != -1) { + if (close(file->fd) < 0) { + fs_set_critical(_file->fs, "close(%s) failed: %m", + _file->path); + } + } + i_free(file->temp_path); + i_free(file->file.path); + i_free(file); +} + +static ssize_t fs_posix_read(struct fs_file *_file, void *buf, size_t size) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + ssize_t ret; + + if (file->seek_to_beginning) { + file->seek_to_beginning = FALSE; + if (lseek(file->fd, 0, SEEK_SET) < 0) { + fs_set_critical(_file->fs, "lseek(%s, 0) failed: %m", + _file->path); + return -1; + } + } + + ret = read(file->fd, buf, size); + if (ret < 0) + fs_set_error(_file->fs, "read(%s) failed: %m", _file->path); + return ret; +} + +static struct istream * +fs_posix_read_stream(struct fs_file *_file, size_t max_buffer_size) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + + return i_stream_create_fd(file->fd, max_buffer_size, FALSE); +} + +static int fs_posix_write_finish(struct posix_fs_file *file) +{ + int ret; + + if ((file->open_flags & FS_OPEN_FLAG_FDATASYNC) != 0) { + if (fs_fdatasync(&file->file) < 0) + return -1; + } + + if (close(file->fd) < 0) { + file->fd = -1; + fs_set_error(file->file.fs, "close(%s) failed: %m", + file->file.path); + return -1; + } + file->fd = -1; + + switch (file->open_mode) { + case FS_OPEN_MODE_CREATE: + if ((ret = link(file->temp_path, file->file.path)) < 0) { + fs_set_error(file->file.fs, "link(%s, %s) failed: %m", + file->temp_path, file->file.path); + } + if (unlink(file->temp_path) < 0) { + fs_set_error(file->file.fs, "unlink(%s) failed: %m", + file->temp_path); + } + if (ret < 0) + return -1; + break; + case FS_OPEN_MODE_REPLACE: + if (rename(file->temp_path, file->file.path) < 0) { + fs_set_error(file->file.fs, "rename(%s, %s) failed: %m", + file->temp_path, file->file.path); + return -1; + } + break; + default: + i_unreached(); + } + file->success = TRUE; + file->seek_to_beginning = TRUE; + return 0; +} + +static int fs_posix_write(struct fs_file *_file, const void *data, size_t size) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + ssize_t ret; + + if (file->open_mode != FS_OPEN_MODE_APPEND) { + if (write_full(file->fd, data, size) < 0) { + fs_set_error(_file->fs, "write(%s) failed: %m", + _file->path); + return -1; + } + return fs_posix_write_finish(file); + } + + /* atomic append - it should either succeed or fail */ + ret = write(file->fd, data, size); + if (ret < 0) { + fs_set_error(_file->fs, "write(%s) failed: %m", _file->path); + return -1; + } + if ((size_t)ret != size) { + fs_set_error(_file->fs, + "write(%s) returned %"PRIuSIZE_T"/%"PRIuSIZE_T, + _file->path, (size_t)ret, size); + errno = ENOSPC; + return -1; + } + return 0; +} + +static void fs_posix_write_stream(struct fs_file *_file) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + + i_assert(_file->output == NULL); + + if (file->open_mode == FS_OPEN_MODE_APPEND) { + file->write_buf = buffer_create_dynamic(default_pool, 1024*32); + _file->output = o_stream_create_buffer(file->write_buf); + return; + } + + _file->output = o_stream_create_fd_file(file->fd, (uoff_t)-1, FALSE); +} + +static int fs_posix_write_stream_finish(struct fs_file *_file, bool success) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + int ret = success ? 0 : -1; + + (void)o_stream_flush(_file->output); + if (_file->output->last_failed_errno < 0) { + errno = _file->output->last_failed_errno; + fs_set_error(_file->fs, "write(%s) failed: %m", + o_stream_get_name(_file->output)); + ret = -1; + } + o_stream_destroy(&_file->output); + + switch (file->open_mode) { + case FS_OPEN_MODE_APPEND: + if (ret == 0) { + ret = fs_posix_write(_file, file->write_buf->data, + file->write_buf->used); + } + buffer_free(&file->write_buf); + break; + case FS_OPEN_MODE_CREATE: + case FS_OPEN_MODE_REPLACE: + if (ret == 0) + ret = fs_posix_write_finish(file); + break; + case FS_OPEN_MODE_RDONLY: + i_unreached(); + } + return 0; +} + +static int +fs_posix_lock(struct fs_file *_file, unsigned int secs, struct fs_lock **lock_r) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + struct posix_fs *fs = (struct posix_fs *)_file->fs; + struct dotlock_settings dotlock_set; + struct posix_fs_lock fs_lock, *ret_lock; + int ret = -1; + + memset(&fs_lock, 0, sizeof(fs_lock)); + fs_lock.lock.file = _file; + + switch (fs->lock_method) { + case FS_POSIX_LOCK_METHOD_FLOCK: +#ifndef HAVE_FLOCK + fs_set_error(_file->fs, "flock() not supported by OS " + "(for file %s)", _file->path); +#else + if (secs == 0) { + ret = file_try_lock(file->fd, _file->path, F_WRLCK, + FILE_LOCK_METHOD_FLOCK, + &fs_lock.file_lock); + } else { + ret = file_wait_lock(file->fd, _file->path, F_WRLCK, + FILE_LOCK_METHOD_FLOCK, secs, + &fs_lock.file_lock); + } + if (ret < 0) { + fs_set_error(_file->fs, "flock(%s) failed: %m", + _file->path); + } +#endif + break; + case FS_POSIX_LOCK_METHOD_DOTLOCK: + memset(&dotlock_set, 0, sizeof(dotlock_set)); + dotlock_set.stale_timeout = FS_POSIX_DOTLOCK_STALE_TIMEOUT_SECS; + dotlock_set.use_excl_lock = TRUE; + dotlock_set.timeout = secs; + + ret = file_dotlock_create(&dotlock_set, _file->path, + secs == 0 ? 0 : + DOTLOCK_CREATE_FLAG_NONBLOCK, + &fs_lock.dotlock); + if (ret < 0) { + fs_set_error(_file->fs, + "file_dotlock_create(%s) failed: %m", + _file->path); + } + break; + } + if (ret <= 0) + return ret; + + ret_lock = i_new(struct posix_fs_lock, 1); + *ret_lock = fs_lock; + *lock_r = &ret_lock->lock; + return 1; +} + +static void fs_posix_unlock(struct fs_lock *_lock) +{ + struct posix_fs_lock *lock = (struct posix_fs_lock *)_lock; + + if (lock->file_lock != NULL) + file_unlock(&lock->file_lock); + if (lock->dotlock != NULL) + (void)file_dotlock_delete(&lock->dotlock); + i_free(lock); +} + +static int fs_posix_fdatasync(struct fs_file *_file) +{ + struct posix_fs_file *file = (struct posix_fs_file *)_file; + + if (fdatasync(file->fd) < 0) { + fs_set_error(_file->fs, "fdatasync(%s) failed: %m", + _file->path); + return -1; + } + return 0; +} + +static int fs_posix_exists(struct fs *fs, const char *path) +{ + struct stat st; + + if (stat(path, &st) < 0) { + if (errno != ENOENT) { + fs_set_error(fs, "stat(%s) failed: %m", path); + return -1; + } + return 0; + } + return 1; +} + +static int fs_posix_stat(struct fs *fs, const char *path, struct stat *st_r) +{ + if (stat(path, st_r) < 0) { + fs_set_error(fs, "stat(%s) failed: %m", path); + return -1; + } + return 0; +} + +static int fs_posix_link(struct fs *fs, const char *src, const char *dest) +{ + int ret; + + ret = link(src, dest); + if (ret < 0 && errno == ENOENT) { + if (fs_posix_create_parent_dir(fs, dest) < 0) + return -1; + ret = link(src, dest); + } + if (ret < 0) { + fs_set_error(fs, "link(%s, %s) failed: %m", src, dest); + return -1; + } + return 0; +} + +static int fs_posix_rename(struct fs *fs, const char *src, const char *dest) +{ + int ret; + + ret = rename(src, dest); + if (ret < 0 && errno == ENOENT) { + if (fs_posix_create_parent_dir(fs, dest) < 0) + return -1; + ret = rename(src, dest); + } + if (ret < 0) { + fs_set_error(fs, "link(%s, %s) failed: %m", src, dest); + return -1; + } + return 0; +} + +static int fs_posix_unlink(struct fs *fs, const char *path) +{ + if (unlink(path) < 0) { + fs_set_error(fs, "unlink(%s) failed: %m", path); + return -1; + } + return 0; +} + +static int fs_posix_rmdir(struct fs *fs, const char *path) +{ + if (rmdir(path) < 0) { + fs_set_error(fs, "rmdir(%s) failed: %m", path); + return -1; + } + return 0; +} + +struct fs fs_class_posix = { + .name = "posix", + .v = { + fs_posix_init, + fs_posix_deinit, + fs_posix_open, + fs_posix_close, + fs_posix_read, + fs_posix_read_stream, + fs_posix_write, + fs_posix_write_stream, + fs_posix_write_stream_finish, + fs_posix_lock, + fs_posix_unlock, + fs_posix_fdatasync, + fs_posix_exists, + fs_posix_stat, + fs_posix_link, + fs_posix_rename, + fs_posix_unlink, + fs_posix_rmdir + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-sis-common.c Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,58 @@ +/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "fs-sis-common.h" + +#include <sys/stat.h> + +int fs_sis_path_parse(struct fs *fs, const char *path, + const char **dir_r, const char **hash_r) +{ + const char *fname, *p; + + fname = strrchr(path, '/'); + if (fname == NULL) { + *dir_r = "."; + fname = path; + } else { + *dir_r = t_strdup_until(path, fname); + fname++; + } + + /* assume filename begins with "<hash>-" */ + p = strchr(fname, '-'); + if (p == NULL) { + fs_set_error(fs, "open(%s) failed: " + "Filenames must begin with '<hash>-'", path); + return -1; + } + *hash_r = t_strdup_until(fname, p); + return 0; +} + +void fs_sis_try_unlink_hash_file(struct fs *fs, struct fs *super, + const char *path) +{ + struct stat st1, st2; + const char *dir, *hash, *hash_path, *hash_dir; + + if (fs_sis_path_parse(fs, path, &dir, &hash) == 0 && + fs_stat(super, path, &st1) == 0 && st1.st_nlink == 2) { + /* this may be the last link. if hashes/ file is the same, + delete it. */ + hash_path = t_strdup_printf("%s/"HASH_DIR_NAME"/%s", dir, hash); + if (fs_stat(super, hash_path, &st2) == 0 && + st1.st_ino == st2.st_ino && + CMP_DEV_T(st1.st_dev, st2.st_dev)) { + if (fs_unlink(super, hash_path) < 0) + i_error("%s", fs_last_error(super)); + else { + /* try to rmdir the hashes/ directory */ + hash_dir = t_strdup_printf("%s/"HASH_DIR_NAME, + dir); + (void)fs_rmdir(super, hash_dir); + } + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-sis-common.h Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,14 @@ +#ifndef FS_SIS_COMMON_H +#define FS_SIS_COMMON_H + +#include "fs-api-private.h" + +#define HASH_DIR_NAME "hashes" + +int fs_sis_path_parse(struct fs *fs, const char *path, + const char **dir_r, const char **hash_r); +void fs_sis_try_unlink_hash_file(struct fs *fs, struct fs *super, + const char *path); + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-sis-queue.c Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,322 @@ +/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "istream.h" +#include "fs-sis-common.h" + +#define QUEUE_DIR_NAME "queue" + +struct sis_queue_fs { + struct fs fs; + struct fs *super; + char *queue_dir; +}; + +struct sis_queue_fs_file { + struct fs_file file; + struct fs_file *super; +}; + +static void fs_sis_queue_copy_error(struct sis_queue_fs *fs) +{ + fs_set_error(&fs->fs, "%s", fs_last_error(fs->super)); +} + +static void fs_sis_queue_file_copy_error(struct sis_queue_fs_file *file) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)file->file.fs; + + fs_sis_queue_copy_error(fs); +} + +static struct fs * +fs_sis_queue_init(const char *args, const struct fs_settings *set) +{ + struct sis_queue_fs *fs; + const char *p, *parent_fs; + + fs = i_new(struct sis_queue_fs, 1); + fs->fs = fs_class_sis_queue; + + /* <queue_dir>:<parent fs>[:<args>] */ + + p = strchr(args, ':'); + if (p == NULL || p[1] == '\0') + i_fatal("fs-sis-queue: Parent filesystem not given as parameter"); + + fs->queue_dir = i_strdup_until(args, p); + parent_fs = p + 1; + + p = strchr(parent_fs, ':'); + if (p == NULL) + fs->super = fs_init(parent_fs, "", set); + else + fs->super = fs_init(t_strdup_until(parent_fs, p), p+1, set); + return &fs->fs; +} + +static void fs_sis_queue_deinit(struct fs *_fs) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + + fs_deinit(&fs->super); + i_free(fs->queue_dir); + i_free(fs); +} + +static int +fs_sis_queue_open(struct fs *_fs, const char *path, enum fs_open_mode mode, + enum fs_open_flags flags, struct fs_file **file_r) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + struct sis_queue_fs_file *file; + struct fs_file *super; + + if (mode == FS_OPEN_MODE_APPEND) { + fs_set_error(_fs, "APPEND mode not supported"); + return -1; + } + + if (fs_open(fs->super, path, mode | flags, &super) < 0) { + fs_sis_queue_copy_error(fs); + return -1; + } + + switch (mode) { + case FS_OPEN_MODE_RDONLY: + *file_r = super; + return 0; + case FS_OPEN_MODE_CREATE: + case FS_OPEN_MODE_REPLACE: + break; + case FS_OPEN_MODE_APPEND: + i_unreached(); + } + + file = i_new(struct sis_queue_fs_file, 1); + file->file.fs = _fs; + file->file.path = i_strdup(fs_file_path(super)); + file->super = super; + *file_r = &file->file; + return 0; +} + +static void fs_sis_queue_close(struct fs_file *_file) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + + fs_close(&file->super); + i_free(file->file.path); + i_free(file); +} + +static ssize_t fs_sis_queue_read(struct fs_file *_file, void *buf, size_t size) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + ssize_t ret; + + if ((ret = fs_read(file->super, buf, size)) < 0) + fs_sis_queue_file_copy_error(file); + return ret; +} + +static struct istream * +fs_sis_queue_read_stream(struct fs_file *_file, size_t max_buffer_size) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + + return fs_read_stream(file->super, max_buffer_size); +} + +static void fs_sis_queue_add(struct sis_queue_fs_file *file) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)file->file.fs; + struct fs_file *queue_file; + const char *fname, *path, *queue_path; + + path = fs_file_path(&file->file); + fname = strrchr(path, '/'); + if (fname != NULL) + fname++; + else + fname = path; + + queue_path = t_strdup_printf("%s/%s", fs->queue_dir, fname); + if (fs_open(fs->super, queue_path, + FS_OPEN_MODE_CREATE | FS_OPEN_FLAG_MKDIR, + &queue_file) < 0) { + i_error("fs-sis-queue: %s", fs_last_error(fs->super)); + return; + } + if (fs_write(queue_file, "", 0) < 0 && errno != EEXIST) + i_error("fs-sis-queue: %s", fs_last_error(fs->super)); + fs_close(&queue_file); +} + +static int fs_sis_queue_write(struct fs_file *_file, const void *data, size_t size) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + + if (fs_write(file->super, data, size) < 0) { + fs_sis_queue_file_copy_error(file); + return -1; + } + T_BEGIN { + fs_sis_queue_add(file); + } T_END; + return 0; +} + +static void fs_sis_queue_write_stream(struct fs_file *_file) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + + i_assert(_file->output == NULL); + + _file->output = fs_write_stream(file->super); +} + +static int fs_sis_queue_write_stream_finish(struct fs_file *_file, bool success) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + + if (!success) { + fs_write_stream_abort(file->super, &_file->output); + fs_sis_queue_file_copy_error(file); + return -1; + } + + if (fs_write_stream_finish(file->super, &_file->output) < 0) { + fs_sis_queue_file_copy_error(file); + return -1; + } + T_BEGIN { + fs_sis_queue_add(file); + } T_END; + return 0; +} + +static int +fs_sis_queue_lock(struct fs_file *_file, unsigned int secs, + struct fs_lock **lock_r) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + + if (fs_lock(file->super, secs, lock_r) < 0) { + fs_sis_queue_file_copy_error(file); + return -1; + } + return 0; +} + +static void fs_sis_queue_unlock(struct fs_lock *_lock ATTR_UNUSED) +{ + i_unreached(); +} + +static int fs_sis_queue_fdatasync(struct fs_file *_file) +{ + struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file; + + if (fs_fdatasync(file->super) < 0) { + fs_sis_queue_file_copy_error(file); + return -1; + } + return 0; +} + +static int fs_sis_queue_exists(struct fs *_fs, const char *path) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + + if (fs_exists(fs->super, path) < 0) { + fs_sis_queue_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_queue_stat(struct fs *_fs, const char *path, + struct stat *st_r) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + + if (fs_stat(fs->super, path, st_r) < 0) { + fs_sis_queue_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_queue_link(struct fs *_fs, const char *src, const char *dest) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + + if (fs_link(fs->super, src, dest) < 0) { + fs_sis_queue_copy_error(fs); + return -1; + } + return 0; +} + +static int +fs_sis_queue_rename(struct fs *_fs, const char *src, const char *dest) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + + if (fs_rename(fs->super, src, dest) < 0) { + fs_sis_queue_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_queue_unlink(struct fs *_fs, const char *path) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + + T_BEGIN { + fs_sis_try_unlink_hash_file(&fs->fs, fs->super, path); + } T_END; + if (fs_unlink(fs->super, path) < 0) { + fs_sis_queue_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_queue_rmdir(struct fs *_fs, const char *path) +{ + struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs; + + if (fs_rmdir(fs->super, path) < 0) { + fs_sis_queue_copy_error(fs); + return -1; + } + return 0; +} + +struct fs fs_class_sis_queue = { + .name = "sis-queue", + .v = { + fs_sis_queue_init, + fs_sis_queue_deinit, + fs_sis_queue_open, + fs_sis_queue_close, + fs_sis_queue_read, + fs_sis_queue_read_stream, + fs_sis_queue_write, + fs_sis_queue_write_stream, + fs_sis_queue_write_stream_finish, + fs_sis_queue_lock, + fs_sis_queue_unlock, + fs_sis_queue_fdatasync, + fs_sis_queue_exists, + fs_sis_queue_stat, + fs_sis_queue_link, + fs_sis_queue_rename, + fs_sis_queue_unlink, + fs_sis_queue_rmdir + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/fs-sis.c Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,428 @@ +/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "istream.h" +#include "ostream-cmp.h" +#include "fs-sis-common.h" + +struct sis_fs { + struct fs fs; + struct fs *super; +}; + +struct sis_fs_file { + struct fs_file file; + struct fs_file *super; + enum fs_open_mode open_mode; + + struct fs_file *hash_file; + struct istream *hash_input; + struct ostream *fs_output; + + char *hash, *hash_path; +}; + +static void fs_sis_copy_error(struct sis_fs *fs) +{ + fs_set_error(&fs->fs, "%s", fs_last_error(fs->super)); +} + +static void fs_sis_file_copy_error(struct sis_fs_file *file) +{ + struct sis_fs *fs = (struct sis_fs *)file->file.fs; + + fs_sis_copy_error(fs); +} + +static struct fs * +fs_sis_init(const char *args, const struct fs_settings *set) +{ + struct sis_fs *fs; + const char *p; + + fs = i_new(struct sis_fs, 1); + fs->fs = fs_class_sis; + + if (*args == '\0') + i_fatal("fs-sis: Parent filesystem not given as parameter"); + + p = strchr(args, ':'); + if (p == NULL) + fs->super = fs_init(args, "", set); + else + fs->super = fs_init(t_strdup_until(args, p), p+1, set); + return &fs->fs; +} + +static void fs_sis_deinit(struct fs *_fs) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + + fs_deinit(&fs->super); + i_free(fs); +} + +static int +fs_sis_open(struct fs *_fs, const char *path, enum fs_open_mode mode, + enum fs_open_flags flags, struct fs_file **file_r) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + struct sis_fs_file *file; + struct fs_file *super; + const char *dir, *hash; + + if (mode == FS_OPEN_MODE_APPEND) { + fs_set_error(_fs, "APPEND mode not supported"); + return -1; + } + + if (fs_open(fs->super, path, mode | flags, &super) < 0) { + fs_sis_copy_error(fs); + return -1; + } + + switch (mode) { + case FS_OPEN_MODE_RDONLY: + *file_r = super; + return 0; + case FS_OPEN_MODE_CREATE: + case FS_OPEN_MODE_REPLACE: + break; + case FS_OPEN_MODE_APPEND: + i_unreached(); + } + + if (fs_sis_path_parse(_fs, path, &dir, &hash) < 0) + return -1; + + file = i_new(struct sis_fs_file, 1); + file->file.fs = _fs; + file->file.path = i_strdup(fs_file_path(super)); + file->super = super; + file->open_mode = mode; + file->hash = i_strdup(hash); + + /* if hashes/<hash> already exists, open it */ + file->hash_path = i_strdup_printf("%s/"HASH_DIR_NAME"/%s", dir, hash); + if (fs_open(fs->super, file->hash_path, FS_OPEN_MODE_RDONLY, + &file->hash_file) < 0 && errno != ENOENT) { + i_error("fs-sis: Couldn't open hash file: %s", + fs_last_error(fs->super)); + } + if (file->hash_file != NULL) { + file->hash_input = + fs_read_stream(file->hash_file, IO_BLOCK_SIZE); + } + + *file_r = &file->file; + return 0; +} + +static void fs_sis_close(struct fs_file *_file) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + + if (file->hash_file != NULL) { + i_stream_unref(&file->hash_input); + fs_close(&file->hash_file); + } + fs_close(&file->super); + i_free(file->hash); + i_free(file->file.path); + i_free(file); +} + +static ssize_t fs_sis_read(struct fs_file *_file, void *buf, size_t size) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + ssize_t ret; + + if ((ret = fs_read(file->super, buf, size)) < 0) + fs_sis_file_copy_error(file); + return ret; +} + +static struct istream * +fs_sis_read_stream(struct fs_file *_file, size_t max_buffer_size) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + + return fs_read_stream(file->super, max_buffer_size); +} + +static bool fs_sis_try_link(struct sis_fs_file *file) +{ + const char *path = fs_file_path(&file->file); + const struct stat *st; + struct stat st2; + + st = i_stream_stat(file->hash_input, FALSE); + + /* we can use the existing file */ + if (fs_link(file->super->fs, file->hash_path, path) < 0) { + if (errno != ENOENT && errno != EMLINK) + i_error("fs-sis: %s", fs_last_error(file->super->fs)); + /* failed to use link(), continue as if it hadn't been equal */ + return FALSE; + } + if (fs_stat(file->super->fs, path, &st2) < 0) { + i_error("fs-sis: %s", fs_last_error(file->super->fs)); + if (fs_unlink(file->super->fs, path) < 0) + i_error("fs-sis: %s", fs_last_error(file->super->fs)); + return FALSE; + } + if (st->st_ino != st2.st_ino) { + /* the hashes/ file was already replaced with something else */ + if (fs_unlink(file->super->fs, path) < 0) + i_error("fs-sis: %s", fs_last_error(file->super->fs)); + return FALSE; + } + return TRUE; +} + +static void fs_sis_replace_hash_file(struct sis_fs_file *file) +{ + const char *hash_fname, *path = fs_file_path(&file->file); + struct fs *super_fs = file->super->fs; + string_t *temp_path; + int ret; + + if (file->hash_input == NULL) { + /* hash file didn't exist previously. we should be able to + create it with link() */ + if (fs_link(super_fs, path, file->hash_path) < 0) { + if (errno == EEXIST) { + /* the file was just created. it's probably + a duplicate, but it's too much trouble + trying to deduplicate it anymore */ + } else { + i_error("fs-sis: %s", fs_last_error(super_fs)); + } + } + return; + } + + temp_path = t_str_new(256); + hash_fname = strrchr(file->hash_path, '/'); + if (hash_fname == NULL) + hash_fname = file->hash_path; + else { + str_append_n(temp_path, file->hash_path, + (hash_fname-file->hash_path) + 1); + hash_fname++; + } + str_printfa(temp_path, "%s%s.tmp", + super_fs->set.temp_file_prefix, hash_fname); + + /* replace existing hash file atomically */ + ret = fs_link(super_fs, path, str_c(temp_path)); + if (ret < 0 && errno == EEXIST) { + /* either someone's racing us or it's a stale file. + try to continue. */ + if (fs_unlink(super_fs, str_c(temp_path)) < 0 && + errno != ENOENT) + i_error("fs-sis: %s", fs_last_error(super_fs)); + ret = fs_link(super_fs, path, str_c(temp_path)); + } + if (ret < 0) { + i_error("fs-sis: %s", fs_last_error(super_fs)); + return; + } + if (fs_rename(super_fs, str_c(temp_path), file->hash_path) < 0) { + if (errno == ENOENT) { + /* apparently someone else just renamed it. ignore. */ + } else { + i_error("fs-sis: %s", fs_last_error(super_fs)); + } + (void)fs_unlink(super_fs, str_c(temp_path)); + } +} + +static int fs_sis_write(struct fs_file *_file, const void *data, size_t size) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + + if (file->hash_input != NULL && + stream_cmp_block(file->hash_input, data, size) && + i_stream_is_eof(file->hash_input)) { + /* try to use existing file */ + if (fs_sis_try_link(file)) + return 0; + } + + if (fs_write(file->super, data, size) < 0) { + fs_sis_file_copy_error(file); + return -1; + } + T_BEGIN { + fs_sis_replace_hash_file(file); + } T_END; + return 0; +} + +static void fs_sis_write_stream(struct fs_file *_file) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + + i_assert(_file->output == NULL); + + file->fs_output = fs_write_stream(file->super); + if (file->hash_input == NULL) + _file->output = file->fs_output; + else { + /* compare if files are equal */ + _file->output = o_stream_create_cmp(file->fs_output, + file->hash_input); + } +} + +static int fs_sis_write_stream_finish(struct fs_file *_file, bool success) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + + if (!success) { + fs_write_stream_abort(file->super, &file->fs_output); + fs_sis_file_copy_error(file); + return -1; + } + + if (file->hash_input != NULL && + o_stream_cmp_equals(_file->output) && + i_stream_is_eof(file->hash_input)) { + if (fs_sis_try_link(file)) { + fs_write_stream_abort(file->super, &file->fs_output); + return 0; + } + } + + if (fs_write_stream_finish(file->super, &file->fs_output) < 0) { + fs_sis_file_copy_error(file); + return -1; + } + T_BEGIN { + fs_sis_replace_hash_file(file); + } T_END; + return 0; +} + +static int +fs_sis_lock(struct fs_file *_file, unsigned int secs, struct fs_lock **lock_r) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + + if (fs_lock(file->super, secs, lock_r) < 0) { + fs_sis_file_copy_error(file); + return -1; + } + return 0; +} + +static void fs_sis_unlock(struct fs_lock *_lock ATTR_UNUSED) +{ + i_unreached(); +} + +static int fs_sis_fdatasync(struct fs_file *_file) +{ + struct sis_fs_file *file = (struct sis_fs_file *)_file; + + if (fs_fdatasync(file->super) < 0) { + fs_sis_file_copy_error(file); + return -1; + } + return 0; +} + +static int fs_sis_exists(struct fs *_fs, const char *path) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + + if (fs_exists(fs->super, path) < 0) { + fs_sis_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_stat(struct fs *_fs, const char *path, struct stat *st_r) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + + if (fs_stat(fs->super, path, st_r) < 0) { + fs_sis_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_link(struct fs *_fs, const char *src, const char *dest) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + + if (fs_link(fs->super, src, dest) < 0) { + fs_sis_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_rename(struct fs *_fs, const char *src, const char *dest) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + + if (fs_rename(fs->super, src, dest) < 0) { + fs_sis_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_unlink(struct fs *_fs, const char *path) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + + T_BEGIN { + fs_sis_try_unlink_hash_file(&fs->fs, fs->super, path); + } T_END; + if (fs_unlink(fs->super, path) < 0) { + fs_sis_copy_error(fs); + return -1; + } + return 0; +} + +static int fs_sis_rmdir(struct fs *_fs, const char *path) +{ + struct sis_fs *fs = (struct sis_fs *)_fs; + + if (fs_rmdir(fs->super, path) < 0) { + fs_sis_copy_error(fs); + return -1; + } + return 0; +} + +struct fs fs_class_sis = { + .name = "sis", + .v = { + fs_sis_init, + fs_sis_deinit, + fs_sis_open, + fs_sis_close, + fs_sis_read, + fs_sis_read_stream, + fs_sis_write, + fs_sis_write_stream, + fs_sis_write_stream_finish, + fs_sis_lock, + fs_sis_unlock, + fs_sis_fdatasync, + fs_sis_exists, + fs_sis_stat, + fs_sis_link, + fs_sis_rename, + fs_sis_unlink, + fs_sis_rmdir + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/ostream-cmp.c Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,118 @@ +/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "ostream-internal.h" +#include "ostream-cmp.h" + +struct cmp_ostream { + struct ostream_private ostream; + + struct istream *input; + struct ostream *output; + bool equals; +}; + +static void cstream_copy_error(struct cmp_ostream *cstream) +{ + struct ostream *src = cstream->output; + struct ostream *dest = &cstream->ostream.ostream; + + dest->stream_errno = src->stream_errno; + dest->last_failed_errno = src->last_failed_errno; + dest->overflow = src->overflow; +} + +static void o_stream_cmp_close(struct iostream_private *stream) +{ + struct cmp_ostream *cstream = (struct cmp_ostream *)stream; + + if (cstream->output == NULL) + return; + + i_stream_unref(&cstream->input); + o_stream_flush(&cstream->ostream.ostream); + o_stream_unref(&cstream->output); +} + +static int o_stream_cmp_flush(struct ostream_private *stream) +{ + struct cmp_ostream *cstream = (struct cmp_ostream *)stream; + int ret; + + ret = o_stream_flush(cstream->output); + if (ret < 0) + cstream_copy_error(cstream); + return ret; +} + +bool stream_cmp_block(struct istream *input, + const unsigned char *data, size_t size) +{ + const unsigned char *indata; + size_t insize, max; + + while (size > 0) { + (void)i_stream_read_data(input, &indata, &insize, size-1); + max = I_MIN(insize, size); + if (insize == 0 || memcmp(data, indata, max) != 0) + return FALSE; + data += max; + size -= max; + i_stream_skip(input, max); + } + return TRUE; +} + +static ssize_t +o_stream_cmp_sendv(struct ostream_private *stream, + const struct const_iovec *iov, unsigned int iov_count) +{ + struct cmp_ostream *cstream = (struct cmp_ostream *)stream; + unsigned int i; + ssize_t ret; + + if (cstream->equals) { + for (i = 0; i < iov_count; i++) { + if (!stream_cmp_block(cstream->input, iov[i].iov_base, + iov[i].iov_len)) { + cstream->equals = FALSE; + break; + } + } + } + + if ((ret = o_stream_sendv(cstream->output, iov, iov_count)) < 0) { + cstream_copy_error(cstream); + return -1; + } + + stream->ostream.offset += ret; + return ret; +} + +struct ostream * +o_stream_create_cmp(struct ostream *output, struct istream *input) +{ + struct cmp_ostream *cstream; + + cstream = i_new(struct cmp_ostream, 1); + cstream->ostream.sendv = o_stream_cmp_sendv; + cstream->ostream.flush = o_stream_cmp_flush; + cstream->ostream.iostream.close = o_stream_cmp_close; + cstream->input = input; + cstream->output = output; + cstream->equals = TRUE; + i_stream_ref(input); + o_stream_ref(output); + + return o_stream_create(&cstream->ostream); +} + +bool o_stream_cmp_equals(struct ostream *_output) +{ + struct cmp_ostream *cstream = + (struct cmp_ostream *)_output->real_stream; + + return cstream->equals; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-fs/ostream-cmp.h Tue Oct 19 18:16:10 2010 +0100 @@ -0,0 +1,15 @@ +#ifndef OSTREAM_CMP_H +#define OSTREAM_CMP_H + +/* Compare given input stream to output being written to output stream. */ +struct ostream * +o_stream_create_cmp(struct ostream *output, struct istream *input); +/* Returns TRUE if input and output are equal so far. If the caller needs to + know if the files are entirely equal, it should check also if input stream + is at EOF. */ +bool o_stream_cmp_equals(struct ostream *output); + +bool stream_cmp_block(struct istream *input, + const unsigned char *data, size_t size); + +#endif