diff options
Diffstat (limited to 'subversion/libsvn_repos')
-rw-r--r-- | subversion/libsvn_repos/authz.c | 55 | ||||
-rw-r--r-- | subversion/libsvn_repos/authz.h | 15 | ||||
-rw-r--r-- | subversion/libsvn_repos/authz_info.c | 53 | ||||
-rw-r--r-- | subversion/libsvn_repos/authz_parse.c | 95 | ||||
-rw-r--r-- | subversion/libsvn_repos/commit.c | 30 | ||||
-rw-r--r-- | subversion/libsvn_repos/config_file.c | 2 | ||||
-rw-r--r-- | subversion/libsvn_repos/config_file.h | 2 | ||||
-rw-r--r-- | subversion/libsvn_repos/delta.c | 14 | ||||
-rw-r--r-- | subversion/libsvn_repos/deprecated.c | 29 | ||||
-rw-r--r-- | subversion/libsvn_repos/dump.c | 90 | ||||
-rw-r--r-- | subversion/libsvn_repos/dump_editor.c | 1040 | ||||
-rw-r--r-- | subversion/libsvn_repos/fs-wrap.c | 4 | ||||
-rw-r--r-- | subversion/libsvn_repos/libsvn_repos.pc.in | 8 | ||||
-rw-r--r-- | subversion/libsvn_repos/list.c | 2 | ||||
-rw-r--r-- | subversion/libsvn_repos/load-fs-vtable.c | 55 | ||||
-rw-r--r-- | subversion/libsvn_repos/load.c | 58 | ||||
-rw-r--r-- | subversion/libsvn_repos/log.c | 6 | ||||
-rw-r--r-- | subversion/libsvn_repos/replay.c | 13 | ||||
-rw-r--r-- | subversion/libsvn_repos/repos.c | 6 |
19 files changed, 1421 insertions, 156 deletions
diff --git a/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c index 668d78dd5993..9f8dbc5a08f7 100644 --- a/subversion/libsvn_repos/authz.c +++ b/subversion/libsvn_repos/authz.c @@ -81,11 +81,11 @@ typedef struct limited_rights_t */ path_access_t access; - /* Minimal access rights that the user has on this or any other node in + /* 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; - /* Maximal access rights that the user has on this or any other node in + /* 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; @@ -369,7 +369,7 @@ ensure_node_in_array(apr_array_header_t **array, * 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); + svn_error_clear(svn_sort__array_insert2(*array, &entry, idx)); return entry.node; } @@ -889,7 +889,7 @@ create_user_authz(authz_full_t *authz, /* Use a separate sub-pool to keep memory usage tight. */ apr_pool_t *subpool = svn_pool_create(scratch_pool); - /* Find all ACLs for REPOSITORY. + /* 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, @@ -947,7 +947,7 @@ create_user_authz(authz_full_t *authz, svn_pool_clear(subpool); trim_tree(root, NO_SEQUENCE_NUMBER, subpool); - /* Calculate recursive rights. + /* 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 @@ -999,7 +999,7 @@ static lookup_state_t * create_lookup_state(apr_pool_t *result_pool) { 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 *)); @@ -1548,6 +1548,8 @@ authz_read(authz_full_t **authz_p, const char *groups_path, svn_boolean_t must_exist, svn_repos_t *repos_hint, + svn_repos_authz_warning_func_t warning_func, + void *warning_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1587,7 +1589,8 @@ authz_read(authz_full_t **authz_p, /* 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); + warning_func, warning_baton, + item_pool, scratch_pool); if (err != SVN_NO_ERROR) { /* That pool would otherwise never get destroyed. */ @@ -1611,11 +1614,11 @@ authz_read(authz_full_t **authz_p, { /* 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); + err = svn_error_quick_wrapf( + svn_authz__parse(authz_p, rules_stream, groups_stream, + warning_func, warning_baton, + result_pool, scratch_pool), + "Error while parsing authz file: '%s':", path); } svn_repos__destroy_config_access(config_access); @@ -1628,11 +1631,13 @@ authz_read(authz_full_t **authz_p, /*** Public functions. ***/ svn_error_t * -svn_repos_authz_read3(svn_authz_t **authz_p, +svn_repos_authz_read4(svn_authz_t **authz_p, const char *path, const char *groups_path, svn_boolean_t must_exist, svn_repos_t *repos_hint, + svn_repos_authz_warning_func_t warning_func, + void *warning_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -1640,7 +1645,8 @@ svn_repos_authz_read3(svn_authz_t **authz_p, authz->pool = result_pool; SVN_ERR(authz_read(&authz->full, &authz->authz_id, path, groups_path, - must_exist, repos_hint, result_pool, scratch_pool)); + must_exist, repos_hint, warning_func, warning_baton, + result_pool, scratch_pool)); *authz_p = authz; return SVN_NO_ERROR; @@ -1648,18 +1654,21 @@ svn_repos_authz_read3(svn_authz_t **authz_p, 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_repos_authz_parse2(svn_authz_t **authz_p, + svn_stream_t *stream, + svn_stream_t *groups_stream, + svn_repos_authz_warning_func_t warning_func, + void *warning_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - apr_pool_t *scratch_pool = svn_pool_create(pool); - svn_authz_t *authz = apr_pcalloc(pool, sizeof(*authz)); - authz->pool = pool; + svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz)); + authz->pool = result_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)); - - svn_pool_destroy(scratch_pool); + SVN_ERR(svn_authz__parse(&authz->full, stream, groups_stream, + warning_func, warning_baton, + result_pool, scratch_pool)); *authz_p = authz; return SVN_NO_ERROR; diff --git a/subversion/libsvn_repos/authz.h b/subversion/libsvn_repos/authz.h index 7187335a94fd..4a62a768cb7a 100644 --- a/subversion/libsvn_repos/authz.h +++ b/subversion/libsvn_repos/authz.h @@ -139,6 +139,10 @@ typedef struct authz_full_t svn_boolean_t has_authn_rights; authz_global_rights_t authn_rights; + /* Globally accumulated rights from inverted selectors. */ + svn_boolean_t has_neg_rights; + authz_global_rights_t neg_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*. */ @@ -257,14 +261,19 @@ typedef struct authz_acl_t /* The parsed rule. */ authz_rule_t rule; - /* Access rights for anonymous users */ + + /* Access rights for anonymous users. */ svn_boolean_t has_anon_access; authz_access_t anon_access; - /* Access rights for authenticated users */ + /* Access rights for authenticated users. */ svn_boolean_t has_authn_access; authz_access_t authn_access; + /* Access rights from inverted selectors. */ + svn_boolean_t has_neg_access; + authz_access_t neg_access; + /* All other user- or group-specific access rights. Aliases are replaced with their definitions, rules for the same user or group are merged. */ @@ -303,6 +312,8 @@ svn_error_t * svn_authz__parse(authz_full_t **authz, svn_stream_t *rules, svn_stream_t *groups, + svn_repos_authz_warning_func_t warning_func, + void *warning_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool); diff --git a/subversion/libsvn_repos/authz_info.c b/subversion/libsvn_repos/authz_info.c index 8f3a8b63b12a..ff102fd4bef3 100644 --- a/subversion/libsvn_repos/authz_info.c +++ b/subversion/libsvn_repos/authz_info.c @@ -148,37 +148,50 @@ svn_authz__get_global_rights(authz_rights_t *rights_p, { /* Check if we have explicit rights for anonymous access. */ if (authz->has_anon_rights) - return resolve_global_rights(rights_p, &authz->anon_rights, repos); + { + return resolve_global_rights(rights_p, &authz->anon_rights, repos); + } + else + { + /* Return the implicit rights, i.e., none. */ + rights_p->min_access = authz_access_none; + rights_p->max_access = authz_access_none; + return FALSE; + } } else { + svn_boolean_t combine_user_rights = FALSE; + svn_boolean_t access = FALSE; + /* 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; + access = resolve_global_rights(rights_p, user_rights, repos); + combine_user_rights = TRUE; + } + else if (authz->has_neg_rights) + { + /* Check if inverted-rule rights apply */ + access = resolve_global_rights(rights_p, &authz->neg_rights, repos); + combine_user_rights = TRUE; } - /* Check if we have explicit rights for authenticated access. */ + /* Rights given to _any_ authenticated user may apply, too. */ if (authz->has_authn_rights) - return resolve_global_rights(rights_p, &authz->authn_rights, repos); - } + { + authz_rights_t authn; + access |= resolve_global_rights(&authn, &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; + if (combine_user_rights) + combine_rights(rights_p, rights_p, &authn); + else + *rights_p = authn; + } + + return access; + } } diff --git a/subversion/libsvn_repos/authz_parse.c b/subversion/libsvn_repos/authz_parse.c index 8b5893ea576a..4fb064d5a855 100644 --- a/subversion/libsvn_repos/authz_parse.c +++ b/subversion/libsvn_repos/authz_parse.c @@ -127,6 +127,10 @@ typedef struct ctor_baton_t svn_membuf_t rule_path_buffer; svn_stringbuf_t *rule_string_buffer; + /* The warning callback and its baton. */ + svn_repos_authz_warning_func_t warning_func; + void *warning_baton; + /* 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. @@ -154,6 +158,8 @@ static const char anon_access_token[] = "$anonymous"; /* The authenticated access token. */ static const char authn_access_token[] = "$authenticated"; +/* Fake token for inverted rights. */ +static const char neg_access_token[] = "~~$inverted"; /* Initialize a rights structure. The minimum rights start with all available access and are later @@ -191,6 +197,8 @@ insert_default_acl(ctor_baton_t *cb) acl->acl.has_anon_access = TRUE; acl->acl.authn_access = authz_access_none; acl->acl.has_authn_access = TRUE; + acl->acl.neg_access = authz_access_none; + acl->acl.has_neg_access = TRUE; acl->acl.user_access = NULL; acl->aces = svn_hash__make(cb->parser_pool); acl->alias_aces = svn_hash__make(cb->parser_pool); @@ -199,7 +207,9 @@ insert_default_acl(ctor_baton_t *cb) /* Initialize a constuctor baton. */ static ctor_baton_t * -create_ctor_baton(apr_pool_t *result_pool, +create_ctor_baton(svn_repos_authz_warning_func_t warning_func, + void *warning_baton, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_pool_t *const parser_pool = svn_pool_create(scratch_pool); @@ -208,6 +218,7 @@ create_ctor_baton(apr_pool_t *result_pool, 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); + init_global_rights(&authz->neg_rights, neg_access_token, result_pool); authz->user_rights = svn_hash__make(result_pool); authz->pool = result_pool; @@ -229,6 +240,9 @@ create_ctor_baton(apr_pool_t *result_pool, svn_membuf__create(&cb->rule_path_buffer, 0, parser_pool); cb->rule_string_buffer = svn_stringbuf_create_empty(parser_pool); + cb->warning_func = warning_func; + cb->warning_baton = warning_baton; + cb->parser_pool = parser_pool; insert_default_acl(cb); @@ -237,6 +251,25 @@ create_ctor_baton(apr_pool_t *result_pool, } +/* Emit a warning. Clears ERROR */ +static void +emit_parser_warning(const ctor_baton_t *cb, + svn_error_t *error, + apr_pool_t *scratch_pool) +{ + if (cb->warning_func) + cb->warning_func(cb->warning_baton, error, scratch_pool); + svn_error_clear(error); +} + +/* Avoid creating an error struct if there is no warning function. */ +#define SVN_AUTHZ_PARSE_WARN(cb, err, pool) \ + do { \ + if ((cb) && (cb)->warning_func) \ + emit_parser_warning((cb), (err), (pool)); \ + } while(0) + + /* Create and store per-user global rights. The USER string must be interned or statically initialized. */ static void @@ -536,7 +569,7 @@ parse_rule_path(authz_rule_t *rule, || (pattern->len == 2 && pattern->data[1] == '*')) { /* Process * and **, applying normalization as per - https://wiki.apache.org/subversion/AuthzImprovements. */ + https://cwiki.apache.org/confluence/display/SVN/Authz+Improvements. */ authz_rule_segment_t *const prev = (nseg > 1 ? segment - 1 : NULL); @@ -758,6 +791,8 @@ rules_open_section(void *baton, svn_stringbuf_t *section) acl.acl.has_anon_access = FALSE; acl.acl.authn_access = authz_access_none; acl.acl.has_authn_access = FALSE; + acl.acl.neg_access = authz_access_none; + acl.acl.has_neg_access = FALSE; acl.acl.user_access = NULL; acl.aces = svn_hash__make(cb->parser_pool); @@ -958,6 +993,14 @@ add_access_entry(ctor_baton_t *cb, svn_stringbuf_t *section, if (!aliased && *ace->name != '@') prepare_global_rights(cb, ace->name); } + + /* Propagate rights for inverted selectors to the global rights, otherwise + an access check can bail out early. See: SVN-4793 */ + if (inverted) + { + acl->acl.has_neg_access = TRUE; + acl->acl.neg_access |= access; + } } return SVN_NO_ERROR; @@ -996,7 +1039,8 @@ close_section(void *baton, svn_stringbuf_t *section) /* Add a user to GROUP. - GROUP is never internalized, but USER always is. */ + GROUP is never internalized, but USER always is. + Adding a NULL user will create an empty group, if it doesn't exist. */ static void add_to_group(ctor_baton_t *cb, const char *group, const char *user) { @@ -1007,7 +1051,8 @@ add_to_group(ctor_baton_t *cb, const char *group, const char *user) members = svn_hash__make(cb->authz->pool); svn_hash_sets(cb->expanded_groups, group, members); } - svn_hash_sets(members, user, interned_empty_string); + if (user) + svn_hash_sets(members, user, interned_empty_string); } @@ -1023,8 +1068,15 @@ expand_group_callback(void *baton, ctor_baton_t *const cb = baton; const char *const group = key; apr_array_header_t *members = value; - int i; + + if (0 == members->nelts) + { + /* Create the group with no members. */ + add_to_group(cb, group, NULL); + return SVN_NO_ERROR; + } + for (i = 0; i < members->nelts; ++i) { const char *member = APR_ARRAY_IDX(members, i, const char*); @@ -1154,10 +1206,24 @@ array_insert_ace(void *baton, 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); + { + return svn_error_createf( + SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, + _("Access entry refers to undefined group '%s'"), + ace->name); + } + else if (0 == apr_hash_count(ace->members)) + { + /* An ACE for an empty group has no effect, so ignore it. */ + SVN_AUTHZ_PARSE_WARN( + iab->cb, + svn_error_createf( + SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, + _("Ignoring access entry for empty group '%s'"), + ace->name), + scratch_pool); + return SVN_NO_ERROR; + } } APR_ARRAY_PUSH(iab->ace_array, authz_ace_t) = *ace; @@ -1271,6 +1337,12 @@ expand_acl_callback(void *baton, update_global_rights(&cb->authz->authn_rights, acl->rule.repos, acl->authn_access); } + if (acl->has_neg_access) + { + cb->authz->has_neg_rights = TRUE; + update_global_rights(&cb->authz->neg_rights, + acl->rule.repos, acl->neg_access); + } SVN_ERR(svn_iter_apr_hash(NULL, cb->authz->user_rights, update_user_rights, acl, scratch_pool)); return SVN_NO_ERROR; @@ -1297,10 +1369,13 @@ svn_error_t * svn_authz__parse(authz_full_t **authz, svn_stream_t *rules, svn_stream_t *groups, + svn_repos_authz_warning_func_t warning_func, + void *warning_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - ctor_baton_t *const cb = create_ctor_baton(result_pool, scratch_pool); + ctor_baton_t *const cb = create_ctor_baton(warning_func, warning_baton, + result_pool, scratch_pool); /* * Pass 1: Parse the authz file. diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c index 6ce4cc6f7ffd..515600d4f94f 100644 --- a/subversion/libsvn_repos/commit.c +++ b/subversion/libsvn_repos/commit.c @@ -189,7 +189,7 @@ check_out_of_date(struct edit_baton *eb, 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, + return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, _("No such revision %ld"), base_rev); } @@ -306,13 +306,14 @@ add_file_or_directory(const char *path, struct edit_baton *eb = pb->edit_baton; apr_pool_t *subpool = svn_pool_create(pool); svn_boolean_t was_copied = FALSE; - const char *full_path; + const char *full_path, *canonicalized_path; /* Reject paths which contain control characters (related to issue #4340). */ SVN_ERR(svn_path_check_valid(path, pool)); - full_path = svn_fspath__join(eb->base_path, - svn_relpath_canonicalize(path, pool), pool); + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, + pool, pool)); + full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); /* Sanity check. */ if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision))) @@ -477,10 +478,11 @@ delete_entry(const char *path, struct edit_baton *eb = parent->edit_baton; svn_node_kind_t kind; svn_repos_authz_access_t required = svn_authz_write; - const char *full_path; + const char *full_path, *canonicalized_path; - full_path = svn_fspath__join(eb->base_path, - svn_relpath_canonicalize(path, pool), pool); + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, + pool, pool)); + full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); /* Check PATH in our transaction. */ SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); @@ -538,10 +540,11 @@ open_directory(const char *path, struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; svn_node_kind_t kind; - const char *full_path; + const char *full_path, *canonicalized_path; - full_path = svn_fspath__join(eb->base_path, - svn_relpath_canonicalize(path, pool), pool); + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, + pool, pool)); + full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); /* Check PATH in our transaction. If it does not exist, return a 'Path not present' error. */ @@ -611,10 +614,11 @@ open_file(const char *path, struct edit_baton *eb = pb->edit_baton; svn_revnum_t cr_rev; apr_pool_t *subpool = svn_pool_create(pool); - const char *full_path; + const char *full_path, *canonicalized_path; - full_path = svn_fspath__join(eb->base_path, - svn_relpath_canonicalize(path, pool), pool); + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, + pool, pool)); + full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); /* Check for read authorization. */ SVN_ERR(check_authz(eb, full_path, eb->txn_root, diff --git a/subversion/libsvn_repos/config_file.c b/subversion/libsvn_repos/config_file.c index 918727796eb7..4bec766e3dd5 100644 --- a/subversion/libsvn_repos/config_file.c +++ b/subversion/libsvn_repos/config_file.c @@ -342,7 +342,7 @@ svn_repos__create_config_access(svn_repos_t *repos_hint, return result; } -void +void svn_repos__destroy_config_access(config_access_t *access) { svn_pool_destroy(access->pool); diff --git a/subversion/libsvn_repos/config_file.h b/subversion/libsvn_repos/config_file.h index 7fce18f13608..745b637f408d 100644 --- a/subversion/libsvn_repos/config_file.h +++ b/subversion/libsvn_repos/config_file.h @@ -48,7 +48,7 @@ svn_repos__create_config_access(svn_repos_t *repos_hint, apr_pool_t *result_pool); /* Release all resources allocated while using ACCESS. */ -void +void svn_repos__destroy_config_access(config_access_t *access); /* Using ACCESS as a helper object, access the textual configuration at PATH, diff --git a/subversion/libsvn_repos/delta.c b/subversion/libsvn_repos/delta.c index 9b1448d91bfd..b7d4ffa7fb36 100644 --- a/subversion/libsvn_repos/delta.c +++ b/subversion/libsvn_repos/delta.c @@ -215,7 +215,7 @@ svn_repos_dir_delta2(svn_fs_root_t *src_root, { void *root_baton = NULL; struct context c; - const char *src_fullpath; + const char *src_fullpath, *canonicalized_path; svn_node_kind_t src_kind, tgt_kind; svn_revnum_t rootrev; svn_fs_node_relation_t relation; @@ -223,14 +223,22 @@ svn_repos_dir_delta2(svn_fs_root_t *src_root, /* SRC_PARENT_DIR must be valid. */ if (src_parent_dir) - src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool); + { + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, + src_parent_dir, pool, pool)); + src_parent_dir = canonicalized_path; + } else return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0, "Invalid source parent directory '(null)'"); /* TGT_FULLPATH must be valid. */ if (tgt_fullpath) - tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool); + { + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, + tgt_fullpath, pool, pool)); + tgt_fullpath = canonicalized_path; + } else return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0, _("Invalid target path")); diff --git a/subversion/libsvn_repos/deprecated.c b/subversion/libsvn_repos/deprecated.c index f502623a20ea..7ca0e17120f0 100644 --- a/subversion/libsvn_repos/deprecated.c +++ b/subversion/libsvn_repos/deprecated.c @@ -1273,6 +1273,21 @@ svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p, /*** From authz.c ***/ svn_error_t * +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_error_trace(svn_repos_authz_read4(authz_p, path, groups_path, + must_exist, repos_hint, + NULL, NULL, result_pool, + scratch_pool)); +} + +svn_error_t * svn_repos_authz_read2(svn_authz_t **authz_p, const char *path, const char *groups_path, @@ -1300,3 +1315,17 @@ svn_repos_authz_read(svn_authz_t **authz_p, const char *file, return svn_error_trace(svn_repos_authz_read2(authz_p, file, NULL, must_exist, pool)); } + +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) +{ + apr_pool_t *scratch_pool = svn_pool_create(pool); + svn_error_t *err = svn_repos_authz_parse2(authz_p, stream, groups_stream, + NULL, NULL, pool, scratch_pool); + svn_pool_destroy(scratch_pool); + + return svn_error_trace(err); +} diff --git a/subversion/libsvn_repos/dump.c b/subversion/libsvn_repos/dump.c index 960bba5816f1..ab318c6397ca 100644 --- a/subversion/libsvn_repos/dump.c +++ b/subversion/libsvn_repos/dump.c @@ -44,6 +44,7 @@ #include "private/svn_sorts_private.h" #include "private/svn_utf_private.h" #include "private/svn_cache.h" +#include "private/svn_fspath.h" #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) @@ -512,6 +513,30 @@ svn_repos__dump_headers(svn_stream_t *stream, } svn_error_t * +svn_repos__dump_magic_header_record(svn_stream_t *dump_stream, + int version, + apr_pool_t *pool) +{ + SVN_ERR(svn_stream_printf(dump_stream, pool, + SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", + version)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_repos__dump_uuid_header_record(svn_stream_t *dump_stream, + const char *uuid, + apr_pool_t *pool) +{ + if (uuid) + { + SVN_ERR(svn_stream_printf(dump_stream, pool, SVN_REPOS_DUMPFILE_UUID + ": %s\n\n", uuid)); + } + return SVN_NO_ERROR; +} + +svn_error_t * svn_repos__dump_revision_record(svn_stream_t *dump_stream, svn_revnum_t revision, apr_hash_t *extra_headers, @@ -713,8 +738,9 @@ struct dir_baton or NULL if this is the top-level directory of the edit. Perform all allocations in POOL. */ -static struct dir_baton * -make_dir_baton(const char *path, +static struct svn_error_t * +make_dir_baton(struct dir_baton **dbp, + const char *path, const char *cmp_path, svn_revnum_t cmp_rev, void *edit_baton, @@ -723,10 +749,10 @@ make_dir_baton(const char *path, { struct edit_baton *eb = edit_baton; struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); - const char *full_path; + const char *full_path, *canonicalized_path; /* A path relative to nothing? I don't think so. */ - SVN_ERR_ASSERT_NO_RETURN(!path || pb); + SVN_ERR_ASSERT(!path || pb); /* Construct the full path of this node. */ if (pb) @@ -736,7 +762,11 @@ make_dir_baton(const char *path, /* Remove leading slashes from copyfrom paths. */ if (cmp_path) - cmp_path = svn_relpath_canonicalize(cmp_path, pool); + { + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, + cmp_path, pool, pool)); + cmp_path = canonicalized_path; + } new_db->edit_baton = eb; new_db->path = full_path; @@ -747,7 +777,8 @@ make_dir_baton(const char *path, new_db->check_name_collision = FALSE; new_db->pool = pool; - return new_db; + *dbp = new_db; + return SVN_NO_ERROR; } static svn_error_t * @@ -1143,7 +1174,12 @@ dump_node(struct edit_baton *eb, /* Remove leading slashes from copyfrom paths. */ if (cmp_path) - cmp_path = svn_relpath_canonicalize(cmp_path, pool); + { + const char *canonicalized_path; + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, + cmp_path, pool, pool)); + cmp_path = canonicalized_path; + } /* Validate the comparison path/rev. */ if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) @@ -1513,9 +1549,9 @@ open_root(void *edit_baton, apr_pool_t *pool, void **root_baton) { - *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, - edit_baton, NULL, pool); - return SVN_NO_ERROR; + return svn_error_trace(make_dir_baton((struct dir_baton **)root_baton, + NULL, NULL, SVN_INVALID_REVNUM, + edit_baton, NULL, pool)); } @@ -1547,8 +1583,10 @@ add_directory(const char *path, struct edit_baton *eb = pb->edit_baton; void *was_deleted; svn_boolean_t is_copy = FALSE; - struct dir_baton *new_db - = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool); + struct dir_baton *new_db; + + SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev, eb, + pb, pool)); /* This might be a replacement -- is the path already deleted? */ was_deleted = svn_hash_gets(pb->deleted_entries, path); @@ -1605,7 +1643,7 @@ open_directory(const char *path, cmp_rev = pb->cmp_rev; } - new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool); + SVN_ERR(make_dir_baton(&new_db, path, cmp_path, cmp_rev, eb, pb, pool)); *child_baton = new_db; return SVN_NO_ERROR; } @@ -1936,25 +1974,11 @@ write_revision_record(svn_stream_t *stream, apr_pool_t *pool) { apr_hash_t *props; - apr_time_t timetemp; - svn_string_t *datevalue; if (include_revprops) { 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 { @@ -1986,6 +2010,11 @@ dump_filter_authz_func(svn_boolean_t *allowed, { dump_filter_baton_t *b = baton; + /* For some nodes (e.g. files under copied directory) PATH may be + * non-canonical (missing leading '/'). Canonicalize PATH before + * passing it to FILTER_FUNC. */ + path = svn_fspath__canonicalize(path, pool); + return svn_error_trace(b->filter_func(allowed, root, path, b->filter_baton, pool)); } @@ -2076,11 +2105,8 @@ svn_repos_dump_fs4(svn_repos_t *repos, /* Write out "general" metadata for the dumpfile. In this case, a magic header followed by a dumpfile format version. */ - SVN_ERR(svn_stream_printf(stream, pool, - SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", - version)); - SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID - ": %s\n\n", uuid)); + SVN_ERR(svn_repos__dump_magic_header_record(stream, version, pool)); + SVN_ERR(svn_repos__dump_uuid_header_record(stream, uuid, pool)); /* Create a notify object that we can reuse in the loop. */ if (notify_func) diff --git a/subversion/libsvn_repos/dump_editor.c b/subversion/libsvn_repos/dump_editor.c new file mode 100644 index 000000000000..c1ece570a499 --- /dev/null +++ b/subversion/libsvn_repos/dump_editor.c @@ -0,0 +1,1040 @@ +/* + * dump_editor.c: A svn_delta_editor_t editor used to dump revisions. + * + * ==================================================================== + * 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_hash.h" +#include "svn_pools.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_subst.h" +#include "svn_dirent_uri.h" + +#include "private/svn_repos_private.h" + +#include <assert.h> + +#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) + + +/* Normalize the line ending style of the values of properties in PROPS + * that "need translation" (according to svn_prop_needs_translation(), + * currently all svn:* props) so that they contain only LF (\n) line endings. + * + * Put the normalized props into NORMAL_PROPS, allocated in RESULT_POOL. + */ +static svn_error_t * +normalize_props(apr_hash_t **normal_props, + apr_hash_t *props, + apr_pool_t *result_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + *normal_props = apr_hash_make(result_pool); + + iterpool = svn_pool_create(result_pool); + for (hi = apr_hash_first(result_pool, props); hi; hi = apr_hash_next(hi)) + { + const char *key = apr_hash_this_key(hi); + const svn_string_t *value = apr_hash_this_val(hi); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_repos__normalize_prop(&value, NULL, key, value, + iterpool, iterpool)); + svn_hash_sets(*normal_props, key, svn_string_dup(value, result_pool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* A directory baton used by all directory-related callback functions + * in the dump editor. */ +struct dir_baton +{ + struct dump_edit_baton *eb; + + /* Pool for per-directory allocations */ + apr_pool_t *pool; + + /* the path to this directory */ + const char *repos_relpath; /* a relpath */ + + /* Copyfrom info for the node, if any. */ + const char *copyfrom_path; /* a relpath */ + svn_revnum_t copyfrom_rev; + + /* Headers accumulated so far for this directory */ + svn_repos__dumpfile_headers_t *headers; + + /* Properties which were modified during change_dir_prop. */ + apr_hash_t *props; + + /* Properties which were deleted during change_dir_prop. */ + apr_hash_t *deleted_props; + + /* Hash of paths that need to be deleted, though some -might- be + replaced. Maps const char * paths to this dir_baton. Note that + they're full paths, because that's what the editor driver gives + us, although they're all really within this directory. */ + apr_hash_t *deleted_entries; + + /* Flag to trigger dumping props. */ + svn_boolean_t dump_props; +}; + +/* A file baton used by all file-related callback functions in the dump + * editor */ +struct file_baton +{ + struct dump_edit_baton *eb; + + /* Pool for per-file allocations */ + apr_pool_t *pool; + + /* the path to this file */ + const char *repos_relpath; /* a relpath */ + + /* Properties which were modified during change_file_prop. */ + apr_hash_t *props; + + /* Properties which were deleted during change_file_prop. */ + apr_hash_t *deleted_props; + + /* The checksum of the file the delta is being applied to */ + const char *base_checksum; + + /* Copy state and source information (if any). */ + svn_boolean_t is_copy; + const char *copyfrom_path; + svn_revnum_t copyfrom_rev; + + /* The action associate with this node. */ + enum svn_node_action action; + + /* Flags to trigger dumping props and text. */ + svn_boolean_t dump_text; + svn_boolean_t dump_props; +}; + +/* The baton used by the dump editor. */ +struct dump_edit_baton { + /* The output stream we write the dumpfile to */ + svn_stream_t *stream; + + /* The repository relpath of the anchor of the editor when driven + via the RA update mechanism; NULL otherwise. (When the editor is + driven via the RA "replay" mechanism instead, the editor is + always anchored at the repository, we don't need to prepend an + anchor path to the dumped node paths, and open_root() doesn't + need to manufacture directory additions.) */ + const char *update_anchor_relpath; + + /* Pool for per-revision allocations */ + apr_pool_t *pool; + + /* Temporary file used for textdelta application along with its + absolute path; these two variables should be allocated in the + per-edit-session pool */ + const char *delta_abspath; + apr_file_t *delta_file; + + /* The baton of the directory node whose block of + dump stream data has not been fully completed; NULL if there's no + such item. */ + struct dir_baton *pending_db; +}; + +/* Make a directory baton to represent the directory at PATH (relative + * to the EDIT_BATON). + * + * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this + * directory should be compared for changes. If the copyfrom + * information is valid, the directory will be compared against its + * copy source. + * + * PB is the directory baton of this directory's parent, or NULL if + * this is the top-level directory of the edit. + * + * Perform all allocations in POOL. */ +static struct svn_error_t * +make_dir_baton(struct dir_baton **dbp, + const char *path, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + void *edit_baton, + struct dir_baton *pb, + apr_pool_t *pool) +{ + struct dump_edit_baton *eb = edit_baton; + struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); + const char *repos_relpath; + + /* Construct the full path of this node. */ + if (pb) + SVN_ERR(svn_relpath_canonicalize_safe(&repos_relpath, NULL, path, + pool, pool)); + else + repos_relpath = ""; + + /* Strip leading slash from copyfrom_path so that the path is + canonical and svn_relpath_join can be used */ + if (copyfrom_path) + copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool); + + new_db->eb = eb; + new_db->pool = pool; + new_db->repos_relpath = repos_relpath; + new_db->copyfrom_path = copyfrom_path + ? svn_relpath_canonicalize(copyfrom_path, pool) + : NULL; + new_db->copyfrom_rev = copyfrom_rev; + new_db->headers = NULL; + new_db->props = apr_hash_make(pool); + new_db->deleted_props = apr_hash_make(pool); + new_db->deleted_entries = apr_hash_make(pool); + + *dbp = new_db; + return SVN_NO_ERROR; +} + +/* Make a file baton to represent the directory at PATH (relative to + * PB->eb). PB is the directory baton of this directory's parent, or + * NULL if this is the top-level directory of the edit. Perform all + * allocations in POOL. */ +static struct file_baton * +make_file_baton(const char *path, + struct dir_baton *pb, + apr_pool_t *pool) +{ + struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); + + new_fb->eb = pb->eb; + new_fb->pool = pool; + new_fb->repos_relpath = svn_relpath_canonicalize(path, pool); + new_fb->props = apr_hash_make(pool); + new_fb->deleted_props = apr_hash_make(pool); + new_fb->is_copy = FALSE; + new_fb->copyfrom_path = NULL; + new_fb->copyfrom_rev = SVN_INVALID_REVNUM; + new_fb->action = svn_node_action_change; + + return new_fb; +} + +/* Append to HEADERS the required headers, and set *CONTENT to the property + * content section, to represent the property delta of PROPS/DELETED_PROPS. + */ +static svn_error_t * +get_props_content(svn_repos__dumpfile_headers_t *headers, + svn_stringbuf_t **content, + apr_hash_t *props, + apr_hash_t *deleted_props, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stream_t *content_stream; + apr_hash_t *normal_props; + + *content = svn_stringbuf_create_empty(result_pool); + + content_stream = svn_stream_from_stringbuf(*content, scratch_pool); + + SVN_ERR(normalize_props(&normal_props, props, scratch_pool)); + SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props, + content_stream, "PROPS-END", + scratch_pool)); + SVN_ERR(svn_stream_close(content_stream)); + + /* Prop-delta: true */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true"); + + return SVN_NO_ERROR; +} + +/* A special case of dump_node(), for a delete record. + * + * The only thing special about this version is it only writes one blank + * line, not two, after the headers. Why? Historical precedent for the + * case where a delete record is used as part of a (delete + add-with-history) + * in implementing a replacement. + */ +static svn_error_t * +dump_node_delete(svn_stream_t *stream, + const char *node_relpath, + apr_pool_t *pool) +{ + svn_repos__dumpfile_headers_t *headers + = svn_repos__dumpfile_headers_create(pool); + + assert(svn_relpath_is_canonical(node_relpath)); + + /* Node-path: ... */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); + + /* Node-action: delete */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); + + SVN_ERR(svn_repos__dump_node_record(stream, headers, + NULL, FALSE, 0, /* props & text */ + FALSE /*content_length_always*/, pool)); + return SVN_NO_ERROR; +} + +/* Set *HEADERS_P to contain some headers for the node at PATH of type KIND. + * + * ACTION describes what is happening to the node (see enum + * svn_node_action). + * + * If the node was itself copied, IS_COPY is TRUE and the + * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV. + * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this + * node is part of a copied subtree. + * + * Iff ACTION is svn_node_action_replace and IS_COPY, then first write a + * complete deletion record to the dump stream. + * + * If ACTION is svn_node_action_delete, then the node record will be + * complete. (The caller may want to write two blank lines after the + * header block.) + */ +static svn_error_t * +dump_node(svn_repos__dumpfile_headers_t **headers_p, + struct dump_edit_baton *eb, + const char *repos_relpath, + struct dir_baton *db, + struct file_baton *fb, + enum svn_node_action action, + svn_boolean_t is_copy, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool) +{ + const char *node_relpath = repos_relpath; + svn_repos__dumpfile_headers_t *headers + = svn_repos__dumpfile_headers_create(pool); + + assert(svn_relpath_is_canonical(repos_relpath)); + assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path)); + assert(! (db && fb)); + + /* Add the edit root relpath prefix if necessary. */ + if (eb->update_anchor_relpath) + node_relpath = svn_relpath_join(eb->update_anchor_relpath, + node_relpath, pool); + + /* Node-path: ... */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); + + /* Node-kind: "file" | "dir" */ + if (fb) + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file"); + else if (db) + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); + + + /* Write the appropriate Node-action header */ + switch (action) + { + case svn_node_action_change: + /* We are here after a change_file_prop or change_dir_prop. They + set up whatever dump_props they needed to- nothing to + do here but print node action information. + + Node-action: change. */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change"); + break; + + case svn_node_action_delete: + /* Node-action: delete */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); + break; + + case svn_node_action_replace: + if (! is_copy) + { + /* Node-action: replace */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace"); + + /* Wait for a change_*_prop to be called before dumping + anything */ + if (fb) + fb->dump_props = TRUE; + else if (db) + db->dump_props = TRUE; + break; + } + else + { + /* More complex case: is_copy is true, and copyfrom_path/ + copyfrom_rev are present: delete the original, and then re-add + it */ + /* ### Why not write a 'replace' record? Don't know. */ + + /* ### Unusually, we end this 'delete' node record with only a single + blank line after the header block -- no extra blank line. */ + SVN_ERR(dump_node_delete(eb->stream, repos_relpath, pool)); + + /* The remaining action is a non-replacing add-with-history */ + /* action = svn_node_action_add; */ + } + /* FALL THROUGH to 'add' */ + + case svn_node_action_add: + /* Node-action: add */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); + + if (is_copy) + { + /* Node-copyfrom-rev / Node-copyfrom-path */ + svn_repos__dumpfile_header_pushf( + headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", copyfrom_rev); + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, copyfrom_path); + } + else + { + /* fb->dump_props (for files) is handled in close_file() + which is called immediately. + + However, directories are not closed until all the work + inside them has been done; db->dump_props (for directories) + is handled (via dump_pending()) in all the functions that + can possibly be called after add_directory(): + + - add_directory() + - open_directory() + - delete_entry() + - close_directory() + - add_file() + - open_file() + + change_dir_prop() is a special case. */ + if (fb) + fb->dump_props = TRUE; + else if (db) + db->dump_props = TRUE; + } + + break; + } + + /* Return the headers so far. We don't necessarily have all the headers + yet -- there may be property-related and content length headers to + come, if this was not a 'delete' record. */ + *headers_p = headers; + return SVN_NO_ERROR; +} + +static svn_error_t * +dump_mkdir(struct dump_edit_baton *eb, + const char *repos_relpath, + apr_pool_t *pool) +{ + svn_stringbuf_t *prop_content; + svn_repos__dumpfile_headers_t *headers + = svn_repos__dumpfile_headers_create(pool); + + /* Node-path: ... */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_PATH, repos_relpath); + + /* Node-kind: dir */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); + + /* Node-action: add */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); + + /* Dump the (empty) property block. */ + SVN_ERR(get_props_content(headers, &prop_content, + apr_hash_make(pool), apr_hash_make(pool), + pool, pool)); + SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, prop_content, + FALSE, 0, FALSE /*content_length_always*/, + pool)); + + /* Newlines to tie it all off. */ + SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); + + return SVN_NO_ERROR; +} + +/* Dump pending headers and properties for the directory EB->pending_db (if + * not null), to allow starting the dump of a child node */ +static svn_error_t * +dump_pending_dir(struct dump_edit_baton *eb, + apr_pool_t *scratch_pool) +{ + struct dir_baton *db = eb->pending_db; + svn_stringbuf_t *prop_content = NULL; + + if (! db) + return SVN_NO_ERROR; + + /* Some pending properties to dump? */ + if (db->dump_props) + { + SVN_ERR(get_props_content(db->headers, &prop_content, + db->props, db->deleted_props, + scratch_pool, scratch_pool)); + } + SVN_ERR(svn_repos__dump_node_record(eb->stream, db->headers, prop_content, + FALSE, 0, FALSE /*content_length_always*/, + scratch_pool)); + + /* No text is going to be dumped. Write a couple of newlines and + wait for the next node/ revision. */ + SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); + + if (db->dump_props) + { + /* Cleanup so that data is never dumped twice. */ + apr_hash_clear(db->props); + apr_hash_clear(db->deleted_props); + db->dump_props = FALSE; + } + + /* Anything that was pending is pending no longer. */ + eb->pending_db = NULL; + + return SVN_NO_ERROR; +} + + + +/*** Editor Function Implementations ***/ + +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **root_baton) +{ + struct dump_edit_baton *eb = edit_baton; + struct dir_baton *new_db = NULL; + + /* Clear the per-revision pool after each revision */ + svn_pool_clear(eb->pool); + + if (eb->update_anchor_relpath) + { + int i; + const char *parent_path = eb->update_anchor_relpath; + apr_array_header_t *dirs_to_add = + apr_array_make(pool, 4, sizeof(const char *)); + apr_pool_t *iterpool = svn_pool_create(pool); + + while (! svn_path_is_empty(parent_path)) + { + APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path; + parent_path = svn_relpath_dirname(parent_path, pool); + } + + for (i = dirs_to_add->nelts; i; --i) + { + const char *dir_to_add = + APR_ARRAY_IDX(dirs_to_add, i - 1, const char *); + + svn_pool_clear(iterpool); + + /* For parents of the source directory, we just manufacture + the adds ourselves. */ + if (i > 1) + { + SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool)); + } + else + { + /* ... but for the source directory itself, we'll defer + to letting the typical plumbing handle this task. */ + SVN_ERR(make_dir_baton(&new_db, NULL, NULL, SVN_INVALID_REVNUM, + edit_baton, NULL, pool)); + SVN_ERR(dump_node(&new_db->headers, + eb, new_db->repos_relpath, new_db, + NULL, svn_node_action_add, FALSE, + NULL, SVN_INVALID_REVNUM, pool)); + + /* Remember that we've started but not yet finished + handling this directory. */ + eb->pending_db = new_db; + } + } + svn_pool_destroy(iterpool); + } + + if (! new_db) + { + SVN_ERR(make_dir_baton(&new_db, NULL, NULL, SVN_INVALID_REVNUM, + edit_baton, NULL, pool)); + } + + *root_baton = new_db; + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + + SVN_ERR(dump_pending_dir(pb->eb, pool)); + + /* We don't dump this deletion immediate. Rather, we add this path + to the deleted_entries of the parent directory baton. That way, + we can tell (later) an addition from a replacement. All the real + deletions get handled in close_directory(). */ + svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->pool, path), pb); + + return SVN_NO_ERROR; +} + +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + void *was_deleted; + struct dir_baton *new_db; + svn_boolean_t is_copy; + + SVN_ERR(dump_pending_dir(pb->eb, pool)); + + SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev, pb->eb, + pb, pb->pool)); + + /* This might be a replacement -- is the path already deleted? */ + was_deleted = svn_hash_gets(pb->deleted_entries, path); + + /* Detect an add-with-history */ + is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); + + /* Dump the node */ + SVN_ERR(dump_node(&new_db->headers, + pb->eb, new_db->repos_relpath, new_db, NULL, + was_deleted ? svn_node_action_replace : svn_node_action_add, + is_copy, + is_copy ? new_db->copyfrom_path : NULL, + is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, + pool)); + + if (was_deleted) + /* Delete the path, it's now been dumped */ + svn_hash_sets(pb->deleted_entries, path, NULL); + + /* Remember that we've started, but not yet finished handling this + directory. */ + pb->eb->pending_db = new_db; + + *child_baton = new_db; + return SVN_NO_ERROR; +} + +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct dir_baton *new_db; + const char *copyfrom_path = NULL; + svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; + + SVN_ERR(dump_pending_dir(pb->eb, pool)); + + /* If the parent directory has explicit comparison path and rev, + record the same for this one. */ + if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) + { + copyfrom_path = svn_relpath_join(pb->copyfrom_path, + svn_relpath_basename(path, NULL), + pb->pool); + copyfrom_rev = pb->copyfrom_rev; + } + + SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev, + pb->eb, pb, pb->pool)); + + *child_baton = new_db; + return SVN_NO_ERROR; +} + +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + apr_hash_index_t *hi; + svn_boolean_t this_pending; + + /* Remember if this directory is the one currently pending. */ + this_pending = (db->eb->pending_db == db); + + SVN_ERR(dump_pending_dir(db->eb, pool)); + + /* If this directory was pending, then dump_pending() should have + taken care of all the props and such. Of course, the only way + that would be the case is if this directory was added/replaced. + + Otherwise, if stuff for this directory has already been written + out (at some point in the past, prior to our handling other + nodes), we might need to generate a second "change" record just + to carry the information we've since learned about the + directory. */ + if ((! this_pending) && (db->dump_props)) + { + SVN_ERR(dump_node(&db->headers, + db->eb, db->repos_relpath, db, NULL, + svn_node_action_change, FALSE, + NULL, SVN_INVALID_REVNUM, pool)); + db->eb->pending_db = db; + SVN_ERR(dump_pending_dir(db->eb, pool)); + } + + /* Dump the deleted directory entries */ + for (hi = apr_hash_first(pool, db->deleted_entries); hi; + hi = apr_hash_next(hi)) + { + const char *path = apr_hash_this_key(hi); + + SVN_ERR(dump_node_delete(db->eb->stream, path, pool)); + /* This deletion record is complete -- write an extra newline */ + SVN_ERR(svn_stream_puts(db->eb->stream, "\n")); + } + + /* ### should be unnecessary */ + apr_hash_clear(db->deleted_entries); + + return SVN_NO_ERROR; +} + +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct file_baton *fb; + void *was_deleted; + + SVN_ERR(dump_pending_dir(pb->eb, pool)); + + /* Make the file baton. */ + fb = make_file_baton(path, pb, pool); + + /* This might be a replacement -- is the path already deleted? */ + was_deleted = svn_hash_gets(pb->deleted_entries, path); + + /* Detect add-with-history. */ + if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev)) + { + fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool); + fb->copyfrom_rev = copyfrom_rev; + fb->is_copy = TRUE; + } + fb->action = was_deleted ? svn_node_action_replace : svn_node_action_add; + + /* Delete the path, it's now been dumped. */ + if (was_deleted) + svn_hash_sets(pb->deleted_entries, path, NULL); + + *file_baton = fb; + return SVN_NO_ERROR; +} + +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t ancestor_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct file_baton *fb; + + SVN_ERR(dump_pending_dir(pb->eb, pool)); + + /* Make the file baton. */ + fb = make_file_baton(path, pb, pool); + + /* If the parent directory has explicit copyfrom path and rev, + record the same for this one. */ + if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) + { + fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path, + svn_relpath_basename(path, NULL), + pb->pool); + fb->copyfrom_rev = pb->copyfrom_rev; + } + + *file_baton = fb; + return SVN_NO_ERROR; +} + +static svn_error_t * +change_dir_prop(void *parent_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton *db = parent_baton; + svn_boolean_t this_pending; + + /* This directory is not pending, but something else is, so handle + the "something else". */ + this_pending = (db->eb->pending_db == db); + if (! this_pending) + SVN_ERR(dump_pending_dir(db->eb, pool)); + + if (svn_property_kind2(name) != svn_prop_regular_kind) + return SVN_NO_ERROR; + + if (value) + svn_hash_sets(db->props, + apr_pstrdup(db->pool, name), + svn_string_dup(value, db->pool)); + else + svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), ""); + + /* Make sure we eventually output the props */ + db->dump_props = TRUE; + + return SVN_NO_ERROR; +} + +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + + if (svn_property_kind2(name) != svn_prop_regular_kind) + return SVN_NO_ERROR; + + if (value) + svn_hash_sets(fb->props, + apr_pstrdup(fb->pool, name), + svn_string_dup(value, fb->pool)); + else + svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), ""); + + /* Dump the property headers and wait; close_file might need + to write text headers too depending on whether + apply_textdelta is called */ + fb->dump_props = TRUE; + + return SVN_NO_ERROR; +} + +static svn_error_t * +apply_textdelta(void *file_baton, const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + struct dump_edit_baton *eb = fb->eb; + svn_stream_t *delta_filestream; + + /* Use a temporary file to measure the Text-content-length */ + delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); + + /* Prepare to write the delta to the delta_filestream */ + svn_txdelta_to_svndiff3(handler, handler_baton, + delta_filestream, 0, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); + + /* Record that there's text to be dumped, and its base checksum. */ + fb->dump_text = TRUE; + fb->base_checksum = apr_pstrdup(fb->pool, base_checksum); + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct dump_edit_baton *eb = fb->eb; + svn_filesize_t text_content_length = 0; + svn_stringbuf_t *propstring = NULL; + svn_repos__dumpfile_headers_t *headers; + + SVN_ERR(dump_pending_dir(eb, pool)); + + /* Start dumping this node, by collecting some basic headers for it. */ + SVN_ERR(dump_node(&headers, eb, fb->repos_relpath, NULL, fb, + fb->action, fb->is_copy, fb->copyfrom_path, + fb->copyfrom_rev, pool)); + + /* Some pending properties to dump? We'll dump just the headers for + now, then dump the actual propchange content only after dumping + the text headers too (if present). */ + if (fb->dump_props) + { + SVN_ERR(get_props_content(headers, &propstring, + fb->props, fb->deleted_props, + pool, pool)); + } + + /* Dump the text headers */ + if (fb->dump_text) + { + /* Text-delta: true */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true"); + + SVN_ERR(svn_io_file_size_get(&text_content_length, eb->delta_file, + pool)); + + if (fb->base_checksum) + /* Text-delta-base-md5: */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, fb->base_checksum); + + /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */ + svn_repos__dumpfile_header_push( + headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, text_checksum); + } + + /* Dump the headers and props now */ + SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, propstring, + fb->dump_text, text_content_length, + FALSE /*content_length_always*/, + pool)); + + if (fb->dump_props) + { + /* Cleanup */ + fb->dump_props = FALSE; + apr_hash_clear(fb->props); + apr_hash_clear(fb->deleted_props); + } + + /* Dump the text */ + if (fb->dump_text) + { + /* Seek to the beginning of the delta file, map it to a stream, + and copy the stream to eb->stream. Then close the stream and + truncate the file so we can reuse it for the next textdelta + application. Note that the file isn't created, opened or + closed here */ + svn_stream_t *delta_filestream; + apr_off_t offset = 0; + + SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool)); + delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); + SVN_ERR(svn_stream_copy3(delta_filestream, + svn_stream_disown(eb->stream, pool), + NULL, NULL, pool)); + + /* Cleanup */ + SVN_ERR(svn_stream_close(delta_filestream)); + SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool)); + } + + /* Write a couple of blank lines for matching output with `svnadmin + dump` */ + SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_edit(void *edit_baton, apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_repos__get_dump_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_stream_t *stream, + const char *update_anchor_relpath, + apr_pool_t *pool) +{ + struct dump_edit_baton *eb; + svn_delta_editor_t *de; + + eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton)); + eb->stream = stream; + eb->update_anchor_relpath = update_anchor_relpath; + eb->pending_db = NULL; + + /* Create a special per-revision pool */ + eb->pool = svn_pool_create(pool); + + /* Open a unique temporary file for all textdelta applications in + this edit session. The file is automatically closed and cleaned + up when the edit session is done. */ + SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath), + NULL, svn_io_file_del_on_close, pool, pool)); + + de = svn_delta_default_editor(pool); + de->open_root = open_root; + de->delete_entry = delete_entry; + de->add_directory = add_directory; + de->open_directory = open_directory; + de->close_directory = close_directory; + de->change_dir_prop = change_dir_prop; + de->change_file_prop = change_file_prop; + de->apply_textdelta = apply_textdelta; + de->add_file = add_file; + de->open_file = open_file; + de->close_file = close_file; + de->close_edit = close_edit; + + /* Set the edit_baton and editor. */ + *edit_baton = eb; + *editor = de; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_repos/fs-wrap.c b/subversion/libsvn_repos/fs-wrap.c index f895d5aa0c22..c5cecdc56c84 100644 --- a/subversion/libsvn_repos/fs-wrap.c +++ b/subversion/libsvn_repos/fs-wrap.c @@ -282,7 +282,7 @@ svn_repos__normalize_prop(const svn_string_t **result_p, } else { - *result_p = svn_string_dup(value, result_pool); + *result_p = value; if (normalized_p) *normalized_p = FALSE; } @@ -1106,7 +1106,7 @@ svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p, apr_pstrdup(result_pool, parent_path + 1); i_props->prop_hash = parent_properties; /* Build the output array in depth-first order. */ - svn_sort__array_insert(inherited_props, &i_props, 0); + SVN_ERR(svn_sort__array_insert2(inherited_props, &i_props, 0)); } } } diff --git a/subversion/libsvn_repos/libsvn_repos.pc.in b/subversion/libsvn_repos/libsvn_repos.pc.in index af70b94d98bd..b4fcf0549927 100644 --- a/subversion/libsvn_repos/libsvn_repos.pc.in +++ b/subversion/libsvn_repos/libsvn_repos.pc.in @@ -6,7 +6,7 @@ includedir=@includedir@ Name: libsvn_repos Description: Subversion Repository Library Version: @PACKAGE_VERSION@ -Requires: apr-@SVN_APR_MAJOR_VERSION@ -Requires.private: libsvn_fs libsvn_delta libsvn_subr -Libs: -L${libdir} -lsvn_repos -Cflags: -I${includedir} +Requires: apr-@SVN_APR_MAJOR_VERSION@ +Requires.private: libsvn_fs, libsvn_delta, libsvn_subr +Libs: -L${libdir} -lsvn_repos-1 +Cflags: -I${includedir}/subversion-1 diff --git a/subversion/libsvn_repos/list.c b/subversion/libsvn_repos/list.c index ef8ac32b1b38..f620b3f62489 100644 --- a/subversion/libsvn_repos/list.c +++ b/subversion/libsvn_repos/list.c @@ -324,7 +324,7 @@ svn_repos_list(svn_fs_root_t *root, 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, + if (matches_any(svn_dirent_basename(path, scratch_pool), patterns, &scratch_buffer)) SVN_ERR(report_dirent(root, path, kind, path_info_only, receiver, receiver_baton, scratch_pool)); diff --git a/subversion/libsvn_repos/load-fs-vtable.c b/subversion/libsvn_repos/load-fs-vtable.c index f6c6bf660b12..17c0d27107a2 100644 --- a/subversion/libsvn_repos/load-fs-vtable.c +++ b/subversion/libsvn_repos/load-fs-vtable.c @@ -75,7 +75,7 @@ struct parse_baton (svn_revnum_t *) in the dump stream to their corresponding revisions (svn_revnum_t *) in the loaded repository. The hash and its contents are allocated in POOL. */ - /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903 + /* ### See https://issues.apache.org/jira/browse/SVN-3903 ### for discussion about improving the memory costs of this mapping. */ apr_hash_t *rev_map; @@ -155,9 +155,11 @@ get_revision_mapping(apr_hash_t *rev_map, } -/* Change revision property NAME to VALUE for REVISION in REPOS. If - VALIDATE_PROPS is set, use functions which perform validation of - the property value. Otherwise, bypass those checks. */ +/* Change revision property NAME to VALUE for REVISION in REPOS. + If NORMALIZE_PROPS is set, attempt to normalize properties before + changing them, if that is needed. If VALIDATE_PROPS is set, use + functions which perform validation of the property value. + Otherwise, bypass those checks. */ static svn_error_t * change_rev_prop(svn_repos_t *repos, svn_revnum_t revision, @@ -179,17 +181,23 @@ change_rev_prop(svn_repos_t *repos, NULL, value, pool); } -/* Change property NAME to VALUE for PATH in TXN_ROOT. If - VALIDATE_PROPS is set, use functions which perform validation of - the property value. Otherwise, bypass those checks. */ +/* Change property NAME to VALUE for PATH in TXN_ROOT. + If NORMALIZE_PROPS is set, attempt to normalize properties before + changing them, if that is needed. If VALIDATE_PROPS is set, use + functions which perform validation of the property value. + Otherwise, bypass those checks. */ static svn_error_t * change_node_prop(svn_fs_root_t *txn_root, const char *path, 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_node_prop(txn_root, path, name, value, pool); else @@ -213,9 +221,11 @@ prefix_mergeinfo_paths(svn_string_t **mergeinfo_val, { const char *merge_source = apr_hash_this_key(hi); svn_rangelist_t *rangelist = apr_hash_this_val(hi); - const char *path; + const char *path, *canonicalized_path; - merge_source = svn_relpath_canonicalize(merge_source, pool); + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, + merge_source, pool, pool)); + merge_source = canonicalized_path; /* The svn:mergeinfo property syntax demands a repos abspath */ path = svn_fspath__canonicalize(svn_relpath_join(parent_dir, @@ -253,7 +263,7 @@ renumber_mergeinfo_revs(svn_string_t **final_val, SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool)); /* Issue #3020 - http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16 + https://issues.apache.org/jira/browse/SVN-3020#desc16 Remove mergeinfo older than the oldest revision in the dump stream and adjust its revisions by the difference between the head rev of the target repository and the current dump stream rev. */ @@ -323,7 +333,7 @@ renumber_mergeinfo_revs(svn_string_t **final_val, mergeinfo with a start rev > end rev. If that gets into the repository then a world of bustage breaks loose anytime that bogus mergeinfo is parsed. See - http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16. + https://issues.apache.org/jira/browse/SVN-3020#desc16. */ continue; } @@ -377,7 +387,10 @@ make_node_baton(struct node_baton **node_baton_p, /* Then add info from the headers. */ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))) { - val = svn_relpath_canonicalize(val, pool); + const char *canonicalized_path; + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, + val, pool, pool)); + val = canonicalized_path; if (rb->pb->parent_dir) nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool); else @@ -869,7 +882,8 @@ set_node_property(void *baton, } return change_node_prop(rb->txn_root, nb->path, name, value, - pb->validate_props, nb->pool); + pb->validate_props, rb->pb->normalize_props, + nb->pool); } @@ -885,7 +899,8 @@ delete_node_property(void *baton, return SVN_NO_ERROR; return change_node_prop(rb->txn_root, nb->path, name, NULL, - rb->pb->validate_props, nb->pool); + rb->pb->validate_props, rb->pb->normalize_props, + nb->pool); } @@ -909,7 +924,8 @@ remove_node_props(void *baton) const char *key = apr_hash_this_key(hi); SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL, - rb->pb->validate_props, nb->pool)); + rb->pb->validate_props, rb->pb->normalize_props, + nb->pool)); } return SVN_NO_ERROR; @@ -1202,7 +1218,12 @@ svn_repos_get_fs_build_parser6(const svn_repos_parse_fns3_t **callbacks, struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb)); if (parent_dir) - parent_dir = svn_relpath_canonicalize(parent_dir, pool); + { + const char *canonicalized_path; + SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, + parent_dir, pool, pool)); + parent_dir = canonicalized_path; + } SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) && SVN_IS_VALID_REVNUM(end_rev)) @@ -1400,7 +1421,7 @@ revprops_close_revision(void *baton) * 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 diff --git a/subversion/libsvn_repos/load.c b/subversion/libsvn_repos/load.c index 27cf4a174552..ec2699f0042b 100644 --- a/subversion/libsvn_repos/load.c +++ b/subversion/libsvn_repos/load.c @@ -355,24 +355,62 @@ parse_text_block(svn_stream_t *stream, -/* Parse VERSIONSTRING and verify that we support the dumpfile format - version number, setting *VERSION appropriately. */ +/* Parse VERSIONSTRING from STREAM and verify that we support the dumpfile + format version number, setting *VERSION appropriately. */ static svn_error_t * parse_format_version(int *version, - const char *versionstring) + svn_stream_t *stream, + apr_pool_t *scratch_pool) { static const int magic_len = sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER) - 1; - const char *p = strchr(versionstring, ':'); + svn_stringbuf_t *linebuf; + const char *p; int value; + /* No svn_stream_readline() here, because malformed streams may not have + the EOL at all, and currently svn_stream_readline() keeps loading the + whole thing into memory until it encounters an EOL or the stream ends. + This is particularly troublesome, because users may incorrectly attempt + to load arbitrary large files instread of proper dump files. + + As a workaround, parse the first line with a length limit. While this + is not a complete solution, doing so handles the common case described + above. For a complete solution, svn_stream_readline() may need to grow + a `limit` argument that would allow us to safely use it everywhere within + this parser. + */ + linebuf = svn_stringbuf_create_empty(scratch_pool); + while (1) + { + apr_size_t len; + char c; + + len = 1; + SVN_ERR(svn_stream_read_full(stream, &c, &len)); + if (len != 1) + return stream_ran_dry(); + + if (c == '\n') + break; + + if (linebuf->len + 1 > 80) + return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL, + _("Malformed dumpfile header '%s'"), + linebuf->data); + + svn_stringbuf_appendbyte(linebuf, c); + } + + p = strchr(linebuf->data, ':'); + if (p == NULL - || p != (versionstring + magic_len) - || strncmp(versionstring, + || p != (linebuf->data + magic_len) + || strncmp(linebuf->data, SVN_REPOS_DUMPFILE_MAGIC_HEADER, magic_len)) return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL, _("Malformed dumpfile header '%s'"), - versionstring); + linebuf->data); SVN_ERR(svn_cstring_atoi(&value, p + 1)); @@ -542,14 +580,10 @@ svn_repos_parse_dumpstream3(svn_stream_t *stream, 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(); - /* The first two lines of the stream are the dumpfile-format version number, and a blank line. To preserve backward compatibility, don't assume the existence of newer parser-vtable functions. */ - SVN_ERR(parse_format_version(&version, linebuf->data)); + SVN_ERR(parse_format_version(&version, stream, linepool)); if (parse_fns->magic_header_record != NULL) SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool)); diff --git a/subversion/libsvn_repos/log.c b/subversion/libsvn_repos/log.c index 7dec5dd8d5b7..d9a1fb1085e1 100644 --- a/subversion/libsvn_repos/log.c +++ b/subversion/libsvn_repos/log.c @@ -655,7 +655,7 @@ fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog, 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. + without any overhead. */ while (change && (!any_mergeinfo || !any_copy)) { @@ -1227,7 +1227,7 @@ typedef struct interesting_merge_baton_t void *inner_baton; } interesting_merge_baton_t; -/* Implements svn_repos_path_change_receiver_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 @@ -2447,7 +2447,7 @@ svn_repos_get_logs5(svn_repos_t *repos, represents all of PATHS' history between START and END. We will use this later to squelch duplicate log revisions that might exist in both natural history and merged-in history. See - http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */ + https://issues.apache.org/jira/browse/SVN-3650#desc5 */ if (include_merged_revisions) { apr_pool_t *subpool = svn_pool_create(scratch_pool); diff --git a/subversion/libsvn_repos/replay.c b/subversion/libsvn_repos/replay.c index 9bcc667f6da4..4c825697e6a6 100644 --- a/subversion/libsvn_repos/replay.c +++ b/subversion/libsvn_repos/replay.c @@ -126,9 +126,6 @@ struct copy_info struct path_driver_cb_baton { - const svn_delta_editor_t *editor; - void *edit_baton; - /* The root of the revision we're replaying. */ svn_fs_root_t *root; @@ -454,14 +451,14 @@ fill_copyfrom(svn_fs_root_t **copyfrom_root, static svn_error_t * path_driver_cb_func(void **dir_baton, + const svn_delta_editor_t *editor, + void *edit_baton, void *parent_baton, void *callback_baton, const char *edit_path, apr_pool_t *pool) { struct path_driver_cb_baton *cb = callback_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_change3_t *change; svn_boolean_t do_add = FALSE, do_delete = FALSE; @@ -894,7 +891,7 @@ get_relevant_changes(apr_hash_t **changed_paths, } /* If the base_path doesn't match the top directory of this path - we don't want anything to do with it... + 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) @@ -957,8 +954,6 @@ svn_repos_replay2(svn_fs_root_t *root, low_water_mark = 0; /* Initialize our callback baton. */ - cb_baton.editor = editor; - cb_baton.edit_baton = edit_baton; cb_baton.root = root; cb_baton.changed_paths = changed_paths; cb_baton.authz_read_func = authz_read_func; @@ -989,7 +984,7 @@ svn_repos_replay2(svn_fs_root_t *root, } /* Call the path-based editor driver. */ - return svn_delta_path_driver2(editor, edit_baton, + return svn_delta_path_driver3(editor, edit_baton, paths, TRUE, path_driver_cb_func, &cb_baton, pool); #else diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c index 2333f561efda..2189de823d20 100644 --- a/subversion/libsvn_repos/repos.c +++ b/subversion/libsvn_repos/repos.c @@ -1183,7 +1183,7 @@ svn_repos_create(svn_repos_t **repos_p, 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 + /* If there was an error making the filesystem, e.g. unknown/supported * filesystem type. Clean up after ourselves. Yes this is safe because * create_repos_structure will fail if the path existed before we started * so we can't accidentally remove a directory that previously existed. @@ -1721,7 +1721,7 @@ svn_repos_recover4(const char *path, } struct freeze_baton_t { - apr_array_header_t *paths; + const apr_array_header_t *paths; int counter; svn_repos_freeze_func_t freeze_func; void *freeze_baton; @@ -1788,7 +1788,7 @@ multi_freeze(void *baton, and an SQLite reserved lock which means the repository is readable while frozen. */ svn_error_t * -svn_repos_freeze(apr_array_header_t *paths, +svn_repos_freeze(const apr_array_header_t *paths, svn_repos_freeze_func_t freeze_func, void *freeze_baton, apr_pool_t *pool) |