blob: 129decd3775f0b2cd716abece35bf54a0fb66a36 [file] [log] [blame]
/*
* 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 2166136261u
/* 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 */
svn_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 */
svn_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;
}