aboutsummaryrefslogtreecommitdiff
path: root/subversion/libsvn_repos
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos')
-rw-r--r--subversion/libsvn_repos/authz.c2233
-rw-r--r--subversion/libsvn_repos/authz.h364
-rw-r--r--subversion/libsvn_repos/authz_info.c184
-rw-r--r--subversion/libsvn_repos/authz_parse.c1442
-rw-r--r--subversion/libsvn_repos/authz_pool.c226
-rw-r--r--subversion/libsvn_repos/commit.c140
-rw-r--r--subversion/libsvn_repos/compat.c179
-rw-r--r--subversion/libsvn_repos/config_file.c386
-rw-r--r--subversion/libsvn_repos/config_file.h74
-rw-r--r--subversion/libsvn_repos/config_pool.c485
-rw-r--r--subversion/libsvn_repos/delta.c24
-rw-r--r--subversion/libsvn_repos/deprecated.c180
-rw-r--r--subversion/libsvn_repos/dump.c140
-rw-r--r--subversion/libsvn_repos/fs-wrap.c102
-rw-r--r--subversion/libsvn_repos/hooks.c15
-rw-r--r--subversion/libsvn_repos/list.c340
-rw-r--r--subversion/libsvn_repos/load-fs-vtable.c271
-rw-r--r--subversion/libsvn_repos/load.c132
-rw-r--r--subversion/libsvn_repos/log.c686
-rw-r--r--subversion/libsvn_repos/replay.c193
-rw-r--r--subversion/libsvn_repos/reporter.c41
-rw-r--r--subversion/libsvn_repos/repos.c68
-rw-r--r--subversion/libsvn_repos/repos.h38
-rw-r--r--subversion/libsvn_repos/rev_hunt.c123
24 files changed, 5883 insertions, 2183 deletions
diff --git a/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c
index 20f9231dc22a..668d78dd5993 100644
--- a/subversion/libsvn_repos/authz.c
+++ b/subversion/libsvn_repos/authz.c
@@ -25,6 +25,7 @@
#include <apr_pools.h>
#include <apr_file_io.h>
+#include <apr_fnmatch.h>
#include "svn_hash.h"
#include "svn_pools.h"
@@ -34,946 +35,1592 @@
#include "svn_repos.h"
#include "svn_config.h"
#include "svn_ctype.h"
+#include "private/svn_atomic.h"
#include "private/svn_fspath.h"
#include "private/svn_repos_private.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_subr_private.h"
#include "repos.h"
+#include "authz.h"
+#include "config_file.h"
-/*** Structures. ***/
+/*** Access rights. ***/
-/* Information for the config enumerators called during authz
- lookup. */
-struct authz_lookup_baton {
- /* The authz configuration. */
- svn_config_t *config;
+/* This structure describes the access rights given to a specific user by
+ * a path rule (actually the rule set specified for a path). I.e. there is
+ * one instance of this per path rule.
+ */
+typedef struct path_access_t
+{
+ /* Sequence number of the path rule that this struct was derived from.
+ * If multiple rules apply to the same path (only possible with wildcard
+ * matching), the one with the highest SEQUENCE_NUMBER wins, i.e. the latest
+ * one defined in the authz file.
+ *
+ * A value of 0 denotes the default rule at the repository root denying
+ * access to everybody. User-defined path rules start with ID 1.
+ */
+ int sequence_number;
- /* The user to authorize. */
- const char *user;
+ /* Access rights of the respective user as defined by the rule set. */
+ authz_access_t rights;
+} path_access_t;
- /* Explicitly granted rights. */
- svn_repos_authz_access_t allow;
- /* Explicitly denied rights. */
- svn_repos_authz_access_t deny;
+/* Use this to indicate that no sequence ID has been assigned.
+ * It will automatically be inferior to (less than) any other sequence ID. */
+#define NO_SEQUENCE_NUMBER (-1)
- /* The rights required by the caller of the lookup. */
- svn_repos_authz_access_t required_access;
+/* Convenience structure combining the node-local access rights with the
+ * min and max rights granted within the sub-tree. */
+typedef struct limited_rights_t
+{
+ /* Access granted to the current user. If the SEQUENCE_NUMBER member is
+ * NO_SEQUENCE_NUMBER, there has been no specific path rule for this PATH
+ * but only for some sub-path(s). There is always a rule at the root node.
+ */
+ path_access_t access;
- /* The following are used exclusively in recursive lookups. */
+ /* Minimal access rights that the user has on this or any other node in
+ * the sub-tree. This does not take inherited rights into account. */
+ authz_access_t min_rights;
- /* The path in the repository (an fspath) to authorize. */
- const char *repos_path;
- /* repos_path prefixed by the repository name and a colon. */
- const char *qualified_repos_path;
+ /* Maximal access rights that the user has on this or any other node in
+ * the sub-tree. This does not take inherited rights into account. */
+ authz_access_t max_rights;
- /* Whether, at the end of a recursive lookup, access is granted. */
- svn_boolean_t access;
-};
+} limited_rights_t;
-/* Information for the config enumeration functions called during the
- validation process. */
-struct authz_validate_baton {
- svn_config_t *config; /* The configuration file being validated. */
- svn_error_t *err; /* The error being thrown out of the
- enumerator, if any. */
-};
+/* Return TRUE, if RIGHTS has local rights defined in the ACCESS member. */
+static svn_boolean_t
+has_local_rule(const limited_rights_t *rights)
+{
+ return rights->access.sequence_number != NO_SEQUENCE_NUMBER;
+}
-/* Currently this structure is just a wrapper around a svn_config_t.
- Please update authz_pool if you modify this structure. */
-struct svn_authz_t
+/* Aggregate the ACCESS spec of TARGET and RIGHTS into TARGET. I.e. if both
+ * are specified, pick one in accordance to the precedence rules. */
+static void
+combine_access(limited_rights_t *target,
+ const limited_rights_t *rights)
{
- svn_config_t *cfg;
-};
+ /* This implies the check for NO_SEQUENCE_NUMBER, i.e no rights being
+ * specified. */
+ if (target->access.sequence_number < rights->access.sequence_number)
+ target->access = rights->access;
+}
+/* Aggregate the min / max access rights of TARGET and RIGHTS into TARGET. */
+static void
+combine_right_limits(limited_rights_t *target,
+ const limited_rights_t *rights)
+{
+ target->max_rights |= rights->max_rights;
+ target->min_rights &= rights->min_rights;
+}
-/*** Checking access. ***/
-/* Determine whether the REQUIRED access is granted given what authz
- * to ALLOW or DENY. Return TRUE if the REQUIRED access is
- * granted.
- *
- * Access is granted either when no required access is explicitly
- * denied (implicit grant), or when the required access is explicitly
- * granted, overriding any denials.
+/*** Authz cache access. ***/
+
+/* All authz instances currently in use as well as all filtered authz
+ * instances in use will be cached here.
+ * Both caches will be instantiated at most once. */
+static svn_object_pool__t *authz_pool = NULL;
+static svn_object_pool__t *filtered_pool = NULL;
+static svn_atomic_t authz_pool_initialized = FALSE;
+
+/* Implements svn_atomic__err_init_func_t. */
+static svn_error_t *
+synchronized_authz_initialize(void *baton, apr_pool_t *pool)
+{
+#if APR_HAS_THREADS
+ svn_boolean_t multi_threaded = TRUE;
+#else
+ svn_boolean_t multi_threaded = FALSE;
+#endif
+
+ SVN_ERR(svn_object_pool__create(&authz_pool, multi_threaded, pool));
+ SVN_ERR(svn_object_pool__create(&filtered_pool, multi_threaded, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_authz_initialize(apr_pool_t *pool)
+{
+ /* Protect against multiple calls. */
+ return svn_error_trace(svn_atomic__init_once(&authz_pool_initialized,
+ synchronized_authz_initialize,
+ NULL, pool));
+}
+
+/* Return a combination of AUTHZ_KEY and GROUPS_KEY, allocated in RESULT_POOL.
+ * GROUPS_KEY may be NULL. This is the key for the AUTHZ_POOL.
*/
-static svn_boolean_t
-authz_access_is_granted(svn_repos_authz_access_t allow,
- svn_repos_authz_access_t deny,
- svn_repos_authz_access_t required)
+static svn_membuf_t *
+construct_authz_key(const svn_checksum_t *authz_key,
+ const svn_checksum_t *groups_key,
+ apr_pool_t *result_pool)
{
- svn_repos_authz_access_t stripped_req =
- required & (svn_authz_read | svn_authz_write);
+ svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
+ if (groups_key)
+ {
+ apr_size_t authz_size = svn_checksum_size(authz_key);
+ apr_size_t groups_size = svn_checksum_size(groups_key);
- if ((deny & required) == svn_authz_none)
- return TRUE;
- else if ((allow & required) == stripped_req)
- return TRUE;
+ svn_membuf__create(result, authz_size + groups_size, result_pool);
+ result->size = authz_size + groups_size; /* exact length is required! */
+
+ memcpy(result->data, authz_key->digest, authz_size);
+ memcpy((char *)result->data + authz_size,
+ groups_key->digest, groups_size);
+ }
else
- return FALSE;
+ {
+ apr_size_t size = svn_checksum_size(authz_key);
+ svn_membuf__create(result, size, result_pool);
+ result->size = size; /* exact length is required! */
+ memcpy(result->data, authz_key->digest, size);
+ }
+
+ return result;
}
+/* Return a combination of REPOS_NAME, USER and AUTHZ_ID, allocated in
+ * RESULT_POOL. USER may be NULL. This is the key for the FILTERED_POOL.
+ */
+static svn_membuf_t *
+construct_filtered_key(const char *repos_name,
+ const char *user,
+ const svn_membuf_t *authz_id,
+ apr_pool_t *result_pool)
+{
+ svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
+ size_t repos_len = strlen(repos_name);
+ size_t user_len = user ? strlen(user) : 1;
+ const char *nullable_user = user ? user : "\0";
+ size_t size = authz_id->size + repos_len + 1 + user_len + 1;
+
+ svn_membuf__create(result, size, result_pool);
+ result->size = size;
+
+ memcpy(result->data, repos_name, repos_len + 1);
+ size = repos_len + 1;
+ memcpy((char *)result->data + size, nullable_user, user_len + 1);
+ size += user_len + 1;
+ memcpy((char *)result->data + size, authz_id->data, authz_id->size);
+
+ return result;
+}
-/* Decide whether the REQUIRED access has been conclusively
- * determined. Return TRUE if the given ALLOW/DENY authz are
- * conclusive regarding the REQUIRED authz.
- *
- * Conclusive determination occurs when any of the REQUIRED authz are
- * granted or denied by ALLOW/DENY.
+
+/*** Constructing the prefix tree. ***/
+
+/* Since prefix arrays may have more than one hit, we need to link them
+ * for fast lookup. */
+typedef struct sorted_pattern_t
+{
+ /* The filtered tree node carrying the prefix. */
+ struct node_t *node;
+
+ /* Entry that is a prefix to this one or NULL. */
+ struct sorted_pattern_t *next;
+} sorted_pattern_t;
+
+/* Substructure of node_t. It contains all sub-node that use patterns
+ * in the next segment level. We keep it separate to save a bit of memory
+ * and to be able to check for pattern presence in a single operation.
*/
-static svn_boolean_t
-authz_access_is_determined(svn_repos_authz_access_t allow,
- svn_repos_authz_access_t deny,
- svn_repos_authz_access_t required)
+typedef struct node_pattern_t
{
- if ((deny & required) || (allow & required))
- return TRUE;
+ /* If not NULL, this represents the "*" follow-segment. */
+ struct node_t *any;
+
+ /* If not NULL, this represents the "**" follow-segment. */
+ struct node_t *any_var;
+
+ /* If not NULL, the segments of all sorted_pattern_t in this array are the
+ * prefix part of "prefix*" patterns. Sorted by segment prefix. */
+ apr_array_header_t *prefixes;
+
+ /* If not NULL, the segments of all sorted_pattern_t in this array are the
+ * reversed suffix part of "*suffix" patterns. Sorted by reversed
+ * segment suffix. */
+ apr_array_header_t *suffixes;
+
+ /* If not NULL, the segments of all sorted_pattern_t in this array contain
+ * wildcards and don't fit into any of the above categories.
+ * The NEXT members of the elements will not be used. */
+ apr_array_header_t *complex;
+
+ /* This node itself is a "**" segment and must therefore itself be added
+ * to the matching node list for the next level. */
+ svn_boolean_t repeat;
+} node_pattern_t;
+
+/* The pattern tree. All relevant path rules are being folded into this
+ * prefix tree, with a single, whole segment stored at each node. The whole
+ * tree applies to a single user only.
+ */
+typedef struct node_t
+{
+ /* The segment as specified in the path rule. During the lookup tree walk,
+ * this will compared to the respective segment of the path to check. */
+ svn_string_t segment;
+
+ /* Immediate access rights granted by rules on this node and the min /
+ * max rights on any path in this sub-tree. */
+ limited_rights_t rights;
+
+ /* Map of sub-segment(const char *) to respective node (node_t) for all
+ * sub-segments that have rules on themselves or their respective subtrees.
+ * NULL, if there are no rules for sub-paths relevant to the user. */
+ apr_hash_t *sub_nodes;
+
+ /* If not NULL, this contains the pattern-based segment sub-nodes. */
+ node_pattern_t *pattern_sub_nodes;
+} node_t;
+
+/* Create a new tree node for SEGMENT.
+ Note: SEGMENT->pattern is always interned and therefore does not
+ have to be copied into the result pool. */
+static node_t *
+create_node(authz_rule_segment_t *segment,
+ apr_pool_t *result_pool)
+{
+ node_t *result = apr_pcalloc(result_pool, sizeof(*result));
+ if (segment)
+ result->segment = segment->pattern;
else
- return FALSE;
+ {
+ result->segment.data = "";
+ result->segment.len = 0;
+ }
+ result->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
+ return result;
}
-/* Return TRUE is USER equals ALIAS. The alias definitions are in the
- "aliases" sections of CFG. Use POOL for temporary allocations during
- the lookup. */
-static svn_boolean_t
-authz_alias_is_user(svn_config_t *cfg,
- const char *alias,
- const char *user,
- apr_pool_t *pool)
+/* Auto-create a node in *NODE, make it apply to SEGMENT and return it. */
+static node_t *
+ensure_node(node_t **node,
+ authz_rule_segment_t *segment,
+ apr_pool_t *result_pool)
{
- const char *value;
+ if (!*node)
+ *node = create_node(segment, result_pool);
- svn_config_get(cfg, &value, "aliases", alias, NULL);
- if (!value)
- return FALSE;
+ return *node;
+}
- if (strcmp(value, user) == 0)
- return TRUE;
+/* compare_func comparing segment names. It takes a sorted_pattern_t* as
+ * VOID_LHS and a const authz_rule_segment_t * as VOID_RHS.
+ */
+static int
+compare_node_rule_segment(const void *void_lhs,
+ const void *void_rhs)
+{
+ const sorted_pattern_t *element = void_lhs;
+ const authz_rule_segment_t *segment = void_rhs;
- return FALSE;
+ return strcmp(element->node->segment.data, segment->pattern.data);
}
+/* compare_func comparing segment names. It takes a sorted_pattern_t* as
+ * VOID_LHS and a const char * as VOID_RHS.
+ */
+static int
+compare_node_path_segment(const void *void_lhs,
+ const void *void_rhs)
+{
+ const sorted_pattern_t *element = void_lhs;
+ const char *segment = void_rhs;
+
+ return strcmp(element->node->segment.data, segment);
+}
-/* Return TRUE if USER is in GROUP. The group definitions are in the
- "groups" section of CFG. Use POOL for temporary allocations during
- the lookup. */
-static svn_boolean_t
-authz_group_contains_user(svn_config_t *cfg,
- const char *group,
- const char *user,
- apr_pool_t *pool)
+/* Make sure a node_t* for SEGMENT exists in *ARRAY and return it.
+ * Auto-create either if they don't exist. Entries in *ARRAY are
+ * sorted by their segment strings.
+ */
+static node_t *
+ensure_node_in_array(apr_array_header_t **array,
+ authz_rule_segment_t *segment,
+ apr_pool_t *result_pool)
{
- const char *value;
- apr_array_header_t *list;
- int i;
+ int idx;
+ sorted_pattern_t entry;
+ sorted_pattern_t *entry_ptr;
+
+ /* Auto-create the array. */
+ if (!*array)
+ *array = apr_array_make(result_pool, 4, sizeof(sorted_pattern_t));
+
+ /* Find the node in ARRAY and the IDX at which it were to be inserted.
+ * Initialize IDX such that we won't attempt a hinted lookup (likely
+ * to fail and therefore pure overhead). */
+ idx = (*array)->nelts;
+ entry_ptr = svn_sort__array_lookup(*array, segment, &idx,
+ compare_node_rule_segment);
+ if (entry_ptr)
+ return entry_ptr->node;
+
+ /* There is no such node, yet.
+ * Create one and insert it into the sorted array. */
+ entry.node = create_node(segment, result_pool);
+ entry.next = NULL;
+ svn_sort__array_insert(*array, &entry, idx);
+
+ return entry.node;
+}
- svn_config_get(cfg, &value, "groups", group, NULL);
+/* Auto-create the PATTERN_SUB_NODES sub-structure in *NODE and return it. */
+static node_pattern_t *
+ensure_pattern_sub_nodes(node_t *node,
+ apr_pool_t *result_pool)
+{
+ if (node->pattern_sub_nodes == NULL)
+ node->pattern_sub_nodes = apr_pcalloc(result_pool,
+ sizeof(*node->pattern_sub_nodes));
+
+ return node->pattern_sub_nodes;
+}
+
+/* Combine an ACL rule segment with the corresponding node in our filtered
+ * data model. */
+typedef struct node_segment_pair_t
+{
+ authz_rule_segment_t *segment;
+ node_t *node;
+} node_segment_pair_t;
+
+/* Context object to be used with process_acl. It allows us to re-use
+ * information from previous insertions. */
+typedef struct construction_context_t
+{
+ /* Array of node_segment_pair_t. It contains all segments already
+ * processed of the current insertion together with the respective
+ * nodes in our filtered tree. Before the next lookup, the tree
+ * walk for the common prefix can be skipped. */
+ apr_array_header_t *path;
+} construction_context_t;
+
+/* Return a new context object allocated in RESULT_POOL. */
+static construction_context_t *
+create_construction_context(apr_pool_t *result_pool)
+{
+ construction_context_t *result = apr_pcalloc(result_pool, sizeof(*result));
+
+ /* Array will be auto-extended but this initial size will make it rarely
+ * ever necessary. */
+ result->path = apr_array_make(result_pool, 32, sizeof(node_segment_pair_t));
- list = svn_cstring_split(value, ",", TRUE, pool);
+ return result;
+}
- for (i = 0; i < list->nelts; i++)
+/* Constructor utility: Below NODE, recursively insert sub-nodes for the
+ * path given as *SEGMENTS of length SEGMENT_COUNT. If matching nodes
+ * already exist, use those instead of creating new ones. Set the leave
+ * node's access rights spec to PATH_ACCESS. Update the context info in CTX.
+ */
+static void
+insert_path(construction_context_t *ctx,
+ node_t *node,
+ path_access_t *path_access,
+ int segment_count,
+ authz_rule_segment_t *segment,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ node_t *sub_node;
+ node_segment_pair_t *node_segment;
+
+ /* End of path? */
+ if (segment_count == 0)
{
- const char *group_user = APR_ARRAY_IDX(list, i, char *);
+ /* Set access rights. Note that there might be multiple rules for
+ * the same path due to non-repo-specific rules vs. repo-specific
+ * ones. Whichever gets defined last wins.
+ */
+ limited_rights_t rights;
+ rights.access = *path_access;
+ rights.max_rights = path_access->rights;
+ rights.min_rights = path_access->rights;
+ combine_access(&node->rights, &rights);
+ return;
+ }
+
+ /* Any wildcards? They will go into a separate sub-structure. */
+ if (segment->kind != authz_rule_literal)
+ ensure_pattern_sub_nodes(node, result_pool);
- /* If the 'user' is a subgroup, recurse into it. */
- if (*group_user == '@')
+ switch (segment->kind)
+ {
+ /* A full wildcard segment? */
+ case authz_rule_any_segment:
+ sub_node = ensure_node(&node->pattern_sub_nodes->any,
+ segment, result_pool);
+ break;
+
+ /* One or more full wildcard segments? */
+ case authz_rule_any_recursive:
+ sub_node = ensure_node(&node->pattern_sub_nodes->any_var,
+ segment, result_pool);
+ ensure_pattern_sub_nodes(sub_node, result_pool)->repeat = TRUE;
+ break;
+
+ /* A single wildcard at the end of the segment? */
+ case authz_rule_prefix:
+ sub_node = ensure_node_in_array(&node->pattern_sub_nodes->prefixes,
+ segment, result_pool);
+ break;
+
+ /* A single wildcard at the start of segments? */
+ case authz_rule_suffix:
+ sub_node = ensure_node_in_array(&node->pattern_sub_nodes->suffixes,
+ segment, result_pool);
+ break;
+
+ /* General pattern? */
+ case authz_rule_fnmatch:
+ sub_node = ensure_node_in_array(&node->pattern_sub_nodes->complex,
+ segment, result_pool);
+ break;
+
+ /* Then it must be a literal. */
+ default:
+ SVN_ERR_ASSERT_NO_RETURN(segment->kind == authz_rule_literal);
+
+ if (!node->sub_nodes)
{
- if (authz_group_contains_user(cfg, &group_user[1],
- user, pool))
- return TRUE;
+ node->sub_nodes = svn_hash__make(result_pool);
+ sub_node = NULL;
}
-
- /* If the 'user' is an alias, verify it. */
- else if (*group_user == '&')
+ else
{
- if (authz_alias_is_user(cfg, &group_user[1],
- user, pool))
- return TRUE;
+ sub_node = svn_hash_gets(node->sub_nodes, segment->pattern.data);
}
- /* If the user matches, stop. */
- else if (strcmp(user, group_user) == 0)
- return TRUE;
+ /* Auto-insert a sub-node for the current segment. */
+ if (!sub_node)
+ {
+ sub_node = create_node(segment, result_pool);
+ apr_hash_set(node->sub_nodes,
+ sub_node->segment.data,
+ sub_node->segment.len,
+ sub_node);
+ }
}
- return FALSE;
+ /* Update context. */
+ node_segment = apr_array_push(ctx->path);
+ node_segment->segment = segment;
+ node_segment->node = sub_node;
+
+ /* Continue at the sub-node with the next segment. */
+ insert_path(ctx, sub_node, path_access, segment_count - 1, segment + 1,
+ result_pool, scratch_pool);
}
-/* Determines whether an authz rule applies to the current
- * user, given the name part of the rule's name-value pair
- * in RULE_MATCH_STRING and the authz_lookup_baton object
- * B with the username in question.
+/* If the ACL is relevant to the REPOSITORY and user (given as MEMBERSHIPS
+ * plus ANONYMOUS flag), insert the respective nodes into tree starting
+ * at ROOT. Use the context info of the previous call in CTX to eliminate
+ * repeated lookups. Allocate new nodes in RESULT_POOL and use SCRATCH_POOL
+ * for temporary allocations.
*/
-static svn_boolean_t
-authz_line_applies_to_user(const char *rule_match_string,
- struct authz_lookup_baton *b,
- apr_pool_t *pool)
+static void
+process_acl(construction_context_t *ctx,
+ const authz_acl_t *acl,
+ node_t *root,
+ const char *repository,
+ const char *user,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- /* If the rule has an inversion, recurse and invert the result. */
- if (rule_match_string[0] == '~')
- return !authz_line_applies_to_user(&rule_match_string[1], b, pool);
-
- /* Check for special tokens. */
- if (strcmp(rule_match_string, "$anonymous") == 0)
- return (b->user == NULL);
- if (strcmp(rule_match_string, "$authenticated") == 0)
- return (b->user != NULL);
-
- /* Check for a wildcard rule. */
- if (strcmp(rule_match_string, "*") == 0)
- return TRUE;
+ path_access_t path_access;
+ int i;
+ node_t *node;
- /* If we get here, then the rule is:
- * - Not an inversion rule.
- * - Not an authz token rule.
- * - Not a wildcard rule.
- *
- * All that's left over is regular user or group specifications.
- */
+ /* Skip ACLs that don't say anything about the current user
+ and/or repository. */
+ if (!svn_authz__get_acl_access(&path_access.rights, acl, user, repository))
+ return;
- /* If the session is anonymous, then a user/group
- * rule definitely won't match.
- */
- if (b->user == NULL)
- return FALSE;
+ /* Insert the rule into the filtered tree. */
+ path_access.sequence_number = acl->sequence_number;
- /* Process the rule depending on whether it is
- * a user, alias or group rule.
- */
- if (rule_match_string[0] == '@')
- return authz_group_contains_user(
- b->config, &rule_match_string[1], b->user, pool);
- else if (rule_match_string[0] == '&')
- return authz_alias_is_user(
- b->config, &rule_match_string[1], b->user, pool);
- else
- return (strcmp(b->user, rule_match_string) == 0);
-}
+ /* Try to reuse results from previous runs.
+ * Basically, skip the common prefix. */
+ node = root;
+ for (i = 0; i < ctx->path->nelts; ++i)
+ {
+ const node_segment_pair_t *step
+ = &APR_ARRAY_IDX(ctx->path, i, const node_segment_pair_t);
+
+ /* Exploit the fact that all strings in the authz model are unique /
+ * internized and can be identified by address alone. */
+ if ( !step->node
+ || i >= acl->rule.len
+ || step->segment->kind != acl->rule.path[i].kind
+ || step->segment->pattern.data != acl->rule.path[i].pattern.data)
+ {
+ ctx->path->nelts = i;
+ break;
+ }
+ else
+ {
+ node = step->node;
+ }
+ }
+ /* Insert the path rule into the filtered tree. */
+ insert_path(ctx, node, &path_access,
+ acl->rule.len - i, acl->rule.path + i,
+ result_pool, scratch_pool);
+}
-/* Callback to parse one line of an authz file and update the
- * authz_baton accordingly.
+/* Forward declaration ... */
+static svn_boolean_t
+trim_tree(node_t *node,
+ int latest_any_var,
+ apr_pool_t *scratch_pool);
+
+/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *HASH of
+ * node_t * and remove empty nodes from. *HASH may be NULL. If all nodes
+ * could be removed, set *HASH to NULL and return TRUE. Allocate temporary
+ * data in SCRATCH_POOL.
*/
static svn_boolean_t
-authz_parse_line(const char *name, const char *value,
- void *baton, apr_pool_t *pool)
+trim_subnode_hash(apr_hash_t **hash,
+ int latest_any_var,
+ apr_pool_t *scratch_pool)
{
- struct authz_lookup_baton *b = baton;
+ if (*hash)
+ {
+ apr_array_header_t *to_remove = apr_array_make(scratch_pool, 0,
+ sizeof(node_t *));
- /* Stop if the rule doesn't apply to this user. */
- if (!authz_line_applies_to_user(name, b, pool))
- return TRUE;
+ apr_hash_index_t *hi;
+ for (hi = apr_hash_first(scratch_pool, *hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ node_t *node = apr_hash_this_val(hi);
+ if (trim_tree(node, latest_any_var, scratch_pool))
+ APR_ARRAY_PUSH(to_remove, node_t *) = node;
+ }
- /* Set the access grants for the rule. */
- if (strchr(value, 'r'))
- b->allow |= svn_authz_read;
- else
- b->deny |= svn_authz_read;
+ /* Are some nodes left? */
+ if (to_remove->nelts < apr_hash_count(*hash))
+ {
+ /* Remove empty nodes (if any). */
+ int i;
+ for (i = 0; i < to_remove->nelts; ++i)
+ {
+ node_t *node = APR_ARRAY_IDX(to_remove, i, node_t *);
+ apr_hash_set(*hash, node->segment.data, node->segment.len,
+ NULL);
+ }
- if (strchr(value, 'w'))
- b->allow |= svn_authz_write;
- else
- b->deny |= svn_authz_write;
+ return FALSE;
+ }
+
+ /* No nodes left. A NULL hash is more efficient than an empty one. */
+ *hash = NULL;
+ }
return TRUE;
}
-
-/* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC
- * (which is a repository name, colon, and repository fspath, such as
- * "myrepos:/trunk/foo").
+/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *ARRAY of
+ * node_t * and remove empty nodes from. *ARRAY may be NULL. If all nodes
+ * could be removed, set *ARRAY to NULL and return TRUE. Allocate
+ * temporary data in SCRATCH_POOL.
*/
static svn_boolean_t
-is_applicable_section(const char *path_spec,
- const char *section_name)
+trim_subnode_array(apr_array_header_t **array,
+ int latest_any_var,
+ apr_pool_t *scratch_pool)
{
- apr_size_t path_spec_len = strlen(path_spec);
+ if (*array)
+ {
+ int i, dest;
+ for (i = 0, dest = 0; i < (*array)->nelts; ++i)
+ {
+ node_t *node = APR_ARRAY_IDX(*array, i, sorted_pattern_t).node;
+ if (!trim_tree(node, latest_any_var, scratch_pool))
+ {
+ if (i != dest)
+ APR_ARRAY_IDX(*array, dest, sorted_pattern_t)
+ = APR_ARRAY_IDX(*array, i, sorted_pattern_t);
+ ++dest;
+ }
+ }
- return ((strncmp(path_spec, section_name, path_spec_len) == 0)
- && (path_spec[path_spec_len - 1] == '/'
- || section_name[path_spec_len] == '/'
- || section_name[path_spec_len] == '\0'));
-}
+ /* Are some nodes left? */
+ if (dest)
+ {
+ /* Trim it to the number of valid entries. */
+ (*array)->nelts = dest;
+ return FALSE;
+ }
+
+ /* No nodes left. A NULL array is more efficient than an empty one. */
+ *array = NULL;
+ }
+ return TRUE;
+}
-/* Callback to parse a section and update the authz_baton if the
- * section denies access to the subtree the baton describes.
+/* Remove all rules and sub-nodes from NODE that are fully eclipsed by the
+ * "any-var" rule with sequence number LATEST_ANY_VAR. Return TRUE, if
+ * there are no rules left in the sub-tree, including NODE.
+ * Allocate temporary data in SCRATCH_POOL.
*/
static svn_boolean_t
-authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
+trim_tree(node_t *node,
+ int latest_any_var,
+ apr_pool_t *scratch_pool)
{
- struct authz_lookup_baton *b = baton;
- svn_boolean_t conclusive;
+ svn_boolean_t removed_all = TRUE;
- /* Does the section apply to us? */
- if (!is_applicable_section(b->qualified_repos_path, section_name)
- && !is_applicable_section(b->repos_path, section_name))
+ /* For convenience, we allow NODE to be NULL: */
+ if (!node)
return TRUE;
- /* Work out what this section grants. */
- b->allow = b->deny = 0;
- svn_config_enumerate2(b->config, section_name,
- authz_parse_line, b, pool);
+ /* Do we have a later "any_var" rule at this node. */
+ if ( node->pattern_sub_nodes
+ && node->pattern_sub_nodes->any_var
+ && node->pattern_sub_nodes->any_var->rights.access.sequence_number
+ > latest_any_var)
+ {
+ latest_any_var
+ = node->pattern_sub_nodes->any_var->rights.access.sequence_number;
+ }
+
+ /* Is there a local rule at this node that is not eclipsed by any_var? */
+ if (has_local_rule(&node->rights))
+ {
+ /* Remove the local rule, if it got eclipsed.
+ * Note that for the latest any_var node, the sequence number is equal. */
+ if (node->rights.access.sequence_number >= latest_any_var)
+ removed_all = FALSE;
+ else
+ node->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
+ }
+
+ /* Process all sub-nodes. */
+ removed_all &= trim_subnode_hash(&node->sub_nodes, latest_any_var,
+ scratch_pool);
- /* Has the section explicitly determined an access? */
- conclusive = authz_access_is_determined(b->allow, b->deny,
- b->required_access);
+ if (node->pattern_sub_nodes)
+ {
+ if (trim_tree(node->pattern_sub_nodes->any, latest_any_var,
+ scratch_pool))
+ node->pattern_sub_nodes->any = NULL;
+ else
+ removed_all = FALSE;
- /* Is access granted OR inconclusive? */
- b->access = authz_access_is_granted(b->allow, b->deny,
- b->required_access)
- || !conclusive;
+ if (trim_tree(node->pattern_sub_nodes->any_var, latest_any_var,
+ scratch_pool))
+ node->pattern_sub_nodes->any_var = NULL;
+ else
+ removed_all = FALSE;
+
+ removed_all &= trim_subnode_array(&node->pattern_sub_nodes->prefixes,
+ latest_any_var, scratch_pool);
+ removed_all &= trim_subnode_array(&node->pattern_sub_nodes->suffixes,
+ latest_any_var, scratch_pool);
+ removed_all &= trim_subnode_array(&node->pattern_sub_nodes->complex,
+ latest_any_var, scratch_pool);
+
+ /* Trim the tree as much as possible to speed up lookup(). */
+ if (removed_all)
+ node->pattern_sub_nodes = NULL;
+ }
- /* As long as access isn't conclusively denied, carry on. */
- return b->access;
+ return removed_all;
}
+/* Forward declaration ... */
+static void
+finalize_tree(node_t *node,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool);
-/* Validate access to the given user for the given path. This
- * function checks rules for exactly the given path, and first tries
- * to access a section specific to the given repository before falling
- * back to pan-repository rules.
- *
- * Update *access_granted to inform the caller of the outcome of the
- * lookup. Return a boolean indicating whether the access rights were
- * successfully determined.
+/* Call finalize_tree() on all elements in the HASH of node_t *, passing
+ * SUM along. HASH may be NULL. Use SCRATCH_POOL for temporary allocations.
*/
-static svn_boolean_t
-authz_get_path_access(svn_config_t *cfg, const char *repos_name,
- const char *path, const char *user,
- svn_repos_authz_access_t required_access,
- svn_boolean_t *access_granted,
- apr_pool_t *pool)
+static void
+finalize_subnode_hash(apr_hash_t *hash,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool)
{
- const char *qualified_path;
- struct authz_lookup_baton baton = { 0 };
-
- baton.config = cfg;
- baton.user = user;
-
- /* Try to locate a repository-specific block first. */
- qualified_path = apr_pstrcat(pool, repos_name, ":", path, SVN_VA_NULL);
- svn_config_enumerate2(cfg, qualified_path,
- authz_parse_line, &baton, pool);
-
- *access_granted = authz_access_is_granted(baton.allow, baton.deny,
- required_access);
+ if (hash)
+ {
+ apr_hash_index_t *hi;
+ for (hi = apr_hash_first(scratch_pool, hash);
+ hi;
+ hi = apr_hash_next(hi))
+ finalize_tree(apr_hash_this_val(hi), sum, scratch_pool);
+ }
+}
- /* If the first test has determined access, stop now. */
- if (authz_access_is_determined(baton.allow, baton.deny,
- required_access))
- return TRUE;
+/* Call finalize_up_tree() on all elements in the ARRAY of node_t *,
+ * passing SUM along. ARRAY may be NULL. Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+static void
+finalize_subnode_array(apr_array_header_t *array,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool)
+{
+ if (array)
+ {
+ int i;
+ for (i = 0; i < array->nelts; ++i)
+ finalize_tree(APR_ARRAY_IDX(array, i, sorted_pattern_t).node, sum,
+ scratch_pool);
+ }
+}
- /* No repository specific rule, try pan-repository rules. */
- svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool);
+/* Link prefixes within the sorted ARRAY. */
+static void
+link_prefix_patterns(apr_array_header_t *array)
+{
+ int i;
+ if (!array)
+ return;
- *access_granted = authz_access_is_granted(baton.allow, baton.deny,
- required_access);
- return authz_access_is_determined(baton.allow, baton.deny,
- required_access);
+ for (i = 1; i < array->nelts; ++i)
+ {
+ sorted_pattern_t *prev
+ = &APR_ARRAY_IDX(array, i - 1, sorted_pattern_t);
+ sorted_pattern_t *pattern
+ = &APR_ARRAY_IDX(array, i, sorted_pattern_t);
+
+ /* Does PATTERN potentially have a prefix in ARRAY?
+ * If so, at least the first char must match with the predecessor's
+ * because the array is sorted by that string. */
+ if (prev->node->segment.data[0] != pattern->node->segment.data[0])
+ continue;
+
+ /* Only the predecessor or any of its prefixes can be the closest
+ * prefix to PATTERN. */
+ for ( ; prev; prev = prev->next)
+ if ( prev->node->segment.len < pattern->node->segment.len
+ && !memcmp(prev->node->segment.data,
+ pattern->node->segment.data,
+ prev->node->segment.len))
+ {
+ pattern->next = prev;
+ break;
+ }
+ }
}
-
-/* Validate access to the given user for the subtree starting at the
- * given path. This function walks the whole authz file in search of
- * rules applying to paths in the requested subtree which deny the
- * requested access.
- *
- * As soon as one is found, or else when the whole ACL file has been
- * searched, return the updated authorization status.
+/* Recursively finalization the tree node properties for NODE. Update SUM
+ * (of NODE's parent) by combining it with the recursive access rights info
+ * on NODE. Use SCRATCH_POOL for temporary allocations.
*/
-static svn_boolean_t
-authz_get_tree_access(svn_config_t *cfg, const char *repos_name,
- const char *path, const char *user,
- svn_repos_authz_access_t required_access,
- apr_pool_t *pool)
+static void
+finalize_tree(node_t *node,
+ limited_rights_t *sum,
+ apr_pool_t *scratch_pool)
{
- struct authz_lookup_baton baton = { 0 };
+ limited_rights_t *local_sum = &node->rights;
- baton.config = cfg;
- baton.user = user;
- baton.required_access = required_access;
- baton.repos_path = path;
- baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
- ":", path, SVN_VA_NULL);
- /* Default to access granted if no rules say otherwise. */
- baton.access = TRUE;
+ /* For convenience, we allow NODE to be NULL: */
+ if (!node)
+ return;
- svn_config_enumerate_sections2(cfg, authz_parse_section,
- &baton, pool);
+ /* Sum of rights at NODE - so far. */
+ if (has_local_rule(local_sum))
+ {
+ local_sum->max_rights = local_sum->access.rights;
+ local_sum->min_rights = local_sum->access.rights;
+ }
+ else
+ {
+ local_sum->min_rights = authz_access_write;
+ local_sum->max_rights = authz_access_none;
+ }
- return baton.access;
-}
+ /* Process all sub-nodes. */
+ finalize_subnode_hash(node->sub_nodes, local_sum, scratch_pool);
+ if (node->pattern_sub_nodes)
+ {
+ finalize_tree(node->pattern_sub_nodes->any, local_sum, scratch_pool);
+ finalize_tree(node->pattern_sub_nodes->any_var, local_sum, scratch_pool);
+
+ finalize_subnode_array(node->pattern_sub_nodes->prefixes, local_sum,
+ scratch_pool);
+ finalize_subnode_array(node->pattern_sub_nodes->suffixes, local_sum,
+ scratch_pool);
+ finalize_subnode_array(node->pattern_sub_nodes->complex, local_sum,
+ scratch_pool);
+
+ /* Link up the prefixes / suffixes. */
+ link_prefix_patterns(node->pattern_sub_nodes->prefixes);
+ link_prefix_patterns(node->pattern_sub_nodes->suffixes);
+ }
-/* Callback to parse sections of the configuration file, looking for
- any kind of granted access. Implements the
- svn_config_section_enumerator2_t interface. */
-static svn_boolean_t
-authz_get_any_access_parser_cb(const char *section_name, void *baton,
- apr_pool_t *pool)
-{
- struct authz_lookup_baton *b = baton;
+ /* Add our min / max info to the parent's info.
+ * Idempotent for parent == node (happens at root). */
+ combine_right_limits(sum, local_sum);
+}
- /* Does the section apply to the query? */
- if (section_name[0] == '/'
- || strncmp(section_name, b->qualified_repos_path,
- strlen(b->qualified_repos_path)) == 0)
+/* From the authz CONFIG, extract the parts relevant to USER and REPOSITORY.
+ * Return the filtered rule tree.
+ */
+static node_t *
+create_user_authz(authz_full_t *authz,
+ const char *repository,
+ const char *user,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ node_t *root = create_node(NULL, result_pool);
+ construction_context_t *ctx = create_construction_context(scratch_pool);
+
+ /* Use a separate sub-pool to keep memory usage tight. */
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+
+ /* Find all ACLs for REPOSITORY.
+ * Note that repo-specific rules replace global rules,
+ * even if they don't apply to the current user. */
+ apr_array_header_t *acls = apr_array_make(subpool, authz->acls->nelts,
+ sizeof(authz_acl_t *));
+ for (i = 0; i < authz->acls->nelts; ++i)
{
- b->allow = b->deny = svn_authz_none;
+ const authz_acl_t *acl = &APR_ARRAY_IDX(authz->acls, i, authz_acl_t);
+ if (svn_authz__acl_applies_to_repo(acl, repository))
+ {
+ /* ACLs in the AUTHZ are sorted by path and repository.
+ * So, if there is a rule for the repo and a global rule for the
+ * same path, we will detect them here. */
+ if (acls->nelts)
+ {
+ const authz_acl_t *prev_acl
+ = APR_ARRAY_IDX(acls, acls->nelts - 1, const authz_acl_t *);
+ if (svn_authz__compare_paths(&prev_acl->rule, &acl->rule) == 0)
+ {
+ SVN_ERR_ASSERT_NO_RETURN(!strcmp(prev_acl->rule.repos,
+ AUTHZ_ANY_REPOSITORY));
+ SVN_ERR_ASSERT_NO_RETURN(strcmp(acl->rule.repos,
+ AUTHZ_ANY_REPOSITORY));
+ apr_array_pop(acls);
+ }
+ }
+
+ APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl;
+ }
+ }
- svn_config_enumerate2(b->config, section_name,
- authz_parse_line, baton, pool);
- b->access = authz_access_is_granted(b->allow, b->deny,
- b->required_access);
+ /* Filtering and tree construction. */
+ for (i = 0; i < acls->nelts; ++i)
+ process_acl(ctx, APR_ARRAY_IDX(acls, i, const authz_acl_t *),
+ root, repository, user, result_pool, subpool);
- /* Continue as long as we don't find a determined, granted access. */
- return !(b->access
- && authz_access_is_determined(b->allow, b->deny,
- b->required_access));
+ /* If there is no relevant rule at the root node, the "no access" default
+ * applies. Give it a SEQUENCE_NUMBER that will never overrule others. */
+ if (!has_local_rule(&root->rights))
+ {
+ root->rights.access.sequence_number = 0;
+ root->rights.access.rights = authz_access_none;
}
- return TRUE;
-}
-
+ /* Trim the tree.
+ *
+ * We can't do pattern comparison, so for most pattern rules we cannot
+ * say that a set of rules "eclipses" / overrides a given other set of
+ * rules for all possible paths. That limits the accuracy of our check
+ * for recursive access in similar ways than for non-pattern rules.
+ *
+ * However, the user expects a rule ending with "**" to eclipse any older
+ * rule in that sub-tree recursively. So, this trim function removes
+ * eclipsed nodes from the tree.
+ */
+ svn_pool_clear(subpool);
+ trim_tree(root, NO_SEQUENCE_NUMBER, subpool);
-/* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS
- * to any path within the REPOSITORY. Return TRUE if so. Use POOL
- * for temporary allocations. */
-static svn_boolean_t
-authz_get_any_access(svn_config_t *cfg, const char *repos_name,
- const char *user,
- svn_repos_authz_access_t required_access,
- apr_pool_t *pool)
-{
- struct authz_lookup_baton baton = { 0 };
-
- baton.config = cfg;
- baton.user = user;
- baton.required_access = required_access;
- baton.access = FALSE; /* Deny access by default. */
- baton.repos_path = "/";
- baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
- ":/", SVN_VA_NULL);
-
- /* We could have used svn_config_enumerate2 for "repos_name:/".
- * However, this requires access for root explicitly (which the user
- * may not always have). So we end up enumerating the sections in
- * the authz CFG and stop on the first match with some access for
- * this user. */
- svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb,
- &baton, pool);
-
- /* If walking the configuration was inconclusive, deny access. */
- if (!authz_access_is_determined(baton.allow,
- baton.deny, baton.required_access))
- return FALSE;
+ /* Calculate recursive rights.
+ *
+ * This is a bottom-up calculation of the range of access rights
+ * specified anywhere in the respective sub-tree, including the base
+ * node itself.
+ *
+ * To prevent additional finalization passes, we piggy-back the addition
+ * of the ordering links of the prefix and suffix sub-node rules.
+ */
+ svn_pool_clear(subpool);
+ finalize_tree(root, &root->rights, subpool);
- return baton.access;
+ /* Done. */
+ svn_pool_destroy(subpool);
+ return root;
}
-
-/*** Validating the authz file. ***/
+/*** Lookup. ***/
-/* Check for errors in GROUP's definition of CFG. The errors
- * detected are references to non-existent groups and circular
- * dependencies between groups. If an error is found, return
- * SVN_ERR_AUTHZ_INVALID_CONFIG. Use POOL for temporary
- * allocations only.
- *
- * CHECKED_GROUPS should be an empty (it is used for recursive calls).
- */
-static svn_error_t *
-authz_group_walk(svn_config_t *cfg,
- const char *group,
- apr_hash_t *checked_groups,
- apr_pool_t *pool)
+/* Reusable lookup state object. It is easy to pass to functions and
+ * recycling it between lookups saves significant setup costs. */
+typedef struct lookup_state_t
{
- const char *value;
- apr_array_header_t *list;
- int i;
+ /* Rights immediately applying to this node and limits to the rights to
+ * any sub-path. */
+ limited_rights_t rights;
- svn_config_get(cfg, &value, "groups", group, NULL);
- /* Having a non-existent group in the ACL configuration might be the
- sign of a typo. Refuse to perform authz on uncertain rules. */
- if (!value)
- return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "An authz rule refers to group '%s', "
- "which is undefined",
- group);
+ /* Nodes applying to the path followed so far. */
+ apr_array_header_t *current;
- list = svn_cstring_split(value, ",", TRUE, pool);
+ /* Temporary array containing the nodes applying to the next path
+ * segment (used to build up the next contents of CURRENT). */
+ apr_array_header_t *next;
- for (i = 0; i < list->nelts; i++)
- {
- const char *group_user = APR_ARRAY_IDX(list, i, char *);
+ /* Scratch pad for path operations. */
+ svn_stringbuf_t *scratch_pad;
- /* If the 'user' is a subgroup, recurse into it. */
- if (*group_user == '@')
- {
- /* A circular dependency between groups is a Bad Thing. We
- don't do authz with invalid ACL files. */
- if (svn_hash_gets(checked_groups, &group_user[1]))
- return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
- NULL,
- "Circular dependency between "
- "groups '%s' and '%s'",
- &group_user[1], group);
-
- /* Add group to hash of checked groups. */
- svn_hash_sets(checked_groups, &group_user[1], "");
-
- /* Recurse on that group. */
- SVN_ERR(authz_group_walk(cfg, &group_user[1],
- checked_groups, pool));
-
- /* Remove group from hash of checked groups, so that we don't
- incorrectly report an error if we see it again as part of
- another group. */
- svn_hash_sets(checked_groups, &group_user[1], NULL);
- }
- else if (*group_user == '&')
- {
- const char *alias;
-
- svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL);
- /* Having a non-existent alias in the ACL configuration might be the
- sign of a typo. Refuse to perform authz on uncertain rules. */
- if (!alias)
- return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "An authz rule refers to alias '%s', "
- "which is undefined",
- &group_user[1]);
- }
- }
+ /* After each lookup iteration, CURRENT and PARENT_RIGHTS will
+ * apply to this path. */
+ svn_stringbuf_t *parent_path;
- return SVN_NO_ERROR;
-}
+ /* Rights that apply at PARENT_PATH, if PARENT_PATH is not empty. */
+ limited_rights_t parent_rights;
+} lookup_state_t;
-/* Callback to perform some simple sanity checks on an authz rule.
- *
- * - If RULE_MATCH_STRING references a group or an alias, verify that
- * the group or alias definition exists.
- * - If RULE_MATCH_STRING specifies a token (starts with $), verify
- * that the token name is valid.
- * - If RULE_MATCH_STRING is using inversion, verify that it isn't
- * doing it more than once within the one rule, and that it isn't
- * "~*", as that would never match.
- * - Check that VALUE part of the rule specifies only allowed rule
- * flag characters ('r' and 'w').
- *
- * Return TRUE if the rule has no errors. Use BATON for context and
- * error reporting.
- */
-static svn_boolean_t authz_validate_rule(const char *rule_match_string,
- const char *value,
- void *baton,
- apr_pool_t *pool)
+/* Constructor for lookup_state_t. */
+static lookup_state_t *
+create_lookup_state(apr_pool_t *result_pool)
{
- const char *val;
- const char *match = rule_match_string;
- struct authz_validate_baton *b = baton;
+ lookup_state_t *state = apr_pcalloc(result_pool, sizeof(*state));
+
+ state->next = apr_array_make(result_pool, 4, sizeof(node_t *));
+ state->current = apr_array_make(result_pool, 4, sizeof(node_t *));
- /* Make sure the user isn't using double-negatives. */
- if (match[0] == '~')
- {
- /* Bump the pointer past the inversion for the other checks. */
- match++;
+ /* Virtually all path segments should fit into this buffer. If they
+ * don't, the buffer gets automatically reallocated.
+ *
+ * Using a smaller initial size would be fine as well but does not
+ * buy us much for the increased risk of being expanded anyway - at
+ * some extra cost. */
+ state->scratch_pad = svn_stringbuf_create_ensure(200, result_pool);
- /* Another inversion is a double negative; we can't not stop. */
- if (match[0] == '~')
- {
- b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "Rule '%s' has more than one "
- "inversion; double negatives are "
- "not permitted.",
- rule_match_string);
- return FALSE;
- }
+ /* Most paths should fit into this buffer. The same rationale as
+ * above applies. */
+ state->parent_path = svn_stringbuf_create_ensure(200, result_pool);
- /* Make sure that the rule isn't "~*", which won't ever match. */
- if (strcmp(match, "*") == 0)
- {
- b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "Authz rules with match string '~*' "
- "are not allowed, because they never "
- "match anyone.");
- return FALSE;
- }
- }
+ return state;
+}
- /* If the rule applies to a group, check its existence. */
- if (match[0] == '@')
+/* Clear the current contents of STATE and re-initialize it for ROOT.
+ * Check whether we can reuse a previous parent path lookup to shorten
+ * the current PATH walk. Return the full or remaining portion of
+ * PATH, respectively. PATH must not be NULL. */
+static const char *
+init_lockup_state(lookup_state_t *state,
+ node_t *root,
+ const char *path)
+{
+ apr_size_t len = strlen(path);
+ if ( (len > state->parent_path->len)
+ && state->parent_path->len
+ && (path[state->parent_path->len] == '/')
+ && !memcmp(path, state->parent_path->data, state->parent_path->len))
{
- const char *group = &match[1];
+ /* The PARENT_PATH of the previous lookup is actually a parent path
+ * of PATH. The CURRENT node list already matches the parent path
+ * and we only have to set the correct rights info. */
+ state->rights = state->parent_rights;
- svn_config_get(b->config, &val, "groups", group, NULL);
-
- /* Having a non-existent group in the ACL configuration might be
- the sign of a typo. Refuse to perform authz on uncertain
- rules. */
- if (!val)
- {
- b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "An authz rule refers to group "
- "'%s', which is undefined",
- rule_match_string);
- return FALSE;
- }
+ /* Tell the caller where to proceed. */
+ return path + state->parent_path->len;
}
- /* If the rule applies to an alias, check its existence. */
- if (match[0] == '&')
- {
- const char *alias = &match[1];
+ /* Start lookup at ROOT for the full PATH. */
+ state->rights = root->rights;
+ state->parent_rights = root->rights;
- svn_config_get(b->config, &val, "aliases", alias, NULL);
+ apr_array_clear(state->next);
+ apr_array_clear(state->current);
+ APR_ARRAY_PUSH(state->current, node_t *) = root;
- if (!val)
- {
- b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "An authz rule refers to alias "
- "'%s', which is undefined",
- rule_match_string);
- return FALSE;
- }
- }
+ /* Var-segment rules match empty segments as well */
+ if (root->pattern_sub_nodes && root->pattern_sub_nodes->any_var)
+ {
+ node_t *node = root->pattern_sub_nodes->any_var;
- /* If the rule specifies a token, check its validity. */
- if (match[0] == '$')
- {
- const char *token_name = &match[1];
+ /* This is non-recursive due to ACL normalization. */
+ combine_access(&state->rights, &node->rights);
+ combine_right_limits(&state->rights, &node->rights);
+ APR_ARRAY_PUSH(state->current, node_t *) = node;
+ }
+
+ svn_stringbuf_setempty(state->parent_path);
+ svn_stringbuf_setempty(state->scratch_pad);
- if ((strcmp(token_name, "anonymous") != 0)
- && (strcmp(token_name, "authenticated") != 0))
+ return path;
+}
+
+/* Add NODE to the list of NEXT nodes in STATE. NODE may be NULL in which
+ * case this is a no-op. Also update and aggregate the access rights data
+ * for the next path segment.
+ */
+static void
+add_next_node(lookup_state_t *state,
+ node_t *node)
+{
+ /* Allowing NULL nodes simplifies the caller. */
+ if (node)
+ {
+ /* The rule with the highest sequence number is the one that applies.
+ * Not all nodes that we are following have rules that apply directly
+ * to this path but are mere intermediates that may only have some
+ * matching deep sub-node. */
+ combine_access(&state->rights, &node->rights);
+
+ /* The rule tree node can be seen as an overlay of all the nodes that
+ * we are following. Any of them _may_ match eventually, so the min/
+ * max possible access rights are a combination of all these sub-trees.
+ */
+ combine_right_limits(&state->rights, &node->rights);
+
+ /* NODE is now enlisted as a (potential) match for the next segment. */
+ APR_ARRAY_PUSH(state->next, node_t *) = node;
+
+ /* Variable length sub-segment sequences apply to the same node as
+ * they match empty sequences as well. */
+ if (node->pattern_sub_nodes && node->pattern_sub_nodes->any_var)
{
- b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "Unrecognized authz token '%s'.",
- rule_match_string);
- return FALSE;
+ node = node->pattern_sub_nodes->any_var;
+
+ /* This is non-recursive due to ACL normalization. */
+ combine_access(&state->rights, &node->rights);
+ combine_right_limits(&state->rights, &node->rights);
+ APR_ARRAY_PUSH(state->next, node_t *) = node;
}
}
+}
- val = value;
+/* If PREFIX is indeed a prefix (or exact match) or SEGMENT, add the
+ * node in PREFIX to STATE. */
+static void
+add_if_prefix_matches(lookup_state_t *state,
+ const sorted_pattern_t *prefix,
+ const svn_stringbuf_t *segment)
+{
+ node_t *node = prefix->node;
+ if ( node->segment.len <= segment->len
+ && !memcmp(node->segment.data, segment->data, node->segment.len))
+ add_next_node(state, node);
+}
- while (*val)
+/* Scan the PREFIXES array of node_t* for all entries whose SEGMENT members
+ * are prefixes of SEGMENT. Add these to STATE for the next tree level. */
+static void
+add_prefix_matches(lookup_state_t *state,
+ const svn_stringbuf_t *segment,
+ apr_array_header_t *prefixes)
+{
+ /* Index of the first node that might be a match. All matches will
+ * be at this and the immediately following indexes. */
+ int i = svn_sort__bsearch_lower_bound(prefixes, segment->data,
+ compare_node_path_segment);
+
+ /* The entry we found may be an exact match (but not a true prefix).
+ * The prefix matching test will still work. */
+ if (i < prefixes->nelts)
+ add_if_prefix_matches(state,
+ &APR_ARRAY_IDX(prefixes, i, sorted_pattern_t),
+ segment);
+
+ /* The immediate predecessor may be a true prefix and all potential
+ * prefixes can be found following the NEXT links between the array
+ * indexes. */
+ if (i > 0)
{
- if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
+ sorted_pattern_t *pattern;
+ for (pattern = &APR_ARRAY_IDX(prefixes, i - 1, sorted_pattern_t);
+ pattern;
+ pattern = pattern->next)
{
- b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "The character '%c' in rule '%s' is not "
- "allowed in authz rules", *val,
- rule_match_string);
- return FALSE;
+ add_if_prefix_matches(state, pattern, segment);
}
-
- ++val;
}
-
- return TRUE;
}
-/* Callback to check ALIAS's definition for validity. Use
- BATON for context and error reporting. */
-static svn_boolean_t authz_validate_alias(const char *alias,
- const char *value,
- void *baton,
- apr_pool_t *pool)
+/* Scan the PATTERNS array of node_t* for all entries whose SEGMENT members
+ * (usually containing wildcards) match SEGMENT. Add these to STATE for the
+ * next tree level. */
+static void
+add_complex_matches(lookup_state_t *state,
+ const svn_stringbuf_t *segment,
+ apr_array_header_t *patterns)
{
- /* No checking at the moment, every alias is valid */
- return TRUE;
+ int i;
+ for (i = 0; i < patterns->nelts; ++i)
+ {
+ node_t *node = APR_ARRAY_IDX(patterns, i, sorted_pattern_t).node;
+ if (0 == apr_fnmatch(node->segment.data, segment->data, 0))
+ add_next_node(state, node);
+ }
}
-
-/* Callback to check GROUP's definition for cyclic dependancies. Use
- BATON for context and error reporting. */
-static svn_boolean_t authz_validate_group(const char *group,
- const char *value,
- void *baton,
- apr_pool_t *pool)
+/* Extract the next segment from PATH and copy it into SEGMENT, whose current
+ * contents get overwritten. Empty paths ("") are supported and leading '/'
+ * segment separators will be interpreted as an empty segment (""). Non-
+ * normalizes parts, i.e. sequences of '/', will be treated as a single '/'.
+ *
+ * Return the start of the next segment within PATH, skipping the '/'
+ * separator(s). Return NULL, if there are no further segments.
+ *
+ * The caller (only called by lookup(), ATM) must ensure that SEGMENT has
+ * enough room to store all of PATH.
+ */
+static const char *
+next_segment(svn_stringbuf_t *segment,
+ const char *path)
{
- struct authz_validate_baton *b = baton;
+ apr_size_t len;
+ char c;
+
+ /* Read and scan PATH for NUL and '/' -- whichever comes first. */
+ for (len = 0, c = *path; c; c = path[++len])
+ if (c == '/')
+ {
+ /* End of segment. */
+ segment->data[len] = 0;
+ segment->len = len;
+
+ /* If PATH is not normalized, this is where we skip whole sequences
+ * of separators. */
+ while (path[++len] == '/')
+ ;
+
+ /* Continue behind the last separator in the sequence. We will
+ * treat trailing '/' as indicating an empty trailing segment.
+ * Therefore, we never have to return NULL here. */
+ return path + len;
+ }
+ else
+ {
+ /* Copy segment contents directly into the result buffer.
+ * On many architectures, this is almost or entirely for free. */
+ segment->data[len] = c;
+ }
+
+ /* No separator found, so all of PATH has been the last segment. */
+ segment->data[len] = 0;
+ segment->len = len;
+
+ /* Tell the caller that this has been the last segment. */
+ return NULL;
+}
- b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool);
- if (b->err)
- return FALSE;
+/* Starting at the respective user's authz root node provided with STATE,
+ * follow PATH and return TRUE, iff the REQUIRED access has been granted to
+ * that user for this PATH. REQUIRED must not contain svn_authz_recursive.
+ * If RECURSIVE is set, all paths in the sub-tree at and below PATH must
+ * have REQUIRED access. PATH does not need to be normalized, may be empty
+ * but must not be NULL.
+ */
+static svn_boolean_t
+lookup(lookup_state_t *state,
+ const char *path,
+ authz_access_t required,
+ svn_boolean_t recursive,
+ apr_pool_t *scratch_pool)
+{
+ /* Create a scratch pad large enough to hold any of PATH's segments. */
+ apr_size_t path_len = strlen(path);
+ svn_stringbuf_ensure(state->scratch_pad, path_len);
- return TRUE;
-}
+ /* Normalize start and end of PATH. Most paths will be fully normalized,
+ * so keep the overhead as low as possible. */
+ if (path_len && path[path_len-1] == '/')
+ {
+ do
+ {
+ --path_len;
+ }
+ while (path_len && path[path_len-1] == '/');
+ path = apr_pstrmemdup(scratch_pool, path, path_len);
+ }
+ while (path[0] == '/')
+ ++path; /* Don't update PATH_LEN as we won't need it anymore. */
-/* Callback to check the contents of the configuration section given
- by NAME. Use BATON for context and error reporting. */
-static svn_boolean_t authz_validate_section(const char *name,
- void *baton,
- apr_pool_t *pool)
-{
- struct authz_validate_baton *b = baton;
-
- /* Use the group checking callback for the "groups" section... */
- if (strcmp(name, "groups") == 0)
- svn_config_enumerate2(b->config, name, authz_validate_group,
- baton, pool);
- /* ...and the alias checking callback for "aliases"... */
- else if (strcmp(name, "aliases") == 0)
- svn_config_enumerate2(b->config, name, authz_validate_alias,
- baton, pool);
- /* ...but for everything else use the rule checking callback. */
- else
+ /* Actually walk the path rule tree following PATH until we run out of
+ * either tree or PATH. */
+ while (state->current->nelts && path)
{
- /* Validate the section's name. Skip the optional REPOS_NAME. */
- const char *fspath = strchr(name, ':');
- if (fspath)
- fspath++;
- else
- fspath = name;
- if (! svn_fspath__is_canonical(fspath))
+ apr_array_header_t *temp;
+ int i;
+ svn_stringbuf_t *segment = state->scratch_pad;
+
+ /* Shortcut 1: We could nowhere find enough rights in this sub-tree. */
+ if ((state->rights.max_rights & required) != required)
+ return FALSE;
+
+ /* Shortcut 2: We will find enough rights everywhere in this sub-tree. */
+ if ((state->rights.min_rights & required) == required)
+ return TRUE;
+
+ /* Extract the next segment. */
+ path = next_segment(segment, path);
+
+ /* Initial state for this segment. */
+ apr_array_clear(state->next);
+ state->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
+ state->rights.access.rights = authz_access_none;
+
+ /* These init values ensure that the first node's value will be used
+ * when combined with them. If there is no first node,
+ * state->access.sequence_number remains unchanged and we will use
+ * the parent's (i.e. inherited) access rights. */
+ state->rights.min_rights = authz_access_write;
+ state->rights.max_rights = authz_access_none;
+
+ /* Update the PARENT_PATH member in STATE to match the nodes in
+ * CURRENT at the end of this iteration, i.e. if and when NEXT
+ * has become CURRENT. */
+ if (path)
{
- b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "Section name '%s' contains non-canonical "
- "fspath '%s'",
- name, fspath);
- return FALSE;
+ svn_stringbuf_appendbyte(state->parent_path, '/');
+ svn_stringbuf_appendbytes(state->parent_path, segment->data,
+ segment->len);
}
- svn_config_enumerate2(b->config, name, authz_validate_rule,
- baton, pool);
+ /* Scan follow all alternative routes to the next level. */
+ for (i = 0; i < state->current->nelts; ++i)
+ {
+ node_t *node = APR_ARRAY_IDX(state->current, i, node_t *);
+ if (node->sub_nodes)
+ add_next_node(state, apr_hash_get(node->sub_nodes, segment->data,
+ segment->len));
+
+ /* Process alternative, wildcard-based sub-nodes. */
+ if (node->pattern_sub_nodes)
+ {
+ add_next_node(state, node->pattern_sub_nodes->any);
+
+ /* If the current node represents a "**" pattern, it matches
+ * to all levels. So, add it to the list for the NEXT level. */
+ if (node->pattern_sub_nodes->repeat)
+ add_next_node(state, node);
+
+ /* Find all prefix pattern matches. */
+ if (node->pattern_sub_nodes->prefixes)
+ add_prefix_matches(state, segment,
+ node->pattern_sub_nodes->prefixes);
+
+ if (node->pattern_sub_nodes->complex)
+ add_complex_matches(state, segment,
+ node->pattern_sub_nodes->complex);
+
+ /* Find all suffux pattern matches.
+ * This must be the last check as it destroys SEGMENT. */
+ if (node->pattern_sub_nodes->suffixes)
+ {
+ /* Suffixes behave like reversed prefixes. */
+ svn_authz__reverse_string(segment->data, segment->len);
+ add_prefix_matches(state, segment,
+ node->pattern_sub_nodes->suffixes);
+ }
+ }
+ }
+
+ /* If no rule applied to this SEGMENT directly, the parent rights
+ * will apply to at least the SEGMENT node itself and possibly
+ * other parts deeper in it's subtree. */
+ if (!has_local_rule(&state->rights))
+ {
+ state->rights.access = state->parent_rights.access;
+ state->rights.min_rights &= state->parent_rights.access.rights;
+ state->rights.max_rights |= state->parent_rights.access.rights;
+ }
+
+ /* The list of nodes for SEGMENT is now complete. If we need to
+ * continue, make it the current and put the old one into the recycler.
+ *
+ * If this is the end of the path, keep the parent path and rights in
+ * STATE as are such that sibling lookups will benefit from it.
+ */
+ if (path)
+ {
+ temp = state->current;
+ state->current = state->next;
+ state->next = temp;
+
+ /* In STATE, PARENT_PATH, PARENT_RIGHTS and CURRENT are now in sync. */
+ state->parent_rights = state->rights;
+ }
}
- if (b->err)
- return FALSE;
+ /* If we check recursively, none of the (potential) sub-paths must have
+ * less than the REQUIRED access rights. "Potential" because we don't
+ * verify that the respective paths actually exist in the repository.
+ */
+ if (recursive)
+ return (state->rights.min_rights & required) == required;
- return TRUE;
+ /* Return whether the access rights on PATH fully include REQUIRED. */
+ return (state->rights.access.rights & required) == required;
}
+
+
+/*** The authz data structure. ***/
-svn_error_t *
-svn_repos__authz_validate(svn_authz_t *authz, apr_pool_t *pool)
+/* An entry in svn_authz_t's USER_RULES cache. All members must be
+ * allocated in the POOL and the latter has to be cleared / destroyed
+ * before overwriting the entries' contents.
+ */
+struct authz_user_rules_t
{
- struct authz_validate_baton baton = { 0 };
+ /* User name for which we filtered the rules.
+ * User NULL for the anonymous user. */
+ const char *user;
- baton.err = SVN_NO_ERROR;
- baton.config = authz->cfg;
+ /* Repository name for which we filtered the rules.
+ * May be empty but never NULL for used entries. */
+ const char *repository;
- /* Step through the entire rule file stopping on error. */
- svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
- &baton, pool);
- SVN_ERR(baton.err);
+ /* The combined min/max rights USER has on REPOSITORY. */
+ authz_rights_t global_rights;
- return SVN_NO_ERROR;
-}
+ /* Root of the filtered path rule tree.
+ * Will remain NULL until the first usage. */
+ node_t *root;
+ /* Reusable lookup state instance. */
+ lookup_state_t *lookup_state;
-/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config
- * file placing the result into CFG_P allocated in POOL.
- *
- * If DIRENT cannot be parsed as a config file then an error is returned. The
- * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing
- * authz file is also an error. The CASE_SENSITIVE controls the lookup
- * behavior for section and option names alike.
- *
- * SCRATCH_POOL will be used for temporary allocations. */
-static svn_error_t *
-authz_retrieve_config_repo(svn_config_t **cfg_p,
- const char *dirent,
- svn_boolean_t must_exist,
- svn_boolean_t case_sensitive,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+ /* Pool from which all data within this struct got allocated.
+ * Can be destroyed or cleaned up with no further side-effects. */
+ apr_pool_t *pool;
+};
+
+/* Return TRUE, iff AUTHZ matches the pair of REPOS_NAME and USER.
+ * Note that USER may be NULL.
+ */
+static svn_boolean_t
+matches_filtered_tree(const authz_user_rules_t *authz,
+ const char *repos_name,
+ const char *user)
{
- svn_error_t *err;
- svn_repos_t *repos;
- const char *repos_root_dirent;
- const char *fs_path;
- svn_fs_t *fs;
- svn_fs_root_t *root;
- svn_revnum_t youngest_rev;
- svn_node_kind_t node_kind;
- svn_stream_t *contents;
-
- /* Search for a repository in the full path. */
- repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
- if (!repos_root_dirent)
- return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL,
- "Unable to find repository at '%s'", dirent);
-
- /* Attempt to open a repository at repos_root_dirent. */
- SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL, scratch_pool,
- scratch_pool));
-
- fs_path = &dirent[strlen(repos_root_dirent)];
-
- /* Root path is always a directory so no reason to go any further */
- if (*fs_path == '\0')
- return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
- "'/' is not a file in repo '%s'",
- repos_root_dirent);
-
- /* We skip some things that are non-important for how we're going to use
- * this repo connection. We do not set any capabilities since none of
- * the current ones are important for what we're doing. We also do not
- * setup the environment that repos hooks would run under since we won't
- * be triggering any. */
-
- /* Get the filesystem. */
- fs = svn_repos_fs(repos);
-
- /* Find HEAD and the revision root */
- SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
- SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
-
- SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
- if (node_kind == svn_node_none)
+ /* Does the user match? */
+ if (user)
{
- if (!must_exist)
- {
- SVN_ERR(svn_config_create2(cfg_p, case_sensitive, case_sensitive,
- result_pool));
- return SVN_NO_ERROR;
- }
- else
- {
- return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
- "'%s' path not found in repo '%s'", fs_path,
- repos_root_dirent);
- }
+ if (authz->user == NULL || strcmp(user, authz->user))
+ return FALSE;
}
- else if (node_kind != svn_node_file)
- {
- return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
- "'%s' is not a file in repo '%s'", fs_path,
- repos_root_dirent);
- }
-
- SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
- err = svn_config_parse(cfg_p, contents, case_sensitive, case_sensitive,
- result_pool);
-
- /* Add the URL to the error stack since the parser doesn't have it. */
- if (err != SVN_NO_ERROR)
- return svn_error_createf(err->apr_err, err,
- "Error while parsing config file: '%s' in repo '%s':",
- fs_path, repos_root_dirent);
+ else if (authz->user != NULL)
+ return FALSE;
- return SVN_NO_ERROR;
+ /* Does the repository match as well? */
+ return strcmp(repos_name, authz->repository) == 0;
}
-svn_error_t *
-svn_repos__retrieve_config(svn_config_t **cfg_p,
- const char *path,
- svn_boolean_t must_exist,
- svn_boolean_t case_sensitive,
- apr_pool_t *pool)
+/* Check if AUTHZ's already contains a path rule tree filtered for this
+ * USER, REPOS_NAME combination. If that does not exist, yet, create one
+ * but don't construct the actual filtered tree, yet.
+ */
+static authz_user_rules_t *
+get_user_rules(svn_authz_t *authz,
+ const char *repos_name,
+ const char *user)
{
- if (svn_path_is_url(path))
+ apr_pool_t *pool;
+
+ /* Search our cache for a suitable previously filtered tree. */
+ if (authz->filtered)
{
- const char *dirent;
- svn_error_t *err;
- apr_pool_t *scratch_pool = svn_pool_create(pool);
+ /* Is this a suitable filtered tree? */
+ if (matches_filtered_tree(authz->filtered, repos_name, user))
+ return authz->filtered;
- err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool);
+ /* Drop the old filtered tree before creating a new one. */
+ svn_pool_destroy(authz->filtered->pool);
+ authz->filtered = NULL;
+ }
- if (err == SVN_NO_ERROR)
- err = authz_retrieve_config_repo(cfg_p, dirent, must_exist,
- case_sensitive, pool, scratch_pool);
+ /* Global cache lookup. Filter the full model only if necessary. */
+ pool = svn_pool_create(authz->pool);
- /* Close the repos and streams we opened. */
- svn_pool_destroy(scratch_pool);
+ /* Write a new entry. */
+ authz->filtered = apr_palloc(pool, sizeof(*authz->filtered));
+ authz->filtered->pool = pool;
+ authz->filtered->repository = apr_pstrdup(pool, repos_name);
+ authz->filtered->user = user ? apr_pstrdup(pool, user) : NULL;
+ authz->filtered->lookup_state = create_lookup_state(pool);
+ authz->filtered->root = NULL;
- return err;
- }
- else
- {
- /* Outside of repo file or Windows registry*/
- SVN_ERR(svn_config_read3(cfg_p, path, must_exist, case_sensitive,
- case_sensitive, pool));
- }
+ svn_authz__get_global_rights(&authz->filtered->global_rights,
+ authz->full, user, repos_name);
- return SVN_NO_ERROR;
+ return authz->filtered;
}
-
-/* Callback to copy (name, value) group into the "groups" section
- of another configuration. */
-static svn_boolean_t
-authz_copy_group(const char *name, const char *value,
- void *baton, apr_pool_t *pool)
+/* In AUTHZ's user rules, construct the actual filtered tree.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+filter_tree(svn_authz_t *authz,
+ apr_pool_t *scratch_pool)
{
- svn_config_t *authz_cfg = baton;
+ apr_pool_t *pool = authz->filtered->pool;
+ const char *repos_name = authz->filtered->repository;
+ const char *user = authz->filtered->user;
+ node_t *root;
- svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
+ if (filtered_pool)
+ {
+ svn_membuf_t *key = construct_filtered_key(repos_name, user,
+ authz->authz_id,
+ scratch_pool);
- return TRUE;
-}
+ /* Cache lookup. */
+ SVN_ERR(svn_object_pool__lookup((void **)&root, filtered_pool, key,
+ pool));
-/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
- * If AUTHZ already contains any group definition, report an error.
- * Use POOL for temporary allocations. */
-static svn_error_t *
-authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
- apr_pool_t *pool)
-{
- /* Easy out: we prohibit local groups in the authz file when global
- groups are being used. */
- if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
+ if (!root)
+ {
+ apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
+ authz_full_t *add_ref = NULL;
+
+ /* Make sure the underlying full authz object lives as long as the
+ * filtered one that we are about to create. We do this by adding
+ * a reference to it in ITEM_POOL (which may live longer than AUTHZ).
+ *
+ * Note that we already have a reference to that full authz in
+ * AUTHZ->FULL. Assert that we actually don't created multiple
+ * instances of the same full model.
+ */
+ svn_error_clear(svn_object_pool__lookup((void **)&add_ref,
+ authz_pool, authz->authz_id,
+ item_pool));
+ SVN_ERR_ASSERT(add_ref == authz->full);
+
+ /* Now construct the new filtered tree and cache it. */
+ root = create_user_authz(authz->full, repos_name, user, item_pool,
+ scratch_pool);
+ svn_error_clear(svn_object_pool__insert((void **)&root,
+ filtered_pool, key, root,
+ item_pool, pool));
+ }
+ }
+ else
{
- return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "Authz file cannot contain any groups "
- "when global groups are being used.");
+ root = create_user_authz(authz->full, repos_name, user, pool,
+ scratch_pool);
}
- svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
- authz_copy_group, authz->cfg, pool);
+ /* Write a new entry. */
+ authz->filtered->root = root;
return SVN_NO_ERROR;
}
-svn_error_t *
-svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
- const char *groups_path, svn_boolean_t must_exist,
- svn_boolean_t accept_urls, apr_pool_t *pool)
+
+
+/* Read authz configuration data from PATH into *AUTHZ_P, allocated in
+ RESULT_POOL. Return the cache key in *AUTHZ_ID. If GROUPS_PATH is set,
+ use the global groups parsed from it. Use SCRATCH_POOL for temporary
+ allocations.
+
+ PATH and GROUPS_PATH may be a dirent or an absolute file url. REPOS_HINT
+ may be specified to speed up access to in-repo authz files.
+
+ If PATH or GROUPS_PATH is not a valid authz rule file, then return
+ SVN_AUTHZ_INVALID_CONFIG. The contents of *AUTHZ_P is then
+ undefined. If MUST_EXIST is TRUE, a missing authz or global groups file
+ is also an error. */
+static svn_error_t *
+authz_read(authz_full_t **authz_p,
+ svn_membuf_t **authz_id,
+ const char *path,
+ const char *groups_path,
+ svn_boolean_t must_exist,
+ svn_repos_t *repos_hint,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+ svn_error_t* err = NULL;
+ svn_stream_t *rules_stream = NULL;
+ svn_stream_t *groups_stream = NULL;
+ svn_checksum_t *rules_checksum = NULL;
+ svn_checksum_t *groups_checksum = NULL;
- /* Load the authz file */
- if (accept_urls)
- SVN_ERR(svn_repos__retrieve_config(&authz->cfg, path, must_exist, TRUE,
- pool));
- else
- SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE,
- pool));
+ config_access_t *config_access =
+ svn_repos__create_config_access(repos_hint, scratch_pool);
+ /* Open the main authz file */
+ SVN_ERR(svn_repos__get_config(&rules_stream, &rules_checksum, config_access,
+ path, must_exist, scratch_pool));
+
+ /* Open the optional groups file */
if (groups_path)
+ SVN_ERR(svn_repos__get_config(&groups_stream, &groups_checksum,
+ config_access, groups_path, must_exist,
+ scratch_pool));
+
+ /* The authz cache is optional. */
+ *authz_id = construct_authz_key(rules_checksum, groups_checksum,
+ result_pool);
+ if (authz_pool)
{
- svn_config_t *groups_cfg;
- svn_error_t *err;
+ /* Cache lookup. */
+ SVN_ERR(svn_object_pool__lookup((void **)authz_p, authz_pool,
+ *authz_id, result_pool));
- /* Load the groups file */
- if (accept_urls)
- SVN_ERR(svn_repos__retrieve_config(&groups_cfg, groups_path,
- must_exist, TRUE, pool));
- else
- SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
- TRUE, TRUE, pool));
-
- /* Copy the groups from groups_cfg into authz. */
- err = authz_copy_groups(authz, groups_cfg, pool);
-
- /* Add the paths to the error stack since the authz_copy_groups
- routine knows nothing about them. */
- if (err != SVN_NO_ERROR)
- return svn_error_createf(err->apr_err, err,
- "Error reading authz file '%s' with "
- "groups file '%s':", path, groups_path);
+ /* If not found, parse and add to cache. */
+ if (!*authz_p)
+ {
+ apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
+
+ /* Parse the configuration(s) and construct the full authz model
+ * from it. */
+ err = svn_authz__parse(authz_p, rules_stream, groups_stream,
+ item_pool, scratch_pool);
+ if (err != SVN_NO_ERROR)
+ {
+ /* That pool would otherwise never get destroyed. */
+ svn_pool_destroy(item_pool);
+
+ /* Add the URL / file name to the error stack since the parser
+ * doesn't have it. */
+ err = svn_error_quick_wrapf(err,
+ "Error while parsing config file: '%s':",
+ path);
+ }
+ else
+ {
+ SVN_ERR(svn_object_pool__insert((void **)authz_p, authz_pool,
+ *authz_id, *authz_p,
+ item_pool, result_pool));
+ }
+ }
+ }
+ else
+ {
+ /* Parse the configuration(s) and construct the full authz model from
+ * it. */
+ err = svn_error_quick_wrapf(svn_authz__parse(authz_p, rules_stream,
+ groups_stream,
+ result_pool, scratch_pool),
+ "Error while parsing authz file: '%s':",
+ path);
}
- /* Make sure there are no errors in the configuration. */
- SVN_ERR(svn_repos__authz_validate(authz, pool));
+ svn_repos__destroy_config_access(config_access);
- *authz_p = authz;
- return SVN_NO_ERROR;
+ return err;
}
@@ -981,12 +1628,22 @@ svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
/*** Public functions. ***/
svn_error_t *
-svn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
- const char *groups_path, svn_boolean_t must_exist,
- apr_pool_t *pool)
+svn_repos_authz_read3(svn_authz_t **authz_p,
+ const char *path,
+ const char *groups_path,
+ svn_boolean_t must_exist,
+ svn_repos_t *repos_hint,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
- TRUE, pool);
+ svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz));
+ authz->pool = result_pool;
+
+ SVN_ERR(authz_read(&authz->full, &authz->authz_id, path, groups_path,
+ must_exist, repos_hint, result_pool, scratch_pool));
+
+ *authz_p = authz;
+ return SVN_NO_ERROR;
}
@@ -994,29 +1651,20 @@ svn_error_t *
svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
svn_stream_t *groups_stream, apr_pool_t *pool)
{
- svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
-
- /* Parse the authz stream */
- SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool));
-
- if (groups_stream)
- {
- svn_config_t *groups_cfg;
-
- /* Parse the groups stream */
- SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool));
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ svn_authz_t *authz = apr_pcalloc(pool, sizeof(*authz));
+ authz->pool = pool;
- SVN_ERR(authz_copy_groups(authz, groups_cfg, pool));
- }
+ /* Parse the configuration and construct the full authz model from it. */
+ SVN_ERR(svn_authz__parse(&authz->full, stream, groups_stream, pool,
+ scratch_pool));
- /* Make sure there are no errors in the configuration. */
- SVN_ERR(svn_repos__authz_validate(authz, pool));
+ svn_pool_destroy(scratch_pool);
*authz_p = authz;
return SVN_NO_ERROR;
}
-
svn_error_t *
svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
const char *path, const char *user,
@@ -1024,51 +1672,58 @@ svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
svn_boolean_t *access_granted,
apr_pool_t *pool)
{
- const char *current_path;
+ const authz_access_t required =
+ ((required_access & svn_authz_read ? authz_access_read_flag : 0)
+ | (required_access & svn_authz_write ? authz_access_write_flag : 0));
+
+ /* Pick or create the suitable pre-filtered path rule tree. */
+ authz_user_rules_t *rules = get_user_rules(
+ authz,
+ (repos_name ? repos_name : AUTHZ_ANY_REPOSITORY),
+ user);
+
+ /* In many scenarios, users have uniform access to a repository
+ * (blanket access or no access at all).
+ *
+ * In these cases, don't bother creating or consulting the filtered tree.
+ */
+ if ((rules->global_rights.min_access & required) == required)
+ {
+ *access_granted = TRUE;
+ return SVN_NO_ERROR;
+ }
- if (!repos_name)
- repos_name = "";
+ if ((rules->global_rights.max_access & required) != required)
+ {
+ *access_granted = FALSE;
+ return SVN_NO_ERROR;
+ }
- /* If PATH is NULL, check if the user has *any* access. */
+ /* No specific path given, i.e. looking for anywhere in the tree? */
if (!path)
{
- *access_granted = authz_get_any_access(authz->cfg, repos_name,
- user, required_access, pool);
+ *access_granted =
+ ((rules->global_rights.max_access & required) == required);
return SVN_NO_ERROR;
}
- /* Sanity check. */
- SVN_ERR_ASSERT(path[0] == '/');
+ /* Rules tree lookup */
- /* Determine the granted access for the requested path. */
- path = svn_fspath__canonicalize(path, pool);
- current_path = path;
+ /* Did we already filter the data model? */
+ if (!rules->root)
+ SVN_ERR(filter_tree(authz, pool));
- while (!authz_get_path_access(authz->cfg, repos_name,
- current_path, user,
- required_access,
- access_granted,
- pool))
- {
- /* Stop if the loop hits the repository root with no
- results. */
- if (current_path[0] == '/' && current_path[1] == '\0')
- {
- /* Deny access by default. */
- *access_granted = FALSE;
- return SVN_NO_ERROR;
- }
+ /* Re-use previous lookup results, if possible. */
+ path = init_lockup_state(authz->filtered->lookup_state,
+ authz->filtered->root, path);
- /* Work back to the parent path. */
- current_path = svn_fspath__dirname(current_path, pool);
- }
+ /* Sanity check. */
+ SVN_ERR_ASSERT(path[0] == '/');
- /* If the caller requested recursive access, we need to walk through
- the entire authz config to see whether any child paths are denied
- to the requested user. */
- if (*access_granted && (required_access & svn_authz_recursive))
- *access_granted = authz_get_tree_access(authz->cfg, repos_name, path,
- user, required_access, pool);
+ /* Determine the granted access for the requested path.
+ * PATH does not need to be normalized for lockup(). */
+ *access_granted = lookup(rules->lookup_state, path, required,
+ !!(required_access & svn_authz_recursive), pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_repos/authz.h b/subversion/libsvn_repos/authz.h
new file mode 100644
index 000000000000..7187335a94fd
--- /dev/null
+++ b/subversion/libsvn_repos/authz.h
@@ -0,0 +1,364 @@
+/* authz.h : authz parsing and searching, private to libsvn_repos
+ *
+ * ====================================================================
+ * 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_REPOS_AUTHZ_H
+#define SVN_REPOS_AUTHZ_H
+
+#include <apr_hash.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_repos.h"
+
+#include "private/svn_string_private.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*
+ * Authz and global group file parsing
+ */
+
+/* A dictionary of rules that are specific to a particular
+ (user, repository) combination. */
+typedef struct authz_user_rules_t authz_user_rules_t;
+
+
+/* Access rights in an ACL.
+ *
+ * This enum is different from and incompatible with
+ * svn_repos_authz_access_t, because it has different semantics and
+ * encodes rights that are not and should never be exposed in the
+ * public API.
+ */
+typedef enum authz_access_t
+{
+ /*
+ * Individual access flags
+ */
+
+ /* TODO: Future extension for lookup/traverse access.
+ authz_access_lookup_flag = 0x10, */
+
+ /* Read access allows listing directory entries, reading file
+ contents and reading properties of files and directories. */
+ authz_access_read_flag = 0x20,
+
+ /* Write access allows adding, removing and renaming directory
+ entries, modifying file contents and adding, removing and
+ modifying properties of files and directories. */
+ authz_access_write_flag = 0x40,
+
+ /*
+ * Combined access flags
+ */
+
+ /* No access. */
+ authz_access_none = 0,
+
+
+ /* TODO: Lookup access is a synonym for the lookup flag.
+ authz_access_lookup = authz_access_lookup_flag, */
+
+ /* Read access (TODO: implies lookup access). */
+ authz_access_read = authz_access_read_flag /* TODO: | authz_access_lookup */,
+
+ /* Write access implies read (TODO: and lookup) access. */
+ authz_access_write = authz_access_write_flag | authz_access_read
+} authz_access_t;
+
+
+/* Accumulated rights for (user, repository). */
+typedef struct authz_rights_t
+{
+ /* The lowest level of access that the user has to every
+ path in the repository. */
+ authz_access_t min_access;
+
+ /* The highest level of access that the user has to
+ any path in the repository. */
+ authz_access_t max_access;
+} authz_rights_t;
+
+
+/* Accumulated global rights for a specific user. */
+typedef struct authz_global_rights_t
+{
+ /* The user name. */
+ const char *user;
+
+ /* Accumulated rights for this user from rules that are not
+ repository-specific. We use this to avoid a hash lookup for the
+ "any" repository rights. */
+ authz_rights_t any_repos_rights;
+
+ /* Accumulated rights for this user across all repositories. */
+ authz_rights_t all_repos_rights;
+
+ /* Accumulated rights for specific repositories.
+ The key is repository name, the value is an authz_rights_t*. */
+ apr_hash_t *per_repos_rights;
+} authz_global_rights_t;
+
+
+/* Immutable authorization info */
+typedef struct authz_full_t
+{
+ /* All ACLs from the authz file, in the order of definition. */
+ apr_array_header_t *acls;
+
+ /* Globally accumulated rights for anonymous access. */
+ svn_boolean_t has_anon_rights;
+ authz_global_rights_t anon_rights;
+
+ /* Globally accumulated rights for authenticated users. */
+ svn_boolean_t has_authn_rights;
+ authz_global_rights_t authn_rights;
+
+ /* Globally accumulated rights, for all concrete users mentioned
+ in the authz file. The key is the user name, the value is
+ an authz_global_rights_t*. */
+ apr_hash_t *user_rights;
+
+ /* The pool from which all the parsed authz data is allocated.
+ This is the RESULT_POOL passed to svn_authz__tng_parse.
+
+ It's a good idea to dedicate a pool for the authz structure, so
+ that the whole authz representation can be deallocated by
+ destroying the pool. */
+ apr_pool_t *pool;
+} authz_full_t;
+
+
+/* Dynamic authorization info */
+struct svn_authz_t
+{
+ /* The parsed and pre-processed contents of the authz file. */
+ authz_full_t *full;
+
+ /* Identifies the authz model content
+ * (a hash value that can be used for e.g. cache lookups). */
+ svn_membuf_t *authz_id;
+
+ /* Rules filtered for a particular user-repository combination.
+ * May be NULL. */
+ authz_user_rules_t *filtered;
+
+ /* The pool from which all the parsed authz data is allocated.
+ This is the RESULT_POOL passed to svn_authz__tng_parse.
+
+ It's a good idea to dedicate a pool for the authz structure, so
+ that the whole authz representation can be deallocated by
+ destroying the pool. */
+ apr_pool_t *pool;
+};
+
+
+/* Rule path segment descriptor. */
+typedef struct authz_rule_segment_t
+{
+ /* The segment type. */
+ enum {
+ /* A literal string match.
+ The path segment must exactly match the pattern.
+
+ Note: Make sure this is always the first constant in the
+ enumeration, otherwise rules that match the repository
+ root will not sort first in the ACL list and the implicit
+ default no-access ACE will not be applied correctly. */
+ authz_rule_literal,
+
+ /* A prefix match: a literal string followed by '*'.
+ The path segment must begin with the literal prefix. */
+ authz_rule_prefix,
+
+ /* A suffix match: '*' followed by a literal string.
+ The path segment must end with the literal suffix.
+ The pattern is stored reversed, so that the matching code can
+ perform a prefix match on the reversed path segment. */
+ authz_rule_suffix,
+
+ /* '*'
+ Matches any single non-empty path segment.
+ The pattern will be an empty string. */
+ authz_rule_any_segment,
+
+ /* '**'
+ Matches any sequence of zero or more path segments.
+ The pattern will be an empty string. */
+ authz_rule_any_recursive,
+
+ /* Any other glob/fnmatch pattern. */
+ authz_rule_fnmatch
+ } kind;
+
+ /* The pattern for this path segment.
+ Any no-op fnmatch escape sequences (i.e., those that do not
+ escape a wildcard or character class) are stripped from the
+ string.
+
+ The pattern string will be interned; therefore, two identical
+ rule patterns will always contain the same pointer value and
+ equality can therefore be tested by comparing the pointer
+ values and segment kinds. */
+ svn_string_t pattern;
+} authz_rule_segment_t;
+
+/* Rule path descriptor. */
+typedef struct authz_rule_t
+{
+ /* The repository that this rule applies to. This will be the empty
+ string string if a the rule did not name a repository. The
+ repository name is interned. */
+ const char *repos;
+
+ /* The number of segments in the rule path. */
+ int len;
+
+ /* The array of path segments for this rule. Will be NULL for the
+ repository root. */
+ authz_rule_segment_t *path;
+} authz_rule_t;
+
+
+/* An access control list defined by access rules. */
+typedef struct authz_acl_t
+{
+ /* The sequence number of the ACL stores the order in which access
+ rules were defined in the authz file. The authz lookup code
+ selects the highest-numbered ACL from amongst a set of equivalent
+ matches. */
+ int sequence_number;
+
+ /* The parsed rule. */
+ authz_rule_t rule;
+
+ /* Access rights for anonymous users */
+ svn_boolean_t has_anon_access;
+ authz_access_t anon_access;
+
+ /* Access rights for authenticated users */
+ svn_boolean_t has_authn_access;
+ authz_access_t authn_access;
+
+ /* All other user- or group-specific access rights.
+ Aliases are replaced with their definitions, rules for the same
+ user or group are merged. */
+ apr_array_header_t *user_access;
+} authz_acl_t;
+
+
+/* An access control entry in authz_acl_t::user_access. */
+typedef struct authz_ace_t
+{
+ /* The name of the alias, user or group that this ACE applies to. */
+ const char *name;
+
+ /* The set of group members, when NAME is the name of a group.
+ We store this reference in the ACE to save a hash lookup when
+ resolving access for group ACEs.
+ */
+ apr_hash_t *members;
+
+ /* True if this is an inverse-match rule. */
+ svn_boolean_t inverted;
+
+ /* The access rights defined by this ACE. */
+ authz_access_t access;
+} authz_ace_t;
+
+
+/* Parse authz definitions from RULES and optional global group
+ * definitions from GROUPS, returning an immutable, in-memory
+ * representation of all the rules, groups and aliases.
+ *
+ * **AUTHZ and its contents will be allocated from RESULT_POOL.
+ * The function uses SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_authz__parse(authz_full_t **authz,
+ svn_stream_t *rules,
+ svn_stream_t *groups,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Reverse a STRING of length LEN in place. */
+void
+svn_authz__reverse_string(char *string, apr_size_t len);
+
+
+/* Compare two rules in lexical order by path only. */
+int
+svn_authz__compare_paths(const authz_rule_t *a, const authz_rule_t *b);
+
+/* Compare two rules in path lexical order, then repository lexical order. */
+int
+svn_authz__compare_rules(const authz_rule_t *a, const authz_rule_t *b);
+
+
+/*
+ * Authorization lookup
+ */
+
+/* The "anonymous" user for authz queries. */
+#define AUTHZ_ANONYMOUS_USER ((const char*)"")
+
+/* Rules with this repository name apply to all repositories. */
+#define AUTHZ_ANY_REPOSITORY ((const char*)"")
+
+/* Check if the ACL applies to the REPOS pair. */
+svn_boolean_t
+svn_authz__acl_applies_to_repo(const authz_acl_t *acl,
+ const char *repos);
+
+/* Check if the ACL applies to the (USER, REPOS) pair. If it does,
+ * and ACCESS is not NULL, set *ACCESS to the actual access rights for
+ * the user in this repository.
+ */
+svn_boolean_t
+svn_authz__get_acl_access(authz_access_t *access,
+ const authz_acl_t *acl,
+ const char *user, const char *repos);
+
+
+/* Set *RIGHTS to the accumulated global access rights calculated in
+ * AUTHZ for (USER, REPOS).
+ * Return TRUE if the rights are explicit (i.e., an ACL for REPOS
+ * applies to USER, or REPOS is AUTHZ_ANY_REPOSITORY).
+ */
+svn_boolean_t
+svn_authz__get_global_rights(authz_rights_t *rights,
+ const authz_full_t *authz,
+ const char *user, const char *repos);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_REPOS_AUTHZ_H */
diff --git a/subversion/libsvn_repos/authz_info.c b/subversion/libsvn_repos/authz_info.c
new file mode 100644
index 000000000000..8f3a8b63b12a
--- /dev/null
+++ b/subversion/libsvn_repos/authz_info.c
@@ -0,0 +1,184 @@
+/* authz_info.c : Information derived from authz settings.
+ *
+ * ====================================================================
+ * 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 <apr_hash.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_hash.h"
+
+#include "svn_private_config.h"
+
+#include "authz.h"
+
+
+svn_boolean_t
+svn_authz__acl_applies_to_repo(const authz_acl_t *acl,
+ const char *repos)
+{
+ /* The repository name must match the one in the rule, iff the rule
+ was defined for a specific repository. */
+ return (0 == strcmp(acl->rule.repos, AUTHZ_ANY_REPOSITORY))
+ || (0 == strcmp(repos, acl->rule.repos));
+}
+
+svn_boolean_t
+svn_authz__get_acl_access(authz_access_t *access_p,
+ const authz_acl_t *acl,
+ const char *user, const char *repos)
+{
+ authz_access_t access;
+ svn_boolean_t has_access;
+ int i;
+
+ /* The repository name must match the one in the rule, iff the rule
+ was defined for a specific repository. */
+ if (!svn_authz__acl_applies_to_repo(acl, repos))
+ return FALSE;
+
+ /* Check anonymous access first. */
+ if (!user || 0 == strcmp(user, AUTHZ_ANONYMOUS_USER))
+ {
+ if (!acl->has_anon_access)
+ return FALSE;
+
+ if (access_p)
+ *access_p = acl->anon_access;
+ return TRUE;
+ }
+
+ /* Get the access rights for all authenticated users. */
+ has_access = acl->has_authn_access;
+ access = (has_access ? acl->authn_access : authz_access_none);
+
+ /* Scan the ACEs in the ACL and merge the access rights. */
+ for (i = 0; i < acl->user_access->nelts; ++i)
+ {
+ const authz_ace_t *const ace =
+ &APR_ARRAY_IDX(acl->user_access, i, authz_ace_t);
+ const svn_boolean_t match =
+ ((ace->members && svn_hash_gets(ace->members, user))
+ || (!ace->members && 0 == strcmp(user, ace->name)));
+
+ if (!match != !ace->inverted) /* match XNOR ace->inverted */
+ {
+ access |= ace->access;
+ has_access = TRUE;
+ }
+ }
+
+ if (access_p)
+ *access_p = access;
+ return has_access;
+}
+
+/* Set *RIGHTS_P to the combination of LHS and RHS, i.e. intersect the
+ * minimal rights and join the maximum rights.
+ */
+static void
+combine_rights(authz_rights_t *rights_p,
+ const authz_rights_t *lhs,
+ const authz_rights_t *rhs)
+{
+ rights_p->min_access = lhs->min_access & rhs->min_access;
+ rights_p->max_access = lhs->max_access | rhs->max_access;
+}
+
+
+/* Given GLOBAL_RIGHTS and a repository name REPOS, set *RIGHTS_P to
+ * to the actual accumulated rights defined for that repository.
+ * Return TRUE if these rights were defined explicitly.
+ */
+static svn_boolean_t
+resolve_global_rights(authz_rights_t *rights_p,
+ const authz_global_rights_t *global_rights,
+ const char *repos)
+{
+ if (0 == strcmp(repos, AUTHZ_ANY_REPOSITORY))
+ {
+ /* Return the accumulated rights that are not repository-specific. */
+ *rights_p = global_rights->any_repos_rights;
+ return TRUE;
+ }
+ else
+ {
+ /* Check if we have explicit rights for this repository. */
+ const authz_rights_t *const rights =
+ svn_hash_gets(global_rights->per_repos_rights, repos);
+
+ if (rights)
+ {
+ combine_rights(rights_p, rights, &global_rights->any_repos_rights);
+ return TRUE;
+ }
+ }
+
+ /* Fall-through: return the rights defined for "any" repository
+ because this user has no specific rules for this specific REPOS. */
+ *rights_p = global_rights->any_repos_rights;
+ return FALSE;
+}
+
+
+svn_boolean_t
+svn_authz__get_global_rights(authz_rights_t *rights_p,
+ const authz_full_t *authz,
+ const char *user, const char *repos)
+{
+ if (!user || 0 == strcmp(user, AUTHZ_ANONYMOUS_USER))
+ {
+ /* Check if we have explicit rights for anonymous access. */
+ if (authz->has_anon_rights)
+ return resolve_global_rights(rights_p, &authz->anon_rights, repos);
+ }
+ else
+ {
+ /* Check if we have explicit rights for this user. */
+ const authz_global_rights_t *const user_rights =
+ svn_hash_gets(authz->user_rights, user);
+
+ if (user_rights)
+ {
+ svn_boolean_t explicit
+ = resolve_global_rights(rights_p, user_rights, repos);
+
+ /* Rights given to _any_ authenticated user may apply, too. */
+ if (authz->has_authn_rights)
+ {
+ authz_rights_t authn;
+ explicit |= resolve_global_rights(&authn, &authz->authn_rights,
+ repos);
+ combine_rights(rights_p, rights_p, &authn);
+ }
+ return explicit;
+ }
+
+ /* Check if we have explicit rights for authenticated access. */
+ if (authz->has_authn_rights)
+ return resolve_global_rights(rights_p, &authz->authn_rights, repos);
+ }
+
+ /* Fall-through: return the implicit rights, i.e., none. */
+ rights_p->min_access = authz_access_none;
+ rights_p->max_access = authz_access_none;
+ return FALSE;
+}
diff --git a/subversion/libsvn_repos/authz_parse.c b/subversion/libsvn_repos/authz_parse.c
new file mode 100644
index 000000000000..23612dedd490
--- /dev/null
+++ b/subversion/libsvn_repos/authz_parse.c
@@ -0,0 +1,1442 @@
+/* authz_parse.c : Parser for path-based access control
+ *
+ * ====================================================================
+ * 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 <apr_fnmatch.h>
+#include <apr_tables.h>
+
+#include "svn_ctype.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_iter.h"
+#include "svn_pools.h"
+#include "svn_repos.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_config_private.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+#include "svn_private_config.h"
+
+#include "authz.h"
+
+
+/* Temporary ACL constructed by the parser. */
+typedef struct parsed_acl_t
+{
+ /* The global ACL.
+ The strings in ACL.rule are allocated from the result pool.
+ ACL.user_access is null during the parsing stage. */
+ authz_acl_t acl;
+
+ /* The set of access control entries. In the second pass, aliases in
+ these entries will be expanded and equivalent entries will be
+ merged. The entries are allocated from the parser pool. */
+ apr_hash_t *aces;
+
+ /* The set of access control entries that use aliases. In the second
+ pass, aliases in these entries will be expanded and merged into ACES.
+ The entries are allocated from the parser pool. */
+ apr_hash_t *alias_aces;
+} parsed_acl_t;
+
+
+/* Temporary group definition constructed by the authz/group parser.
+ Once all groups and aliases are defined, a second pass over these
+ data will recursively expand group memberships. */
+typedef struct parsed_group_t
+{
+ svn_boolean_t local_group;
+ apr_array_header_t *members;
+} parsed_group_t;
+
+
+/* Baton for the parser constructor. */
+typedef struct ctor_baton_t
+{
+ /* The final output of the parser. */
+ authz_full_t *authz;
+
+ /* Interned-string set, allocated in AUTHZ->pool.
+ Stores singleton instances of user, group and repository names,
+ which are used by members of the AUTHZ structure. By reusing the
+ same immutable string multiple times, we reduce the size of the
+ authz representation in the result pool.
+
+ N.B.: Whilst the strings are allocated from teh result pool, the
+ hash table itself is not. */
+ apr_hash_t *strings;
+
+ /* A set of all the sections that were seen in the authz or global
+ groups file. Rules, aliases and groups may each only be defined
+ once in the authz file. The global groups file may only contain a
+ [groups] section. */
+ apr_hash_t *sections;
+
+ /* The name of the section we're currently parsing. */
+ const char *section;
+
+ /* TRUE iff we're parsing the global groups file. */
+ svn_boolean_t parsing_groups;
+
+ /* TRUE iff we're parsing a [groups] section. */
+ svn_boolean_t in_groups;
+
+ /* TRUE iff we're parsing an [aliases] section. */
+ svn_boolean_t in_aliases;
+
+ /* A set of all the unique rules we parsed from the section names. */
+ apr_hash_t *parsed_rules;
+
+ /* Temporary parsed-groups definitions. */
+ apr_hash_t *parsed_groups;
+
+ /* Temporary alias mappings. */
+ apr_hash_t *parsed_aliases;
+
+ /* Temporary parsed-acl definitions. */
+ apr_array_header_t *parsed_acls;
+
+ /* Temporary expanded groups definitions. */
+ apr_hash_t *expanded_groups;
+
+ /* The temporary ACL we're currently constructing. */
+ parsed_acl_t *current_acl;
+
+ /* Temporary buffers used to parse a rule into segments. */
+ svn_membuf_t rule_path_buffer;
+ svn_stringbuf_t *rule_string_buffer;
+
+ /* The parser's scratch pool. This may not be the same pool as
+ passed to the constructor callbacks, that is supposed to be an
+ iteration pool maintained by the generic parser.
+
+ N.B.: The result pool is AUTHZ->pool. */
+ apr_pool_t *parser_pool;
+} ctor_baton_t;
+
+
+/* An empty string with a known address. */
+static const char interned_empty_string[] = "";
+
+/* The name of the aliases section. */
+static const char aliases_section[] = "aliases";
+
+/* The name of the groups section. */
+static const char groups_section[] = "groups";
+
+/* The token indicating that an authz rule contains wildcards. */
+static const char glob_rule_token[] = "glob";
+
+/* The anonymous access token. */
+static const char anon_access_token[] = "$anonymous";
+
+/* The authenticated access token. */
+static const char authn_access_token[] = "$authenticated";
+
+
+/* Initialize a rights structure.
+ The minimum rights start with all available access and are later
+ bitwise-and'ed with actual access rights. The maximum rights begin
+ empty and are later bitwise-and'ed with actual rights. */
+static void init_rights(authz_rights_t *rights)
+{
+ rights->min_access = authz_access_write;
+ rights->max_access = authz_access_none;
+ }
+
+/* Initialize a global rights structure.
+ The USER string must be interned or statically initialized. */
+static void
+init_global_rights(authz_global_rights_t *gr, const char *user,
+ apr_pool_t *result_pool)
+{
+ gr->user = user;
+ init_rights(&gr->all_repos_rights);
+ init_rights(&gr->any_repos_rights);
+ gr->per_repos_rights = apr_hash_make(result_pool);
+}
+
+
+/* Insert the default global ACL into the parsed ACLs. */
+static void
+insert_default_acl(ctor_baton_t *cb)
+{
+ parsed_acl_t *acl = &APR_ARRAY_PUSH(cb->parsed_acls, parsed_acl_t);
+ acl->acl.sequence_number = 0;
+ acl->acl.rule.repos = interned_empty_string;
+ acl->acl.rule.len = 0;
+ acl->acl.rule.path = NULL;
+ acl->acl.anon_access = authz_access_none;
+ acl->acl.has_anon_access = TRUE;
+ acl->acl.authn_access = authz_access_none;
+ acl->acl.has_authn_access = TRUE;
+ acl->acl.user_access = NULL;
+ acl->aces = svn_hash__make(cb->parser_pool);
+ acl->alias_aces = svn_hash__make(cb->parser_pool);
+}
+
+
+/* Initialize a constuctor baton. */
+static ctor_baton_t *
+create_ctor_baton(apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *const parser_pool = svn_pool_create(scratch_pool);
+ ctor_baton_t *const cb = apr_pcalloc(parser_pool, sizeof(*cb));
+
+ authz_full_t *const authz = apr_pcalloc(result_pool, sizeof(*authz));
+ init_global_rights(&authz->anon_rights, anon_access_token, result_pool);
+ init_global_rights(&authz->authn_rights, authn_access_token, result_pool);
+ authz->user_rights = svn_hash__make(result_pool);
+ authz->pool = result_pool;
+
+ cb->authz = authz;
+ cb->strings = svn_hash__make(parser_pool);
+
+ cb->sections = svn_hash__make(parser_pool);
+ cb->section = NULL;
+ cb->parsing_groups = FALSE;
+ cb->in_groups = FALSE;
+ cb->in_aliases = FALSE;
+
+ cb->parsed_rules = svn_hash__make(parser_pool);
+ cb->parsed_groups = svn_hash__make(parser_pool);
+ cb->parsed_aliases = svn_hash__make(parser_pool);
+ cb->parsed_acls = apr_array_make(parser_pool, 64, sizeof(parsed_acl_t));
+ cb->current_acl = NULL;
+
+ svn_membuf__create(&cb->rule_path_buffer, 0, parser_pool);
+ cb->rule_string_buffer = svn_stringbuf_create_empty(parser_pool);
+
+ cb->parser_pool = parser_pool;
+
+ insert_default_acl(cb);
+
+ return cb;
+}
+
+
+/* Create and store per-user global rights.
+ The USER string must be interned or statically initialized. */
+static void
+prepare_global_rights(ctor_baton_t *cb, const char *user)
+{
+ authz_global_rights_t *gr = svn_hash_gets(cb->authz->user_rights, user);
+ if (!gr)
+ {
+ gr = apr_palloc(cb->authz->pool, sizeof(*gr));
+ init_global_rights(gr, user, cb->authz->pool);
+ svn_hash_sets(cb->authz->user_rights, gr->user, gr);
+ }
+}
+
+
+/* Internalize a string that will be referenced by the parsed svn_authz_t.
+ If LEN is (apr_size_t)-1, assume the string is NUL-terminated. */
+static const char *
+intern_string(ctor_baton_t *cb, const char *str, apr_size_t len)
+{
+ const char *interned;
+
+ if (len == (apr_size_t)-1)
+ len = strlen(str);
+
+ interned = apr_hash_get(cb->strings, str, len);
+ if (!interned)
+ {
+ interned = apr_pstrmemdup(cb->authz->pool, str, len);
+ apr_hash_set(cb->strings, interned, len, interned);
+ }
+ return interned;
+}
+
+
+/* Helper for rules_open_section and groups_open_section. */
+static svn_error_t *
+check_open_section(ctor_baton_t *cb, svn_stringbuf_t *section)
+{
+ SVN_ERR_ASSERT(!cb->current_acl && !cb->section);
+ if (apr_hash_get(cb->sections, section->data, section->len))
+ {
+ if (cb->parsing_groups)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Section appears more than once"
+ " in the global groups file: [%s]"),
+ section->data);
+ else
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Section appears more than once"
+ " in the authz file: [%s]"),
+ section->data);
+ }
+
+ cb->section = apr_pstrmemdup(cb->parser_pool, section->data, section->len);
+ svn_hash_sets(cb->sections, cb->section, interned_empty_string);
+ return SVN_NO_ERROR;
+}
+
+
+/* Constructor callback: Begins the [groups] section. */
+static svn_error_t *
+groups_open_section(void *baton, svn_stringbuf_t *section)
+{
+ ctor_baton_t *const cb = baton;
+
+ if (cb->parsing_groups)
+ SVN_ERR(check_open_section(cb, section));
+
+ if (0 == strcmp(section->data, groups_section))
+ {
+ cb->in_groups = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ (cb->parsing_groups
+ ? _("Section is not valid in the global group file: [%s]")
+ : _("Section is not valid in the authz file: [%s]")),
+ section->data);
+}
+
+
+/* Constructor callback: Parses a group declaration. */
+static svn_error_t *
+groups_add_value(void *baton, svn_stringbuf_t *section,
+ svn_stringbuf_t *option, svn_stringbuf_t *value)
+{
+ ctor_baton_t *const cb = baton;
+ const char *group;
+ apr_size_t group_len;
+
+ SVN_ERR_ASSERT(cb->in_groups);
+
+ if (strchr("@$&*~", *option->data))
+ {
+ if (cb->parsing_groups)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Global group name '%s' may not begin with '%c'"),
+ option->data, *option->data);
+ else
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Group name '%s' may not begin with '%c'"),
+ option->data, *option->data);
+ }
+
+ /* Decorate the name to make lookups consistent. */
+ group = apr_pstrcat(cb->parser_pool, "@", option->data, SVN_VA_NULL);
+ group_len = option->len + 1;
+ if (apr_hash_get(cb->parsed_groups, group, group_len))
+ {
+ if (cb->parsing_groups)
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Can't override definition"
+ " of global group '%s'"),
+ group);
+ else
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Can't override definition"
+ " of group '%s'"),
+ group);
+ }
+
+ /* We store the whole group definition, so that we can use the
+ temporary groups in the baton hash later to fully expand group
+ memberships.
+ At this point, we can finally internalize the group name. */
+ apr_hash_set(cb->parsed_groups,
+ intern_string(cb, group, group_len), group_len,
+ svn_cstring_split(value->data, ",", TRUE, cb->parser_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Remove escape sequences in-place. */
+static void
+unescape_in_place(svn_stringbuf_t *buf)
+{
+ char *p = buf->data;
+ apr_size_t i;
+
+ /* Skip the string up to the first escape sequence. */
+ for (i = 0; i < buf->len; ++i)
+ {
+ if (*p == '\\')
+ break;
+ ++p;
+ }
+
+ if (i < buf->len)
+ {
+ /* Unescape the remainder of the string. */
+ svn_boolean_t escape = TRUE;
+ const char *q;
+
+ for (q = p + 1, ++i; i < buf->len; ++i)
+ {
+ if (escape)
+ {
+ *p++ = *q++;
+ escape = FALSE;
+ }
+ else if (*q == '\\')
+ {
+ ++q;
+ escape = TRUE;
+ }
+ else
+ *p++ = *q++;
+ }
+
+ /* A trailing backslash is literal, so make it part of the pattern. */
+ if (escape)
+ *p++ = '\\';
+ *p = '\0';
+ buf->len = p - buf->data;
+ }
+}
+
+
+/* Internalize a pattern. */
+static void
+intern_pattern(ctor_baton_t *cb,
+ svn_string_t *pattern,
+ const char *string,
+ apr_size_t len)
+{
+ pattern->data = intern_string(cb, string, len);
+ pattern->len = len;
+}
+
+
+/* Parse a rule path PATH up to PATH_LEN into *RULE.
+ If GLOB is TRUE, treat PATH as possibly containing wildcards.
+ SECTION is the whole rule in the authz file.
+ Use pools and buffers from CB to do the obvious thing. */
+static svn_error_t *
+parse_rule_path(authz_rule_t *rule,
+ ctor_baton_t *cb,
+ svn_boolean_t glob,
+ const char *path,
+ apr_size_t path_len,
+ const char *section)
+{
+ svn_stringbuf_t *const pattern = cb->rule_string_buffer;
+ const char *const path_end = path + path_len;
+ authz_rule_segment_t *segment;
+ const char *start;
+ const char *end;
+ int nseg;
+
+ SVN_ERR_ASSERT(*path == '/');
+
+ nseg = 0;
+ for (start = path; start != path_end; start = end)
+ {
+ apr_size_t pattern_len;
+
+ /* Skip the leading slash and find the end of the segment. */
+ end = memchr(++start, '/', path_len - 1);
+ if (!end)
+ end = path_end;
+
+ pattern_len = end - start;
+ path_len -= pattern_len + 1;
+
+ if (pattern_len == 0)
+ {
+ if (nseg == 0)
+ {
+ /* This is an empty (root) path. */
+ rule->len = 0;
+ rule->path = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* A path with two consecutive slashes is not canonical. */
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG,
+ svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Found empty name in authz rule path")),
+ _("Non-canonical path '%s' in authz rule [%s]"),
+ path, section);
+ }
+
+ /* A path with . or .. segments is not canonical. */
+ if (*start == '.'
+ && (pattern_len == 1
+ || (pattern_len == 2 && start[1] == '.')))
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG,
+ (end == start + 1
+ ? svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Found '.' in authz rule path"))
+ : svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Found '..' in authz rule path"))),
+ _("Non-canonical path '%s' in authz rule [%s]"),
+ path, section);
+
+ /* Make space for the current segment. */
+ ++nseg;
+ svn_membuf__resize(&cb->rule_path_buffer, nseg * sizeof(*segment));
+ segment = cb->rule_path_buffer.data;
+ segment += (nseg - 1);
+
+ if (!glob)
+ {
+ /* Trivial case: this is not a glob rule, so every segment
+ is a literal match. */
+ segment->kind = authz_rule_literal;
+ intern_pattern(cb, &segment->pattern, start, pattern_len);
+ continue;
+ }
+
+ /* Copy the segment into the temporary buffer. */
+ svn_stringbuf_setempty(pattern);
+ svn_stringbuf_appendbytes(pattern, start, pattern_len);
+
+ if (0 == apr_fnmatch_test(pattern->data))
+ {
+ /* It's a literal match after all. */
+ segment->kind = authz_rule_literal;
+ unescape_in_place(pattern);
+ intern_pattern(cb, &segment->pattern, pattern->data, pattern->len);
+ continue;
+ }
+
+ if (*pattern->data == '*')
+ {
+ if (pattern->len == 1
+ || (pattern->len == 2 && pattern->data[1] == '*'))
+ {
+ /* Process * and **, applying normalization as per
+ https://wiki.apache.org/subversion/AuthzImprovements. */
+
+ authz_rule_segment_t *const prev =
+ (nseg > 1 ? segment - 1 : NULL);
+
+ if (pattern_len == 1)
+ {
+ /* This is a *. Replace **|* with *|**. */
+ if (prev && prev->kind == authz_rule_any_recursive)
+ {
+ prev->kind = authz_rule_any_segment;
+ segment->kind = authz_rule_any_recursive;
+ }
+ else
+ segment->kind = authz_rule_any_segment;
+ }
+ else
+ {
+ /* This is a **. Replace **|** with a single **. */
+ if (prev && prev->kind == authz_rule_any_recursive)
+ {
+ /* Simply drop the redundant new segment. */
+ --nseg;
+ continue;
+ }
+ else
+ segment->kind = authz_rule_any_recursive;
+ }
+
+ segment->pattern.data = interned_empty_string;
+ segment->pattern.len = 0;
+ continue;
+ }
+
+ /* Maybe it's a suffix match? */
+ if (0 == apr_fnmatch_test(pattern->data + 1))
+ {
+ svn_stringbuf_leftchop(pattern, 1);
+ segment->kind = authz_rule_suffix;
+ unescape_in_place(pattern);
+ svn_authz__reverse_string(pattern->data, pattern->len);
+ intern_pattern(cb, &segment->pattern,
+ pattern->data, pattern->len);
+ continue;
+ }
+ }
+
+ if (pattern->data[pattern->len - 1] == '*')
+ {
+ /* Might be a prefix match. Note that because of the
+ previous test, we already know that the pattern is longer
+ than one character. */
+ if (pattern->data[pattern->len - 2] != '\\')
+ {
+ /* OK, the * wasn't escaped. Chop off the wildcard. */
+ svn_stringbuf_chop(pattern, 1);
+ if (0 == apr_fnmatch_test(pattern->data))
+ {
+ segment->kind = authz_rule_prefix;
+ unescape_in_place(pattern);
+ intern_pattern(cb, &segment->pattern,
+ pattern->data, pattern->len);
+ continue;
+ }
+
+ /* Restore the wildcard since it was not a prefix match. */
+ svn_stringbuf_appendbyte(pattern, '*');
+ }
+ }
+
+ /* It's a generic fnmatch pattern. */
+ segment->kind = authz_rule_fnmatch;
+ intern_pattern(cb, &segment->pattern, pattern->data, pattern->len);
+ }
+
+ SVN_ERR_ASSERT(nseg > 0);
+
+ /* Copy the temporary segments array into the result pool. */
+ {
+ const apr_size_t path_size = nseg * sizeof(*segment);
+ SVN_ERR_ASSERT(path_size <= cb->rule_path_buffer.size);
+
+ rule->len = nseg;
+ rule->path = apr_palloc(cb->authz->pool, path_size);
+ memcpy(rule->path, cb->rule_path_buffer.data, path_size);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Check that the parsed RULE is unique within the authz file.
+ With the introduction of wildcards, just looking at the SECTION
+ names is not sufficient to determine uniqueness.
+ Use pools and buffers from CB to do the obvious thing. */
+static svn_error_t *
+check_unique_rule(ctor_baton_t *cb,
+ const authz_rule_t *rule,
+ const char *section)
+{
+ svn_stringbuf_t *const buf = cb->rule_string_buffer;
+ const char *exists;
+ int i;
+
+ /* Construct the key for this rule */
+ svn_stringbuf_setempty(buf);
+ svn_stringbuf_appendcstr(buf, rule->repos);
+ svn_stringbuf_appendbyte(buf, '\n');
+
+ for (i = 0; i < rule->len; ++i)
+ {
+ authz_rule_segment_t *const seg = &rule->path[i];
+ svn_stringbuf_appendbyte(buf, '@' + seg->kind);
+ svn_stringbuf_appendbytes(buf, seg->pattern.data, seg->pattern.len);
+ svn_stringbuf_appendbyte(buf, '\n');
+ }
+
+ /* Check if the section exists. */
+ exists = apr_hash_get(cb->parsed_rules, buf->data, buf->len);
+ if (exists)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Section [%s] describes the same rule as section [%s]"),
+ section, exists);
+
+ /* Insert the rule into the known rules set. */
+ apr_hash_set(cb->parsed_rules,
+ apr_pstrmemdup(cb->parser_pool, buf->data, buf->len),
+ buf->len,
+ apr_pstrdup(cb->parser_pool, section));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Constructor callback: Starts a rule or [aliases] section. */
+static svn_error_t *
+rules_open_section(void *baton, svn_stringbuf_t *section)
+{
+ ctor_baton_t *const cb = baton;
+ const char *rule = section->data;
+ apr_size_t rule_len = section->len;
+ svn_boolean_t glob;
+ const char *endp;
+ parsed_acl_t acl;
+
+ SVN_ERR(check_open_section(cb, section));
+
+ /* Parse rule property tokens. */
+ if (*rule != ':')
+ glob = FALSE;
+ else
+ {
+ /* This must be a wildcard rule. */
+ apr_size_t token_len;
+
+ ++rule; --rule_len;
+ endp = memchr(rule, ':', rule_len);
+ if (!endp)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Empty repository name in authz rule [%s]"),
+ section->data);
+
+ /* Note: the size of glob_rule_token includes the NUL terminator. */
+ token_len = endp - rule;
+ if (token_len != sizeof(glob_rule_token) - 1
+ || memcmp(rule, glob_rule_token, token_len))
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Invalid type token '%s' in authz rule [%s]"),
+ apr_pstrmemdup(cb->parser_pool, rule, token_len),
+ section->data);
+
+ glob = TRUE;
+ rule = endp + 1;
+ rule_len -= token_len + 1;
+ }
+
+ /* Parse the repository name. */
+ endp = (*rule == '/' ? NULL : memchr(rule, ':', rule_len));
+ if (!endp)
+ acl.acl.rule.repos = interned_empty_string;
+ else
+ {
+ const apr_size_t repos_len = endp - rule;
+
+ /* The rule contains a repository name. */
+ if (0 == repos_len)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Empty repository name in authz rule [%s]"),
+ section->data);
+
+ acl.acl.rule.repos = intern_string(cb, rule, repos_len);
+ rule = endp + 1;
+ rule_len -= repos_len + 1;
+ }
+
+ /* Parse the actual rule. */
+ if (*rule == '/')
+ {
+ SVN_ERR(parse_rule_path(&acl.acl.rule, cb, glob, rule, rule_len,
+ section->data));
+ SVN_ERR(check_unique_rule(cb, &acl.acl.rule, section->data));
+ }
+ else if (0 == strcmp(section->data, aliases_section))
+ {
+ cb->in_aliases = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* This must be the [groups] section. */
+ return groups_open_section(cb, section);
+ }
+
+ acl.acl.sequence_number = cb->parsed_acls->nelts;
+ acl.acl.anon_access = authz_access_none;
+ acl.acl.has_anon_access = FALSE;
+ acl.acl.authn_access = authz_access_none;
+ acl.acl.has_authn_access = FALSE;
+ acl.acl.user_access = NULL;
+
+ acl.aces = svn_hash__make(cb->parser_pool);
+ acl.alias_aces = svn_hash__make(cb->parser_pool);
+
+ cb->current_acl = &APR_ARRAY_PUSH(cb->parsed_acls, parsed_acl_t);
+ *cb->current_acl = acl;
+ return SVN_NO_ERROR;
+}
+
+
+/* Parses an alias declaration. The definition (username) of the
+ alias will always be interned. */
+static svn_error_t *
+add_alias_definition(ctor_baton_t *cb,
+ svn_stringbuf_t *option, svn_stringbuf_t *value)
+{
+ const char *alias;
+ apr_size_t alias_len;
+ const char *user;
+
+ if (strchr("@$&*~", *option->data))
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Alias name '%s' may not begin with '%c'"),
+ option->data, *option->data);
+
+ /* Decorate the name to make lookups consistent. */
+ alias = apr_pstrcat(cb->parser_pool, "&", option->data, SVN_VA_NULL);
+ alias_len = option->len + 1;
+ if (apr_hash_get(cb->parsed_aliases, alias, alias_len))
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Can't override definition of alias '%s'"),
+ alias);
+
+ user = intern_string(cb, value->data, value->len);
+ apr_hash_set(cb->parsed_aliases, alias, alias_len, user);
+
+ /* Prepare the global rights struct for this user. */
+ prepare_global_rights(cb, user);
+ return SVN_NO_ERROR;
+}
+
+/* Parses an access entry. Groups and users in access entry names will
+ always be interned, aliases will never be. */
+static svn_error_t *
+add_access_entry(ctor_baton_t *cb, svn_stringbuf_t *section,
+ svn_stringbuf_t *option, svn_stringbuf_t *value)
+{
+ parsed_acl_t *const acl = cb->current_acl;
+ const char *name = option->data;
+ apr_size_t name_len = option->len;
+ const svn_boolean_t inverted = (*name == '~');
+ svn_boolean_t anonymous = FALSE;
+ svn_boolean_t authenticated = FALSE;
+ authz_access_t access = authz_access_none;
+ authz_ace_t *ace;
+ int i;
+
+ SVN_ERR_ASSERT(acl != NULL);
+
+ if (inverted)
+ {
+ ++name;
+ --name_len;
+ }
+
+ /* Determine the access entry type. */
+ switch (*name)
+ {
+ case '~':
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Access entry '%s' has more than one inversion;"
+ " double negatives are not permitted"),
+ option->data);
+ break;
+
+ case '*':
+ if (name_len != 1)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Access entry '%s' is not valid;"
+ " it must be a single '*'"),
+ option->data);
+
+ if (inverted)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Access entry '~*' will never match"));
+
+ anonymous = TRUE;
+ authenticated = TRUE;
+ break;
+
+ case '$':
+ if (0 == strcmp(name, anon_access_token))
+ {
+ if (inverted)
+ authenticated = TRUE;
+ else
+ anonymous = TRUE;
+ }
+ else if (0 == strcmp(name, authn_access_token))
+ {
+ if (inverted)
+ anonymous = TRUE;
+ else
+ authenticated = TRUE;
+ }
+ else
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Access entry token '%s' is not valid;"
+ " should be '%s' or '%s'"),
+ option->data, anon_access_token, authn_access_token);
+ break;
+
+ default:
+ /* A username, group name or alias. */;
+ }
+
+ /* Parse the access rights. */
+ for (i = 0; i < value->len; ++i)
+ {
+ const char access_code = value->data[i];
+ switch (access_code)
+ {
+ case 'r':
+ access |= authz_access_read_flag;
+ break;
+
+ case 'w':
+ access |= authz_access_write_flag;
+ break;
+
+ default:
+ if (!svn_ctype_isspace(access_code))
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("The access mode '%c' in access entry '%s'"
+ " of rule [%s] is not valid"),
+ access_code, option->data, section->data);
+ }
+ }
+
+ /* We do not support write-only access. */
+ if ((access & authz_access_write_flag) && !(access & authz_access_read_flag))
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Write-only access entry '%s' of rule [%s] is not valid"),
+ option->data, section->data);
+
+ /* Update the parsed ACL with this access entry. */
+ if (anonymous || authenticated)
+ {
+ if (anonymous)
+ {
+ acl->acl.has_anon_access = TRUE;
+ acl->acl.anon_access |= access;
+ }
+ if (authenticated)
+ {
+ acl->acl.has_authn_access = TRUE;
+ acl->acl.authn_access |= access;
+ }
+ }
+ else
+ {
+ /* The inversion tag must be part of the key in the hash
+ table, otherwise we can't tell regular and inverted
+ entries appart. */
+ const char *key = (inverted ? name - 1 : name);
+ const apr_size_t key_len = (inverted ? name_len + 1 : name_len);
+ const svn_boolean_t aliased = (*name == '&');
+ apr_hash_t *aces = (aliased ? acl->alias_aces : acl->aces);
+
+ ace = apr_hash_get(aces, key, key_len);
+ if (ace)
+ ace->access |= access;
+ else
+ {
+ ace = apr_palloc(cb->parser_pool, sizeof(*ace));
+ ace->name = (aliased
+ ? apr_pstrmemdup(cb->parser_pool, name, name_len)
+ : intern_string(cb, name, name_len));
+ ace->members = NULL;
+ ace->inverted = inverted;
+ ace->access = access;
+
+ key = (inverted
+ ? apr_pstrmemdup(cb->parser_pool, key, key_len)
+ : ace->name);
+ apr_hash_set(aces, key, key_len, ace);
+
+ /* Prepare the global rights struct for this user. */
+ if (!aliased && *ace->name != '@')
+ prepare_global_rights(cb, ace->name);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Constructor callback: Parse a rule, alias or group delcaration. */
+static svn_error_t *
+rules_add_value(void *baton, svn_stringbuf_t *section,
+ svn_stringbuf_t *option, svn_stringbuf_t *value)
+{
+ ctor_baton_t *const cb = baton;
+
+ if (cb->in_groups)
+ return groups_add_value(baton, section, option, value);
+
+ if (cb->in_aliases)
+ return add_alias_definition(cb, option, value);
+
+ return add_access_entry(cb, section, option, value);
+}
+
+
+/* Constructor callback: Close a section. */
+static svn_error_t *
+close_section(void *baton, svn_stringbuf_t *section)
+{
+ ctor_baton_t *const cb = baton;
+
+ SVN_ERR_ASSERT(0 == strcmp(cb->section, section->data));
+ cb->section = NULL;
+ cb->current_acl = NULL;
+ cb->in_groups = FALSE;
+ cb->in_aliases = FALSE;
+ return SVN_NO_ERROR;
+}
+
+
+/* Add a user to GROUP.
+ GROUP is never internalized, but USER always is. */
+static void
+add_to_group(ctor_baton_t *cb, const char *group, const char *user)
+{
+ apr_hash_t *members = svn_hash_gets(cb->expanded_groups, group);
+ if (!members)
+ {
+ group = intern_string(cb, group, -1);
+ members = svn_hash__make(cb->authz->pool);
+ svn_hash_sets(cb->expanded_groups, group, members);
+ }
+ svn_hash_sets(members, user, interned_empty_string);
+}
+
+
+/* Hash iterator for expanding group definitions.
+ WARNING: This function is recursive! */
+static svn_error_t *
+expand_group_callback(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ ctor_baton_t *const cb = baton;
+ const char *const group = key;
+ apr_array_header_t *members = value;
+
+ int i;
+ for (i = 0; i < members->nelts; ++i)
+ {
+ const char *member = APR_ARRAY_IDX(members, i, const char*);
+ if (0 == strcmp(member, group))
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Recursive definition of group '%s'"),
+ group);
+
+ if (*member == '&')
+ {
+ /* Add expanded alias to the group.
+ N.B.: the user name is already internalized. */
+ const char *user = svn_hash_gets(cb->parsed_aliases, member);
+ if (!user)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Alias '%s' was never defined"),
+ member);
+
+ add_to_group(cb, group, user);
+ }
+ else if (*member != '@')
+ {
+ /* Add the member to the group. */
+ const char *user = intern_string(cb, member, -1);
+ add_to_group(cb, group, user);
+
+ /* Prepare the global rights struct for this user. */
+ prepare_global_rights(cb, user);
+ }
+ else
+ {
+ /* Recursively expand the group membership */
+ members = svn_hash_gets(cb->parsed_groups, member);
+ if (!members)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Undefined group '%s'"),
+ member);
+ SVN_ERR(expand_group_callback(cb, key, klen,
+ members, scratch_pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Hash iteration baton for merge_alias_ace. */
+typedef struct merge_alias_baton_t
+{
+ apr_hash_t *aces;
+ ctor_baton_t *cb;
+} merge_alias_baton_t;
+
+/* Hash iterator for expanding and mergina alias-based ACEs
+ into the user/group-based ACEs. */
+static svn_error_t *
+merge_alias_ace(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ merge_alias_baton_t *const mab = baton;
+ authz_ace_t *aliased_ace = value;
+ const char *alias = aliased_ace->name;
+ const char *unaliased_key;
+ const char *user;
+ authz_ace_t *ace;
+
+ user = svn_hash_gets(mab->cb->parsed_aliases, alias);
+ if (!user)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Alias '%s' was never defined"),
+ alias);
+
+ /* N.B.: The user name is always internalized,
+ but the inverted key may not be. */
+ if (!aliased_ace->inverted)
+ unaliased_key = user;
+ else
+ {
+ unaliased_key = apr_pstrcat(mab->cb->parser_pool,
+ "~", user, SVN_VA_NULL);
+ unaliased_key = intern_string(mab->cb, unaliased_key, -1);
+ }
+
+ ace = svn_hash_gets(mab->aces, unaliased_key);
+ if (!ace)
+ {
+ aliased_ace->name = user;
+ svn_hash_sets(mab->aces, unaliased_key, aliased_ace);
+ }
+ else
+ {
+ SVN_ERR_ASSERT(!ace->inverted == !aliased_ace->inverted);
+ ace->access |= aliased_ace->access;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Hash iteration baton for array_insert_ace. */
+typedef struct insert_ace_baton_t
+{
+ apr_array_header_t *ace_array;
+ ctor_baton_t *cb;
+} insert_ace_baton_t;
+
+/* Hash iterator, inserts an ACE into the ACLs array. */
+static svn_error_t *
+array_insert_ace(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ insert_ace_baton_t *iab = baton;
+ authz_ace_t *ace = value;
+
+ /* Add group membership info to the ACE. */
+ if (*ace->name == '@')
+ {
+ SVN_ERR_ASSERT(ace->members == NULL);
+ ace->members = svn_hash_gets(iab->cb->expanded_groups, ace->name);
+ if (!ace->members)
+ return svn_error_createf(
+ SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ _("Access entry refers to undefined group '%s'"),
+ ace->name);
+ }
+
+ APR_ARRAY_PUSH(iab->ace_array, authz_ace_t) = *ace;
+ return SVN_NO_ERROR;
+}
+
+
+/* Update accumulated RIGHTS from ACCESS. */
+static void
+update_rights(authz_rights_t *rights,
+ authz_access_t access)
+{
+ rights->min_access &= access;
+ rights->max_access |= access;
+}
+
+
+/* Update a global RIGHTS based on REPOS and ACCESS. */
+static void
+update_global_rights(authz_global_rights_t *gr,
+ const char *repos,
+ authz_access_t access)
+{
+ update_rights(&gr->all_repos_rights, access);
+ if (0 == strcmp(repos, AUTHZ_ANY_REPOSITORY))
+ update_rights(&gr->any_repos_rights, access);
+ else
+ {
+ authz_rights_t *rights = svn_hash_gets(gr->per_repos_rights, repos);
+ if (rights)
+ update_rights(rights, access);
+ else
+ {
+ rights = apr_palloc(apr_hash_pool_get(gr->per_repos_rights),
+ sizeof(*rights));
+ init_rights(rights);
+ update_rights(rights, access);
+ svn_hash_sets(gr->per_repos_rights, repos, rights);
+ }
+ }
+}
+
+
+/* Hash iterator to update global per-user rights from an ACL. */
+static svn_error_t *
+update_user_rights(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ const authz_acl_t *const acl = baton;
+ const char *const user = key;
+ authz_global_rights_t *const gr = value;
+ authz_access_t access;
+ svn_boolean_t has_access =
+ svn_authz__get_acl_access(&access, acl, user, acl->rule.repos);
+
+ if (has_access)
+ update_global_rights(gr, acl->rule.repos, access);
+ return SVN_NO_ERROR;
+}
+
+
+/* List iterator, expands/merges a parsed ACL into its final form and
+ appends it to the authz info's ACL array. */
+static svn_error_t *
+expand_acl_callback(void *baton,
+ void *item,
+ apr_pool_t *scratch_pool)
+{
+ ctor_baton_t *const cb = baton;
+ parsed_acl_t *const pacl = item;
+ authz_acl_t *const acl = &pacl->acl;
+
+ /* Expand and merge the aliased ACEs. */
+ if (apr_hash_count(pacl->alias_aces))
+ {
+ merge_alias_baton_t mab;
+ mab.aces = pacl->aces;
+ mab.cb = cb;
+ SVN_ERR(svn_iter_apr_hash(NULL, pacl->alias_aces,
+ merge_alias_ace, &mab, scratch_pool));
+ }
+
+ /* Make an array from the merged hashes. */
+ acl->user_access =
+ apr_array_make(cb->authz->pool, apr_hash_count(pacl->aces),
+ sizeof(authz_ace_t));
+ {
+ insert_ace_baton_t iab;
+ iab.ace_array = acl->user_access;
+ iab.cb = cb;
+ SVN_ERR(svn_iter_apr_hash(NULL, pacl->aces,
+ array_insert_ace, &iab, scratch_pool));
+ }
+
+ /* Store the completed ACL into authz. */
+ APR_ARRAY_PUSH(cb->authz->acls, authz_acl_t) = *acl;
+
+ /* Update global access rights for this ACL. */
+ if (acl->has_anon_access)
+ {
+ cb->authz->has_anon_rights = TRUE;
+ update_global_rights(&cb->authz->anon_rights,
+ acl->rule.repos, acl->anon_access);
+ }
+ if (acl->has_authn_access)
+ {
+ cb->authz->has_authn_rights = TRUE;
+ update_global_rights(&cb->authz->authn_rights,
+ acl->rule.repos, acl->authn_access);
+ }
+ SVN_ERR(svn_iter_apr_hash(NULL, cb->authz->user_rights,
+ update_user_rights, acl, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Compare two ACLs in rule lexical order, then repository order, then
+ order of definition. This ensures that our default ACL is always
+ first in the sorted array. */
+static int
+compare_parsed_acls(const void *va, const void *vb)
+{
+ const parsed_acl_t *const a = va;
+ const parsed_acl_t *const b = vb;
+
+ int cmp = svn_authz__compare_rules(&a->acl.rule, &b->acl.rule);
+ if (cmp == 0)
+ cmp = a->acl.sequence_number - b->acl.sequence_number;
+ return cmp;
+}
+
+
+svn_error_t *
+svn_authz__parse(authz_full_t **authz,
+ svn_stream_t *rules,
+ svn_stream_t *groups,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ ctor_baton_t *const cb = create_ctor_baton(result_pool, scratch_pool);
+
+ /*
+ * Pass 1: Parse the authz file.
+ */
+ SVN_ERR(svn_config__parse_stream(rules,
+ svn_config__constructor_create(
+ rules_open_section,
+ close_section,
+ rules_add_value,
+ cb->parser_pool),
+ cb, cb->parser_pool));
+
+ /*
+ * Pass 1.6487: Parse the global groups file.
+ */
+ if (groups)
+ {
+ /* Check that the authz file did not contain any groups. */
+ if (0 != apr_hash_count(cb->parsed_groups))
+ return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ ("Authz file cannot contain any groups"
+ " when global groups are being used."));
+
+ apr_hash_clear(cb->sections);
+ cb->parsing_groups = TRUE;
+ SVN_ERR(svn_config__parse_stream(groups,
+ svn_config__constructor_create(
+ groups_open_section,
+ close_section,
+ groups_add_value,
+ cb->parser_pool),
+ cb, cb->parser_pool));
+ }
+
+ /*
+ * Pass 2: Expand groups and construct the final svn_authz_t.
+ */
+ cb->expanded_groups = svn_hash__make(cb->parser_pool);
+ SVN_ERR(svn_iter_apr_hash(NULL, cb->parsed_groups,
+ expand_group_callback, cb, cb->parser_pool));
+
+
+ /* Sort the parsed ACLs in rule lexical order and pop off the
+ default global ACL iff an equivalent ACL was defined in the authz
+ file. */
+ if (cb->parsed_acls->nelts > 1)
+ {
+ parsed_acl_t *defacl;
+ parsed_acl_t *nxtacl;
+
+ svn_sort__array(cb->parsed_acls, compare_parsed_acls);
+ defacl = &APR_ARRAY_IDX(cb->parsed_acls, 0, parsed_acl_t);
+ nxtacl = &APR_ARRAY_IDX(cb->parsed_acls, 1, parsed_acl_t);
+
+ /* If the first ACL is not our default thingamajig, there's a
+ bug in our comparator. */
+ SVN_ERR_ASSERT(
+ defacl->acl.sequence_number == 0 && defacl->acl.rule.len == 0
+ && 0 == strcmp(defacl->acl.rule.repos, AUTHZ_ANY_REPOSITORY));
+
+ /* Pop the default ACL off the array if another equivalent
+ exists, after merging the default rights. */
+ if (0 == svn_authz__compare_rules(&defacl->acl.rule, &nxtacl->acl.rule))
+ {
+ nxtacl->acl.has_anon_access = TRUE;
+ nxtacl->acl.has_authn_access = TRUE;
+ cb->parsed_acls->elts = (char*)(nxtacl);
+ --cb->parsed_acls->nelts;
+ }
+ }
+
+ cb->authz->acls = apr_array_make(cb->authz->pool, cb->parsed_acls->nelts,
+ sizeof(authz_acl_t));
+ SVN_ERR(svn_iter_apr_array(NULL, cb->parsed_acls,
+ expand_acl_callback, cb, cb->parser_pool));
+
+ *authz = cb->authz;
+ apr_pool_destroy(cb->parser_pool);
+ return SVN_NO_ERROR;
+}
+
+
+void
+svn_authz__reverse_string(char *string, apr_size_t len)
+{
+ char *left = string;
+ char *right = string + len - 1;
+ for (; left < right; ++left, --right)
+ {
+ char c = *left;
+ *left = *right;
+ *right = c;
+ }
+}
+
+
+int
+svn_authz__compare_paths(const authz_rule_t *a, const authz_rule_t *b)
+{
+ const int min_len = (a->len > b->len ? b->len : a->len);
+ int i;
+
+ for (i = 0; i < min_len; ++i)
+ {
+ int cmp = a->path[i].kind - b->path[i].kind;
+ if (0 == cmp)
+ {
+ const char *const aseg = a->path[i].pattern.data;
+ const char *const bseg = b->path[i].pattern.data;
+
+ /* Exploit the fact that segment patterns are interned. */
+ if (aseg != bseg)
+ cmp = strcmp(aseg, bseg);
+ else
+ cmp = 0;
+ }
+ if (0 != cmp)
+ return cmp;
+ }
+
+ /* Sort shorter rules first. */
+ if (a->len != b->len)
+ return a->len - b->len;
+
+ return 0;
+}
+
+int
+svn_authz__compare_rules(const authz_rule_t *a, const authz_rule_t *b)
+{
+ int diff = svn_authz__compare_paths(a, b);
+ if (diff)
+ return diff;
+
+ /* Repository names are interned, too. */
+ if (a->repos != b->repos)
+ return strcmp(a->repos, b->repos);
+
+ return 0;
+}
diff --git a/subversion/libsvn_repos/authz_pool.c b/subversion/libsvn_repos/authz_pool.c
deleted file mode 100644
index f8ac52835876..000000000000
--- a/subversion/libsvn_repos/authz_pool.c
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * authz_pool.c : pool of authorization objects
- *
- * ====================================================================
- * 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 "svn_checksum.h"
-#include "svn_config.h"
-#include "svn_error.h"
-#include "svn_pools.h"
-
-#include "private/svn_dep_compat.h"
-#include "private/svn_mutex.h"
-#include "private/svn_object_pool.h"
-#include "private/svn_subr_private.h"
-#include "private/svn_repos_private.h"
-#include "private/svn_string_private.h"
-#include "private/svn_subr_private.h"
-
-#include "repos.h"
-
-/* Currently this structure is just a wrapper around a svn_config_t.
- */
-struct svn_authz_t
-{
- svn_config_t *cfg;
-};
-
-/* The wrapper object structure that we store in the object pool. It
- * combines the authz with the underlying config structures and their
- * identifying keys.
- */
-typedef struct authz_object_t
-{
- /* key = concatenation of AUTHZ_KEY and GROUPS_KEY */
- svn_membuf_t *key;
-
- /* keys used to identify AUTHZ_CFG and GROUPS_CFG */
- svn_membuf_t *authz_key;
- svn_membuf_t *groups_key;
-
- /* r/o references to configurations from the configuration pool.
- GROUPS_CFG may be NULL. */
- svn_config_t *authz_cfg;
- svn_config_t *groups_cfg;
-
- /* Case-sensitive config. */
- svn_authz_t *authz;
-} authz_object_t;
-
-/* Root data structure simply adding the config_pool to the basic object pool.
- */
-struct svn_repos__authz_pool_t
-{
- /* authz_object_t object storage */
- svn_object_pool__t *object_pool;
-
- /* factory and storage of (shared) configuration objects */
- svn_repos__config_pool_t *config_pool;
-};
-
-/* Return a combination of AUTHZ_KEY and GROUPS_KEY, allocated in POOL.
- * GROUPS_KEY may be NULL.
- */
-static svn_membuf_t *
-construct_key(svn_membuf_t *authz_key,
- svn_membuf_t *groups_key,
- apr_pool_t *pool)
-{
- svn_membuf_t *result = apr_pcalloc(pool, sizeof(*result));
- apr_size_t size;
- if (groups_key)
- {
- size = authz_key->size + groups_key->size;
- svn_membuf__create(result,size, pool);
- memcpy(result->data, authz_key->data, authz_key->size);
- memcpy((char *)result->data + authz_key->size,
- groups_key->data, groups_key->size);
- }
- else
- {
- size = authz_key->size;
- svn_membuf__create(result, size, pool);
- memcpy(result->data, authz_key->data, authz_key->size);
- }
-
- result->size = size;
- return result;
-}
-
-/* Implement svn_object_pool__getter_t on authz_object_t structures.
- */
-static void *
-getter(void *object,
- void *baton,
- apr_pool_t *pool)
-{
- return ((authz_object_t *)object)->authz;
-}
-
-/* API implementation */
-
-svn_error_t *
-svn_repos__authz_pool_create(svn_repos__authz_pool_t **authz_pool,
- svn_repos__config_pool_t *config_pool,
- svn_boolean_t thread_safe,
- apr_pool_t *pool)
-{
- svn_repos__authz_pool_t *result;
- svn_object_pool__t *object_pool;
-
- /* there is no setter as we don't need to update existing authz */
- SVN_ERR(svn_object_pool__create(&object_pool, getter, NULL, thread_safe,
- pool));
-
- result = apr_pcalloc(pool, sizeof(*result));
- result->object_pool = object_pool;
- result->config_pool = config_pool;
-
- *authz_pool = result;
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_repos__authz_pool_get(svn_authz_t **authz_p,
- svn_repos__authz_pool_t *authz_pool,
- const char *path,
- const char *groups_path,
- svn_boolean_t must_exist,
- svn_repos_t *preferred_repos,
- apr_pool_t *pool)
-{
- apr_pool_t *authz_ref_pool
- = svn_object_pool__new_wrapper_pool(authz_pool->object_pool);
- authz_object_t *authz_ref
- = apr_pcalloc(authz_ref_pool, sizeof(*authz_ref));
- svn_boolean_t have_all_keys;
-
- /* read the configurations */
- SVN_ERR(svn_repos__config_pool_get(&authz_ref->authz_cfg,
- &authz_ref->authz_key,
- authz_pool->config_pool,
- path, must_exist, TRUE,
- preferred_repos, authz_ref_pool));
- have_all_keys = authz_ref->authz_key != NULL;
-
- if (groups_path)
- {
- SVN_ERR(svn_repos__config_pool_get(&authz_ref->groups_cfg,
- &authz_ref->groups_key,
- authz_pool->config_pool,
- groups_path, must_exist, TRUE,
- preferred_repos, authz_ref_pool));
- have_all_keys &= authz_ref->groups_key != NULL;
- }
-
- /* fall back to standard implementation in case we don't have all the
- * facts (i.e. keys). */
- if (!have_all_keys)
- return svn_error_trace(svn_repos_authz_read2(authz_p, path, groups_path,
- must_exist, pool));
-
- /* all keys are known and lookup is unambigious. */
- authz_ref->key = construct_key(authz_ref->authz_key,
- authz_ref->groups_key,
- authz_ref_pool);
-
- SVN_ERR(svn_object_pool__lookup((void **)authz_p, authz_pool->object_pool,
- authz_ref->key, NULL, pool));
- if (*authz_p)
- {
- svn_pool_destroy(authz_ref_pool);
- return SVN_NO_ERROR;
- }
-
- authz_ref->authz = apr_palloc(authz_ref_pool, sizeof(*authz_ref->authz));
- authz_ref->authz->cfg = authz_ref->authz_cfg;
-
- if (groups_path)
- {
- /* Easy out: we prohibit local groups in the authz file when global
- groups are being used. */
- if (svn_config_has_section(authz_ref->authz->cfg,
- SVN_CONFIG_SECTION_GROUPS))
- return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
- "Error reading authz file '%s' with "
- "groups file '%s':"
- "Authz file cannot contain any groups "
- "when global groups are being used.",
- path, groups_path);
-
- /* We simply need to add the [Groups] section to the authz config.
- */
- svn_config__shallow_replace_section(authz_ref->authz->cfg,
- authz_ref->groups_cfg,
- SVN_CONFIG_SECTION_GROUPS);
- }
-
- /* Make sure there are no errors in the configuration. */
- SVN_ERR(svn_repos__authz_validate(authz_ref->authz, authz_ref_pool));
-
- SVN_ERR(svn_object_pool__insert((void **)authz_p, authz_pool->object_pool,
- authz_ref->key, authz_ref, NULL,
- authz_ref_pool, pool));
-
- return SVN_NO_ERROR;
-}
diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c
index 1190acc29fc5..6ce4cc6f7ffd 100644
--- a/subversion/libsvn_repos/commit.c
+++ b/subversion/libsvn_repos/commit.c
@@ -124,6 +124,7 @@ struct dir_baton
svn_revnum_t base_rev; /* the revision I'm based on */
svn_boolean_t was_copied; /* was this directory added with history? */
apr_pool_t *pool; /* my personal pool, in which I am allocated. */
+ svn_boolean_t checked_write; /* TRUE after successfull write check */
};
@@ -131,6 +132,7 @@ struct file_baton
{
struct edit_baton *edit_baton;
const char *path; /* the -absolute- path to this file in the fs */
+ svn_boolean_t checked_write; /* TRUE after successfull write check */
};
@@ -171,6 +173,30 @@ out_of_date(const char *path, svn_node_kind_t kind)
path);
}
+/* Perform an out of date check for base_rev against created rev,
+ and a sanity check of base_rev. */
+static svn_error_t *
+check_out_of_date(struct edit_baton *eb,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_revnum_t base_rev,
+ svn_revnum_t created_rev)
+{
+ if (base_rev < created_rev)
+ {
+ return out_of_date(path, kind);
+ }
+ else if (base_rev > created_rev)
+ {
+ if (base_rev > svn_fs_txn_base_revision(eb->txn))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"),
+ base_rev);
+ }
+
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *
invoke_commit_cb(svn_commit_callback2_t commit_cb,
@@ -184,15 +210,16 @@ invoke_commit_cb(svn_commit_callback2_t commit_cb,
/* const */ svn_string_t *date;
/* const */ svn_string_t *author;
svn_commit_info_t *commit_info;
+ apr_hash_t *revprops;
if (commit_cb == NULL)
return SVN_NO_ERROR;
- SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
- scratch_pool));
- SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
- SVN_PROP_REVISION_AUTHOR,
- scratch_pool));
+ SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, revision,
+ TRUE, scratch_pool, scratch_pool));
+
+ date = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
+ author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
commit_info = svn_create_commit_info(scratch_pool);
@@ -298,7 +325,6 @@ add_file_or_directory(const char *path,
const char *fs_path;
svn_fs_root_t *copy_root;
svn_node_kind_t kind;
- size_t repos_url_len;
svn_repos_authz_access_t required;
/* Copy requires recursive write access to the destination path
@@ -320,14 +346,12 @@ add_file_or_directory(const char *path,
/* For now, require that the url come from the same repository
that this commit is operating on. */
copy_path = svn_path_uri_decode(copy_path, subpool);
- repos_url_len = strlen(eb->repos_url_decoded);
- if (strncmp(copy_path, eb->repos_url_decoded, repos_url_len) != 0)
+ fs_path = svn_cstring_skip_prefix(copy_path, eb->repos_url_decoded);
+ if (!fs_path)
return svn_error_createf
(SVN_ERR_FS_GENERAL, NULL,
_("Source url '%s' is from different repository"), copy_path);
- fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
-
/* Now use the "fs_path" as an absolute path within the
repository to make the copy from. */
SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
@@ -364,14 +388,18 @@ add_file_or_directory(const char *path,
/* Build a new child baton. */
if (is_dir)
{
- *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
- SVN_INVALID_REVNUM, pool);
+ struct dir_baton *new_db = make_dir_baton(eb, pb, full_path, was_copied,
+ SVN_INVALID_REVNUM, pool);
+
+ new_db->checked_write = TRUE; /* Just created */
+ *return_baton = new_db;
}
else
{
struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
new_fb->edit_baton = eb;
new_fb->path = full_path;
+ new_fb->checked_write = TRUE; /* Just created */
*return_baton = new_fb;
}
@@ -392,9 +420,9 @@ open_root(void *edit_baton,
struct edit_baton *eb = edit_baton;
svn_revnum_t youngest;
- /* Ignore BASE_REVISION. We always build our transaction against
- HEAD. However, we will keep it in our dir baton for out of
- dateness checks. */
+ /* We always build our transaction against HEAD. However, we will
+ sanity-check BASE_REVISION and keep it in our dir baton for out
+ of dateness checks. */
SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
if (base_revision > youngest)
@@ -448,7 +476,6 @@ delete_entry(const char *path,
struct dir_baton *parent = parent_baton;
struct edit_baton *eb = parent->edit_baton;
svn_node_kind_t kind;
- svn_revnum_t cr_rev;
svn_repos_authz_access_t required = svn_authz_write;
const char *full_path;
@@ -473,14 +500,18 @@ delete_entry(const char *path,
/* Now, make sure we're deleting the node we *think* we're
deleting, else return an out-of-dateness error. */
- SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
- if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
- return svn_error_trace(out_of_date(full_path, kind));
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ svn_revnum_t cr_rev;
+
+ SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
+ SVN_ERR(check_out_of_date(eb, full_path, kind, revision, cr_rev));
+ }
/* This routine is a mindless wrapper. We call svn_fs_delete()
because that will delete files and recursively delete
directories. */
- return svn_fs_delete(eb->txn_root, full_path, pool);
+ return svn_error_trace(svn_fs_delete(eb->txn_root, full_path, pool));
}
@@ -535,18 +566,23 @@ apply_textdelta(void *file_baton,
void **handler_baton)
{
struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
- /* Check for write authorization. */
- SVN_ERR(check_authz(fb->edit_baton, fb->path,
- fb->edit_baton->txn_root,
- svn_authz_write, pool));
+ if (!fb->checked_write)
+ {
+ /* Check for write authorization. */
+ SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
+ svn_authz_write, pool));
+ fb->checked_write = TRUE;
+ }
- return svn_fs_apply_textdelta(handler, handler_baton,
- fb->edit_baton->txn_root,
- fb->path,
- base_checksum,
- NULL,
- pool);
+ return svn_error_trace(
+ svn_fs_apply_textdelta(handler, handler_baton,
+ eb->txn_root,
+ fb->path,
+ base_checksum,
+ NULL,
+ pool));
}
@@ -590,8 +626,9 @@ open_file(const char *path,
/* If the node our caller has is an older revision number than the
one in our transaction, return an out-of-dateness error. */
- if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
- return svn_error_trace(out_of_date(full_path, svn_node_file));
+ if (SVN_IS_VALID_REVNUM(base_revision))
+ SVN_ERR(check_out_of_date(eb, full_path, svn_node_file,
+ base_revision, cr_rev));
/* Build a new file baton */
new_fb = apr_pcalloc(pool, sizeof(*new_fb));
@@ -616,9 +653,13 @@ change_file_prop(void *file_baton,
struct file_baton *fb = file_baton;
struct edit_baton *eb = fb->edit_baton;
- /* Check for write authorization. */
- SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
- svn_authz_write, pool));
+ if (!fb->checked_write)
+ {
+ /* Check for write authorization. */
+ SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
+ svn_authz_write, pool));
+ fb->checked_write = TRUE;
+ }
return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
name, value, pool);
@@ -663,19 +704,24 @@ change_dir_prop(void *dir_baton,
struct edit_baton *eb = db->edit_baton;
/* Check for write authorization. */
- SVN_ERR(check_authz(eb, db->path, eb->txn_root,
- svn_authz_write, pool));
-
- if (SVN_IS_VALID_REVNUM(db->base_rev))
+ if (!db->checked_write)
{
- /* Subversion rule: propchanges can only happen on a directory
- which is up-to-date. */
- svn_revnum_t created_rev;
- SVN_ERR(svn_fs_node_created_rev(&created_rev,
- eb->txn_root, db->path, pool));
-
- if (db->base_rev < created_rev)
- return svn_error_trace(out_of_date(db->path, svn_node_dir));
+ SVN_ERR(check_authz(eb, db->path, eb->txn_root,
+ svn_authz_write, pool));
+
+ if (SVN_IS_VALID_REVNUM(db->base_rev))
+ {
+ /* Subversion rule: propchanges can only happen on a directory
+ which is up-to-date. */
+ svn_revnum_t created_rev;
+ SVN_ERR(svn_fs_node_created_rev(&created_rev,
+ eb->txn_root, db->path, pool));
+
+ SVN_ERR(check_out_of_date(eb, db->path, svn_node_dir,
+ db->base_rev, created_rev));
+ }
+
+ db->checked_write = TRUE; /* Skip on further prop changes */
}
return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
diff --git a/subversion/libsvn_repos/compat.c b/subversion/libsvn_repos/compat.c
new file mode 100644
index 000000000000..405b1c415b8d
--- /dev/null
+++ b/subversion/libsvn_repos/compat.c
@@ -0,0 +1,179 @@
+/*
+ * compat.c: compatibility shims to adapt between different API versions.
+ *
+ * ====================================================================
+ * 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 "svn_repos.h"
+#include "svn_compat.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "repos.h"
+
+#include "private/svn_repos_private.h"
+#include "private/svn_subr_private.h"
+
+
+
+/*** log4 -> log5 ***/
+
+/* Baton type to be used with both log4 compatibility callbacks.
+ * For each revision, we collect the CHANGES and then pass them
+ * on to INNER. */
+typedef struct log_entry_receiver_baton_t
+{
+ /* Pool to use to allocate CHANGES and its entries.
+ * Gets cleared after each revision. */
+ apr_pool_t *changes_pool;
+
+ /* Path changes reported so far for the current revision.
+ * Will be NULL before the first item gets added and will be reset
+ * to NULL after the INNER callback has returned. */
+ apr_hash_t *changes;
+
+ /* User-provided callback to send the log entry to. */
+ svn_log_entry_receiver_t inner;
+ void *inner_baton;
+} log_entry_receiver_baton_t;
+
+/* Return the action character (see svn_log_changed_path2_t) for KIND.
+ * Returns 0 for invalid KINDs. */
+static char
+path_change_kind_to_char(svn_fs_path_change_kind_t kind)
+{
+ const char symbol[] = "MADR";
+
+ if (kind < svn_fs_path_change_modify || kind > svn_fs_path_change_replace)
+ return 0;
+
+ return symbol[kind];
+}
+
+/* Implement svn_repos_path_change_receiver_t.
+ * Convert CHANGE and add it to the CHANGES list in *BATON. */
+static svn_error_t *
+log4_path_change_receiver(void *baton,
+ svn_repos_path_change_t *change,
+ apr_pool_t *scratch_pool)
+{
+ log_entry_receiver_baton_t *b = baton;
+ svn_log_changed_path2_t *change_copy;
+ const char *path = apr_pstrmemdup(b->changes_pool, change->path.data,
+ change->path.len);
+
+ /* Create a deep copy of the temporary CHANGE struct. */
+ change_copy = svn_log_changed_path2_create(b->changes_pool);
+ change_copy->action = path_change_kind_to_char(change->change_kind);
+
+ if (change->copyfrom_path)
+ change_copy->copyfrom_path = apr_pstrdup(b->changes_pool,
+ change->copyfrom_path);
+
+ change_copy->copyfrom_rev = change->copyfrom_rev;
+ change_copy->node_kind = change->node_kind;
+ change_copy->text_modified = change->text_mod ? svn_tristate_true
+ : svn_tristate_false;
+ change_copy->props_modified = change->prop_mod ? svn_tristate_true
+ : svn_tristate_false;
+
+ /* Auto-create the CHANGES container (happens for each first change
+ * in any revison. */
+ if (b->changes == NULL)
+ b->changes = svn_hash__make(b->changes_pool);
+
+ /* Add change to per-revision collection. */
+ apr_hash_set(b->changes, path, change->path.len, change_copy);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_log_entry_receiver_t.
+ * Combine the data gathered in BATON for this revision and send it
+ * to the user-provided log4-compatible callback. */
+static svn_error_t *
+log4_entry_receiver(void *baton,
+ svn_repos_log_entry_t *log_entry,
+ apr_pool_t *scratch_pool)
+{
+ log_entry_receiver_baton_t *b = baton;
+ svn_log_entry_t *entry = svn_log_entry_create(scratch_pool);
+
+ /* Complete the ENTRY. */
+ entry->changed_paths = b->changes;
+ entry->revision = log_entry->revision;
+ entry->revprops = log_entry->revprops;
+ entry->has_children = log_entry->has_children;
+ entry->changed_paths2 = b->changes;
+ entry->non_inheritable = log_entry->non_inheritable;
+ entry->subtractive_merge = log_entry->subtractive_merge;
+
+ /* Invoke the log4-compatible callback. */
+ SVN_ERR(b->inner(b->inner_baton, entry, scratch_pool));
+
+ /* Release per-revision data. */
+ svn_pool_clear(b->changes_pool);
+ b->changes = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__get_logs_compat(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *changes_pool = svn_pool_create(pool);
+
+ log_entry_receiver_baton_t baton;
+ baton.changes_pool = changes_pool;
+ baton.changes = NULL;
+ baton.inner = receiver;
+ baton.inner_baton = receiver_baton;
+
+ SVN_ERR(svn_repos_get_logs5(repos, paths, start, end, limit,
+ strict_node_history,
+ include_merged_revisions,
+ revprops,
+ authz_read_func, authz_read_baton,
+ discover_changed_paths
+ ? log4_path_change_receiver
+ : NULL,
+ &baton,
+ log4_entry_receiver, &baton,
+ pool));
+
+ svn_pool_destroy(changes_pool);
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_repos/config_file.c b/subversion/libsvn_repos/config_file.c
new file mode 100644
index 000000000000..918727796eb7
--- /dev/null
+++ b/subversion/libsvn_repos/config_file.c
@@ -0,0 +1,386 @@
+/*
+ * config_file.c : efficiently read config files from disk or repo
+ *
+ * ====================================================================
+ * 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 "svn_checksum.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_config_private.h"
+
+#include "config_file.h"
+
+#include "svn_private_config.h"
+
+
+
+struct config_access_t
+{
+ /* The last repository that we found the requested URL in. May be NULL. */
+ svn_repos_t *repos;
+
+ /* Owning pool of this structure and is private to this structure.
+ * All objects with the lifetime of this access object will be allocated
+ * from this pool. */
+ apr_pool_t *pool;
+};
+
+
+
+/* A stream object that gives access to a representation's content but
+ * delays accessing the repository data until the stream is first used.
+ * IOW, the stream object is cheap as long as it is not accessed.
+ */
+typedef struct presentation_stream_baton_t
+{
+ svn_fs_root_t *root;
+ const char *fs_path;
+ apr_pool_t *pool;
+ svn_stream_t *inner;
+} presentation_stream_baton_t;
+
+static svn_error_t *
+auto_open_inner_stream(presentation_stream_baton_t *b)
+{
+ if (!b->inner)
+ {
+ svn_filesize_t length;
+ svn_stream_t *stream;
+ svn_stringbuf_t *contents;
+
+ SVN_ERR(svn_fs_file_length(&length, b->root, b->fs_path, b->pool));
+ SVN_ERR(svn_fs_file_contents(&stream, b->root, b->fs_path, b->pool));
+ SVN_ERR(svn_stringbuf_from_stream(&contents, stream,
+ (apr_size_t)length, b->pool));
+ b->inner = svn_stream_from_stringbuf(contents, b->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_handler_rep(void *baton, char *buffer, apr_size_t *len)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_read2(b->inner, buffer, len));
+}
+
+static svn_error_t *
+mark_handler_rep(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_mark(b->inner, mark, pool));
+}
+
+static svn_error_t *
+seek_handler_rep(void *baton, const svn_stream_mark_t *mark)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_seek(b->inner, mark));
+}
+
+static svn_error_t *
+skip_handler_rep(void *baton, apr_size_t len)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_skip(b->inner, len));
+}
+
+static svn_error_t *
+data_available_handler_rep(void *baton, svn_boolean_t *data_available)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_data_available(b->inner, data_available));
+}
+
+static svn_error_t *
+readline_handler_rep(void *baton,
+ svn_stringbuf_t **stringbuf,
+ const char *eol,
+ svn_boolean_t *eof,
+ apr_pool_t *pool)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_readline(b->inner, stringbuf, eol, eof,
+ pool));
+}
+
+/* Return a lazy access stream for FS_PATH under ROOT, allocated in POOL. */
+static svn_stream_t *
+representation_stream(svn_fs_root_t *root,
+ const char *fs_path,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ presentation_stream_baton_t *baton;
+
+ baton = apr_pcalloc(pool, sizeof(*baton));
+ baton->root = root;
+ baton->fs_path = fs_path;
+ baton->pool = pool;
+
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_read2(stream, read_handler_rep, read_handler_rep);
+ svn_stream_set_mark(stream, mark_handler_rep);
+ svn_stream_set_seek(stream, seek_handler_rep);
+ svn_stream_set_skip(stream, skip_handler_rep);
+ svn_stream_set_data_available(stream, data_available_handler_rep);
+ svn_stream_set_readline(stream, readline_handler_rep);
+ return stream;
+}
+
+/* Handle the case of a file PATH / url pointing to anything that is either
+ * not a file or does not exist at all. The case is given by NODE_KIND.
+ *
+ * If MUST_EXIST is not set and the file does not exist at all, return a
+ * default *STREAM and *CHECKSUM allocated in the context of ACCESS, or an
+ * error otherwise.
+ */
+static svn_error_t *
+handle_missing_file(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ svn_node_kind_t node_kind)
+{
+ if (node_kind == svn_node_none && !must_exist)
+ {
+ *stream = svn_stream_empty(access->pool);
+ SVN_ERR(svn_checksum(checksum, svn_checksum_md5, "", 0, access->pool));
+ }
+ else if (node_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' is not a file", path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the in-repository file at URL, return its content checksum in
+ * *CHECKSUM and the content itself through *STREAM. Allocate those with
+ * the lifetime of ACCESS and use SCRATCH_POOL for temporaries.
+ *
+ * Error out when the file does not exist but MUST_EXIST is set.
+ */
+static svn_error_t *
+get_repos_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *url,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t node_kind;
+ const char *dirent;
+ const char *fs_path;
+ const char *repos_root_dirent;
+
+ SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, access->pool));
+
+ /* Maybe we can use the repos hint instance instead of creating a
+ * new one. */
+ if (access->repos)
+ {
+ repos_root_dirent = svn_repos_path(access->repos, scratch_pool);
+ if (!svn_dirent_is_absolute(repos_root_dirent))
+ SVN_ERR(svn_dirent_get_absolute(&repos_root_dirent,
+ repos_root_dirent,
+ scratch_pool));
+
+ if (!svn_dirent_is_ancestor(repos_root_dirent, dirent))
+ access->repos = NULL;
+ }
+
+ /* Open repos if no suitable repos is available. */
+ if (!access->repos)
+ {
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
+
+ /* Attempt to open a repository at repos_root_dirent. */
+ SVN_ERR(svn_repos_open3(&access->repos, repos_root_dirent, NULL,
+ access->pool, scratch_pool));
+ }
+
+ fs_path = &dirent[strlen(repos_root_dirent)];
+
+ /* Get the filesystem. */
+ fs = svn_repos_fs(access->repos);
+
+ /* Find HEAD and the revision root */
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, access->pool));
+
+ /* Special case: non-existent paths may be handled as "empty" contents. */
+ SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
+ if (node_kind != svn_node_file)
+ return svn_error_trace(handle_missing_file(stream, checksum, access,
+ url, must_exist, node_kind));
+
+ /* Fetch checksum and see whether we already have a matching config */
+ SVN_ERR(svn_fs_file_checksum(checksum, svn_checksum_md5, root, fs_path,
+ TRUE, access->pool));
+
+ *stream = representation_stream(root, fs_path, access->pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the file at PATH, return its content checksum in CHECKSUM and the
+ * content itself through *STREAM. Allocate those with the lifetime of
+ * ACCESS.
+ */
+static svn_error_t *
+get_file_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *contents;
+ svn_node_kind_t node_kind;
+
+ /* Special case: non-existent paths may be handled as "empty" contents. */
+ SVN_ERR(svn_io_check_path(path, &node_kind, scratch_pool));
+ if (node_kind != svn_node_file)
+ return svn_error_trace(handle_missing_file(stream, checksum, access,
+ path, must_exist, node_kind));
+
+ /* Now, we should be able to read the file. */
+ SVN_ERR(svn_stringbuf_from_file2(&contents, path, access->pool));
+
+ /* calculate MD5 over the whole file contents */
+ SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
+ contents->data, contents->len, access->pool));
+ *stream = svn_stream_from_stringbuf(contents, access->pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the configuration from path, URL or registry sub-tree PATH, return
+ * its content checksum in CHECKSUM and the content itself through *STREAM.
+ * Allocate those with the lifetime of ACCESS.
+ */
+static svn_error_t *
+get_generic_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *contents = svn_stringbuf_create_empty(access->pool);
+ svn_config_t *config;
+
+ /* Read the configuration and serialize it into CONTENTS.
+ * That copy can then be processed by the authz parser etc. */
+ SVN_ERR(svn_config_read3(&config, path, must_exist, TRUE, TRUE,
+ scratch_pool));
+ SVN_ERR(svn_config__write(svn_stream_from_stringbuf(contents, scratch_pool),
+ config, scratch_pool));
+
+ /* calculate MD5 over the whole file contents */
+ SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
+ contents->data, contents->len, access->pool));
+ *stream = svn_stream_from_stringbuf(contents, access->pool);
+
+ return SVN_NO_ERROR;
+}
+
+config_access_t *
+svn_repos__create_config_access(svn_repos_t *repos_hint,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *pool = svn_pool_create(result_pool);
+ config_access_t *result = apr_pcalloc(pool, sizeof(*result));
+
+ result->repos = repos_hint;
+ result->pool = pool;
+
+ return result;
+}
+
+void
+svn_repos__destroy_config_access(config_access_t *access)
+{
+ svn_pool_destroy(access->pool);
+}
+
+svn_error_t *
+svn_repos__get_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ /* Directly access the config data. */
+ if (svn_path_is_url(path))
+ err = get_repos_config(stream, checksum, access, path, must_exist,
+ scratch_pool);
+ else
+ err = get_file_config(stream, checksum, access, path, must_exist,
+ scratch_pool);
+
+ /* Fallback to indirect access using the generic config file parser.
+ * This is mainly used for registry support under Win32. */
+ if (err)
+ {
+ svn_error_t *err2 = get_generic_config(stream, checksum, access, path,
+ must_exist, scratch_pool);
+ if (err2)
+ {
+ svn_error_clear(err2);
+ }
+ else
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_trace(err);
+}
diff --git a/subversion/libsvn_repos/config_file.h b/subversion/libsvn_repos/config_file.h
new file mode 100644
index 000000000000..7fce18f13608
--- /dev/null
+++ b/subversion/libsvn_repos/config_file.h
@@ -0,0 +1,74 @@
+/* config_file.h : authz parsing and searching, private to libsvn_repos
+ *
+ * ====================================================================
+ * 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_REPOS_CONFIG_FILE_H
+#define SVN_REPOS_CONFIG_FILE_H
+
+#include <apr_hash.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_repos.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* An opaque struct that helps making config data access resource efficient. */
+typedef struct config_access_t config_access_t;
+
+/* Return a new config access struct allocated in RESULT_POOL.
+ * Try to access REPOS_HINT first when resolving URLs; may be NULL. */
+config_access_t *
+svn_repos__create_config_access(svn_repos_t *repos_hint,
+ apr_pool_t *result_pool);
+
+/* Release all resources allocated while using ACCESS. */
+void
+svn_repos__destroy_config_access(config_access_t *access);
+
+/* Using ACCESS as a helper object, access the textual configuration at PATH,
+ * which may be an URL or a local path. Return content's checksum in
+ * *CHECKSUM and provide its content in *STREAM.
+ *
+ * The access will fail if the item does not exist and MUST_EXIST is set.
+ * The result has the same lifetime as ACCESS. Use SCRATCH_POOL for
+ * temporary allocations.
+ */
+svn_error_t *
+svn_repos__get_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_REPOS_CONFIG_FILE_H */
diff --git a/subversion/libsvn_repos/config_pool.c b/subversion/libsvn_repos/config_pool.c
index 164bd983c02b..acf4be34061e 100644
--- a/subversion/libsvn_repos/config_pool.c
+++ b/subversion/libsvn_repos/config_pool.c
@@ -25,110 +25,17 @@
#include "svn_checksum.h"
-#include "svn_config.h"
-#include "svn_error.h"
-#include "svn_hash.h"
#include "svn_path.h"
#include "svn_pools.h"
-#include "svn_repos.h"
-#include "private/svn_dep_compat.h"
-#include "private/svn_mutex.h"
#include "private/svn_subr_private.h"
#include "private/svn_repos_private.h"
-#include "private/svn_object_pool.h"
#include "svn_private_config.h"
-
-/* Our wrapper structure for parsed svn_config_t* instances. All data in
- * CS_CFG and CI_CFG is expanded (to make it thread-safe) and considered
- * read-only.
- */
-typedef struct config_object_t
-{
- /* UUID of the configuration contents.
- * This is a SHA1 checksum of the parsed textual representation of CFG. */
- svn_checksum_t *key;
-
- /* Parsed and expanded configuration. At least one of the following
- * must not be NULL. */
-
- /* Case-sensitive config. May be NULL */
- svn_config_t *cs_cfg;
-
- /* Case-insensitive config. May be NULL */
- svn_config_t *ci_cfg;
-} config_object_t;
-
-
-/* Data structure used to short-circuit the repository access for configs
- * read via URL. After reading such a config successfully, we store key
- * repository information here and will validate it without actually opening
- * the repository.
- *
- * As this is only an optimization and may create many entries in
- * svn_repos__config_pool_t's IN_REPO_HASH_POOL index, we clean them up
- * once in a while.
- */
-typedef struct in_repo_config_t
-{
- /* URL used to open the configuration */
- const char *url;
-
- /* Path of the repository that contained URL */
- const char *repo_root;
-
- /* Head revision of that repository when last read */
- svn_revnum_t revision;
-
- /* Contents checksum of the file stored under URL@REVISION */
- svn_checksum_t *key;
-} in_repo_config_t;
-
-
-/* Core data structure extending the encapsulated OBJECT_POOL. All access
- * to it must be serialized using the OBJECT_POOL->MUTEX.
- *
- * To speed up URL@HEAD lookups, we maintain IN_REPO_CONFIGS as a secondary
- * hash index. It maps URLs as provided by the caller onto in_repo_config_t
- * instances. If that is still up-to-date, a further lookup into CONFIG
- * may yield the desired configuration without the need to actually open
- * the respective repository.
- *
- * Unused configurations that are kept in the IN_REPO_CONFIGS hash and may
- * be cleaned up when the hash is about to grow.
- */
-struct svn_repos__config_pool_t
-{
- svn_object_pool__t *object_pool;
-
- /* URL -> in_repo_config_t* mapping.
- * This is only a partial index and will get cleared regularly. */
- apr_hash_t *in_repo_configs;
-
- /* allocate the IN_REPO_CONFIGS index and in_repo_config_t here */
- apr_pool_t *in_repo_hash_pool;
-};
+#include "config_file.h"
-/* Return an automatic reference to the CFG member in CONFIG that will be
- * released when POOL gets cleaned up. The case sensitivity flag in *BATON
- * selects the desired option and section name matching mode.
- */
-static void *
-getter(void *object,
- void *baton,
- apr_pool_t *pool)
-{
- config_object_t *wrapper = object;
- svn_boolean_t *case_sensitive = baton;
- svn_config_t *config = *case_sensitive ? wrapper->cs_cfg : wrapper->ci_cfg;
-
- /* we need to duplicate the root structure as it contains temp. buffers */
- return config ? svn_config__shallow_copy(config, pool) : NULL;
-}
-
/* Return a memory buffer structure allocated in POOL and containing the
* data from CHECKSUM.
*/
@@ -146,288 +53,43 @@ checksum_as_key(svn_checksum_t *checksum,
return result;
}
-/* Copy the configuration from the wrapper in SOURCE to the wrapper in
- * *TARGET with the case sensitivity flag in *BATON selecting the config
- * to copy. This is usually done to add the missing case-(in)-sensitive
- * variant. Since we must hold all data in *TARGET from the same POOL,
- * a deep copy is required.
- */
-static svn_error_t *
-setter(void **target,
- void *source,
- void *baton,
- apr_pool_t *pool)
-{
- svn_boolean_t *case_sensitive = baton;
- config_object_t *target_cfg = *(config_object_t **)target;
- config_object_t *source_cfg = source;
-
- /* Maybe, we created a variant with different case sensitivity? */
- if (*case_sensitive && target_cfg->cs_cfg == NULL)
- {
- SVN_ERR(svn_config_dup(&target_cfg->cs_cfg, source_cfg->cs_cfg, pool));
- svn_config__set_read_only(target_cfg->cs_cfg, pool);
- }
- else if (!*case_sensitive && target_cfg->ci_cfg == NULL)
- {
- SVN_ERR(svn_config_dup(&target_cfg->ci_cfg, source_cfg->ci_cfg, pool));
- svn_config__set_read_only(target_cfg->ci_cfg, pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Set *CFG to the configuration passed in as text in CONTENTS and *KEY to
- * the corresponding object pool key. If no such configuration exists in
- * CONFIG_POOL, yet, parse CONTENTS and cache the result. CASE_SENSITIVE
- * controls option and section name matching.
+/* Set *CFG to the configuration serialized in STREAM and cache it in
+ * CONFIG_POOL under CHECKSUM. The configuration will only be parsed if
+ * we can't find it the CONFIG_POOL already.
*
* RESULT_POOL determines the lifetime of the returned reference and
* SCRATCH_POOL is being used for temporary allocations.
*/
static svn_error_t *
-auto_parse(svn_config_t **cfg,
- svn_membuf_t **key,
- svn_repos__config_pool_t *config_pool,
- svn_stringbuf_t *contents,
- svn_boolean_t case_sensitive,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- svn_checksum_t *checksum;
- config_object_t *config_object;
- apr_pool_t *cfg_pool;
-
- /* calculate SHA1 over the whole file contents */
- SVN_ERR(svn_stream_close
- (svn_stream_checksummed2
- (svn_stream_from_stringbuf(contents, scratch_pool),
- &checksum, NULL, svn_checksum_sha1, TRUE, scratch_pool)));
-
- /* return reference to suitable config object if that already exists */
- *key = checksum_as_key(checksum, result_pool);
- SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
- *key, &case_sensitive, result_pool));
- if (*cfg)
- return SVN_NO_ERROR;
-
- /* create a pool for the new config object and parse the data into it */
- cfg_pool = svn_object_pool__new_wrapper_pool(config_pool->object_pool);
-
- config_object = apr_pcalloc(cfg_pool, sizeof(*config_object));
-
- SVN_ERR(svn_config_parse(case_sensitive ? &config_object->cs_cfg
- : &config_object->ci_cfg,
- svn_stream_from_stringbuf(contents, scratch_pool),
- case_sensitive, case_sensitive, cfg_pool));
-
- /* switch config data to r/o mode to guarantee thread-safe access */
- svn_config__set_read_only(case_sensitive ? config_object->cs_cfg
- : config_object->ci_cfg,
- cfg_pool);
-
- /* add config in pool, handle loads races and return the right config */
- SVN_ERR(svn_object_pool__insert((void **)cfg, config_pool->object_pool,
- *key, config_object, &case_sensitive,
- cfg_pool, result_pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Store a URL@REVISION to CHECKSUM, REPOS_ROOT in CONFIG_POOL.
- */
-static svn_error_t *
-add_checksum(svn_repos__config_pool_t *config_pool,
- const char *url,
- const char *repos_root,
- svn_revnum_t revision,
- svn_checksum_t *checksum)
+find_config(svn_config_t **cfg,
+ svn_repos__config_pool_t *config_pool,
+ svn_stream_t *stream,
+ svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- apr_size_t path_len = strlen(url);
- apr_pool_t *pool = config_pool->in_repo_hash_pool;
- in_repo_config_t *config = apr_hash_get(config_pool->in_repo_configs,
- url, path_len);
- if (config)
- {
- /* update the existing entry */
- memcpy((void *)config->key->digest, checksum->digest,
- svn_checksum_size(checksum));
- config->revision = revision;
-
- /* duplicate the string only if necessary */
- if (strcmp(config->repo_root, repos_root))
- config->repo_root = apr_pstrdup(pool, repos_root);
- }
- else
- {
- /* insert a new entry.
- * Limit memory consumption by cyclically clearing pool and hash. */
- if (2 * svn_object_pool__count(config_pool->object_pool)
- < apr_hash_count(config_pool->in_repo_configs))
- {
- svn_pool_clear(pool);
- config_pool->in_repo_configs = svn_hash__make(pool);
- }
-
- /* construct the new entry */
- config = apr_pcalloc(pool, sizeof(*config));
- config->key = svn_checksum_dup(checksum, pool);
- config->url = apr_pstrmemdup(pool, url, path_len);
- config->repo_root = apr_pstrdup(pool, repos_root);
- config->revision = revision;
+ /* First, attempt the cache lookup. */
+ svn_membuf_t *key = checksum_as_key(checksum, scratch_pool);
+ SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool, key,
+ result_pool));
- /* add to index */
- apr_hash_set(config_pool->in_repo_configs, url, path_len, config);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Set *CFG to the configuration stored in URL@HEAD and cache it in
- * CONFIG_POOL. CASE_SENSITIVE controls
- * option and section name matching. If PREFERRED_REPOS is given,
- * use that if it also matches URL.
- *
- * RESULT_POOL determines the lifetime of the returned reference and
- * SCRATCH_POOL is being used for temporary allocations.
- */
-static svn_error_t *
-find_repos_config(svn_config_t **cfg,
- svn_membuf_t **key,
- svn_repos__config_pool_t *config_pool,
- const char *url,
- svn_boolean_t case_sensitive,
- svn_repos_t *preferred_repos,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- svn_repos_t *repos = NULL;
- svn_fs_t *fs;
- svn_fs_root_t *root;
- svn_revnum_t youngest_rev;
- svn_node_kind_t node_kind;
- const char *dirent;
- svn_stream_t *stream;
- const char *fs_path;
- const char *repos_root_dirent;
- svn_checksum_t *checksum;
- svn_stringbuf_t *contents;
-
- *cfg = NULL;
- SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, scratch_pool));
-
- /* maybe we can use the preferred repos instance instead of creating a
- * new one */
- if (preferred_repos)
- {
- repos_root_dirent = svn_repos_path(preferred_repos, scratch_pool);
- if (!svn_dirent_is_absolute(repos_root_dirent))
- SVN_ERR(svn_dirent_get_absolute(&repos_root_dirent,
- repos_root_dirent,
- scratch_pool));
-
- if (svn_dirent_is_ancestor(repos_root_dirent, dirent))
- repos = preferred_repos;
- }
-
- /* open repos if no suitable preferred repos was provided. */
- if (!repos)
- {
- /* Search for a repository in the full path. */
- repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
-
- /* Attempt to open a repository at repos_root_dirent. */
- SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL,
- scratch_pool, scratch_pool));
- }
-
- fs_path = &dirent[strlen(repos_root_dirent)];
-
- /* Get the filesystem. */
- fs = svn_repos_fs(repos);
-
- /* Find HEAD and the revision root */
- SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
- SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
-
- /* Fetch checksum and see whether we already have a matching config */
- SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root, fs_path,
- FALSE, scratch_pool));
- if (checksum)
- {
- *key = checksum_as_key(checksum, scratch_pool);
- SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
- *key, &case_sensitive, result_pool));
- }
-
- /* not parsed, yet? */
+ /* Not found? => parse and cache */
if (!*cfg)
{
- svn_filesize_t length;
+ svn_config_t *config;
- /* fetch the file contents */
- SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
- if (node_kind != svn_node_file)
- return SVN_NO_ERROR;
+ /* create a pool for the new config object and parse the data into it */
+ apr_pool_t *cfg_pool = svn_object_pool__new_item_pool(config_pool);
+ SVN_ERR(svn_config_parse(&config, stream, FALSE, FALSE, cfg_pool));
- SVN_ERR(svn_fs_file_length(&length, root, fs_path, scratch_pool));
- SVN_ERR(svn_fs_file_contents(&stream, root, fs_path, scratch_pool));
- SVN_ERR(svn_stringbuf_from_stream(&contents, stream,
- (apr_size_t)length, scratch_pool));
+ /* switch config data to r/o mode to guarantee thread-safe access */
+ svn_config__set_read_only(config, cfg_pool);
- /* handle it like ordinary file contents and cache it */
- SVN_ERR(auto_parse(cfg, key, config_pool, contents, case_sensitive,
- result_pool, scratch_pool));
+ /* add config in pool, handle loads races and return the right config */
+ SVN_ERR(svn_object_pool__insert((void **)cfg, config_pool, key,
+ config, cfg_pool, result_pool));
}
- /* store the (path,rev) -> checksum mapping as well */
- if (*cfg && checksum)
- SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
- add_checksum(config_pool, url, repos_root_dirent,
- youngest_rev, checksum));
-
- return SVN_NO_ERROR;
-}
-
-/* Given the URL, search the CONFIG_POOL for an entry that maps it URL to
- * a content checksum and is still up-to-date. If this could be found,
- * return the object's *KEY. Use POOL for allocations.
- *
- * Requires external serialization on CONFIG_POOL.
- *
- * Note that this is only the URL(+rev) -> Checksum lookup and does not
- * guarantee that there is actually a config object available for *KEY.
- */
-static svn_error_t *
-key_by_url(svn_membuf_t **key,
- svn_repos__config_pool_t *config_pool,
- const char *url,
- apr_pool_t *pool)
-{
- svn_error_t *err;
- svn_stringbuf_t *contents;
- apr_int64_t current;
-
- /* hash lookup url -> sha1 -> config */
- in_repo_config_t *config = svn_hash_gets(config_pool->in_repo_configs, url);
- *key = NULL;
- if (!config)
- return SVN_NO_ERROR;
-
- /* found *some* reference to a configuration.
- * Verify that it is still current. Will fail for BDB repos. */
- err = svn_stringbuf_from_file2(&contents,
- svn_dirent_join(config->repo_root,
- "db/current", pool),
- pool);
- if (!err)
- err = svn_cstring_atoi64(&current, contents->data);
-
- if (err)
- svn_error_clear(err);
- else if (current == config->revision)
- *key = checksum_as_key(config->key, pool);
-
return SVN_NO_ERROR;
}
@@ -438,94 +100,49 @@ svn_repos__config_pool_create(svn_repos__config_pool_t **config_pool,
svn_boolean_t thread_safe,
apr_pool_t *pool)
{
- svn_repos__config_pool_t *result;
- svn_object_pool__t *object_pool;
-
- SVN_ERR(svn_object_pool__create(&object_pool, getter, setter,
- thread_safe, pool));
-
- /* construct the config pool in our private ROOT_POOL to survive POOL
- * cleanup and to prevent threading issues with the allocator */
- result = apr_pcalloc(pool, sizeof(*result));
-
- result->object_pool = object_pool;
- result->in_repo_hash_pool = svn_pool_create(pool);
- result->in_repo_configs = svn_hash__make(result->in_repo_hash_pool);
-
- *config_pool = result;
- return SVN_NO_ERROR;
+ return svn_error_trace(svn_object_pool__create(config_pool,
+ thread_safe, pool));
}
svn_error_t *
svn_repos__config_pool_get(svn_config_t **cfg,
- svn_membuf_t **key,
svn_repos__config_pool_t *config_pool,
const char *path,
svn_boolean_t must_exist,
- svn_boolean_t case_sensitive,
svn_repos_t *preferred_repos,
apr_pool_t *pool)
{
svn_error_t *err = SVN_NO_ERROR;
apr_pool_t *scratch_pool = svn_pool_create(pool);
+ config_access_t *access = svn_repos__create_config_access(preferred_repos,
+ scratch_pool);
+ svn_stream_t *stream;
+ svn_checksum_t *checksum;
- /* make sure we always have a *KEY object */
- svn_membuf_t *local_key = NULL;
- if (key == NULL)
- key = &local_key;
- else
- *key = NULL;
-
- if (svn_path_is_url(path))
- {
- /* Read config file from repository.
- * Attempt a quick lookup first. */
- SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
- key_by_url(key, config_pool, path, pool));
- if (*key)
- {
- SVN_ERR(svn_object_pool__lookup((void **)cfg,
- config_pool->object_pool,
- *key, &case_sensitive, pool));
- if (*cfg)
- {
- svn_pool_destroy(scratch_pool);
- return SVN_NO_ERROR;
- }
- }
-
- /* Read and cache the configuration. This may fail. */
- err = find_repos_config(cfg, key, config_pool, path, case_sensitive,
- preferred_repos, pool, scratch_pool);
- if (err || !*cfg)
- {
- /* let the standard implementation handle all the difficult cases */
- svn_error_clear(err);
- err = svn_repos__retrieve_config(cfg, path, must_exist,
- case_sensitive, pool);
- }
- }
- else
+ *cfg = NULL;
+ err = svn_repos__get_config(&stream, &checksum, access, path, must_exist,
+ scratch_pool);
+ if (!err)
+ err = svn_error_quick_wrapf(find_config(cfg, config_pool, stream,
+ checksum, pool, scratch_pool),
+ "Error while parsing config file: '%s':",
+ path);
+
+ /* Let the standard implementation handle all the difficult cases.
+ * Note that for in-repo configs, there are no further special cases to
+ * check for and deal with. */
+ if (!*cfg && !svn_path_is_url(path))
{
- /* Outside of repo file. Read it. */
- svn_stringbuf_t *contents;
- err = svn_stringbuf_from_file2(&contents, path, scratch_pool);
- if (err)
- {
- /* let the standard implementation handle all the difficult cases */
- svn_error_clear(err);
- err = svn_config_read3(cfg, path, must_exist, case_sensitive,
- case_sensitive, pool);
- }
- else
- {
- /* parsing and caching will always succeed */
- err = auto_parse(cfg, key, config_pool, contents, case_sensitive,
- pool, scratch_pool);
- }
+ svn_error_clear(err);
+ err = svn_config_read3(cfg, path, must_exist, FALSE, FALSE, pool);
}
+ svn_repos__destroy_config_access(access);
svn_pool_destroy(scratch_pool);
- return err;
+ /* we need to duplicate the root structure as it contains temp. buffers */
+ if (*cfg)
+ *cfg = svn_config__shallow_copy(*cfg, pool);
+
+ return svn_error_trace(err);
}
diff --git a/subversion/libsvn_repos/delta.c b/subversion/libsvn_repos/delta.c
index 3e28c70b8d0e..9b1448d91bfd 100644
--- a/subversion/libsvn_repos/delta.c
+++ b/subversion/libsvn_repos/delta.c
@@ -266,6 +266,13 @@ svn_repos_dir_delta2(svn_fs_root_t *src_root,
_("Invalid editor anchoring; at least one of the "
"input paths is not a directory and there was no source entry"));
+ /* Don't report / compare stale revprops. However, revprop changes that
+ * are made by a 3rd party outside this delta operation, may not be
+ * detected as per our visibility guarantees. Reset the revprop caches
+ * for both roots in case they belong to different svn_fs_t instances. */
+ SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(tgt_root), pool));
+ SVN_ERR(svn_fs_refresh_revision_props(svn_fs_root_fs(src_root), pool));
+
/* Set the global target revision if one can be determined. */
if (svn_fs_is_revision_root(tgt_root))
{
@@ -491,8 +498,8 @@ delta_proplists(struct context *c,
SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
cr_str, subpool));
- SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev,
- pool));
+ SVN_ERR(svn_fs_revision_proplist2(&r_props, fs, committed_rev,
+ FALSE, pool, subpool));
/* Transmit the committed-date. */
committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
@@ -596,19 +603,6 @@ send_text_delta(struct context *c,
}
}
-svn_error_t *
-svn_repos__compare_files(svn_boolean_t *changed_p,
- svn_fs_root_t *root1,
- const char *path1,
- svn_fs_root_t *root2,
- const char *path2,
- apr_pool_t *pool)
-{
- return svn_error_trace(svn_fs_contents_different(changed_p, root1, path1,
- root2, path2, pool));
-}
-
-
/* Make the appropriate edits on FILE_BATON to change its contents and
properties from those in SOURCE_PATH to those in TARGET_PATH. */
static svn_error_t *
diff --git a/subversion/libsvn_repos/deprecated.c b/subversion/libsvn_repos/deprecated.c
index fe9d1d22761f..f502623a20ea 100644
--- a/subversion/libsvn_repos/deprecated.c
+++ b/subversion/libsvn_repos/deprecated.c
@@ -29,12 +29,17 @@
#include "svn_repos.h"
#include "svn_compat.h"
#include "svn_hash.h"
+#include "svn_path.h"
#include "svn_props.h"
+#include "svn_pools.h"
#include "svn_private_config.h"
#include "repos.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_subr_private.h"
+
@@ -503,9 +508,72 @@ svn_repos_fs_get_locks(apr_hash_t **locks,
authz_read_baton, pool));
}
+static svn_error_t *
+mergeinfo_receiver(const char *path,
+ svn_mergeinfo_t mergeinfo,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t catalog = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(catalog);
+ apr_size_t len = strlen(path);
+
+ apr_hash_set(catalog,
+ apr_pstrmemdup(result_pool, path, len),
+ len,
+ svn_mergeinfo_dup(mergeinfo, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
+ svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t rev,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_catalog_t result_catalog = svn_hash__make(pool);
+ SVN_ERR(svn_repos_fs_get_mergeinfo2(repos, paths, rev, inherit,
+ include_descendants,
+ authz_read_func, authz_read_baton,
+ mergeinfo_receiver, result_catalog,
+ pool));
+ *mergeinfo = result_catalog;
+
+ return SVN_NO_ERROR;
+}
/*** From logs.c ***/
svn_error_t *
+svn_repos_get_logs4(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos__get_logs_compat(repos, paths, start, end, limit,
+ discover_changed_paths,
+ strict_node_history,
+ include_merged_revisions, revprops,
+ authz_read_func, authz_read_baton,
+ receiver, receiver_baton, pool);
+}
+
+svn_error_t *
svn_repos_get_logs3(svn_repos_t *repos,
const apr_array_header_t *paths,
svn_revnum_t start,
@@ -731,6 +799,34 @@ repos_notify_handler(void *baton,
}
}
+svn_error_t *
+svn_repos_dump_fs3(svn_repos_t *repos,
+ svn_stream_t *stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t incremental,
+ svn_boolean_t use_deltas,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_dump_fs4(repos,
+ stream,
+ start_rev,
+ end_rev,
+ incremental,
+ use_deltas,
+ TRUE,
+ TRUE,
+ notify_func,
+ notify_baton,
+ NULL, NULL,
+ cancel_func,
+ cancel_baton,
+ pool));
+}
svn_error_t *
svn_repos_dump_fs2(svn_repos_t *repos,
@@ -806,6 +902,31 @@ svn_repos_verify_fs(svn_repos_t *repos,
/*** From load.c ***/
svn_error_t *
+svn_repos_load_fs5(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t validate_props,
+ svn_boolean_t ignore_dates,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_load_fs6(repos, dumpstream, start_rev, end_rev,
+ uuid_action, parent_dir,
+ use_post_commit_hook, use_post_commit_hook,
+ validate_props, ignore_dates, FALSE,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
svn_repos_load_fs4(svn_repos_t *repos,
svn_stream_t *dumpstream,
svn_revnum_t start_rev,
@@ -997,6 +1118,40 @@ svn_repos_load_fs(svn_repos_t *repos,
}
svn_error_t *
+svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **parser,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t use_history,
+ svn_boolean_t validate_props,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t ignore_dates,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_repos_get_fs_build_parser6(parser, parse_baton,
+ repos,
+ start_rev, end_rev,
+ use_history,
+ validate_props,
+ uuid_action,
+ parent_dir,
+ use_pre_commit_hook,
+ use_post_commit_hook,
+ ignore_dates,
+ FALSE /* normalize_props */,
+ notify_func,
+ notify_baton,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
void **parse_baton,
svn_repos_t *repos,
@@ -1118,9 +1273,30 @@ svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p,
/*** From authz.c ***/
svn_error_t *
+svn_repos_authz_read2(svn_authz_t **authz_p,
+ const char *path,
+ const char *groups_path,
+ svn_boolean_t must_exist,
+ apr_pool_t *pool)
+{
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ svn_error_t *err = svn_repos_authz_read3(authz_p, path, groups_path,
+ must_exist, NULL,
+ pool, scratch_pool);
+ svn_pool_destroy(scratch_pool);
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
svn_repos_authz_read(svn_authz_t **authz_p, const char *file,
svn_boolean_t must_exist, apr_pool_t *pool)
{
- return svn_repos__authz_read(authz_p, file, NULL, must_exist,
- FALSE, pool);
+ /* Prevent accidental new features in existing API. */
+ if (svn_path_is_url(file))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' is not a file name", file);
+
+ return svn_error_trace(svn_repos_authz_read2(authz_p, file, NULL,
+ must_exist, pool));
}
diff --git a/subversion/libsvn_repos/dump.c b/subversion/libsvn_repos/dump.c
index 189d724cd2de..960bba5816f1 100644
--- a/subversion/libsvn_repos/dump.c
+++ b/subversion/libsvn_repos/dump.c
@@ -326,7 +326,7 @@ store_delta(apr_file_t **tempfile, svn_filesize_t *len,
svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
{
svn_stream_t *temp_stream;
- apr_off_t offset = 0;
+ apr_off_t offset;
svn_txdelta_stream_t *delta_stream;
svn_txdelta_window_handler_t wh;
void *whb;
@@ -346,7 +346,7 @@ store_delta(apr_file_t **tempfile, svn_filesize_t *len,
SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
/* Get the length of the temporary file and rewind it. */
- SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, *tempfile, pool));
*len = offset;
offset = 0;
return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
@@ -1922,50 +1922,90 @@ get_dump_editor(const svn_delta_editor_t **editor,
/* Helper for svn_repos_dump_fs.
- Write a revision record of REV in FS to writable STREAM, using POOL.
+ Write a revision record of REV in REPOS to writable STREAM, using POOL.
+ Dump revision properties as well if INCLUDE_REVPROPS has been set.
+ AUTHZ_FUNC and AUTHZ_BATON are passed directly to the repos layer.
*/
static svn_error_t *
write_revision_record(svn_stream_t *stream,
- svn_fs_t *fs,
+ svn_repos_t *repos,
svn_revnum_t rev,
+ svn_boolean_t include_revprops,
+ svn_repos_authz_func_t authz_func,
+ void *authz_baton,
apr_pool_t *pool)
{
apr_hash_t *props;
apr_time_t timetemp;
svn_string_t *datevalue;
- SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
-
- /* Run revision date properties through the time conversion to
- canonicalize them. */
- /* ### Remove this when it is no longer needed for sure. */
- datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
- if (datevalue)
+ if (include_revprops)
{
- SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
- datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
- pool);
- svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
+ SVN_ERR(svn_repos_fs_revision_proplist(&props, repos, rev,
+ authz_func, authz_baton, pool));
+
+ /* Run revision date properties through the time conversion to
+ canonicalize them. */
+ /* ### Remove this when it is no longer needed for sure. */
+ datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
+ if (datevalue)
+ {
+ SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
+ datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
+ pool);
+ svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
+ }
+ }
+ else
+ {
+ /* Although we won't use it, we still need this container for the
+ call below. */
+ props = apr_hash_make(pool);
}
SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props,
- TRUE /*props_section_always*/,
+ include_revprops,
pool));
return SVN_NO_ERROR;
}
+/* Baton for dump_filter_authz_func(). */
+typedef struct dump_filter_baton_t
+{
+ svn_repos_dump_filter_func_t filter_func;
+ void *filter_baton;
+} dump_filter_baton_t;
+
+/* Implements svn_repos_authz_func_t. */
+static svn_error_t *
+dump_filter_authz_func(svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool)
+{
+ dump_filter_baton_t *b = baton;
+
+ return svn_error_trace(b->filter_func(allowed, root, path, b->filter_baton,
+ pool));
+}
+
/* The main dumper. */
svn_error_t *
-svn_repos_dump_fs3(svn_repos_t *repos,
+svn_repos_dump_fs4(svn_repos_t *repos,
svn_stream_t *stream,
svn_revnum_t start_rev,
svn_revnum_t end_rev,
svn_boolean_t incremental,
svn_boolean_t use_deltas,
+ svn_boolean_t include_revprops,
+ svn_boolean_t include_changes,
svn_repos_notify_func_t notify_func,
void *notify_baton,
+ svn_repos_dump_filter_func_t filter_func,
+ void *filter_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -1974,13 +2014,19 @@ svn_repos_dump_fs3(svn_repos_t *repos,
void *dump_edit_baton = NULL;
svn_revnum_t rev;
svn_fs_t *fs = svn_repos_fs(repos);
- apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(pool);
svn_revnum_t youngest;
const char *uuid;
int version;
svn_boolean_t found_old_reference = FALSE;
svn_boolean_t found_old_mergeinfo = FALSE;
svn_repos_notify_t *notify;
+ svn_repos_authz_func_t authz_func;
+ dump_filter_baton_t authz_baton = {0};
+
+ /* Make sure we catch up on the latest revprop changes. This is the only
+ * time we will refresh the revprop data in this query. */
+ SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
/* Determine the current youngest revision of the filesystem. */
SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
@@ -2005,6 +2051,20 @@ svn_repos_dump_fs3(svn_repos_t *repos,
"(youngest revision is %ld)"),
end_rev, youngest);
+ /* We use read authz callback to implement dump filtering. If there is no
+ * read access for some node, it will be excluded from dump as well as
+ * references to it (e.g. copy source). */
+ if (filter_func)
+ {
+ authz_func = dump_filter_authz_func;
+ authz_baton.filter_func = filter_func;
+ authz_baton.filter_baton = filter_baton;
+ }
+ else
+ {
+ authz_func = NULL;
+ }
+
/* Write out the UUID. */
SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
@@ -2033,18 +2093,20 @@ svn_repos_dump_fs3(svn_repos_t *repos,
svn_fs_root_t *to_root;
svn_boolean_t use_deltas_for_rev;
- svn_pool_clear(subpool);
+ svn_pool_clear(iterpool);
/* Check for cancellation. */
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
/* Write the revision record. */
- SVN_ERR(write_revision_record(stream, fs, rev, subpool));
+ SVN_ERR(write_revision_record(stream, repos, rev, include_revprops,
+ authz_func, &authz_baton, iterpool));
/* When dumping revision 0, we just write out the revision record.
- The parser might want to use its properties. */
- if (rev == 0)
+ The parser might want to use its properties.
+ If we don't want revision changes at all, skip in any case. */
+ if (rev == 0 || !include_changes)
goto loop_end;
/* Fetch the editor which dumps nodes to a file. Regardless of
@@ -2056,10 +2118,10 @@ svn_repos_dump_fs3(svn_repos_t *repos,
&found_old_mergeinfo, NULL,
notify_func, notify_baton,
start_rev, use_deltas_for_rev, FALSE, FALSE,
- subpool));
+ iterpool));
/* Drive the editor in one way or another. */
- SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool));
+ SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
/* If this is the first revision of a non-incremental dump,
we're in for a full tree dump. Otherwise, we want to simply
@@ -2068,35 +2130,34 @@ svn_repos_dump_fs3(svn_repos_t *repos,
{
/* Compare against revision 0, so everything appears to be added. */
svn_fs_root_t *from_root;
- SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool));
+ SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, iterpool));
SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
to_root, "",
dump_editor, dump_edit_baton,
- NULL,
- NULL,
+ authz_func, &authz_baton,
FALSE, /* don't send text-deltas */
svn_depth_infinity,
FALSE, /* don't send entry props */
FALSE, /* don't ignore ancestry */
- subpool));
+ iterpool));
}
else
{
/* The normal case: compare consecutive revs. */
SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
dump_editor, dump_edit_baton,
- NULL, NULL, subpool));
+ authz_func, &authz_baton, iterpool));
/* While our editor close_edit implementation is a no-op, we still
do this for completeness. */
- SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
+ SVN_ERR(dump_editor->close_edit(dump_edit_baton, iterpool));
}
loop_end:
if (notify_func)
{
notify->revision = rev;
- notify_func(notify_baton, notify, subpool);
+ notify_func(notify_baton, notify, iterpool);
}
}
@@ -2107,12 +2168,12 @@ svn_repos_dump_fs3(svn_repos_t *repos,
warning, since the inline warnings already issued might easily be
missed. */
- notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
- notify_func(notify_baton, notify, subpool);
+ notify = svn_repos_notify_create(svn_repos_notify_dump_end, iterpool);
+ notify_func(notify_baton, notify, iterpool);
if (found_old_reference)
{
- notify_warning(subpool, notify_func, notify_baton,
+ notify_warning(iterpool, notify_func, notify_baton,
svn_repos_notify_warning_found_old_reference,
_("The range of revisions dumped "
"contained references to "
@@ -2124,7 +2185,7 @@ svn_repos_dump_fs3(svn_repos_t *repos,
in dumped mergeinfo. */
if (found_old_mergeinfo)
{
- notify_warning(subpool, notify_func, notify_baton,
+ notify_warning(iterpool, notify_func, notify_baton,
svn_repos_notify_warning_found_old_mergeinfo,
_("The range of revisions dumped "
"contained mergeinfo "
@@ -2133,7 +2194,7 @@ svn_repos_dump_fs3(svn_repos_t *repos,
}
}
- svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
@@ -2314,7 +2375,8 @@ verify_one_revision(svn_fs_t *fs,
do this for completeness. */
SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool));
- SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool));
+ SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, scratch_pool,
+ scratch_pool));
return SVN_NO_ERROR;
}
@@ -2394,6 +2456,10 @@ svn_repos_verify_fs3(svn_repos_t *repos,
struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL;
svn_error_t *err;
+ /* Make sure we catch up on the latest revprop changes. This is the only
+ * time we will refresh the revprop data in this query. */
+ SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
+
/* Determine the current youngest revision of the filesystem. */
SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
diff --git a/subversion/libsvn_repos/fs-wrap.c b/subversion/libsvn_repos/fs-wrap.c
index b46cda650fc2..f895d5aa0c22 100644
--- a/subversion/libsvn_repos/fs-wrap.c
+++ b/subversion/libsvn_repos/fs-wrap.c
@@ -33,6 +33,7 @@
#include "svn_repos.h"
#include "svn_time.h"
#include "svn_sorts.h"
+#include "svn_subst.h"
#include "repos.h"
#include "svn_private_config.h"
#include "private/svn_repos_private.h"
@@ -58,6 +59,8 @@ svn_repos_fs_commit_txn(const char **conflict_p,
apr_hash_t *hooks_env;
*new_rev = SVN_INVALID_REVNUM;
+ if (conflict_p)
+ *conflict_p = NULL;
/* Parse the hooks-env file (if any). */
SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
@@ -233,10 +236,13 @@ svn_repos__validate_prop(const char *name,
* carriage return characters ('\r'). */
if (strchr(value->data, '\r') != NULL)
{
- return svn_error_createf
- (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
+ svn_error_t *err = svn_error_createf
+ (SVN_ERR_BAD_PROPERTY_VALUE_EOL, NULL,
_("Cannot accept non-LF line endings in '%s' property"),
name);
+
+ return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE, err,
+ _("Invalid property value"));
}
}
@@ -257,6 +263,34 @@ svn_repos__validate_prop(const char *name,
}
+svn_error_t *
+svn_repos__normalize_prop(const svn_string_t **result_p,
+ svn_boolean_t *normalized_p,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (svn_prop_needs_translation(name) && value)
+ {
+ svn_string_t *new_value;
+
+ SVN_ERR(svn_subst_translate_string2(&new_value, NULL, normalized_p,
+ value, "UTF-8", TRUE,
+ result_pool, scratch_pool));
+ *result_p = new_value;
+ }
+ else
+ {
+ *result_p = svn_string_dup(value, result_pool);
+ if (normalized_p)
+ *normalized_p = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
/* Verify the mergeinfo property value VALUE and return an error if it
* is invalid. The PATH on which that property is set is used for error
* messages only. Use SCRATCH_POOL for temporary allocations. */
@@ -378,7 +412,8 @@ svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
* to the hooks to be accurate. */
svn_string_t *old_value2;
- SVN_ERR(svn_fs_revision_prop(&old_value2, repos->fs, rev, name, pool));
+ SVN_ERR(svn_fs_revision_prop2(&old_value2, repos->fs, rev, name,
+ TRUE, pool, pool));
old_value = old_value2;
}
@@ -448,12 +483,13 @@ svn_repos_fs_revision_prop(svn_string_t **value_p,
*value_p = NULL;
else
- SVN_ERR(svn_fs_revision_prop(value_p, repos->fs,
- rev, propname, pool));
+ SVN_ERR(svn_fs_revision_prop2(value_p, repos->fs,
+ rev, propname, TRUE, pool, pool));
}
else /* wholly readable revision */
{
- SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, rev, propname, pool));
+ SVN_ERR(svn_fs_revision_prop2(value_p, repos->fs, rev, propname, TRUE,
+ pool, pool));
}
return SVN_NO_ERROR;
@@ -486,7 +522,8 @@ svn_repos_fs_revision_proplist(apr_hash_t **table_p,
svn_string_t *value;
/* Produce two property hashtables, both in POOL. */
- SVN_ERR(svn_fs_revision_proplist(&tmphash, repos->fs, rev, pool));
+ SVN_ERR(svn_fs_revision_proplist2(&tmphash, repos->fs, rev, TRUE,
+ pool, pool));
*table_p = apr_hash_make(pool);
/* If they exist, we only copy svn:author and svn:date into the
@@ -501,7 +538,8 @@ svn_repos_fs_revision_proplist(apr_hash_t **table_p,
}
else /* wholly readable revision */
{
- SVN_ERR(svn_fs_revision_proplist(table_p, repos->fs, rev, pool));
+ SVN_ERR(svn_fs_revision_proplist2(table_p, repos->fs, rev, TRUE,
+ pool, pool));
}
return SVN_NO_ERROR;
@@ -895,25 +933,26 @@ svn_repos_fs_get_locks2(apr_hash_t **locks,
svn_error_t *
-svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
- svn_repos_t *repos,
- const apr_array_header_t *paths,
- svn_revnum_t rev,
- svn_mergeinfo_inheritance_t inherit,
- svn_boolean_t include_descendants,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
- apr_pool_t *pool)
+svn_repos_fs_get_mergeinfo2(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t rev,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_repos_mergeinfo_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *scratch_pool)
{
/* Here we cast away 'const', but won't try to write through this pointer
* without first allocating a new array. */
apr_array_header_t *readable_paths = (apr_array_header_t *) paths;
svn_fs_root_t *root;
- apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
if (!SVN_IS_VALID_REVNUM(rev))
- SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, pool));
- SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, pool));
+ SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, scratch_pool));
/* Filter out unreadable paths before divining merge tracking info. */
if (authz_read_func)
@@ -934,7 +973,7 @@ svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
/* Requested paths differ from readable paths. Fork
list of readable paths from requested paths. */
int j;
- readable_paths = apr_array_make(pool, paths->nelts - 1,
+ readable_paths = apr_array_make(scratch_pool, paths->nelts - 1,
sizeof(char *));
for (j = 0; j < i; j++)
{
@@ -951,10 +990,10 @@ svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
the change itself. */
/* ### TODO(reint): ... but how about descendant merged-to paths? */
if (readable_paths->nelts > 0)
- SVN_ERR(svn_fs_get_mergeinfo2(mergeinfo, root, readable_paths, inherit,
- include_descendants, TRUE, pool, pool));
- else
- *mergeinfo = apr_hash_make(pool);
+ SVN_ERR(svn_fs_get_mergeinfo3(root, readable_paths, inherit,
+ include_descendants, TRUE,
+ receiver, receiver_baton,
+ scratch_pool));
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
@@ -975,15 +1014,18 @@ pack_notify_func(void *baton,
{
struct pack_notify_baton *pnb = baton;
svn_repos_notify_t *notify;
+ svn_repos_notify_action_t repos_action;
/* Simple conversion works for these values. */
SVN_ERR_ASSERT(pack_action >= svn_fs_pack_notify_start
- && pack_action <= svn_fs_pack_notify_end_revprop);
+ && pack_action <= svn_fs_pack_notify_noop);
+
+ repos_action = pack_action == svn_fs_pack_notify_noop
+ ? svn_repos_notify_pack_noop
+ : pack_action + svn_repos_notify_pack_shard_start
+ - svn_fs_pack_notify_start;
- notify = svn_repos_notify_create(pack_action
- + svn_repos_notify_pack_shard_start
- - svn_fs_pack_notify_start,
- pool);
+ notify = svn_repos_notify_create(repos_action, pool);
notify->shard = shard;
pnb->notify_func(pnb->notify_baton, notify, pool);
diff --git a/subversion/libsvn_repos/hooks.c b/subversion/libsvn_repos/hooks.c
index a4cc2491416f..8d17a82c31bd 100644
--- a/subversion/libsvn_repos/hooks.c
+++ b/subversion/libsvn_repos/hooks.c
@@ -476,11 +476,8 @@ svn_repos__hooks_start_commit(svn_repos_t *repos,
if (capabilities)
{
- capabilities_string = svn_cstring_join(capabilities, ":", pool);
-
- /* Get rid of that annoying final colon. */
- if (capabilities_string[0])
- capabilities_string[strlen(capabilities_string) - 1] = '\0';
+ capabilities_string = svn_cstring_join2(capabilities, ":",
+ FALSE, pool);
}
else
{
@@ -799,8 +796,8 @@ svn_repos__hooks_post_lock(svn_repos_t *repos,
{
const char *args[5];
apr_file_t *stdin_handle = NULL;
- svn_string_t *paths_str = svn_string_create(svn_cstring_join
- (paths, "\n", pool),
+ svn_string_t *paths_str = svn_string_create(svn_cstring_join2
+ (paths, "\n", TRUE, pool),
pool);
SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
@@ -875,8 +872,8 @@ svn_repos__hooks_post_unlock(svn_repos_t *repos,
{
const char *args[5];
apr_file_t *stdin_handle = NULL;
- svn_string_t *paths_str = svn_string_create(svn_cstring_join
- (paths, "\n", pool),
+ svn_string_t *paths_str = svn_string_create(svn_cstring_join2
+ (paths, "\n", TRUE, pool),
pool);
SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
diff --git a/subversion/libsvn_repos/list.c b/subversion/libsvn_repos/list.c
new file mode 100644
index 000000000000..ef8ac32b1b38
--- /dev/null
+++ b/subversion/libsvn_repos/list.c
@@ -0,0 +1,340 @@
+/* list.c : listing repository contents
+ *
+ * ====================================================================
+ * 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 <apr_pools.h>
+#include <apr_fnmatch.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_time.h"
+
+#include "private/svn_repos_private.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_utf_private.h"
+#include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
+
+#include "repos.h"
+
+
+
+/* Utility function. Given DIRENT->KIND, set all other elements of *DIRENT
+ * with the values retrieved for PATH under ROOT. Allocate them in POOL.
+ */
+static svn_error_t *
+fill_dirent(svn_dirent_t *dirent,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ const char *datestring;
+
+ if (dirent->kind == svn_node_file)
+ SVN_ERR(svn_fs_file_length(&(dirent->size), root, path, scratch_pool));
+ else
+ dirent->size = SVN_INVALID_FILESIZE;
+
+ SVN_ERR(svn_fs_node_has_props(&dirent->has_props, root, path,
+ scratch_pool));
+
+ SVN_ERR(svn_repos_get_committed_info(&(dirent->created_rev),
+ &datestring,
+ &(dirent->last_author),
+ root, path, scratch_pool));
+ if (datestring)
+ SVN_ERR(svn_time_from_cstring(&(dirent->time), datestring,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_stat(svn_dirent_t **dirent,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_dirent_t *ent;
+
+ SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
+
+ if (kind == svn_node_none)
+ {
+ *dirent = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ ent = svn_dirent_create(pool);
+ ent->kind = kind;
+
+ SVN_ERR(fill_dirent(ent, root, path, pool));
+
+ *dirent = ent;
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE of DIRNAME matches any of the const char * in PATTERNS.
+ * Note that any DIRNAME will match if PATTERNS is empty.
+ * Use SCRATCH_BUFFER for temporary string contents. */
+static svn_boolean_t
+matches_any(const char *dirname,
+ const apr_array_header_t *patterns,
+ svn_membuf_t *scratch_buffer)
+{
+ return patterns
+ ? svn_utf__fuzzy_glob_match(dirname, patterns, scratch_buffer)
+ : TRUE;
+}
+
+/* Utility to prevent code duplication.
+ *
+ * Construct a svn_dirent_t for PATH of type KIND under ROOT and, if
+ * PATH_INFO_ONLY is not set, fill it. Call RECEIVER with the result
+ * and RECEIVER_BATON.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+report_dirent(svn_fs_root_t *root,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_boolean_t path_info_only,
+ svn_repos_dirent_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_dirent_t dirent = { 0 };
+
+ /* Fetch the details to report - if required. */
+ dirent.kind = kind;
+ if (!path_info_only)
+ SVN_ERR(fill_dirent(&dirent, root, path, scratch_pool));
+
+ /* Report the entry. */
+ SVN_ERR(receiver(path, &dirent, receiver_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Utility data struct, used to attach a filter result flag to a dirent. */
+typedef struct filtered_dirent_t
+{
+ /* Actual dirent, never NULL. */
+ svn_fs_dirent_t *dirent;
+
+ /* DIRENT passed the filter. */
+ svn_boolean_t is_match;
+} filtered_dirent_t;
+
+/* Implement a standard sort function for filtered_dirent_t *, sorting them
+ * by entry name. */
+static int
+compare_filtered_dirent(const void *lhs,
+ const void *rhs)
+{
+ const filtered_dirent_t *lhs_dirent = (const filtered_dirent_t *)lhs;
+ const filtered_dirent_t *rhs_dirent = (const filtered_dirent_t *)rhs;
+
+ return strcmp(lhs_dirent->dirent->name, rhs_dirent->dirent->name);
+}
+
+/* Core of svn_repos_list with the same parameter list.
+ *
+ * However, DEPTH is not svn_depth_empty and PATH has already been reported.
+ * Therefore, we can call this recursively.
+ *
+ * Uses SCRATCH_BUFFER for temporary string contents.
+ */
+static svn_error_t *
+do_list(svn_fs_root_t *root,
+ const char *path,
+ const apr_array_header_t *patterns,
+ svn_depth_t depth,
+ svn_boolean_t path_info_only,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_repos_dirent_receiver_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_membuf_t *scratch_buffer,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_array_header_t *sorted;
+ int i;
+
+ /* Fetch all directory entries, filter and sort them.
+ *
+ * Performance trade-off:
+ * Constructing a full path vs. faster sort due to authz filtering.
+ * We filter according to DEPTH and PATTERNS only because constructing
+ * the full path required for authz is somewhat expensive and we don't
+ * want to do this twice while authz will rarely filter paths out.
+ */
+ SVN_ERR(svn_fs_dir_entries(&entries, root, path, scratch_pool));
+ sorted = apr_array_make(scratch_pool, apr_hash_count(entries),
+ sizeof(filtered_dirent_t));
+ for (hi = apr_hash_first(scratch_pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ filtered_dirent_t filtered;
+ svn_pool_clear(iterpool);
+
+ filtered.dirent = apr_hash_this_val(hi);
+
+ /* Skip directories if we want to report files only. */
+ if (filtered.dirent->kind == svn_node_dir && depth == svn_depth_files)
+ continue;
+
+ /* We can skip files that don't match any of the search patterns. */
+ filtered.is_match = matches_any(filtered.dirent->name, patterns,
+ scratch_buffer);
+ if (!filtered.is_match && filtered.dirent->kind == svn_node_file)
+ continue;
+
+ APR_ARRAY_PUSH(sorted, filtered_dirent_t) = filtered;
+ }
+
+ svn_sort__array(sorted, compare_filtered_dirent);
+
+ /* Iterate over all remaining directory entries and report them.
+ * Recurse into sub-directories if requested. */
+ for (i = 0; i < sorted->nelts; ++i)
+ {
+ const char *sub_path;
+ filtered_dirent_t *filtered;
+ svn_fs_dirent_t *dirent;
+
+ svn_pool_clear(iterpool);
+
+ filtered = &APR_ARRAY_IDX(sorted, i, filtered_dirent_t);
+ dirent = filtered->dirent;
+
+ /* Skip paths that we don't have access to? */
+ sub_path = svn_dirent_join(path, dirent->name, iterpool);
+ if (authz_read_func)
+ {
+ svn_boolean_t has_access;
+ SVN_ERR(authz_read_func(&has_access, root, sub_path,
+ authz_read_baton, iterpool));
+ if (!has_access)
+ continue;
+ }
+
+ /* Report entry, if it passed the filter. */
+ if (filtered->is_match)
+ SVN_ERR(report_dirent(root, sub_path, dirent->kind, path_info_only,
+ receiver, receiver_baton, iterpool));
+
+ /* Check for cancellation before recursing down. This should be
+ * slightly more responsive for deep trees. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Recurse on directories. */
+ if (depth == svn_depth_infinity && dirent->kind == svn_node_dir)
+ SVN_ERR(do_list(root, sub_path, patterns, svn_depth_infinity,
+ path_info_only, authz_read_func, authz_read_baton,
+ receiver, receiver_baton, cancel_func,
+ cancel_baton, scratch_buffer, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_list(svn_fs_root_t *root,
+ const char *path,
+ const apr_array_header_t *patterns,
+ svn_depth_t depth,
+ svn_boolean_t path_info_only,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_repos_dirent_receiver_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_membuf_t scratch_buffer;
+
+ /* Parameter check. */
+ svn_node_kind_t kind;
+ if (depth < svn_depth_empty)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ "Invalid depth '%d' in svn_repos_list", depth);
+
+ /* Do we have access this sub-tree? */
+ if (authz_read_func)
+ {
+ svn_boolean_t has_access;
+ SVN_ERR(authz_read_func(&has_access, root, path, authz_read_baton,
+ scratch_pool));
+ if (!has_access)
+ return SVN_NO_ERROR;
+ }
+
+ /* Does the sub-tree even exist?
+ *
+ * Note that we must do this after the authz check to not indirectly
+ * confirm the existence of PATH. */
+ SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
+ if (kind == svn_node_file)
+ {
+ /* There is no recursion on files. */
+ depth = svn_depth_empty;
+ }
+ else if (kind != svn_node_dir)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' not found"), path);
+ }
+
+ /* Special case: Empty pattern list.
+ * We don't want the server to waste time here. */
+ if (patterns && patterns->nelts == 0)
+ return SVN_NO_ERROR;
+
+ /* We need a scratch buffer for temporary string data.
+ * Create one with a reasonable initial size. */
+ svn_membuf__create(&scratch_buffer, 256, scratch_pool);
+
+ /* Actually report PATH, if it passes the filters. */
+ if (matches_any(svn_dirent_dirname(path, scratch_pool), patterns,
+ &scratch_buffer))
+ SVN_ERR(report_dirent(root, path, kind, path_info_only,
+ receiver, receiver_baton, scratch_pool));
+
+ /* Report directory contents if requested. */
+ if (depth > svn_depth_empty)
+ SVN_ERR(do_list(root, path, patterns, depth,
+ path_info_only, authz_read_func, authz_read_baton,
+ receiver, receiver_baton, cancel_func, cancel_baton,
+ &scratch_buffer, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_repos/load-fs-vtable.c b/subversion/libsvn_repos/load-fs-vtable.c
index ca3a5cd34d17..f6c6bf660b12 100644
--- a/subversion/libsvn_repos/load-fs-vtable.c
+++ b/subversion/libsvn_repos/load-fs-vtable.c
@@ -55,6 +55,7 @@ struct parse_baton
svn_boolean_t use_history;
svn_boolean_t validate_props;
svn_boolean_t ignore_dates;
+ svn_boolean_t normalize_props;
svn_boolean_t use_pre_commit_hook;
svn_boolean_t use_post_commit_hook;
enum svn_repos_load_uuid uuid_action;
@@ -163,8 +164,12 @@ change_rev_prop(svn_repos_t *repos,
const char *name,
const svn_string_t *value,
svn_boolean_t validate_props,
+ svn_boolean_t normalize_props,
apr_pool_t *pool)
{
+ if (normalize_props)
+ SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
+
if (validate_props)
return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
NULL, value, FALSE, FALSE,
@@ -624,14 +629,6 @@ maybe_add_with_history(struct node_baton *nb,
}
static svn_error_t *
-magic_header_record(int version,
- void *parse_baton,
- apr_pool_t *pool)
-{
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
uuid_record(const char *uuid,
void *parse_baton,
apr_pool_t *pool)
@@ -1022,7 +1019,8 @@ close_revision(void *baton)
apr_array_header_t *diff;
int i;
- SVN_ERR(svn_fs_revision_proplist(&orig_props, pb->fs, 0, rb->pool));
+ SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, 0, TRUE,
+ rb->pool, rb->pool));
new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
@@ -1031,7 +1029,8 @@ close_revision(void *baton)
const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value,
- pb->validate_props, rb->pool));
+ pb->validate_props, pb->normalize_props,
+ rb->pool));
}
}
@@ -1049,6 +1048,23 @@ close_revision(void *baton)
prop->value = NULL;
}
+ if (rb->pb->normalize_props)
+ {
+ apr_pool_t *iterpool;
+ int i;
+
+ iterpool = svn_pool_create(rb->pool);
+ for (i = 0; i < rb->revprops->nelts; i++)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(rb->revprops, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_repos__normalize_prop(&prop->value, NULL, prop->name,
+ prop->value, rb->pool, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
/* Apply revision property changes. */
if (rb->pb->validate_props)
SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
@@ -1165,7 +1181,7 @@ close_revision(void *baton)
svn_error_t *
-svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **callbacks,
+svn_repos_get_fs_build_parser6(const svn_repos_parse_fns3_t **callbacks,
void **parse_baton,
svn_repos_t *repos,
svn_revnum_t start_rev,
@@ -1177,6 +1193,7 @@ svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **callbacks,
svn_boolean_t use_pre_commit_hook,
svn_boolean_t use_post_commit_hook,
svn_boolean_t ignore_dates,
+ svn_boolean_t normalize_props,
svn_repos_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
@@ -1194,7 +1211,7 @@ svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **callbacks,
if (SVN_IS_VALID_REVNUM(start_rev))
SVN_ERR_ASSERT(start_rev <= end_rev);
- parser->magic_header_record = magic_header_record;
+ parser->magic_header_record = NULL;
parser->uuid_record = uuid_record;
parser->new_revision_record = new_revision_record;
parser->new_node_record = new_node_record;
@@ -1225,6 +1242,7 @@ svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **callbacks,
pb->use_pre_commit_hook = use_pre_commit_hook;
pb->use_post_commit_hook = use_post_commit_hook;
pb->ignore_dates = ignore_dates;
+ pb->normalize_props = normalize_props;
*callbacks = parser;
*parse_baton = pb;
@@ -1233,7 +1251,7 @@ svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **callbacks,
svn_error_t *
-svn_repos_load_fs5(svn_repos_t *repos,
+svn_repos_load_fs6(svn_repos_t *repos,
svn_stream_t *dumpstream,
svn_revnum_t start_rev,
svn_revnum_t end_rev,
@@ -1243,6 +1261,7 @@ svn_repos_load_fs5(svn_repos_t *repos,
svn_boolean_t use_post_commit_hook,
svn_boolean_t validate_props,
svn_boolean_t ignore_dates,
+ svn_boolean_t normalize_props,
svn_repos_notify_func_t notify_func,
void *notify_baton,
svn_cancel_func_t cancel_func,
@@ -1254,7 +1273,7 @@ svn_repos_load_fs5(svn_repos_t *repos,
/* This is really simple. */
- SVN_ERR(svn_repos_get_fs_build_parser5(&parser, &parse_baton,
+ SVN_ERR(svn_repos_get_fs_build_parser6(&parser, &parse_baton,
repos,
start_rev, end_rev,
TRUE, /* look for copyfrom revs */
@@ -1264,6 +1283,7 @@ svn_repos_load_fs5(svn_repos_t *repos,
use_pre_commit_hook,
use_post_commit_hook,
ignore_dates,
+ normalize_props,
notify_func,
notify_baton,
pool));
@@ -1271,3 +1291,226 @@ svn_repos_load_fs5(svn_repos_t *repos,
return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
cancel_func, cancel_baton, pool);
}
+
+/*----------------------------------------------------------------------*/
+
+/** The same functionality for revprops only **/
+
+/* Implement svn_repos_parse_fns3_t.new_revision_record.
+ *
+ * Because the revision is supposed to already exist, we don't need to
+ * start transactions etc. */
+static svn_error_t *
+revprops_new_revision_record(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ struct parse_baton *pb = parse_baton;
+ struct revision_baton *rb;
+
+ rb = make_revision_baton(headers, pb, pool);
+
+ /* If we're skipping this revision, try to notify someone. */
+ if (rb->skipped && pb->notify_func)
+ {
+ /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_load_skipped_rev,
+ pb->notify_pool);
+
+ notify->old_revision = rb->rev;
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
+ }
+
+ /* If we're parsing revision 0, only the revision props are (possibly)
+ interesting to us: when loading the stream into an empty
+ filesystem, then we want new filesystem's revision 0 to have the
+ same props. Otherwise, we just ignore revision 0 in the stream. */
+
+ *revision_baton = rb;
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_repos_parse_fns3_t.close_revision.
+ *
+ * Simply set the revprops we previously parsed and send notifications.
+ * This is the place where we will detect missing revisions. */
+static svn_error_t *
+revprops_close_revision(void *baton)
+{
+ struct revision_baton *rb = baton;
+ struct parse_baton *pb = rb->pb;
+ apr_hash_t *orig_props;
+ apr_hash_t *new_props;
+ apr_array_header_t *diff;
+ int i;
+
+ /* If we're skipping this revision we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
+ /* If the dumpstream doesn't have an 'svn:date' property and we
+ aren't ignoring the dates in the dumpstream altogether, remove
+ any 'svn:date' revision property that was set by FS layer when
+ the TXN was created. */
+ if (! (pb->ignore_dates || rb->datestamp))
+ {
+ svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
+ prop->name = SVN_PROP_REVISION_DATE;
+ prop->value = NULL;
+ }
+
+ SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, rb->rev, TRUE,
+ rb->pool, rb->pool));
+ new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
+ SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
+
+ for (i = 0; i < diff->nelts; i++)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
+
+ SVN_ERR(change_rev_prop(pb->repos, rb->rev, prop->name, prop->value,
+ pb->validate_props, pb->normalize_props,
+ rb->pool));
+ }
+
+ if (pb->notify_func)
+ {
+ /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_load_revprop_set,
+ pb->notify_pool);
+
+ notify->new_revision = rb->rev;
+ notify->old_revision = SVN_INVALID_REVNUM;
+ pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
+ svn_pool_clear(pb->notify_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CALLBACKS and *PARSE_BATON to a vtable parser which commits new
+ * revisions to the fs in REPOS. Allocate the objects in RESULT_POOL.
+ *
+ * START_REV and END_REV act as filters, the lower and upper (inclusive)
+ * range values of revisions in DUMPSTREAM which will be loaded. Either
+ * both of these values are #SVN_INVALID_REVNUM (in which case no
+ * revision-based filtering occurs at all), or both are valid revisions
+ * (where START_REV is older than or equivalent to END_REV).
+ *
+ * START_REV and END_REV act as filters, the lower and upper (inclusive)
+ * range values of revisions which will
+ * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in
+ * which case no revision-based filtering occurs at all), or both are
+ * valid revisions (where START_REV is older than or equivalent to
+ * END_REV). They refer to dump stream revision numbers rather than
+ * committed revision numbers.
+ *
+ * If VALIDATE_PROPS is set, then validate Subversion revision properties
+ * (those in the svn: namespace) against established rules for those things.
+ *
+ * If IGNORE_DATES is set, ignore any revision datestamps found in
+ * DUMPSTREAM, keeping whatever timestamps the revisions currently have.
+ *
+ * If NORMALIZE_PROPS is set, attempt to normalize invalid Subversion
+ * revision and node properties (those in the svn: namespace) so that
+ * their values would follow the established rules for them. Currently,
+ * this means translating non-LF line endings in the property values to LF.
+ */
+static svn_error_t *
+build_revprop_parser(const svn_repos_parse_fns3_t **callbacks,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t validate_props,
+ svn_boolean_t ignore_dates,
+ svn_boolean_t normalize_props,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool)
+{
+ svn_repos_parse_fns3_t *parser = apr_pcalloc(result_pool, sizeof(*parser));
+ struct parse_baton *pb = apr_pcalloc(result_pool, sizeof(*pb));
+
+ SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
+ SVN_IS_VALID_REVNUM(end_rev))
+ || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
+ (! SVN_IS_VALID_REVNUM(end_rev))));
+ if (SVN_IS_VALID_REVNUM(start_rev))
+ SVN_ERR_ASSERT(start_rev <= end_rev);
+
+ parser->magic_header_record = NULL;
+ parser->uuid_record = uuid_record;
+ parser->new_revision_record = revprops_new_revision_record;
+ parser->new_node_record = NULL;
+ parser->set_revision_property = set_revision_property;
+ parser->set_node_property = NULL;
+ parser->remove_node_props = NULL;
+ parser->set_fulltext = NULL;
+ parser->close_node = NULL;
+ parser->close_revision = revprops_close_revision;
+ parser->delete_node_property = NULL;
+ parser->apply_textdelta = NULL;
+
+ pb->repos = repos;
+ pb->fs = svn_repos_fs(repos);
+ pb->use_history = FALSE;
+ pb->validate_props = validate_props;
+ pb->notify_func = notify_func;
+ pb->notify_baton = notify_baton;
+ pb->uuid_action = svn_repos_load_uuid_ignore; /* Never touch the UUID. */
+ pb->parent_dir = NULL;
+ pb->pool = result_pool;
+ pb->notify_pool = svn_pool_create(result_pool);
+ pb->rev_map = NULL;
+ pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
+ pb->last_rev_mapped = SVN_INVALID_REVNUM;
+ pb->start_rev = start_rev;
+ pb->end_rev = end_rev;
+ pb->use_pre_commit_hook = FALSE;
+ pb->use_post_commit_hook = FALSE;
+ pb->ignore_dates = ignore_dates;
+ pb->normalize_props = normalize_props;
+
+ *callbacks = parser;
+ *parse_baton = pb;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_load_fs_revprops(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t validate_props,
+ svn_boolean_t ignore_dates,
+ svn_boolean_t normalize_props,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_repos_parse_fns3_t *parser;
+ void *parse_baton;
+
+ /* This is really simple. */
+
+ SVN_ERR(build_revprop_parser(&parser, &parse_baton,
+ repos,
+ start_rev, end_rev,
+ validate_props,
+ ignore_dates,
+ normalize_props,
+ notify_func,
+ notify_baton,
+ scratch_pool));
+
+ return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
+ cancel_func, cancel_baton, scratch_pool);
+}
diff --git a/subversion/libsvn_repos/load.c b/subversion/libsvn_repos/load.c
index 96eda85cb183..27cf4a174552 100644
--- a/subversion/libsvn_repos/load.c
+++ b/subversion/libsvn_repos/load.c
@@ -385,7 +385,135 @@ parse_format_version(int *version,
return SVN_NO_ERROR;
}
+/*----------------------------------------------------------------------*/
+
+/** Dummy callback implementations for functions not provided by the user **/
+static svn_error_t *
+dummy_handler_magic_header_record(int version,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_uuid_record(const char *uuid,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_new_revision_record(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ *revision_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_new_node_record(void **node_baton,
+ apr_hash_t *headers,
+ void *revision_baton,
+ apr_pool_t *pool)
+{
+ *node_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_set_revision_property(void *revision_baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_set_node_property(void *node_baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_delete_node_property(void *node_baton,
+ const char *name)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_remove_node_props(void *node_baton)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_set_fulltext(svn_stream_t **stream,
+ void *node_baton)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_apply_textdelta(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ void *node_baton)
+{
+ /* Only called by parse_text_block() and that tests for NULL handlers. */
+ *handler = NULL;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_close_node(void *node_baton)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dummy_handler_close_revision(void *revision_baton)
+{
+ return SVN_NO_ERROR;
+}
+
+/* Helper macro to copy the function pointer SOURCE->NAME to DEST->NAME.
+ * If the source pointer is NULL, pick the corresponding dummy handler
+ * instead. */
+#define SET_VTABLE_ENTRY(dest, source, name) \
+ dest->name = provided->name ? provided->name : dummy_handler_##name
+
+/* Return a copy of PROVIDED with all NULL callbacks replaced by a dummy
+ * handler. Allocate the result in RESULT_POOL. */
+static const svn_repos_parse_fns3_t *
+complete_vtable(const svn_repos_parse_fns3_t *provided,
+ apr_pool_t *result_pool)
+{
+ svn_repos_parse_fns3_t *completed = apr_pcalloc(result_pool,
+ sizeof(*completed));
+
+ SET_VTABLE_ENTRY(completed, provided, magic_header_record);
+ SET_VTABLE_ENTRY(completed, provided, uuid_record);
+ SET_VTABLE_ENTRY(completed, provided, new_revision_record);
+ SET_VTABLE_ENTRY(completed, provided, new_node_record);
+ SET_VTABLE_ENTRY(completed, provided, set_revision_property);
+ SET_VTABLE_ENTRY(completed, provided, set_node_property);
+ SET_VTABLE_ENTRY(completed, provided, delete_node_property);
+ SET_VTABLE_ENTRY(completed, provided, remove_node_props);
+ SET_VTABLE_ENTRY(completed, provided, set_fulltext);
+ SET_VTABLE_ENTRY(completed, provided, apply_textdelta);
+ SET_VTABLE_ENTRY(completed, provided, close_node);
+ SET_VTABLE_ENTRY(completed, provided, close_revision);
+
+ return completed;
+}
/*----------------------------------------------------------------------*/
@@ -410,6 +538,10 @@ svn_repos_parse_dumpstream3(svn_stream_t *stream,
apr_pool_t *nodepool = svn_pool_create(pool);
int version;
+ /* Make sure we can blindly invoke callbacks. */
+ parse_fns = complete_vtable(parse_fns, pool);
+
+ /* Start parsing process. */
SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
if (eof)
return stream_ran_dry();
diff --git a/subversion/libsvn_repos/log.c b/subversion/libsvn_repos/log.c
index 82caf0219ea8..7dec5dd8d5b7 100644
--- a/subversion/libsvn_repos/log.c
+++ b/subversion/libsvn_repos/log.c
@@ -43,8 +43,72 @@
#include "private/svn_mergeinfo_private.h"
#include "private/svn_subr_private.h"
#include "private/svn_sorts_private.h"
+#include "private/svn_string_private.h"
+/* This is a mere convenience struct such that we don't need to pass that
+ many parameters around individually. */
+typedef struct log_callbacks_t
+{
+ svn_repos_path_change_receiver_t path_change_receiver;
+ void *path_change_receiver_baton;
+ svn_repos_log_entry_receiver_t revision_receiver;
+ void *revision_receiver_baton;
+ svn_repos_authz_func_t authz_read_func;
+ void *authz_read_baton;
+} log_callbacks_t;
+
+
+svn_repos_path_change_t *
+svn_repos_path_change_create(apr_pool_t *result_pool)
+{
+ svn_repos_path_change_t *change = apr_pcalloc(result_pool, sizeof(*change));
+
+ change->path.data = "";
+ change->change_kind = svn_fs_path_change_reset;
+ change->mergeinfo_mod = svn_tristate_unknown;
+ change->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ return change;
+}
+
+svn_repos_path_change_t *
+svn_repos_path_change_dup(svn_repos_path_change_t *change,
+ apr_pool_t *result_pool)
+{
+ svn_repos_path_change_t *new_change = apr_pmemdup(result_pool, change,
+ sizeof(*new_change));
+
+ new_change->path.data = apr_pstrmemdup(result_pool, change->path.data,
+ change->path.len);
+ if (change->copyfrom_path)
+ new_change->copyfrom_path = apr_pstrdup(result_pool,
+ change->copyfrom_path);
+
+ return new_change;
+}
+
+svn_repos_log_entry_t *
+svn_repos_log_entry_create(apr_pool_t *result_pool)
+{
+ svn_repos_log_entry_t *log_entry = apr_pcalloc(result_pool,
+ sizeof(*log_entry));
+
+ return log_entry;
+}
+
+svn_repos_log_entry_t *
+svn_repos_log_entry_dup(const svn_repos_log_entry_t *log_entry,
+ apr_pool_t *result_pool)
+{
+ svn_repos_log_entry_t *new_entry = apr_pmemdup(result_pool, log_entry,
+ sizeof(*new_entry));
+
+ if (log_entry->revprops)
+ new_entry->revprops = svn_prop_hash_dup(log_entry->revprops, result_pool);
+
+ return new_entry;
+}
svn_error_t *
svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
@@ -56,11 +120,11 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
{
svn_fs_t *fs = svn_repos_fs(repos);
svn_fs_root_t *rev_root;
- apr_hash_t *changes;
- apr_hash_index_t *hi;
+ svn_fs_path_change_iterator_t *iterator;
+ svn_fs_path_change3_t *change;
svn_boolean_t found_readable = FALSE;
svn_boolean_t found_unreadable = FALSE;
- apr_pool_t *subpool;
+ apr_pool_t *iterpool;
/* By default, we'll grant full read access to REVISION. */
*access_level = svn_repos_revision_access_full;
@@ -71,25 +135,27 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
/* Fetch the changes associated with REVISION. */
SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
- SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
+ SVN_ERR(svn_fs_paths_changed3(&iterator, rev_root, pool, pool));
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
- /* No changed paths? We're done. */
- if (apr_hash_count(changes) == 0)
+ /* No changed paths? We're done.
+
+ Note that the check at "decision:" assumes that at least one
+ path has been processed. So, this actually affects functionality. */
+ if (!change)
return SVN_NO_ERROR;
/* Otherwise, we have to check the readability of each changed
path, or at least enough to answer the question asked. */
- subpool = svn_pool_create(pool);
- for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ iterpool = svn_pool_create(pool);
+ while (change)
{
- const char *key = apr_hash_this_key(hi);
- svn_fs_path_change2_t *change = apr_hash_this_val(hi);
svn_boolean_t readable;
- svn_pool_clear(subpool);
+ svn_pool_clear(iterpool);
- SVN_ERR(authz_read_func(&readable, rev_root, key,
- authz_read_baton, subpool));
+ SVN_ERR(authz_read_func(&readable, rev_root, change->path.data,
+ authz_read_baton, iterpool));
if (! readable)
found_unreadable = TRUE;
else
@@ -109,15 +175,16 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
svn_revnum_t copyfrom_rev;
SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
- rev_root, key, subpool));
+ rev_root, change->path.data,
+ iterpool));
if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
{
svn_fs_root_t *copyfrom_root;
SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
- copyfrom_rev, subpool));
+ copyfrom_rev, iterpool));
SVN_ERR(authz_read_func(&readable,
copyfrom_root, copyfrom_path,
- authz_read_baton, subpool));
+ authz_read_baton, iterpool));
if (! readable)
found_unreadable = TRUE;
@@ -134,10 +201,12 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
default:
break;
}
+
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
}
decision:
- svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
/* Either every changed path was unreadable... */
if (! found_readable)
@@ -152,21 +221,14 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
}
-/* Store as keys in CHANGED the paths of all node in ROOT that show a
- * significant change. "Significant" means that the text or
- * properties of the node were changed, or that the node was added or
- * deleted.
- *
- * The CHANGED hash set and its keys and values are allocated in POOL;
- * keys are const char * paths and values are svn_log_changed_path_t.
+/* Find all significant changes under ROOT and, if not NULL, report them
+ * to the CALLBACKS->PATH_CHANGE_RECEIVER. "Significant" means that the
+ * text or properties of the node were changed, or that the node was added
+ * or deleted.
*
- * To prevent changes from being processed over and over again, the
- * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the
- * latter is NULL, we will request the list inside this function.
- *
- * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
- * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
- * copyfrom_path) is readable:
+ * If optional CALLBACKS->AUTHZ_READ_FUNC is non-NULL, then use it (with
+ * CALLBACKS->AUTHZ_READ_BATON and FS) to check whether each changed-path
+ * (and copyfrom_path) is readable:
*
* - If absolutely every changed-path (and copyfrom_path) is
* readable, then return the full CHANGED hash, and set
@@ -184,40 +246,22 @@ svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
*/
static svn_error_t *
detect_changed(svn_repos_revision_access_level_t *access_level,
- apr_hash_t **changed,
svn_fs_root_t *root,
svn_fs_t *fs,
- apr_hash_t *prefetched_changes,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
- apr_pool_t *pool)
+ const log_callbacks_t *callbacks,
+ apr_pool_t *scratch_pool)
{
- apr_hash_t *changes = prefetched_changes;
- apr_hash_index_t *hi;
+ svn_fs_path_change_iterator_t *iterator;
+ svn_fs_path_change3_t *change;
apr_pool_t *iterpool;
svn_boolean_t found_readable = FALSE;
svn_boolean_t found_unreadable = FALSE;
- /* If we create the CHANGES hash ourselves, we can reuse it as the
- * result hash as it contains the exact same keys - but with _all_
- * values being replaced by structs of a different type. */
- if (changes == NULL)
- {
- SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
-
- /* If we are going to filter the results, we won't use the exact
- * same keys but put them into a new hash. */
- if (authz_read_func)
- *changed = svn_hash__make(pool);
- else
- *changed = changes;
- }
- else
- {
- *changed = svn_hash__make(pool);
- }
+ /* Retrieve the first change in the list. */
+ SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
- if (apr_hash_count(changes) == 0)
+ if (!change)
{
/* No paths changed in this revision? Uh, sure, I guess the
revision is readable, then. */
@@ -225,30 +269,26 @@ detect_changed(svn_repos_revision_access_level_t *access_level,
return SVN_NO_ERROR;
}
- iterpool = svn_pool_create(pool);
- for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ iterpool = svn_pool_create(scratch_pool);
+ while (change)
{
/* NOTE: Much of this loop is going to look quite similar to
svn_repos_check_revision_access(), but we have to do more things
here, so we'll live with the duplication. */
- const char *path = apr_hash_this_key(hi);
- apr_ssize_t path_len = apr_hash_this_key_len(hi);
- svn_fs_path_change2_t *change = apr_hash_this_val(hi);
- char action;
- svn_log_changed_path2_t *item;
-
+ const char *path = change->path.data;
svn_pool_clear(iterpool);
/* Skip path if unreadable. */
- if (authz_read_func)
+ if (callbacks->authz_read_func)
{
svn_boolean_t readable;
- SVN_ERR(authz_read_func(&readable,
- root, path,
- authz_read_baton, iterpool));
+ SVN_ERR(callbacks->authz_read_func(&readable, root, path,
+ callbacks->authz_read_baton,
+ iterpool));
if (! readable)
{
found_unreadable = TRUE;
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
continue;
}
}
@@ -256,41 +296,9 @@ detect_changed(svn_repos_revision_access_level_t *access_level,
/* At least one changed-path was readable. */
found_readable = TRUE;
- switch (change->change_kind)
- {
- case svn_fs_path_change_reset:
- continue;
-
- case svn_fs_path_change_add:
- action = 'A';
- break;
-
- case svn_fs_path_change_replace:
- action = 'R';
- break;
-
- case svn_fs_path_change_delete:
- action = 'D';
- break;
-
- case svn_fs_path_change_modify:
- default:
- action = 'M';
- break;
- }
-
- item = svn_log_changed_path2_create(pool);
- item->action = action;
- item->node_kind = change->node_kind;
- item->copyfrom_rev = SVN_INVALID_REVNUM;
- item->text_modified = change->text_mod ? svn_tristate_true
- : svn_tristate_false;
- item->props_modified = change->prop_mod ? svn_tristate_true
- : svn_tristate_false;
-
/* Pre-1.6 revision files don't store the change path kind, so fetch
it manually. */
- if (item->node_kind == svn_node_unknown)
+ if (change->node_kind == svn_node_unknown)
{
svn_fs_root_t *check_root = root;
const char *check_path = path;
@@ -315,18 +323,19 @@ detect_changed(svn_repos_revision_access_level_t *access_level,
SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
iterpool));
- SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
- iterpool));
- SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, iterpool));
+ SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev,
+ history, iterpool));
+ SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev,
+ iterpool));
check_path = svn_fspath__join(parent_path, name, iterpool);
}
- SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
+ SVN_ERR(svn_fs_check_path(&change->node_kind, check_root, check_path,
iterpool));
}
-
- if ((action == 'A') || (action == 'R'))
+ if ( (change->change_kind == svn_fs_path_change_add)
+ || (change->change_kind == svn_fs_path_change_replace))
{
const char *copyfrom_path = change->copyfrom_path;
svn_revnum_t copyfrom_rev = change->copyfrom_rev;
@@ -338,35 +347,44 @@ detect_changed(svn_repos_revision_access_level_t *access_level,
{
SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
root, path, iterpool));
- copyfrom_path = apr_pstrdup(pool, copyfrom_path);
+ change->copyfrom_known = TRUE;
}
if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
{
svn_boolean_t readable = TRUE;
- if (authz_read_func)
+ if (callbacks->authz_read_func)
{
svn_fs_root_t *copyfrom_root;
SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
copyfrom_rev, iterpool));
- SVN_ERR(authz_read_func(&readable,
- copyfrom_root, copyfrom_path,
- authz_read_baton, iterpool));
+ SVN_ERR(callbacks->authz_read_func(&readable,
+ copyfrom_root,
+ copyfrom_path,
+ callbacks->authz_read_baton,
+ iterpool));
if (! readable)
found_unreadable = TRUE;
}
if (readable)
{
- item->copyfrom_path = copyfrom_path;
- item->copyfrom_rev = copyfrom_rev;
+ change->copyfrom_path = copyfrom_path;
+ change->copyfrom_rev = copyfrom_rev;
}
}
}
- apr_hash_set(*changed, path, path_len, item);
+ if (callbacks->path_change_receiver)
+ SVN_ERR(callbacks->path_change_receiver(
+ callbacks->path_change_receiver_baton,
+ change,
+ iterpool));
+
+ /* Next changed path. */
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
}
svn_pool_destroy(iterpool);
@@ -593,23 +611,21 @@ next_history_rev(const apr_array_header_t *histories)
/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
catalogs describing how mergeinfo values on paths (which are the
- keys of those catalogs) were changed in REV. If *PREFETCHED_CHANGES
- already contains the changed paths for REV, use that. Otherwise,
- request that data and return it in *PREFETCHED_CHANGES. */
+ keys of those catalogs) were changed in REV. */
/* ### TODO: This would make a *great*, useful public function,
### svn_repos_fs_mergeinfo_changed()! -- cmpilato */
static svn_error_t *
fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
- apr_hash_t **prefetched_changes,
svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_fs_root_t *root;
- apr_pool_t *iterpool;
- apr_hash_index_t *hi;
+ apr_pool_t *iterpool, *iterator_pool;
+ svn_fs_path_change_iterator_t *iterator;
+ svn_fs_path_change3_t *change;
svn_boolean_t any_mergeinfo = FALSE;
svn_boolean_t any_copy = FALSE;
@@ -621,56 +637,69 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
if (rev == 0)
return SVN_NO_ERROR;
+ /* FS iterators are potentially heavy objects.
+ * Hold them in a separate pool to clean them up asap. */
+ iterator_pool = svn_pool_create(scratch_pool);
+
/* We're going to use the changed-paths information for REV to
narrow down our search. */
SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
- if (*prefetched_changes == NULL)
- SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
+ SVN_ERR(svn_fs_paths_changed3(&iterator, root, iterator_pool,
+ iterator_pool));
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
/* Look for copies and (potential) mergeinfo changes.
- We will use both flags to take shortcuts further down the road. */
- for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
- hi;
- hi = apr_hash_next(hi))
+ We will use both flags to take shortcuts further down the road.
+
+ The critical information here is whether there are any copies
+ because that greatly influences the costs for log processing.
+ So, it is faster to iterate over the changes twice - in the worst
+ case b/c most times there is no m/i at all and we exit out early
+ without any overhead.
+ */
+ while (change && (!any_mergeinfo || !any_copy))
{
- svn_fs_path_change2_t *change = apr_hash_this_val(hi);
-
/* If there was a prop change and we are not positive that _no_
mergeinfo change happened, we must assume that it might have. */
if (change->mergeinfo_mod != svn_tristate_false && change->prop_mod)
any_mergeinfo = TRUE;
- switch (change->change_kind)
- {
- case svn_fs_path_change_add:
- case svn_fs_path_change_replace:
- any_copy = TRUE;
- break;
+ if ( (change->change_kind == svn_fs_path_change_add)
+ || (change->change_kind == svn_fs_path_change_replace))
+ any_copy = TRUE;
- default:
- break;
- }
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
}
/* No potential mergeinfo changes? We're done. */
if (! any_mergeinfo)
- return SVN_NO_ERROR;
+ {
+ svn_pool_destroy(iterator_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* There is or may be some m/i change. Look closely now. */
+ svn_pool_clear(iterator_pool);
+ SVN_ERR(svn_fs_paths_changed3(&iterator, root, iterator_pool,
+ iterator_pool));
/* Loop over changes, looking for anything that might carry an
svn:mergeinfo change and is one of our paths of interest, or a
child or [grand]parent directory thereof. */
iterpool = svn_pool_create(scratch_pool);
- for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
- hi;
- hi = apr_hash_next(hi))
+ while (TRUE)
{
const char *changed_path;
- svn_fs_path_change2_t *change = apr_hash_this_val(hi);
const char *base_path = NULL;
svn_revnum_t base_rev = SVN_INVALID_REVNUM;
svn_fs_root_t *base_root = NULL;
svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
+ /* Next change. */
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
+ if (!change)
+ break;
+
/* Cheap pre-checks that don't require memory allocation etc. */
/* No mergeinfo change? -> nothing to do here. */
@@ -682,7 +711,7 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
continue;
/* Begin actual processing */
- changed_path = apr_hash_this_key(hi);
+ changed_path = change->path.data;
svn_pool_clear(iterpool);
switch (change->change_kind)
@@ -840,20 +869,18 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
}
svn_pool_destroy(iterpool);
+ svn_pool_destroy(iterator_pool);
+
return SVN_NO_ERROR;
}
/* Determine what (if any) mergeinfo for PATHS was modified in
revision REV, returning the differences for added mergeinfo in
- *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
- If *PREFETCHED_CHANGES already contains the changed paths for
- REV, use that. Otherwise, request that data and return it in
- *PREFETCHED_CHANGES. */
+ *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO. */
static svn_error_t *
get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
svn_mergeinfo_t *deleted_mergeinfo,
- apr_hash_t **prefetched_changes,
svn_fs_t *fs,
const apr_array_header_t *paths,
svn_revnum_t rev,
@@ -882,7 +909,6 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
/* Fetch the mergeinfo changes for REV. */
err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
&added_mergeinfo_catalog,
- prefetched_changes,
fs, rev,
scratch_pool, scratch_pool);
if (err)
@@ -1071,38 +1097,31 @@ get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
/* Fill LOG_ENTRY with history information in FS at REV. */
static svn_error_t *
-fill_log_entry(svn_log_entry_t *log_entry,
+fill_log_entry(svn_repos_log_entry_t *log_entry,
svn_revnum_t rev,
svn_fs_t *fs,
- apr_hash_t *prefetched_changes,
- svn_boolean_t discover_changed_paths,
const apr_array_header_t *revprops,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
+ const log_callbacks_t *callbacks,
apr_pool_t *pool)
{
- apr_hash_t *r_props, *changed_paths = NULL;
+ apr_hash_t *r_props;
svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
svn_boolean_t want_revprops = !revprops || revprops->nelts;
/* Discover changed paths if the user requested them
or if we need to check that they are readable. */
if ((rev > 0)
- && (authz_read_func || discover_changed_paths))
+ && (callbacks->authz_read_func || callbacks->path_change_receiver))
{
svn_fs_root_t *newroot;
svn_repos_revision_access_level_t access_level;
SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
- SVN_ERR(detect_changed(&access_level, &changed_paths,
- newroot, fs, prefetched_changes,
- authz_read_func, authz_read_baton,
- pool));
+ SVN_ERR(detect_changed(&access_level, newroot, fs, callbacks, pool));
if (access_level == svn_repos_revision_access_none)
{
/* All changed-paths are unreadable, so clear all fields. */
- changed_paths = NULL;
get_revprops = FALSE;
}
else if (access_level == svn_repos_revision_access_partial)
@@ -1112,17 +1131,13 @@ fill_log_entry(svn_log_entry_t *log_entry,
missing from the hash.) */
censor_revprops = TRUE;
}
-
- /* It may be the case that an authz func was passed in, but
- the user still doesn't want to see any changed-paths. */
- if (! discover_changed_paths)
- changed_paths = NULL;
}
if (get_revprops && want_revprops)
{
/* User is allowed to see at least some revprops. */
- SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
+ SVN_ERR(svn_fs_revision_proplist2(&r_props, fs, rev, FALSE, pool,
+ pool));
if (revprops == NULL)
{
/* Requested all revprops... */
@@ -1150,9 +1165,9 @@ fill_log_entry(svn_log_entry_t *log_entry,
we want static initialization here and must therefore emulate
strlen(x) by sizeof(x)-1. */
static const svn_string_t svn_prop_revision_author
- = {SVN_PROP_REVISION_AUTHOR, sizeof(SVN_PROP_REVISION_AUTHOR)-1};
+ = SVN__STATIC_STRING(SVN_PROP_REVISION_AUTHOR);
static const svn_string_t svn_prop_revision_date
- = {SVN_PROP_REVISION_DATE, sizeof(SVN_PROP_REVISION_DATE)-1};
+ = SVN__STATIC_STRING(SVN_PROP_REVISION_DATE);
/* often only the standard revprops got requested and delivered.
In that case, we can simply pass the hash on. */
@@ -1192,20 +1207,83 @@ fill_log_entry(svn_log_entry_t *log_entry,
}
}
- log_entry->changed_paths = changed_paths;
- log_entry->changed_paths2 = changed_paths;
log_entry->revision = rev;
return SVN_NO_ERROR;
}
-/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
+/* Baton type to be used with the interesting_merge callback. */
+typedef struct interesting_merge_baton_t
+{
+ /* What we are looking for. */
+ svn_revnum_t rev;
+ svn_mergeinfo_t log_target_history_as_mergeinfo;
+
+ /* Set to TRUE if we found it. */
+ svn_boolean_t found_rev_of_interest;
+
+ /* We need to invoke this user-provided callback if not NULL. */
+ svn_repos_path_change_receiver_t inner;
+ void *inner_baton;
+} interesting_merge_baton_t;
+
+/* Implements svn_repos_path_change_receiver_t.
+ * *BATON is a interesting_merge_baton_t.
+ *
+ * If BATON->REV a merged revision that is not already part of
+ * BATON->LOG_TARGET_HISTORY_AS_MERGEINFO, set BATON->FOUND_REV_OF_INTEREST.
+ */
+static svn_error_t *
+interesting_merge(void *baton,
+ svn_repos_path_change_t *change,
+ apr_pool_t *scratch_pool)
+{
+ interesting_merge_baton_t *b = baton;
+ apr_hash_index_t *hi;
+
+ if (b->inner)
+ SVN_ERR(b->inner(b->inner_baton, change, scratch_pool));
+
+ if (b->found_rev_of_interest)
+ return SVN_NO_ERROR;
+
+ /* Look at each path on the log target's mergeinfo. */
+ for (hi = apr_hash_first(scratch_pool, b->log_target_history_as_mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *mergeinfo_path = apr_hash_this_key(hi);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi);
+
+ /* Check whether CHANGED_PATH at revision REV is a child of
+ a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
+ if (svn_fspath__skip_ancestor(mergeinfo_path, change->path.data))
+ {
+ int i;
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range
+ = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ if (b->rev > range->start && b->rev <= range->end)
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ b->found_rev_of_interest = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Send a log message for REV to the CALLBACKS.
FS is used with REV to fetch the interesting history information,
such as changed paths, revprops, etc.
- The detect_changed function is used if either AUTHZ_READ_FUNC is
- not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details.
+ The detect_changed function is used if either CALLBACKS->AUTHZ_READ_FUNC
+ is not NULL, or if CALLBACKS->PATH_CHANGE_RECEIVER is not NULL.
+ See it for details.
If DESCENDING_ORDER is true, send child messages in descending order.
@@ -1227,107 +1305,51 @@ fill_log_entry(svn_log_entry_t *log_entry,
static svn_error_t *
send_log(svn_revnum_t rev,
svn_fs_t *fs,
- apr_hash_t *prefetched_changes,
svn_mergeinfo_t log_target_history_as_mergeinfo,
svn_bit_array__t *nested_merges,
- svn_boolean_t discover_changed_paths,
svn_boolean_t subtractive_merge,
svn_boolean_t handling_merged_revision,
const apr_array_header_t *revprops,
svn_boolean_t has_children,
- svn_log_entry_receiver_t receiver,
- void *receiver_baton,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
+ const log_callbacks_t *callbacks,
apr_pool_t *pool)
{
- svn_log_entry_t *log_entry;
- /* Assume we want to send the log for REV. */
- svn_boolean_t found_rev_of_interest = TRUE;
+ svn_repos_log_entry_t log_entry = { 0 };
+ log_callbacks_t my_callbacks = *callbacks;
- log_entry = svn_log_entry_create(pool);
- SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
- discover_changed_paths || handling_merged_revision,
- revprops, authz_read_func, authz_read_baton, pool));
- log_entry->has_children = has_children;
- log_entry->subtractive_merge = subtractive_merge;
+ interesting_merge_baton_t baton;
/* Is REV a merged revision that is already part of
LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no
- need to send it, since it already was (or will be) sent. */
+ need to send it, since it already was (or will be) sent.
+
+ Use our callback to snoop through the changes. */
if (handling_merged_revision
- && log_entry->changed_paths2
&& log_target_history_as_mergeinfo
&& apr_hash_count(log_target_history_as_mergeinfo))
{
- apr_hash_index_t *hi;
- apr_pool_t *iterpool = svn_pool_create(pool);
-
- /* REV was merged in, but it might already be part of the log target's
- natural history, so change our starting assumption. */
- found_rev_of_interest = FALSE;
-
- /* Look at each changed path in REV. */
- for (hi = apr_hash_first(pool, log_entry->changed_paths2);
- hi;
- hi = apr_hash_next(hi))
- {
- svn_boolean_t path_is_in_history = FALSE;
- const char *changed_path = apr_hash_this_key(hi);
- apr_hash_index_t *hi2;
-
- /* Look at each path on the log target's mergeinfo. */
- for (hi2 = apr_hash_first(iterpool,
- log_target_history_as_mergeinfo);
- hi2;
- hi2 = apr_hash_next(hi2))
- {
- const char *mergeinfo_path = apr_hash_this_key(hi2);
- svn_rangelist_t *rangelist = apr_hash_this_val(hi2);
-
- /* Check whether CHANGED_PATH at revision REV is a child of
- a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
- if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
- {
- int i;
-
- for (i = 0; i < rangelist->nelts; i++)
- {
- svn_merge_range_t *range =
- APR_ARRAY_IDX(rangelist, i,
- svn_merge_range_t *);
- if (rev > range->start && rev <= range->end)
- {
- path_is_in_history = TRUE;
- break;
- }
- }
- }
- if (path_is_in_history)
- break;
- }
- svn_pool_clear(iterpool);
-
- if (!path_is_in_history)
- {
- /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
- LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
- log for REV. */
- found_rev_of_interest = TRUE;
- break;
- }
- }
- svn_pool_destroy(iterpool);
+ baton.found_rev_of_interest = FALSE;
+ baton.rev = rev;
+ baton.log_target_history_as_mergeinfo = log_target_history_as_mergeinfo;
+ baton.inner = callbacks->path_change_receiver;
+ baton.inner_baton = callbacks->path_change_receiver_baton;
+
+ my_callbacks.path_change_receiver = interesting_merge;
+ my_callbacks.path_change_receiver_baton = &baton;
+ callbacks = &my_callbacks;
+ }
+ else
+ {
+ baton.found_rev_of_interest = TRUE;
}
- /* If we only got changed paths the sake of detecting redundant merged
- revisions, then be sure we don't send that info to the receiver. */
- if (!discover_changed_paths && handling_merged_revision)
- log_entry->changed_paths = log_entry->changed_paths2 = NULL;
+ SVN_ERR(fill_log_entry(&log_entry, rev, fs, revprops, callbacks, pool));
+ log_entry.has_children = has_children;
+ log_entry.subtractive_merge = subtractive_merge;
/* Send the entry to the receiver, unless it is a redundant merged
revision. */
- if (found_rev_of_interest)
+ if (baton.found_rev_of_interest)
{
apr_pool_t *scratch_pool;
@@ -1351,7 +1373,8 @@ send_log(svn_revnum_t rev,
/* Pass a scratch pool to ensure no temporary state stored
by the receiver callback persists. */
scratch_pool = svn_pool_create(pool);
- SVN_ERR(receiver(receiver_baton, log_entry, scratch_pool));
+ SVN_ERR(callbacks->revision_receiver(callbacks->revision_receiver_baton,
+ &log_entry, scratch_pool));
svn_pool_destroy(scratch_pool);
}
@@ -1700,7 +1723,6 @@ do_logs(svn_fs_t *fs,
svn_revnum_t hist_start,
svn_revnum_t hist_end,
int limit,
- svn_boolean_t discover_changed_paths,
svn_boolean_t strict_node_history,
svn_boolean_t include_merged_revisions,
svn_boolean_t handling_merged_revisions,
@@ -1708,10 +1730,7 @@ do_logs(svn_fs_t *fs,
svn_boolean_t ignore_missing_locations,
const apr_array_header_t *revprops,
svn_boolean_t descending_order,
- svn_log_entry_receiver_t receiver,
- void *receiver_baton,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
+ log_callbacks_t *callbacks,
apr_pool_t *pool);
/* Comparator function for handle_merged_revisions(). Sorts path_list_range
@@ -1753,17 +1772,13 @@ handle_merged_revisions(svn_revnum_t rev,
svn_mergeinfo_t processed,
svn_mergeinfo_t added_mergeinfo,
svn_mergeinfo_t deleted_mergeinfo,
- svn_boolean_t discover_changed_paths,
svn_boolean_t strict_node_history,
const apr_array_header_t *revprops,
- svn_log_entry_receiver_t receiver,
- void *receiver_baton,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
+ log_callbacks_t *callbacks,
apr_pool_t *pool)
{
apr_array_header_t *combined_list = NULL;
- svn_log_entry_t *empty_log_entry;
+ svn_repos_log_entry_t empty_log_entry = { 0 };
apr_pool_t *iterpool;
int i;
@@ -1794,17 +1809,16 @@ handle_merged_revisions(svn_revnum_t rev,
SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
processed, nested_merges,
pl_range->range.start, pl_range->range.end, 0,
- discover_changed_paths, strict_node_history,
+ strict_node_history,
TRUE, pl_range->reverse_merge, TRUE, TRUE,
- revprops, TRUE, receiver, receiver_baton,
- authz_read_func, authz_read_baton, iterpool));
+ revprops, TRUE, callbacks, iterpool));
}
svn_pool_destroy(iterpool);
/* Send the empty revision. */
- empty_log_entry = svn_log_entry_create(pool);
- empty_log_entry->revision = SVN_INVALID_REVNUM;
- return (*receiver)(receiver_baton, empty_log_entry, pool);
+ empty_log_entry.revision = SVN_INVALID_REVNUM;
+ return (callbacks->revision_receiver)(callbacks->revision_receiver_baton,
+ &empty_log_entry, pool);
}
/* This is used by do_logs to differentiate between forward and
@@ -1907,8 +1921,7 @@ store_search(svn_mergeinfo_t processed,
const char *path = APR_ARRAY_IDX(paths, i, const char *);
svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
sizeof(svn_merge_range_t*));
- svn_merge_range_t *range = apr_palloc(processed_pool,
- sizeof(svn_merge_range_t));
+ svn_merge_range_t *range = apr_palloc(processed_pool, sizeof(*range));
range->start = start;
range->end = end;
@@ -1922,10 +1935,10 @@ store_search(svn_mergeinfo_t processed,
return SVN_NO_ERROR;
}
-/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
- RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send
- the logs back as we find them, else buffer the logs and send them back
- in youngest->oldest order.
+/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke the
+ CALLBACKS on them. If DESCENDING_ORDER is TRUE, send the logs back as
+ we find them, else buffer the logs and send them back in youngest->oldest
+ order.
If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
repository locations as fatal -- just ignore them.
@@ -1935,7 +1948,7 @@ store_search(svn_mergeinfo_t processed,
If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
merged revisions, see INCLUDE_MERGED_REVISIONS argument to
- svn_repos_get_logs4(). If SUBTRACTIVE_MERGE is true, then this is a
+ svn_repos_get_logs5(). If SUBTRACTIVE_MERGE is true, then this is a
recursive call for reverse merged revisions.
If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
@@ -1950,7 +1963,7 @@ store_search(svn_mergeinfo_t processed,
revisions that have already been searched. Allocated like
NESTED_MERGES above.
- All other parameters are the same as svn_repos_get_logs4().
+ All other parameters are the same as svn_repos_get_logs5().
*/
static svn_error_t *
do_logs(svn_fs_t *fs,
@@ -1961,7 +1974,6 @@ do_logs(svn_fs_t *fs,
svn_revnum_t hist_start,
svn_revnum_t hist_end,
int limit,
- svn_boolean_t discover_changed_paths,
svn_boolean_t strict_node_history,
svn_boolean_t include_merged_revisions,
svn_boolean_t subtractive_merge,
@@ -1969,10 +1981,7 @@ do_logs(svn_fs_t *fs,
svn_boolean_t ignore_missing_locations,
const apr_array_header_t *revprops,
svn_boolean_t descending_order,
- svn_log_entry_receiver_t receiver,
- void *receiver_baton,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
+ log_callbacks_t *callbacks,
apr_pool_t *pool)
{
apr_pool_t *iterpool, *iterpool2;
@@ -2005,7 +2014,8 @@ do_logs(svn_fs_t *fs,
revisions contain real changes to at least one of our paths. */
SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
strict_node_history, ignore_missing_locations,
- authz_read_func, authz_read_baton, pool));
+ callbacks->authz_read_func,
+ callbacks->authz_read_baton, pool));
/* Loop through all the revisions in the range and add any
where a path was changed to the array, or if they wanted
@@ -2029,9 +2039,10 @@ do_logs(svn_fs_t *fs,
/* Check history for this path in current rev. */
SVN_ERR(check_history(&changed, info, fs, current,
- strict_node_history, authz_read_func,
- authz_read_baton, hist_start, pool,
- iterpool2));
+ strict_node_history,
+ callbacks->authz_read_func,
+ callbacks->authz_read_baton,
+ hist_start, pool, iterpool2));
if (! info->done)
any_histories_left = TRUE;
}
@@ -2044,7 +2055,6 @@ do_logs(svn_fs_t *fs,
svn_mergeinfo_t added_mergeinfo = NULL;
svn_mergeinfo_t deleted_mergeinfo = NULL;
svn_boolean_t has_children = FALSE;
- apr_hash_t *changes = NULL;
/* If we're including merged revisions, we need to calculate
the mergeinfo deltas committed in this revision to our
@@ -2065,7 +2075,6 @@ do_logs(svn_fs_t *fs,
}
SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
&deleted_mergeinfo,
- &changes,
fs, cur_paths,
current,
iterpool, iterpool));
@@ -2078,13 +2087,10 @@ do_logs(svn_fs_t *fs,
in anyway). */
if (descending_order)
{
- SVN_ERR(send_log(current, fs, changes,
+ SVN_ERR(send_log(current, fs,
log_target_history_as_mergeinfo, nested_merges,
- discover_changed_paths,
subtractive_merge, handling_merged_revisions,
- revprops, has_children,
- receiver, receiver_baton,
- authz_read_func, authz_read_baton, iterpool));
+ revprops, has_children, callbacks, iterpool));
if (has_children) /* Implies include_merged_revisions == TRUE */
{
@@ -2103,12 +2109,9 @@ do_logs(svn_fs_t *fs,
log_target_history_as_mergeinfo, nested_merges,
processed,
added_mergeinfo, deleted_mergeinfo,
- discover_changed_paths,
strict_node_history,
revprops,
- receiver, receiver_baton,
- authz_read_func,
- authz_read_baton,
+ callbacks,
iterpool));
}
if (limit && ++send_count >= limit)
@@ -2176,20 +2179,17 @@ do_logs(svn_fs_t *fs,
if (rev_mergeinfo)
{
struct added_deleted_mergeinfo *add_and_del_mergeinfo =
- apr_hash_get(rev_mergeinfo, &current, sizeof(svn_revnum_t));
+ apr_hash_get(rev_mergeinfo, &current, sizeof(current));
added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
has_children = (apr_hash_count(added_mergeinfo) > 0
|| apr_hash_count(deleted_mergeinfo) > 0);
}
- SVN_ERR(send_log(current, fs, NULL,
+ SVN_ERR(send_log(current, fs,
log_target_history_as_mergeinfo, nested_merges,
- discover_changed_paths, subtractive_merge,
- handling_merged_revisions,
- revprops, has_children,
- receiver, receiver_baton, authz_read_func,
- authz_read_baton, iterpool));
+ subtractive_merge, handling_merged_revisions,
+ revprops, has_children, callbacks, iterpool));
if (has_children)
{
if (!nested_merges)
@@ -2204,12 +2204,8 @@ do_logs(svn_fs_t *fs,
processed,
added_mergeinfo,
deleted_mergeinfo,
- discover_changed_paths,
strict_node_history,
- revprops,
- receiver, receiver_baton,
- authz_read_func,
- authz_read_baton,
+ revprops, callbacks,
iterpool));
}
if (limit && i + 1 >= limit)
@@ -2227,7 +2223,7 @@ struct location_segment_baton
apr_pool_t *pool;
};
-/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
+/* svn_location_segment_receiver_t implementation for svn_repos_get_logs5. */
static svn_error_t *
location_segment_receiver(svn_location_segment_t *segment,
void *baton,
@@ -2247,7 +2243,7 @@ location_segment_receiver(svn_location_segment_t *segment,
filesystem. START_REV and END_REV must be valid revisions. RESULT_POOL
is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
other (temporary) allocations. Other parameters are the same as
- svn_repos_get_logs4(). */
+ svn_repos_get_logs5(). */
static svn_error_t *
get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
svn_repos_t *repos,
@@ -2308,41 +2304,56 @@ get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
}
svn_error_t *
-svn_repos_get_logs4(svn_repos_t *repos,
+svn_repos_get_logs5(svn_repos_t *repos,
const apr_array_header_t *paths,
svn_revnum_t start,
svn_revnum_t end,
int limit,
- svn_boolean_t discover_changed_paths,
svn_boolean_t strict_node_history,
svn_boolean_t include_merged_revisions,
const apr_array_header_t *revprops,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
- svn_log_entry_receiver_t receiver,
- void *receiver_baton,
- apr_pool_t *pool)
+ svn_repos_path_change_receiver_t path_change_receiver,
+ void *path_change_receiver_baton,
+ svn_repos_log_entry_receiver_t revision_receiver,
+ void *revision_receiver_baton,
+ apr_pool_t *scratch_pool)
{
svn_revnum_t head = SVN_INVALID_REVNUM;
svn_fs_t *fs = repos->fs;
svn_boolean_t descending_order;
svn_mergeinfo_t paths_history_mergeinfo = NULL;
+ log_callbacks_t callbacks;
+
+ callbacks.path_change_receiver = path_change_receiver;
+ callbacks.path_change_receiver_baton = path_change_receiver_baton;
+ callbacks.revision_receiver = revision_receiver;
+ callbacks.revision_receiver_baton = revision_receiver_baton;
+ callbacks.authz_read_func = authz_read_func;
+ callbacks.authz_read_baton = authz_read_baton;
if (revprops)
{
int i;
apr_array_header_t *new_revprops
- = apr_array_make(pool, revprops->nelts, sizeof(svn_string_t *));
+ = apr_array_make(scratch_pool, revprops->nelts,
+ sizeof(svn_string_t *));
for (i = 0; i < revprops->nelts; ++i)
APR_ARRAY_PUSH(new_revprops, svn_string_t *)
- = svn_string_create(APR_ARRAY_IDX(revprops, i, const char *), pool);
+ = svn_string_create(APR_ARRAY_IDX(revprops, i, const char *),
+ scratch_pool);
revprops = new_revprops;
}
+ /* Make sure we catch up on the latest revprop changes. This is the only
+ * time we will refresh the revprop data in this query. */
+ SVN_ERR(svn_fs_refresh_revision_props(fs, scratch_pool));
+
/* Setup log range. */
- SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
+ SVN_ERR(svn_fs_youngest_rev(&head, fs, scratch_pool));
if (! SVN_IS_VALID_REVNUM(start))
start = head;
@@ -2371,7 +2382,7 @@ svn_repos_get_logs4(svn_repos_t *repos,
}
if (! paths)
- paths = apr_array_make(pool, 0, sizeof(const char *));
+ paths = apr_array_make(scratch_pool, 0, sizeof(const char *));
/* If we're not including merged revisions, and we were given no
paths or a single empty (or "/") path, then we can bypass a bunch
@@ -2386,7 +2397,7 @@ svn_repos_get_logs4(svn_repos_t *repos,
{
apr_uint64_t send_count = 0;
int i;
- apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
/* If we are provided an authz callback function, use it to
verify that the user has read access to the root path in the
@@ -2402,9 +2413,10 @@ svn_repos_get_logs4(svn_repos_t *repos,
svn_fs_root_t *rev_root;
SVN_ERR(svn_fs_revision_root(&rev_root, fs,
- descending_order ? end : start, pool));
+ descending_order ? end : start,
+ scratch_pool));
SVN_ERR(authz_read_func(&readable, rev_root, "",
- authz_read_baton, pool));
+ authz_read_baton, scratch_pool));
if (! readable)
return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
}
@@ -2422,10 +2434,9 @@ svn_repos_get_logs4(svn_repos_t *repos,
rev = end - i;
else
rev = start + i;
- SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
- discover_changed_paths, FALSE,
- FALSE, revprops, FALSE, receiver, receiver_baton,
- authz_read_func, authz_read_baton, iterpool));
+ SVN_ERR(send_log(rev, fs, NULL, NULL,
+ FALSE, FALSE, revprops, FALSE,
+ &callbacks, iterpool));
}
svn_pool_destroy(iterpool);
@@ -2439,19 +2450,18 @@ svn_repos_get_logs4(svn_repos_t *repos,
http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
if (include_merged_revisions)
{
- apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
repos, paths, start, end,
authz_read_func,
authz_read_baton,
- pool, subpool));
+ scratch_pool, subpool));
svn_pool_destroy(subpool);
}
- return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
- limit, discover_changed_paths, strict_node_history,
+ return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL,
+ start, end, limit, strict_node_history,
include_merged_revisions, FALSE, FALSE, FALSE,
- revprops, descending_order, receiver, receiver_baton,
- authz_read_func, authz_read_baton, pool);
+ revprops, descending_order, &callbacks, scratch_pool);
}
diff --git a/subversion/libsvn_repos/replay.c b/subversion/libsvn_repos/replay.c
index bcf260c47a04..9bcc667f6da4 100644
--- a/subversion/libsvn_repos/replay.c
+++ b/subversion/libsvn_repos/replay.c
@@ -198,7 +198,7 @@ add_subdir(svn_fs_root_t *source_root,
for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
{
- svn_fs_path_change2_t *change;
+ svn_fs_path_change3_t *change;
svn_boolean_t readable = TRUE;
svn_fs_dirent_t *dent = apr_hash_this_val(hi);
const char *copyfrom_path = NULL;
@@ -412,7 +412,7 @@ fill_copyfrom(svn_fs_root_t **copyfrom_root,
svn_revnum_t *copyfrom_rev,
svn_boolean_t *src_readable,
svn_fs_root_t *root,
- svn_fs_path_change2_t *change,
+ svn_fs_path_change3_t *change,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
const char *path,
@@ -463,7 +463,7 @@ path_driver_cb_func(void **dir_baton,
const svn_delta_editor_t *editor = cb->editor;
void *edit_baton = cb->edit_baton;
svn_fs_root_t *root = cb->root;
- svn_fs_path_change2_t *change;
+ svn_fs_path_change3_t *change;
svn_boolean_t do_add = FALSE, do_delete = FALSE;
void *file_baton = NULL;
svn_revnum_t copyfrom_rev;
@@ -555,10 +555,6 @@ path_driver_cb_func(void **dir_baton,
return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL,
_("Root directory already exists."));
- /* A NULL parent_baton will cause a segfault. It should never be
- NULL for non-root paths. */
- SVN_ERR_ASSERT(parent_baton);
-
/* Was this node copied? */
SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
&src_readable, root, change,
@@ -847,6 +843,80 @@ fetch_props_func(apr_hash_t **props,
+/* Retrieve the path changes under ROOT, filter them with AUTHZ_READ_FUNC
+ and AUTHZ_READ_BATON and return those that intersect with BASE_RELPATH.
+
+ The svn_fs_path_change3_t* will be returned in *CHANGED_PATHS, keyed by
+ their path. The paths themselves are additionally returned in *PATHS.
+
+ Allocate the returned data in RESULT_POOL and use SCRATCH_POOL for
+ temporary allocations.
+ */
+static svn_error_t *
+get_relevant_changes(apr_hash_t **changed_paths,
+ apr_array_header_t **paths,
+ svn_fs_root_t *root,
+ const char *base_relpath,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_path_change_iterator_t *iterator;
+ svn_fs_path_change3_t *change;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Fetch the paths changed under ROOT. */
+ SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
+
+ /* Make an array from the keys of our CHANGED_PATHS hash, and copy
+ the values into a new hash whose keys have no leading slashes. */
+ *paths = apr_array_make(result_pool, 16, sizeof(const char *));
+ *changed_paths = apr_hash_make(result_pool);
+ while (change)
+ {
+ const char *path = change->path.data;
+ apr_ssize_t keylen = change->path.len;
+ svn_boolean_t allowed = TRUE;
+
+ svn_pool_clear(iterpool);
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
+ iterpool));
+
+ if (allowed)
+ {
+ if (path[0] == '/')
+ {
+ path++;
+ keylen--;
+ }
+
+ /* If the base_path doesn't match the top directory of this path
+ we don't want anything to do with it...
+ ...unless this was a change to one of the parent directories of
+ base_path. */
+ if ( svn_relpath_skip_ancestor(base_relpath, path)
+ || svn_relpath_skip_ancestor(path, base_relpath))
+ {
+ change = svn_fs_path_change3_dup(change, result_pool);
+ path = change->path.data;
+ if (path[0] == '/')
+ path++;
+
+ APR_ARRAY_PUSH(*paths, const char *) = path;
+ apr_hash_set(*changed_paths, path, keylen, change);
+ }
+ }
+
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_repos_replay2(svn_fs_root_t *root,
const char *base_path,
@@ -859,9 +929,7 @@ svn_repos_replay2(svn_fs_root_t *root,
apr_pool_t *pool)
{
#ifndef USE_EV2_IMPL
- apr_hash_t *fs_changes;
apr_hash_t *changed_paths;
- apr_hash_index_t *hi;
apr_array_header_t *paths;
struct path_driver_cb_baton cb_baton;
@@ -873,54 +941,15 @@ svn_repos_replay2(svn_fs_root_t *root,
return SVN_NO_ERROR;
}
- /* Fetch the paths changed under ROOT. */
- SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
-
if (! base_path)
base_path = "";
else if (base_path[0] == '/')
++base_path;
- /* Make an array from the keys of our CHANGED_PATHS hash, and copy
- the values into a new hash whose keys have no leading slashes. */
- paths = apr_array_make(pool, apr_hash_count(fs_changes),
- sizeof(const char *));
- changed_paths = apr_hash_make(pool);
- for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
- {
- const char *path = apr_hash_this_key(hi);
- apr_ssize_t keylen = apr_hash_this_key_len(hi);
- svn_fs_path_change2_t *change = apr_hash_this_val(hi);
- svn_boolean_t allowed = TRUE;
-
- if (authz_read_func)
- SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
- pool));
-
- if (allowed)
- {
- if (path[0] == '/')
- {
- path++;
- keylen--;
- }
-
- /* If the base_path doesn't match the top directory of this path
- we don't want anything to do with it... */
- if (svn_relpath_skip_ancestor(base_path, path) != NULL)
- {
- APR_ARRAY_PUSH(paths, const char *) = path;
- apr_hash_set(changed_paths, path, keylen, change);
- }
- /* ...unless this was a change to one of the parent directories of
- base_path. */
- else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
- {
- APR_ARRAY_PUSH(paths, const char *) = path;
- apr_hash_set(changed_paths, path, keylen, change);
- }
- }
- }
+ /* Fetch the paths changed under ROOT. */
+ SVN_ERR(get_relevant_changes(&changed_paths, &paths, root, base_path,
+ authz_read_func, authz_read_baton,
+ pool, pool));
/* If we were not given a low water mark, assume that everything is there,
all the way back to revision 0. */
@@ -971,6 +1000,11 @@ svn_repos_replay2(svn_fs_root_t *root,
const char *repos_root = "";
void *unlock_baton;
+ /* If we were not given a low water mark, assume that everything is there,
+ all the way back to revision 0. */
+ if (! SVN_IS_VALID_REVNUM(low_water_mark))
+ low_water_mark = 0;
+
/* Special-case r0, which we know is an empty revision; if we don't
special-case it we might end up trying to compare it to "r-1". */
if (svn_fs_is_revision_root(root)
@@ -1061,7 +1095,7 @@ add_subdir_ev2(svn_fs_root_t *source_root,
for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
{
- svn_fs_path_change2_t *change;
+ svn_fs_path_change3_t *change;
svn_boolean_t readable = TRUE;
svn_fs_dirent_t *dent = apr_hash_this_val(hi);
const char *copyfrom_path = NULL;
@@ -1189,7 +1223,7 @@ replay_node(svn_fs_root_t *root,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- svn_fs_path_change2_t *change;
+ svn_fs_path_change3_t *change;
svn_boolean_t do_add = FALSE;
svn_boolean_t do_delete = FALSE;
svn_revnum_t copyfrom_rev;
@@ -1484,16 +1518,14 @@ svn_repos__replay_ev2(svn_fs_root_t *root,
void *authz_read_baton,
apr_pool_t *scratch_pool)
{
- apr_hash_t *fs_changes;
apr_hash_t *changed_paths;
- apr_hash_index_t *hi;
apr_array_header_t *paths;
apr_array_header_t *copies;
apr_pool_t *iterpool;
svn_error_t *err = SVN_NO_ERROR;
int i;
- SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath));
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(base_repos_relpath));
/* Special-case r0, which we know is an empty revision; if we don't
special-case it we might end up trying to compare it to "r-1". */
@@ -1504,49 +1536,10 @@ svn_repos__replay_ev2(svn_fs_root_t *root,
}
/* Fetch the paths changed under ROOT. */
- SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
-
- /* Make an array from the keys of our CHANGED_PATHS hash, and copy
- the values into a new hash whose keys have no leading slashes. */
- paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes),
- sizeof(const char *));
- changed_paths = apr_hash_make(scratch_pool);
- for (hi = apr_hash_first(scratch_pool, fs_changes); hi;
- hi = apr_hash_next(hi))
- {
- const char *path = apr_hash_this_key(hi);
- apr_ssize_t keylen = apr_hash_this_key_len(hi);
- svn_fs_path_change2_t *change = apr_hash_this_val(hi);
- svn_boolean_t allowed = TRUE;
-
- if (authz_read_func)
- SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
- scratch_pool));
-
- if (allowed)
- {
- if (path[0] == '/')
- {
- path++;
- keylen--;
- }
-
- /* If the base_path doesn't match the top directory of this path
- we don't want anything to do with it... */
- if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
- {
- APR_ARRAY_PUSH(paths, const char *) = path;
- apr_hash_set(changed_paths, path, keylen, change);
- }
- /* ...unless this was a change to one of the parent directories of
- base_path. */
- else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
- {
- APR_ARRAY_PUSH(paths, const char *) = path;
- apr_hash_set(changed_paths, path, keylen, change);
- }
- }
- }
+ SVN_ERR(get_relevant_changes(&changed_paths, &paths, root,
+ base_repos_relpath,
+ authz_read_func, authz_read_baton,
+ scratch_pool, scratch_pool));
/* If we were not given a low water mark, assume that everything is there,
all the way back to revision 0. */
diff --git a/subversion/libsvn_repos/reporter.c b/subversion/libsvn_repos/reporter.c
index 76c72016144c..f56cb2ee980c 100644
--- a/subversion/libsvn_repos/reporter.c
+++ b/subversion/libsvn_repos/reporter.c
@@ -481,10 +481,12 @@ get_revision_info(report_baton_t *b,
{
/* Info is not available, yet.
Get all revprops. */
- SVN_ERR(svn_fs_revision_proplist(&r_props,
- b->repos->fs,
- rev,
- scratch_pool));
+ SVN_ERR(svn_fs_revision_proplist2(&r_props,
+ b->repos->fs,
+ rev,
+ FALSE,
+ scratch_pool,
+ scratch_pool));
/* Extract the committed-date. */
cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
@@ -520,19 +522,13 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
{
svn_fs_root_t *s_root;
apr_hash_t *s_props = NULL, *t_props;
- apr_array_header_t *prop_diffs;
- int i;
svn_revnum_t crev;
- revision_info_t *revision_info;
- svn_boolean_t changed;
- const svn_prop_t *pc;
- svn_lock_t *lock;
- apr_hash_index_t *hi;
/* Fetch the created-rev and send entry props. */
SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
if (SVN_IS_VALID_REVNUM(crev))
{
+ revision_info_t *revision_info;
/* convert committed-rev to string */
char buf[SVN_INT64_BUFFER_SIZE];
svn_string_t cr_str;
@@ -563,6 +559,7 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
/* Update lock properties. */
if (lock_token)
{
+ svn_lock_t *lock;
SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool));
/* Delete a defunct lock. */
@@ -573,6 +570,7 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
if (s_path)
{
+ svn_boolean_t changed;
SVN_ERR(get_source_root(b, &s_root, s_rev));
/* Is this deltification worth our time? */
@@ -590,16 +588,20 @@ delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
if (s_props && apr_hash_count(s_props))
{
+ apr_array_header_t *prop_diffs;
+ int i;
+
/* Now transmit the differences. */
SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
for (i = 0; i < prop_diffs->nelts; i++)
{
- pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+ const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
}
}
else if (apr_hash_count(t_props))
{
+ apr_hash_index_t *hi;
/* So source, i.e. all new. Transmit all target props. */
for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi))
{
@@ -669,7 +671,6 @@ delta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
const char *s_path, const char *t_path, const char *lock_token,
apr_pool_t *pool)
{
- svn_boolean_t changed;
svn_fs_root_t *s_root = NULL;
svn_txdelta_stream_t *dstream = NULL;
svn_checksum_t *s_checksum;
@@ -683,14 +684,15 @@ delta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
if (s_path)
{
+ svn_boolean_t changed;
SVN_ERR(get_source_root(b, &s_root, s_rev));
/* We're not interested in the theoretical difference between "has
contents which have not changed with respect to" and "has the same
actual contents as" when sending text-deltas. If we know the
delta is an empty one, we avoiding sending it in either case. */
- SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
- s_root, s_path, pool));
+ SVN_ERR(svn_fs_contents_different(&changed, b->t_root, t_path,
+ s_root, s_path, pool));
if (!changed)
return SVN_NO_ERROR;
@@ -971,11 +973,11 @@ update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
if (s_root == NULL)
SVN_ERR(get_source_root(b, &s_root, s_rev));
- SVN_ERR(svn_fs_props_different(&changed, s_root, s_path,
- b->t_root, t_path, pool));
+ SVN_ERR(svn_fs_props_changed(&changed, s_root, s_path,
+ b->t_root, t_path, pool));
if (!changed)
- SVN_ERR(svn_fs_contents_different(&changed, s_root, s_path,
- b->t_root, t_path, pool));
+ SVN_ERR(svn_fs_contents_changed(&changed, s_root, s_path,
+ b->t_root, t_path, pool));
}
if ((distance == 0 || !changed) && !any_path_info(b, e_path)
@@ -1567,6 +1569,7 @@ svn_repos_finish_report(void *baton, apr_pool_t *pool)
{
report_baton_t *b = baton;
+ SVN_ERR(svn_fs_refresh_revision_props(svn_repos_fs(b->repos), pool));
return svn_error_trace(finish_report(b, pool));
}
diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
index 1d62aeb501f1..2333f561efda 100644
--- a/subversion/libsvn_repos/repos.c
+++ b/subversion/libsvn_repos/repos.c
@@ -393,15 +393,15 @@ create_hooks(svn_repos_t *repos, apr_pool_t *pool)
"# e.g.: \"" SVN_RA_CAPABILITY_MERGEINFO ":some-other-capability\" " \
"(the order is undefined)." NL
"#" NL
+"# The list is self-reported by the client. Therefore, you should not" NL
+"# make security assumptions based on the capabilities list, nor should" NL
+"# you assume that clients reliably report every capability they have." NL
+"#" NL
"# Note: The TXN-NAME parameter is new in Subversion 1.8. Prior to version" NL
"# 1.8, the start-commit hook was invoked before the commit txn was even" NL
"# created, so the ability to inspect the commit txn and its metadata from" NL
"# within the start-commit hook was not possible." NL
"# " NL
-"# The list is self-reported by the client. Therefore, you should not" NL
-"# make security assumptions based on the capabilities list, nor should" NL
-"# you assume that clients reliably report every capability they have." NL
-"#" NL
"# If the hook program exits with success, the commit continues; but" NL
"# if it exits with failure (non-zero), the commit is stopped before" NL
"# a Subversion txn is created, and STDERR is returned to the client." NL;
@@ -882,7 +882,7 @@ create_conf(svn_repos_t *repos, apr_pool_t *pool)
"[sasl]" NL
"### This option specifies whether you want to use the Cyrus SASL" NL
"### library for authentication. Default is false." NL
-"### This section will be ignored if svnserve is not built with Cyrus" NL
+"### Enabling this option requires svnserve to have been built with Cyrus" NL
"### SASL support; to check, run 'svnserve --version' and look for a line" NL
"### reading 'Cyrus SASL authentication is available.'" NL
"# use-sasl = true" NL
@@ -1180,8 +1180,8 @@ svn_repos_create(svn_repos_t **repos_p,
SVN_ERR(lock_repos(repos, FALSE, FALSE, scratch_pool));
/* Create an environment for the filesystem. */
- if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config,
- result_pool)))
+ if ((err = svn_fs_create2(&repos->fs, repos->db_path, fs_config,
+ result_pool, scratch_pool)))
{
/* If there was an error making the filesytem, e.g. unknown/supported
* filesystem type. Clean up after ourselves. Yes this is safe because
@@ -1504,6 +1504,16 @@ static const char *capability_yes = "yes";
/* Repository does not support the capability. */
static const char *capability_no = "no";
+static svn_error_t *
+dummy_mergeinfo_receiver(const char *path,
+ svn_mergeinfo_t mergeinfo,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
svn_error_t *
svn_repos_has_capability(svn_repos_t *repos,
svn_boolean_t *has,
@@ -1525,14 +1535,13 @@ svn_repos_has_capability(svn_repos_t *repos,
{
svn_error_t *err;
svn_fs_root_t *root;
- svn_mergeinfo_catalog_t ignored;
apr_array_header_t *paths = apr_array_make(pool, 1,
sizeof(char *));
SVN_ERR(svn_fs_revision_root(&root, repos->fs, 0, pool));
APR_ARRAY_PUSH(paths, const char *) = "";
- err = svn_fs_get_mergeinfo2(&ignored, root, paths, FALSE, FALSE,
- TRUE, pool, pool);
+ err = svn_fs_get_mergeinfo3(root, paths, FALSE, FALSE, TRUE,
+ dummy_mergeinfo_receiver, NULL, pool);
if (err)
{
@@ -2061,45 +2070,6 @@ svn_repos_version(void)
SVN_VERSION_BODY;
}
-
-
-svn_error_t *
-svn_repos_stat(svn_dirent_t **dirent,
- svn_fs_root_t *root,
- const char *path,
- apr_pool_t *pool)
-{
- svn_node_kind_t kind;
- svn_dirent_t *ent;
- const char *datestring;
-
- SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
-
- if (kind == svn_node_none)
- {
- *dirent = NULL;
- return SVN_NO_ERROR;
- }
-
- ent = svn_dirent_create(pool);
- ent->kind = kind;
-
- if (kind == svn_node_file)
- SVN_ERR(svn_fs_file_length(&(ent->size), root, path, pool));
-
- SVN_ERR(svn_fs_node_has_props(&ent->has_props, root, path, pool));
-
- SVN_ERR(svn_repos_get_committed_info(&(ent->created_rev),
- &datestring,
- &(ent->last_author),
- root, path, pool));
- if (datestring)
- SVN_ERR(svn_time_from_cstring(&(ent->time), datestring, pool));
-
- *dirent = ent;
- return SVN_NO_ERROR;
-}
-
svn_error_t *
svn_repos_remember_client_capabilities(svn_repos_t *repos,
const apr_array_header_t *capabilities)
diff --git a/subversion/libsvn_repos/repos.h b/subversion/libsvn_repos/repos.h
index b1039acd8d9f..2274032dd769 100644
--- a/subversion/libsvn_repos/repos.h
+++ b/subversion/libsvn_repos/repos.h
@@ -27,6 +27,7 @@
#include <apr_hash.h>
#include "svn_fs.h"
+#include "svn_config.h"
#ifdef __cplusplus
extern "C" {
@@ -362,45 +363,8 @@ svn_repos__hooks_post_unlock(svn_repos_t *repos,
apr_pool_t *pool);
-/*** Authz Functions ***/
-
-/* Read authz configuration data from PATH into *AUTHZ_P, allocated
- in POOL. If GROUPS_PATH is set, use the global groups parsed from it.
-
- PATH and GROUPS_PATH may be a dirent or a registry path and iff ACCEPT_URLS
- is set it may also be an absolute file url.
-
- If PATH or GROUPS_PATH is not a valid authz rule file, then return
- SVN_AUTHZ_INVALID_CONFIG. The contents of *AUTHZ_P is then
- undefined. If MUST_EXIST is TRUE, a missing authz or global groups file
- is also an error. */
-svn_error_t *
-svn_repos__authz_read(svn_authz_t **authz_p,
- const char *path,
- const char *groups_path,
- svn_boolean_t must_exist,
- svn_boolean_t accept_urls,
- apr_pool_t *pool);
-
-/* Walk the configuration in AUTHZ looking for any errors. */
-svn_error_t *
-svn_repos__authz_validate(svn_authz_t *authz,
- apr_pool_t *pool);
-
-
/*** Utility Functions ***/
-/* Set *CHANGED_P to TRUE if ROOT1/PATH1 and ROOT2/PATH2 have
- different contents, FALSE if they have the same contents.
- Use POOL for temporary allocation. */
-svn_error_t *
-svn_repos__compare_files(svn_boolean_t *changed_p,
- svn_fs_root_t *root1,
- const char *path1,
- svn_fs_root_t *root2,
- const char *path2,
- apr_pool_t *pool);
-
/* Set *PREV_PATH and *PREV_REV to the path and revision which
represent the location at which PATH in FS was located immediately
prior to REVISION iff there was a copy operation (to PATH or one of
diff --git a/subversion/libsvn_repos/rev_hunt.c b/subversion/libsvn_repos/rev_hunt.c
index d6cc49576d12..558d691b9bfe 100644
--- a/subversion/libsvn_repos/rev_hunt.c
+++ b/subversion/libsvn_repos/rev_hunt.c
@@ -44,7 +44,7 @@
/* Note: this binary search assumes that the datestamp properties on
each revision are in chronological order. That is if revision A >
- revision B, then A's datestamp is younger then B's datestamp.
+ revision B, then A's datestamp is younger than B's datestamp.
If someone comes along and sets a bogus datestamp, this routine
might not work right.
@@ -65,8 +65,8 @@ get_time(apr_time_t *tm,
{
svn_string_t *date_str;
- SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
- pool));
+ SVN_ERR(svn_fs_revision_prop2(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
+ FALSE, pool, pool));
if (! date_str)
return svn_error_createf
(SVN_ERR_FS_GENERAL, NULL,
@@ -88,6 +88,7 @@ svn_repos_dated_revision(svn_revnum_t *revision,
/* Initialize top and bottom values of binary search. */
SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
+ SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
rev_bot = 0;
rev_top = rev_latest;
@@ -170,7 +171,8 @@ svn_repos_get_committed_info(svn_revnum_t *committed_rev,
SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
/* Get the revision properties of this revision. */
- SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
+ SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, *committed_rev, TRUE,
+ pool, pool));
/* Extract date and author from these revprops. */
committed_date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
@@ -669,8 +671,7 @@ svn_repos_trace_node_locations(svn_fs_t *fs,
/* First - let's sort the array of the revisions from the greatest revision
* downward, so it will be easier to search on. */
location_revisions = apr_array_copy(pool, location_revisions_orig);
- qsort(location_revisions->elts, location_revisions->nelts,
- sizeof(*revision_ptr), svn_sort_compare_revisions);
+ svn_sort__array(location_revisions, svn_sort_compare_revisions);
revision_ptr = (svn_revnum_t *)location_revisions->elts;
revision_ptr_end = revision_ptr + location_revisions->nelts;
@@ -835,27 +836,32 @@ svn_repos_node_location_segments(svn_repos_t *repos,
{
svn_fs_t *fs = svn_repos_fs(repos);
svn_stringbuf_t *current_path;
- svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
+ svn_revnum_t youngest_rev, current_rev;
apr_pool_t *subpool;
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
+
/* No PEG_REVISION? We'll use HEAD. */
if (! SVN_IS_VALID_REVNUM(peg_revision))
- {
- SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
- peg_revision = youngest_rev;
- }
+ peg_revision = youngest_rev;
+
+ if (peg_revision > youngest_rev)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), peg_revision);
- /* No START_REV? We'll use HEAD (which we may have already fetched). */
+ /* No START_REV? We'll use peg rev. */
if (! SVN_IS_VALID_REVNUM(start_rev))
- {
- if (SVN_IS_VALID_REVNUM(youngest_rev))
- start_rev = youngest_rev;
- else
- SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
- }
+ start_rev = peg_revision;
+ else if (start_rev > peg_revision)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), start_rev);
/* No END_REV? We'll use 0. */
- end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
+ if (! SVN_IS_VALID_REVNUM(end_rev))
+ end_rev = 0;
+ else if (end_rev > start_rev)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), end_rev);
/* Are the revision properly ordered? They better be -- the API
demands it. */
@@ -1006,26 +1012,39 @@ get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
svn_error_t *err;
svn_fs_root_t *root, *prev_root;
- apr_hash_t *changed_paths;
- const char *path = old_path_rev->path;
+ const char *start_path = old_path_rev->path;
+ const char *path = NULL;
+
+ svn_fs_path_change_iterator_t *iterator;
+ svn_fs_path_change3_t *change;
/* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
if there is a property change. */
SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
scratch_pool));
- SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
- while (1)
+ SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
+
+ /* Find the changed PATH closest to START_PATH which may have a mergeinfo
+ * change. */
+ while (change)
{
- svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
- if (changed_path && changed_path->prop_mod
- && changed_path->mergeinfo_mod != svn_tristate_false)
- break;
- if (svn_fspath__is_root(path, strlen(path)))
+ if ( change->prop_mod
+ && change->mergeinfo_mod != svn_tristate_false
+ && svn_fspath__skip_ancestor(change->path.data, start_path))
{
- *merged_mergeinfo = NULL;
- return SVN_NO_ERROR;
+ if (!path || svn_fspath__skip_ancestor(path, change->path.data))
+ path = apr_pstrmemdup(scratch_pool, change->path.data,
+ change->path.len);
}
- path = svn_fspath__dirname(path, scratch_pool);
+
+ SVN_ERR(svn_fs_path_change_get(&change, iterator));
+ }
+
+ if (path == NULL)
+ {
+ *merged_mergeinfo = NULL;
+ return SVN_NO_ERROR;
}
/* First, find the mergeinfo difference for old_path_rev->revnum, and
@@ -1350,22 +1369,48 @@ send_path_revision(struct path_revision *path_rev,
void *delta_baton = NULL;
apr_pool_t *tmp_pool; /* For swapping */
svn_boolean_t contents_changed;
+ svn_boolean_t props_changed;
svn_pool_clear(sb->iterpool);
/* Get the revision properties. */
- SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
- path_rev->revnum, sb->iterpool));
+ SVN_ERR(svn_fs_revision_proplist2(&rev_props, repos->fs,
+ path_rev->revnum, FALSE,
+ sb->iterpool, sb->iterpool));
/* Open the revision root. */
SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
sb->iterpool));
- /* Get the file's properties for this revision and compute the diffs. */
- SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
+ /* Check if the props *may* have changed. */
+ if (sb->last_root)
+ {
+ /* We don't use svn_fs_props_different() because it's more
+ * expensive. */
+ SVN_ERR(svn_fs_props_changed(&props_changed,
+ sb->last_root, sb->last_path,
+ root, path_rev->path, sb->iterpool));
+ }
+ else
+ {
+ props_changed = TRUE;
+ }
+
+ /* Calculate actual difference between last and current properties. */
+ if (props_changed)
+ {
+ /* Get the file's properties for this revision and compute the diffs. */
+ SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
sb->iterpool));
- SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
- sb->iterpool));
+ SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
+ sb->iterpool));
+ }
+ else
+ {
+ /* Properties didn't change: copy LAST_PROPS to current POOL. */
+ props = svn_prop_hash_dup(sb->last_props, sb->iterpool);
+ prop_diffs = apr_array_make(sb->iterpool, 0, sizeof(svn_prop_t));
+ }
/* Check if the contents *may* have changed. */
if (! sb->last_root)
@@ -1590,6 +1635,10 @@ svn_repos_get_file_revs2(svn_repos_t *repos,
end = youngest_rev;
}
+ /* Make sure we catch up on the latest revprop changes. This is the only
+ * time we will refresh the revprop data in this query. */
+ SVN_ERR(svn_fs_refresh_revision_props(repos->fs, scratch_pool));
+
if (end < start)
{
if (include_merged_revisions)