Author: stefan2
Date: Mon Aug 26 23:53:19 2013
New Revision: 1517733
URL: http://svn.apache.org/r1517733
Log:
Following up on a suggestion by Ivan on @dev about a month ago.
This is an initial draft plus completely untested implementation
for an SVN file API. Its implementation works around a few
inefficiencies and scalability issues with apr_file_t. See the
header comments for details.
Tests will be added eventually and the code stabilized based on
the test results. To prevent accidental use in current production
code, the new API is only available if you define #ENABLE_SVN_FILE
in your code.
* subversion/include/private/svn_file.h
(): new file, declaring the svn_file_t API.
* subversion/libsvn_subr/file.c
(): implement new API; missing documentation
Added:
subversion/trunk/subversion/include/private/svn_file.h
subversion/trunk/subversion/libsvn_subr/file.c
Added: subversion/trunk/subversion/include/private/svn_file.h
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_file.h?rev=1517733&view=auto
==============================================================================
--- subversion/trunk/subversion/include/private/svn_file.h (added)
+++ subversion/trunk/subversion/include/private/svn_file.h Mon Aug 26 23:53:19
2013
@@ -0,0 +1,177 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_file.h
+ * @brief Functions efficient handling of buffered files
+ */
+
+#if !defined(SVN_FILE_H) && defined(ENABLE_SVN_FILE)
+#define SVN_FILE_H
+
+#include "svn_types.h"
+#include "svn_error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* svn_file_t build upon apr_file_t but come with the following improvements
+ * and limitations:
+ *
+ * + unlimited number of instances while limiting the number of open
+ * file handles used per process
+ * + efficient forward & backward navigation due to using 2 buffers
+ * (code allows for even more)
+ * + aligned data access
+ * + user-defined buffer sizes
+ * + seek()s will be deferred until the next data access
+ * + read-after-write does not force buffer to be flushed
+ * + efficient file size / eof detection
+ * + low-overhead getc / putc functions
+ *
+ * - only applicable to random access files
+ * - concurrent access to the same file must not change the file size
+ * - file open flags limited to SVN_FILE__SUPPORTED_FLAGS
+ */
+
+/* Returns the maximum number of OS-level file handles that shall be used.
+ *
+ * Please note that this limit may be exceeded in heavily multi-threaded
+ * applications if more threads than this limit are interacting with files
+ * at the same time.
+ */
+apr_size_t
+svn_file__get_max_shared_handles(void);
+
+/* Allow up to NEW_MAX OS-level file handles to be open at the same time.
+ * The limit may be changed at any time. 0 is a valid limit.
+ */
+svn_error_t *
+svn_file__set_max_shared_handles(apr_size_t new_max);
+
+/* Opaque file data type.
+ */
+typedef struct svn_file_t svn_file_t;
+
+/* These file open flags are set implicitly on all files. Specifying
+ * them or not in svn_file__open will have no effect.
+ */
+#define SVN_FILE__IMPLICIT_FLAGS \
+ (APR_BINARY | APR_BUFFERED | APR_XTHREAD)
+
+/* Only these file open flags are allowed with svn_file__open.
+ * Using other flags will trigger an assertion.
+ */
+#define SVN_FILE__SUPPORTED_FLAGS \
+ (SVN_FILE__IMPLICIT_FLAGS | APR_READ | APR_WRITE | APR_CREATE \
+ | APR_APPEND | APR_TRUNCATE | APR_EXCL)
+
+/* Create a file object in *RESULT for the file NAME and the given open
+ * FLAGs. Use data buffers of BUFFER_SIZE each (must be a power of 2).
+ * If DEFER_CREATION is set, no APR file handle will be allocated and
+ * the disc contents remain unchanged until the first data access.
+ *
+ * Allocate data in POOL. The file will be closed automatically when
+ * POOL gets cleaned up.
+ */
+svn_error_t *
+svn_file__open(svn_file_t **result,
+ const char *name,
+ apr_int32_t flag,
+ apr_size_t buffer_size,
+ svn_boolean_t defer_creation,
+ apr_pool_t *pool);
+
+/* Close file object FILE. All modified buffers will written back to
+ * disk and the underlying APR file handle (if any) be closed.
+ */
+svn_error_t *
+svn_file__close(svn_file_t *file);
+
+/* Read TO_READ bytes from the current position in FILE and write them
+ * into DATA and set *READ to the number of bytes actually read. The only
+ * reason why *READ may differ from TO_READ is hitting eof. If READ is
+ * NULL in that case, the function will return an error. If HIT_EOF is
+ * not NULL, it will also indicate whether eof is just after the last
+ * byte returned.
+ */
+svn_error_t *
+svn_file__read(svn_file_t *file,
+ void *data,
+ apr_size_t to_read,
+ apr_size_t *read,
+ svn_boolean_t *hit_eof);
+
+/* Read one byte from FILE and return it in *DATA. The file pointer must
+ * not be on eof.
+ */
+svn_error_t *
+svn_file__getc(svn_file_t *file,
+ char *data);
+
+/* Write TO_WRITE bytes from DATA to the current position in FILE.
+ */
+svn_error_t *
+svn_file__write(svn_file_t *file,
+ const void *data,
+ apr_size_t to_write);
+
+/* Write one byte DATA to the current position in FILE.
+ */
+svn_error_t *
+svn_file__putc(svn_file_t *file,
+ char data);
+
+/* Return the size of FILE in *FILE_SIZE.
+ */
+svn_error_t *
+svn_file__get_size(apr_off_t *file_size,
+ svn_file_t *file);
+
+/* Move the file pointer of FILE to the absolute POSITION >= 0.
+ */
+svn_error_t *
+svn_file__seek(svn_file_t *file,
+ apr_off_t position);
+
+/* Return the current position of the read / write pointer of FILE.
+ */
+apr_off_t
+svn_file__get_position(svn_file_t *file);
+
+/* Set the file size of FILE to its current position.
+ */
+svn_error_t *
+svn_file__truncate(svn_file_t *file);
+
+/* Set *EOF to TRUE, if FILE's position is at or behind eof.
+ */
+svn_error_t *
+svn_file__at_eof(svn_boolean_t *eof,
+ svn_file_t *file);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_FILE_H */
Added: subversion/trunk/subversion/libsvn_subr/file.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/file.c?rev=1517733&view=auto
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/file.c (added)
+++ subversion/trunk/subversion/libsvn_subr/file.c Mon Aug 26 23:53:19 2013
@@ -0,0 +1,1120 @@
+/*
+ * file.c : routines for efficient file handling
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define ENABLE_SVN_FILE
+
+#include <assert.h>
+#include <apr_file_io.h>
+
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_sorts.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_atomic.h"
+#include "private/svn_mutex.h"
+#include "private/svn_file.h"
+
+#include "svn_private_config.h"
+
+#define DEFAULT_CAPACITY 16
+#define BUFFER_COUNT 2
+#define INVALID_OFFSET (-1)
+
+typedef struct shared_handle_t
+{
+ apr_file_t *file;
+
+ const char *file_name;
+ apr_int32_t reopen_flags;
+ apr_uint32_t name_hash;
+ apr_off_t position;
+
+ int idx;
+ struct shared_handle_t *next;
+ struct shared_handle_t *previous;
+
+ apr_pool_t *pool;
+} shared_handle_t;
+
+typedef struct shared_handle_pool_t
+{
+ svn_mutex__t *mutex;
+
+ apr_array_header_t *handles;
+
+ shared_handle_t *first_open;
+ shared_handle_t *last_open;
+ shared_handle_t *first_unused;
+
+ int capacity;
+ int unused_count; /* shared_handle_t instances w/o an open file handle */
+ int open_count; /* shared_handle_t instances with an open file handle */
+ int used_count; /* shared_handle_t instances currently handed out */
+
+ apr_pool_t *pool;
+} shared_handle_pool_t;
+
+typedef struct buffer_t
+{
+ unsigned char *data;
+ apr_size_t size;
+ apr_size_t used;
+
+ apr_off_t start_offset;
+ svn_boolean_t modified;
+} buffer_t;
+
+struct svn_file_t
+{
+ buffer_t *buffers[BUFFER_COUNT];
+ apr_size_t buffer_count;
+
+ apr_off_t position;
+ apr_off_t size;
+
+ const char *file_name;
+ apr_uint32_t name_hash;
+ apr_int32_t reopen_flags;
+ apr_size_t buffer_size;
+ apr_pool_t *pool;
+
+ int handle_hint;
+};
+
+static volatile svn_atomic_t handle_pool_init_state = 0;
+static shared_handle_pool_t global_handle_pool = { 0 };
+
+static svn_error_t *
+init_handle_pool(void *baton, apr_pool_t *scratch_pool)
+{
+ /* Global pool for the temp path */
+ apr_pool_t *pool = svn_pool_create(NULL);
+
+ SVN_ERR(svn_mutex__init(&global_handle_pool.mutex, TRUE, pool));
+ global_handle_pool.handles
+ = apr_array_make(pool, DEFAULT_CAPACITY, sizeof(shared_handle_t *));
+
+ global_handle_pool.capacity = DEFAULT_CAPACITY;
+ global_handle_pool.first_open = 0;
+ global_handle_pool.last_open = 0;
+ global_handle_pool.first_unused = 0;
+
+ global_handle_pool.pool = pool;
+
+ return SVN_NO_ERROR;
+}
+
+static shared_handle_pool_t *
+get_handle_pool(void)
+{
+ SVN_ERR_ASSERT_NO_RETURN( SVN_NO_ERROR
+ == svn_atomic__init_once(&handle_pool_init_state,
+ init_handle_pool,
+ NULL, NULL));
+
+ return &global_handle_pool;
+}
+
+#define FNV1_PRIME_32 0x01000193
+#define FNV1_BASE_32 2166136261
+
+/* FNV-1a core implementation returning a 32 bit checksum over the first
+ * LEN bytes in INPUT. HASH is the checksum over preceding data (if any).
+ */
+static apr_uint32_t
+calc_hash(const void *input)
+{
+ const unsigned char *data = input;
+ apr_uint32_t hash = FNV1_BASE_32;
+
+ for (; *data; ++data)
+ {
+ hash ^= *data;
+ hash *= FNV1_PRIME_32;
+ }
+
+ return hash;
+}
+
+static shared_handle_t *
+created_shared_handle(shared_handle_pool_t *handle_pool)
+{
+ shared_handle_t *result = apr_pcalloc(handle_pool->pool, sizeof(*result));
+ result->pool = svn_pool_create(handle_pool->pool);
+
+ result->idx = handle_pool->handles->nelts;
+ APR_ARRAY_PUSH(handle_pool->handles, shared_handle_t *) = NULL;
+ ++handle_pool->open_count;
+
+ return result;
+}
+
+static shared_handle_t *
+reclaim_shared_handle(shared_handle_pool_t *handle_pool)
+{
+ shared_handle_t *result = handle_pool->last_open;
+ assert(result);
+
+ handle_pool->last_open = result->previous;
+ result->previous = NULL;
+ assert(result->next == NULL);
+
+ APR_ARRAY_IDX(handle_pool->handles, result->idx,
+ shared_handle_t *) = NULL;
+
+ /* implicitly closes the file */
+ apr_pool_clear(result->pool);
+
+ return result;
+}
+
+static shared_handle_t *
+recycle_shared_handle(shared_handle_pool_t *handle_pool)
+{
+ shared_handle_t *result = handle_pool->first_unused;
+ assert(handle_pool->first_unused);
+
+ handle_pool->first_unused = result->next;
+ result->next = NULL;
+ assert(result->previous == NULL);
+
+ ++handle_pool->open_count;
+ --handle_pool->unused_count;
+
+ return result;
+}
+
+static svn_error_t *
+allocate_handle_internal(shared_handle_t **handle,
+ shared_handle_pool_t *handle_pool,
+ svn_file_t *file)
+{
+ shared_handle_t *result;
+
+ if (handle_pool->capacity <= handle_pool->open_count)
+ /* only open a new new handle if we have no other choice */
+ result = handle_pool->open_count == handle_pool->used_count
+ ? created_shared_handle(handle_pool)
+ : reclaim_shared_handle(handle_pool);
+ else
+ /* open a new handle while keeping existing ones untouched */
+ result = handle_pool->unused_count == 0
+ ? created_shared_handle(handle_pool)
+ : recycle_shared_handle(handle_pool);
+
+ result->file_name = apr_pstrdup(result->pool, file->file_name);
+ result->name_hash = calc_hash(result->file_name);
+ result->reopen_flags = ((APR_READ | APR_WRITE) & file->reopen_flags)
+ | (APR_BINARY | APR_EXCL | APR_XTHREAD);
+ result->position = 0;
+ SVN_ERR(svn_io_file_open(&result->file,
+ result->file_name,
+ file->reopen_flags,
+ APR_OS_DEFAULT,
+ result->pool));
+
+ ++handle_pool->used_count;
+
+ file->reopen_flags = result->reopen_flags;
+ file->handle_hint = result->idx;
+
+ *handle = result;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+allocate_handle(shared_handle_t **handle,
+ svn_file_t *file)
+{
+ shared_handle_pool_t *handle_pool = get_handle_pool();
+ SVN_MUTEX__WITH_LOCK(handle_pool->mutex,
+ allocate_handle_internal(handle, handle_pool, file));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+handle_matches(shared_handle_t *handle,
+ svn_file_t *file)
+{
+ if (file->name_hash != handle->name_hash)
+ return FALSE;
+ if (file->reopen_flags != handle->reopen_flags)
+ return FALSE;
+
+ return strcmp(file->file_name, handle->file_name) == 0;
+}
+
+static svn_error_t *
+get_handle_internal(shared_handle_t **handle,
+ shared_handle_pool_t *handle_pool,
+ svn_boolean_t auto_create,
+ svn_file_t *file)
+{
+ shared_handle_t *result;
+
+ /* try quick match */
+ result = APR_ARRAY_IDX(handle_pool->handles, file->handle_hint,
+ shared_handle_t *);
+ if (!result || !handle_matches(result, file))
+ /* crawl open files */
+ for (result = handle_pool->first_open; result; result = result->next)
+ if (handle_matches(result, file))
+ break;
+
+ if (result)
+ {
+ /* can we reuse the entry? */
+ if (result->next)
+ result->next->previous = result->previous;
+ else
+ handle_pool->last_open = result->previous;
+
+ if (result->previous)
+ result->previous->next = result->next;
+ else
+ handle_pool->first_open = result->next;
+
+ result->previous = NULL;
+ result->next = NULL;
+
+ APR_ARRAY_IDX(handle_pool->handles, result->idx,
+ shared_handle_t *) = NULL;
+ ++handle_pool->used_count;
+ }
+ else if (auto_create)
+ {
+ /* we need a new handle */
+ SVN_ERR(allocate_handle_internal(&result, handle_pool, file));
+ }
+
+ *handle = result;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_handle(shared_handle_t **handle,
+ svn_file_t *file)
+{
+ shared_handle_pool_t *handle_pool = get_handle_pool();
+ SVN_MUTEX__WITH_LOCK(handle_pool->mutex,
+ get_handle_internal(handle, handle_pool,
+ TRUE, file));
+
+ return SVN_NO_ERROR;
+}
+
+static void
+close_handle(shared_handle_pool_t *handle_pool,
+ shared_handle_t *handle)
+{
+ /* implicitly closes the file */
+ apr_pool_clear(handle->pool);
+
+ handle_pool->last_open = handle->previous;
+ handle->next = handle_pool->first_unused;
+ handle_pool->first_unused = handle;
+
+ ++handle_pool->unused_count;
+}
+
+static svn_error_t *
+release_handle_internal(shared_handle_pool_t *handle_pool,
+ shared_handle_t *handle,
+ svn_boolean_t keep_open)
+{
+ --handle_pool->used_count;
+ if (!keep_open || handle_pool->capacity <= handle_pool->used_count)
+ {
+ close_handle(handle_pool, handle);
+ }
+ else
+ {
+ if (handle_pool->first_open)
+ {
+ handle->next = handle_pool->first_open;
+ handle_pool->first_open->previous = handle;
+ handle_pool->first_open = handle;
+ }
+ else
+ {
+ handle_pool->first_open = handle;
+ handle_pool->last_open = handle;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+release_handle(shared_handle_t *handle,
+ svn_boolean_t keep_open)
+{
+ shared_handle_pool_t *handle_pool = get_handle_pool();
+ SVN_MUTEX__WITH_LOCK(handle_pool->mutex,
+ release_handle_internal(handle_pool, handle,
+ keep_open));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file_internal(shared_handle_pool_t *handle_pool,
+ svn_file_t *file)
+{
+ shared_handle_t *handle;
+ SVN_ERR(get_handle_internal(&handle, handle_pool, FALSE, file));
+
+ if (handle)
+ close_handle(handle_pool, handle);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(svn_file_t *file)
+{
+ shared_handle_pool_t *handle_pool = get_handle_pool();
+ SVN_MUTEX__WITH_LOCK(handle_pool->mutex,
+ close_file_internal(handle_pool, file));
+
+ return SVN_NO_ERROR;
+}
+
+apr_size_t
+svn_file__get_max_shared_handles(void)
+{
+ return get_handle_pool()->capacity;
+}
+
+static svn_error_t *
+set_max_shared_handles_internal(shared_handle_pool_t *handle_pool,
+ apr_size_t new_max)
+{
+ handle_pool->capacity = new_max;
+ while ( (handle_pool->capacity > handle_pool->open_count)
+ && (handle_pool->used_count < handle_pool->open_count))
+ {
+ reclaim_shared_handle(handle_pool);
+ --handle_pool->open_count;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__set_max_shared_handles(apr_size_t new_max)
+{
+ shared_handle_pool_t *handle_pool = get_handle_pool();
+ SVN_MUTEX__WITH_LOCK(handle_pool->mutex,
+ set_max_shared_handles_internal(handle_pool, new_max));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+handle_seek(shared_handle_t *handle,
+ apr_off_t offset)
+{
+ if (handle->position != offset)
+ {
+ apr_off_t actual_offset = offset;
+ SVN_ERR(svn_io_file_seek(handle->file, APR_SET,
+ &actual_offset, handle->pool));
+ handle->position = actual_offset;
+
+ SVN_ERR_ASSERT(actual_offset == offset);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+flush_buffer(shared_handle_t *handle,
+ buffer_t *buffer,
+ apr_pool_t *pool)
+{
+ assert(buffer->modified);
+ assert(buffer->size >= buffer->used);
+
+ SVN_ERR(handle_seek(handle, buffer->start_offset));
+ SVN_ERR(svn_io_file_write_full(handle->file, buffer->data,
+ buffer->used, NULL, pool));
+ handle->position += buffer->used;
+
+ buffer->modified = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+sort_buffers(buffer_t **buffers,
+ apr_size_t count)
+{
+ apr_size_t i, k;
+
+ for (i = 0; i < count - 1; ++i)
+ for (k = i + 1; k < count; ++k)
+ if (buffers[i]->start_offset > buffers[k]->start_offset)
+ {
+ buffer_t *temp = buffers[i];
+ buffers[i] = buffers[k];
+ buffers[k] = temp;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+flush_all_buffers(shared_handle_t *handle,
+ svn_file_t *file)
+{
+ apr_size_t i;
+
+ sort_buffers(file->buffers, file->buffer_count);
+ for (i = 0; i < file->buffer_count; ++i)
+ if (file->buffers[i]->modified)
+ SVN_ERR(flush_buffer(handle, file->buffers[i], file->pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_close_internal(svn_file_t *file)
+{
+ apr_size_t i;
+ svn_boolean_t buffers_dirty = FALSE;
+
+ for (i = 0; i < file->buffer_count; ++i)
+ buffers_dirty |= file->buffers[i]->modified;
+
+ if (buffers_dirty)
+ {
+ shared_handle_t *handle;
+
+ SVN_ERR(get_handle(&handle, file));
+ SVN_ERR(flush_all_buffers(handle, file));
+ SVN_ERR(release_handle(handle, FALSE));
+ }
+ else
+ {
+ SVN_ERR(close_file(file));
+ }
+
+ /* free all buffer & temp memory */
+ svn_pool_destroy(file->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static apr_status_t
+file_destructor(void *arg)
+{
+ svn_file_t *file = arg;
+ svn_error_t *err = file_close_internal(file);
+ apr_status_t apr_err = 0;
+
+ if (err)
+ {
+ apr_err = err->apr_err;
+ svn_error_clear(err);
+ }
+
+ return apr_err;
+}
+
+svn_error_t *
+svn_file__open(svn_file_t **result,
+ const char *name,
+ apr_int32_t flag,
+ apr_size_t buffer_size,
+ svn_boolean_t defer_creation,
+ apr_pool_t *pool)
+{
+ shared_handle_t *handle;
+ svn_file_t *file;
+
+ /* using any of the unsupported flags may result in unspecified behavior */
+ SVN_ERR_ASSERT((flag & SVN_FILE__SUPPORTED_FLAGS) == flag);
+
+ /* buffer size must be a power of two*/
+ SVN_ERR_ASSERT((buffer_size & (buffer_size - 1)) == 0);
+
+ /* init the file data structure */
+ file = apr_pcalloc(pool, sizeof(*file));
+ file->size = INVALID_OFFSET;
+ file->file_name = apr_pstrdup(pool, name);
+ file->name_hash = calc_hash(name);
+ file->reopen_flags = (flag & ~APR_BUFFERED) | APR_BINARY | APR_XTHREAD;
+ file->buffer_size = buffer_size;
+ file->pool = svn_pool_create(pool);
+
+ /* sometimes, we know that the file will be empty when opened */
+ if (flag & (APR_CREATE | APR_TRUNCATE))
+ file->size = 0;
+
+ /* force file creation & check its existence */
+ if (!defer_creation)
+ {
+ SVN_ERR(allocate_handle(&handle, file));
+ SVN_ERR(release_handle(handle, TRUE));
+ }
+
+ /* auto-close on pool destruction */
+ apr_pool_cleanup_register(file->pool, file, file_destructor,
+ apr_pool_cleanup_null);
+ *result = file;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__close(svn_file_t *file)
+{
+ /* don't try to close the file twice */
+ apr_pool_cleanup_kill(file->pool, file, file_destructor);
+
+ /* flush buffers & close the underlying file */
+ SVN_ERR(file_close_internal(file));
+
+ /* free all buffer & temp memory */
+ svn_pool_destroy(file->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+is_single_buffer_access(svn_file_t *file,
+ apr_size_t to_read)
+{
+ return ((file->position + to_read) ^ file->position) < file->buffer_size;
+}
+
+static svn_error_t *
+report_bytes_read(svn_file_t *file,
+ apr_size_t to_read,
+ apr_size_t actual,
+ apr_size_t *read)
+{
+ if (read)
+ *read = actual;
+ else if (to_read != actual)
+ return svn_error_wrap_apr(APR_EOF, _("Incomplete read in file '%s'"),
+ svn_dirent_local_style(file->file_name,
+ file->pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_buffer(buffer_t **buffer,
+ shared_handle_t **handle,
+ svn_file_t *file,
+ apr_off_t start_offset)
+{
+ apr_size_t i;
+ apr_size_t to_read;
+ svn_boolean_t hit_eof;
+ buffer_t *result;
+ assert(start_offset % file->buffer_size == 0);
+
+ for (i = 0; i < file->buffer_count; ++i)
+ if (file->buffers[i]->start_offset == start_offset)
+ {
+ *buffer = file->buffers[i];
+ if (i > 0)
+ {
+ memmove(file->buffers + i, file->buffers + i - 1,
+ i * sizeof(*buffer));
+ file->buffers[0] = *buffer;
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ if (!*handle)
+ SVN_ERR(get_handle(handle, file));
+
+ if (file->buffer_count < BUFFER_COUNT)
+ {
+ result = apr_pcalloc(file->pool, sizeof(*result));
+ result->data = apr_pcalloc(file->pool, file->buffer_size);
+ result->size = file->buffer_size;
+ }
+ else
+ {
+ result = file->buffers[BUFFER_COUNT-1];
+ if (result->modified)
+ SVN_ERR(flush_buffer(*handle, result, file->pool));
+ }
+
+ result->start_offset = start_offset;
+ result->used = 0;
+
+ SVN_ERR(handle_seek(*handle, start_offset));
+
+ to_read = file->size == INVALID_OFFSET
+ ? result->size
+ : result->start_offset + result->size <= file->size
+ ? result->size
+ : (apr_size_t)(file->size - result->start_offset);
+
+ SVN_ERR(svn_io_file_read_full2((*handle)->file, result->data, to_read,
+ &result->used, &hit_eof, file->pool));
+ (*handle)->position += result->used;
+
+ if (result->used < result->size)
+ file->size = result->used + result->start_offset;
+
+ if (file->buffer_count > 1)
+ memmove(file->buffers + file->buffer_count - 1,
+ file->buffers + file->buffer_count - 2,
+ (file->buffer_count - 1) * sizeof(result));
+
+ file->buffers[0] = result;
+ *buffer = result;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+buffered_read(shared_handle_t **handle,
+ svn_file_t *file,
+ void *data,
+ apr_size_t to_read,
+ apr_size_t *read)
+{
+ buffer_t *buffer;
+ apr_size_t to_copy;
+ apr_size_t offset = file->position & ~file->buffer_size;
+
+ SVN_ERR(get_buffer(&buffer, handle, file, file->position - offset));
+ SVN_ERR_ASSERT(offset <= buffer->used);
+
+ to_copy = MIN(buffer->used - offset, to_read);
+ memcpy(data, buffer->data + offset, to_copy);
+ file->position += to_copy;
+
+ SVN_ERR(report_bytes_read(file, to_read, to_copy, read));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+require_read_access(svn_file_t *file)
+{
+ return (file->reopen_flags & APR_READ)
+ ? SVN_NO_ERROR
+ : svn_error_wrap_apr(APR_EACCES, _("No read access to file '%s'"),
+ svn_dirent_local_style(file->file_name, file->pool));
+}
+
+svn_error_t *
+svn_file__read(svn_file_t *file,
+ void *data,
+ apr_size_t to_read,
+ apr_size_t *read,
+ svn_boolean_t *hit_eof)
+{
+ shared_handle_t *handle = NULL;
+ SVN_ERR(require_read_access(file));
+
+ if (is_single_buffer_access(file, to_read))
+ {
+ SVN_ERR(buffered_read(&handle, file, data, to_read, read));
+ }
+ else
+ {
+ buffer_t *buffer;
+ apr_size_t i;
+ apr_off_t file_size = 0;
+ apr_off_t final_position = 0;
+ apr_size_t initial_to_read = to_read;
+
+ /* restrict the read operation to what we can do inside EOF */
+ SVN_ERR(svn_file__get_size(&file_size, file));
+ if (file_size < file->position + to_read)
+ to_read = file->size - file->position;
+
+ SVN_ERR(report_bytes_read(file, initial_to_read, to_read, read));
+ final_position = file->position + to_read;
+
+ /* copy data from existing buffers to DATA */
+ sort_buffers(file->buffers, file->buffer_count);
+
+ for (i = 0; i < BUFFER_COUNT && file->buffers[i]; ++i)
+ {
+ buffer = file->buffers[i];
+ if ( buffer->start_offset <= file->position
+ && buffer->start_offset + buffer->used > file->position)
+ {
+ apr_size_t offset = file->position - buffer->start_offset;
+ apr_size_t to_copy = buffer->used - offset;
+ memcpy(data, file->buffers[i]->data + offset, to_copy);
+
+ file->position += to_copy;
+ data = (char *)data + to_copy;
+ }
+ }
+
+ for (i = BUFFER_COUNT; i > 0; --i)
+ {
+ apr_off_t end = file->position + to_read;
+ buffer = file->buffers[i-1];
+ if ( buffer->start_offset < end
+ && buffer->start_offset + buffer->used >= end)
+ {
+ apr_size_t to_copy = end - buffer->start_offset;
+ assert(buffer->used == to_copy);
+ memcpy(data, file->buffers[i]->data, to_copy);
+ to_copy -= to_copy;
+ }
+ }
+
+ /* flush data from middle buffers */
+ for (i = 0; i < BUFFER_COUNT && file->buffers[i]; ++i)
+ {
+ buffer = file->buffers[i];
+ if ( buffer->start_offset + buffer->used > file->position
+ && buffer->start_offset < file->position + to_read)
+ {
+ if (!handle)
+ SVN_ERR(get_handle(&handle, file));
+ SVN_ERR(flush_buffer(handle, buffer, file->pool));
+ }
+ }
+
+ /* read & buffer incomplete start block */
+ if (to_read && (file->position & ~file->buffer_size))
+ {
+ apr_size_t data_read;
+ SVN_ERR(buffered_read(&handle, file, data, to_read, &data_read));
+ data = (char *)data + data_read;
+ to_read -= data_read;
+ }
+
+ /* Read complete inner blocks without buffering them.
+ * If the last block is a full block, we will read it into a
+ * buffer further down to allow for back & forth navigation.
+ */
+ if (to_read > file->buffer_size)
+ {
+ apr_size_t data_read;
+ if (!handle)
+ SVN_ERR(get_handle(&handle, file));
+
+ SVN_ERR(handle_seek(handle, file->position));
+ SVN_ERR(svn_io_file_read_full2(handle->file, data,
+ to_read & -file->buffer_size,
+ &data_read, NULL, file->pool));
+
+ file->position += data_read;
+ handle->position = file->position;
+
+ data = (char *)data + data_read;
+ to_read -= data_read;
+ }
+
+ /* read & buffer incomplete end block */
+ if (to_read)
+ {
+ apr_size_t data_read;
+ SVN_ERR(buffered_read(&handle, file, data, to_read, &data_read));
+ }
+
+ file->position = final_position;
+ }
+
+ if (handle)
+ SVN_ERR(release_handle(handle, TRUE));
+
+ /* do this *after* we released the handle to prevent the creation of
+ * a second handle
+ */
+ if (hit_eof)
+ SVN_ERR(svn_file__at_eof(hit_eof, file));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__getc(svn_file_t *file,
+ char *data)
+{
+ apr_size_t offset = file->position & ~file->buffer_size;
+ apr_off_t block_start = file->position - offset;
+
+ SVN_ERR(require_read_access(file));
+
+ /* if we read a file linearly using getc(), the data will almost
+ * certainly (>99.99%) be in the first buffer.
+ */
+ if ( file->buffers[0]
+ && file->buffers[0]->start_offset == block_start
+ && file->buffers[0]->used > offset)
+ {
+ *data = file->buffers[0]->data[offset];
+ ++file->position;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Handle all other cases using the standard read mechanism.
+ * This will also prime the first buffer for future getc() - if there
+ * is any data left to be read.
+ */
+ SVN_ERR(svn_file__read(file, &data, 1, NULL, NULL));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+require_write_access(svn_file_t *file)
+{
+ return (file->reopen_flags & APR_WRITE)
+ ? SVN_NO_ERROR
+ : svn_error_wrap_apr(APR_EACCES, _("No write access to file '%s'"),
+ svn_dirent_local_style(file->file_name, file->pool));
+}
+
+static svn_error_t *
+buffered_write(shared_handle_t **handle,
+ svn_file_t *file,
+ const char *data,
+ apr_size_t to_write)
+{
+ buffer_t *buffer;
+ apr_size_t offset = file->position & ~file->buffer_size;
+
+ SVN_ERR(get_buffer(&buffer, handle, file, file->position - offset));
+ SVN_ERR_ASSERT(offset <= buffer->used);
+
+ memcpy(buffer->data + offset, data, to_write);
+ buffer->modified = TRUE;
+ buffer->used = MAX(buffer->used, offset + to_write);
+
+ file->position += to_write;
+ if (file->size != INVALID_OFFSET && file->size < file->position)
+ file->size = file->position;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__write(svn_file_t *file,
+ const void *data,
+ apr_size_t to_write)
+{
+ shared_handle_t *handle = NULL;
+ SVN_ERR(require_write_access(file));
+
+ if (is_single_buffer_access(file, to_write))
+ {
+ SVN_ERR(buffered_write(&handle, file, data, to_write));
+ }
+ else
+ {
+ buffer_t *buffer;
+ apr_size_t i;
+
+ /* update existing buffers with data from existing buffers to DATA */
+ sort_buffers(file->buffers, file->buffer_count);
+
+ for (i = 0; i < BUFFER_COUNT && file->buffers[i]; ++i)
+ {
+ buffer = file->buffers[i];
+ if (buffer->start_offset <= file->position)
+ {
+ if (buffer->start_offset + buffer->used >= file->position)
+ {
+ apr_size_t buffer_left = buffer->start_offset + buffer->size
+ - file->position;
+ apr_size_t to_copy = MIN(to_write, buffer_left);
+ apr_size_t offset = file->position - buffer->start_offset;
+
+ if (to_copy)
+ {
+ memcpy(buffer->data + offset, data, to_copy);
+ buffer->used = MAX(buffer->used, offset + to_copy);
+ buffer->modified = TRUE;
+
+ if (buffer->used == buffer->size)
+ {
+ file->position += to_copy;
+ to_write -= to_copy;
+ data = (const char *)data + to_copy;
+ }
+ }
+ }
+ }
+ else if (buffer->start_offset < file->position + to_write)
+ {
+ apr_size_t offset = file->position - buffer->start_offset;
+ apr_size_t to_copy = MIN(buffer->size, to_write - offset);
+
+ memcpy(buffer->data, (const char *)data + offset, to_copy);
+ buffer->used = MAX(buffer->used, to_copy);
+ buffer->modified = TRUE;
+ }
+ }
+
+ /* write remaining data to disk */
+ if (to_write)
+ {
+ SVN_ERR(get_handle(&handle, file));
+ SVN_ERR(handle_seek(handle, file->position));
+ SVN_ERR(svn_io_file_write_full(handle->file, data, to_write,
+ NULL, file->pool));
+
+ file->position += to_write;
+ }
+
+ /* update file size info */
+ if (file->size != INVALID_OFFSET)
+ file->size = MAX(file->size, file->position);
+ }
+
+ if (handle)
+ SVN_ERR(release_handle(handle, TRUE));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__putc(svn_file_t *file,
+ char data)
+{
+ apr_size_t offset = file->position & ~file->buffer_size;
+
+ SVN_ERR(require_write_access(file));
+
+ /* if we write a file linearly using putc(), the data will almost
+ * certainly (>99.99%) be in the first buffer.
+ */
+ if ((file->buffers[0]->start_offset ^ file->position) < file->buffer_size)
+ {
+ assert(offset <= file->buffers[0]->used);
+ file->buffers[0]->data[offset] = data;
+
+ if (offset == file->buffers[0]->used)
+ file->buffers[0]->used++;
+
+ if (file->size == file->position)
+ ++file->size;
+ ++file->position;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Handle all other cases using the standard read mechanism.
+ * This will also prime the first buffer for future putc().
+ */
+ SVN_ERR(svn_file__write(file, &data, 1));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__get_size(apr_off_t *file_size,
+ svn_file_t *file)
+{
+ if (file->size == INVALID_OFFSET)
+ {
+ shared_handle_t *handle;
+ apr_off_t offset = 0;
+
+ SVN_ERR(get_handle(&handle, file));
+ SVN_ERR(svn_io_file_seek(handle->file, APR_END, &offset, file->pool));
+ handle->position = offset;
+ file->size = offset;
+ SVN_ERR(release_handle(handle, TRUE));
+ }
+
+ *file_size = file->size;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__seek(svn_file_t *file,
+ apr_off_t position)
+{
+ SVN_ERR_ASSERT(position >= 0);
+ file->position = position;
+
+ return SVN_NO_ERROR;
+}
+
+apr_off_t
+svn_file__get_position(svn_file_t *file)
+{
+ return file->position;
+}
+
+svn_error_t *
+svn_file__truncate(svn_file_t *file)
+{
+ shared_handle_t *handle;
+ apr_size_t i;
+
+ if (file->position == file->size)
+ return SVN_NO_ERROR;
+
+ /* shorten the file */
+ SVN_ERR(get_handle(&handle, file));
+ SVN_ERR(svn_io_file_trunc(handle->file, file->position, file->pool));
+ handle->position = file->position;
+ SVN_ERR(release_handle(handle, TRUE));
+
+ file->size = file->position;
+
+ /* truncate buffers accordingly */
+ for (i = 0; i < file->buffer_count; ++i)
+ if (file->buffers[i]->start_offset >= file->position)
+ {
+ file->buffers[i]->start_offset = file->position;
+ file->buffers[i]->used = 0;
+ file->buffers[i]->modified = FALSE;
+ }
+ else if ( file->buffers[i]->start_offset + file->buffers[i]->used
+ >= file->position)
+ {
+ file->buffers[i]->used
+ = file->position - file->buffers[i]->start_offset;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_file__at_eof(svn_boolean_t *eof,
+ svn_file_t *file)
+{
+ apr_off_t file_size;
+ SVN_ERR(svn_file__get_size(&file_size, file));
+ *eof = file_size <= file->position;
+
+ return SVN_NO_ERROR;
+}