view src/lib-fs/fs-test.c @ 21322:5ab8dc1a4a6f

global: Change string position/length from unsigned int to size_t Mainly to avoid truncating >4GB strings, which might potentially cause some security holes. Normally there are other limits, which prevent such excessive strings from being created in the first place. I'm sure this didn't find everything. Maybe everything could be found with compiler warnings. -Wconversion kind of does it, but it gives way too many unnecessary warnings. These were mainly found with: grep " = strlen" egrep "unsigned int.*(size|len)"
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Mon, 12 Dec 2016 07:19:55 +0200
parents 5f6962a5be0f
children 59437f8764c6
line wrap: on
line source

/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "istream.h"
#include "ostream.h"
#include "test-common.h"
#include "fs-test.h"

static struct fs *fs_test_alloc(void)
{
	struct test_fs *fs;

	fs = i_new(struct test_fs, 1);
	fs->fs = fs_class_test;
	i_array_init(&fs->iter_files, 32);
	return &fs->fs;
}

static int
fs_test_init(struct fs *_fs ATTR_UNUSED, const char *args ATTR_UNUSED,
	     const struct fs_settings *set ATTR_UNUSED)
{
	return 0;
}

static void fs_test_deinit(struct fs *_fs)
{
	struct test_fs *fs = (struct test_fs *)_fs;

	array_free(&fs->iter_files);
	i_free(fs);
}

static enum fs_properties fs_test_get_properties(struct fs *_fs)
{
	struct test_fs *fs = (struct test_fs *)_fs;

	return fs->properties;
}

static struct fs_file *
fs_test_file_init(struct fs *_fs, const char *path,
		  enum fs_open_mode mode, enum fs_open_flags flags)
{
	struct test_fs_file *file;

	file = i_new(struct test_fs_file, 1);
	file->file.fs = _fs;
	file->file.path = i_strdup(path);
	file->file.flags = flags;
	file->mode = mode;
	file->contents = buffer_create_dynamic(default_pool, 1024);
	file->exists = TRUE;
	file->seekable = TRUE;
	file->wait_async = (flags & FS_OPEN_FLAG_ASYNC) != 0;
	return &file->file;
}

static void fs_test_file_deinit(struct fs_file *_file)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	buffer_free(&file->contents);
	i_free(file->file.path);
	i_free(file);
}

static void fs_test_file_close(struct fs_file *_file)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	file->closed = TRUE;
}

static const char *fs_test_file_get_path(struct fs_file *_file)
{
	return _file->path;
}

static void
fs_test_set_async_callback(struct fs_file *_file,
			   fs_file_async_callback_t *callback,
			   void *context)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	file->async_callback = callback;
	file->async_context = context;
}

static int fs_test_wait_async(struct fs *_fs ATTR_UNUSED)
{
	return 0;
}

static void
fs_test_set_metadata(struct fs_file *_file, const char *key,
		     const char *value)
{
	fs_default_set_metadata(_file, key, value);
}

static int
fs_test_get_metadata(struct fs_file *_file,
		     const ARRAY_TYPE(fs_metadata) **metadata_r)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	if (file->wait_async) {
		fs_set_error_async(_file->fs);
		return -1;
	}
	if (file->io_failure) {
		errno = EIO;
		return -1;
	}
	fs_metadata_init(_file);
	*metadata_r = &_file->metadata;
	return 0;
}

static bool fs_test_prefetch(struct fs_file *_file ATTR_UNUSED,
			     uoff_t length ATTR_UNUSED)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	file->prefetched = TRUE;
	return TRUE;
}

static void fs_test_stream_destroyed(struct test_fs_file *file)
{
	i_assert(file->input != NULL);
	file->input = NULL;
}

static struct istream *
fs_test_read_stream(struct fs_file *_file, size_t max_buffer_size ATTR_UNUSED)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;
	struct istream *input;

	i_assert(file->input == NULL);

	if (!file->exists)
		return i_stream_create_error(ENOENT);
	if (file->io_failure)
		return i_stream_create_error(EIO);
	input = test_istream_create_data(file->contents->data,
					 file->contents->used);
	i_stream_add_destroy_callback(input, fs_test_stream_destroyed, file);
	if (!file->seekable)
		input->seekable = FALSE;
	file->input = input;
	return input;
}

static void fs_test_write_stream(struct fs_file *_file)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	i_assert(_file->output == NULL);

	buffer_set_used_size(file->contents, 0);
	_file->output = o_stream_create_buffer(file->contents);
}

static int fs_test_write_stream_finish(struct fs_file *_file, bool success)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	if (_file->output != NULL)
		o_stream_destroy(&_file->output);
	if (file->wait_async) {
		fs_set_error_async(_file->fs);
		return 0;
	}
	if (file->io_failure)
		success = FALSE;
	if (!success)
		buffer_set_used_size(file->contents, 0);
	return success ? 1 : -1;
}

static int
fs_test_lock(struct fs_file *_file, unsigned int secs ATTR_UNUSED,
	     struct fs_lock **lock_r)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	if (file->locked)
		return 0;
	file->locked = TRUE;
	*lock_r = i_new(struct fs_lock, 1);
	(*lock_r)->file = _file;
	return 1;
}

static void fs_test_unlock(struct fs_lock *lock)
{
	struct test_fs_file *file = (struct test_fs_file *)lock->file;

	file->locked = FALSE;
	i_free(lock);
}

static int fs_test_exists(struct fs_file *_file)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	if (file->wait_async) {
		fs_set_error_async(_file->fs);
		return -1;
	}
	if (file->io_failure) {
		errno = EIO;
		return -1;
	}
	return file->exists ? 1 : 0;
}

static int fs_test_stat(struct fs_file *_file, struct stat *st_r)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	if (file->wait_async) {
		fs_set_error_async(_file->fs);
		return -1;
	}
	if (file->io_failure) {
		errno = EIO;
		return -1;
	}
	if (!file->exists) {
		errno = ENOENT;
		return -1;
	}
	memset(st_r, 0, sizeof(*st_r));
	st_r->st_size = file->contents->used;
	return 0;
}

static int fs_test_copy(struct fs_file *_src, struct fs_file *_dest)
{
	struct test_fs_file *src;
	struct test_fs_file *dest = (struct test_fs_file *)_dest;

	if (_src != NULL)
		dest->copy_src = test_fs_file_get(_src->fs, fs_file_path(_src));
	src = dest->copy_src;
	if (dest->wait_async) {
		fs_set_error_async(_dest->fs);
		return -1;
	}
	dest->copy_src = NULL;

	if (dest->io_failure) {
		errno = EIO;
		return -1;
	}
	if (!src->exists) {
		errno = ENOENT;
		return -1;
	}
	buffer_set_used_size(dest->contents, 0);
	buffer_append_buf(dest->contents, src->contents, 0, (size_t)-1);
	dest->exists = TRUE;
	return 0;
}

static int fs_test_rename(struct fs_file *_src, struct fs_file *_dest)
{
	struct test_fs_file *src = (struct test_fs_file *)_src;
	struct test_fs_file *dest = (struct test_fs_file *)_dest;

	if (src->wait_async || dest->wait_async) {
		fs_set_error_async(_dest->fs);
		return -1;
	}

	if (fs_test_copy(_src, _dest) < 0)
		return -1;
	src->exists = FALSE;
	return 0;
}

static int fs_test_delete(struct fs_file *_file)
{
	struct test_fs_file *file = (struct test_fs_file *)_file;

	if (file->wait_async) {
		fs_set_error_async(_file->fs);
		return -1;
	}

	if (!file->exists) {
		errno = ENOENT;
		return -1;
	}
	return 0;
}

static struct fs_iter *
fs_test_iter_init(struct fs *_fs, const char *path,
		  enum fs_iter_flags flags)
{
	struct test_fs *fs = (struct test_fs *)_fs;
	struct test_fs_iter *iter;

	iter = i_new(struct test_fs_iter, 1);
	iter->iter.fs = _fs;
	iter->iter.flags = flags;
	iter->prefix = i_strdup(path);
	iter->prefix_len = strlen(iter->prefix);
	iter->prev_dir = i_strdup("");
	array_sort(&fs->iter_files, i_strcmp_p);
	return &iter->iter;
}

static const char *fs_test_iter_next(struct fs_iter *_iter)
{
	struct test_fs_iter *iter = (struct test_fs_iter *)_iter;
	struct test_fs *fs = (struct test_fs *)_iter->fs;
	const char *const *files, *p;
	unsigned int count;
	size_t len, prev_dir_len = strlen(iter->prev_dir);

	files = array_get(&fs->iter_files, &count);
	for (; iter->idx < count; iter->idx++) {
		const char *fname = files[iter->idx];

		if (strncmp(fname, iter->prefix, iter->prefix_len) != 0)
			continue;
		p = strrchr(fname, '/');
		if ((_iter->flags & FS_ITER_FLAG_DIRS) == 0) {
			if (p == NULL)
				return fname;
			if (p[1] == '\0')
				continue; /* dir/ */
			return p+1;
		}

		if (p == NULL)
			continue;
		len = p - fname;
		if (len == 0)
			continue;
		if (len == prev_dir_len &&
		    strncmp(fname, iter->prev_dir, len) == 0)
			continue;
		i_free(iter->prev_dir);
		iter->prev_dir = i_strndup(fname, len);
		return iter->prev_dir;
	}
	return NULL;
}

static int fs_test_iter_deinit(struct fs_iter *_iter)
{
	struct test_fs_iter *iter = (struct test_fs_iter *)_iter;
	int ret = iter->failed ? -1 : 0;

	i_free(iter->prefix);
	i_free(iter);
	return ret;
}

struct test_fs *test_fs_get(struct fs *fs)
{
	while (strcmp(fs->name, "test") != 0) {
		i_assert(fs->parent != NULL);
		fs = fs->parent;
	}
	return (struct test_fs *)fs;
}

struct test_fs_file *test_fs_file_get(struct fs *fs, const char *path)
{
	struct fs_file *file;

	fs = &test_fs_get(fs)->fs;

	for (file = fs->files;; file = file->next) {
		i_assert(file != NULL);
		if (strcmp(fs_file_path(file), path) == 0)
			break;
	}
	return (struct test_fs_file *)file;
}

const struct fs fs_class_test = {
	.name = "test",
	.v = {
		fs_test_alloc,
		fs_test_init,
		fs_test_deinit,
		fs_test_get_properties,
		fs_test_file_init,
		fs_test_file_deinit,
		fs_test_file_close,
		fs_test_file_get_path,
		fs_test_set_async_callback,
		fs_test_wait_async,
		fs_test_set_metadata,
		fs_test_get_metadata,
		fs_test_prefetch,
		NULL,
		fs_test_read_stream,
		NULL,
		fs_test_write_stream,
		fs_test_write_stream_finish,
		fs_test_lock,
		fs_test_unlock,
		fs_test_exists,
		fs_test_stat,
		fs_test_copy,
		fs_test_rename,
		fs_test_delete,
		fs_test_iter_init,
		fs_test_iter_next,
		fs_test_iter_deinit,
		NULL,
		NULL,
	}
};