#include "recovery.h"
#include "svn_hash.h"
#include "svn_pools.h"
#include "private/svn_string_private.h"
#include "index.h"
#include "low_level.h"
#include "rep-cache.h"
#include "revprops.h"
#include "util.h"
#include "cached_data.h"
#include "../libsvn_fs/fs-loader.h"
#include "svn_private_config.h"
static svn_error_t *
recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
{
apr_pool_t *iterpool;
svn_revnum_t left, right = 1;
iterpool = svn_pool_create(pool);
while (1)
{
svn_error_t *err;
svn_fs_fs__revision_file_t *file;
svn_pool_clear(iterpool);
err = svn_fs_fs__open_pack_or_rev_file(&file, fs, right, iterpool,
iterpool);
if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
{
svn_error_clear(err);
break;
}
else
SVN_ERR(err);
right <<= 1;
}
left = right >> 1;
while (left + 1 < right)
{
svn_revnum_t probe = left + ((right - left) / 2);
svn_error_t *err;
svn_fs_fs__revision_file_t *file;
svn_pool_clear(iterpool);
err = svn_fs_fs__open_pack_or_rev_file(&file, fs, probe, iterpool,
iterpool);
if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
{
svn_error_clear(err);
right = probe;
}
else
{
SVN_ERR(err);
left = probe;
}
}
svn_pool_destroy(iterpool);
*rev = left;
return SVN_NO_ERROR;
}
struct recover_read_from_file_baton
{
svn_stream_t *stream;
apr_pool_t *pool;
apr_off_t remaining;
};
static svn_error_t *
read_handler_recover(void *baton, char *buffer, apr_size_t *len)
{
struct recover_read_from_file_baton *b = baton;
apr_size_t bytes_to_read = *len;
if (b->remaining == 0)
{
*len = 0;
return SVN_NO_ERROR;
}
if ((apr_int64_t)bytes_to_read > (apr_int64_t)b->remaining)
bytes_to_read = (apr_size_t)b->remaining;
b->remaining -= bytes_to_read;
return svn_stream_read_full(b->stream, buffer, &bytes_to_read);
}
static svn_error_t *
recover_find_max_ids(svn_fs_t *fs,
svn_revnum_t rev,
svn_fs_fs__revision_file_t *rev_file,
apr_off_t offset,
apr_uint64_t *max_node_id,
apr_uint64_t *max_copy_id,
apr_pool_t *pool)
{
svn_fs_fs__rep_header_t *header;
struct recover_read_from_file_baton baton;
svn_stream_t *stream;
apr_hash_t *entries;
apr_hash_index_t *hi;
apr_pool_t *iterpool;
node_revision_t *noderev;
svn_error_t *err;
baton.stream = rev_file->stream;
SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
SVN_ERR(svn_fs_fs__read_noderev(&noderev, baton.stream, pool, pool));
if (noderev->kind != svn_node_dir)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Recovery encountered a non-directory node"));
if (!noderev->data_rep)
return SVN_NO_ERROR;
if (noderev->data_rep->revision != rev)
return SVN_NO_ERROR;
SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL,
noderev->data_rep->item_index, pool));
SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
SVN_ERR(svn_fs_fs__read_rep_header(&header, baton.stream, pool, pool));
if (header->type != svn_fs_fs__rep_plain)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Recovery encountered a deltified directory "
"representation"));
baton.pool = pool;
baton.remaining = noderev->data_rep->expanded_size;
stream = svn_stream_create(&baton, pool);
svn_stream_set_read2(stream, NULL ,
read_handler_recover);
entries = apr_hash_make(pool);
err = svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool);
if (err)
{
svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
err = svn_error_compose_create(err, svn_stream_close(stream));
return svn_error_quick_wrapf(err,
_("malformed representation for node-revision '%s'"),
id_str->data);
}
SVN_ERR(svn_stream_close(stream));
iterpool = svn_pool_create(pool);
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
{
char *str_val;
char *str;
svn_node_kind_t kind;
const svn_fs_id_t *id;
const svn_fs_fs__id_part_t *rev_item;
apr_uint64_t node_id, copy_id;
apr_off_t child_dir_offset;
const svn_string_t *path = apr_hash_this_val(hi);
svn_pool_clear(iterpool);
str_val = apr_pstrdup(iterpool, path->data);
str = svn_cstring_tokenize(" ", &str_val);
if (str == NULL)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Directory entry corrupt"));
if (strcmp(str, SVN_FS_FS__KIND_FILE) == 0)
kind = svn_node_file;
else if (strcmp(str, SVN_FS_FS__KIND_DIR) == 0)
kind = svn_node_dir;
else
{
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Directory entry corrupt"));
}
str = svn_cstring_tokenize(" ", &str_val);
if (str == NULL)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Directory entry corrupt"));
SVN_ERR(svn_fs_fs__id_parse(&id, str, iterpool));
rev_item = svn_fs_fs__id_rev_item(id);
if (rev_item->revision != rev)
{
continue;
}
node_id = svn_fs_fs__id_node_id(id)->number;
copy_id = svn_fs_fs__id_copy_id(id)->number;
if (node_id > *max_node_id)
*max_node_id = node_id;
if (copy_id > *max_copy_id)
*max_copy_id = copy_id;
if (kind == svn_node_file)
continue;
SVN_ERR(svn_fs_fs__item_offset(&child_dir_offset, fs,
rev_file, rev, NULL, rev_item->number,
iterpool));
SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
max_node_id, max_copy_id, iterpool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static svn_error_t *
recover_get_root_offset(apr_off_t *root_offset,
svn_revnum_t rev,
svn_fs_fs__revision_file_t *rev_file,
apr_pool_t *pool)
{
char buffer[64];
svn_stringbuf_t *trailer;
apr_off_t start;
apr_off_t end;
apr_size_t len;
SVN_ERR_ASSERT(!rev_file->is_packed);
end = 0;
SVN_ERR(svn_io_file_seek(rev_file->file, APR_END, &end, pool));
if (end < sizeof(buffer))
{
len = (apr_size_t)end;
start = 0;
}
else
{
len = sizeof(buffer);
start = end - sizeof(buffer);
}
SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &start, pool));
SVN_ERR(svn_io_file_read_full2(rev_file->file, buffer, len,
NULL, NULL, pool));
trailer = svn_stringbuf_ncreate(buffer, len, pool);
SVN_ERR(svn_fs_fs__parse_revision_trailer(root_offset, NULL, trailer, rev));
return SVN_NO_ERROR;
}
struct recover_baton {
svn_fs_t *fs;
svn_cancel_func_t cancel_func;
void *cancel_baton;
};
static svn_error_t *
recover_body(void *baton, apr_pool_t *pool)
{
struct recover_baton *b = baton;
svn_fs_t *fs = b->fs;
fs_fs_data_t *ffd = fs->fsap_data;
svn_revnum_t max_rev;
apr_uint64_t next_node_id = 0;
apr_uint64_t next_copy_id = 0;
svn_revnum_t youngest_rev;
svn_node_kind_t youngest_revprops_kind;
SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
SVN_ERR(svn_fs_fs__youngest_rev(&youngest_rev, fs, pool));
if (youngest_rev > max_rev)
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
_("Expected current rev to be <= %ld "
"but found %ld"), max_rev, youngest_rev);
if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
{
svn_revnum_t rev;
apr_pool_t *iterpool = svn_pool_create(pool);
for (rev = 0; rev <= max_rev; rev++)
{
svn_fs_fs__revision_file_t *rev_file;
apr_off_t root_offset;
svn_pool_clear(iterpool);
if (b->cancel_func)
SVN_ERR(b->cancel_func(b->cancel_baton));
SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rev, pool,
iterpool));
SVN_ERR(recover_get_root_offset(&root_offset, rev, rev_file, pool));
SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
&next_node_id, &next_copy_id, pool));
SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
}
svn_pool_destroy(iterpool);
next_node_id++;
next_copy_id++;
}
SVN_ERR(svn_io_check_path(svn_fs_fs__path_revprops(fs, max_rev, pool),
&youngest_revprops_kind, pool));
if (youngest_revprops_kind == svn_node_none)
{
svn_boolean_t missing = TRUE;
if (!svn_fs_fs__packed_revprop_available(&missing, fs, max_rev, pool))
{
if (missing)
{
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
_("Revision %ld has a revs file but no "
"revprops file"),
max_rev);
}
else
{
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
_("Revision %ld has a revs file but the "
"revprops file is inaccessible"),
max_rev);
}
}
}
else if (youngest_revprops_kind != svn_node_file)
{
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
_("Revision %ld has a non-file where its "
"revprops file should be"),
max_rev);
}
if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
{
svn_boolean_t rep_cache_exists;
SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
if (rep_cache_exists)
SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
}
return svn_fs_fs__write_current(fs, max_rev, next_node_id, next_copy_id,
pool);
}
svn_error_t *
svn_fs_fs__recover(svn_fs_t *fs,
svn_cancel_func_t cancel_func, void *cancel_baton,
apr_pool_t *pool)
{
struct recover_baton b;
b.fs = fs;
b.cancel_func = cancel_func;
b.cancel_baton = cancel_baton;
return svn_fs_fs__with_all_locks(fs, recover_body, &b, pool);
}