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/11/30 00:19:55 UTC
svn commit: r1415451 [4/9] - in
/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs: fs.c fs_fs.c
fs_fs.h low_level.c low_level.h rep-cache.c revprops.c revprops.h util.c
util.h
Copied: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c (from r1413382, subversion/trunk/subversion/libsvn_fs_fs/fs_fs.c)
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c?p2=subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c&p1=subversion/trunk/subversion/libsvn_fs_fs/fs_fs.c&r1=1413382&r2=1415451&rev=1415451&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/fs_fs.c (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c Thu Nov 29 23:19:53 2012
@@ -1,4 +1,4 @@
-/* fs_fs.c --- filesystem operations specific to fs_fs
+/* low_level.c --- low level r/w access to fs_fs file structures
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -20,11268 +20,986 @@
* ====================================================================
*/
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <apr_general.h>
-#include <apr_pools.h>
-#include <apr_file_io.h>
-#include <apr_uuid.h>
-#include <apr_lib.h>
#include <apr_md5.h>
#include <apr_sha1.h>
-#include <apr_strings.h>
-#include <apr_thread_mutex.h>
+#include "svn_private_config.h"
#include "svn_pools.h"
-#include "svn_fs.h"
-#include "svn_dirent_uri.h"
-#include "svn_path.h"
-#include "svn_hash.h"
-#include "svn_props.h"
#include "svn_sorts.h"
-#include "svn_string.h"
-#include "svn_time.h"
-#include "svn_mergeinfo.h"
-#include "svn_config.h"
-#include "svn_ctype.h"
-
-#include "fs.h"
-#include "tree.h"
-#include "lock.h"
-#include "key-gen.h"
-#include "fs_fs.h"
-#include "id.h"
-#include "rep-cache.h"
-#include "temp_serializer.h"
-
-#include "private/svn_string_private.h"
-#include "private/svn_fs_util.h"
-#include "private/svn_subr_private.h"
-#include "private/svn_delta_private.h"
+
#include "../libsvn_fs/fs-loader.h"
-#include "svn_private_config.h"
-#include "temp_serializer.h"
+#include "low_level.h"
-/* An arbitrary maximum path length, so clients can't run us out of memory
- * by giving us arbitrarily large paths. */
-#define FSFS_MAX_PATH_LEN 4096
-
-/* The default maximum number of files per directory to store in the
- rev and revprops directory. The number below is somewhat arbitrary,
- and can be overridden by defining the macro while compiling; the
- figure of 1000 is reasonable for VFAT filesystems, which are by far
- the worst performers in this area. */
-#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
-#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
-#endif
-
-/* Begin deltification after a node history exceeded this this limit.
- Useful values are 4 to 64 with 16 being a good compromise between
- computational overhead and repository size savings.
- Should be a power of 2.
- Values < 2 will result in standard skip-delta behavior. */
-#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
-
-/* Finding a deltification base takes operations proportional to the
- number of changes being skipped. To prevent exploding runtime
- during commits, limit the deltification range to this value.
- Should be a power of 2 minus one.
- Values < 1 disable deltification. */
-#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
-
-/* Give writing processes 10 seconds to replace an existing revprop
- file with a new one. After that time, we assume that the writing
- process got aborted and that we have re-read revprops. */
-#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
-
-/* The following are names of atomics that will be used to communicate
- * revprop updates across all processes on this machine. */
-#define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
-#define ATOMIC_REVPROP_TIMEOUT "rev-prop-timeout"
-#define ATOMIC_REVPROP_NAMESPACE "rev-prop-atomics"
-
-/* Following are defines that specify the textual elements of the
- native filesystem directories and revision files. */
-
-/* Headers used to describe node-revision in the revision file. */
-#define HEADER_ID "id"
-#define HEADER_TYPE "type"
-#define HEADER_COUNT "count"
-#define HEADER_PROPS "props"
-#define HEADER_TEXT "text"
-#define HEADER_CPATH "cpath"
-#define HEADER_PRED "pred"
-#define HEADER_COPYFROM "copyfrom"
-#define HEADER_COPYROOT "copyroot"
-#define HEADER_FRESHTXNRT "is-fresh-txn-root"
-#define HEADER_MINFO_HERE "minfo-here"
-#define HEADER_MINFO_CNT "minfo-cnt"
-
-/* Kinds that a change can be. */
-#define ACTION_MODIFY "modify"
-#define ACTION_ADD "add"
-#define ACTION_DELETE "delete"
-#define ACTION_REPLACE "replace"
-#define ACTION_RESET "reset"
-
-/* True and False flags. */
-#define FLAG_TRUE "true"
-#define FLAG_FALSE "false"
-
-/* Kinds that a node-rev can be. */
-#define KIND_FILE "file"
-#define KIND_DIR "dir"
-
-/* Kinds of representation. */
-#define REP_PLAIN "PLAIN"
-#define REP_DELTA "DELTA"
-
-/* Notes:
-
-To avoid opening and closing the rev-files all the time, it would
-probably be advantageous to keep each rev-file open for the
-lifetime of the transaction object. I'll leave that as a later
-optimization for now.
-
-I didn't keep track of pool lifetimes at all in this code. There
-are likely some errors because of that.
-
-*/
-
-/* The vtable associated with an open transaction object. */
-static txn_vtable_t txn_vtable = {
- svn_fs_fs__commit_txn,
- svn_fs_fs__abort_txn,
- svn_fs_fs__txn_prop,
- svn_fs_fs__txn_proplist,
- svn_fs_fs__change_txn_prop,
- svn_fs_fs__txn_root,
- svn_fs_fs__change_txn_props
-};
-/* Declarations. */
+/* The 256 is an arbitrary size large enough to hold the node id and the
+ * various flags. */
+#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
-static svn_error_t *
-read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
- const char *path,
- apr_pool_t *pool);
+/* Given the last "few" bytes (should be at least 40) of revision REV in
+ * TRAILER, parse the last line and return the offset of the root noderev
+ * in *ROOT_OFFSET and the offset of the changes list in *CHANGES_OFFSET.
+ * All offsets are relative to the revision's start offset.
+ *
+ * Note that REV is only used to construct nicer error objects.
+ */
+svn_error_t *
+parse_revision_trailer(apr_off_t *root_offset,
+ apr_off_t *changes_offset,
+ svn_stringbuf_t *trailer,
+ svn_revnum_t rev)
+{
+ int i, num_bytes;
+ const char *str;
-static svn_error_t *
-update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
+ /* This cast should be safe since the maximum amount read, 64, will
+ never be bigger than the size of an int. */
+ num_bytes = (int) trailer->len;
-static svn_error_t *
-get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
+ /* The last byte should be a newline. */
+ if (trailer->len == 0 || trailer->data[trailer->len - 1] != '\n')
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision file (r%ld) lacks trailing newline"),
+ rev);
+ }
-/* Pathname helper functions */
+ /* Look for the next previous newline. */
+ for (i = num_bytes - 2; i >= 0; i--)
+ {
+ if (trailer->data[i] == '\n')
+ break;
+ }
-/* Return TRUE is REV is packed in FS, FALSE otherwise. */
-static svn_boolean_t
-is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ if (i < 0)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Final line in revision file (r%ld) longer "
+ "than 64 characters"),
+ rev);
+ }
- return (rev < ffd->min_unpacked_rev);
-}
+ i++;
+ str = &trailer->data[i];
-/* Return TRUE is REV is packed in FS, FALSE otherwise. */
-static svn_boolean_t
-is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ /* find the next space */
+ for ( ; i < (num_bytes - 2) ; i++)
+ if (trailer->data[i] == ' ')
+ break;
- /* rev 0 will not be packed */
- return (rev < ffd->min_unpacked_rev)
- && (rev != 0)
- && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
-}
+ if (i == (num_bytes - 2))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Final line in revision file r%ld missing space"),
+ rev);
-static const char *
-path_format(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_FORMAT, pool);
-}
+ if (root_offset)
+ {
+ apr_int64_t val;
-static APR_INLINE const char *
-path_uuid(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_UUID, pool);
-}
+ trailer->data[i] = '\0';
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ *root_offset = (apr_off_t)val;
+ }
-const char *
-svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_CURRENT, pool);
-}
+ i++;
+ str = &trailer->data[i];
-static APR_INLINE const char *
-path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
-}
+ /* find the next newline */
+ for ( ; i < num_bytes; i++)
+ if (trailer->data[i] == '\n')
+ break;
-static APR_INLINE const char *
-path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
-}
+ if (changes_offset)
+ {
+ apr_int64_t val;
-static APR_INLINE const char *
-path_lock(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
+ trailer->data[i] = '\0';
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ *changes_offset = (apr_off_t)val;
+ }
+
+ return SVN_NO_ERROR;
}
-static const char *
-path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
+/* Given the offset of the root noderev in ROOT_OFFSET and the offset of
+ * the changes list in CHANGES_OFFSET, return the corresponding revision's
+ * trailer. Allocate it in POOL.
+ */
+svn_stringbuf_t *
+unparse_revision_trailer(apr_off_t root_offset,
+ apr_off_t changes_offset,
+ apr_pool_t *pool)
{
- return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
+ return svn_stringbuf_createf(pool,
+ "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
+ root_offset,
+ changes_offset);
}
-static const char *
-path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
- apr_pool_t *pool)
+/* Given a revision file FILE that has been pre-positioned at the
+ beginning of a Node-Rev header block, read in that header block and
+ store it in the apr_hash_t HEADERS. All allocations will be from
+ POOL. */
+svn_error_t *
+read_header_block(apr_hash_t **headers,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- assert(ffd->max_files_per_dir);
- assert(is_packed_rev(fs, rev));
+ *headers = apr_hash_make(pool);
- return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
- apr_psprintf(pool,
- "%ld" PATH_EXT_PACKED_SHARD,
- rev / ffd->max_files_per_dir),
- kind, NULL);
-}
+ while (1)
+ {
+ svn_stringbuf_t *header_str;
+ const char *name, *value;
+ apr_size_t i = 0;
+ svn_boolean_t eof;
-static const char *
-path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
- assert(ffd->max_files_per_dir);
- return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
- apr_psprintf(pool, "%ld",
- rev / ffd->max_files_per_dir),
- NULL);
-}
+ if (eof || header_str->len == 0)
+ break; /* end of header block */
-static const char *
-path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ while (header_str->data[i] != ':')
+ {
+ if (header_str->data[i] == '\0')
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Found malformed header '%s' in "
+ "revision file"),
+ header_str->data);
+ i++;
+ }
- assert(! is_packed_rev(fs, rev));
+ /* Create a 'name' string and point to it. */
+ header_str->data[i] = '\0';
+ name = header_str->data;
- if (ffd->max_files_per_dir)
- {
- return svn_dirent_join(path_rev_shard(fs, rev, pool),
- apr_psprintf(pool, "%ld", rev),
- pool);
- }
+ /* Skip over the NULL byte and the space following it. */
+ i += 2;
- return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
- apr_psprintf(pool, "%ld", rev), NULL);
-}
+ if (i > header_str->len)
+ {
+ /* Restore the original line for the error. */
+ i -= 2;
+ header_str->data[i] = ':';
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Found malformed header '%s' in "
+ "revision file"),
+ header_str->data);
+ }
-svn_error_t *
-svn_fs_fs__path_rev_absolute(const char **path,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ value = header_str->data + i;
- if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
- || ! is_packed_rev(fs, rev))
- {
- *path = path_rev(fs, rev, pool);
- }
- else
- {
- *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
+ /* header_str is safely in our pool, so we can use bits of it as
+ key and value. */
+ apr_hash_set(*headers, name, APR_HASH_KEY_STRING, value);
}
return SVN_NO_ERROR;
}
-static const char *
-path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+/* Parse the description of a representation from STRING and store it
+ into *REP_P. If the representation is mutable (the revision is
+ given as -1), then use TXN_ID for the representation's txn_id
+ field. If MUTABLE_REP_TRUNCATED is true, then this representation
+ is for property or directory contents, and no information will be
+ expected except the "-1" revision number for a mutable
+ representation. Allocate *REP_P in POOL. */
+svn_error_t *
+read_rep_offsets_body(representation_t **rep_p,
+ char *string,
+ const char *txn_id,
+ svn_boolean_t mutable_rep_truncated,
+ apr_pool_t *pool)
{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- assert(ffd->max_files_per_dir);
- return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
- apr_psprintf(pool, "%ld",
- rev / ffd->max_files_per_dir),
- NULL);
-}
+ representation_t *rep;
+ char *str;
+ apr_int64_t val;
-static const char *
-path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
+ rep = apr_pcalloc(pool, sizeof(*rep));
+ *rep_p = rep;
- assert(ffd->max_files_per_dir);
- return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
- apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
- rev / ffd->max_files_per_dir),
- NULL);
-}
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
-static const char *
-path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- if (ffd->max_files_per_dir)
+ rep->revision = SVN_STR_TO_REV(str);
+ if (rep->revision == SVN_INVALID_REVNUM)
{
- return svn_dirent_join(path_revprops_shard(fs, rev, pool),
- apr_psprintf(pool, "%ld", rev),
- pool);
+ rep->txn_id = txn_id;
+ if (mutable_rep_truncated)
+ return SVN_NO_ERROR;
}
- return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
- apr_psprintf(pool, "%ld", rev), NULL);
-}
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
-static APR_INLINE const char *
-path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
- return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
- apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
- (char *)NULL),
- NULL);
-}
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep->offset = (apr_off_t)val;
-/* Return the name of the sha1->rep mapping file in transaction TXN_ID
- * within FS for the given SHA1 checksum. Use POOL for allocations.
- */
-static APR_INLINE const char *
-path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
- apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
- svn_checksum_to_cstring(sha1, pool),
- pool);
-}
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
-static APR_INLINE const char *
-path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
-}
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep->size = (svn_filesize_t)val;
-static APR_INLINE const char *
-path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
-}
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
-static APR_INLINE const char *
-path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
-}
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep->expanded_size = (svn_filesize_t)val;
-static APR_INLINE const char *
-path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
-}
+ /* Read in the MD5 hash. */
+ str = svn_cstring_tokenize(" ", &string);
+ if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+ SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
+ pool));
-static APR_INLINE const char *
-path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
- apr_pstrcat(pool, txn_id, PATH_EXT_REV,
- (char *)NULL),
- NULL);
- else
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
-}
+ /* The remaining fields are only used for formats >= 4, so check that. */
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return SVN_NO_ERROR;
-static APR_INLINE const char *
-path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
- apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
- (char *)NULL),
- NULL);
- else
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
- pool);
-}
+ /* Read the SHA1 hash. */
+ if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
-static const char *
-path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
- const char *txn_id = svn_fs_fs__id_txn_id(id);
- const char *node_id = svn_fs_fs__id_node_id(id);
- const char *copy_id = svn_fs_fs__id_copy_id(id);
- const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
- node_id, copy_id);
+ SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
+ pool));
- return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
-}
+ /* Read the uniquifier. */
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
-static APR_INLINE const char *
-path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
- return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
- (char *)NULL);
-}
+ rep->uniquifier = apr_pstrdup(pool, str);
-static APR_INLINE const char *
-path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
-{
- return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
- PATH_EXT_CHILDREN, (char *)NULL);
+ return SVN_NO_ERROR;
}
-static APR_INLINE const char *
-path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
-{
- size_t len = strlen(node_id);
- const char *node_id_minus_last_char =
- (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
- return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
- node_id_minus_last_char, NULL);
-}
-
-static APR_INLINE const char *
-path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
-{
- const char *path;
- apr_off_t offset = 0;
-
- if (apr_file_name_get(&path, file) != APR_SUCCESS)
- path = "(unknown)";
-
- if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
- offset = -1;
-
- return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
-}
-
-
-
-/* Functions for working with shared transaction data. */
-
-/* Return the transaction object for transaction TXN_ID from the
- transaction list of filesystem FS (which must already be locked via the
- txn_list_lock mutex). If the transaction does not exist in the list,
- then create a new transaction object and return it (if CREATE_NEW is
- true) or return NULL (otherwise). */
-static fs_fs_shared_txn_data_t *
-get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
- fs_fs_shared_txn_data_t *txn;
-
- for (txn = ffsd->txns; txn; txn = txn->next)
- if (strcmp(txn->txn_id, txn_id) == 0)
- break;
-
- if (txn || !create_new)
- return txn;
-
- /* Use the transaction object from the (single-object) freelist,
- if one is available, or otherwise create a new object. */
- if (ffsd->free_txn)
- {
- txn = ffsd->free_txn;
- ffsd->free_txn = NULL;
- }
- else
- {
- apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
- txn = apr_palloc(subpool, sizeof(*txn));
- txn->pool = subpool;
- }
-
- assert(strlen(txn_id) < sizeof(txn->txn_id));
- apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
- txn->being_written = FALSE;
-
- /* Link this transaction into the head of the list. We will typically
- be dealing with only one active transaction at a time, so it makes
- sense for searches through the transaction list to look at the
- newest transactions first. */
- txn->next = ffsd->txns;
- ffsd->txns = txn;
-
- return txn;
-}
-
-/* Free the transaction object for transaction TXN_ID, and remove it
- from the transaction list of filesystem FS (which must already be
- locked via the txn_list_lock mutex). Do nothing if the transaction
- does not exist. */
-static void
-free_shared_txn(svn_fs_t *fs, const char *txn_id)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
- fs_fs_shared_txn_data_t *txn, *prev = NULL;
-
- for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
- if (strcmp(txn->txn_id, txn_id) == 0)
- break;
-
- if (!txn)
- return;
-
- if (prev)
- prev->next = txn->next;
- else
- ffsd->txns = txn->next;
-
- /* As we typically will be dealing with one transaction after another,
- we will maintain a single-object free list so that we can hopefully
- keep reusing the same transaction object. */
- if (!ffsd->free_txn)
- ffsd->free_txn = txn;
- else
- svn_pool_destroy(txn->pool);
-}
-
-
-/* Obtain a lock on the transaction list of filesystem FS, call BODY
- with FS, BATON, and POOL, and then unlock the transaction list.
- Return what BODY returned. */
-static svn_error_t *
-with_txnlist_lock(svn_fs_t *fs,
- svn_error_t *(*body)(svn_fs_t *fs,
- const void *baton,
- apr_pool_t *pool),
- const void *baton,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
-
- SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
- body(fs, baton, pool));
-
- return SVN_NO_ERROR;
-}
-
-
-/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
-static svn_error_t *
-get_lock_on_filesystem(const char *lock_filename,
- apr_pool_t *pool)
-{
- svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
-
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- /* No lock file? No big deal; these are just empty files
- anyway. Create it and try again. */
- svn_error_clear(err);
- err = NULL;
-
- SVN_ERR(svn_io_file_create(lock_filename, "", pool));
- SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
- }
-
- return svn_error_trace(err);
-}
-
-/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
- When registered with the pool holding the lock on the lock file,
- this makes sure the flag gets reset just before we release the lock. */
-static apr_status_t
-reset_lock_flag(void *baton_void)
-{
- fs_fs_data_t *ffd = baton_void;
- ffd->has_write_lock = FALSE;
- return APR_SUCCESS;
-}
-
-/* Obtain a write lock on the file LOCK_FILENAME (protecting with
- LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
- BATON and that subpool, destroy the subpool (releasing the write
- lock) and return what BODY returned. If IS_GLOBAL_LOCK is set,
- set the HAS_WRITE_LOCK flag while we keep the write lock. */
-static svn_error_t *
-with_some_lock_file(svn_fs_t *fs,
- svn_error_t *(*body)(void *baton,
- apr_pool_t *pool),
- void *baton,
- const char *lock_filename,
- svn_boolean_t is_global_lock,
- apr_pool_t *pool)
-{
- apr_pool_t *subpool = svn_pool_create(pool);
- svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
-
- if (!err)
- {
- fs_fs_data_t *ffd = fs->fsap_data;
-
- if (is_global_lock)
- {
- /* set the "got the lock" flag and register reset function */
- apr_pool_cleanup_register(subpool,
- ffd,
- reset_lock_flag,
- apr_pool_cleanup_null);
- ffd->has_write_lock = TRUE;
- }
-
- /* nobody else will modify the repo state
- => read HEAD & pack info once */
- if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(update_min_unpacked_rev(fs, pool));
- SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
- pool));
- err = body(baton, subpool);
- }
-
- svn_pool_destroy(subpool);
-
- return svn_error_trace(err);
-}
-
-svn_error_t *
-svn_fs_fs__with_write_lock(svn_fs_t *fs,
- svn_error_t *(*body)(void *baton,
- apr_pool_t *pool),
- void *baton,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
-
- SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
- with_some_lock_file(fs, body, baton,
- path_lock(fs, pool),
- TRUE,
- pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Run BODY (with BATON and POOL) while the txn-current file
- of FS is locked. */
-static svn_error_t *
-with_txn_current_lock(svn_fs_t *fs,
- svn_error_t *(*body)(void *baton,
- apr_pool_t *pool),
- void *baton,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- fs_fs_shared_data_t *ffsd = ffd->shared;
-
- SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
- with_some_lock_file(fs, body, baton,
- path_txn_current_lock(fs, pool),
- FALSE,
- pool));
-
- return SVN_NO_ERROR;
-}
-
-/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
- which see. */
-struct unlock_proto_rev_baton
-{
- const char *txn_id;
- void *lockcookie;
-};
-
-/* Callback used in the implementation of unlock_proto_rev(). */
-static svn_error_t *
-unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
- const struct unlock_proto_rev_baton *b = baton;
- const char *txn_id = b->txn_id;
- apr_file_t *lockfile = b->lockcookie;
- fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
- apr_status_t apr_err;
-
- if (!txn)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Can't unlock unknown transaction '%s'"),
- txn_id);
- if (!txn->being_written)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Can't unlock nonlocked transaction '%s'"),
- txn_id);
-
- apr_err = apr_file_unlock(lockfile);
- if (apr_err)
- return svn_error_wrap_apr
- (apr_err,
- _("Can't unlock prototype revision lockfile for transaction '%s'"),
- txn_id);
- apr_err = apr_file_close(lockfile);
- if (apr_err)
- return svn_error_wrap_apr
- (apr_err,
- _("Can't close prototype revision lockfile for transaction '%s'"),
- txn_id);
-
- txn->being_written = FALSE;
-
- return SVN_NO_ERROR;
-}
-
-/* Unlock the prototype revision file for transaction TXN_ID in filesystem
- FS using cookie LOCKCOOKIE. The original prototype revision file must
- have been closed _before_ calling this function.
-
- Perform temporary allocations in POOL. */
-static svn_error_t *
-unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
- apr_pool_t *pool)
-{
- struct unlock_proto_rev_baton b;
-
- b.txn_id = txn_id;
- b.lockcookie = lockcookie;
- return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
-}
-
-/* Same as unlock_proto_rev(), but requires that the transaction list
- lock is already held. */
-static svn_error_t *
-unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
- void *lockcookie,
- apr_pool_t *pool)
-{
- struct unlock_proto_rev_baton b;
-
- b.txn_id = txn_id;
- b.lockcookie = lockcookie;
- return unlock_proto_rev_body(fs, &b, pool);
-}
-
-/* A structure used by get_writable_proto_rev() and
- get_writable_proto_rev_body(), which see. */
-struct get_writable_proto_rev_baton
-{
- apr_file_t **file;
- void **lockcookie;
- const char *txn_id;
-};
-
-/* Callback used in the implementation of get_writable_proto_rev(). */
-static svn_error_t *
-get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
- const struct get_writable_proto_rev_baton *b = baton;
- apr_file_t **file = b->file;
- void **lockcookie = b->lockcookie;
- const char *txn_id = b->txn_id;
- svn_error_t *err;
- fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
-
- /* First, ensure that no thread in this process (including this one)
- is currently writing to this transaction's proto-rev file. */
- if (txn->being_written)
- return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
- _("Cannot write to the prototype revision file "
- "of transaction '%s' because a previous "
- "representation is currently being written by "
- "this process"),
- txn_id);
-
-
- /* We know that no thread in this process is writing to the proto-rev
- file, and by extension, that no thread in this process is holding a
- lock on the prototype revision lock file. It is therefore safe
- for us to attempt to lock this file, to see if any other process
- is holding a lock. */
-
- {
- apr_file_t *lockfile;
- apr_status_t apr_err;
- const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
-
- /* Open the proto-rev lockfile, creating it if necessary, as it may
- not exist if the transaction dates from before the lockfiles were
- introduced.
-
- ### We'd also like to use something like svn_io_file_lock2(), but
- that forces us to create a subpool just to be able to unlock
- the file, which seems a waste. */
- SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
- APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
-
- apr_err = apr_file_lock(lockfile,
- APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
- if (apr_err)
- {
- svn_error_clear(svn_io_file_close(lockfile, pool));
-
- if (APR_STATUS_IS_EAGAIN(apr_err))
- return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
- _("Cannot write to the prototype revision "
- "file of transaction '%s' because a "
- "previous representation is currently "
- "being written by another process"),
- txn_id);
-
- return svn_error_wrap_apr(apr_err,
- _("Can't get exclusive lock on file '%s'"),
- svn_dirent_local_style(lockfile_path, pool));
- }
-
- *lockcookie = lockfile;
- }
-
- /* We've successfully locked the transaction; mark it as such. */
- txn->being_written = TRUE;
-
-
- /* Now open the prototype revision file and seek to the end. */
- err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
- APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
-
- /* You might expect that we could dispense with the following seek
- and achieve the same thing by opening the file using APR_APPEND.
- Unfortunately, APR's buffered file implementation unconditionally
- places its initial file pointer at the start of the file (even for
- files opened with APR_APPEND), so we need this seek to reconcile
- the APR file pointer to the OS file pointer (since we need to be
- able to read the current file position later). */
- if (!err)
- {
- apr_off_t offset = 0;
- err = svn_io_file_seek(*file, APR_END, &offset, pool);
- }
-
- if (err)
- {
- err = svn_error_compose_create(
- err,
- unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
-
- *lockcookie = NULL;
- }
-
- return svn_error_trace(err);
-}
-
-/* Get a handle to the prototype revision file for transaction TXN_ID in
- filesystem FS, and lock it for writing. Return FILE, a file handle
- positioned at the end of the file, and LOCKCOOKIE, a cookie that
- should be passed to unlock_proto_rev() to unlock the file once FILE
- has been closed.
-
- If the prototype revision file is already locked, return error
- SVN_ERR_FS_REP_BEING_WRITTEN.
-
- Perform all allocations in POOL. */
-static svn_error_t *
-get_writable_proto_rev(apr_file_t **file,
- void **lockcookie,
- svn_fs_t *fs, const char *txn_id,
- apr_pool_t *pool)
-{
- struct get_writable_proto_rev_baton b;
-
- b.file = file;
- b.lockcookie = lockcookie;
- b.txn_id = txn_id;
-
- return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
-}
-
-/* Callback used in the implementation of purge_shared_txn(). */
-static svn_error_t *
-purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
-{
- const char *txn_id = baton;
-
- free_shared_txn(fs, txn_id);
- svn_fs_fs__reset_txn_caches(fs);
-
- return SVN_NO_ERROR;
-}
-
-/* Purge the shared data for transaction TXN_ID in filesystem FS.
- Perform all allocations in POOL. */
-static svn_error_t *
-purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
-{
- return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
-}
-
-
-
-/* Fetch the current offset of FILE into *OFFSET_P. */
-static svn_error_t *
-get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
-{
- apr_off_t offset;
-
- /* Note that, for buffered files, one (possibly surprising) side-effect
- of this call is to flush any unwritten data to disk. */
- offset = 0;
- SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
- *offset_p = offset;
-
- return SVN_NO_ERROR;
-}
-
-
-/* Check that BUF, a nul-terminated buffer of text from file PATH,
- contains only digits at OFFSET and beyond, raising an error if not.
- TITLE contains a user-visible description of the file, usually the
- short file name.
-
- Uses POOL for temporary allocation. */
-static svn_error_t *
-check_file_buffer_numeric(const char *buf, apr_off_t offset,
- const char *path, const char *title,
- apr_pool_t *pool)
-{
- const char *p;
-
- for (p = buf + offset; *p; p++)
- if (!svn_ctype_isdigit(*p))
- return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
- _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
- title, svn_dirent_local_style(path, pool), *p, buf);
-
- return SVN_NO_ERROR;
-}
-
-/* Check that BUF, a nul-terminated buffer of text from format file PATH,
- contains only digits at OFFSET and beyond, raising an error if not.
-
- Uses POOL for temporary allocation. */
-static svn_error_t *
-check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
- const char *path, apr_pool_t *pool)
-{
- return check_file_buffer_numeric(buf, offset, path, "Format", pool);
-}
-
-/* Read the format number and maximum number of files per directory
- from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
- respectively.
-
- *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
- will be set to zero if a linear scheme should be used.
-
- Use POOL for temporary allocation. */
-static svn_error_t *
-read_format(int *pformat, int *max_files_per_dir,
- const char *path, apr_pool_t *pool)
-{
- svn_error_t *err;
- svn_stream_t *stream;
- svn_stringbuf_t *content;
- svn_stringbuf_t *buf;
- svn_boolean_t eos = FALSE;
-
- err = svn_stringbuf_from_file2(&content, path, pool);
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- /* Treat an absent format file as format 1. Do not try to
- create the format file on the fly, because the repository
- might be read-only for us, or this might be a read-only
- operation, and the spirit of FSFS is to make no changes
- whatseover in read-only operations. See thread starting at
- http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
- for more. */
- svn_error_clear(err);
- *pformat = 1;
- *max_files_per_dir = 0;
-
- return SVN_NO_ERROR;
- }
- SVN_ERR(err);
-
- stream = svn_stream_from_stringbuf(content, pool);
- SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
- if (buf->len == 0 && eos)
- {
- /* Return a more useful error message. */
- return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
- _("Can't read first line of format file '%s'"),
- svn_dirent_local_style(path, pool));
- }
-
- /* Check that the first line contains only digits. */
- SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
- SVN_ERR(svn_cstring_atoi(pformat, buf->data));
-
- /* Set the default values for anything that can be set via an option. */
- *max_files_per_dir = 0;
-
- /* Read any options. */
- while (!eos)
- {
- SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
- if (buf->len == 0)
- break;
-
- if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
- strncmp(buf->data, "layout ", 7) == 0)
- {
- if (strcmp(buf->data + 7, "linear") == 0)
- {
- *max_files_per_dir = 0;
- continue;
- }
-
- if (strncmp(buf->data + 7, "sharded ", 8) == 0)
- {
- /* Check that the argument is numeric. */
- SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
- SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
- continue;
- }
- }
-
- return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
- _("'%s' contains invalid filesystem format option '%s'"),
- svn_dirent_local_style(path, pool), buf->data);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Write the format number and maximum number of files per directory
- to a new format file in PATH, possibly expecting to overwrite a
- previously existing file.
-
- Use POOL for temporary allocation. */
-static svn_error_t *
-write_format(const char *path, int format, int max_files_per_dir,
- svn_boolean_t overwrite, apr_pool_t *pool)
-{
- svn_stringbuf_t *sb;
-
- SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
-
- sb = svn_stringbuf_createf(pool, "%d\n", format);
-
- if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
- {
- if (max_files_per_dir)
- svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
- max_files_per_dir));
- else
- svn_stringbuf_appendcstr(sb, "layout linear\n");
- }
-
- /* svn_io_write_version_file() does a load of magic to allow it to
- replace version files that already exist. We only need to do
- that when we're allowed to overwrite an existing file. */
- if (! overwrite)
- {
- /* Create the file */
- SVN_ERR(svn_io_file_create(path, sb->data, pool));
- }
- else
- {
- const char *path_tmp;
-
- SVN_ERR(svn_io_write_unique(&path_tmp,
- svn_dirent_dirname(path, pool),
- sb->data, sb->len,
- svn_io_file_del_none, pool));
-
- /* rename the temp file as the real destination */
- SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
- }
-
- /* And set the perms to make it read only */
- return svn_io_set_file_read_only(path, FALSE, pool);
-}
-
-/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
- number is not the same as a format number supported by this
- Subversion. */
-static svn_error_t *
-check_format(int format)
-{
- /* Blacklist. These formats may be either younger or older than
- SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
- if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
- return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
- _("Found format '%d', only created by "
- "unreleased dev builds; see "
- "http://subversion.apache.org"
- "/docs/release-notes/1.7#revprop-packing"),
- format);
-
- /* We support all formats from 1-current simultaneously */
- if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
- return SVN_NO_ERROR;
-
- return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
- _("Expected FS format between '1' and '%d'; found format '%d'"),
- SVN_FS_FS__FORMAT_NUMBER, format);
-}
-
-svn_boolean_t
-svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
-}
-
-/* Read the configuration information of the file system at FS_PATH
- * and set the respective values in FFD. Use POOL for allocations.
- */
-static svn_error_t *
-read_config(fs_fs_data_t *ffd,
- const char *fs_path,
- apr_pool_t *pool)
-{
- SVN_ERR(svn_config_read2(&ffd->config,
- svn_dirent_join(fs_path, PATH_CONFIG, pool),
- FALSE, FALSE, pool));
-
- /* Initialize ffd->rep_sharing_allowed. */
- if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed,
- CONFIG_SECTION_REP_SHARING,
- CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
- else
- ffd->rep_sharing_allowed = FALSE;
-
- /* Initialize deltification settings in ffd. */
- if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
- {
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories,
- CONFIG_SECTION_DELTIFICATION,
- CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
- FALSE));
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties,
- CONFIG_SECTION_DELTIFICATION,
- CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
- FALSE));
- SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk,
- CONFIG_SECTION_DELTIFICATION,
- CONFIG_OPTION_MAX_DELTIFICATION_WALK,
- SVN_FS_FS_MAX_DELTIFICATION_WALK));
- SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification,
- CONFIG_SECTION_DELTIFICATION,
- CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
- SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
- }
- else
- {
- ffd->deltify_directories = FALSE;
- ffd->deltify_properties = FALSE;
- ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
- ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
- }
-
- /* Initialize revprop packing settings in ffd. */
- if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
- {
- SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops,
- CONFIG_SECTION_PACKED_REVPROPS,
- CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
- FALSE));
- SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size,
- CONFIG_SECTION_PACKED_REVPROPS,
- CONFIG_OPTION_REVPROP_PACK_SIZE,
- ffd->compress_packed_revprops
- ? 0x100
- : 0x40));
-
- ffd->revprop_pack_size *= 1024;
- }
- else
- {
- ffd->revprop_pack_size = 0x10000;
- ffd->compress_packed_revprops = FALSE;
- }
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-write_config(svn_fs_t *fs,
- apr_pool_t *pool)
-{
-#define NL APR_EOL_STR
- static const char * const fsfs_conf_contents =
-"### This file controls the configuration of the FSFS filesystem." NL
-"" NL
-"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL
-"### These options name memcached servers used to cache internal FSFS" NL
-"### data. See http://www.danga.com/memcached/ for more information on" NL
-"### memcached. To use memcached with FSFS, run one or more memcached" NL
-"### servers, and specify each of them as an option like so:" NL
-"# first-server = 127.0.0.1:11211" NL
-"# remote-memcached = mymemcached.corp.example.com:11212" NL
-"### The option name is ignored; the value is of the form HOST:PORT." NL
-"### memcached servers can be shared between multiple repositories;" NL
-"### however, if you do this, you *must* ensure that repositories have" NL
-"### distinct UUIDs and paths, or else cached data from one repository" NL
-"### might be used by another accidentally. Note also that memcached has" NL
-"### no authentication for reads or writes, so you must ensure that your" NL
-"### memcached servers are only accessible by trusted users." NL
-"" NL
-"[" CONFIG_SECTION_CACHES "]" NL
-"### When a cache-related error occurs, normally Subversion ignores it" NL
-"### and continues, logging an error if the server is appropriately" NL
-"### configured (and ignoring it with file:// access). To make" NL
-"### Subversion never ignore cache errors, uncomment this line." NL
-"# " CONFIG_OPTION_FAIL_STOP " = true" NL
-"" NL
-"[" CONFIG_SECTION_REP_SHARING "]" NL
-"### To conserve space, the filesystem can optionally avoid storing" NL
-"### duplicate representations. This comes at a slight cost in" NL
-"### performance, as maintaining a database of shared representations can" NL
-"### increase commit times. The space savings are dependent upon the size" NL
-"### of the repository, the number of objects it contains and the amount of" NL
-"### duplication between them, usually a function of the branching and" NL
-"### merging process." NL
-"###" NL
-"### The following parameter enables rep-sharing in the repository. It can" NL
-"### be switched on and off at will, but for best space-saving results" NL
-"### should be enabled consistently over the life of the repository." NL
-"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
-"### rep-sharing is enabled by default." NL
-"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true" NL
-"" NL
-"[" CONFIG_SECTION_DELTIFICATION "]" NL
-"### To conserve space, the filesystem stores data as differences against" NL
-"### existing representations. This comes at a slight cost in performance," NL
-"### as calculating differences can increase commit times. Reading data" NL
-"### will also create higher CPU load and the data will be fragmented." NL
-"### Since deltification tends to save significant amounts of disk space," NL
-"### the overall I/O load can actually be lower." NL
-"###" NL
-"### The options in this section allow for tuning the deltification" NL
-"### strategy. Their effects on data size and server performance may vary" NL
-"### from one repository to another. Versions prior to 1.8 will ignore" NL
-"### this section." NL
-"###" NL
-"### The following parameter enables deltification for directories. It can" NL
-"### be switched on and off at will, but for best space-saving results" NL
-"### should be enabled consistently over the life of the repository." NL
-"### Repositories containing large directories will benefit greatly." NL
-"### In rarely read repositories, the I/O overhead may be significant as" NL
-"### cache hit rates will most likely be low" NL
-"### directory deltification is disabled by default." NL
-"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false" NL
-"###" NL
-"### The following parameter enables deltification for properties on files" NL
-"### and directories. Overall, this is a minor tuning option but can save" NL
-"### some disk space if you merge frequently or frequently change node" NL
-"### properties. You should not activate this if rep-sharing has been" NL
-"### disabled because this may result in a net increase in repository size." NL
-"### property deltification is disabled by default." NL
-"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false" NL
-"###" NL
-"### During commit, the server may need to walk the whole change history of" NL
-"### of a given node to find a suitable deltification base. This linear" NL
-"### process can impact commit times, svnadmin load and similar operations." NL
-"### This setting limits the depth of the deltification history. If the" NL
-"### threshold has been reached, the node will be stored as fulltext and a" NL
-"### new deltification history begins." NL
-"### Note, this is unrelated to svn log." NL
-"### Very large values rarely provide significant additional savings but" NL
-"### can impact performance greatly - in particular if directory" NL
-"### deltification has been activated. Very small values may be useful in" NL
-"### repositories that are dominated by large, changing binaries." NL
-"### Should be a power of two minus 1. A value of 0 will effectively" NL
-"### disable deltification." NL
-"### For 1.8, the default value is 1023; earlier versions have no limit." NL
-"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023" NL
-"###" NL
-"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
-"### delta information where a simple delta against the latest version is" NL
-"### often smaller. By default, 1.8+ will therefore use skip deltas only" NL
-"### after the linear chain of deltas has grown beyond the threshold" NL
-"### specified by this setting." NL
-"### Values up to 64 can result in some reduction in repository size for" NL
-"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller" NL
-"### numbers can reduce those costs at the cost of more disk space. For" NL
-"### rarely read repositories or those containing larger binaries, this may" NL
-"### present a better trade-off." NL
-"### Should be a power of two. A value of 1 or smaller will cause the" NL
-"### exclusive use of skip-deltas (as in pre-1.8)." NL
-"### For 1.8, the default value is 16; earlier versions use 1." NL
-"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL
-"" NL
-"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL
-"### This parameter controls the size (in kBytes) of packed revprop files." NL
-"### Revprops of consecutive revisions will be concatenated into a single" NL
-"### file up to but not exceeding the threshold given here. However, each" NL
-"### pack file may be much smaller and revprops of a single revision may be" NL
-"### much larger than the limit set here. The threshold will be applied" NL
-"### before optional compression takes place." NL
-"### Large values will reduce disk space usage at the expense of increased" NL
-"### latency and CPU usage reading and changing individual revprops. They" NL
-"### become an advantage when revprop caching has been enabled because a" NL
-"### lot of data can be read in one go. Values smaller than 4 kByte will" NL
-"### not improve latency any further and quickly render revprop packing" NL
-"### ineffective." NL
-"### revprop-pack-size is 64 kBytes by default for non-compressed revprop" NL
-"### pack files and 256 kBytes when compression has been enabled." NL
-"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64" NL
-"###" NL
-"### To save disk space, packed revprop files may be compressed. Standard" NL
-"### revprops tend to allow for very effective compression. Reading and" NL
-"### even more so writing, become significantly more CPU intensive. With" NL
-"### revprop caching enabled, the overhead can be offset by reduced I/O" NL
-"### unless you often modify revprops after packing." NL
-"### Compressing packed revprops is disabled by default." NL
-"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false" NL
-;
-#undef NL
- return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
- fsfs_conf_contents, pool);
-}
-
-static svn_error_t *
-read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
- const char *path,
- apr_pool_t *pool)
-{
- char buf[80];
- apr_file_t *file;
- apr_size_t len;
-
- SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
- APR_OS_DEFAULT, pool));
- len = sizeof(buf);
- SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
- SVN_ERR(svn_io_file_close(file, pool));
-
- *min_unpacked_rev = SVN_STR_TO_REV(buf);
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
-
- return read_min_unpacked_rev(&ffd->min_unpacked_rev,
- path_min_unpacked_rev(fs, pool),
- pool);
-}
-
-svn_error_t *
-svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- apr_file_t *uuid_file;
- int format, max_files_per_dir;
- char buf[APR_UUID_FORMATTED_LENGTH + 2];
- apr_size_t limit;
-
- fs->path = apr_pstrdup(fs->pool, path);
-
- /* Read the FS format number. */
- SVN_ERR(read_format(&format, &max_files_per_dir,
- path_format(fs, pool), pool));
- SVN_ERR(check_format(format));
-
- /* Now we've got a format number no matter what. */
- ffd->format = format;
- ffd->max_files_per_dir = max_files_per_dir;
-
- /* Read in and cache the repository uuid. */
- SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
-
- limit = sizeof(buf);
- SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
- fs->uuid = apr_pstrdup(fs->pool, buf);
-
- SVN_ERR(svn_io_file_close(uuid_file, pool));
-
- /* Read the min unpacked revision. */
- if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(update_min_unpacked_rev(fs, pool));
-
- /* Read the configuration file. */
- SVN_ERR(read_config(ffd, fs->path, pool));
-
- return get_youngest(&(ffd->youngest_rev_cache), path, pool);
-}
-
-/* Wrapper around svn_io_file_create which ignores EEXIST. */
-static svn_error_t *
-create_file_ignore_eexist(const char *file,
- const char *contents,
- apr_pool_t *pool)
-{
- svn_error_t *err = svn_io_file_create(file, contents, pool);
- if (err && APR_STATUS_IS_EEXIST(err->apr_err))
- {
- svn_error_clear(err);
- err = SVN_NO_ERROR;
- }
- return svn_error_trace(err);
-}
-
-/* forward declarations */
-
-static svn_error_t *
-pack_revprops_shard(const char *pack_file_dir,
- const char *shard_path,
- apr_int64_t shard,
- int max_files_per_dir,
- apr_off_t max_pack_size,
- int compression_level,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool);
-
-static svn_error_t *
-delete_revprops_shard(const char *shard_path,
- apr_int64_t shard,
- int max_files_per_dir,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool);
-
-/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
- * Use SCRATCH_POOL for temporary allocations.
- */
-static svn_error_t *
-upgrade_pack_revprops(svn_fs_t *fs,
- apr_pool_t *scratch_pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- const char *revprops_shard_path;
- const char *revprops_pack_file_dir;
- apr_int64_t shard;
- apr_int64_t first_unpacked_shard
- = ffd->min_unpacked_rev / ffd->max_files_per_dir;
-
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
- scratch_pool);
- int compression_level = ffd->compress_packed_revprops
- ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
- : SVN_DELTA_COMPRESSION_LEVEL_NONE;
-
- /* first, pack all revprops shards to match the packed revision shards */
- for (shard = 0; shard < first_unpacked_shard; ++shard)
- {
- revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
- apr_psprintf(iterpool,
- "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
- shard),
- iterpool);
- revprops_shard_path = svn_dirent_join(revsprops_dir,
- apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
- iterpool);
-
- SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
- shard, ffd->max_files_per_dir,
- (int)(0.9 * ffd->revprop_pack_size),
- compression_level,
- NULL, NULL, iterpool));
- svn_pool_clear(iterpool);
- }
-
- /* delete the non-packed revprops shards afterwards */
- for (shard = 0; shard < first_unpacked_shard; ++shard)
- {
- revprops_shard_path = svn_dirent_join(revsprops_dir,
- apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
- iterpool);
- SVN_ERR(delete_revprops_shard(revprops_shard_path,
- shard, ffd->max_files_per_dir,
- NULL, NULL, iterpool));
- svn_pool_clear(iterpool);
- }
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-upgrade_body(void *baton, apr_pool_t *pool)
-{
- svn_fs_t *fs = baton;
- int format, max_files_per_dir;
- const char *format_path = path_format(fs, pool);
- svn_node_kind_t kind;
-
- /* Read the FS format number and max-files-per-dir setting. */
- SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
- SVN_ERR(check_format(format));
-
- /* If the config file does not exist, create one. */
- SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
- &kind, pool));
- switch (kind)
- {
- case svn_node_none:
- SVN_ERR(write_config(fs, pool));
- break;
- case svn_node_file:
- break;
- default:
- return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
- _("'%s' is not a regular file."
- " Please move it out of "
- "the way and try again"),
- svn_dirent_join(fs->path, PATH_CONFIG, pool));
- }
-
- /* If we're already up-to-date, there's nothing else to be done here. */
- if (format == SVN_FS_FS__FORMAT_NUMBER)
- return SVN_NO_ERROR;
-
- /* If our filesystem predates the existance of the 'txn-current
- file', make that file and its corresponding lock file. */
- if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
- {
- SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
- pool));
- SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
- pool));
- }
-
- /* If our filesystem predates the existance of the 'txn-protorevs'
- dir, make that directory. */
- if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
- {
- /* We don't use path_txn_proto_rev() here because it expects
- we've already bumped our format. */
- SVN_ERR(svn_io_make_dir_recursively(
- svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
- }
-
- /* If our filesystem is new enough, write the min unpacked rev file. */
- if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
- SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
-
- /* If the file system supports revision packing but not revprop packing,
- pack the revprops up to the point that revision data has been packed. */
- if ( format >= SVN_FS_FS__MIN_PACKED_FORMAT
- && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
- SVN_ERR(upgrade_pack_revprops(fs, pool));
-
- /* Bump the format file. */
- return write_format(format_path, SVN_FS_FS__FORMAT_NUMBER, max_files_per_dir,
- TRUE, pool);
-}
-
-
-svn_error_t *
-svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
-{
- return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
-}
-
-
-/* Functions for dealing with recoverable errors on mutable files
- *
- * Revprops, current, and txn-current files are mutable; that is, they
- * change as part of normal fsfs operation, in constrat to revs files, or
- * the format file, which are written once at create (or upgrade) time.
- * When more than one host writes to the same repository, we will
- * sometimes see these recoverable errors when accesssing these files.
- *
- * These errors all relate to NFS, and thus we only use this retry code if
- * ESTALE is defined.
- *
- ** ESTALE
- *
- * In NFS v3 and under, the server doesn't track opened files. If you
- * unlink(2) or rename(2) a file held open by another process *on the
- * same host*, that host's kernel typically renames the file to
- * .nfsXXXX and automatically deletes that when it's no longer open,
- * but this behavior is not required.
- *
- * For obvious reasons, this does not work *across hosts*. No one
- * knows about the opened file; not the server, and not the deleting
- * client. So the file vanishes, and the reader gets stale NFS file
- * handle.
- *
- ** EIO, ENOENT
- *
- * Some client implementations (at least the 2.6.18.5 kernel that ships
- * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
- * even EIO errors when trying to read these files that have been renamed
- * over on some other host.
- *
- ** Solution
- *
- * Try open and read of such files in try_stringbuf_from_file(). Call
- * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
- * (though, realistically, the second try will succeed).
- */
-
-#define RECOVERABLE_RETRY_COUNT 10
-
-/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
- * not be modified unless the whole file was read successfully.
- *
- * ESTALE, EIO and ENOENT will not cause this function to return an error
- * unless LAST_ATTEMPT has been set. If MISSING is not NULL, indicate
- * missing files (ENOENT) there.
- *
- * Use POOL for allocations.
- */
-static svn_error_t *
-try_stringbuf_from_file(svn_stringbuf_t **content,
- svn_boolean_t *missing,
- const char *path,
- svn_boolean_t last_attempt,
- apr_pool_t *pool)
-{
- svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
- if (missing)
- *missing = FALSE;
-
- if (err)
- {
- *content = NULL;
-
- if (APR_STATUS_IS_ENOENT(err->apr_err))
- {
- if (!last_attempt)
- {
- svn_error_clear(err);
- if (missing)
- *missing = TRUE;
- return SVN_NO_ERROR;
- }
- }
-#ifdef ESTALE
- else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
- || APR_TO_OS_ERROR(err->apr_err) == EIO)
- {
- if (!last_attempt)
- {
- svn_error_clear(err);
- return SVN_NO_ERROR;
- }
- }
-#endif
- }
-
- return svn_error_trace(err);
-}
-
-/* Read the 'current' file FNAME and store the contents in *BUF.
- Allocations are performed in POOL. */
-static svn_error_t *
-read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
-{
- int i;
- *content = NULL;
-
- for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
- SVN_ERR(try_stringbuf_from_file(content, NULL,
- fname, i + 1 < RECOVERABLE_RETRY_COUNT,
- pool));
-
- if (!*content)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Can't read '%s'"),
- svn_dirent_local_style(fname, pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Find the youngest revision in a repository at path FS_PATH and
- return it in *YOUNGEST_P. Perform temporary allocations in
- POOL. */
-static svn_error_t *
-get_youngest(svn_revnum_t *youngest_p,
- const char *fs_path,
- apr_pool_t *pool)
-{
- svn_stringbuf_t *buf;
- SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
- pool));
-
- *youngest_p = SVN_STR_TO_REV(buf->data);
-
- return SVN_NO_ERROR;
-}
-
-
-svn_error_t *
-svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- SVN_ERR(get_youngest(youngest_p, fs->path, pool));
- ffd->youngest_rev_cache = *youngest_p;
-
- return SVN_NO_ERROR;
-}
-
-/* Given a revision file FILE that has been pre-positioned at the
- beginning of a Node-Rev header block, read in that header block and
- store it in the apr_hash_t HEADERS. All allocations will be from
- POOL. */
-static svn_error_t * read_header_block(apr_hash_t **headers,
- svn_stream_t *stream,
- apr_pool_t *pool)
-{
- *headers = apr_hash_make(pool);
-
- while (1)
- {
- svn_stringbuf_t *header_str;
- const char *name, *value;
- apr_size_t i = 0;
- svn_boolean_t eof;
-
- SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
-
- if (eof || header_str->len == 0)
- break; /* end of header block */
-
- while (header_str->data[i] != ':')
- {
- if (header_str->data[i] == '\0')
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Found malformed header '%s' in "
- "revision file"),
- header_str->data);
- i++;
- }
-
- /* Create a 'name' string and point to it. */
- header_str->data[i] = '\0';
- name = header_str->data;
-
- /* Skip over the NULL byte and the space following it. */
- i += 2;
-
- if (i > header_str->len)
- {
- /* Restore the original line for the error. */
- i -= 2;
- header_str->data[i] = ':';
- return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
- _("Found malformed header '%s' in "
- "revision file"),
- header_str->data);
- }
-
- value = header_str->data + i;
-
- /* header_str is safely in our pool, so we can use bits of it as
- key and value. */
- apr_hash_set(*headers, name, APR_HASH_KEY_STRING, value);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
- than the current youngest revision or is simply not a valid
- revision number, else return success.
-
- FSFS is based around the concept that commits only take effect when
- the number in "current" is bumped. Thus if there happens to be a rev
- or revprops file installed for a revision higher than the one recorded
- in "current" (because a commit failed between installing the rev file
- and bumping "current", or because an administrator rolled back the
- repository by resetting "current" without deleting rev files, etc), it
- ought to be completely ignored. This function provides the check
- by which callers can make that decision. */
-static svn_error_t *
-ensure_revision_exists(svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
-
- if (! SVN_IS_VALID_REVNUM(rev))
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("Invalid revision number '%ld'"), rev);
-
-
- /* Did the revision exist the last time we checked the current
- file? */
- if (rev <= ffd->youngest_rev_cache)
- return SVN_NO_ERROR;
-
- SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
-
- /* Check again. */
- if (rev <= ffd->youngest_rev_cache)
- return SVN_NO_ERROR;
-
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("No such revision %ld"), rev);
-}
-
-svn_error_t *
-svn_fs_fs__revision_exists(svn_revnum_t rev,
- svn_fs_t *fs,
- apr_pool_t *pool)
-{
- /* Different order of parameters. */
- SVN_ERR(ensure_revision_exists(fs, rev, pool));
- return SVN_NO_ERROR;
-}
-
-/* Open the correct revision file for REV. If the filesystem FS has
- been packed, *FILE will be set to the packed file; otherwise, set *FILE
- to the revision file for REV. Return SVN_ERR_FS_NO_SUCH_REVISION if the
- file doesn't exist.
-
- TODO: Consider returning an indication of whether this is a packed rev
- file, so the caller need not rely on is_packed_rev() which in turn
- relies on the cached FFD->min_unpacked_rev value not having changed
- since the rev file was opened.
-
- Use POOL for allocations. */
-static svn_error_t *
-open_pack_or_rev_file(apr_file_t **file,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_error_t *err;
- const char *path;
- svn_boolean_t retry = FALSE;
-
- do
- {
- err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
-
- /* open the revision file in buffered r/o mode */
- if (! err)
- err = svn_io_file_open(file, path,
- APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
-
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
- {
- /* Could not open the file. This may happen if the
- * file once existed but got packed later. */
- svn_error_clear(err);
-
- /* if that was our 2nd attempt, leave it at that. */
- if (retry)
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("No such revision %ld"), rev);
-
- /* We failed for the first time. Refresh cache & retry. */
- SVN_ERR(update_min_unpacked_rev(fs, pool));
-
- retry = TRUE;
- }
- else
- {
- svn_error_clear(err);
- return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
- _("No such revision %ld"), rev);
- }
- }
- else
- {
- retry = FALSE;
- }
- }
- while (retry);
-
- return svn_error_trace(err);
-}
-
-/* Reads a line from STREAM and converts it to a 64 bit integer to be
- * returned in *RESULT. If we encounter eof, set *HIT_EOF and leave
- * *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS"
- * error return.
- * SCRATCH_POOL is used for temporary allocations.
- */
-static svn_error_t *
-read_number_from_stream(apr_int64_t *result,
- svn_boolean_t *hit_eof,
- svn_stream_t *stream,
- apr_pool_t *scratch_pool)
-{
- svn_stringbuf_t *sb;
- svn_boolean_t eof;
- svn_error_t *err;
-
- SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
- if (hit_eof)
- *hit_eof = eof;
- else
- if (eof)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
-
- if (!eof)
- {
- err = svn_cstring_atoi64(result, sb->data);
- if (err)
- return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
- _("Number '%s' invalid or too large"),
- sb->data);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
- Use POOL for temporary allocations. */
-static svn_error_t *
-get_packed_offset(apr_off_t *rev_offset,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool)
-{
- fs_fs_data_t *ffd = fs->fsap_data;
- svn_stream_t *manifest_stream;
- svn_boolean_t is_cached;
- svn_revnum_t shard;
- apr_int64_t shard_pos;
- apr_array_header_t *manifest;
- apr_pool_t *iterpool;
-
- shard = rev / ffd->max_files_per_dir;
-
- /* position of the shard within the manifest */
- shard_pos = rev % ffd->max_files_per_dir;
-
- /* fetch exactly that element into *rev_offset, if the manifest is found
- in the cache */
- SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
- ffd->packed_offset_cache, &shard,
- svn_fs_fs__get_sharded_offset, &shard_pos,
- pool));
-
- if (is_cached)
- return SVN_NO_ERROR;
-
- /* Open the manifest file. */
- SVN_ERR(svn_stream_open_readonly(&manifest_stream,
- path_rev_packed(fs, rev, PATH_MANIFEST,
- pool),
- pool, pool));
-
- /* While we're here, let's just read the entire manifest file into an array,
- so we can cache the entire thing. */
- iterpool = svn_pool_create(pool);
- manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
- while (1)
- {
- svn_boolean_t eof;
- apr_int64_t val;
-
- svn_pool_clear(iterpool);
- SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
- if (eof)
- break;
-
- APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
- }
- svn_pool_destroy(iterpool);
-
- *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
- apr_off_t);
-
- /* Close up shop and cache the array. */
- SVN_ERR(svn_stream_close(manifest_stream));
- return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
-}
-
-/* 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(ensure_revision_exists(fs, rev, pool));
-
- SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
-
- if (is_packed_rev(fs, rev))
- {
- apr_off_t rev_offset;
-
- SVN_ERR(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);
-}
-
-/* Parse the description of a representation from STRING and store it
- into *REP_P. If the representation is mutable (the revision is
- given as -1), then use TXN_ID for the representation's txn_id
- field. If MUTABLE_REP_TRUNCATED is true, then this representation
- is for property or directory contents, and no information will be
- expected except the "-1" revision number for a mutable
- representation. Allocate *REP_P in POOL. */
-static svn_error_t *
-read_rep_offsets_body(representation_t **rep_p,
- char *string,
- const char *txn_id,
- svn_boolean_t mutable_rep_truncated,
- apr_pool_t *pool)
-{
- representation_t *rep;
- char *str;
- apr_int64_t val;
-
- rep = apr_pcalloc(pool, sizeof(*rep));
- *rep_p = rep;
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
-
- rep->revision = SVN_STR_TO_REV(str);
- if (rep->revision == SVN_INVALID_REVNUM)
- {
- rep->txn_id = txn_id;
- if (mutable_rep_truncated)
- return SVN_NO_ERROR;
- }
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep->offset = (apr_off_t)val;
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep->size = (svn_filesize_t)val;
-
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_cstring_atoi64(&val, str));
- rep->expanded_size = (svn_filesize_t)val;
-
- /* Read in the MD5 hash. */
- str = svn_cstring_tokenize(" ", &string);
- if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
- pool));
-
- /* The remaining fields are only used for formats >= 4, so check that. */
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return SVN_NO_ERROR;
-
- /* Read the SHA1 hash. */
- if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
- pool));
-
- /* Read the uniquifier. */
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
-
- rep->uniquifier = apr_pstrdup(pool, str);
-
- return SVN_NO_ERROR;
-}
-
-/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
- and adding an error message. */
-static svn_error_t *
-read_rep_offsets(representation_t **rep_p,
- char *string,
- const svn_fs_id_t *noderev_id,
- svn_boolean_t mutable_rep_truncated,
- apr_pool_t *pool)
-{
- svn_error_t *err;
- const char *txn_id;
-
- if (noderev_id)
- txn_id = svn_fs_fs__id_txn_id(noderev_id);
- else
- txn_id = NULL;
-
- err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
- pool);
- if (err)
- {
- const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
- const char *where;
- where = apr_psprintf(pool,
- _("While reading representation offsets "
- "for node-revision '%s':"),
- noderev_id ? id_unparsed->data : "(null)");
-
- return svn_error_quick_wrap(err, where);
- }
- else
- return SVN_NO_ERROR;
-}
-
-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);
-}
-
[... 9616 lines stripped ...]