changeset 15198:ce57bacc3010

lib-fs API cleanups and improvements
author Timo Sirainen <tss@iki.fi>
date Fri, 12 Oct 2012 00:22:19 +0300
parents 2ac184a82b9a
children 73916b7be94e
files 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-storage/index/dbox-common/dbox-storage.c src/lib-storage/index/dbox-single/sdbox-copy.c src/lib-storage/index/dbox-single/sdbox-file.c src/lib-storage/index/index-attachment.c
diffstat 12 files changed, 868 insertions(+), 457 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-fs/fs-api-private.h	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-api-private.h	Fri Oct 12 00:22:19 2012 +0300
@@ -4,14 +4,29 @@
 #include "fs-api.h"
 
 struct fs_vfuncs {
-	int (*init)(const char *args, const struct fs_settings *set,
-		    struct fs **fs_r, const char **error_r);
+	struct fs *(*alloc)(void);
+	int (*init)(struct fs *fs, 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);
+	enum fs_properties (*get_properties)(struct fs *fs);
+
+	struct fs_file *(*file_init)(struct fs *fs, const char *path,
+				     enum fs_open_mode mode,
+				     enum fs_open_flags flags);
+	void (*file_deinit)(struct fs_file *file);
 
+	void (*set_async_callback)(struct fs_file *file,
+				   fs_file_async_callback_t *callback,
+				   void *context);
+	void (*wait_async)(struct fs *fs);
+
+	void (*set_metadata)(struct fs_file *file, const char *key,
+			     const char *value);
+	int (*get_metadata)(struct fs_file *file,
+			    const ARRAY_TYPE(fs_metadata) **metadata_r);
+
+	bool (*prefetch)(struct fs_file *file, uoff_t length);
 	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);
@@ -23,14 +38,16 @@
 	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);
+	int (*exists)(struct fs_file *file);
+	int (*stat)(struct fs_file *file, struct stat *st_r);
+	int (*copy)(struct fs_file *src, struct fs_file *dest);
+	int (*rename)(struct fs_file *src, struct fs_file *dest);
+	int (*delete_file)(struct fs_file *file);
+
+	struct fs_iter *(*iter_init)(struct fs *fs, const char *path);
+	const char *(*iter_next)(struct fs_iter *iter);
+	int (*iter_deinit)(struct fs_iter *iter);
 };
 
 struct fs {
@@ -53,6 +70,10 @@
 	struct fs_file *file;
 };
 
+struct fs_iter {
+	struct fs *fs;
+};
+
 extern struct fs fs_class_posix;
 extern struct fs fs_class_sis;
 extern struct fs fs_class_sis_queue;
@@ -60,4 +81,6 @@
 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);
 
+void fs_set_error_async(struct fs *fs);
+
 #endif
--- a/src/lib-fs/fs-api.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-api.c	Fri Oct 12 00:22:19 2012 +0300
@@ -14,26 +14,24 @@
 fs_alloc(const struct fs *fs_class, const char *args,
 	 const struct fs_settings *set, struct fs **fs_r, const char **error_r)
 {
-	struct fs *fs = NULL;
-	char *error_dup = NULL;
+	struct fs *fs;
 	int ret;
 
+	fs = fs_class->v.alloc();
+	fs->last_error = str_new(default_pool, 64);
+
 	T_BEGIN {
-		const char *error;
-
-		ret = fs_class->v.init(args, set, &fs, &error);
-		if (ret < 0)
-			error_dup = i_strdup(error);
+		ret = fs_class->v.init(fs, args, set);
 	} T_END;
 	if (ret < 0) {
 		/* a bit kludgy way to allow data stack frame usage in normal
 		   conditions but still be able to return error message from
 		   data stack. */
-		*error_r = t_strdup_printf("%s: %s", fs_class->name, error_dup);
-		i_free(error_dup);
+		*error_r = t_strdup_printf("%s: %s", fs_class->name,
+					   fs_last_error(fs));
+		fs_deinit(&fs);
 		return -1;
 	}
-	fs->last_error = str_new(default_pool, 64);
 	*fs_r = fs;
 	return 0;
 }
@@ -57,6 +55,7 @@
 void fs_deinit(struct fs **_fs)
 {
 	struct fs *fs = *_fs;
+	string_t *last_error = fs->last_error;
 
 	*_fs = NULL;
 
@@ -65,25 +64,25 @@
 			fs->name, fs->files_open_count);
 	}
 
-	str_free(&fs->last_error);
 	fs->v.deinit(fs);
+	str_free(&last_error);
 }
 
-int fs_open(struct fs *fs, const char *path, int mode_flags,
-	    struct fs_file **file_r)
+struct fs_file *fs_file_init(struct fs *fs, const char *path, int mode_flags)
 {
-	int ret;
+	struct fs_file *file;
+
+	i_assert(path != NULL);
 
 	T_BEGIN {
-		ret = fs->v.open(fs, path, mode_flags & FS_OPEN_MODE_MASK,
-				 mode_flags & ~FS_OPEN_MODE_MASK, file_r);
+		file = fs->v.file_init(fs, path, mode_flags & FS_OPEN_MODE_MASK,
+				       mode_flags & ~FS_OPEN_MODE_MASK);
 	} T_END;
-	if (ret == 0)
-		fs->files_open_count++;
-	return ret;
+	fs->files_open_count++;
+	return file;
 }
 
-void fs_close(struct fs_file **_file)
+void fs_file_deinit(struct fs_file **_file)
 {
 	struct fs_file *file = *_file;
 
@@ -92,7 +91,28 @@
 	*_file = NULL;
 
 	file->fs->files_open_count--;
-	file->fs->v.close(file);
+	file->fs->v.file_deinit(file);
+}
+
+enum fs_properties fs_get_properties(struct fs *fs)
+{
+	return fs->v.get_properties(fs);
+}
+
+void fs_set_metadata(struct fs_file *file, const char *key, const char *value)
+{
+	if (file->fs->v.set_metadata != NULL)
+		file->fs->v.set_metadata(file, key, value);
+}
+
+int fs_get_metadata(struct fs_file *file,
+		    const ARRAY_TYPE(fs_metadata) **metadata_r)
+{
+	if (file->fs->v.get_metadata == NULL) {
+		fs_set_error(file->fs, "Metadata not supported by backend");
+		return -1;
+	}
+	return file->fs->v.get_metadata(file, metadata_r);
 }
 
 const char *fs_file_path(struct fs_file *file)
@@ -112,6 +132,11 @@
 	return fs_last_error(file->fs);
 }
 
+bool fs_prefetch(struct fs_file *file, uoff_t length)
+{
+	return file->fs->v.prefetch(file, length);
+}
+
 ssize_t fs_read(struct fs_file *file, void *buf, size_t size)
 {
 	return file->fs->v.read(file, buf, size);
@@ -150,6 +175,22 @@
 	(void)file->fs->v.write_stream_finish(file, FALSE);
 }
 
+void fs_file_set_async_callback(struct fs_file *file,
+				fs_file_async_callback_t *callback,
+				void *context)
+{
+	if (file->fs->v.set_async_callback != NULL)
+		file->fs->v.set_async_callback(file, callback, context);
+	else
+		callback(context);
+}
+
+void fs_wait_async(struct fs *fs)
+{
+	if (fs->v.wait_async != NULL)
+		fs->v.wait_async(fs);
+}
+
 int fs_lock(struct fs_file *file, unsigned int secs, struct fs_lock **lock_r)
 {
 	return file->fs->v.lock(file, secs, lock_r);
@@ -163,39 +204,49 @@
 	lock->file->fs->v.unlock(lock);
 }
 
-int fs_fdatasync(struct fs_file *file)
+int fs_exists(struct fs_file *file)
 {
-	return file->fs->v.fdatasync(file);
+	return file->fs->v.exists(file);
+}
+
+int fs_stat(struct fs_file *file, struct stat *st_r)
+{
+	return file->fs->v.stat(file, st_r);
 }
 
-int fs_exists(struct fs *fs, const char *path)
+int fs_copy(struct fs_file *src, struct fs_file *dest)
 {
-	return fs->v.exists(fs, path);
+	i_assert(src->fs == dest->fs);
+	return src->fs->v.copy(src, dest);
 }
 
-int fs_stat(struct fs *fs, const char *path, struct stat *st_r)
+int fs_rename(struct fs_file *src, struct fs_file *dest)
 {
-	return fs->v.stat(fs, path, st_r);
+	i_assert(src->fs == dest->fs);
+	return src->fs->v.rename(src, dest);
 }
 
-int fs_link(struct fs *fs, const char *src, const char *dest)
+int fs_delete(struct fs_file *file)
 {
-	return fs->v.link(fs, src, dest);
+	return file->fs->v.delete_file(file);
+}
+
+struct fs_iter *fs_iter_init(struct fs *fs, const char *path)
+{
+	return fs->v.iter_init(fs, path);
 }
 
-int fs_rename(struct fs *fs, const char *src, const char *dest)
+int fs_iter_deinit(struct fs_iter **_iter)
 {
-	return fs->v.rename(fs, src, dest);
+	struct fs_iter *iter = *_iter;
+
+	*_iter = NULL;
+	return iter->fs->v.iter_deinit(iter);
 }
 
-int fs_unlink(struct fs *fs, const char *path)
+const char *fs_iter_next(struct fs_iter *iter)
 {
-	return fs->v.unlink(fs, path);
-}
-
-int fs_rmdir(struct fs *fs, const char *path)
-{
-	return fs->v.rmdir(fs, path);
+	return iter->fs->v.iter_next(iter);
 }
 
 void fs_set_error(struct fs *fs, const char *fmt, ...)
@@ -218,3 +269,9 @@
 	i_error("fs-%s: %s", fs->name, str_c(fs->last_error));
 	va_end(args);
 }
+
+void fs_set_error_async(struct fs *fs)
+{
+	fs_set_error(fs, "Asynchronous operation in progress");
+	errno = EAGAIN;
+}
--- a/src/lib-fs/fs-api.h	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-api.h	Fri Oct 12 00:22:19 2012 +0300
@@ -6,11 +6,23 @@
 struct fs_file;
 struct fs_lock;
 
+enum fs_properties {
+	FS_PROPERTY_METADATA	= 0x01,
+	FS_PROPERTY_LOCKS	= 0x02,
+	FS_PROPERTY_FASTCOPY	= 0x04,
+	FS_PROPERTY_RENAME	= 0x08,
+	FS_PROPERTY_STAT	= 0x10
+};
+
 enum fs_open_mode {
 	/* Open only for reading, or fail with ENOENT if it doesn't exist */
-	FS_OPEN_MODE_RDONLY,
+	FS_OPEN_MODE_READONLY,
 	/* Create a new file, fail with EEXIST if it already exists */
 	FS_OPEN_MODE_CREATE,
+	/* Create a new file with a new unique name. The generated name is a
+	   128bit hex-encoded string. The fs_open()'s path parameter specifies
+	   only the directory where the file is created to. */
+	FS_OPEN_MODE_CREATE_UNIQUE_128,
 	/* Create or replace a file */
 	FS_OPEN_MODE_REPLACE,
 	/* Append to existing file, fail with ENOENT if it doesn't exist */
@@ -20,29 +32,58 @@
 };
 
 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
+	/* File being written isn't very important, performance is more
+	   important than actually guaranteeing that the file gets saved */
+	FS_OPEN_FLAG_UNIMPORTANT	= 0x10,
+	/* Asynchronous writes: fs_write() will fail with EAGAIN if it needs to
+	   be called again (the retries can use size=0). For streams
+	   fs_write_stream_finish() may request retrying with 0.
+
+	   Asynchronous reads: fs_read() will fail with EAGAIN if it's not
+	   finished and fs_read_stream() returns a nonblocking stream. */
+	FS_OPEN_FLAG_ASYNC		= 0x20
 };
 
 struct fs_settings {
+	/* Dovecot instance's base_dir */
+	const char *base_dir;
+	/* Automatically try to rmdir() directories up to this path when
+	   deleting files. */
+	const char *root_path;
 	/* When creating temporary files, use this prefix
 	   (to avoid conflicts with existing files). */
 	const char *temp_file_prefix;
 };
 
+struct fs_metadata {
+	const char *key;
+	const char *value;
+};
+ARRAY_DEFINE_TYPE(fs_metadata, struct fs_metadata);
+
+typedef void fs_file_async_callback_t(void *context);
+
 int fs_init(const char *driver, const char *args,
 	    const struct fs_settings *set,
 	    struct fs **fs_r, const char **error_r);
 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);
+struct fs_file *fs_file_init(struct fs *fs, const char *path, int mode_flags);
+void fs_file_deinit(struct fs_file **file);
+
+/* Return properties supported by backend. */
+enum fs_properties fs_get_properties(struct fs *fs);
 
-/* Returns the path given to fs_open(). */
+/* Add/replace metadata when saving a file. This makes sense only when the
+   file is being created/replaced. */
+void fs_set_metadata(struct fs_file *file, const char *key, const char *value);
+/* Return file's all metadata. */
+int fs_get_metadata(struct fs_file *file,
+		    const ARRAY_TYPE(fs_metadata) **metadata_r);
+
+/* Returns the path given to fs_open(). If file was opened with
+   FS_OPEN_MODE_CREATE_UNIQUE_128 and the write has already finished,
+   return the path including the generated filename. */
 const char *fs_file_path(struct fs_file *file);
 
 /* Return the error message for the last failed operation. */
@@ -50,6 +91,11 @@
 /* Convenience function for the above. Errors aren't preserved across files. */
 const char *fs_file_last_error(struct fs_file *file);
 
+/* Try to asynchronously prefetch file into memory. Returns TRUE if file is
+   already in memory (i.e. caller should handle this file before prefetching
+   more), FALSE if not. The length is a hint of how much the caller expects
+   to read, but it may be more or less (0=whole file). */
+bool fs_prefetch(struct fs_file *file, uoff_t length);
 /* 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,
@@ -68,36 +114,50 @@
 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. */
+   won't be visible earlier. Returns 1 if ok, 0 if async write isn't finished
+   yet (retry calling this), -1 if error */
 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);
 
+/* Call the specified callback whenever the file can be read/written to.
+   May call the callback immediately. */
+void fs_file_set_async_callback(struct fs_file *file,
+				fs_file_async_callback_t *callback,
+				void *context);
+/* Wait until some file can be read/written to more before returning.
+   It's an error to call this when there are no pending async operations. */
+void fs_wait_async(struct fs *fs);
+
+/* Returns 1 if file exists, 0 if not, -1 if error occurred. */
+int fs_exists(struct fs_file *file);
+/* Delete a file. Returns 1 if file was actually deleted by us,
+   0 if file didn't exist, -1 if error. */
+int fs_delete(struct fs_file *file);
+
+/* 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_file *file, struct stat *st_r);
+/* Copy an object with possibly updated metadata. Destination parent
+   directories are created automatically. Returns 0 if ok, -1 if error
+   occurred. */
+int fs_copy(struct fs_file *src, struct fs_file *dest);
+/* Atomically rename a file. Destination parent directories are created
+   automatically. Returns 0 if ok, -1 if error occurred. */
+int fs_rename(struct fs_file *src, struct fs_file *dest);
+
 /* 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);
+/* Iterate through all files (but not directories) in the given directory.
+   Doesn't recurse to child directories. */
+struct fs_iter *fs_iter_init(struct fs *fs, const char *path);
+/* Returns 0 if ok, -1 if iteration failed. */
+int fs_iter_deinit(struct fs_iter **iter);
+/* Returns the next filename. */
+const char *fs_iter_next(struct fs_iter *iter);
 
 #endif
--- a/src/lib-fs/fs-posix.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-posix.c	Fri Oct 12 00:22:19 2012 +0300
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "buffer.h"
 #include "str.h"
+#include "guid.h"
 #include "istream.h"
 #include "ostream.h"
 #include "safe-mkstemp.h"
@@ -15,6 +16,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <dirent.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 
@@ -29,7 +31,7 @@
 
 struct posix_fs {
 	struct fs fs;
-	char *temp_file_prefix;
+	char *temp_file_prefix, *root_path;
 	enum fs_posix_lock_method lock_method;
 	mode_t mode, dir_mode;
 };
@@ -53,18 +55,33 @@
 	struct dotlock *dotlock;
 };
 
-static int
-fs_posix_init(const char *args, const struct fs_settings *set,
-	      struct fs **fs_r, const char **error_r)
+struct posix_fs_iter {
+	struct fs_iter iter;
+	char *path;
+	DIR *dir;
+	int err;
+};
+
+static struct fs *fs_posix_alloc(void)
 {
 	struct posix_fs *fs;
-	const char *const *tmp;
 
 	fs = i_new(struct posix_fs, 1);
 	fs->fs = fs_class_posix;
+	return &fs->fs;
+}
+
+static int
+fs_posix_init(struct fs *_fs, const char *args, const struct fs_settings *set)
+{
+	struct posix_fs *fs = (struct posix_fs *)_fs;
+	const char *const *tmp;
+
 	fs->temp_file_prefix = set->temp_file_prefix != NULL ?
 		i_strdup(set->temp_file_prefix) : i_strdup("temp.dovecot.");
+	fs->root_path = i_strdup(set->root_path);
 	fs->fs.set.temp_file_prefix = fs->temp_file_prefix;
+	fs->fs.set.root_path = fs->root_path;
 
 	fs->lock_method = FS_POSIX_LOCK_METHOD_FLOCK;
 	fs->mode = FS_DEFAULT_MODE;
@@ -80,12 +97,11 @@
 		else if (strncmp(arg, "mode=", 5) == 0) {
 			fs->mode = strtoul(arg+5, NULL, 8) & 0666;
 			if (fs->mode == 0) {
-				*error_r = t_strdup_printf("Invalid mode: %s",
-							   arg+5);
+				fs_set_error(_fs, "Invalid mode: %s", arg+5);
 				return -1;
 			}
 		} else {
-			*error_r = t_strdup_printf("Unknown arg '%s'", arg);
+			fs_set_error(_fs, "Unknown arg '%s'", arg);
 			return -1;
 		}
 	}
@@ -93,8 +109,6 @@
 	if ((fs->dir_mode & 0600) != 0) fs->dir_mode |= 0100;
 	if ((fs->dir_mode & 0060) != 0) fs->dir_mode |= 0010;
 	if ((fs->dir_mode & 0006) != 0) fs->dir_mode |= 0001;
-
-	*fs_r = &fs->fs;
 	return 0;
 }
 
@@ -103,10 +117,17 @@
 	struct posix_fs *fs = (struct posix_fs *)_fs;
 
 	i_free(fs->temp_file_prefix);
+	i_free(fs->root_path);
 	i_free(fs);
 }
 
-static int fs_posix_create_parent_dir(struct posix_fs *fs, const char *path)
+static enum fs_properties fs_posix_get_properties(struct fs *fs ATTR_UNUSED)
+{
+	return FS_PROPERTY_LOCKS | FS_PROPERTY_FASTCOPY | FS_PROPERTY_RENAME |
+		FS_PROPERTY_STAT;
+}
+
+static int fs_posix_mkdir_parents(struct posix_fs *fs, const char *path)
 {
 	const char *dir, *fname;
 
@@ -124,92 +145,127 @@
 	}
 }
 
-static int
-fs_posix_create(struct posix_fs *fs, const char *path, enum fs_open_flags flags,
-		char **temp_path_r)
+static int fs_posix_rmdir_parents(struct posix_fs *fs, const char *path)
 {
-	struct fs *_fs = &fs->fs;
+	const char *p;
+
+	if (fs->root_path == NULL)
+		return 0;
+
+	while ((p = strrchr(path, '/')) != NULL) {
+		path = t_strdup_until(path, p);
+		if (strcmp(path, fs->root_path) == 0)
+			break;
+		if (rmdir(path) == 0) {
+			/* success, continue to parent */
+		} else if (errno == ENOTEMPTY || errno == EEXIST) {
+			/* there are other entries in this directory */
+			break;
+		} else if (errno == EBUSY || errno == ENOENT) {
+			/* some other not-unexpected error */
+			break;
+		} else {
+			fs_set_error(&fs->fs, "rmdir(%s) failed: %m", path);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int fs_posix_create(struct posix_fs_file *file)
+{
+	struct posix_fs *fs = (struct posix_fs *)file->file.fs;
 	string_t *str = t_str_new(256);
-	const char *slash = strrchr(path, '/');
+	const char *slash;
 	unsigned int try_count = 0;
 	int fd;
 
-	if (slash != NULL)
-		str_append_n(str, path, slash-path + 1);
+	i_assert(file->temp_path == NULL);
+
+	if (file->open_mode == FS_OPEN_MODE_CREATE_UNIQUE_128) {
+		str_append(str, file->file.path);
+		str_append_c(str, '/');
+	} else if ((slash = strrchr(file->file.path, '/')) != NULL) {
+		str_append_n(str, file->file.path, slash - file->file.path + 1);
+	}
 	str_append(str, fs->temp_file_prefix);
 
 	fd = safe_mkstemp_hostpid(str, fs->mode, (uid_t)-1, (gid_t)-1);
 	while (fd == -1 && errno == ENOENT &&
-	       try_count <= MAX_MKDIR_RETRY_COUNT &&
-	       (flags & FS_OPEN_FLAG_MKDIR) != 0) {
-		if (fs_posix_create_parent_dir(fs, path) < 0)
+	       try_count <= MAX_MKDIR_RETRY_COUNT) {
+		if (fs_posix_mkdir_parents(fs, str_c(str)) < 0)
 			return -1;
 		fd = safe_mkstemp_hostpid(str, fs->mode, (uid_t)-1, (gid_t)-1);
 		try_count++;
 	}
 	if (fd == -1) {
-		fs_set_error(_fs, "safe_mkstemp(%s) failed: %m", str_c(str));
+		fs_set_error(&fs->fs, "safe_mkstemp(%s) failed: %m", str_c(str));
 		return -1;
 	}
-	*temp_path_r = i_strdup(str_c(str));
+	file->temp_path = 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)
+static int fs_posix_open(struct posix_fs_file *file)
 {
-	struct posix_fs *fs = (struct posix_fs *)_fs;
-	struct posix_fs_file *file;
-	char *temp_path = NULL;
-	int fd = -1;
+	struct posix_fs *fs = (struct posix_fs *)file->file.fs;
+	const char *path = file->file.path;
+
+	i_assert(file->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);
+	switch (file->open_mode) {
+	case FS_OPEN_MODE_READONLY:
+		file->fd = open(path, O_RDONLY);
+		if (file->fd == -1)
+			fs_set_error(&fs->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);
+		file->fd = open(path, O_RDWR | O_APPEND);
+		if (file->fd == -1)
+			fs_set_error(&fs->fs, "open(%s) failed: %m", path);
 		break;
+	case FS_OPEN_MODE_CREATE_UNIQUE_128:
 	case FS_OPEN_MODE_CREATE:
 	case FS_OPEN_MODE_REPLACE:
 		T_BEGIN {
-			fd = fs_posix_create(fs, path, flags, &temp_path);
+			file->fd = fs_posix_create(file);
 		} T_END;
 		break;
 	}
-	if (fd == -1)
+	if (file->fd == -1)
 		return -1;
+	return 0;
+}
+
+static struct fs_file *
+fs_posix_file_init(struct fs *_fs, const char *path,
+		   enum fs_open_mode mode, enum fs_open_flags flags)
+{
+	struct posix_fs_file *file;
 
 	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;
+	file->fd = -1;
+	return &file->file;
 }
 
-static void fs_posix_close(struct fs_file *_file)
+static void fs_posix_file_deinit(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_READONLY:
 	case FS_OPEN_MODE_APPEND:
 		break;
+	case FS_OPEN_MODE_CREATE_UNIQUE_128:
 	case FS_OPEN_MODE_CREATE:
 	case FS_OPEN_MODE_REPLACE:
-		if (file->success)
+		if (file->success || file->temp_path == NULL)
 			break;
 		/* failed to create/replace this. delete the temp file */
 		if (unlink(file->temp_path) < 0) {
@@ -230,11 +286,32 @@
 	i_free(file);
 }
 
+static bool fs_posix_prefetch(struct fs_file *_file, uoff_t length)
+{
+	struct posix_fs_file *file = (struct posix_fs_file *)_file;
+
+	if (file->fd == -1) {
+		if (fs_posix_open(file) < 0)
+			return TRUE;
+	}
+
+	if (posix_fadvise(file->fd, 0, length, POSIX_FADV_WILLNEED) < 0) {
+		i_error("posix_fadvise(%s) failed: %m", _file->path);
+		return TRUE;
+	}
+	return FALSE;
+}
+
 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->fd == -1) {
+		if (fs_posix_open(file) < 0)
+			return -1;
+	}
+
 	if (file->seek_to_beginning) {
 		file->seek_to_beginning = FALSE;
 		if (lseek(file->fd, 0, SEEK_SET) < 0) {
@@ -254,7 +331,15 @@
 fs_posix_read_stream(struct fs_file *_file, size_t max_buffer_size)
 {
 	struct posix_fs_file *file = (struct posix_fs_file *)_file;
+	struct istream *input;
 
+	if (file->fd == -1) {
+		if (fs_posix_open(file) < 0) {
+			input = i_stream_create_error(errno);
+			i_stream_set_name(input, _file->path);
+			return input;
+		}
+	}
 	return i_stream_create_fd(file->fd, max_buffer_size, FALSE);
 }
 
@@ -262,9 +347,12 @@
 {
 	int ret;
 
-	if ((file->open_flags & FS_OPEN_FLAG_FDATASYNC) != 0) {
-		if (fs_fdatasync(&file->file) < 0)
+	if ((file->open_flags & FS_OPEN_FLAG_UNIMPORTANT) == 0) {
+		if (fdatasync(file->fd) < 0) {
+			fs_set_error(file->file.fs, "fdatasync(%s) failed: %m",
+				     file->file.path);
 			return -1;
+		}
 	}
 
 	if (close(file->fd) < 0) {
@@ -276,6 +364,18 @@
 	file->fd = -1;
 
 	switch (file->open_mode) {
+	case FS_OPEN_MODE_CREATE_UNIQUE_128:
+		T_BEGIN {
+			guid_128_t guid;
+			char *path;
+
+			guid_128_generate(guid);
+			path = i_strdup_printf("%s/%s", file->file.path,
+					       guid_128_to_string(guid));
+			i_free(file->file.path);
+			file->file.path = path;
+		} T_END;
+		/* fall through */
 	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",
@@ -298,6 +398,7 @@
 	default:
 		i_unreached();
 	}
+	i_free_and_null(file->temp_path);
 	file->success = TRUE;
 	file->seek_to_beginning = TRUE;
 	return 0;
@@ -308,6 +409,11 @@
 	struct posix_fs_file *file = (struct posix_fs_file *)_file;
 	ssize_t ret;
 
+	if (file->fd == -1) {
+		if (fs_posix_open(file) < 0)
+			return -1;
+	}
+
 	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",
@@ -342,10 +448,13 @@
 	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;
+	} else if (file->fd == -1 && fs_posix_open(file) < 0) {
+		_file->output = o_stream_create_error(errno);
+	} else {
+		_file->output = o_stream_create_fd_file(file->fd,
+							(uoff_t)-1, FALSE);
 	}
-
-	_file->output = o_stream_create_fd_file(file->fd, (uoff_t)-1, FALSE);
+	o_stream_set_name(_file->output, _file->path);
 }
 
 static int fs_posix_write_stream_finish(struct fs_file *_file, bool success)
@@ -369,14 +478,15 @@
 		buffer_free(&file->write_buf);
 		break;
 	case FS_OPEN_MODE_CREATE:
+	case FS_OPEN_MODE_CREATE_UNIQUE_128:
 	case FS_OPEN_MODE_REPLACE:
 		if (ret == 0)
 			ret = fs_posix_write_finish(file);
 		break;
-	case FS_OPEN_MODE_RDONLY:
+	case FS_OPEN_MODE_READONLY:
 		i_unreached();
 	}
-	return ret;
+	return ret < 0 ? -1 : 1;
 }
 
 static int
@@ -451,25 +561,14 @@
 	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)
+static int fs_posix_exists(struct fs_file *_file)
 {
 	struct stat st;
 
-	if (stat(path, &st) < 0) {
+	if (stat(_file->path, &st) < 0) {
 		if (errno != ENOENT) {
-			fs_set_error(fs, "stat(%s) failed: %m", path);
+			fs_set_error(_file->fs, "stat(%s) failed: %m",
+				     _file->path);
 			return -1;
 		}
 		return 0;
@@ -477,82 +576,157 @@
 	return 1;
 }
 
-static int fs_posix_stat(struct fs *fs, const char *path, struct stat *st_r)
+static int fs_posix_stat(struct fs_file *_file, struct stat *st_r)
 {
-	if (stat(path, st_r) < 0) {
-		fs_set_error(fs, "stat(%s) failed: %m", path);
+	if (stat(_file->path, st_r) < 0) {
+		fs_set_error(_file->fs, "stat(%s) failed: %m", _file->path);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_posix_link(struct fs *_fs, const char *src, const char *dest)
+static int fs_posix_copy(struct fs_file *_src, struct fs_file *_dest)
 {
-	struct posix_fs *fs = (struct posix_fs *)_fs;
+	struct posix_fs *fs = (struct posix_fs *)_src->fs;
 	unsigned int try_count = 0;
 	int ret;
 
-	ret = link(src, dest);
+	ret = link(_src->path, _dest->path);
 	while (ret < 0 && errno == ENOENT &&
 	       try_count <= MAX_MKDIR_RETRY_COUNT) {
-		if (fs_posix_create_parent_dir(fs, dest) < 0)
+		if (fs_posix_mkdir_parents(fs, _dest->path) < 0)
 			return -1;
-		ret = link(src, dest);
+		ret = link(_src->path, _dest->path);
 		try_count++;
 	}
 	if (ret < 0) {
-		fs_set_error(_fs, "link(%s, %s) failed: %m", src, dest);
+		fs_set_error(_src->fs, "link(%s, %s) failed: %m",
+			     _src->path, _dest->path);
+		return -1;
+	}
+	return 0;
+}
+
+static int fs_posix_rename(struct fs_file *_src, struct fs_file *_dest)
+{
+	struct posix_fs *fs = (struct posix_fs *)_src->fs;
+	unsigned int try_count = 0;
+	int ret;
+
+	ret = rename(_src->path, _dest->path);
+	while (ret < 0 && errno == ENOENT &&
+	       try_count <= MAX_MKDIR_RETRY_COUNT) {
+		if (fs_posix_mkdir_parents(fs, _dest->path) < 0)
+			return -1;
+		ret = rename(_src->path, _dest->path);
+		try_count++;
+	}
+	if (ret < 0) {
+		fs_set_error(_src->fs, "rename(%s, %s) failed: %m",
+			     _src->path, _dest->path);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_posix_rename(struct fs *_fs, const char *src, const char *dest)
+static int fs_posix_delete(struct fs_file *_file)
 {
-	struct posix_fs *fs = (struct posix_fs *)_fs;
-	unsigned int try_count = 0;
-	int ret;
+	struct posix_fs *fs = (struct posix_fs *)_file->fs;
 
-	ret = rename(src, dest);
-	while (ret < 0 && errno == ENOENT &&
-	       try_count <= MAX_MKDIR_RETRY_COUNT) {
-		if (fs_posix_create_parent_dir(fs, dest) < 0)
-			return -1;
-		ret = rename(src, dest);
-		try_count++;
-	}
-	if (ret < 0) {
-		fs_set_error(_fs, "link(%s, %s) failed: %m", src, dest);
+	if (unlink(_file->path) < 0) {
+		fs_set_error(_file->fs, "unlink(%s) failed: %m", _file->path);
 		return -1;
 	}
+	(void)fs_posix_rmdir_parents(fs, _file->path);
 	return 0;
 }
 
-static int fs_posix_unlink(struct fs *fs, const char *path)
+static struct fs_iter *fs_posix_iter_init(struct fs *fs, const char *path)
 {
-	if (unlink(path) < 0) {
-		fs_set_error(fs, "unlink(%s) failed: %m", path);
-		return -1;
+	struct posix_fs_iter *iter;
+
+	iter = i_new(struct posix_fs_iter, 1);
+	iter->iter.fs = fs;
+	iter->path = i_strdup(path);
+	iter->dir = opendir(path);
+	if (iter->dir == NULL) {
+		iter->err = errno;
+		fs_set_error(fs, "opendir(%s) failed: %m", path);
 	}
-	return 0;
+	return &iter->iter;
+}
+
+static bool fs_posix_iter_want(const char *dir, const char *fname)
+{
+	bool ret;
+
+	T_BEGIN {
+		const char *path = t_strdup_printf("%s/%s", dir, fname);
+		struct stat st;
+
+		if (stat(path, &st) < 0)
+			ret = FALSE;
+		else
+			ret = !S_ISDIR(st.st_mode);
+	} T_END;
+	return ret;
 }
 
-static int fs_posix_rmdir(struct fs *fs, const char *path)
+static const char *fs_posix_iter_next(struct fs_iter *_iter)
 {
-	if (rmdir(path) < 0) {
-		fs_set_error(fs, "rmdir(%s) failed: %m", path);
-		return -1;
+	struct posix_fs_iter *iter = (struct posix_fs_iter *)_iter;
+	struct dirent *d;
+
+	if (iter->dir == NULL)
+		return NULL;
+
+	errno = 0;
+	while ((d = readdir(iter->dir)) != NULL) {
+#ifdef HAVE_DIRENT_D_TYPE
+		if (d->d_type != DT_DIR) {
+			if (d->d_type == DT_UNKNOWN &&
+			    fs_posix_iter_want(iter->path, d->d_name))
+			return d->d_name;
+		}
+#else
+		if (fs_posix_iter_want(iter->path, d->d_name))
+			return d->d_name;
+#endif
+		errno = 0;
 	}
-	return 0;
+	if (errno != 0) {
+		iter->err = errno;
+		fs_set_error(_iter->fs, "readdir(%s) failed: %m", iter->path);
+	}
+	return NULL;
+}
+
+static int fs_posix_iter_deinit(struct fs_iter *_iter)
+{
+	struct posix_fs_iter *iter = (struct posix_fs_iter *)_iter;
+	int ret = 0;
+
+	if (iter->err != 0) {
+		errno = iter->err;
+		ret = -1;
+	}
+	i_free(iter->path);
+	i_free(iter);
+	return ret;
 }
 
 struct fs fs_class_posix = {
 	.name = "posix",
 	.v = {
+		fs_posix_alloc,
 		fs_posix_init,
 		fs_posix_deinit,
-		fs_posix_open,
-		fs_posix_close,
+		fs_posix_get_properties,
+		fs_posix_file_init,
+		fs_posix_file_deinit,
+		NULL, NULL,
+		NULL, NULL,
+		fs_posix_prefetch,
 		fs_posix_read,
 		fs_posix_read_stream,
 		fs_posix_write,
@@ -560,12 +734,13 @@
 		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_copy,
 		fs_posix_rename,
-		fs_posix_unlink,
-		fs_posix_rmdir
+		fs_posix_delete,
+		fs_posix_iter_init,
+		fs_posix_iter_next,
+		fs_posix_iter_deinit
 	}
 };
--- a/src/lib-fs/fs-sis-common.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-sis-common.c	Fri Oct 12 00:22:19 2012 +0300
@@ -30,29 +30,26 @@
 	return 0;
 }
 
-void fs_sis_try_unlink_hash_file(struct fs *fs, struct fs *super,
-				 const char *path)
+void fs_sis_try_unlink_hash_file(struct fs *sis_fs, struct fs_file *super_file)
 {
+	struct fs_file *hash_file;
 	struct stat st1, st2;
-	const char *dir, *hash, *hash_path, *hash_dir;
+	const char *dir, *hash, *hash_path;
 
-	if (fs_sis_path_parse(fs, path, &dir, &hash) == 0 &&
-	    fs_stat(super, path, &st1) == 0 && st1.st_nlink == 2) {
+	if (fs_sis_path_parse(sis_fs, super_file->path, &dir, &hash) == 0 &&
+	    fs_stat(super_file, &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 &&
+		hash_file = fs_file_init(super_file->fs, hash_path,
+					 FS_OPEN_MODE_READONLY);
+		if (fs_stat(hash_file, &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);
-			}
+			if (fs_delete(hash_file) < 0)
+				i_error("%s", fs_last_error(hash_file->fs));
 		}
+		fs_file_deinit(&hash_file);
 	}
 }
 
--- a/src/lib-fs/fs-sis-common.h	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-sis-common.h	Fri Oct 12 00:22:19 2012 +0300
@@ -7,8 +7,7 @@
 
 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);
+void fs_sis_try_unlink_hash_file(struct fs *sis_fs, struct fs_file *super_file);
 
 #endif
 
--- a/src/lib-fs/fs-sis-queue.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-sis-queue.c	Fri Oct 12 00:22:19 2012 +0300
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "str.h"
 #include "istream.h"
+#include "ostream.h"
 #include "fs-sis-common.h"
 
 #define QUEUE_DIR_NAME "queue"
@@ -15,6 +16,7 @@
 
 struct sis_queue_fs_file {
 	struct fs_file file;
+	struct sis_queue_fs *fs;
 	struct fs_file *super;
 };
 
@@ -30,21 +32,27 @@
 	fs_sis_queue_copy_error(fs);
 }
 
-static int
-fs_sis_queue_init(const char *args, const struct fs_settings *set,
-		  struct fs **fs_r, const char **error_r)
+static struct fs *fs_sis_queue_alloc(void)
 {
 	struct sis_queue_fs *fs;
-	const char *p, *parent_name, *parent_args, *error;
 
 	fs = i_new(struct sis_queue_fs, 1);
 	fs->fs = fs_class_sis_queue;
+	return &fs->fs;
+}
+
+static int
+fs_sis_queue_init(struct fs *_fs, const char *args,
+		  const struct fs_settings *set)
+{
+	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
+	const char *p, *parent_name, *parent_args, *error;
 
 	/* <queue_dir>:<parent fs>[:<args>] */
 
 	p = strchr(args, ':');
 	if (p == NULL || p[1] == '\0') {
-		*error_r = "Parent filesystem not given as parameter";
+		fs_set_error(_fs, "Parent filesystem not given as parameter");
 		return -1;
 	}
 
@@ -57,10 +65,9 @@
 	else
 		parent_name = t_strdup_until(parent_name, parent_args++);
 	if (fs_init(parent_name, parent_args, set, &fs->super, &error) < 0) {
-		*error_r = t_strdup_printf("%s: %s", parent_name, error);
+		fs_set_error(_fs, "%s: %s", parent_name, error);
 		return -1;
 	}
-	*fs_r = &fs->fs;
 	return 0;
 }
 
@@ -68,55 +75,88 @@
 {
 	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
 
-	fs_deinit(&fs->super);
+	if (fs->super != NULL)
+		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)
+static enum fs_properties fs_sis_queue_get_properties(struct fs *_fs)
+{
+	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
+
+	return fs_get_properties(fs->super);
+}
+
+static struct fs_file *
+fs_sis_queue_file_init(struct fs *_fs, const char *path,
+		       enum fs_open_mode mode, enum fs_open_flags flags)
 {
 	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;
+	file->file.path = i_strdup(path);
+	file->fs = fs;
+
+	if (mode == FS_OPEN_MODE_APPEND)
+		fs_set_error(_fs, "APPEND mode not supported");
+	else
+		file->super = fs_file_init(fs->super, path, mode | flags);
+	return &file->file;
 }
 
-static void fs_sis_queue_close(struct fs_file *_file)
+static void fs_sis_queue_file_deinit(struct fs_file *_file)
+{
+	struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file;
+
+	if (file->super != NULL)
+		fs_file_deinit(&file->super);
+	i_free(file->file.path);
+	i_free(file);
+}
+
+static void
+fs_sis_queue_set_async_callback(struct fs_file *_file,
+				fs_file_async_callback_t *callback,
+				void *context)
 {
 	struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file;
 
-	fs_close(&file->super);
-	i_free(file->file.path);
-	i_free(file);
+	fs_file_set_async_callback(file->super, callback, context);
+}
+
+static void fs_sis_queue_wait_async(struct fs *_fs)
+{
+	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
+
+	fs_wait_async(fs->super);
+}
+
+static void
+fs_sis_queue_set_metadata(struct fs_file *_file, const char *key,
+			  const char *value)
+{
+	struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file;
+
+	fs_set_metadata(file->super, key, value);
+}
+
+static int
+fs_sis_queue_get_metadata(struct fs_file *_file,
+			  const ARRAY_TYPE(fs_metadata) **metadata_r)
+{
+	struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file;
+
+	return fs_get_metadata(file->super, metadata_r);
+}
+
+static bool fs_sis_queue_prefetch(struct fs_file *_file, uoff_t length)
+{
+	struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file;
+
+	return fs_prefetch(file->super, length);
 }
 
 static ssize_t fs_sis_queue_read(struct fs_file *_file, void *buf, size_t size)
@@ -151,21 +191,18 @@
 		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;
-	}
+	queue_file = fs_file_init(fs->super, queue_path, FS_OPEN_MODE_CREATE);
 	if (fs_write(queue_file, "", 0) < 0 && errno != EEXIST)
 		i_error("fs-sis-queue: %s", fs_last_error(fs->super));
-	fs_close(&queue_file);
+	fs_file_deinit(&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 (file->super == NULL)
+		return -1;
 	if (fs_write(file->super, data, size) < 0) {
 		fs_sis_queue_file_copy_error(file);
 		return -1;
@@ -182,7 +219,11 @@
 
 	i_assert(_file->output == NULL);
 
-	_file->output = fs_write_stream(file->super);
+	if (file->super == NULL)
+		_file->output = o_stream_create_error(EINVAL);
+	else
+		_file->output = fs_write_stream(file->super);
+	o_stream_set_name(_file->output, _file->path);
 }
 
 static int fs_sis_queue_write_stream_finish(struct fs_file *_file, bool success)
@@ -190,8 +231,10 @@
 	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);
+		if (file->super != NULL) {
+			fs_write_stream_abort(file->super, &_file->output);
+			fs_sis_queue_file_copy_error(file);
+		}
 		return -1;
 	}
 
@@ -202,7 +245,7 @@
 	T_BEGIN {
 		fs_sis_queue_add(file);
 	} T_END;
-	return 0;
+	return 1;
 }
 
 static int
@@ -223,95 +266,89 @@
 	i_unreached();
 }
 
-static int fs_sis_queue_fdatasync(struct fs_file *_file)
+static int fs_sis_queue_exists(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);
+	if (fs_exists(file->super) < 0) {
+		fs_sis_queue_copy_error(file->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_queue_exists(struct fs *_fs, const char *path)
+static int fs_sis_queue_stat(struct fs_file *_file, struct stat *st_r)
 {
-	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
+	struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file;
 
-	if (fs_exists(fs->super, path) < 0) {
-		fs_sis_queue_copy_error(fs);
+	if (fs_stat(file->super, st_r) < 0) {
+		fs_sis_queue_copy_error(file->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_queue_stat(struct fs *_fs, const char *path,
-			     struct stat *st_r)
+static int fs_sis_queue_copy(struct fs_file *_src, struct fs_file *_dest)
 {
-	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
+	struct sis_queue_fs_file *src = (struct sis_queue_fs_file *)_src;
+	struct sis_queue_fs_file *dest = (struct sis_queue_fs_file *)_dest;
 
-	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);
+	if (fs_copy(src->super, dest->super) < 0) {
+		fs_sis_queue_copy_error(src->fs);
 		return -1;
 	}
 	return 0;
 }
 
 static int
-fs_sis_queue_rename(struct fs *_fs, const char *src, const char *dest)
+fs_sis_queue_rename(struct fs_file *_src, struct fs_file *_dest)
 {
-	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
+	struct sis_queue_fs_file *src = (struct sis_queue_fs_file *)_src;
+	struct sis_queue_fs_file *dest = (struct sis_queue_fs_file *)_dest;
 
-	if (fs_rename(fs->super, src, dest) < 0) {
-		fs_sis_queue_copy_error(fs);
+	if (fs_rename(src->super, dest->super) < 0) {
+		fs_sis_queue_copy_error(src->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_queue_unlink(struct fs *_fs, const char *path)
+static int fs_sis_queue_delete(struct fs_file *_file)
 {
-	struct sis_queue_fs *fs = (struct sis_queue_fs *)_fs;
+	struct sis_queue_fs_file *file = (struct sis_queue_fs_file *)_file;
 
 	T_BEGIN {
-		fs_sis_try_unlink_hash_file(&fs->fs, fs->super, path);
+		fs_sis_try_unlink_hash_file(_file->fs, file->super);
 	} T_END;
-	if (fs_unlink(fs->super, path) < 0) {
-		fs_sis_queue_copy_error(fs);
+	if (fs_delete(file->super) < 0) {
+		fs_sis_queue_copy_error(file->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_queue_rmdir(struct fs *_fs, const char *path)
+static struct fs_iter *
+fs_sis_queue_iter_init(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;
+	return fs_iter_init(fs->super, path);
 }
 
 struct fs fs_class_sis_queue = {
 	.name = "sis-queue",
 	.v = {
+		fs_sis_queue_alloc,
 		fs_sis_queue_init,
 		fs_sis_queue_deinit,
-		fs_sis_queue_open,
-		fs_sis_queue_close,
+		fs_sis_queue_get_properties,
+		fs_sis_queue_file_init,
+		fs_sis_queue_file_deinit,
+		fs_sis_queue_set_async_callback,
+		fs_sis_queue_wait_async,
+		fs_sis_queue_set_metadata,
+		fs_sis_queue_get_metadata,
+		fs_sis_queue_prefetch,
 		fs_sis_queue_read,
 		fs_sis_queue_read_stream,
 		fs_sis_queue_write,
@@ -319,12 +356,13 @@
 		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_copy,
 		fs_sis_queue_rename,
-		fs_sis_queue_unlink,
-		fs_sis_queue_rmdir
+		fs_sis_queue_delete,
+		fs_sis_queue_iter_init,
+		NULL,
+		NULL
 	}
 };
--- a/src/lib-fs/fs-sis.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-fs/fs-sis.c	Fri Oct 12 00:22:19 2012 +0300
@@ -3,9 +3,13 @@
 #include "lib.h"
 #include "str.h"
 #include "istream.h"
+#include "ostream.h"
 #include "ostream-cmp.h"
 #include "fs-sis-common.h"
 
+#define FS_SIS_REQUIRED_PROPS \
+	(FS_PROPERTY_FASTCOPY | FS_PROPERTY_STAT)
+
 struct sis_fs {
 	struct fs fs;
 	struct fs *super;
@@ -13,6 +17,7 @@
 
 struct sis_fs_file {
 	struct fs_file file;
+	struct sis_fs *fs;
 	struct fs_file *super;
 	enum fs_open_mode open_mode;
 
@@ -21,6 +26,7 @@
 	struct ostream *fs_output;
 
 	char *hash, *hash_path;
+	bool opened;
 };
 
 static void fs_sis_copy_error(struct sis_fs *fs)
@@ -35,18 +41,24 @@
 	fs_sis_copy_error(fs);
 }
 
-static int
-fs_sis_init(const char *args, const struct fs_settings *set,
-	    struct fs **fs_r, const char **error_r)
+static struct fs *fs_sis_alloc(void)
 {
 	struct sis_fs *fs;
-	const char *parent_name, *parent_args, *error;
 
 	fs = i_new(struct sis_fs, 1);
 	fs->fs = fs_class_sis;
+	return &fs->fs;
+}
+
+static int
+fs_sis_init(struct fs *_fs, const char *args, const struct fs_settings *set)
+{
+	struct sis_fs *fs = (struct sis_fs *)_fs;
+	enum fs_properties props;
+	const char *parent_name, *parent_args, *error;
 
 	if (*args == '\0') {
-		*error_r = "Parent filesystem not given as parameter";
+		fs_set_error(_fs, "Parent filesystem not given as parameter");
 		return -1;
 	}
 
@@ -59,10 +71,15 @@
 		parent_args++;
 	}
 	if (fs_init(parent_name, parent_args, set, &fs->super, &error) < 0) {
-		*error_r = t_strdup_printf("%s: %s", parent_name, error);
+		fs_set_error(_fs, "%s: %s", parent_name, error);
 		return -1;
 	}
-	*fs_r = &fs->fs;
+	props = fs_get_properties(fs->super);
+	if ((props & FS_SIS_REQUIRED_PROPS) != FS_SIS_REQUIRED_PROPS) {
+		fs_set_error(_fs, "%s backend can't be used with SIS",
+			     parent_name);
+		return -1;
+	}
 	return 0;
 }
 
@@ -70,81 +87,115 @@
 {
 	struct sis_fs *fs = (struct sis_fs *)_fs;
 
-	fs_deinit(&fs->super);
+	if (fs->super != NULL)
+		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)
+static enum fs_properties fs_sis_get_properties(struct fs *_fs)
+{
+	struct sis_fs *fs = (struct sis_fs *)_fs;
+
+	return fs_get_properties(fs->super);
+}
+
+static struct fs_file *
+fs_sis_file_init(struct fs *_fs, const char *path,
+		 enum fs_open_mode mode, enum fs_open_flags flags)
 {
 	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->file.path = i_strdup(path);
+	file->fs = fs;
 	file->open_mode = mode;
-	file->hash = i_strdup(hash);
+	if (mode == FS_OPEN_MODE_APPEND) {
+		fs_set_error(_fs, "APPEND mode not supported");
+		return &file->file;
+	}
+
+	if (fs_sis_path_parse(_fs, path, &dir, &hash) < 0) {
+		fs_set_error(_fs, "Invalid path");
+		return &file->file;
+	}
 
 	/* 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->hash_file = fs_file_init(fs->super, file->hash_path,
+				       FS_OPEN_MODE_READONLY);
+
+	file->hash_input = fs_read_stream(file->hash_file, IO_BLOCK_SIZE);
+	if (i_stream_read(file->hash_input) == -1) {
+		/* doesn't exist */
+		if (errno != ENOENT) {
+			i_error("fs-sis: Couldn't read hash file %s: %m",
+				file->hash_path);
+		}
+		i_stream_destroy(&file->hash_input);
 	}
 
-	*file_r = &file->file;
-	return 0;
+	file->super = fs_file_init(fs->super, path, mode | flags);
+	return &file->file;
 }
 
-static void fs_sis_close(struct fs_file *_file)
+static void fs_sis_file_deinit(struct fs_file *_file)
 {
 	struct sis_fs_file *file = (struct sis_fs_file *)_file;
 
-	if (file->hash_file != NULL) {
+	if (file->hash_input != NULL)
 		i_stream_unref(&file->hash_input);
-		fs_close(&file->hash_file);
-	}
-	fs_close(&file->super);
+	fs_file_deinit(&file->hash_file);
+	fs_file_deinit(&file->super);
 	i_free(file->hash);
 	i_free(file->hash_path);
 	i_free(file->file.path);
 	i_free(file);
 }
 
+static void
+fs_sis_set_async_callback(struct fs_file *_file,
+			  fs_file_async_callback_t *callback, void *context)
+{
+	struct sis_fs_file *file = (struct sis_fs_file *)_file;
+
+	fs_file_set_async_callback(file->super, callback, context);
+}
+
+static void fs_sis_wait_async(struct fs *_fs)
+{
+	struct sis_fs *fs = (struct sis_fs *)_fs;
+
+	fs_wait_async(fs->super);
+}
+
+static void
+fs_sis_set_metadata(struct fs_file *_file, const char *key,
+		    const char *value)
+{
+	struct sis_fs_file *file = (struct sis_fs_file *)_file;
+
+	fs_set_metadata(file->super, key, value);
+}
+
+static int
+fs_sis_get_metadata(struct fs_file *_file,
+		    const ARRAY_TYPE(fs_metadata) **metadata_r)
+{
+	struct sis_fs_file *file = (struct sis_fs_file *)_file;
+
+	return fs_get_metadata(file->super, metadata_r);
+}
+
+static bool fs_sis_prefetch(struct fs_file *_file, uoff_t length)
+{
+	struct sis_fs_file *file = (struct sis_fs_file *)_file;
+
+	return fs_prefetch(file->super, length);
+}
+
 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;
@@ -165,7 +216,6 @@
 
 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;
 
@@ -173,21 +223,21 @@
 		return FALSE;
 
 	/* we can use the existing file */
-	if (fs_link(file->super->fs, file->hash_path, path) < 0) {
+	if (fs_copy(file->hash_file, file->super) < 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) {
+	if (fs_stat(file->super, &st2) < 0) {
 		i_error("fs-sis: %s", fs_last_error(file->super->fs));
-		if (fs_unlink(file->super->fs, path) < 0)
+		if (fs_delete(file->super) < 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)
+		if (fs_delete(file->super) < 0)
 			i_error("fs-sis: %s", fs_last_error(file->super->fs));
 		return FALSE;
 	}
@@ -196,15 +246,16 @@
 
 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;
+	struct fs_file *temp_file;
+	const char *hash_fname;
 	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 (fs_copy(file->super, file->hash_file) < 0) {
 			if (errno == EEXIST) {
 				/* the file was just created. it's probably
 				   a duplicate, but it's too much trouble
@@ -229,33 +280,41 @@
 		    super_fs->set.temp_file_prefix, hash_fname);
 
 	/* replace existing hash file atomically */
-	ret = fs_link(super_fs, path, str_c(temp_path));
+	temp_file = fs_file_init(super_fs, str_c(temp_path),
+				 FS_OPEN_MODE_READONLY);
+	ret = fs_copy(file->super, temp_file);
 	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 &&
+		if (fs_delete(temp_file) < 0 &&
 		    errno != ENOENT)
 			i_error("fs-sis: %s", fs_last_error(super_fs));
-		ret = fs_link(super_fs, path, str_c(temp_path));
+		ret = fs_copy(file->super, temp_file);
 	}
 	if (ret < 0) {
 		i_error("fs-sis: %s", fs_last_error(super_fs));
+		fs_file_deinit(&temp_file);
 		return;
 	}
-	if (fs_rename(super_fs, str_c(temp_path), file->hash_path) < 0) {
+
+	if (fs_rename(temp_file, file->hash_file) < 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));
+		(void)fs_delete(temp_file);
 	}
+	fs_file_deinit(&temp_file);
 }
 
 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->super == NULL)
+		return -1;
+
 	if (file->hash_input != NULL &&
 	    stream_cmp_block(file->hash_input, data, size) &&
 	    i_stream_is_eof(file->hash_input)) {
@@ -280,14 +339,19 @@
 
 	i_assert(_file->output == NULL);
 
-	file->fs_output = fs_write_stream(file->super);
-	if (file->hash_input == NULL)
-		_file->output = file->fs_output;
+	if (file->super == NULL)
+		_file->output = o_stream_create_error(EINVAL);
 	else {
-		/* compare if files are equal */
-		_file->output = o_stream_create_cmp(file->fs_output,
-						    file->hash_input);
+		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);
+		}
 	}
+	o_stream_set_name(_file->output, _file->path);
 }
 
 static int fs_sis_write_stream_finish(struct fs_file *_file, bool success)
@@ -295,8 +359,10 @@
 	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);
+		if (file->super != NULL) {
+			fs_write_stream_abort(file->super, &file->fs_output);
+			fs_sis_file_copy_error(file);
+		}
 		return -1;
 	}
 
@@ -305,7 +371,7 @@
 	    i_stream_is_eof(file->hash_input)) {
 		if (fs_sis_try_link(file)) {
 			fs_write_stream_abort(file->super, &file->fs_output);
-			return 0;
+			return 1;
 		}
 	}
 
@@ -316,7 +382,7 @@
 	T_BEGIN {
 		fs_sis_replace_hash_file(file);
 	} T_END;
-	return 0;
+	return 1;
 }
 
 static int
@@ -336,93 +402,88 @@
 	i_unreached();
 }
 
-static int fs_sis_fdatasync(struct fs_file *_file)
+static int fs_sis_exists(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);
+	if (fs_exists(file->super) < 0) {
+		fs_sis_copy_error(file->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_exists(struct fs *_fs, const char *path)
+static int fs_sis_stat(struct fs_file *_file, struct stat *st_r)
 {
-	struct sis_fs *fs = (struct sis_fs *)_fs;
+	struct sis_fs_file *file = (struct sis_fs_file *)_file;
 
-	if (fs_exists(fs->super, path) < 0) {
-		fs_sis_copy_error(fs);
+	if (fs_stat(file->super, st_r) < 0) {
+		fs_sis_copy_error(file->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_stat(struct fs *_fs, const char *path, struct stat *st_r)
+static int fs_sis_copy(struct fs_file *_src, struct fs_file *_dest)
 {
-	struct sis_fs *fs = (struct sis_fs *)_fs;
+	struct sis_fs_file *src = (struct sis_fs_file *)_src;
+	struct sis_fs_file *dest = (struct sis_fs_file *)_dest;
 
-	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);
+	if (fs_copy(src->super, dest->super) < 0) {
+		fs_sis_copy_error(src->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_rename(struct fs *_fs, const char *src, const char *dest)
+static int fs_sis_rename(struct fs_file *_src, struct fs_file *_dest)
 {
-	struct sis_fs *fs = (struct sis_fs *)_fs;
+	struct sis_fs_file *src = (struct sis_fs_file *)_src;
+	struct sis_fs_file *dest = (struct sis_fs_file *)_dest;
 
-	if (fs_rename(fs->super, src, dest) < 0) {
-		fs_sis_copy_error(fs);
+	if (fs_rename(src->super, dest->super) < 0) {
+		fs_sis_copy_error(src->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_unlink(struct fs *_fs, const char *path)
+static int fs_sis_delete(struct fs_file *_file)
 {
-	struct sis_fs *fs = (struct sis_fs *)_fs;
+	struct sis_fs_file *file = (struct sis_fs_file *)_file;
 
 	T_BEGIN {
-		fs_sis_try_unlink_hash_file(&fs->fs, fs->super, path);
+		fs_sis_try_unlink_hash_file(_file->fs, file->super);
 	} T_END;
-	if (fs_unlink(fs->super, path) < 0) {
-		fs_sis_copy_error(fs);
+	if (fs_delete(file->super) < 0) {
+		fs_sis_copy_error(file->fs);
 		return -1;
 	}
 	return 0;
 }
 
-static int fs_sis_rmdir(struct fs *_fs, const char *path)
+static struct fs_iter *
+fs_sis_iter_init(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;
+	return fs_iter_init(fs->super, path);
 }
 
 struct fs fs_class_sis = {
 	.name = "sis",
 	.v = {
+		fs_sis_alloc,
 		fs_sis_init,
 		fs_sis_deinit,
-		fs_sis_open,
-		fs_sis_close,
+		fs_sis_get_properties,
+		fs_sis_file_init,
+		fs_sis_file_deinit,
+		fs_sis_set_async_callback,
+		fs_sis_wait_async,
+		fs_sis_set_metadata,
+		fs_sis_get_metadata,
+		fs_sis_prefetch,
 		fs_sis_read,
 		fs_sis_read_stream,
 		fs_sis_write,
@@ -430,12 +491,13 @@
 		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_copy,
 		fs_sis_rename,
-		fs_sis_unlink,
-		fs_sis_rmdir
+		fs_sis_delete,
+		fs_sis_iter_init,
+		NULL,
+		NULL
 	}
 };
--- a/src/lib-storage/index/dbox-common/dbox-storage.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-storage/index/dbox-common/dbox-storage.c	Fri Oct 12 00:22:19 2012 +0300
@@ -102,6 +102,7 @@
 		dir = mail_user_home_expand(_storage->user,
 					    set->mail_attachment_dir);
 		storage->attachment_dir = p_strdup(_storage->pool, dir);
+		fs_set.root_path = storage->attachment_dir;
 		if (fs_init(name, args, &fs_set, &storage->attachment_fs,
 			    &error) < 0) {
 			*error_r = t_strdup_printf("mail_attachment_fs: %s",
--- a/src/lib-storage/index/dbox-single/sdbox-copy.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-storage/index/dbox-single/sdbox-copy.c	Fri Oct 12 00:22:19 2012 +0300
@@ -15,6 +15,7 @@
 {
 	struct dbox_storage *src_storage = src_file->file.storage;
 	struct dbox_storage *dest_storage = dest_file->file.storage;
+	struct fs_file *src_fsfile, *dest_fsfile;
 	ARRAY_TYPE(mail_attachment_extref) extrefs;
 	const struct mail_attachment_extref *extref;
 	const char *extrefs_line, *src, *dest, *dest_relpath;
@@ -61,7 +62,11 @@
 					   guid_generate(), NULL);
 		dest = t_strdup_printf("%s/%s", dest_storage->attachment_dir,
 				       dest_relpath);
-		if (fs_link(dest_storage->attachment_fs, src, dest) < 0) {
+		src_fsfile = fs_file_init(src_storage->attachment_fs, src,
+					  FS_OPEN_MODE_READONLY);
+		dest_fsfile = fs_file_init(dest_storage->attachment_fs, dest,
+					   FS_OPEN_MODE_READONLY);
+		if (fs_copy(src_fsfile, dest_fsfile) < 0) {
 			mail_storage_set_critical(&dest_storage->storage, "%s",
 				fs_last_error(dest_storage->attachment_fs));
 			ret = -1;
@@ -69,6 +74,8 @@
 			array_append(&dest_file->attachment_paths,
 				     &dest_relpath, 1);
 		}
+		fs_file_deinit(&src_fsfile);
+		fs_file_deinit(&dest_fsfile);
 	} T_END;
 	pool_unref(&pool);
 	return ret;
--- a/src/lib-storage/index/dbox-single/sdbox-file.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-storage/index/dbox-single/sdbox-file.c	Fri Oct 12 00:22:19 2012 +0300
@@ -126,6 +126,7 @@
 static int sdbox_file_rename_attachments(struct sdbox_file *file)
 {
 	struct dbox_storage *storage = file->file.storage;
+	struct fs_file *src_file, *dest_file;
 	const char *const *pathp, *src, *dest;
 	int ret = 0;
 
@@ -133,11 +134,17 @@
 		src = t_strdup_printf("%s/%s", storage->attachment_dir, *pathp);
 		dest = t_strdup_printf("%s/%s", storage->attachment_dir,
 				sdbox_file_attachment_relpath(file, *pathp));
-		if (fs_rename(storage->attachment_fs, src, dest) < 0) {
+		src_file = fs_file_init(storage->attachment_fs, src,
+					FS_OPEN_MODE_READONLY);
+		dest_file = fs_file_init(storage->attachment_fs, dest,
+					FS_OPEN_MODE_READONLY);
+		if (fs_rename(src_file, dest_file) < 0) {
 			mail_storage_set_critical(&storage->storage, "%s",
 				fs_last_error(storage->attachment_fs));
 			ret = -1;
 		}
+		fs_file_deinit(&src_file);
+		fs_file_deinit(&dest_file);
 	} T_END;
 	return ret;
 }
@@ -181,6 +188,7 @@
 {
 	struct dbox_storage *storage = file->file.storage;
 	struct fs *fs = storage->attachment_fs;
+	struct fs_file *fs_file;
 	const char *const *pathp, *path;
 	int ret = 0;
 
@@ -191,20 +199,25 @@
 		   attachment paths), so it's safe to delete them. */
 		path = t_strdup_printf("%s/%s", storage->attachment_dir,
 				       *pathp);
-		if (fs_unlink(fs, path) < 0 &&
+		fs_file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY);
+		if (fs_delete(fs_file) < 0 &&
 		    errno != ENOENT) {
 			mail_storage_set_critical(&storage->storage, "%s",
 						  fs_last_error(fs));
 			ret = -1;
 		}
+		fs_file_deinit(&fs_file);
+
 		path = t_strdup_printf("%s/%s", storage->attachment_dir,
 				sdbox_file_attachment_relpath(file, *pathp));
-		if (fs_unlink(fs, path) < 0 &&
+		fs_file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY);
+		if (fs_delete(fs_file) < 0 &&
 		    errno != ENOENT) {
 			mail_storage_set_critical(&storage->storage, "%s",
 						  fs_last_error(fs));
 			ret = -1;
 		}
+		fs_file_deinit(&fs_file);
 	} T_END;
 	return ret;
 }
--- a/src/lib-storage/index/index-attachment.c	Thu Oct 11 23:01:13 2012 +0300
+++ b/src/lib-storage/index/index-attachment.c	Fri Oct 12 00:22:19 2012 +0300
@@ -81,14 +81,14 @@
 	struct mail_save_attachment *attach = ctx->data.attach;
 	struct mail_storage *storage = ctx->transaction->box->storage;
 	struct mail_attachment_extref *extref;
-	enum fs_open_flags flags = FS_OPEN_FLAG_MKDIR;
+	enum fs_open_flags flags = 0;
 	const char *attachment_dir, *path, *digest = info->hash;
 	guid_128_t guid_128;
 
 	i_assert(attach->cur_file == NULL);
 
-	if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER)
-		flags |= FS_OPEN_FLAG_FDATASYNC;
+	if (storage->set->parsed_fsync_mode == FSYNC_MODE_NEVER)
+		flags |= FS_OPEN_FLAG_UNIMPORTANT;
 
 	if (strlen(digest) < 4) {
 		/* make sure we can access first 4 bytes without accessing
@@ -102,12 +102,8 @@
 			       digest[0], digest[1],
 			       digest[2], digest[3], digest,
 			       guid_128_to_string(guid_128));
-	if (fs_open(attach->fs, path,
-		    FS_OPEN_MODE_CREATE | flags, &attach->cur_file) < 0) {
-		mail_storage_set_critical(storage, "%s",
-			fs_last_error(attach->fs));
-		return -1;
-	}
+	attach->cur_file = fs_file_init(attach->fs, path,
+					FS_OPEN_MODE_CREATE | flags);
 
 	extref = array_append_space(&attach->extrefs);
 	extref->start_offset = info->start_offset;
@@ -139,7 +135,7 @@
 			fs_file_last_error(attach->cur_file));
 		ret = -1;
 	}
-	fs_close(&attach->cur_file);
+	fs_file_deinit(&attach->cur_file);
 
 	if (ret < 0) {
 		array_delete(&attach->extrefs,
@@ -258,32 +254,15 @@
 index_attachment_delete_real(struct mail_storage *storage,
 			     struct fs *fs, const char *name)
 {
-	const char *path, *p, *attachment_dir;
+	struct fs_file *file;
+	const char *path;
 	int ret;
 
 	path = t_strdup_printf("%s/%s", index_attachment_dir_get(storage), name);
-	if ((ret = fs_unlink(fs, path)) < 0)
+	file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY);
+	if ((ret = fs_delete(file)) < 0)
 		mail_storage_set_critical(storage, "%s", fs_last_error(fs));
-
-	/* if the directory is now empty, rmdir it and its parents
-	   until it fails */
-	attachment_dir = index_attachment_dir_get(storage);
-	while ((p = strrchr(path, '/')) != NULL) {
-		path = t_strdup_until(path, p);
-		if (strcmp(path, attachment_dir) == 0)
-			break;
-
-		if (fs_rmdir(fs, path) == 0) {
-			/* success, continue to parent */
-		} else if (errno == ENOTEMPTY || errno == EEXIST) {
-			/* there are other entries in this directory */
-			break;
-		} else {
-			mail_storage_set_critical(storage, "%s",
-				fs_last_error(fs));
-			break;
-		}
-	}
+	fs_file_deinit(&file);
 	return ret;
 }