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