#include <assert.h>
#include "svn_io.h"
#include "svn_pools.h"
#include "svn_sorts.h"
#include "svn_private_config.h"
#include "private/svn_sorts_private.h"
#include "private/svn_subr_private.h"
#include "private/svn_temp_serializer.h"
#include "index.h"
#include "pack.h"
#include "temp_serializer.h"
#include "util.h"
#include "fs_fs.h"
#include "../libsvn_fs/fs-loader.h"
#define ENCODED_INT_LENGTH 10
static
const apr_uint64_t off_t_max = (sizeof(apr_off_t) == sizeof(apr_int64_t))
? APR_INT64_MAX
: APR_INT32_MAX;
#define P2L_PROTO_INDEX_ENTRY_SIZE (6 * sizeof(apr_uint64_t))
#define L2P_STREAM_PREFIX "L2P-INDEX\n"
#define P2L_STREAM_PREFIX "P2L-INDEX\n"
#define STREAM_PREFIX_LEN MAX(sizeof(L2P_STREAM_PREFIX), \
sizeof(P2L_STREAM_PREFIX))
typedef struct l2p_page_table_entry_t
{
apr_uint64_t offset;
apr_uint32_t entry_count;
apr_uint32_t size;
} l2p_page_table_entry_t;
typedef struct l2p_header_t
{
svn_revnum_t first_revision;
apr_size_t revision_count;
apr_uint32_t page_size;
apr_size_t * page_table_index;
l2p_page_table_entry_t * page_table;
} l2p_header_t;
typedef struct l2p_page_t
{
apr_uint32_t entry_count;
apr_uint64_t *offsets;
} l2p_page_t;
typedef struct l2p_proto_entry_t
{
apr_uint64_t offset;
apr_uint64_t item_index;
} l2p_proto_entry_t;
typedef struct p2l_header_t
{
svn_revnum_t first_revision;
apr_uint64_t page_size;
apr_size_t page_count;
apr_uint64_t file_size;
apr_off_t *offsets;
} p2l_header_t;
enum { MAX_NUMBER_PREFETCH = 64 };
typedef struct value_position_pair_t
{
apr_uint64_t value;
apr_size_t total_len;
} value_position_pair_t;
struct svn_fs_fs__packed_number_stream_t
{
apr_file_t *file;
apr_off_t stream_start;
apr_off_t stream_end;
apr_size_t used;
apr_size_t current;
apr_off_t start_offset;
apr_off_t next_offset;
apr_size_t block_size;
apr_pool_t *pool;
value_position_pair_t buffer[MAX_NUMBER_PREFETCH];
};
static svn_error_t *
stream_error_create(svn_fs_fs__packed_number_stream_t *stream,
apr_status_t err,
const char *message)
{
const char *file_name;
apr_off_t offset;
SVN_ERR(svn_io_file_name_get(&file_name, stream->file,
stream->pool));
SVN_ERR(svn_io_file_get_offset(&offset, stream->file, stream->pool));
return svn_error_createf(err, NULL, message, file_name,
apr_psprintf(stream->pool,
"%" APR_UINT64_T_HEX_FMT,
(apr_uint64_t)offset));
}
SVN__PREVENT_INLINE
static svn_error_t *
packed_stream_read(svn_fs_fs__packed_number_stream_t *stream)
{
unsigned char buffer[MAX_NUMBER_PREFETCH];
apr_size_t bytes_read = 0;
apr_size_t i;
value_position_pair_t *target;
apr_off_t block_start = 0;
apr_off_t block_left = 0;
apr_status_t err;
stream->start_offset = stream->next_offset;
SVN_ERR(svn_io_file_aligned_seek(stream->file, stream->block_size,
&block_start, stream->next_offset,
stream->pool));
bytes_read = sizeof(buffer);
block_left = stream->block_size - (stream->next_offset - block_start);
if (block_left >= 10 && block_left < bytes_read)
bytes_read = (apr_size_t)block_left;
bytes_read = (apr_size_t)MIN(bytes_read,
stream->stream_end - stream->next_offset);
err = apr_file_read(stream->file, buffer, &bytes_read);
if (err && !APR_STATUS_IS_EOF(err))
return stream_error_create(stream, err,
_("Can't read index file '%s' at offset 0x%s"));
while (bytes_read > 0 && buffer[bytes_read-1] >= 0x80)
--bytes_read;
if SVN__PREDICT_FALSE(bytes_read == 0)
return stream_error_create(stream, err,
_("Unexpected end of index file %s at offset 0x%s"));
target = stream->buffer;
for (i = 0; i < bytes_read;)
{
if (buffer[i] < 0x80)
{
target->value = buffer[i];
++i;
target->total_len = i;
++target;
}
else
{
apr_uint64_t value = 0;
apr_uint64_t shift = 0;
while (buffer[i] >= 0x80)
{
value += ((apr_uint64_t)buffer[i] & 0x7f) << shift;
shift += 7;
++i;
}
target->value = value + ((apr_uint64_t)buffer[i] << shift);
++i;
target->total_len = i;
++target;
if SVN__PREDICT_FALSE(shift > 8 * sizeof(value))
return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Corrupt index: number too large"));
}
}
stream->used = target - stream->buffer;
stream->next_offset = stream->start_offset + i;
stream->current = 0;
return SVN_NO_ERROR;
}
static svn_error_t *
packed_stream_open(svn_fs_fs__packed_number_stream_t **stream,
apr_file_t *file,
apr_off_t start,
apr_off_t end,
const char *stream_prefix,
apr_size_t block_size,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
char buffer[STREAM_PREFIX_LEN + 1] = { 0 };
apr_size_t len = strlen(stream_prefix);
svn_fs_fs__packed_number_stream_t *result;
SVN_ERR_ASSERT(len < sizeof(buffer));
SVN_ERR(svn_io_file_aligned_seek(file, block_size, NULL, start,
scratch_pool));
SVN_ERR(svn_io_file_read_full2(file, buffer, len, NULL, NULL,
scratch_pool));
if (strncmp(buffer, stream_prefix, len))
return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Index stream header prefix mismatch.\n"
" expected: %s"
" found: %s"), stream_prefix, buffer);
result = apr_palloc(result_pool, sizeof(*result));
result->pool = result_pool;
result->file = file;
result->stream_start = start + len;
result->stream_end = end;
result->used = 0;
result->current = 0;
result->start_offset = result->stream_start;
result->next_offset = result->stream_start;
result->block_size = block_size;
*stream = result;
return SVN_NO_ERROR;
}
SVN__FORCE_INLINE
static svn_error_t*
packed_stream_get(apr_uint64_t *value,
svn_fs_fs__packed_number_stream_t *stream)
{
if (stream->current == stream->used)
SVN_ERR(packed_stream_read(stream));
*value = stream->buffer[stream->current].value;
++stream->current;
return SVN_NO_ERROR;
}
static void
packed_stream_seek(svn_fs_fs__packed_number_stream_t *stream,
apr_off_t offset)
{
apr_off_t file_offset = offset + stream->stream_start;
if ( stream->used == 0
|| offset < stream->start_offset
|| offset >= stream->next_offset)
{
stream->start_offset = file_offset;
stream->next_offset = file_offset;
stream->current = 0;
stream->used = 0;
}
else
{
apr_size_t i;
for (i = 0; i < stream->used; ++i)
if (stream->buffer[i].total_len > file_offset - stream->start_offset)
break;
stream->current = i;
}
}
static apr_off_t
packed_stream_offset(svn_fs_fs__packed_number_stream_t *stream)
{
apr_off_t file_offset
= stream->current == 0
? stream->start_offset
: stream->buffer[stream->current-1].total_len + stream->start_offset;
return file_offset - stream->stream_start;
}
static apr_size_t
encode_uint(unsigned char *p, apr_uint64_t value)
{
unsigned char *start = p;
while (value >= 0x80)
{
*p = (unsigned char)((value % 0x80) + 0x80);
value /= 0x80;
++p;
}
*p = (unsigned char)(value % 0x80);
return (p - start) + 1;
}
static apr_size_t
encode_int(unsigned char *p, apr_int64_t value)
{
return encode_uint(p, (apr_uint64_t)(value < 0 ? -1 - 2*value : 2*value));
}
static svn_error_t *
stream_write_encoded(svn_stream_t *stream,
apr_uint64_t value)
{
unsigned char encoded[ENCODED_INT_LENGTH];
apr_size_t len = encode_uint(encoded, value);
return svn_error_trace(svn_stream_write(stream, (char *)encoded, &len));
}
static apr_int64_t
decode_int(apr_uint64_t value)
{
return (apr_int64_t)(value % 2 ? -1 - value / 2 : value / 2);
}
static svn_error_t *
write_uint64_to_proto_index(apr_file_t *proto_index,
apr_uint64_t value,
apr_pool_t *scratch_pool)
{
apr_byte_t buffer[sizeof(value)];
int i;
apr_size_t written;
for (i = 0; i < sizeof(buffer); ++i)
{
buffer[i] = (apr_byte_t)value;
value >>= CHAR_BIT;
}
SVN_ERR(svn_io_file_write_full(proto_index, buffer, sizeof(buffer),
&written, scratch_pool));
SVN_ERR_ASSERT(written == sizeof(buffer));
return SVN_NO_ERROR;
}
static svn_error_t *
read_uint64_from_proto_index(apr_file_t *proto_index,
apr_uint64_t *value_p,
svn_boolean_t *eof,
apr_pool_t *scratch_pool)
{
apr_byte_t buffer[sizeof(*value_p)];
apr_size_t bytes_read;
SVN_ERR(svn_io_file_read_full2(proto_index, buffer, sizeof(buffer),
&bytes_read, eof, scratch_pool));
SVN_ERR_ASSERT((eof && *eof) || bytes_read == sizeof(buffer));
if (!eof || !*eof)
{
int i;
apr_uint64_t value;
value = 0;
for (i = sizeof(buffer) - 1; i >= 0; --i)
value = (value << CHAR_BIT) + buffer[i];
*value_p = value;
}
return SVN_NO_ERROR;
}
static svn_error_t *
read_uint32_from_proto_index(apr_file_t *proto_index,
apr_uint32_t *value_p,
svn_boolean_t *eof,
apr_pool_t *scratch_pool)
{
apr_uint64_t value;
SVN_ERR(read_uint64_from_proto_index(proto_index, &value, eof,
scratch_pool));
if (!eof || !*eof)
{
if (value > APR_UINT32_MAX)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
_("UINT32 0x%s too large, max = 0x%s"),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_HEX_FMT,
value),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_HEX_FMT,
(apr_uint64_t)APR_UINT32_MAX));
*value_p = (apr_uint32_t)value;
}
return SVN_NO_ERROR;
}
static svn_error_t *
read_off_t_from_proto_index(apr_file_t *proto_index,
apr_off_t *value_p,
svn_boolean_t *eof,
apr_pool_t *scratch_pool)
{
apr_uint64_t value;
SVN_ERR(read_uint64_from_proto_index(proto_index, &value, eof,
scratch_pool));
if (!eof || !*eof)
{
if (value > off_t_max)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
_("File offset 0x%s too large, max = 0x%s"),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_HEX_FMT,
value),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_HEX_FMT,
off_t_max));
*value_p = (apr_off_t)value;
}
return SVN_NO_ERROR;
}
static svn_error_t *
write_l2p_entry_to_proto_index(apr_file_t *proto_index,
l2p_proto_entry_t entry,
apr_pool_t *scratch_pool)
{
SVN_ERR(write_uint64_to_proto_index(proto_index, entry.offset,
scratch_pool));
SVN_ERR(write_uint64_to_proto_index(proto_index, entry.item_index,
scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
read_l2p_entry_from_proto_index(apr_file_t *proto_index,
l2p_proto_entry_t *entry,
svn_boolean_t *eof,
apr_pool_t *scratch_pool)
{
SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->offset, eof,
scratch_pool));
SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->item_index, eof,
scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
encode_l2p_page(apr_array_header_t *entries,
int start,
int end,
svn_spillbuf_t *buffer,
apr_pool_t *scratch_pool)
{
unsigned char encoded[ENCODED_INT_LENGTH];
int i;
const apr_uint64_t *values = (const apr_uint64_t *)entries->elts;
apr_uint64_t last_value = 0;
for (i = start; i < end; ++i)
{
apr_int64_t diff = values[i] - last_value;
last_value = values[i];
SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
encode_int(encoded, diff), scratch_pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__l2p_proto_index_open(apr_file_t **proto_index,
const char *file_name,
apr_pool_t *result_pool)
{
SVN_ERR(svn_io_file_open(proto_index, file_name, APR_READ | APR_WRITE
| APR_CREATE | APR_APPEND | APR_BUFFERED,
APR_OS_DEFAULT, result_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__l2p_proto_index_add_revision(apr_file_t *proto_index,
apr_pool_t *scratch_pool)
{
l2p_proto_entry_t entry;
entry.offset = 0;
entry.item_index = 0;
return svn_error_trace(write_l2p_entry_to_proto_index(proto_index, entry,
scratch_pool));
}
svn_error_t *
svn_fs_fs__l2p_proto_index_add_entry(apr_file_t *proto_index,
apr_off_t offset,
apr_uint64_t item_index,
apr_pool_t *scratch_pool)
{
l2p_proto_entry_t entry;
SVN_ERR_ASSERT(offset >= -1);
entry.offset = (apr_uint64_t)offset + 1;
SVN_ERR_ASSERT(item_index < UINT_MAX / 2);
entry.item_index = item_index;
return svn_error_trace(write_l2p_entry_to_proto_index(proto_index, entry,
scratch_pool));
}
svn_error_t *
svn_fs_fs__l2p_index_append(svn_checksum_t **checksum,
svn_fs_t *fs,
apr_file_t *index_file,
const char *proto_file_name,
svn_revnum_t revision,
apr_pool_t * result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
apr_file_t *proto_index = NULL;
svn_stream_t *stream;
int i;
apr_uint64_t entry;
svn_boolean_t eof = FALSE;
int last_page_count = 0;
apr_pool_t *local_pool = svn_pool_create(scratch_pool);
apr_pool_t *iterpool = svn_pool_create(local_pool);
apr_array_header_t *page_counts
= apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
apr_array_header_t *page_sizes
= apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
apr_array_header_t *entry_counts
= apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
apr_array_header_t *entries
= apr_array_make(local_pool, 256, sizeof(apr_uint64_t));
svn_spillbuf_t *buffer
= svn_spillbuf__create(0x10000, 0x1000000, local_pool);
if (ffd->l2p_page_size > APR_INT32_MAX)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("L2P index page size %s"
" exceeds current limit of 2G entries"),
apr_psprintf(local_pool, "%" APR_UINT64_T_FMT,
ffd->l2p_page_size));
SVN_ERR(svn_io_file_open(&proto_index, proto_file_name,
APR_READ | APR_CREATE | APR_BUFFERED,
APR_OS_DEFAULT, scratch_pool));
for (entry = 0; !eof; ++entry)
{
l2p_proto_entry_t proto_entry;
SVN_ERR(read_l2p_entry_from_proto_index(proto_index, &proto_entry,
&eof, local_pool));
if ((entry > 0 && proto_entry.offset == 0) || eof)
{
int entry_count = 0;
for (i = 0; i < entries->nelts; i += entry_count)
{
apr_uint64_t last_buffer_size
= (apr_uint64_t)svn_spillbuf__get_size(buffer);
svn_pool_clear(iterpool);
entry_count = ffd->l2p_page_size < entries->nelts - i
? (int)ffd->l2p_page_size
: entries->nelts - i;
SVN_ERR(encode_l2p_page(entries, i, i + entry_count,
buffer, iterpool));
APR_ARRAY_PUSH(entry_counts, apr_uint64_t) = entry_count;
APR_ARRAY_PUSH(page_sizes, apr_uint64_t)
= svn_spillbuf__get_size(buffer) - last_buffer_size;
}
apr_array_clear(entries);
APR_ARRAY_PUSH(page_counts, apr_uint64_t)
= page_sizes->nelts - last_page_count;
last_page_count = page_sizes->nelts;
}
else
{
int idx;
if (proto_entry.item_index > APR_INT32_MAX)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("Item index %s too large "
"in l2p proto index for revision %ld"),
apr_psprintf(local_pool, "%" APR_UINT64_T_FMT,
proto_entry.item_index),
revision + page_counts->nelts);
idx = (int)proto_entry.item_index;
while (idx >= entries->nelts)
APR_ARRAY_PUSH(entries, apr_uint64_t) = 0;
APR_ARRAY_IDX(entries, idx, apr_uint64_t) = proto_entry.offset;
}
}
SVN_ERR(svn_io_file_close(proto_index, local_pool));
if (page_counts->nelts > APR_INT32_MAX)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("L2P index page count %d"
" exceeds current limit of 2G pages"),
page_counts->nelts);
stream = svn_stream_checksummed2(svn_stream_from_aprfile2(index_file, TRUE,
local_pool),
NULL, checksum, svn_checksum_md5, FALSE,
result_pool);
SVN_ERR(svn_stream_puts(stream, L2P_STREAM_PREFIX));
SVN_ERR(stream_write_encoded(stream, revision));
SVN_ERR(stream_write_encoded(stream, ffd->l2p_page_size));
SVN_ERR(stream_write_encoded(stream, page_counts->nelts));
SVN_ERR(stream_write_encoded(stream, page_sizes->nelts));
for (i = 0; i < page_counts->nelts; ++i)
{
apr_uint64_t value = APR_ARRAY_IDX(page_counts, i, apr_uint64_t);
SVN_ERR(stream_write_encoded(stream, value));
}
for (i = 0; i < page_sizes->nelts; ++i)
{
apr_uint64_t value = APR_ARRAY_IDX(page_sizes, i, apr_uint64_t);
SVN_ERR(stream_write_encoded(stream, value));
value = APR_ARRAY_IDX(entry_counts, i, apr_uint64_t);
SVN_ERR(stream_write_encoded(stream, value));
}
SVN_ERR(svn_stream_copy3(svn_stream__from_spillbuf(buffer, local_pool),
stream, NULL, NULL, local_pool));
svn_pool_destroy(local_pool);
return SVN_NO_ERROR;
}
static svn_error_t *
auto_open_l2p_index(svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t revision)
{
if (rev_file->l2p_stream == NULL)
{
fs_fs_data_t *ffd = fs->fsap_data;
SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
SVN_ERR(packed_stream_open(&rev_file->l2p_stream,
rev_file->file,
rev_file->l2p_offset,
rev_file->p2l_offset,
L2P_STREAM_PREFIX,
(apr_size_t)ffd->block_size,
rev_file->pool,
rev_file->pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
get_l2p_header_body(l2p_header_t **header,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t revision,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
apr_uint64_t value;
apr_size_t i;
apr_size_t page, page_count;
apr_off_t offset;
l2p_header_t *result = apr_pcalloc(result_pool, sizeof(*result));
apr_size_t page_table_index;
svn_revnum_t next_rev;
pair_cache_key_t key;
key.revision = rev_file->start_revision;
key.second = rev_file->is_packed;
SVN_ERR(auto_open_l2p_index(rev_file, fs, revision));
packed_stream_seek(rev_file->l2p_stream, 0);
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
result->first_revision = (svn_revnum_t)value;
if (result->first_revision != rev_file->start_revision)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Index rev / pack file revision numbers do not match"));
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
result->page_size = (apr_uint32_t)value;
if (!result->page_size || (result->page_size & (result->page_size - 1)))
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("L2P index page size is not a power of two"));
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
result->revision_count = (int)value;
if ( result->revision_count != 1
&& result->revision_count != (apr_uint64_t)ffd->max_files_per_dir)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Invalid number of revisions in L2P index"));
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
page_count = (apr_size_t)value;
if (page_count < result->revision_count)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Fewer L2P index pages than revisions"));
if (page_count > (rev_file->p2l_offset - rev_file->l2p_offset) / 2)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("L2P index page count implausibly large"));
next_rev = result->first_revision + (svn_revnum_t)result->revision_count;
if (result->first_revision > revision || next_rev <= revision)
return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Corrupt L2P index for r%ld only covers r%ld:%ld"),
revision, result->first_revision, next_rev);
result->page_table
= apr_pcalloc(result_pool, page_count * sizeof(*result->page_table));
result->page_table_index
= apr_pcalloc(result_pool, (result->revision_count + 1)
* sizeof(*result->page_table_index));
page_table_index = 0;
result->page_table_index[0] = page_table_index;
for (i = 0; i < result->revision_count; ++i)
{
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
if (value == 0)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Revision with no L2P index pages"));
page_table_index += (apr_size_t)value;
if (page_table_index > page_count)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("L2P page table exceeded"));
result->page_table_index[i+1] = page_table_index;
}
if (page_table_index != page_count)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Revisions do not cover the full L2P index page table"));
for (page = 0; page < page_count; ++page)
{
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
if (value == 0)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Empty L2P index page"));
result->page_table[page].size = (apr_uint32_t)value;
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
if (value > result->page_size)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Page exceeds L2P index page size"));
result->page_table[page].entry_count = (apr_uint32_t)value;
}
offset = packed_stream_offset(rev_file->l2p_stream);
for (page = 0; page < page_count; ++page)
{
result->page_table[page].offset = offset;
offset += result->page_table[page].size;
}
*header = result;
SVN_ERR(svn_cache__set(ffd->l2p_header_cache, &key, result, scratch_pool));
return SVN_NO_ERROR;
}
typedef struct l2p_page_info_baton_t
{
svn_revnum_t revision;
apr_uint64_t item_index;
l2p_page_table_entry_t entry;
apr_uint32_t page_no;
apr_uint32_t page_offset;
svn_revnum_t first_revision;
} l2p_page_info_baton_t;
static svn_error_t *
l2p_page_info_copy(l2p_page_info_baton_t *baton,
const l2p_header_t *header,
const l2p_page_table_entry_t *page_table,
const apr_size_t *page_table_index,
apr_pool_t *scratch_pool)
{
apr_size_t rel_revision = baton->revision - header->first_revision;
if (rel_revision >= header->revision_count)
return svn_error_createf(SVN_ERR_FS_INDEX_REVISION , NULL,
_("Revision %ld not covered by item index"),
baton->revision);
if (baton->item_index < header->page_size)
{
baton->page_offset = (apr_uint32_t)baton->item_index;
baton->page_no = 0;
baton->entry = page_table[page_table_index[rel_revision]];
}
else
{
const l2p_page_table_entry_t *first_entry;
const l2p_page_table_entry_t *last_entry;
apr_uint64_t max_item_index;
first_entry = page_table + page_table_index[rel_revision];
last_entry = page_table + page_table_index[rel_revision + 1];
max_item_index = (apr_uint64_t)header->page_size
* (last_entry - first_entry);
if (baton->item_index >= max_item_index)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("Item index %s exceeds l2p limit "
"of %s for revision %ld"),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_FMT,
baton->item_index),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_FMT,
max_item_index),
baton->revision);
baton->page_offset = (apr_uint32_t)(baton->item_index % header->page_size);
baton->page_no = (apr_uint32_t)(baton->item_index / header->page_size);
baton->entry = first_entry[baton->page_no];
}
baton->first_revision = header->first_revision;
return SVN_NO_ERROR;
}
static svn_error_t *
l2p_page_info_access_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
const l2p_header_t *header = data;
const l2p_page_table_entry_t *page_table
= svn_temp_deserializer__ptr(header,
(const void *const *)&header->page_table);
const apr_size_t *page_table_index
= svn_temp_deserializer__ptr(header,
(const void *const *)&header->page_table_index);
return l2p_page_info_copy(baton, header, page_table, page_table_index,
result_pool);
}
static svn_error_t *
get_l2p_page_info(l2p_page_info_baton_t *baton,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
l2p_header_t *result;
svn_boolean_t is_cached = FALSE;
void *dummy = NULL;
pair_cache_key_t key;
key.revision = rev_file->start_revision;
key.second = rev_file->is_packed;
SVN_ERR(svn_cache__get_partial((void**)&dummy, &is_cached,
ffd->l2p_header_cache, &key,
l2p_page_info_access_func, baton,
scratch_pool));
if (is_cached)
return SVN_NO_ERROR;
SVN_ERR(get_l2p_header_body(&result, rev_file, fs, baton->revision,
scratch_pool, scratch_pool));
SVN_ERR(l2p_page_info_copy(baton, result, result->page_table,
result->page_table_index, scratch_pool));
return SVN_NO_ERROR;
}
typedef struct l2p_page_table_baton_t
{
svn_revnum_t revision;
apr_array_header_t *pages;
} l2p_page_table_baton_t;
static svn_error_t *
l2p_page_table_access_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
l2p_page_table_baton_t *table_baton = baton;
const l2p_header_t *header = (const l2p_header_t *)data;
const l2p_page_table_entry_t *page_table
= svn_temp_deserializer__ptr(header,
(const void *const *)&header->page_table);
const apr_size_t *page_table_index
= svn_temp_deserializer__ptr(header,
(const void *const *)&header->page_table_index);
apr_size_t rel_revision = table_baton->revision - header->first_revision;
if (rel_revision < header->revision_count)
{
const l2p_page_table_entry_t *entry
= page_table + page_table_index[rel_revision];
const l2p_page_table_entry_t *last_entry
= page_table + page_table_index[rel_revision + 1];
for (; entry < last_entry; ++entry)
APR_ARRAY_PUSH(table_baton->pages, l2p_page_table_entry_t)
= *entry;
}
*out = table_baton->pages;
return SVN_NO_ERROR;
}
static svn_error_t *
get_l2p_page_table(apr_array_header_t *pages,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t revision,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_boolean_t is_cached = FALSE;
l2p_page_table_baton_t baton;
pair_cache_key_t key;
key.revision = rev_file->start_revision;
key.second = rev_file->is_packed;
apr_array_clear(pages);
baton.revision = revision;
baton.pages = pages;
SVN_ERR(svn_cache__get_partial((void**)&pages, &is_cached,
ffd->l2p_header_cache, &key,
l2p_page_table_access_func, &baton,
scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
get_l2p_page(l2p_page_t **page,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t start_revision,
l2p_page_table_entry_t *table_entry,
apr_pool_t *result_pool)
{
apr_uint32_t i;
l2p_page_t *result = apr_pcalloc(result_pool, sizeof(*result));
apr_uint64_t last_value = 0;
SVN_ERR(auto_open_l2p_index(rev_file, fs, start_revision));
packed_stream_seek(rev_file->l2p_stream, table_entry->offset);
result->entry_count = table_entry->entry_count;
result->offsets = apr_pcalloc(result_pool, result->entry_count
* sizeof(*result->offsets));
for (i = 0; i < result->entry_count; ++i)
{
apr_uint64_t value = 0;
SVN_ERR(packed_stream_get(&value, rev_file->l2p_stream));
last_value += decode_int(value);
result->offsets[i] = last_value - 1;
}
if ( packed_stream_offset(rev_file->l2p_stream)
!= table_entry->offset + table_entry->size)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("L2P actual page size does not match page table value."));
*page = result;
return SVN_NO_ERROR;
}
static svn_error_t *
prefetch_l2p_pages(svn_boolean_t *end,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t first_revision,
svn_revnum_t revision,
apr_array_header_t *pages,
int exlcuded_page_no,
apr_off_t min_offset,
apr_off_t max_offset,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
int i;
apr_pool_t *iterpool;
svn_fs_fs__page_cache_key_t key = { 0 };
if (min_offset < 0)
min_offset = 0;
if (max_offset <= 0)
{
*end = TRUE;
return SVN_NO_ERROR;
}
*end = FALSE;
SVN_ERR(get_l2p_page_table(pages, fs, rev_file, revision, scratch_pool));
if (pages->nelts == 0 || rev_file->l2p_stream == NULL)
{
*end = TRUE;
return SVN_NO_ERROR;
}
iterpool = svn_pool_create(scratch_pool);
assert(revision <= APR_UINT32_MAX);
key.revision = (apr_uint32_t)revision;
key.is_packed = rev_file->is_packed;
for (i = 0; i < pages->nelts && !*end; ++i)
{
svn_boolean_t is_cached;
l2p_page_table_entry_t *entry
= &APR_ARRAY_IDX(pages, i, l2p_page_table_entry_t);
svn_pool_clear(iterpool);
if (i == exlcuded_page_no)
continue;
if ( entry->offset < (apr_uint64_t)min_offset
|| entry->offset + entry->size > (apr_uint64_t)max_offset)
{
*end = TRUE;
continue;
}
key.page = i;
SVN_ERR(svn_cache__has_key(&is_cached, ffd->l2p_page_cache,
&key, iterpool));
if (!is_cached)
{
l2p_page_t *page = NULL;
SVN_ERR(get_l2p_page(&page, rev_file, fs, first_revision, entry,
iterpool));
SVN_ERR(svn_cache__set(ffd->l2p_page_cache, &key, page,
iterpool));
}
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
typedef struct l2p_entry_baton_t
{
svn_revnum_t revision;
apr_uint64_t item_index;
apr_uint32_t page_offset;
apr_uint64_t offset;
} l2p_entry_baton_t;
static svn_error_t *
l2p_page_get_entry(l2p_entry_baton_t *baton,
const l2p_page_t *page,
const apr_uint64_t *offsets,
apr_pool_t *scratch_pool)
{
if (page->entry_count <= baton->page_offset)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("Item index %s"
" too large in revision %ld"),
apr_psprintf(scratch_pool, "%" APR_UINT64_T_FMT,
baton->item_index),
baton->revision);
baton->offset = offsets[baton->page_offset];
return SVN_NO_ERROR;
}
static svn_error_t *
l2p_entry_access_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
const l2p_page_t *page = data;
const apr_uint64_t *offsets
= svn_temp_deserializer__ptr(page, (const void *const *)&page->offsets);
return l2p_page_get_entry(baton, page, offsets, result_pool);
}
static svn_error_t *
l2p_index_lookup(apr_off_t *offset,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t revision,
apr_uint64_t item_index,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
l2p_page_info_baton_t info_baton;
l2p_entry_baton_t page_baton;
l2p_page_t *page = NULL;
svn_fs_fs__page_cache_key_t key = { 0 };
svn_boolean_t is_cached = FALSE;
void *dummy = NULL;
info_baton.revision = revision;
info_baton.item_index = item_index;
SVN_ERR(get_l2p_page_info(&info_baton, rev_file, fs, scratch_pool));
page_baton.revision = revision;
page_baton.item_index = item_index;
page_baton.page_offset = info_baton.page_offset;
assert(revision <= APR_UINT32_MAX);
key.revision = (apr_uint32_t)revision;
key.is_packed = svn_fs_fs__is_packed_rev(fs, revision);
key.page = info_baton.page_no;
SVN_ERR(svn_cache__get_partial(&dummy, &is_cached,
ffd->l2p_page_cache, &key,
l2p_entry_access_func, &page_baton,
scratch_pool));
if (!is_cached)
{
apr_array_header_t *pages;
svn_revnum_t prefetch_revision;
svn_revnum_t last_revision
= info_baton.first_revision
+ (key.is_packed ? ffd->max_files_per_dir : 1);
svn_boolean_t end;
apr_off_t max_offset
= APR_ALIGN(info_baton.entry.offset + info_baton.entry.size,
ffd->block_size);
apr_off_t min_offset = max_offset - ffd->block_size;
SVN_ERR(get_l2p_page(&page, rev_file, fs, info_baton.first_revision,
&info_baton.entry, scratch_pool));
SVN_ERR(svn_cache__set(ffd->l2p_page_cache, &key, page, scratch_pool));
SVN_ERR(l2p_page_get_entry(&page_baton, page, page->offsets,
scratch_pool));
if (ffd->use_block_read)
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
pages = apr_array_make(scratch_pool, 16,
sizeof(l2p_page_table_entry_t));
end = FALSE;
for (prefetch_revision = revision;
prefetch_revision < last_revision && !end;
++prefetch_revision)
{
int excluded_page_no = prefetch_revision == revision
? info_baton.page_no
: -1;
svn_pool_clear(iterpool);
SVN_ERR(prefetch_l2p_pages(&end, fs, rev_file,
info_baton.first_revision,
prefetch_revision, pages,
excluded_page_no, min_offset,
max_offset, iterpool));
}
end = FALSE;
for (prefetch_revision = revision-1;
prefetch_revision >= info_baton.first_revision && !end;
--prefetch_revision)
{
svn_pool_clear(iterpool);
SVN_ERR(prefetch_l2p_pages(&end, fs, rev_file,
info_baton.first_revision,
prefetch_revision, pages, -1,
min_offset, max_offset, iterpool));
}
svn_pool_destroy(iterpool);
}
}
*offset = page_baton.offset;
return SVN_NO_ERROR;
}
static svn_error_t *
l2p_proto_index_lookup(apr_off_t *offset,
svn_fs_t *fs,
const svn_fs_fs__id_part_t *txn_id,
apr_uint64_t item_index,
apr_pool_t *scratch_pool)
{
svn_boolean_t eof = FALSE;
apr_file_t *file = NULL;
SVN_ERR(svn_io_file_open(&file,
svn_fs_fs__path_l2p_proto_index(fs, txn_id,
scratch_pool),
APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
scratch_pool));
*offset = -1;
while (!eof)
{
l2p_proto_entry_t entry;
SVN_ERR(read_l2p_entry_from_proto_index(file, &entry, &eof,
scratch_pool));
if (!eof && entry.item_index == item_index)
{
*offset = (apr_off_t)entry.offset - 1;
break;
}
}
SVN_ERR(svn_io_file_close(file, scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
get_l2p_header(l2p_header_t **header,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t revision,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_boolean_t is_cached = FALSE;
pair_cache_key_t key;
key.revision = rev_file->start_revision;
key.second = rev_file->is_packed;
SVN_ERR(svn_cache__get((void**)header, &is_cached, ffd->l2p_header_cache,
&key, result_pool));
if (is_cached)
return SVN_NO_ERROR;
SVN_ERR(get_l2p_header_body(header, rev_file, fs, revision, result_pool,
scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__l2p_get_max_ids(apr_array_header_t **max_ids,
svn_fs_t *fs,
svn_revnum_t start_rev,
apr_size_t count,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
l2p_header_t *header = NULL;
svn_revnum_t revision;
svn_revnum_t last_rev = (svn_revnum_t)(start_rev + count);
svn_fs_fs__revision_file_t *rev_file;
apr_pool_t *header_pool = svn_pool_create(scratch_pool);
SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, start_rev,
header_pool, header_pool));
SVN_ERR(get_l2p_header(&header, rev_file, fs, start_rev, header_pool,
header_pool));
SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
*max_ids = apr_array_make(result_pool, (int)count, sizeof(apr_uint64_t));
for (revision = start_rev; revision < last_rev; ++revision)
{
apr_uint64_t full_page_count;
apr_uint64_t item_count;
apr_size_t first_page_index, last_page_index;
if (revision - header->first_revision >= header->revision_count)
{
svn_pool_clear(header_pool);
SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, revision,
header_pool, header_pool));
SVN_ERR(get_l2p_header(&header, rev_file, fs, revision,
header_pool, header_pool));
SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
}
first_page_index
= header->page_table_index[revision - header->first_revision];
last_page_index
= header->page_table_index[revision - header->first_revision + 1];
full_page_count = last_page_index - first_page_index - 1;
item_count = full_page_count * header->page_size
+ header->page_table[last_page_index - 1].entry_count;
APR_ARRAY_PUSH(*max_ids, apr_uint64_t) = item_count;
}
svn_pool_destroy(header_pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__item_offset(apr_off_t *absolute_position,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t revision,
const svn_fs_fs__id_part_t *txn_id,
apr_uint64_t item_index,
apr_pool_t *scratch_pool)
{
svn_error_t *err = SVN_NO_ERROR;
if (txn_id)
{
if (svn_fs_fs__use_log_addressing(fs))
{
SVN_ERR(l2p_proto_index_lookup(absolute_position, fs, txn_id,
item_index, scratch_pool));
}
else
{
*absolute_position = item_index;
}
}
else if (svn_fs_fs__use_log_addressing(fs))
{
SVN_ERR(l2p_index_lookup(absolute_position, fs, rev_file, revision,
item_index, scratch_pool));
}
else if (rev_file->is_packed)
{
apr_off_t rev_offset;
SVN_ERR(svn_fs_fs__get_packed_offset(&rev_offset, fs, revision,
scratch_pool));
*absolute_position = rev_offset + item_index;
}
else
{
*absolute_position = item_index;
}
return svn_error_trace(err);
}
svn_error_t *
svn_fs_fs__p2l_proto_index_open(apr_file_t **proto_index,
const char *file_name,
apr_pool_t *result_pool)
{
SVN_ERR(svn_io_file_open(proto_index, file_name, APR_READ | APR_WRITE
| APR_CREATE | APR_APPEND | APR_BUFFERED,
APR_OS_DEFAULT, result_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__p2l_proto_index_add_entry(apr_file_t *proto_index,
const svn_fs_fs__p2l_entry_t *entry,
apr_pool_t *scratch_pool)
{
apr_uint64_t revision;
SVN_ERR_ASSERT(entry->offset >= 0);
SVN_ERR_ASSERT(entry->size >= 0);
SVN_ERR_ASSERT( entry->item.revision >= 0
|| entry->item.revision == SVN_INVALID_REVNUM);
revision = entry->item.revision == SVN_INVALID_REVNUM
? 0
: ((apr_uint64_t)entry->item.revision + 1);
SVN_ERR(write_uint64_to_proto_index(proto_index, entry->offset,
scratch_pool));
SVN_ERR(write_uint64_to_proto_index(proto_index, entry->size,
scratch_pool));
SVN_ERR(write_uint64_to_proto_index(proto_index, entry->type,
scratch_pool));
SVN_ERR(write_uint64_to_proto_index(proto_index, entry->fnv1_checksum,
scratch_pool));
SVN_ERR(write_uint64_to_proto_index(proto_index, revision,
scratch_pool));
SVN_ERR(write_uint64_to_proto_index(proto_index, entry->item.number,
scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
read_p2l_entry_from_proto_index(apr_file_t *proto_index,
svn_fs_fs__p2l_entry_t *entry,
svn_boolean_t *eof,
apr_pool_t *scratch_pool)
{
apr_uint64_t revision;
SVN_ERR(read_off_t_from_proto_index(proto_index, &entry->offset,
eof, scratch_pool));
SVN_ERR(read_off_t_from_proto_index(proto_index, &entry->size,
eof, scratch_pool));
SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->type,
eof, scratch_pool));
SVN_ERR(read_uint32_from_proto_index(proto_index, &entry->fnv1_checksum,
eof, scratch_pool));
SVN_ERR(read_uint64_from_proto_index(proto_index, &revision,
eof, scratch_pool));
SVN_ERR(read_uint64_from_proto_index(proto_index, &entry->item.number,
eof, scratch_pool));
if (!eof || !*eof)
{
if (revision > 0 && revision - 1 > LONG_MAX)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW, NULL,
_("Revision 0x%s too large, max = 0x%s"),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_HEX_FMT,
revision),
apr_psprintf(scratch_pool,
"%" APR_UINT64_T_HEX_FMT,
(apr_uint64_t)LONG_MAX));
entry->item.revision = revision == 0
? SVN_INVALID_REVNUM
: (long)(revision - 1);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__p2l_proto_index_next_offset(apr_off_t *next_offset,
apr_file_t *proto_index,
apr_pool_t *scratch_pool)
{
apr_off_t offset = 0;
SVN_ERR(svn_io_file_seek(proto_index, APR_END, &offset, scratch_pool));
if (offset == 0)
{
*next_offset = 0;
}
else
{
svn_fs_fs__p2l_entry_t entry;
offset -= P2L_PROTO_INDEX_ENTRY_SIZE;
SVN_ERR(svn_io_file_seek(proto_index, APR_SET, &offset, scratch_pool));
SVN_ERR(read_p2l_entry_from_proto_index(proto_index, &entry,
NULL, scratch_pool));
*next_offset = entry.offset + entry.size;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__p2l_index_append(svn_checksum_t **checksum,
svn_fs_t *fs,
apr_file_t *index_file,
const char *proto_file_name,
svn_revnum_t revision,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
apr_uint64_t page_size = ffd->p2l_page_size;
apr_file_t *proto_index = NULL;
svn_stream_t *stream;
int i;
svn_boolean_t eof = FALSE;
unsigned char encoded[ENCODED_INT_LENGTH];
svn_revnum_t last_revision = revision;
apr_uint64_t last_compound = 0;
apr_uint64_t last_entry_end = 0;
apr_uint64_t last_page_end = 0;
apr_uint64_t last_buffer_size = 0;
apr_uint64_t file_size = 0;
apr_pool_t *local_pool = svn_pool_create(scratch_pool);
apr_array_header_t *table_sizes
= apr_array_make(local_pool, 16, sizeof(apr_uint64_t));
svn_spillbuf_t *buffer
= svn_spillbuf__create(0x10000, 0x1000000, local_pool);
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
SVN_ERR(svn_io_file_open(&proto_index, proto_file_name,
APR_READ | APR_CREATE | APR_BUFFERED,
APR_OS_DEFAULT, scratch_pool));
while (!eof)
{
svn_fs_fs__p2l_entry_t entry;
apr_uint64_t entry_end;
svn_boolean_t new_page = svn_spillbuf__get_size(buffer) == 0;
apr_uint64_t compound;
apr_int64_t rev_diff, compound_diff;
svn_pool_clear(iterpool);
SVN_ERR(read_p2l_entry_from_proto_index(proto_index, &entry,
&eof, iterpool));
if (eof)
{
file_size = last_entry_end;
entry.offset = last_entry_end;
entry.size = APR_ALIGN(entry.offset, page_size) - entry.offset;
entry.type = SVN_FS_FS__ITEM_TYPE_UNUSED;
entry.fnv1_checksum = 0;
entry.item.revision = last_revision;
entry.item.number = 0;
}
else
{
if (entry.item.revision == SVN_INVALID_REVNUM)
entry.item.revision = revision;
}
entry_end = entry.offset + entry.size;
while (entry_end - last_page_end > page_size)
{
apr_uint64_t buffer_size = svn_spillbuf__get_size(buffer);
APR_ARRAY_PUSH(table_sizes, apr_uint64_t)
= buffer_size - last_buffer_size;
last_buffer_size = buffer_size;
last_page_end += page_size;
new_page = TRUE;
}
if (new_page)
{
SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
encode_uint(encoded, entry.offset),
iterpool));
last_revision = revision;
last_compound = 0;
}
SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
encode_uint(encoded, entry.size),
iterpool));
rev_diff = entry.item.revision - last_revision;
last_revision = entry.item.revision;
compound = entry.item.number * 8 + entry.type;
compound_diff = compound - last_compound;
last_compound = compound;
SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
encode_int(encoded, compound_diff),
iterpool));
SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
encode_int(encoded, rev_diff),
iterpool));
SVN_ERR(svn_spillbuf__write(buffer, (const char *)encoded,
encode_uint(encoded, entry.fnv1_checksum),
iterpool));
last_entry_end = entry_end;
}
SVN_ERR(svn_io_file_close(proto_index, local_pool));
APR_ARRAY_PUSH(table_sizes, apr_uint64_t)
= svn_spillbuf__get_size(buffer) - last_buffer_size;
stream = svn_stream_checksummed2(svn_stream_from_aprfile2(index_file, TRUE,
local_pool),
NULL, checksum, svn_checksum_md5, FALSE,
result_pool);
SVN_ERR(svn_stream_puts(stream, P2L_STREAM_PREFIX));
SVN_ERR(stream_write_encoded(stream, revision));
SVN_ERR(stream_write_encoded(stream, file_size));
SVN_ERR(stream_write_encoded(stream, page_size));
SVN_ERR(stream_write_encoded(stream, table_sizes->nelts));
for (i = 0; i < table_sizes->nelts; ++i)
{
apr_uint64_t value = APR_ARRAY_IDX(table_sizes, i, apr_uint64_t);
SVN_ERR(stream_write_encoded(stream, value));
}
SVN_ERR(svn_stream_copy3(svn_stream__from_spillbuf(buffer, local_pool),
stream, NULL, NULL, local_pool));
svn_pool_destroy(iterpool);
svn_pool_destroy(local_pool);
return SVN_NO_ERROR;
}
static svn_error_t *
auto_open_p2l_index(svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t revision)
{
if (rev_file->p2l_stream == NULL)
{
fs_fs_data_t *ffd = fs->fsap_data;
SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
SVN_ERR(packed_stream_open(&rev_file->p2l_stream,
rev_file->file,
rev_file->p2l_offset,
rev_file->footer_offset,
P2L_STREAM_PREFIX,
(apr_size_t)ffd->block_size,
rev_file->pool,
rev_file->pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
get_p2l_header(p2l_header_t **header,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t revision,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
apr_uint64_t value;
apr_size_t i;
apr_off_t offset;
p2l_header_t *result;
svn_boolean_t is_cached = FALSE;
pair_cache_key_t key;
key.revision = rev_file->start_revision;
key.second = rev_file->is_packed;
SVN_ERR(svn_cache__get((void**)header, &is_cached, ffd->p2l_header_cache,
&key, result_pool));
if (is_cached)
return SVN_NO_ERROR;
if (rev_file->p2l_stream == NULL)
SVN_ERR(auto_open_p2l_index(rev_file, fs, rev_file->start_revision));
else
packed_stream_seek(rev_file->p2l_stream, 0);
result = apr_pcalloc(result_pool, sizeof(*result));
SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
result->first_revision = (svn_revnum_t)value;
if (result->first_revision != rev_file->start_revision)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Index rev / pack file revision numbers do not match"));
SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
result->file_size = value;
if (result->file_size != (apr_uint64_t)rev_file->l2p_offset)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Index offset and rev / pack file size do not match"));
SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
result->page_size = value;
if (!result->page_size || (result->page_size & (result->page_size - 1)))
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("P2L index page size is not a power of two"));
SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
result->page_count = (apr_size_t)value;
if (result->page_count != (result->file_size - 1) / result->page_size + 1)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("P2L page count does not match rev / pack file size"));
result->offsets
= apr_pcalloc(result_pool, (result->page_count + 1) * sizeof(*result->offsets));
result->offsets[0] = 0;
for (i = 0; i < result->page_count; ++i)
{
SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
result->offsets[i+1] = result->offsets[i] + (apr_off_t)value;
}
offset = packed_stream_offset(rev_file->p2l_stream);
for (i = 0; i <= result->page_count; ++i)
result->offsets[i] += offset;
SVN_ERR(svn_cache__set(ffd->p2l_header_cache, &key, result, scratch_pool));
*header = result;
return SVN_NO_ERROR;
}
typedef struct p2l_page_info_baton_t
{
svn_revnum_t revision;
apr_off_t offset;
apr_size_t page_no;
svn_revnum_t first_revision;
apr_off_t start_offset;
apr_off_t next_offset;
apr_off_t page_start;
apr_size_t page_count;
apr_uint64_t page_size;
} p2l_page_info_baton_t;
static void
p2l_page_info_copy(p2l_page_info_baton_t *baton,
const p2l_header_t *header,
const apr_off_t *offsets)
{
if (baton->offset / header->page_size < header->page_count)
{
baton->page_no = (apr_size_t)(baton->offset / header->page_size);
baton->start_offset = offsets[baton->page_no];
baton->next_offset = offsets[baton->page_no + 1];
baton->page_size = header->page_size;
}
else
{
baton->page_no = header->page_count;
baton->start_offset = offsets[baton->page_no];
baton->next_offset = offsets[baton->page_no];
baton->page_size = 0;
}
baton->first_revision = header->first_revision;
baton->page_start = (apr_off_t)(header->page_size * baton->page_no);
baton->page_count = header->page_count;
}
static svn_error_t *
p2l_page_info_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
const p2l_header_t *header = data;
const apr_off_t *offsets
= svn_temp_deserializer__ptr(header,
(const void *const *)&header->offsets);
p2l_page_info_copy(baton, header, offsets);
return SVN_NO_ERROR;
}
static svn_error_t *
get_p2l_page_info(p2l_page_info_baton_t *baton,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
p2l_header_t *header;
svn_boolean_t is_cached = FALSE;
void *dummy = NULL;
pair_cache_key_t key;
key.revision = rev_file->start_revision;
key.second = rev_file->is_packed;
SVN_ERR(svn_cache__get_partial(&dummy, &is_cached, ffd->p2l_header_cache,
&key, p2l_page_info_func, baton,
scratch_pool));
if (is_cached)
return SVN_NO_ERROR;
SVN_ERR(get_p2l_header(&header, rev_file, fs, baton->revision,
scratch_pool, scratch_pool));
p2l_page_info_copy(baton, header, header->offsets);
return SVN_NO_ERROR;
}
static svn_error_t *
read_entry(svn_fs_fs__packed_number_stream_t *stream,
apr_off_t *item_offset,
svn_revnum_t *last_revision,
apr_uint64_t *last_compound,
apr_array_header_t *result)
{
apr_uint64_t value;
svn_fs_fs__p2l_entry_t entry;
entry.offset = *item_offset;
SVN_ERR(packed_stream_get(&value, stream));
entry.size = (apr_off_t)value;
SVN_ERR(packed_stream_get(&value, stream));
*last_compound += decode_int(value);
entry.type = *last_compound & 7;
entry.item.number = *last_compound / 8;
if (entry.type > SVN_FS_FS__ITEM_TYPE_CHANGES)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Invalid item type in P2L index"));
if ( entry.type == SVN_FS_FS__ITEM_TYPE_CHANGES
&& entry.item.number != SVN_FS_FS__ITEM_INDEX_CHANGES)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Changed path list must have item number 1"));
SVN_ERR(packed_stream_get(&value, stream));
*last_revision += (svn_revnum_t)decode_int(value);
entry.item.revision = *last_revision;
SVN_ERR(packed_stream_get(&value, stream));
entry.fnv1_checksum = (apr_uint32_t)value;
if (value > APR_UINT32_MAX)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Invalid FNV1 checksum in P2L index"));
if (entry.type == SVN_FS_FS__ITEM_TYPE_UNUSED)
if ( entry.item.number != SVN_FS_FS__ITEM_INDEX_UNUSED
|| entry.fnv1_checksum != 0)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Empty regions must have item number 0 and checksum 0"));
if ((apr_uint64_t)entry.offset + (apr_uint64_t)entry.size > off_t_max)
return svn_error_create(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("P2L index entry size overflow."));
APR_ARRAY_PUSH(result, svn_fs_fs__p2l_entry_t) = entry;
*item_offset += entry.size;
return SVN_NO_ERROR;
}
static svn_error_t *
get_p2l_page(apr_array_header_t **entries,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t start_revision,
apr_off_t start_offset,
apr_off_t next_offset,
apr_off_t page_start,
apr_uint64_t page_size,
apr_pool_t *result_pool)
{
apr_uint64_t value;
apr_array_header_t *result
= apr_array_make(result_pool, 16, sizeof(svn_fs_fs__p2l_entry_t));
apr_off_t item_offset;
apr_off_t offset;
svn_revnum_t last_revision;
apr_uint64_t last_compound;
SVN_ERR(auto_open_p2l_index(rev_file, fs, start_revision));
packed_stream_seek(rev_file->p2l_stream, start_offset);
SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
item_offset = (apr_off_t)value;
last_revision = start_revision;
last_compound = 0;
if (start_offset == next_offset)
{
SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
&last_revision, &last_compound, result));
}
else
{
do
{
SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
&last_revision, &last_compound, result));
offset = packed_stream_offset(rev_file->p2l_stream);
}
while (offset < next_offset);
if (offset != next_offset)
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("P2L page description overlaps with next page description"));
if (item_offset < page_start + page_size)
{
SVN_ERR(packed_stream_get(&value, rev_file->p2l_stream));
item_offset = (apr_off_t)value;
last_revision = start_revision;
last_compound = 0;
SVN_ERR(read_entry(rev_file->p2l_stream, &item_offset,
&last_revision, &last_compound, result));
}
}
*entries = result;
return SVN_NO_ERROR;
}
static svn_error_t *
prefetch_p2l_page(svn_boolean_t *end,
int *leaking_bucket,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
p2l_page_info_baton_t *baton,
apr_off_t min_offset,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_boolean_t already_cached;
apr_array_header_t *page;
svn_fs_fs__page_cache_key_t key = { 0 };
*end = FALSE;
baton->revision = baton->first_revision;
SVN_ERR(get_p2l_page_info(baton, rev_file, fs, scratch_pool));
if (baton->start_offset < min_offset || !rev_file->p2l_stream)
{
*end = TRUE;
return SVN_NO_ERROR;
}
assert(baton->first_revision <= APR_UINT32_MAX);
key.revision = (apr_uint32_t)baton->first_revision;
key.is_packed = svn_fs_fs__is_packed_rev(fs, baton->first_revision);
key.page = baton->page_no;
SVN_ERR(svn_cache__has_key(&already_cached, ffd->p2l_page_cache,
&key, scratch_pool));
if (already_cached)
{
if (!--*leaking_bucket)
*end = TRUE;
return SVN_NO_ERROR;
}
++*leaking_bucket;
SVN_ERR(get_p2l_page(&page, rev_file, fs,
baton->first_revision,
baton->start_offset,
baton->next_offset,
baton->page_start,
baton->page_size,
scratch_pool));
SVN_ERR(svn_cache__set(ffd->p2l_page_cache, &key, page, scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
get_p2l_keys(p2l_page_info_baton_t *page_info_p,
svn_fs_fs__page_cache_key_t *key_p,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t revision,
apr_off_t offset,
apr_pool_t *scratch_pool)
{
p2l_page_info_baton_t page_info;
page_info.offset = offset;
page_info.revision = revision;
SVN_ERR(get_p2l_page_info(&page_info, rev_file, fs, scratch_pool));
if (page_info.page_count <= page_info.page_no)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("Offset %s too large in revision %ld"),
apr_off_t_toa(scratch_pool, offset), revision);
if (page_info_p)
*page_info_p = page_info;
if (key_p)
{
svn_fs_fs__page_cache_key_t key = { 0 };
assert(page_info.first_revision <= APR_UINT32_MAX);
key.revision = (apr_uint32_t)page_info.first_revision;
key.is_packed = rev_file->is_packed;
key.page = page_info.page_no;
*key_p = key;
}
return SVN_NO_ERROR;
}
static int
compare_start_p2l_entry(const void *lhs,
const void *rhs)
{
const svn_fs_fs__p2l_entry_t *entry = lhs;
apr_off_t start = *(const apr_off_t*)rhs;
apr_off_t diff = entry->offset - start;
return diff < 0 ? -1 : (diff == 0 ? 0 : 1);
}
static void
append_p2l_entries(apr_array_header_t *entries,
apr_array_header_t *page_entries,
apr_off_t block_start,
apr_off_t block_end)
{
const svn_fs_fs__p2l_entry_t *entry;
int idx = svn_sort__bsearch_lower_bound(page_entries, &block_start,
compare_start_p2l_entry);
if (idx > 0)
{
entry = &APR_ARRAY_IDX(page_entries, idx - 1, svn_fs_fs__p2l_entry_t);
if (entry->offset + entry->size > block_start)
--idx;
}
for ( ; idx < page_entries->nelts; ++idx)
{
entry = &APR_ARRAY_IDX(page_entries, idx, svn_fs_fs__p2l_entry_t);
if (entry->offset >= block_end)
break;
APR_ARRAY_PUSH(entries, svn_fs_fs__p2l_entry_t) = *entry;
}
}
typedef struct p2l_entries_baton_t
{
apr_off_t start;
apr_off_t end;
} p2l_entries_baton_t;
static svn_error_t *
p2l_entries_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
apr_array_header_t *entries = *(apr_array_header_t **)out;
const apr_array_header_t *raw_page = data;
p2l_entries_baton_t *block = baton;
apr_array_header_t page = *raw_page;
page.elts = (void *)svn_temp_deserializer__ptr(raw_page,
(const void * const *)&raw_page->elts);
append_p2l_entries(entries, &page, block->start, block->end);
return SVN_NO_ERROR;
}
static svn_error_t *
p2l_index_lookup(apr_array_header_t *entries,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_t *fs,
svn_revnum_t revision,
apr_off_t block_start,
apr_off_t block_end,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_fs_fs__page_cache_key_t key;
svn_boolean_t is_cached = FALSE;
p2l_page_info_baton_t page_info;
apr_array_header_t *local_result = entries;
p2l_entries_baton_t block;
block.start = block_start;
block.end = block_end;
SVN_ERR_ASSERT(block_start < block_end);
SVN_ERR(get_p2l_keys(&page_info, &key, rev_file, fs, revision, block_start,
scratch_pool));
SVN_ERR(svn_cache__get_partial((void**)&local_result, &is_cached,
ffd->p2l_page_cache, &key, p2l_entries_func,
&block, scratch_pool));
if (!is_cached)
{
svn_boolean_t end;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_off_t original_page_start = page_info.page_start;
int leaking_bucket = 4;
p2l_page_info_baton_t prefetch_info = page_info;
apr_array_header_t *page_entries;
apr_off_t max_offset
= APR_ALIGN(page_info.next_offset, ffd->block_size);
apr_off_t min_offset
= APR_ALIGN(page_info.start_offset, ffd->block_size) - ffd->block_size;
if (ffd->use_block_read)
{
end = FALSE;
prefetch_info.offset = original_page_start;
while (prefetch_info.offset >= prefetch_info.page_size && !end)
{
svn_pool_clear(iterpool);
prefetch_info.offset -= prefetch_info.page_size;
SVN_ERR(prefetch_p2l_page(&end, &leaking_bucket, fs, rev_file,
&prefetch_info, min_offset,
iterpool));
}
}
SVN_ERR(get_p2l_page(&page_entries, rev_file, fs,
page_info.first_revision,
page_info.start_offset,
page_info.next_offset,
page_info.page_start,
page_info.page_size, iterpool));
if (page_entries->nelts)
{
const svn_fs_fs__p2l_entry_t *entry
= &APR_ARRAY_IDX(page_entries, page_entries->nelts - 1,
svn_fs_fs__p2l_entry_t);
if ( entry->offset + entry->size
> page_info.page_size * page_info.page_count)
return svn_error_createf(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
_("Last P2L index entry extends beyond "
"the last page in revision %ld."),
revision);
}
SVN_ERR(svn_cache__set(ffd->p2l_page_cache, &key, page_entries,
iterpool));
append_p2l_entries(entries, page_entries, block_start, block_end);
if (ffd->use_block_read)
{
end = FALSE;
leaking_bucket = 4;
prefetch_info = page_info;
prefetch_info.offset = original_page_start;
while ( prefetch_info.next_offset < max_offset
&& prefetch_info.page_no + 1 < prefetch_info.page_count
&& !end)
{
svn_pool_clear(iterpool);
prefetch_info.offset += prefetch_info.page_size;
SVN_ERR(prefetch_p2l_page(&end, &leaking_bucket, fs, rev_file,
&prefetch_info, min_offset,
iterpool));
}
}
svn_pool_destroy(iterpool);
}
SVN_ERR_ASSERT(entries->nelts > 0);
if (page_info.page_no + 1 >= page_info.page_count)
{
svn_fs_fs__p2l_entry_t *entry
= &APR_ARRAY_IDX(entries, entries->nelts-1, svn_fs_fs__p2l_entry_t);
apr_off_t entry_end = entry->offset + entry->size;
if (entry_end < block_end)
{
if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
{
entry->size = block_end - entry->offset;
}
else
{
entry = apr_array_push(entries);
entry->offset = entry_end;
entry->size = block_end - entry_end;
entry->type = SVN_FS_FS__ITEM_TYPE_UNUSED;
entry->fnv1_checksum = 0;
entry->item.revision = SVN_INVALID_REVNUM;
entry->item.number = SVN_FS_FS__ITEM_INDEX_UNUSED;
}
}
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__p2l_index_lookup(apr_array_header_t **entries,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t revision,
apr_off_t block_start,
apr_off_t block_size,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_off_t block_end = block_start + block_size;
int last_count = 0;
apr_array_header_t *result = apr_array_make(result_pool, 16,
sizeof(svn_fs_fs__p2l_entry_t));
while (block_start < block_end)
{
svn_fs_fs__p2l_entry_t *entry;
SVN_ERR(p2l_index_lookup(result, rev_file, fs, revision, block_start,
block_end, scratch_pool));
SVN_ERR_ASSERT(result->nelts > 0);
entry = &APR_ARRAY_IDX(result, result->nelts-1, svn_fs_fs__p2l_entry_t);
block_start = entry->offset + entry->size;
if (last_count > 0 && last_count < result->nelts)
{
entry = &APR_ARRAY_IDX(result, last_count - 1,
svn_fs_fs__p2l_entry_t);
SVN_ERR_ASSERT(APR_ARRAY_IDX(result, last_count,
svn_fs_fs__p2l_entry_t).offset
>= entry->offset + entry->size);
}
last_count = result->nelts;
}
*entries = result;
return SVN_NO_ERROR;
}
static int
compare_p2l_entry_offsets(const void *lhs, const void *rhs)
{
const svn_fs_fs__p2l_entry_t *entry = (const svn_fs_fs__p2l_entry_t *)lhs;
apr_off_t offset = *(const apr_off_t *)rhs;
return entry->offset < offset ? -1 : (entry->offset == offset ? 0 : 1);
}
static svn_fs_fs__p2l_entry_t *
get_p2l_entry_from_cached_page(const void *data,
apr_uint64_t offset,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const apr_array_header_t *page = data;
apr_array_header_t *entries = apr_pmemdup(scratch_pool, page,
sizeof(*page));
svn_fs_fs__p2l_entry_t *entry;
entries->elts = (char *)svn_temp_deserializer__ptr(page,
(const void *const *)&page->elts);
entry = svn_sort__array_lookup(entries, &offset, NULL,
(int (*)(const void *, const void *))compare_p2l_entry_offsets);
return entry ? apr_pmemdup(result_pool, entry, sizeof(*entry)) : NULL;
}
static svn_error_t *
p2l_entry_lookup_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
svn_fs_fs__p2l_entry_t *entry
= get_p2l_entry_from_cached_page(data, *(apr_off_t *)baton, result_pool,
result_pool);
*out = entry && entry->offset == *(apr_off_t *)baton
? apr_pmemdup(result_pool, entry, sizeof(*entry))
: NULL;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__p2l_entry_lookup(svn_fs_fs__p2l_entry_t **entry_p,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t revision,
apr_off_t offset,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_fs_fs__page_cache_key_t key = { 0 };
svn_boolean_t is_cached = FALSE;
p2l_page_info_baton_t page_info;
*entry_p = NULL;
SVN_ERR(get_p2l_keys(&page_info, &key, rev_file, fs, revision, offset,
scratch_pool));
SVN_ERR(svn_cache__get_partial((void**)entry_p, &is_cached,
ffd->p2l_page_cache, &key,
p2l_entry_lookup_func, &offset,
result_pool));
if (!is_cached)
{
apr_array_header_t *entries = apr_array_make(result_pool, 1,
sizeof(**entry_p));
SVN_ERR(p2l_index_lookup(entries, rev_file, fs, revision, offset,
offset + 1, scratch_pool));
*entry_p = svn_sort__array_lookup(entries, &offset, NULL,
(int (*)(const void *, const void *))compare_p2l_entry_offsets);
}
return SVN_NO_ERROR;
}
static svn_error_t *
p2l_get_max_offset_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
const p2l_header_t *header = data;
apr_off_t max_offset = header->file_size;
*out = apr_pmemdup(result_pool, &max_offset, sizeof(max_offset));
return SVN_NO_ERROR;
}
static svn_error_t *
p2l_get_max_offset(apr_off_t *offset,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t revision,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
p2l_header_t *header;
svn_boolean_t is_cached = FALSE;
apr_off_t *offset_p;
pair_cache_key_t key;
key.revision = rev_file->start_revision;
key.second = rev_file->is_packed;
SVN_ERR(svn_cache__get_partial((void **)&offset_p, &is_cached,
ffd->p2l_header_cache, &key,
p2l_get_max_offset_func, NULL,
scratch_pool));
if (is_cached)
{
*offset = *offset_p;
return SVN_NO_ERROR;
}
SVN_ERR(get_p2l_header(&header, rev_file, fs, revision, scratch_pool,
scratch_pool));
*offset = header->file_size;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__p2l_get_max_offset(apr_off_t *offset,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_revnum_t revision,
apr_pool_t *scratch_pool)
{
return svn_error_trace(p2l_get_max_offset(offset, fs, rev_file, revision,
scratch_pool));
}
static svn_error_t *
calc_fnv1(svn_fs_fs__p2l_entry_t *entry,
svn_fs_fs__revision_file_t *rev_file,
apr_pool_t *scratch_pool)
{
unsigned char buffer[4096];
svn_checksum_t *checksum;
svn_checksum_ctx_t *context
= svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, scratch_pool);
apr_off_t size = entry->size;
if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
{
entry->fnv1_checksum = 0;
return SVN_NO_ERROR;
}
SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &entry->offset,
scratch_pool));
while (size > 0)
{
apr_size_t to_read = size > sizeof(buffer)
? sizeof(buffer)
: (apr_size_t)size;
SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, to_read, NULL,
NULL, scratch_pool));
SVN_ERR(svn_checksum_update(context, buffer, to_read));
size -= to_read;
}
SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
entry->fnv1_checksum = ntohl(*(const apr_uint32_t *)checksum->digest);
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__p2l_index_from_p2l_entries(const char **protoname,
svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
apr_array_header_t *entries,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_file_t *proto_index;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
SVN_ERR(svn_io_open_unique_file3(NULL, protoname, NULL,
svn_io_file_del_on_pool_cleanup,
result_pool, scratch_pool));
SVN_ERR(svn_fs_fs__p2l_proto_index_open(&proto_index, *protoname,
scratch_pool));
for (i = 0; i < entries->nelts; ++i)
{
svn_fs_fs__p2l_entry_t *entry
= APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t *);
svn_pool_clear(iterpool);
SVN_ERR(calc_fnv1(entry, rev_file, iterpool));
SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(proto_index, entry,
iterpool));
}
SVN_ERR(svn_io_file_close(proto_index, iterpool));
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static int
compare_p2l_entry_revision(const void *lhs,
const void *rhs)
{
const svn_fs_fs__p2l_entry_t *lhs_entry
=*(const svn_fs_fs__p2l_entry_t **)lhs;
const svn_fs_fs__p2l_entry_t *rhs_entry
=*(const svn_fs_fs__p2l_entry_t **)rhs;
if (lhs_entry->item.revision < rhs_entry->item.revision)
return -1;
return lhs_entry->item.revision == rhs_entry->item.revision ? 0 : 1;
}
svn_error_t *
svn_fs_fs__l2p_index_from_p2l_entries(const char **protoname,
svn_fs_t *fs,
apr_array_header_t *entries,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_file_t *proto_index;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
svn_revnum_t last_revision = SVN_INVALID_REVNUM;
svn_sort__array(entries, compare_p2l_entry_revision);
SVN_ERR(svn_io_open_unique_file3(NULL, protoname, NULL,
svn_io_file_del_on_pool_cleanup,
result_pool, scratch_pool));
SVN_ERR(svn_fs_fs__l2p_proto_index_open(&proto_index, *protoname,
scratch_pool));
for (i = 0; i < entries->nelts; ++i)
{
const svn_fs_fs__p2l_entry_t *entry
= APR_ARRAY_IDX(entries, i, const svn_fs_fs__p2l_entry_t *);
svn_pool_clear(iterpool);
if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
continue;
if (last_revision != entry->item.revision)
{
SVN_ERR(svn_fs_fs__l2p_proto_index_add_revision(proto_index,
scratch_pool));
last_revision = entry->item.revision;
}
SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(proto_index,
entry->offset,
entry->item.number,
iterpool));
}
SVN_ERR(svn_io_file_close(proto_index, iterpool));
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__serialize_l2p_header(void **data,
apr_size_t *data_len,
void *in,
apr_pool_t *pool)
{
l2p_header_t *header = in;
svn_temp_serializer__context_t *context;
svn_stringbuf_t *serialized;
apr_size_t page_count = header->page_table_index[header->revision_count];
apr_size_t page_table_size = page_count * sizeof(*header->page_table);
apr_size_t index_size
= (header->revision_count + 1) * sizeof(*header->page_table_index);
apr_size_t data_size = sizeof(*header) + index_size + page_table_size;
context = svn_temp_serializer__init(header,
sizeof(*header),
data_size + 32,
pool);
svn_temp_serializer__add_leaf(context,
(const void * const *)&header->page_table_index,
index_size);
svn_temp_serializer__add_leaf(context,
(const void * const *)&header->page_table,
page_table_size);
serialized = svn_temp_serializer__get(context);
*data = serialized->data;
*data_len = serialized->len;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__deserialize_l2p_header(void **out,
void *data,
apr_size_t data_len,
apr_pool_t *pool)
{
l2p_header_t *header = (l2p_header_t *)data;
svn_temp_deserializer__resolve(header, (void**)&header->page_table_index);
svn_temp_deserializer__resolve(header, (void**)&header->page_table);
*out = header;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__serialize_l2p_page(void **data,
apr_size_t *data_len,
void *in,
apr_pool_t *pool)
{
l2p_page_t *page = in;
svn_temp_serializer__context_t *context;
svn_stringbuf_t *serialized;
apr_size_t of_table_size = page->entry_count * sizeof(*page->offsets);
context = svn_temp_serializer__init(page,
sizeof(*page),
of_table_size + sizeof(*page) + 32,
pool);
svn_temp_serializer__add_leaf(context,
(const void * const *)&page->offsets,
of_table_size);
serialized = svn_temp_serializer__get(context);
*data = serialized->data;
*data_len = serialized->len;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__deserialize_l2p_page(void **out,
void *data,
apr_size_t data_len,
apr_pool_t *pool)
{
l2p_page_t *page = data;
svn_temp_deserializer__resolve(page, (void**)&page->offsets);
*out = page;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__serialize_p2l_header(void **data,
apr_size_t *data_len,
void *in,
apr_pool_t *pool)
{
p2l_header_t *header = in;
svn_temp_serializer__context_t *context;
svn_stringbuf_t *serialized;
apr_size_t table_size = (header->page_count + 1) * sizeof(*header->offsets);
context = svn_temp_serializer__init(header,
sizeof(*header),
table_size + sizeof(*header) + 32,
pool);
svn_temp_serializer__add_leaf(context,
(const void * const *)&header->offsets,
table_size);
serialized = svn_temp_serializer__get(context);
*data = serialized->data;
*data_len = serialized->len;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__deserialize_p2l_header(void **out,
void *data,
apr_size_t data_len,
apr_pool_t *pool)
{
p2l_header_t *header = data;
svn_temp_deserializer__resolve(header, (void**)&header->offsets);
*out = header;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__serialize_p2l_page(void **data,
apr_size_t *data_len,
void *in,
apr_pool_t *pool)
{
apr_array_header_t *page = in;
svn_temp_serializer__context_t *context;
svn_stringbuf_t *serialized;
apr_size_t table_size = page->elt_size * page->nelts;
context = svn_temp_serializer__init(page,
sizeof(*page),
table_size + sizeof(*page) + 32,
pool);
svn_temp_serializer__add_leaf(context,
(const void * const *)&page->elts,
table_size);
serialized = svn_temp_serializer__get(context);
*data = serialized->data;
*data_len = serialized->len;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__deserialize_p2l_page(void **out,
void *data,
apr_size_t data_len,
apr_pool_t *pool)
{
apr_array_header_t *page = (apr_array_header_t *)data;
svn_temp_deserializer__resolve(page, (void**)&page->elts);
page->pool = pool;
page->nalloc = page->nelts;
*out = page;
return SVN_NO_ERROR;
}