You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2012/12/23 16:17:47 UTC
svn commit: r1425472 [1/6] -
/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/
Author: stefan2
Date: Sun Dec 23 15:17:47 2012
New Revision: 1425472
URL: http://svn.apache.org/viewvc?rev=1425472&view=rev
Log:
On the fsfs-format7 branch, follow-up to r1415451:
More splitting up fs_fs.c.
This is the second step in that process. The 5 new c/h file pairs are
- cached_data.* for cached (read) read access to FSFS data structures
and representation content.
- hotcopy.* FSFS hotcopy functionality
- pack.* FSFS shared packing code and access to packed rev files
- recovery.* FSFS data recovery code
- transaction.* all FSFS transaction and commit functionality
Also, a number of smaller modifications to other files were necessary
to facilitate the split.
* subversion/libsvn_fs_fs/cached_data.h
(): new header file
(svn_fs_fs__get_node_revision,
svn_fs_fs__rev_get_root,
svn_fs_fs__try_process_file_contents,
svn_fs_fs__get_file_delta_stream,
svn_fs_fs__rep_contents_dir,
svn_fs_fs__rep_contents_dir_entry,
svn_fs_fs__get_proplist): declare API functions moved from fs_fs.h
(svn_fs_fs__get_contents): ditto; address by rep instead of noderev
(svn_fs_fs__check_rep,
svn_fs_fs__get_changes): declare new API functions
* subversion/libsvn_fs_fs/cached_data.c
(): new source file
(rep_state_t,
rep_read_baton,
cache_access_wrapper_baton_t,
delta_read_baton): define data types taken from fs_fs.c
(open_and_seek_revision,
open_and_seek_transaction,
open_and_seek_representation,
err_dangling_id,
get_cached_node_revision_body,
set_cached_node_revision_body,
get_node_revision_body,
get_fs_id_at_offset,
create_rep_state_body,
create_rep_state,
get_window_key,
get_cached_window,
set_cached_window,
get_cached_combined_window,
set_cached_combined_window,
build_rep_list,
rep_read_get_baton,
read_delta_window,
read_plain_window,
get_combined_window,
fulltext_size_is_cachable,
rep_read_contents_close,
get_contents,
rep_read_contents,
cache_access_wrapper,
delta_read_next_window,
delta_read_md5_digest,
get_dir_contents,
parse_dir_entries,
locate_dir_cache): static functions taken from fs_fs.c
(svn_fs_fs__get_node_revision,
svn_fs_fs__rev_get_root,
svn_fs_fs__try_process_file_contents,
svn_fs_fs__get_file_delta_stream,
svn_fs_fs__rep_contents_dir,
svn_fs_fs__rep_contents_dir_entry,
svn_fs_fs__get_proplist): implement API functions taken from fs_fs.c
(svn_fs_fs__get_contents): ditto; address by rep instead of noderev
(svn_fs_fs__check_rep,
svn_fs_fs__get_changes): implemenent new API functions
* subversion/libsvn_fs_fs/hotcopy.h
(): new header file
(svn_fs_fs__hotcopy): declare API function taken from fs_fs.h
* subversion/libsvn_fs_fs/hotcopy.c
(): new source file
(hotcopy_body_baton): define data types taken from fs_fs.c
(hotcopy_io_dir_file_copy,
entry_name_to_utf8,
hotcopy_io_copy_dir_recursively,
hotcopy_copy_shard_file,
hotcopy_copy_packed_shard,
hotcopy_update_current,
hotcopy_remove_rev_files,
hotcopy_incremental_check_preconditions,
hotcopy_body,
hotcopy_setup_shared_fs_data,
hotcopy_create_empty_dest): static functions taken from fs_fs.c
(svn_fs_fs__hotcopy): implement API function taken from fs_fs.c
* subversion/libsvn_fs_fs/pack.h
(): new header file
(svn_fs_fs__pack): declare API function moved from fs_fs.h
(svn_fs_fs__get_packed_offset): declare new API function
* subversion/libsvn_fs_fs/pack.c
(): new source file
(pack_baton): define data types taken from fs_fs.c
(pack_rev_shard,
pack_shard,
pack_body): static functions taken from fs_fs.c
(svn_fs_fs__pack): implement API function taken from fs_fs.c
(svn_fs_fs__get_packed_offset): implement new API function
* subversion/libsvn_fs_fs/recovery.h
(): new header file
(svn_fs_fs__find_max_ids,
svn_fs_fs__recover): declare API functions moved from fs_fs.h
* subversion/libsvn_fs_fs/recovery.c
(): new source file
(recover_read_from_file_baton,
recover_baton): define data types taken from fs_fs.c
(recover_get_largest_revision,
read_handler_recover,
recover_find_max_ids,
recover_body): static functions taken from fs_fs.c
(svn_fs_fs__find_max_ids,
svn_fs_fs__recover): implement API functions taken from fs_fs.c
* subversion/libsvn_fs_fs/transaction.h
(): new header file
(svn_fs_fs__with_write_lock,
svn_fs_fs__put_node_revision,
svn_fs_fs__txn_changes_fetch,
svn_fs_fs__paths_changed,
svn_fs_fs__create_txn,
svn_fs_fs__change_txn_prop,
svn_fs_fs__change_txn_props,
svn_fs_fs__get_txn,
svn_fs_fs__reserve_copy_id,
svn_fs_fs__create_node,
svn_fs_fs__purge_txn,
svn_fs_fs__abort_txn,
svn_fs_fs__set_entry,
svn_fs_fs__add_change,
svn_fs_fs__set_contents,
svn_fs_fs__create_successor,
svn_fs_fs__set_proplist,
svn_fs_fs__commit,
svn_fs_fs__list_transactions,
svn_fs_fs__open_txn,
svn_fs_fs__txn_proplist,
svn_fs_fs__delete_node_revision,
svn_fs_fs__get_txn_ids,
svn_fs_fs__txn_prop,
svn_fs_fs__begin_txn): declare API functions moved from fs_fs.h
* subversion/libsvn_fs_fs/transaction.c
(): new source file
(txn_vtable): static variable taken from fs_fs.c
(unlock_proto_rev_baton,
get_writable_proto_rev_baton,
get_and_increment_txn_key_baton,
rep_write_baton,
write_hash_baton,
commit_baton): define data types taken from fs_fs.c
(get_shared_txn,
free_shared_txn,
with_txnlist_lock,
get_lock_on_filesystem,
reset_lock_flag,
with_some_lock_file,
with_txn_current_lock,
get_writable_proto_rev_body,
get_writable_proto_rev,
purge_shared_txn_body,
purge_shared_txn,
get_writable_proto_rev_body,
get_writable_proto_rev,
purge_shared_txn_body,
purge_shared_txn,
store_sha1_rep_mapping,
unparse_dir_entry,
unparse_dir_entries,
fold_change,
process_changes,
create_new_txn_noderev_from_rev,
get_and_increment_txn_key_body,
create_txn_dir,
create_txn_dir_pre_1_5,
get_txn_proplist,
write_next_ids,
read_next_ids,
get_new_txn_node_id,
rep_write_contents,
choose_delta_base,
rep_write_cleanup,
rep_write_get_baton,
get_shared_rep,
rep_write_contents_close,
set_representation,
get_next_revision_ids,
write_hash_handler,
write_hash_rep,
write_hash_delta_rep,
validate_root_noderev,
write_final_rev,
write_final_changed_path_info,
write_final_current,
verify_locks,
commit_body,
write_reps_to_cache,
commit_sqlite_txn_callback): static functions taken from fs_fs.c
(svn_fs_fs__with_write_lock,
svn_fs_fs__put_node_revision,
svn_fs_fs__txn_changes_fetch,
svn_fs_fs__paths_changed,
svn_fs_fs__create_txn,
svn_fs_fs__change_txn_prop,
svn_fs_fs__change_txn_props,
svn_fs_fs__get_txn,
svn_fs_fs__reserve_copy_id,
svn_fs_fs__create_node,
svn_fs_fs__purge_txn,
svn_fs_fs__abort_txn,
svn_fs_fs__set_entry,
svn_fs_fs__add_change,
svn_fs_fs__set_contents,
svn_fs_fs__create_successor,
svn_fs_fs__set_proplist,
svn_fs_fs__commit,
svn_fs_fs__list_transactions,
svn_fs_fs__open_txn,
svn_fs_fs__txn_proplist,
svn_fs_fs__delete_node_revision,
svn_fs_fs__get_txn_ids,
svn_fs_fs__txn_prop,
svn_fs_fs__begin_txn): implement API functions taken from fs_fs.c
* subversion/libsvn_fs_fs/util.h
(read_min_unpacked_rev): take an FS instead of a path
(write_revnum_file,
write_current,
get_file_offset): publish API
* subversion/libsvn_fs_fs/util.c
(read_min_unpacked_rev): adapt implementation
(write_revnum_file,
write_current,
get_file_offset): implementation moved here from fs_fs.c
* subversion/libsvn_fs_fs/fs_fs.h
(svn_fs_fs__get_node_revision,
svn_fs_fs__rev_get_root,
svn_fs_fs__get_contents,
svn_fs_fs__try_process_file_contents,
svn_fs_fs__get_file_delta_stream,
svn_fs_fs__rep_contents_dir,
svn_fs_fs__rep_contents_dir_entry,
svn_fs_fs__get_proplist): moved to cached_data.h
(svn_fs_fs__hotcopy): moved to hotcopy.h
(svn_fs_fs__pack): moved to pack.h
(svn_fs_fs__find_max_ids,
svn_fs_fs__recover): moved to recovery.h
(svn_fs_fs__with_write_lock,
svn_fs_fs__put_node_revision,
svn_fs_fs__txn_changes_fetch,
svn_fs_fs__paths_changed,
svn_fs_fs__create_txn,
svn_fs_fs__change_txn_prop,
svn_fs_fs__change_txn_props,
svn_fs_fs__get_txn,
svn_fs_fs__reserve_copy_id,
svn_fs_fs__create_node,
svn_fs_fs__purge_txn,
svn_fs_fs__abort_txn,
svn_fs_fs__set_entry,
svn_fs_fs__add_change,
svn_fs_fs__set_contents,
svn_fs_fs__create_successor,
svn_fs_fs__set_proplist,
svn_fs_fs__commit,
svn_fs_fs__list_transactions,
svn_fs_fs__open_txn,
svn_fs_fs__txn_proplist,
svn_fs_fs__delete_node_revision,
svn_fs_fs__get_txn_ids,
svn_fs_fs__txn_prop,
svn_fs_fs__begin_txn): moved to tranaction.h
(svn_fs_fs__open_pack_or_rev_file): declare new API
* subversion/libsvn_fs_fs/fs_fs.c
(): shorten include list
(rep_state_t,
rep_read_baton,
cache_access_wrapper_baton_t,
delta_read_baton,
open_and_seek_revision,
open_and_seek_transaction,
open_and_seek_representation,
err_dangling_id,
get_cached_node_revision_body,
set_cached_node_revision_body,
get_node_revision_body,
get_fs_id_at_offset,
create_rep_state_body,
create_rep_state,
get_window_key,
get_cached_window,
set_cached_window,
get_cached_combined_window,
set_cached_combined_window,
build_rep_list,
rep_read_get_baton,
read_delta_window,
read_plain_window,
get_combined_window,
fulltext_size_is_cachable,
rep_read_contents_close,
get_contents,
rep_read_contents,
cache_access_wrapper,
delta_read_next_window,
delta_read_md5_digest,
get_dir_contents,
parse_dir_entries,
locate_dir_cache,
svn_fs_fs__get_node_revision,
svn_fs_fs__rev_get_root,
svn_fs_fs__get_contents,
svn_fs_fs__try_process_file_contents,
svn_fs_fs__get_file_delta_stream,
svn_fs_fs__rep_contents_dir,
svn_fs_fs__rep_contents_dir_entry,
svn_fs_fs__get_proplist): moved to cached_data.c
(hotcopy_body_baton,
hotcopy_io_dir_file_copy,
entry_name_to_utf8,
hotcopy_io_copy_dir_recursively,
hotcopy_copy_shard_file,
hotcopy_copy_packed_shard,
hotcopy_update_current,
hotcopy_remove_rev_files,
hotcopy_incremental_check_preconditions,
hotcopy_body,
hotcopy_setup_shared_fs_data,
hotcopy_create_empty_dest,
svn_fs_fs__hotcopy): moved to hotcopy.c
(pack_baton,
pack_rev_shard,
pack_shard,
pack_body,
svn_fs_fs__pack): moved to pack.c
(recover_read_from_file_baton,
recover_baton,
recover_get_largest_revision,
read_handler_recover,
recover_find_max_ids,
recover_body,
svn_fs_fs__find_max_ids,
svn_fs_fs__recover): moved to recovery.c
(txn_vtable,
unlock_proto_rev_baton,
get_writable_proto_rev_baton,
get_and_increment_txn_key_baton,
rep_write_baton,
write_hash_baton,
commit_baton,
get_shared_txn,
free_shared_txn,
with_txnlist_lock,
get_lock_on_filesystem,
reset_lock_flag,
with_some_lock_file,
with_txn_current_lock,
get_writable_proto_rev_body,
get_writable_proto_rev,
purge_shared_txn_body,
purge_shared_txn,
get_writable_proto_rev_body,
get_writable_proto_rev,
purge_shared_txn_body,
purge_shared_txn,
store_sha1_rep_mapping,
unparse_dir_entry,
unparse_dir_entries,
fold_change,
process_changes,
create_new_txn_noderev_from_rev,
get_and_increment_txn_key_body,
create_txn_dir,
create_txn_dir_pre_1_5,
get_txn_proplist,
write_next_ids,
read_next_ids,
get_new_txn_node_id,
rep_write_contents,
choose_delta_base,
rep_write_cleanup,
rep_write_get_baton,
get_shared_rep,
rep_write_contents_close,
set_representation,
get_next_revision_ids,
write_hash_handler,
write_hash_rep,
write_hash_delta_rep,
validate_root_noderev,
write_final_rev,
write_final_changed_path_info,
write_final_current,
verify_locks,
commit_body,
write_reps_to_cache,
commit_sqlite_txn_callback,
svn_fs_fs__with_write_lock,
svn_fs_fs__put_node_revision,
svn_fs_fs__txn_changes_fetch,
svn_fs_fs__paths_changed,
svn_fs_fs__create_txn,
svn_fs_fs__change_txn_prop,
svn_fs_fs__change_txn_props,
svn_fs_fs__get_txn,
svn_fs_fs__reserve_copy_id,
svn_fs_fs__create_node,
svn_fs_fs__purge_txn,
svn_fs_fs__abort_txn,
svn_fs_fs__set_entry,
svn_fs_fs__add_change,
svn_fs_fs__set_contents,
svn_fs_fs__create_successor,
svn_fs_fs__set_proplist,
svn_fs_fs__commit,
svn_fs_fs__list_transactions,
svn_fs_fs__open_txn,
svn_fs_fs__txn_proplist,
svn_fs_fs__delete_node_revision,
svn_fs_fs__get_txn_ids,
svn_fs_fs__txn_prop,
svn_fs_fs__begin_txn): moved to transaction.c
(write_revnum_file,
write_current,
get_file_offset): moved to low_level.c
(write_changed_path_info): moved to low_level.c
(upgrade_body,
svn_fs_fs__create): use svn_fs_fs__write_format API intead of write_format
(svn_fs_fs__open_pack_or_rev_file): turn into API;
renamed from open_pack_or_rev_file
(verify_walker): use new svn_fs_fs__check_rep API
* subversion/libsvn_fs_fs/dag.c
(svn_fs_fs__dag_get_contents): adapt to API change
* subversion/libsvn_fs_fs/low_level.h
(write_changed_path_info): declare / publish API
* subversion/libsvn_fs_fs/low_level.c
(): add #includes to new headers
(write_changed_path_info): move implementation from fs_fs.c here
* subversion/libsvn_fs_fs/fs.c
(): add #includes to new headers
* subversion/libsvn_fs_fs/lock.c
(): add #includes to new headers
* subversion/libsvn_fs_fs/revprops.c
(): add #includes to new headers
* subversion/libsvn_fs_fs/tree.c
(): add #includes to new headers
* tools/server-side/svn-rep-sharing-stats.c
(): add #includes to new headers
Added:
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.h
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.h
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.h
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/recovery.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/recovery.h
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/transaction.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/transaction.h
Modified:
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/dag.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.h
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/lock.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.h
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/revprops.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/tree.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/util.c
subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/util.h
Added: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.c?rev=1425472&view=auto
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.c (added)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.c Sun Dec 23 15:17:47 2012
@@ -0,0 +1,1699 @@
+/* cached_data.c --- cached (read) access to FSFS data
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include "cached_data.h"
+
+#include "svn_hash.h"
+#include "svn_ctype.h"
+
+#include "fs_fs.h"
+#include "low_level.h"
+#include "util.h"
+#include "pack.h"
+#include "temp_serializer.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* Open the revision file for revision REV in filesystem FS and store
+ the newly opened file in FILE. Seek to location OFFSET before
+ returning. Perform temporary allocations in POOL. */
+static svn_error_t *
+open_and_seek_revision(apr_file_t **file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ apr_file_t *rev_file;
+
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, rev, pool));
+
+ if (is_packed_rev(fs, rev))
+ {
+ apr_off_t rev_offset;
+
+ SVN_ERR(svn_fs_fs__get_packed_offset(&rev_offset, fs, rev, pool));
+ offset += rev_offset;
+ }
+
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+ *file = rev_file;
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the representation for a node-revision in transaction TXN_ID
+ in filesystem FS and store the newly opened file in FILE. Seek to
+ location OFFSET before returning. Perform temporary allocations in
+ POOL. Only appropriate for file contents, nor props or directory
+ contents. */
+static svn_error_t *
+open_and_seek_transaction(apr_file_t **file,
+ svn_fs_t *fs,
+ const char *txn_id,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ apr_file_t *rev_file;
+ apr_off_t offset;
+
+ SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ offset = rep->offset;
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+ *file = rev_file;
+
+ return SVN_NO_ERROR;
+}
+
+/* Given a node-id ID, and a representation REP in filesystem FS, open
+ the correct file and seek to the correction location. Store this
+ file in *FILE_P. Perform any allocations in POOL. */
+static svn_error_t *
+open_and_seek_representation(apr_file_t **file_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ if (! rep->txn_id)
+ return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
+ pool);
+ else
+ return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
+}
+
+
+
+static svn_error_t *
+err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
+{
+ svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ID_NOT_FOUND, 0,
+ _("Reference to non-existent node '%s' in filesystem '%s'"),
+ id_str->data, fs->path);
+}
+
+/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
+ * caching has been enabled and the data can be found, IS_CACHED will
+ * be set to TRUE. The noderev will be allocated from POOL.
+ *
+ * Non-permanent ids (e.g. ids within a TXN) will not be cached.
+ */
+static svn_error_t *
+get_cached_node_revision_body(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ svn_boolean_t *is_cached,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
+ {
+ *is_cached = FALSE;
+ }
+ else
+ {
+ pair_cache_key_t key;
+
+ key.revision = svn_fs_fs__id_rev(id);
+ key.second = svn_fs_fs__id_offset(id);
+ SVN_ERR(svn_cache__get((void **) noderev_p,
+ is_cached,
+ ffd->node_revision_cache,
+ &key,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* If noderev caching has been enabled, store the NODEREV_P for the given ID
+ * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
+ *
+ * Non-permanent ids (e.g. ids within a TXN) will not be cached.
+ */
+static svn_error_t *
+set_cached_node_revision_body(node_revision_t *noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
+ {
+ pair_cache_key_t key;
+
+ key.revision = svn_fs_fs__id_rev(id);
+ key.second = svn_fs_fs__id_offset(id);
+ return svn_cache__set(ffd->node_revision_cache,
+ &key,
+ noderev_p,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Get the node-revision for the node ID in FS.
+ Set *NODEREV_P to the new node-revision structure, allocated in POOL.
+ See svn_fs_fs__get_node_revision, which wraps this and adds another
+ error. */
+static svn_error_t *
+get_node_revision_body(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ apr_file_t *revision_file;
+ svn_error_t *err;
+ svn_boolean_t is_cached = FALSE;
+
+ /* First, try a cache lookup. If that succeeds, we are done here. */
+ SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ if (svn_fs_fs__id_txn_id(id))
+ {
+ /* This is a transaction node-rev. */
+ err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
+ }
+ else
+ {
+ /* This is a revision node-rev. */
+ err = open_and_seek_revision(&revision_file, fs,
+ svn_fs_fs__id_rev(id),
+ svn_fs_fs__id_offset(id),
+ pool);
+ }
+
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return svn_error_trace(err_dangling_id(fs, id));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
+ svn_stream_from_aprfile2(revision_file, FALSE,
+ pool),
+ pool));
+ /* Workaround issue #4031: is-fresh-txn-root in revision files. */
+ if (svn_fs_fs__id_txn_id(id) == NULL)
+ (*noderev_p)->is_fresh_txn_root = FALSE;
+
+
+ /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
+ return set_cached_node_revision_body(*noderev_p, fs, id, pool);
+}
+
+svn_error_t *
+svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
+ if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
+ {
+ svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Corrupt node-revision '%s'",
+ id_string->data);
+ }
+ return svn_error_trace(err);
+}
+
+
+
+/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
+ of the header located at OFFSET and store it in *ID_P. Allocate
+ temporary variables from POOL. */
+static svn_error_t *
+get_fs_id_at_offset(svn_fs_id_t **id_p,
+ apr_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id;
+ apr_hash_t *headers;
+ const char *node_id_str;
+
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+ SVN_ERR(read_header_block(&headers,
+ svn_stream_from_aprfile2(rev_file, TRUE, pool),
+ pool));
+
+ /* In error messages, the offset is relative to the pack file,
+ not to the rev file. */
+
+ node_id_str = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
+
+ if (node_id_str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing node-id in node-rev at r%ld "
+ "(offset %s)"),
+ rev,
+ apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
+
+ id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
+
+ if (id == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt node-id '%s' in node-rev at r%ld "
+ "(offset %s)"),
+ node_id_str, rev,
+ apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
+
+ *id_p = id;
+
+ /* ### assert that the txn_id is REV/OFFSET ? */
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_file_t *revision_file;
+ apr_off_t root_offset;
+ svn_fs_id_t *root_id = NULL;
+ svn_boolean_t is_cached;
+
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
+
+ SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
+ ffd->rev_root_id_cache, &rev, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&revision_file, fs, rev, pool));
+ SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
+ pool));
+
+ SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
+ root_offset, pool));
+
+ SVN_ERR(svn_io_file_close(revision_file, pool));
+
+ SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
+
+ *root_id_p = root_id;
+
+ return SVN_NO_ERROR;
+}
+
+/* Represents where in the current svndiff data block each
+ representation is. */
+typedef struct rep_state_t
+{
+ apr_file_t *file;
+ /* The txdelta window cache to use or NULL. */
+ svn_cache__t *window_cache;
+ /* Caches un-deltified windows. May be NULL. */
+ svn_cache__t *combined_cache;
+ apr_off_t start; /* The starting offset for the raw
+ svndiff/plaintext data minus header. */
+ apr_off_t off; /* The current offset into the file. */
+ apr_off_t end; /* The end offset of the raw data. */
+ int ver; /* If a delta, what svndiff version? */
+ int chunk_index;
+} rep_state_t;
+
+/* See create_rep_state, which wraps this and adds another error. */
+static svn_error_t *
+create_rep_state_body(rep_state_t **rep_state,
+ rep_args_t **rep_args,
+ apr_file_t **file_hint,
+ svn_revnum_t *rev_hint,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ rep_state_t *rs = apr_pcalloc(pool, sizeof(*rs));
+ rep_args_t *ra;
+ unsigned char buf[4];
+
+ /* If the hint is
+ * - given,
+ * - refers to a packed revision,
+ * - as does the rep we want to read, and
+ * - refers to the same pack file as the rep
+ * ...
+ */
+ if ( file_hint && rev_hint && *file_hint
+ && *rev_hint < ffd->min_unpacked_rev
+ && rep->revision < ffd->min_unpacked_rev
+ && ( (*rev_hint / ffd->max_files_per_dir)
+ == (rep->revision / ffd->max_files_per_dir)))
+ {
+ /* ... we can re-use the same, already open file object
+ */
+ apr_off_t offset;
+ SVN_ERR(svn_fs_fs__get_packed_offset(&offset, fs, rep->revision, pool));
+
+ offset += rep->offset;
+ SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
+
+ rs->file = *file_hint;
+ }
+ else
+ {
+ /* otherwise, create a new file object
+ */
+ SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
+ }
+
+ /* remember the current file, if suggested by the caller */
+ if (file_hint)
+ *file_hint = rs->file;
+ if (rev_hint)
+ *rev_hint = rep->revision;
+
+ /* continue constructing RS and RA */
+ rs->window_cache = ffd->txdelta_window_cache;
+ rs->combined_cache = ffd->combined_window_cache;
+
+ SVN_ERR(read_rep_line(&ra, svn_stream_from_aprfile2(rs->file, TRUE, pool),
+ pool));
+ SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
+ rs->off = rs->start;
+ rs->end = rs->start + rep->size;
+ *rep_state = rs;
+ *rep_args = ra;
+
+ if (ra->is_delta == FALSE)
+ /* This is a plaintext, so just return the current rep_state. */
+ return SVN_NO_ERROR;
+
+ /* We are dealing with a delta, find out what version. */
+ SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
+ NULL, NULL, pool));
+ /* ### Layering violation */
+ if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed svndiff data in representation"));
+ rs->ver = buf[3];
+ rs->chunk_index = 0;
+ rs->off += 4;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the rep args for REP in filesystem FS and create a rep_state
+ for reading the representation. Return the rep_state in *REP_STATE
+ and the rep args in *REP_ARGS, both allocated in POOL.
+
+ When reading multiple reps, i.e. a skip delta chain, you may provide
+ non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first
+ call it should be a pointer to NULL.) The function will use these variables
+ to store the previous call results and tries to re-use them. This may
+ result in significant savings in I/O for packed files.
+ */
+static svn_error_t *
+create_rep_state(rep_state_t **rep_state,
+ rep_args_t **rep_args,
+ apr_file_t **file_hint,
+ svn_revnum_t *rev_hint,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = create_rep_state_body(rep_state, rep_args,
+ file_hint, rev_hint,
+ rep, fs, pool);
+ if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* ### This always returns "-1" for transaction reps, because
+ ### this particular bit of code doesn't know if the rep is
+ ### stored in the protorev or in the mutable area (for props
+ ### or dir contents). It is pretty rare for FSFS to *read*
+ ### from the protorev file, though, so this is probably OK.
+ ### And anyone going to debug corruption errors is probably
+ ### going to jump straight to this comment anyway! */
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Corrupt representation '%s'",
+ rep
+ ? representation_string(rep, ffd->format, TRUE,
+ TRUE, pool)
+ : "(null)");
+ }
+ /* ### Call representation_string() ? */
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_fs__check_rep(representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ rep_state_t *rs;
+ rep_args_t *rep_args;
+
+ /* ### Should this be using read_rep_line() directly? */
+ SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct rep_read_baton
+{
+ /* The FS from which we're reading. */
+ svn_fs_t *fs;
+
+ /* If not NULL, this is the base for the first delta window in rs_list */
+ svn_stringbuf_t *base_window;
+
+ /* The state of all prior delta representations. */
+ apr_array_header_t *rs_list;
+
+ /* The plaintext state, if there is a plaintext. */
+ rep_state_t *src_state;
+
+ /* The index of the current delta chunk, if we are reading a delta. */
+ int chunk_index;
+
+ /* The buffer where we store undeltified data. */
+ char *buf;
+ apr_size_t buf_pos;
+ apr_size_t buf_len;
+
+ /* A checksum context for summing the data read in order to verify it.
+ Note: we don't need to use the sha1 checksum because we're only doing
+ data verification, for which md5 is perfectly safe. */
+ svn_checksum_ctx_t *md5_checksum_ctx;
+
+ svn_boolean_t checksum_finalized;
+
+ /* The stored checksum of the representation we are reading, its
+ length, and the amount we've read so far. Some of this
+ information is redundant with rs_list and src_state, but it's
+ convenient for the checksumming code to have it here. */
+ svn_checksum_t *md5_checksum;
+
+ svn_filesize_t len;
+ svn_filesize_t off;
+
+ /* The key for the fulltext cache for this rep, if there is a
+ fulltext cache. */
+ pair_cache_key_t fulltext_cache_key;
+ /* The text we've been reading, if we're going to cache it. */
+ svn_stringbuf_t *current_fulltext;
+
+ /* Used for temporary allocations during the read. */
+ apr_pool_t *pool;
+
+ /* Pool used to store file handles and other data that is persistant
+ for the entire stream read. */
+ apr_pool_t *filehandle_pool;
+};
+
+/* Combine the name of the rev file in RS with the given OFFSET to form
+ * a cache lookup key. Allocations will be made from POOL. May return
+ * NULL if the key cannot be constructed. */
+static const char*
+get_window_key(rep_state_t *rs, apr_off_t offset, apr_pool_t *pool)
+{
+ const char *name;
+ const char *last_part;
+ const char *name_last;
+
+ /* the rev file name containing the txdelta window.
+ * If this fails we are in serious trouble anyways.
+ * And if nobody else detects the problems, the file content checksum
+ * comparison _will_ find them.
+ */
+ if (apr_file_name_get(&name, rs->file))
+ return NULL;
+
+ /* Handle packed files as well by scanning backwards until we find the
+ * revision or pack number. */
+ name_last = name + strlen(name) - 1;
+ while (! svn_ctype_isdigit(*name_last))
+ --name_last;
+
+ last_part = name_last;
+ while (svn_ctype_isdigit(*last_part))
+ --last_part;
+
+ /* We must differentiate between packed files (as of today, the number
+ * is being followed by a dot) and non-packed files (followed by \0).
+ * Otherwise, there might be overlaps in the numbering range if the
+ * repo gets packed after caching the txdeltas of non-packed revs.
+ * => add the first non-digit char to the packed number. */
+ if (name_last[1] != '\0')
+ ++name_last;
+
+ /* copy one char MORE than the actual number to mark packed files,
+ * i.e. packed revision file content uses different key space then
+ * non-packed ones: keys for packed rev file content ends with a dot
+ * for non-packed rev files they end with a digit. */
+ name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
+ return svn_fs_fs__combine_number_and_string(offset, name, pool);
+}
+
+/* Read the WINDOW_P for the rep state RS from the current FSFS session's
+ * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
+ * cache has been given. If a cache is available IS_CACHED will inform
+ * the caller about the success of the lookup. Allocations (of the window
+ * in particualar) will be made from POOL.
+ *
+ * If the information could be found, put RS and the position within the
+ * rev file into the same state as if the data had just been read from it.
+ */
+static svn_error_t *
+get_cached_window(svn_txdelta_window_t **window_p,
+ rep_state_t *rs,
+ svn_boolean_t *is_cached,
+ apr_pool_t *pool)
+{
+ if (! rs->window_cache)
+ {
+ /* txdelta window has not been enabled */
+ *is_cached = FALSE;
+ }
+ else
+ {
+ /* ask the cache for the desired txdelta window */
+ svn_fs_fs__txdelta_cached_window_t *cached_window;
+ SVN_ERR(svn_cache__get((void **) &cached_window,
+ is_cached,
+ rs->window_cache,
+ get_window_key(rs, rs->off, pool),
+ pool));
+
+ if (*is_cached)
+ {
+ /* found it. Pass it back to the caller. */
+ *window_p = cached_window->window;
+
+ /* manipulate the RS as if we just read the data */
+ rs->chunk_index++;
+ rs->off = cached_window->end_offset;
+
+ /* manipulate the rev file as if we just read from it */
+ SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read at OFFSET for the rep state RS in the current
+ * FSFS session's cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_window(svn_txdelta_window_t *window,
+ rep_state_t *rs,
+ apr_off_t offset,
+ apr_pool_t *scratch_pool)
+{
+ if (rs->window_cache)
+ {
+ /* store the window and the first offset _past_ it */
+ svn_fs_fs__txdelta_cached_window_t cached_window;
+
+ cached_window.window = window;
+ cached_window.end_offset = rs->off;
+
+ /* but key it with the start offset because that is the known state
+ * when we will look it up */
+ return svn_cache__set(rs->window_cache,
+ get_window_key(rs, offset, scratch_pool),
+ &cached_window,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the WINDOW_P for the rep state RS from the current FSFS session's
+ * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
+ * cache has been given. If a cache is available IS_CACHED will inform
+ * the caller about the success of the lookup. Allocations (of the window
+ * in particualar) will be made from POOL.
+ */
+static svn_error_t *
+get_cached_combined_window(svn_stringbuf_t **window_p,
+ rep_state_t *rs,
+ svn_boolean_t *is_cached,
+ apr_pool_t *pool)
+{
+ if (! rs->combined_cache)
+ {
+ /* txdelta window has not been enabled */
+ *is_cached = FALSE;
+ }
+ else
+ {
+ /* ask the cache for the desired txdelta window */
+ return svn_cache__get((void **)window_p,
+ is_cached,
+ rs->combined_cache,
+ get_window_key(rs, rs->start, pool),
+ pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read at OFFSET for the rep state RS in the current
+ * FSFS session's cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_combined_window(svn_stringbuf_t *window,
+ rep_state_t *rs,
+ apr_off_t offset,
+ apr_pool_t *scratch_pool)
+{
+ if (rs->combined_cache)
+ {
+ /* but key it with the start offset because that is the known state
+ * when we will look it up */
+ return svn_cache__set(rs->combined_cache,
+ get_window_key(rs, offset, scratch_pool),
+ window,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Build an array of rep_state structures in *LIST giving the delta
+ reps from first_rep to a plain-text or self-compressed rep. Set
+ *SRC_STATE to the plain-text rep we find at the end of the chain,
+ or to NULL if the final delta representation is self-compressed.
+ The representation to start from is designated by filesystem FS, id
+ ID, and representation REP.
+ Also, set *WINDOW_P to the base window content for *LIST, if it
+ could be found in cache. Otherwise, *LIST will contain the base
+ representation for the whole delta chain.
+ Finally, return the expanded size of the representation in
+ *EXPANDED_SIZE. It will take care of cases where only the on-disk
+ size is known. */
+static svn_error_t *
+build_rep_list(apr_array_header_t **list,
+ svn_stringbuf_t **window_p,
+ rep_state_t **src_state,
+ svn_filesize_t *expanded_size,
+ svn_fs_t *fs,
+ representation_t *first_rep,
+ apr_pool_t *pool)
+{
+ representation_t rep;
+ rep_state_t *rs = NULL;
+ rep_args_t *rep_args;
+ svn_boolean_t is_cached = FALSE;
+ apr_file_t *last_file = NULL;
+ svn_revnum_t last_revision;
+
+ *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
+ rep = *first_rep;
+
+ /* The value as stored in the data struct.
+ 0 is either for unknown length or actually zero length. */
+ *expanded_size = first_rep->expanded_size;
+
+ /* for the top-level rep, we need the rep_args */
+ SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
+ &last_revision, &rep, fs, pool));
+
+ /* Unknown size or empty representation?
+ That implies the this being the first iteration.
+ Usually size equals on-disk size, except for empty,
+ compressed representations (delta, size = 4).
+ Please note that for all non-empty deltas have
+ a 4-byte header _plus_ some data. */
+ if (*expanded_size == 0)
+ if (! rep_args->is_delta || first_rep->size != 4)
+ *expanded_size = first_rep->size;
+
+ while (1)
+ {
+ /* fetch state, if that has not been done already */
+ if (!rs)
+ SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
+ &last_revision, &rep, fs, pool));
+
+ SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
+ if (is_cached)
+ {
+ /* We already have a reconstructed window in our cache.
+ Write a pseudo rep_state with the full length. */
+ rs->off = rs->start;
+ rs->end = rs->start + (*window_p)->len;
+ *src_state = rs;
+ return SVN_NO_ERROR;
+ }
+
+ if (rep_args->is_delta == FALSE)
+ {
+ /* This is a plaintext, so just return the current rep_state. */
+ *src_state = rs;
+ return SVN_NO_ERROR;
+ }
+
+ /* Push this rep onto the list. If it's self-compressed, we're done. */
+ APR_ARRAY_PUSH(*list, rep_state_t *) = rs;
+ if (rep_args->is_delta_vs_empty)
+ {
+ *src_state = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ rep.revision = rep_args->base_revision;
+ rep.offset = rep_args->base_offset;
+ rep.size = rep_args->base_length;
+ rep.txn_id = NULL;
+
+ rs = NULL;
+ }
+}
+
+
+/* Create a rep_read_baton structure for node revision NODEREV in
+ filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not
+ NULL, it is the rep's key in the fulltext cache, and a stringbuf
+ must be allocated to store the text. Perform all allocations in
+ POOL. If rep is mutable, it must be for file contents. */
+static svn_error_t *
+rep_read_get_baton(struct rep_read_baton **rb_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ pair_cache_key_t fulltext_cache_key,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *b;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+ b->fs = fs;
+ b->base_window = NULL;
+ b->chunk_index = 0;
+ b->buf = NULL;
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ b->checksum_finalized = FALSE;
+ b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
+ b->len = rep->expanded_size;
+ b->off = 0;
+ b->fulltext_cache_key = fulltext_cache_key;
+ b->pool = svn_pool_create(pool);
+ b->filehandle_pool = svn_pool_create(pool);
+
+ SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
+ &b->src_state, &b->len, fs, rep,
+ b->filehandle_pool));
+
+ if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
+ b->current_fulltext = svn_stringbuf_create_ensure
+ ((apr_size_t)b->len,
+ b->filehandle_pool);
+ else
+ b->current_fulltext = NULL;
+
+ /* Save our output baton. */
+ *rb_p = b;
+
+ return SVN_NO_ERROR;
+}
+
+/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
+ window into *NWIN. */
+static svn_error_t *
+read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
+ rep_state_t *rs, apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ svn_boolean_t is_cached;
+ apr_off_t old_offset;
+
+ SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
+
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+
+ /* Skip windows to reach the current chunk if we aren't there yet. */
+ while (rs->chunk_index < this_chunk)
+ {
+ SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
+ rs->chunk_index++;
+ SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
+ if (rs->off >= rs->end)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Reading one svndiff window read "
+ "beyond the end of the "
+ "representation"));
+ }
+
+ /* Read the next window. But first, try to find it in the cache. */
+ SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* Actually read the next window. */
+ old_offset = rs->off;
+ stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
+ SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
+ rs->chunk_index++;
+ SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
+
+ if (rs->off > rs->end)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Reading one svndiff window read beyond "
+ "the end of the representation"));
+
+ /* the window has not been cached before, thus cache it now
+ * (if caching is used for them at all) */
+ return set_cached_window(*nwin, rs, old_offset, pool);
+}
+
+/* Read SIZE bytes from the representation RS and return it in *NWIN. */
+static svn_error_t *
+read_plain_window(svn_stringbuf_t **nwin, rep_state_t *rs,
+ apr_size_t size, apr_pool_t *pool)
+{
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+
+ /* Read the plain data. */
+ *nwin = svn_stringbuf_create_ensure(size, pool);
+ SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
+ pool));
+ (*nwin)->data[size] = 0;
+
+ /* Update RS. */
+ rs->off += (apr_off_t)size;
+
+ return SVN_NO_ERROR;
+}
+
+/* Get the undeltified window that is a result of combining all deltas
+ from the current desired representation identified in *RB with its
+ base representation. Store the window in *RESULT. */
+static svn_error_t *
+get_combined_window(svn_stringbuf_t **result,
+ struct rep_read_baton *rb)
+{
+ apr_pool_t *pool, *new_pool, *window_pool;
+ int i;
+ svn_txdelta_window_t *window;
+ apr_array_header_t *windows;
+ svn_stringbuf_t *source, *buf = rb->base_window;
+ rep_state_t *rs;
+
+ /* Read all windows that we need to combine. This is fine because
+ the size of each window is relatively small (100kB) and skip-
+ delta limits the number of deltas in a chain to well under 100.
+ Stop early if one of them does not depend on its predecessors. */
+ window_pool = svn_pool_create(rb->pool);
+ windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
+ for (i = 0; i < rb->rs_list->nelts; ++i)
+ {
+ rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *);
+ SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
+
+ APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
+ if (window->src_ops == 0)
+ {
+ ++i;
+ break;
+ }
+ }
+
+ /* Combine in the windows from the other delta reps. */
+ pool = svn_pool_create(rb->pool);
+ for (--i; i >= 0; --i)
+ {
+
+ rs = APR_ARRAY_IDX(rb->rs_list, i, rep_state_t *);
+ window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
+
+ /* Maybe, we've got a PLAIN start representation. If we do, read
+ as much data from it as the needed for the txdelta window's source
+ view.
+ Note that BUF / SOURCE may only be NULL in the first iteration. */
+ source = buf;
+ if (source == NULL && rb->src_state != NULL)
+ SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
+ pool));
+
+ /* Combine this window with the current one. */
+ new_pool = svn_pool_create(rb->pool);
+ buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
+ buf->len = window->tview_len;
+
+ svn_txdelta_apply_instructions(window, source ? source->data : NULL,
+ buf->data, &buf->len);
+ if (buf->len != window->tview_len)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("svndiff window length is "
+ "corrupt"));
+
+ /* Cache windows only if the whole rep content could be read as a
+ single chunk. Only then will no other chunk need a deeper RS
+ list than the cached chunk. */
+ if ((rb->chunk_index == 0) && (rs->off == rs->end))
+ SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
+
+ /* Cycle pools so that we only need to hold three windows at a time. */
+ svn_pool_destroy(pool);
+ pool = new_pool;
+ }
+
+ svn_pool_destroy(window_pool);
+
+ *result = buf;
+ return SVN_NO_ERROR;
+}
+
+/* Returns whether or not the expanded fulltext of the file is cachable
+ * based on its size SIZE. The decision depends on the cache used by RB.
+ */
+static svn_boolean_t
+fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
+{
+ return (size < APR_SIZE_MAX)
+ && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
+}
+
+/* Close method used on streams returned by read_representation().
+ */
+static svn_error_t *
+rep_read_contents_close(void *baton)
+{
+ struct rep_read_baton *rb = baton;
+
+ svn_pool_destroy(rb->pool);
+ svn_pool_destroy(rb->filehandle_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the next *LEN bytes of the rep and store them in *BUF. */
+static svn_error_t *
+get_contents(struct rep_read_baton *rb,
+ char *buf,
+ apr_size_t *len)
+{
+ apr_size_t copy_len, remaining = *len;
+ char *cur = buf;
+ rep_state_t *rs;
+
+ /* Special case for when there are no delta reps, only a plain
+ text. */
+ if (rb->rs_list->nelts == 0)
+ {
+ copy_len = remaining;
+ rs = rb->src_state;
+
+ if (rb->base_window != NULL)
+ {
+ /* We got the desired rep directly from the cache.
+ This is where we need the pseudo rep_state created
+ by build_rep_list(). */
+ apr_size_t offset = (apr_size_t)(rs->off - rs->start);
+ if (copy_len + offset > rb->base_window->len)
+ copy_len = offset < rb->base_window->len
+ ? rb->base_window->len - offset
+ : 0ul;
+
+ memcpy (cur, rb->base_window->data + offset, copy_len);
+ }
+ else
+ {
+ if (((apr_off_t) copy_len) > rs->end - rs->off)
+ copy_len = (apr_size_t) (rs->end - rs->off);
+ SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
+ NULL, rb->pool));
+ }
+
+ rs->off += copy_len;
+ *len = copy_len;
+ return SVN_NO_ERROR;
+ }
+
+ while (remaining > 0)
+ {
+ /* If we have buffered data from a previous chunk, use that. */
+ if (rb->buf)
+ {
+ /* Determine how much to copy from the buffer. */
+ copy_len = rb->buf_len - rb->buf_pos;
+ if (copy_len > remaining)
+ copy_len = remaining;
+
+ /* Actually copy the data. */
+ memcpy(cur, rb->buf + rb->buf_pos, copy_len);
+ rb->buf_pos += copy_len;
+ cur += copy_len;
+ remaining -= copy_len;
+
+ /* If the buffer is all used up, clear it and empty the
+ local pool. */
+ if (rb->buf_pos == rb->buf_len)
+ {
+ svn_pool_clear(rb->pool);
+ rb->buf = NULL;
+ }
+ }
+ else
+ {
+ svn_stringbuf_t *sbuf = NULL;
+
+ rs = APR_ARRAY_IDX(rb->rs_list, 0, rep_state_t *);
+ if (rs->off == rs->end)
+ break;
+
+ /* Get more buffered data by evaluating a chunk. */
+ SVN_ERR(get_combined_window(&sbuf, rb));
+
+ rb->chunk_index++;
+ rb->buf_len = sbuf->len;
+ rb->buf = sbuf->data;
+ rb->buf_pos = 0;
+ }
+ }
+
+ *len = cur - buf;
+
+ return SVN_NO_ERROR;
+}
+
+/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
+ representation and store them in *BUF. Sum as we read and verify
+ the MD5 sum at the end. */
+static svn_error_t *
+rep_read_contents(void *baton,
+ char *buf,
+ apr_size_t *len)
+{
+ struct rep_read_baton *rb = baton;
+
+ /* Get the next block of data. */
+ SVN_ERR(get_contents(rb, buf, len));
+
+ if (rb->current_fulltext)
+ svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
+
+ /* Perform checksumming. We want to check the checksum as soon as
+ the last byte of data is read, in case the caller never performs
+ a short read, but we don't want to finalize the MD5 context
+ twice. */
+ if (!rb->checksum_finalized)
+ {
+ SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
+ rb->off += *len;
+ if (rb->off == rb->len)
+ {
+ svn_checksum_t *md5_checksum;
+
+ rb->checksum_finalized = TRUE;
+ SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
+ rb->pool));
+ if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
+ return svn_error_create(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
+ rb->pool,
+ _("Checksum mismatch while reading representation")),
+ NULL);
+ }
+ }
+
+ if (rb->off == rb->len && rb->current_fulltext)
+ {
+ fs_fs_data_t *ffd = rb->fs->fsap_data;
+ SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
+ rb->current_fulltext, rb->pool));
+ rb->current_fulltext = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_contents(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ if (! rep)
+ {
+ *contents_p = svn_stream_empty(pool);
+ }
+ else
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t fulltext_cache_key;
+ svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
+ struct rep_read_baton *rb;
+
+ fulltext_cache_key.revision = rep->revision;
+ fulltext_cache_key.second = rep->offset;
+ if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
+ && fulltext_size_is_cachable(ffd, len))
+ {
+ svn_stringbuf_t *fulltext;
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
+ ffd->fulltext_cache, &fulltext_cache_key,
+ pool));
+ if (is_cached)
+ {
+ *contents_p = svn_stream_from_stringbuf(fulltext, pool);
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ fulltext_cache_key.revision = SVN_INVALID_REVNUM;
+
+ SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
+
+ *contents_p = svn_stream_create(rb, pool);
+ svn_stream_set_read(*contents_p, rep_read_contents);
+ svn_stream_set_close(*contents_p, rep_read_contents_close);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton for cache_access_wrapper. Wraps the original parameters of
+ * svn_fs_fs__try_process_file_content().
+ */
+typedef struct cache_access_wrapper_baton_t
+{
+ svn_fs_process_contents_func_t func;
+ void* baton;
+} cache_access_wrapper_baton_t;
+
+/* Wrapper to translate between svn_fs_process_contents_func_t and
+ * svn_cache__partial_getter_func_t.
+ */
+static svn_error_t *
+cache_access_wrapper(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ cache_access_wrapper_baton_t *wrapper_baton = baton;
+
+ SVN_ERR(wrapper_baton->func((const unsigned char *)data,
+ data_len - 1, /* cache adds terminating 0 */
+ wrapper_baton->baton,
+ pool));
+
+ /* non-NULL value to signal the calling cache that all went well */
+ *out = baton;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ representation_t *rep = noderev->data_rep;
+ if (rep)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t fulltext_cache_key;
+
+ fulltext_cache_key.revision = rep->revision;
+ fulltext_cache_key.second = rep->offset;
+ if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
+ && fulltext_size_is_cachable(ffd, rep->expanded_size))
+ {
+ cache_access_wrapper_baton_t wrapper_baton;
+ void *dummy = NULL;
+
+ wrapper_baton.func = processor;
+ wrapper_baton.baton = baton;
+ return svn_cache__get_partial(&dummy, success,
+ ffd->fulltext_cache,
+ &fulltext_cache_key,
+ cache_access_wrapper,
+ &wrapper_baton,
+ pool);
+ }
+ }
+
+ *success = FALSE;
+ return SVN_NO_ERROR;
+}
+
+/* Baton used when reading delta windows. */
+struct delta_read_baton
+{
+ struct rep_state_t *rs;
+ svn_checksum_t *checksum;
+};
+
+/* This implements the svn_txdelta_next_window_fn_t interface. */
+static svn_error_t *
+delta_read_next_window(svn_txdelta_window_t **window, void *baton,
+ apr_pool_t *pool)
+{
+ struct delta_read_baton *drb = baton;
+
+ if (drb->rs->off == drb->rs->end)
+ {
+ *window = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
+}
+
+/* This implements the svn_txdelta_md5_digest_fn_t interface. */
+static const unsigned char *
+delta_read_md5_digest(void *baton)
+{
+ struct delta_read_baton *drb = baton;
+
+ if (drb->checksum->kind == svn_checksum_md5)
+ return drb->checksum->digest;
+ else
+ return NULL;
+}
+
+svn_error_t *
+svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_t *fs,
+ node_revision_t *source,
+ node_revision_t *target,
+ apr_pool_t *pool)
+{
+ svn_stream_t *source_stream, *target_stream;
+
+ /* Try a shortcut: if the target is stored as a delta against the source,
+ then just use that delta. */
+ if (source && source->data_rep && target->data_rep)
+ {
+ rep_state_t *rep_state;
+ rep_args_t *rep_args;
+
+ /* Read target's base rep if any. */
+ SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
+ target->data_rep, fs, pool));
+ /* If that matches source, then use this delta as is. */
+ if (rep_args->is_delta
+ && (rep_args->is_delta_vs_empty
+ || (rep_args->base_revision == source->data_rep->revision
+ && rep_args->base_offset == source->data_rep->offset)))
+ {
+ /* Create the delta read baton. */
+ struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
+ drb->rs = rep_state;
+ drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
+ pool);
+ *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
+ delta_read_md5_digest, pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(svn_io_file_close(rep_state->file, pool));
+ }
+
+ /* Read both fulltexts and construct a delta. */
+ if (source)
+ SVN_ERR(svn_fs_fs__get_contents(&source_stream, fs, source->data_rep, pool));
+ else
+ source_stream = svn_stream_empty(pool);
+ SVN_ERR(svn_fs_fs__get_contents(&target_stream, fs, target->data_rep, pool));
+
+ /* Because source and target stream will already verify their content,
+ * there is no need to do this once more. In particular if the stream
+ * content is being fetched from cache. */
+ svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Fetch the contents of a directory into ENTRIES. Values are stored
+ as filename to string mappings; further conversion is necessary to
+ convert them into svn_fs_dirent_t values. */
+static svn_error_t *
+get_dir_contents(apr_hash_t *entries,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ svn_stream_t *contents;
+
+ if (noderev->data_rep && noderev->data_rep->txn_id)
+ {
+ const char *filename = path_txn_node_children(fs, noderev->id, pool);
+
+ /* The representation is mutable. Read the old directory
+ contents from the mutable children file, followed by the
+ changes we've made in this transaction. */
+ SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
+ SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
+ SVN_ERR(svn_stream_close(contents));
+ }
+ else if (noderev->data_rep)
+ {
+ /* use a temporary pool for temp objects.
+ * Also undeltify content before parsing it. Otherwise, we could only
+ * parse it byte-by-byte.
+ */
+ apr_pool_t *text_pool = svn_pool_create(pool);
+ apr_size_t len = noderev->data_rep->expanded_size
+ ? (apr_size_t)noderev->data_rep->expanded_size
+ : (apr_size_t)noderev->data_rep->size;
+ svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
+ text->len = len;
+
+ /* The representation is immutable. Read it normally. */
+ SVN_ERR(svn_fs_fs__get_contents(&contents, fs, noderev->data_rep, text_pool));
+ SVN_ERR(svn_stream_read(contents, text->data, &text->len));
+ SVN_ERR(svn_stream_close(contents));
+
+ /* de-serialize hash */
+ contents = svn_stream_from_stringbuf(text, text_pool);
+ SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
+
+ svn_pool_destroy(text_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a hash STR_ENTRIES with values as svn_string_t as specified
+ in an FSFS directory contents listing, return a hash of dirents in
+ *ENTRIES_P. Perform allocations in POOL. */
+static svn_error_t *
+parse_dir_entries(apr_hash_t **entries_p,
+ apr_hash_t *str_entries,
+ const char *unparsed_id,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ *entries_p = apr_hash_make(pool);
+
+ /* Translate the string dir entries into real entries. */
+ for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *str_val = svn__apr_hash_index_val(hi);
+ char *str, *last_str;
+ svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
+
+ last_str = apr_pstrdup(pool, str_val->data);
+ dirent->name = apr_pstrdup(pool, name);
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ unparsed_id);
+
+ if (strcmp(str, KIND_FILE) == 0)
+ {
+ dirent->kind = svn_node_file;
+ }
+ else if (strcmp(str, KIND_DIR) == 0)
+ {
+ dirent->kind = svn_node_dir;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ unparsed_id);
+ }
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ unparsed_id);
+
+ dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
+
+ apr_hash_set(*entries_p, dirent->name, APR_HASH_KEY_STRING, dirent);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the cache object in FS responsible to storing the directory
+ * the NODEREV. If none exists, return NULL. */
+static svn_cache__t *
+locate_dir_cache(svn_fs_t *fs,
+ node_revision_t *noderev)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return svn_fs_fs__id_txn_id(noderev->id)
+ ? ffd->txn_dir_cache
+ : ffd->dir_cache;
+}
+
+svn_error_t *
+svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ const char *unparsed_id = NULL;
+ apr_hash_t *unparsed_entries, *parsed_entries;
+
+ /* find the cache we may use */
+ svn_cache__t *cache = locate_dir_cache(fs, noderev);
+ if (cache)
+ {
+ svn_boolean_t found;
+
+ unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
+ SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
+ unparsed_id, pool));
+ if (found)
+ return SVN_NO_ERROR;
+ }
+
+ /* Read in the directory hash. */
+ unparsed_entries = apr_hash_make(pool);
+ SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
+ SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
+ unparsed_id, pool));
+
+ /* Update the cache, if we are to use one. */
+ if (cache)
+ SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
+
+ *entries_p = parsed_entries;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t found = FALSE;
+
+ /* find the cache we may use */
+ svn_cache__t *cache = locate_dir_cache(fs, noderev);
+ if (cache)
+ {
+ const char *unparsed_id =
+ svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
+
+ /* Cache lookup. */
+ SVN_ERR(svn_cache__get_partial((void **)dirent,
+ &found,
+ cache,
+ unparsed_id,
+ svn_fs_fs__extract_dir_entry,
+ (void*)name,
+ result_pool));
+ }
+
+ /* fetch data from disk if we did not find it in the cache */
+ if (! found)
+ {
+ apr_hash_t *entries;
+ svn_fs_dirent_t *entry;
+ svn_fs_dirent_t *entry_copy = NULL;
+
+ /* read the dir from the file system. It will probably be put it
+ into the cache for faster lookup in future calls. */
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
+ scratch_pool));
+
+ /* find desired entry and return a copy in POOL, if found */
+ entry = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
+ if (entry != NULL)
+ {
+ entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
+ entry_copy->name = apr_pstrdup(result_pool, entry->name);
+ entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
+ entry_copy->kind = entry->kind;
+ }
+
+ *dirent = entry_copy;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ apr_hash_t *proplist;
+ svn_stream_t *stream;
+
+ if (noderev->prop_rep && noderev->prop_rep->txn_id)
+ {
+ const char *filename = path_txn_node_props(fs, noderev->id, pool);
+ proplist = apr_hash_make(pool);
+
+ SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+ }
+ else if (noderev->prop_rep)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ representation_t *rep = noderev->prop_rep;
+ pair_cache_key_t key;
+
+ key.revision = rep->revision;
+ key.second = rep->offset;
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ {
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
+ ffd->properties_cache, &key, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ proplist = apr_hash_make(pool);
+ SVN_ERR(svn_fs_fs__get_contents(&stream, fs, noderev->prop_rep, pool));
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
+ }
+ else
+ {
+ /* return an empty prop list if the node doesn't have any props */
+ proplist = apr_hash_make(pool);
+ }
+
+ *proplist_p = proplist;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
+ * Allocate the result in POOL.
+ */
+svn_error_t *
+svn_fs_fs__get_changes(apr_array_header_t **changes,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ apr_off_t changes_offset;
+ apr_file_t *revision_file;
+ svn_boolean_t found;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* try cache lookup first */
+
+ if (ffd->changes_cache)
+ {
+ SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
+ &rev, pool));
+ if (found)
+ return SVN_NO_ERROR;
+ }
+
+ /* read changes from revision file */
+
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
+
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&revision_file, fs, rev, pool));
+
+ SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
+ rev, pool));
+
+ SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
+ SVN_ERR(read_all_changes(changes, revision_file, pool));
+
+ SVN_ERR(svn_io_file_close(revision_file, pool));
+
+ /* cache for future reference */
+
+ if (ffd->changes_cache)
+ SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
+
+ return SVN_NO_ERROR;
+}
+
Added: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.h?rev=1425472&view=auto
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.h (added)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/cached_data.h Sun Dec 23 15:17:47 2012
@@ -0,0 +1,129 @@
+/* cached_data.h --- cached (read) access to FSFS data
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__CACHED_DATA_H
+#define SVN_LIBSVN_FS__CACHED_DATA_H
+
+#include "svn_pools.h"
+#include "svn_fs.h"
+
+#include "fs.h"
+
+
+
+/* Set *NODEREV_P to the node-revision for the node ID in FS. Do any
+ allocations in POOL. */
+svn_error_t *
+svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Set *ROOT_ID to the node-id for the root of revision REV in
+ filesystem FS. Do any allocations in POOL. */
+svn_error_t *
+svn_fs_fs__rev_get_root(svn_fs_id_t **root_id,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Verify that representation REP in FS can be accessed.
+ Do any allocations in POOL. */
+svn_error_t *
+svn_fs_fs__check_rep(representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Set *CONTENTS to be a readable svn_stream_t that receives the text
+ representation REP as seen in filesystem FS.
+ Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__get_contents(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool);
+
+/* Attempt to fetch the text representation of node-revision NODEREV as
+ seen in filesystem FS and pass it along with the BATON to the PROCESSOR.
+ Set *SUCCESS only of the data could be provided and the processing
+ had been called.
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+
+/* Set *STREAM_P to a delta stream turning the contents of the file SOURCE into
+ the contents of the file TARGET, allocated in POOL.
+ If SOURCE is null, the empty string will be used. */
+svn_error_t *
+svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_t *fs,
+ node_revision_t *source,
+ node_revision_t *target,
+ apr_pool_t *pool);
+
+/* Set *ENTRIES to an apr_hash_t of dirent structs that contain the
+ directory entries of node-revision NODEREV in filesystem FS. The
+ returned table (and its keys and values) is allocated in POOL,
+ which is also used for temporary allocations. */
+svn_error_t *
+svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Set *DIRENT to the entry identified by NAME in the directory given
+ by NODEREV in filesystem FS. If no such entry exits, *DIRENT will
+ be NULL. The returned object is allocated in RESULT_POOL; SCRATCH_POOL
+ used for temporary allocations. */
+svn_error_t *
+svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *PROPLIST to be an apr_hash_t containing the property list of
+ node-revision NODEREV as seen in filesystem FS. Use POOL for
+ temporary allocations. */
+svn_error_t *
+svn_fs_fs__get_proplist(apr_hash_t **proplist,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
+ * Allocate the result in POOL.
+ */
+svn_error_t *
+svn_fs_fs__get_changes(apr_array_header_t **changes,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+#endif
\ No newline at end of file
Modified: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/dag.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/dag.c?rev=1425472&r1=1425471&r2=1425472&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/dag.c (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/dag.c Sun Dec 23 15:17:47 2012
@@ -33,6 +33,8 @@
#include "key-gen.h"
#include "fs_fs.h"
#include "id.h"
+#include "cached_data.h"
+#include "transaction.h"
#include "../libsvn_fs/fs-loader.h"
@@ -909,7 +911,7 @@ svn_fs_fs__dag_get_contents(svn_stream_t
/* Get a stream to the contents. */
SVN_ERR(svn_fs_fs__get_contents(&contents, file->fs,
- noderev, pool));
+ noderev->data_rep, pool));
*contents_p = contents;
Modified: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs.c?rev=1425472&r1=1425471&r2=1425472&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs.c (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs.c Sun Dec 23 15:17:47 2012
@@ -35,11 +35,15 @@
#include "svn_pools.h"
#include "fs.h"
#include "fs_fs.h"
+#include "pack.h"
+#include "recovery.h"
+#include "hotcopy.h"
#include "tree.h"
#include "lock.h"
#include "id.h"
#include "revprops.h"
#include "rep-cache.h"
+#include "transaction.h"
#include "svn_private_config.h"
#include "private/svn_fs_util.h"