diff options
Diffstat (limited to 'subversion/libsvn_subr')
90 files changed, 50994 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/adler32.c b/subversion/libsvn_subr/adler32.c new file mode 100644 index 000000000000..e290e68e8ca0 --- /dev/null +++ b/subversion/libsvn_subr/adler32.c @@ -0,0 +1,101 @@ +/* + * adler32.c : routines for handling Adler-32 checksums + * + * ==================================================================== + * 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.h> +#include <zlib.h> + +#include "private/svn_adler32.h" + +/** + * An Adler-32 implementation per RFC1950. + * + * "The Adler-32 algorithm is much faster than the CRC32 algorithm yet + * still provides an extremely low probability of undetected errors" + */ + +/* + * 65521 is the largest prime less than 65536. + * "That 65521 is prime is important to avoid a possible large class of + * two-byte errors that leave the check unchanged." + */ +#define ADLER_MOD_BASE 65521 + +/* + * Start with CHECKSUM and update the checksum by processing a chunk + * of DATA sized LEN. + */ +apr_uint32_t +svn__adler32(apr_uint32_t checksum, const char *data, apr_off_t len) +{ + /* The actual limit can be set somewhat higher but should + * not be lower because the SIMD code would not be used + * in that case. + * + * However, it must be lower than 5552 to make sure our local + * implementation does not suffer from overflows. + */ + if (len >= 80) + { + /* Larger buffers can be effiently handled by Marc Adler's + * optimized code. Also, new zlib versions will come with + * SIMD code for x86 and x64. + */ + return (apr_uint32_t)adler32(checksum, + (const Bytef *)data, + (uInt)len); + } + else + { + const unsigned char *input = (const unsigned char *)data; + apr_uint32_t s1 = checksum & 0xFFFF; + apr_uint32_t s2 = checksum >> 16; + apr_uint32_t b; + + /* Some loop unrolling + * (approx. one clock tick per byte + 2 ticks loop overhead) + */ + for (; len >= 8; len -= 8, input += 8) + { + s1 += input[0]; s2 += s1; + s1 += input[1]; s2 += s1; + s1 += input[2]; s2 += s1; + s1 += input[3]; s2 += s1; + s1 += input[4]; s2 += s1; + s1 += input[5]; s2 += s1; + s1 += input[6]; s2 += s1; + s1 += input[7]; s2 += s1; + } + + /* Adler-32 calculation as a simple two ticks per iteration loop. + */ + while (len--) + { + b = *input++; + s1 += b; + s2 += s1; + } + + return ((s2 % ADLER_MOD_BASE) << 16) | (s1 % ADLER_MOD_BASE); + } +} diff --git a/subversion/libsvn_subr/atomic.c b/subversion/libsvn_subr/atomic.c new file mode 100644 index 000000000000..b760da4fe156 --- /dev/null +++ b/subversion/libsvn_subr/atomic.c @@ -0,0 +1,85 @@ +/* atomic.c : perform atomic initialization + * + * ==================================================================== + * 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_time.h> +#include "private/svn_atomic.h" + +/* Magic values for atomic initialization */ +#define SVN_ATOMIC_UNINITIALIZED 0 +#define SVN_ATOMIC_START_INIT 1 +#define SVN_ATOMIC_INIT_FAILED 2 +#define SVN_ATOMIC_INITIALIZED 3 + +svn_error_t* +svn_atomic__init_once(volatile svn_atomic_t *global_status, + svn_error_t *(*init_func)(void*,apr_pool_t*), + void *baton, + apr_pool_t* pool) +{ + /* !! Don't use localizable strings in this function, because these + !! might cause deadlocks. This function can be used to initialize + !! libraries that are used for generating error messages. */ + + /* We have to call init_func exactly once. Because APR + doesn't have statically-initialized mutexes, we implement a poor + man's spinlock using svn_atomic_cas. */ + svn_atomic_t status = svn_atomic_cas(global_status, + SVN_ATOMIC_START_INIT, + SVN_ATOMIC_UNINITIALIZED); + + if (status == SVN_ATOMIC_UNINITIALIZED) + { + svn_error_t *err = init_func(baton, pool); + if (err) + { +#if APR_HAS_THREADS + /* Tell other threads that the initialization failed. */ + svn_atomic_cas(global_status, + SVN_ATOMIC_INIT_FAILED, + SVN_ATOMIC_START_INIT); +#endif + return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, err, + "Couldn't perform atomic initialization"); + } + svn_atomic_cas(global_status, + SVN_ATOMIC_INITIALIZED, + SVN_ATOMIC_START_INIT); + } +#if APR_HAS_THREADS + /* Wait for whichever thread is performing initialization to finish. */ + /* XXX FIXME: Should we have a maximum wait here, like we have in + the Windows file IO spinner? */ + else while (status != SVN_ATOMIC_INITIALIZED) + { + if (status == SVN_ATOMIC_INIT_FAILED) + return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, NULL, + "Couldn't perform atomic initialization"); + + apr_sleep(APR_USEC_PER_SEC / 1000); + status = svn_atomic_cas(global_status, + SVN_ATOMIC_UNINITIALIZED, + SVN_ATOMIC_UNINITIALIZED); + } +#endif /* APR_HAS_THREADS */ + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/auth.c b/subversion/libsvn_subr/auth.c new file mode 100644 index 000000000000..540635827d30 --- /dev/null +++ b/subversion/libsvn_subr/auth.c @@ -0,0 +1,652 @@ +/* + * auth.c: authentication support functions for Subversion + * + * ==================================================================== + * 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_tables.h> +#include <apr_strings.h> + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_auth.h" +#include "svn_config.h" +#include "svn_private_config.h" +#include "svn_dso.h" +#include "svn_version.h" +#include "private/svn_dep_compat.h" + +#include "auth.h" + +/* AN OVERVIEW + =========== + + A good way to think of this machinery is as a set of tables. + + - Each type of credentials selects a single table. + + - In a given table, each row is a 'provider' capable of returning + the same type of credentials. Each column represents a + provider's repeated attempts to provide credentials. + + + Fetching Credentials from Providers + ----------------------------------- + + When the caller asks for a particular type of credentials, the + machinery in this file walks over the appropriate table. It starts + with the first provider (first row), and calls first_credentials() + to get the first set of credentials (first column). If the caller + is unhappy with the credentials, then each subsequent call to + next_credentials() traverses the row from left to right. If the + provider returns error at any point, then we go to the next provider + (row). We continue this way until every provider fails, or + until the client is happy with the returned credentials. + + Note that the caller cannot see the table traversal, and thus has + no idea when we switch providers. + + + Storing Credentials with Providers + ---------------------------------- + + When the server has validated a set of credentials, and when + credential caching is enabled, we have the chance to store those + credentials for later use. The provider which provided the working + credentials is the first one given the opportunity to (re)cache + those credentials. Its save_credentials() function is invoked with + the working credentials. If that provider reports that it + successfully stored the credentials, we're done. Otherwise, we + walk the providers (rows) for that type of credentials in order + from the top of the table, allowing each in turn the opportunity to + store the credentials. When one reports that it has done so + successfully -- or when we run out of providers (rows) to try -- + the table walk ends. +*/ + + + +/* This effectively defines a single table. Every provider in this + array returns the same kind of credentials. */ +typedef struct provider_set_t +{ + /* ordered array of svn_auth_provider_object_t */ + apr_array_header_t *providers; + +} provider_set_t; + + +/* The main auth baton. */ +struct svn_auth_baton_t +{ + /* a collection of tables. maps cred_kind -> provider_set */ + apr_hash_t *tables; + + /* the pool I'm allocated in. */ + apr_pool_t *pool; + + /* run-time parameters needed by providers. */ + apr_hash_t *parameters; + + /* run-time credentials cache. */ + apr_hash_t *creds_cache; + +}; + +/* Abstracted iteration baton */ +struct svn_auth_iterstate_t +{ + provider_set_t *table; /* the table being searched */ + int provider_idx; /* the current provider (row) */ + svn_boolean_t got_first; /* did we get the provider's first creds? */ + void *provider_iter_baton; /* the provider's own iteration context */ + const char *realmstring; /* The original realmstring passed in */ + const char *cache_key; /* key to use in auth_baton's creds_cache */ + svn_auth_baton_t *auth_baton; /* the original auth_baton. */ +}; + + + +void +svn_auth_open(svn_auth_baton_t **auth_baton, + const apr_array_header_t *providers, + apr_pool_t *pool) +{ + svn_auth_baton_t *ab; + svn_auth_provider_object_t *provider; + int i; + + /* Build the auth_baton. */ + ab = apr_pcalloc(pool, sizeof(*ab)); + ab->tables = apr_hash_make(pool); + ab->parameters = apr_hash_make(pool); + ab->creds_cache = apr_hash_make(pool); + ab->pool = pool; + + /* Register each provider in order. Providers of different + credentials will be automatically sorted into different tables by + register_provider(). */ + for (i = 0; i < providers->nelts; i++) + { + provider_set_t *table; + provider = APR_ARRAY_IDX(providers, i, svn_auth_provider_object_t *); + + /* Add it to the appropriate table in the auth_baton */ + table = svn_hash_gets(ab->tables, provider->vtable->cred_kind); + if (! table) + { + table = apr_pcalloc(pool, sizeof(*table)); + table->providers + = apr_array_make(pool, 1, sizeof(svn_auth_provider_object_t *)); + + svn_hash_sets(ab->tables, provider->vtable->cred_kind, table); + } + APR_ARRAY_PUSH(table->providers, svn_auth_provider_object_t *) + = provider; + } + + *auth_baton = ab; +} + + + +void +svn_auth_set_parameter(svn_auth_baton_t *auth_baton, + const char *name, + const void *value) +{ + svn_hash_sets(auth_baton->parameters, name, value); +} + +const void * +svn_auth_get_parameter(svn_auth_baton_t *auth_baton, + const char *name) +{ + return svn_hash_gets(auth_baton->parameters, name); +} + + +/* Return the key used to address the in-memory cache of auth + credentials of type CRED_KIND and associated with REALMSTRING. */ +static const char * +make_cache_key(const char *cred_kind, + const char *realmstring, + apr_pool_t *pool) +{ + return apr_pstrcat(pool, cred_kind, ":", realmstring, (char *)NULL); +} + +svn_error_t * +svn_auth_first_credentials(void **credentials, + svn_auth_iterstate_t **state, + const char *cred_kind, + const char *realmstring, + svn_auth_baton_t *auth_baton, + apr_pool_t *pool) +{ + int i = 0; + provider_set_t *table; + svn_auth_provider_object_t *provider = NULL; + void *creds = NULL; + void *iter_baton = NULL; + svn_boolean_t got_first = FALSE; + svn_auth_iterstate_t *iterstate; + const char *cache_key; + + /* Get the appropriate table of providers for CRED_KIND. */ + table = svn_hash_gets(auth_baton->tables, cred_kind); + if (! table) + return svn_error_createf(SVN_ERR_AUTHN_NO_PROVIDER, NULL, + _("No provider registered for '%s' credentials"), + cred_kind); + + /* First, see if we have cached creds in the auth_baton. */ + cache_key = make_cache_key(cred_kind, realmstring, pool); + creds = svn_hash_gets(auth_baton->creds_cache, cache_key); + if (creds) + { + got_first = FALSE; + } + else + /* If not, find a provider that can give "first" credentials. */ + { + /* Find a provider that can give "first" credentials. */ + for (i = 0; i < table->providers->nelts; i++) + { + provider = APR_ARRAY_IDX(table->providers, i, + svn_auth_provider_object_t *); + SVN_ERR(provider->vtable->first_credentials(&creds, &iter_baton, + provider->provider_baton, + auth_baton->parameters, + realmstring, + auth_baton->pool)); + + if (creds != NULL) + { + got_first = TRUE; + break; + } + } + } + + if (! creds) + *state = NULL; + else + { + /* Build an abstract iteration state. */ + iterstate = apr_pcalloc(pool, sizeof(*iterstate)); + iterstate->table = table; + iterstate->provider_idx = i; + iterstate->got_first = got_first; + iterstate->provider_iter_baton = iter_baton; + iterstate->realmstring = apr_pstrdup(pool, realmstring); + iterstate->cache_key = cache_key; + iterstate->auth_baton = auth_baton; + *state = iterstate; + + /* Put the creds in the cache */ + svn_hash_sets(auth_baton->creds_cache, + apr_pstrdup(auth_baton->pool, cache_key), + creds); + } + + *credentials = creds; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth_next_credentials(void **credentials, + svn_auth_iterstate_t *state, + apr_pool_t *pool) +{ + svn_auth_baton_t *auth_baton = state->auth_baton; + svn_auth_provider_object_t *provider; + provider_set_t *table = state->table; + void *creds = NULL; + + /* Continue traversing the table from where we left off. */ + for (/* no init */; + state->provider_idx < table->providers->nelts; + state->provider_idx++) + { + provider = APR_ARRAY_IDX(table->providers, + state->provider_idx, + svn_auth_provider_object_t *); + if (! state->got_first) + { + SVN_ERR(provider->vtable->first_credentials( + &creds, &(state->provider_iter_baton), + provider->provider_baton, auth_baton->parameters, + state->realmstring, auth_baton->pool)); + state->got_first = TRUE; + } + else if (provider->vtable->next_credentials) + { + SVN_ERR(provider->vtable->next_credentials( + &creds, state->provider_iter_baton, + provider->provider_baton, auth_baton->parameters, + state->realmstring, auth_baton->pool)); + } + + if (creds != NULL) + { + /* Put the creds in the cache */ + svn_hash_sets(auth_baton->creds_cache, state->cache_key, creds); + break; + } + + state->got_first = FALSE; + } + + *credentials = creds; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth_save_credentials(svn_auth_iterstate_t *state, + apr_pool_t *pool) +{ + int i; + svn_auth_provider_object_t *provider; + svn_boolean_t save_succeeded = FALSE; + const char *no_auth_cache; + svn_auth_baton_t *auth_baton; + void *creds; + + if (! state || state->table->providers->nelts <= state->provider_idx) + return SVN_NO_ERROR; + + auth_baton = state->auth_baton; + creds = svn_hash_gets(state->auth_baton->creds_cache, state->cache_key); + if (! creds) + return SVN_NO_ERROR; + + /* Do not save the creds if SVN_AUTH_PARAM_NO_AUTH_CACHE is set */ + no_auth_cache = svn_hash_gets(auth_baton->parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + if (no_auth_cache) + return SVN_NO_ERROR; + + /* First, try to save the creds using the provider that produced them. */ + provider = APR_ARRAY_IDX(state->table->providers, + state->provider_idx, + svn_auth_provider_object_t *); + if (provider->vtable->save_credentials) + SVN_ERR(provider->vtable->save_credentials(&save_succeeded, + creds, + provider->provider_baton, + auth_baton->parameters, + state->realmstring, + pool)); + if (save_succeeded) + return SVN_NO_ERROR; + + /* Otherwise, loop from the top of the list, asking every provider + to attempt a save. ### todo: someday optimize so we don't + necessarily start from the top of the list. */ + for (i = 0; i < state->table->providers->nelts; i++) + { + provider = APR_ARRAY_IDX(state->table->providers, i, + svn_auth_provider_object_t *); + if (provider->vtable->save_credentials) + SVN_ERR(provider->vtable->save_credentials + (&save_succeeded, creds, + provider->provider_baton, + auth_baton->parameters, + state->realmstring, + pool)); + + if (save_succeeded) + break; + } + + /* ### notice that at the moment, if no provider can save, there's + no way the caller will know. */ + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth_forget_credentials(svn_auth_baton_t *auth_baton, + const char *cred_kind, + const char *realmstring, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT((cred_kind && realmstring) || (!cred_kind && !realmstring)); + + /* If we have a CRED_KIND and REALMSTRING, we clear out just the + cached item (if any). Otherwise, empty the whole hash. */ + if (cred_kind) + { + svn_hash_sets(auth_baton->creds_cache, + make_cache_key(cred_kind, realmstring, scratch_pool), + NULL); + } + else + { + apr_hash_clear(auth_baton->creds_cache); + } + + return SVN_NO_ERROR; +} + + +svn_auth_ssl_server_cert_info_t * +svn_auth_ssl_server_cert_info_dup + (const svn_auth_ssl_server_cert_info_t *info, apr_pool_t *pool) +{ + svn_auth_ssl_server_cert_info_t *new_info + = apr_palloc(pool, sizeof(*new_info)); + + *new_info = *info; + + new_info->hostname = apr_pstrdup(pool, new_info->hostname); + new_info->fingerprint = apr_pstrdup(pool, new_info->fingerprint); + new_info->valid_from = apr_pstrdup(pool, new_info->valid_from); + new_info->valid_until = apr_pstrdup(pool, new_info->valid_until); + new_info->issuer_dname = apr_pstrdup(pool, new_info->issuer_dname); + new_info->ascii_cert = apr_pstrdup(pool, new_info->ascii_cert); + + return new_info; +} + +svn_error_t * +svn_auth_get_platform_specific_provider(svn_auth_provider_object_t **provider, + const char *provider_name, + const char *provider_type, + apr_pool_t *pool) +{ + *provider = NULL; + + if (apr_strnatcmp(provider_name, "gnome_keyring") == 0 || + apr_strnatcmp(provider_name, "kwallet") == 0) + { +#if defined(SVN_HAVE_GNOME_KEYRING) || defined(SVN_HAVE_KWALLET) + apr_dso_handle_t *dso; + apr_dso_handle_sym_t provider_function_symbol, version_function_symbol; + const char *library_label, *library_name; + const char *provider_function_name, *version_function_name; + library_name = apr_psprintf(pool, + "libsvn_auth_%s-%d.so.%d", + provider_name, + SVN_VER_MAJOR, SVN_SOVERSION); + library_label = apr_psprintf(pool, "svn_%s", provider_name); + provider_function_name = apr_psprintf(pool, + "svn_auth_get_%s_%s_provider", + provider_name, provider_type); + version_function_name = apr_psprintf(pool, + "svn_auth_%s_version", + provider_name); + SVN_ERR(svn_dso_load(&dso, library_name)); + if (dso) + { + if (apr_dso_sym(&version_function_symbol, + dso, + version_function_name) == 0) + { + svn_version_func_t version_function + = version_function_symbol; + svn_version_checklist_t check_list[2]; + + check_list[0].label = library_label; + check_list[0].version_query = version_function; + check_list[1].label = NULL; + check_list[1].version_query = NULL; + SVN_ERR(svn_ver_check_list(svn_subr_version(), check_list)); + } + if (apr_dso_sym(&provider_function_symbol, + dso, + provider_function_name) == 0) + { + if (strcmp(provider_type, "simple") == 0) + { + svn_auth_simple_provider_func_t provider_function + = provider_function_symbol; + provider_function(provider, pool); + } + else if (strcmp(provider_type, "ssl_client_cert_pw") == 0) + { + svn_auth_ssl_client_cert_pw_provider_func_t provider_function + = provider_function_symbol; + provider_function(provider, pool); + } + } + } +#endif + } + else + { +#if defined(SVN_HAVE_GPG_AGENT) + if (strcmp(provider_name, "gpg_agent") == 0 && + strcmp(provider_type, "simple") == 0) + { + svn_auth_get_gpg_agent_simple_provider(provider, pool); + } +#endif +#ifdef SVN_HAVE_KEYCHAIN_SERVICES + if (strcmp(provider_name, "keychain") == 0 && + strcmp(provider_type, "simple") == 0) + { + svn_auth_get_keychain_simple_provider(provider, pool); + } + else if (strcmp(provider_name, "keychain") == 0 && + strcmp(provider_type, "ssl_client_cert_pw") == 0) + { + svn_auth_get_keychain_ssl_client_cert_pw_provider(provider, pool); + } +#endif + +#if defined(WIN32) && !defined(__MINGW32__) + if (strcmp(provider_name, "windows") == 0 && + strcmp(provider_type, "simple") == 0) + { + svn_auth_get_windows_simple_provider(provider, pool); + } + else if (strcmp(provider_name, "windows") == 0 && + strcmp(provider_type, "ssl_client_cert_pw") == 0) + { + svn_auth_get_windows_ssl_client_cert_pw_provider(provider, pool); + } + else if (strcmp(provider_name, "windows") == 0 && + strcmp(provider_type, "ssl_server_trust") == 0) + { + svn_auth_get_windows_ssl_server_trust_provider(provider, pool); + } +#endif + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_auth_get_platform_specific_client_providers(apr_array_header_t **providers, + svn_config_t *config, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *provider; + const char *password_stores_config_option; + apr_array_header_t *password_stores; + int i; + +#define SVN__MAYBE_ADD_PROVIDER(list, p) \ + { if (p) APR_ARRAY_PUSH(list, svn_auth_provider_object_t *) = p; } + +#define SVN__DEFAULT_AUTH_PROVIDER_LIST \ + "gnome-keyring,kwallet,keychain,gpg-agent,windows-cryptoapi" + + *providers = apr_array_make(pool, 12, sizeof(svn_auth_provider_object_t *)); + + /* Fetch the configured list of password stores, and split them into + an array. */ + svn_config_get(config, + &password_stores_config_option, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_PASSWORD_STORES, + SVN__DEFAULT_AUTH_PROVIDER_LIST); + password_stores = svn_cstring_split(password_stores_config_option, + " ,", TRUE, pool); + + for (i = 0; i < password_stores->nelts; i++) + { + const char *password_store = APR_ARRAY_IDX(password_stores, i, + const char *); + + /* GNOME Keyring */ + if (apr_strnatcmp(password_store, "gnome-keyring") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "gnome_keyring", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "gnome_keyring", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* GPG-AGENT */ + else if (apr_strnatcmp(password_store, "gpg-agent") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "gpg_agent", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* KWallet */ + else if (apr_strnatcmp(password_store, "kwallet") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "kwallet", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "kwallet", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* Keychain */ + else if (apr_strnatcmp(password_store, "keychain") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "keychain", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "keychain", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + /* Windows */ + else if (apr_strnatcmp(password_store, "windows-cryptoapi") == 0) + { + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "windows", + "simple", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "windows", + "ssl_client_cert_pw", + pool)); + SVN__MAYBE_ADD_PROVIDER(*providers, provider); + } + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/auth.h b/subversion/libsvn_subr/auth.h new file mode 100644 index 000000000000..0885f6d7fbd0 --- /dev/null +++ b/subversion/libsvn_subr/auth.h @@ -0,0 +1,49 @@ +/* + * auth.h : shared stuff internal to the subr library. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_AUTH_H +#define SVN_LIBSVN_SUBR_AUTH_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "svn_auth.h" + +/* Helper for svn_config_{read|write}_auth_data. Return a path to a + file within ~/.subversion/auth/ that holds CRED_KIND credentials + within REALMSTRING. If no path is available *PATH will be set to + NULL. */ +svn_error_t * +svn_auth__file_path(const char **path, + const char *cred_kind, + const char *realmstring, + const char *config_dir, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_AUTH_H */ diff --git a/subversion/libsvn_subr/base64.c b/subversion/libsvn_subr/base64.c new file mode 100644 index 000000000000..97ee3d285c50 --- /dev/null +++ b/subversion/libsvn_subr/base64.c @@ -0,0 +1,567 @@ +/* + * base64.c: base64 encoding and decoding functions + * + * ==================================================================== + * 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 <string.h> + +#include <apr.h> +#include <apr_pools.h> +#include <apr_general.h> /* for APR_INLINE */ + +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_error.h" +#include "svn_base64.h" +#include "private/svn_string_private.h" +#include "private/svn_subr_private.h" + +/* When asked to format the base64-encoded output as multiple lines, + we put this many chars in each line (plus one new line char) unless + we run out of data. + It is vital for some of the optimizations below that this value is + a multiple of 4. */ +#define BASE64_LINELEN 76 + +/* This number of bytes is encoded in a line of base64 chars. */ +#define BYTES_PER_LINE (BASE64_LINELEN / 4 * 3) + +/* Value -> base64 char mapping table (2^6 entries) */ +static const char base64tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "abcdefghijklmnopqrstuvwxyz0123456789+/"; + + +/* Binary input --> base64-encoded output */ + +struct encode_baton { + svn_stream_t *output; + unsigned char buf[3]; /* Bytes waiting to be encoded */ + size_t buflen; /* Number of bytes waiting */ + size_t linelen; /* Bytes output so far on this line */ + apr_pool_t *scratch_pool; +}; + + +/* Base64-encode a group. IN needs to have three bytes and OUT needs + to have room for four bytes. The input group is treated as four + six-bit units which are treated as lookups into base64tab for the + bytes of the output group. */ +static APR_INLINE void +encode_group(const unsigned char *in, char *out) +{ + /* Expand input bytes to machine word length (with zero extra cost + on x86/x64) ... */ + apr_size_t part0 = in[0]; + apr_size_t part1 = in[1]; + apr_size_t part2 = in[2]; + + /* ... to prevent these arithmetic operations from being limited to + byte size. This saves non-zero cost conversions of the result when + calculating the addresses within base64tab. */ + out[0] = base64tab[part0 >> 2]; + out[1] = base64tab[((part0 & 3) << 4) | (part1 >> 4)]; + out[2] = base64tab[((part1 & 0xf) << 2) | (part2 >> 6)]; + out[3] = base64tab[part2 & 0x3f]; +} + +/* Base64-encode a line, i.e. BYTES_PER_LINE bytes from DATA into + BASE64_LINELEN chars and append it to STR. It does not assume that + a new line char will be appended, though. + The code in this function will simply transform the data without + performing any boundary checks. Therefore, DATA must have at least + BYTES_PER_LINE left and space for at least another BASE64_LINELEN + chars must have been pre-allocated in STR before calling this + function. */ +static void +encode_line(svn_stringbuf_t *str, const char *data) +{ + /* Translate directly from DATA to STR->DATA. */ + const unsigned char *in = (const unsigned char *)data; + char *out = str->data + str->len; + char *end = out + BASE64_LINELEN; + + /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN + a multiple of 4. */ + for ( ; out != end; in += 3, out += 4) + encode_group(in, out); + + /* Expand and terminate the string. */ + *out = '\0'; + str->len += BASE64_LINELEN; +} + +/* (Continue to) Base64-encode the byte string DATA (of length LEN) + into STR. Include newlines every so often if BREAK_LINES is true. + INBUF, INBUFLEN, and LINELEN are used internally; the caller shall + make INBUF have room for three characters and initialize *INBUFLEN + and *LINELEN to 0. + + INBUF and *INBUFLEN carry the leftover data from call to call, and + *LINELEN carries the length of the current output line. */ +static void +encode_bytes(svn_stringbuf_t *str, const void *data, apr_size_t len, + unsigned char *inbuf, size_t *inbuflen, size_t *linelen, + svn_boolean_t break_lines) +{ + char group[4]; + const char *p = data, *end = p + len; + apr_size_t buflen; + + /* Resize the stringbuf to make room for the (approximate) size of + output, to avoid repeated resizes later. + Please note that our optimized code relies on the fact that STR + never needs to be resized until we leave this function. */ + buflen = len * 4 / 3 + 4; + if (break_lines) + { + /* Add an extra space for line breaks. */ + buflen += buflen / BASE64_LINELEN; + } + svn_stringbuf_ensure(str, str->len + buflen); + + /* Keep encoding three-byte groups until we run out. */ + while (*inbuflen + (end - p) >= 3) + { + /* May we encode BYTES_PER_LINE bytes without caring about + line breaks, data in the temporary INBUF or running out + of data? */ + if ( *inbuflen == 0 + && (*linelen == 0 || !break_lines) + && (end - p >= BYTES_PER_LINE)) + { + /* Yes, we can encode a whole chunk of data at once. */ + encode_line(str, p); + p += BYTES_PER_LINE; + *linelen += BASE64_LINELEN; + } + else + { + /* No, this is one of a number of special cases. + Encode the data byte by byte. */ + memcpy(inbuf + *inbuflen, p, 3 - *inbuflen); + p += (3 - *inbuflen); + encode_group(inbuf, group); + svn_stringbuf_appendbytes(str, group, 4); + *inbuflen = 0; + *linelen += 4; + } + + /* Add line breaks as necessary. */ + if (break_lines && *linelen == BASE64_LINELEN) + { + svn_stringbuf_appendbyte(str, '\n'); + *linelen = 0; + } + } + + /* Tack any extra input onto *INBUF. */ + memcpy(inbuf + *inbuflen, p, end - p); + *inbuflen += (end - p); +} + + +/* Encode leftover data, if any, and possibly a final newline (if + there has been any data and BREAK_LINES is set), appending to STR. + LEN must be in the range 0..2. */ +static void +encode_partial_group(svn_stringbuf_t *str, const unsigned char *extra, + size_t len, size_t linelen, svn_boolean_t break_lines) +{ + unsigned char ingroup[3]; + char outgroup[4]; + + if (len > 0) + { + memcpy(ingroup, extra, len); + memset(ingroup + len, 0, 3 - len); + encode_group(ingroup, outgroup); + memset(outgroup + (len + 1), '=', 4 - (len + 1)); + svn_stringbuf_appendbytes(str, outgroup, 4); + linelen += 4; + } + if (break_lines && linelen > 0) + svn_stringbuf_appendbyte(str, '\n'); +} + + +/* Write handler for svn_base64_encode. */ +static svn_error_t * +encode_data(void *baton, const char *data, apr_size_t *len) +{ + struct encode_baton *eb = baton; + svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool); + apr_size_t enclen; + svn_error_t *err = SVN_NO_ERROR; + + /* Encode this block of data and write it out. */ + encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen, TRUE); + enclen = encoded->len; + if (enclen != 0) + err = svn_stream_write(eb->output, encoded->data, &enclen); + svn_pool_clear(eb->scratch_pool); + return err; +} + + +/* Close handler for svn_base64_encode(). */ +static svn_error_t * +finish_encoding_data(void *baton) +{ + struct encode_baton *eb = baton; + svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool); + apr_size_t enclen; + svn_error_t *err = SVN_NO_ERROR; + + /* Encode a partial group at the end if necessary, and write it out. */ + encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen, TRUE); + enclen = encoded->len; + if (enclen != 0) + err = svn_stream_write(eb->output, encoded->data, &enclen); + + /* Pass on the close request and clean up the baton. */ + if (err == SVN_NO_ERROR) + err = svn_stream_close(eb->output); + svn_pool_destroy(eb->scratch_pool); + return err; +} + + +svn_stream_t * +svn_base64_encode(svn_stream_t *output, apr_pool_t *pool) +{ + struct encode_baton *eb = apr_palloc(pool, sizeof(*eb)); + svn_stream_t *stream; + + eb->output = output; + eb->buflen = 0; + eb->linelen = 0; + eb->scratch_pool = svn_pool_create(pool); + stream = svn_stream_create(eb, pool); + svn_stream_set_write(stream, encode_data); + svn_stream_set_close(stream, finish_encoding_data); + return stream; +} + + +const svn_string_t * +svn_base64_encode_string2(const svn_string_t *str, + svn_boolean_t break_lines, + apr_pool_t *pool) +{ + svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool); + unsigned char ingroup[3]; + size_t ingrouplen = 0; + size_t linelen = 0; + + encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen, + break_lines); + encode_partial_group(encoded, ingroup, ingrouplen, linelen, + break_lines); + return svn_stringbuf__morph_into_string(encoded); +} + +const svn_string_t * +svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool) +{ + return svn_base64_encode_string2(str, TRUE, pool); +} + + + +/* Base64-encoded input --> binary output */ + +struct decode_baton { + svn_stream_t *output; + unsigned char buf[4]; /* Bytes waiting to be decoded */ + int buflen; /* Number of bytes waiting */ + svn_boolean_t done; /* True if we already saw an '=' */ + apr_pool_t *scratch_pool; +}; + + +/* Base64-decode a group. IN needs to have four bytes and OUT needs + to have room for three bytes. The input bytes must already have + been decoded from base64tab into the range 0..63. The four + six-bit values are pasted together to form three eight-bit bytes. */ +static APR_INLINE void +decode_group(const unsigned char *in, char *out) +{ + out[0] = (char)((in[0] << 2) | (in[1] >> 4)); + out[1] = (char)(((in[1] & 0xf) << 4) | (in[2] >> 2)); + out[2] = (char)(((in[2] & 0x3) << 6) | in[3]); +} + +/* Lookup table for base64 characters; reverse_base64[ch] gives a + negative value if ch is not a valid base64 character, or otherwise + the value of the byte represented; 'A' => 0 etc. */ +static const signed char reverse_base64[256] = { +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, +52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, +-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, +15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, +-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, +41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* Similar to decode_group but this function also translates the + 6-bit values from the IN buffer before translating them. + Return FALSE if a non-base64 char (e.g. '=' or new line) + has been encountered. */ +static APR_INLINE svn_boolean_t +decode_group_directly(const unsigned char *in, char *out) +{ + /* Translate the base64 chars in values [0..63, 0xff] */ + apr_size_t part0 = (unsigned char)reverse_base64[(unsigned char)in[0]]; + apr_size_t part1 = (unsigned char)reverse_base64[(unsigned char)in[1]]; + apr_size_t part2 = (unsigned char)reverse_base64[(unsigned char)in[2]]; + apr_size_t part3 = (unsigned char)reverse_base64[(unsigned char)in[3]]; + + /* Pack 4x6 bits into 3x8.*/ + out[0] = (char)((part0 << 2) | (part1 >> 4)); + out[1] = (char)(((part1 & 0xf) << 4) | (part2 >> 2)); + out[2] = (char)(((part2 & 0x3) << 6) | part3); + + /* FALSE, iff any part is 0xff. */ + return (part0 | part1 | part2 | part3) != (unsigned char)(-1); +} + +/* Base64-encode up to BASE64_LINELEN chars from *DATA and append it to + STR. After the function returns, *DATA will point to the first char + that has not been translated, yet. Returns TRUE if all BASE64_LINELEN + chars could be translated, i.e. no special char has been encountered + in between. + The code in this function will simply transform the data without + performing any boundary checks. Therefore, DATA must have at least + BASE64_LINELEN left and space for at least another BYTES_PER_LINE + chars must have been pre-allocated in STR before calling this + function. */ +static svn_boolean_t +decode_line(svn_stringbuf_t *str, const char **data) +{ + /* Decode up to BYTES_PER_LINE bytes directly from *DATA into STR->DATA. */ + const unsigned char *p = *(const unsigned char **)data; + char *out = str->data + str->len; + char *end = out + BYTES_PER_LINE; + + /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN + a multiple of 4. Stop translation as soon as we encounter a special + char. Leave the entire group untouched in that case. */ + for (; out < end; p += 4, out += 3) + if (!decode_group_directly(p, out)) + break; + + /* Update string sizes and positions. */ + str->len = out - str->data; + *out = '\0'; + *data = (const char *)p; + + /* Return FALSE, if the caller should continue the decoding process + using the slow standard method. */ + return out == end; +} + + +/* (Continue to) Base64-decode the byte string DATA (of length LEN) + into STR. INBUF, INBUFLEN, and DONE are used internally; the + caller shall have room for four bytes in INBUF and initialize + *INBUFLEN to 0 and *DONE to FALSE. + + INBUF and *INBUFLEN carry the leftover bytes from call to call, and + *DONE keeps track of whether we've seen an '=' which terminates the + encoded data. */ +static void +decode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len, + unsigned char *inbuf, int *inbuflen, svn_boolean_t *done) +{ + const char *p = data; + char group[3]; + signed char find; + const char *end = data + len; + + /* Resize the stringbuf to make room for the maximum size of output, + to avoid repeated resizes later. The optimizations in + decode_line rely on no resizes being necessary! + + (*inbuflen+len) is encoded data length + (*inbuflen+len)/4 is the number of complete 4-bytes sets + (*inbuflen+len)/4*3 is the number of decoded bytes + svn_stringbuf_ensure will add an additional byte for the terminating 0. + */ + svn_stringbuf_ensure(str, str->len + ((*inbuflen + len) / 4) * 3); + + while ( !*done && p < end ) + { + /* If no data is left in temporary INBUF and there is at least + one line-sized chunk left to decode, we may use the optimized + code path. */ + if ((*inbuflen == 0) && (p + BASE64_LINELEN <= end)) + if (decode_line(str, &p)) + continue; + + /* A special case or decode_line encountered a special char. */ + if (*p == '=') + { + /* We are at the end and have to decode a partial group. */ + if (*inbuflen >= 2) + { + memset(inbuf + *inbuflen, 0, 4 - *inbuflen); + decode_group(inbuf, group); + svn_stringbuf_appendbytes(str, group, *inbuflen - 1); + } + *done = TRUE; + } + else + { + find = reverse_base64[(unsigned char)*p]; + ++p; + + if (find >= 0) + inbuf[(*inbuflen)++] = find; + if (*inbuflen == 4) + { + decode_group(inbuf, group); + svn_stringbuf_appendbytes(str, group, 3); + *inbuflen = 0; + } + } + } +} + + +/* Write handler for svn_base64_decode. */ +static svn_error_t * +decode_data(void *baton, const char *data, apr_size_t *len) +{ + struct decode_baton *db = baton; + svn_stringbuf_t *decoded; + apr_size_t declen; + svn_error_t *err = SVN_NO_ERROR; + + /* Decode this block of data. */ + decoded = svn_stringbuf_create_empty(db->scratch_pool); + decode_bytes(decoded, data, *len, db->buf, &db->buflen, &db->done); + + /* Write the output, clean up, go home. */ + declen = decoded->len; + if (declen != 0) + err = svn_stream_write(db->output, decoded->data, &declen); + svn_pool_clear(db->scratch_pool); + return err; +} + + +/* Close handler for svn_base64_decode(). */ +static svn_error_t * +finish_decoding_data(void *baton) +{ + struct decode_baton *db = baton; + svn_error_t *err; + + /* Pass on the close request and clean up the baton. */ + err = svn_stream_close(db->output); + svn_pool_destroy(db->scratch_pool); + return err; +} + + +svn_stream_t * +svn_base64_decode(svn_stream_t *output, apr_pool_t *pool) +{ + struct decode_baton *db = apr_palloc(pool, sizeof(*db)); + svn_stream_t *stream; + + db->output = output; + db->buflen = 0; + db->done = FALSE; + db->scratch_pool = svn_pool_create(pool); + stream = svn_stream_create(db, pool); + svn_stream_set_write(stream, decode_data); + svn_stream_set_close(stream, finish_decoding_data); + return stream; +} + + +const svn_string_t * +svn_base64_decode_string(const svn_string_t *str, apr_pool_t *pool) +{ + svn_stringbuf_t *decoded = svn_stringbuf_create_empty(pool); + unsigned char ingroup[4]; + int ingrouplen = 0; + svn_boolean_t done = FALSE; + + decode_bytes(decoded, str->data, str->len, ingroup, &ingrouplen, &done); + return svn_stringbuf__morph_into_string(decoded); +} + + +/* Return a base64-encoded representation of CHECKSUM, allocated in POOL. + If CHECKSUM->kind is not recognized, return NULL. + ### That 'NULL' claim was in the header file when this was public, but + doesn't look true in the implementation. + + ### This is now only used as a new implementation of svn_base64_from_md5(); + it would probably be safer to revert that to its old implementation. */ +static svn_stringbuf_t * +base64_from_checksum(const svn_checksum_t *checksum, apr_pool_t *pool) +{ + svn_stringbuf_t *checksum_str; + unsigned char ingroup[3]; + size_t ingrouplen = 0; + size_t linelen = 0; + checksum_str = svn_stringbuf_create_empty(pool); + + encode_bytes(checksum_str, checksum->digest, + svn_checksum_size(checksum), ingroup, &ingrouplen, + &linelen, TRUE); + encode_partial_group(checksum_str, ingroup, ingrouplen, linelen, TRUE); + + /* Our base64-encoding routines append a final newline if any data + was created at all, so let's hack that off. */ + if (checksum_str->len) + { + checksum_str->len--; + checksum_str->data[checksum_str->len] = 0; + } + + return checksum_str; +} + + +svn_stringbuf_t * +svn_base64_from_md5(unsigned char digest[], apr_pool_t *pool) +{ + svn_checksum_t *checksum + = svn_checksum__from_digest_md5(digest, pool); + + return base64_from_checksum(checksum, pool); +} diff --git a/subversion/libsvn_subr/cache-inprocess.c b/subversion/libsvn_subr/cache-inprocess.c new file mode 100644 index 000000000000..6401f9f7c26f --- /dev/null +++ b/subversion/libsvn_subr/cache-inprocess.c @@ -0,0 +1,648 @@ +/* + * cache-inprocess.c: in-memory caching for Subversion + * + * ==================================================================== + * 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 <assert.h> + +#include <apr_thread_mutex.h> + +#include "svn_pools.h" + +#include "svn_private_config.h" + +#include "cache.h" +#include "private/svn_mutex.h" + +/* The (internal) cache object. */ +typedef struct inprocess_cache_t { + /* A user-defined identifier for this cache instance. */ + const char *id; + + /* HASH maps a key (of size KLEN) to a struct cache_entry. */ + apr_hash_t *hash; + apr_ssize_t klen; + + /* Used to copy values into the cache. */ + svn_cache__serialize_func_t serialize_func; + + /* Used to copy values out of the cache. */ + svn_cache__deserialize_func_t deserialize_func; + + /* Maximum number of pages that this cache instance may allocate */ + apr_uint64_t total_pages; + /* The number of pages we're allowed to allocate before having to + * try to reuse one. */ + apr_uint64_t unallocated_pages; + /* Number of cache entries stored on each page. Must be at least 1. */ + apr_uint64_t items_per_page; + + /* A dummy cache_page serving as the head of a circular doubly + * linked list of cache_pages. SENTINEL->NEXT is the most recently + * used page, and SENTINEL->PREV is the least recently used page. + * All pages in this list are "full"; the page currently being + * filled (PARTIAL_PAGE) is not in the list. */ + struct cache_page *sentinel; + + /* A page currently being filled with entries, or NULL if there's no + * partially-filled page. This page is not in SENTINEL's list. */ + struct cache_page *partial_page; + /* If PARTIAL_PAGE is not null, this is the number of entries + * currently on PARTIAL_PAGE. */ + apr_uint64_t partial_page_number_filled; + + /* The pool that the svn_cache__t itself, HASH, and all pages are + * allocated in; subpools of this pool are used for the cache_entry + * structs, as well as the dup'd values and hash keys. + */ + apr_pool_t *cache_pool; + + /* Sum of the SIZE members of all cache_entry elements that are + * accessible from HASH. This is used to make statistics available + * even if the sub-pools have already been destroyed. + */ + apr_size_t data_size; + + /* A lock for intra-process synchronization to the cache, or NULL if + * the cache's creator doesn't feel the cache needs to be + * thread-safe. */ + svn_mutex__t *mutex; +} inprocess_cache_t; + +/* A cache page; all items on the page are allocated from the same + * pool. */ +struct cache_page { + /* Pointers for the LRU list anchored at the cache's SENTINEL. + * (NULL for the PARTIAL_PAGE.) */ + struct cache_page *prev; + struct cache_page *next; + + /* The pool in which cache_entry structs, hash keys, and dup'd + * values are allocated. The CACHE_PAGE structs are allocated + * in CACHE_POOL and have the same lifetime as the cache itself. + * (The cache will never allocate more than TOTAL_PAGES page + * structs (inclusive of the sentinel) from CACHE_POOL.) + */ + apr_pool_t *page_pool; + + /* A singly linked list of the entries on this page; used to remove + * them from the cache's HASH before reusing the page. */ + struct cache_entry *first_entry; +}; + +/* An cache entry. */ +struct cache_entry { + const void *key; + + /* serialized value */ + void *value; + + /* length of the serialized value in bytes */ + apr_size_t size; + + /* The page it's on (needed so that the LRU list can be + * maintained). */ + struct cache_page *page; + + /* Next entry on the page. */ + struct cache_entry *next_entry; +}; + + +/* Removes PAGE from the doubly-linked list it is in (leaving its PREV + * and NEXT fields undefined). */ +static void +remove_page_from_list(struct cache_page *page) +{ + page->prev->next = page->next; + page->next->prev = page->prev; +} + +/* Inserts PAGE after CACHE's sentinel. */ +static void +insert_page(inprocess_cache_t *cache, + struct cache_page *page) +{ + struct cache_page *pred = cache->sentinel; + + page->prev = pred; + page->next = pred->next; + page->prev->next = page; + page->next->prev = page; +} + +/* If PAGE is in the circularly linked list (eg, its NEXT isn't NULL), + * move it to the front of the list. */ +static svn_error_t * +move_page_to_front(inprocess_cache_t *cache, + struct cache_page *page) +{ + /* This function is called whilst CACHE is locked. */ + + SVN_ERR_ASSERT(page != cache->sentinel); + + if (! page->next) + return SVN_NO_ERROR; + + remove_page_from_list(page); + insert_page(cache, page); + + return SVN_NO_ERROR; +} + +/* Return a copy of KEY inside POOL, using CACHE->KLEN to figure out + * how. */ +static const void * +duplicate_key(inprocess_cache_t *cache, + const void *key, + apr_pool_t *pool) +{ + if (cache->klen == APR_HASH_KEY_STRING) + return apr_pstrdup(pool, key); + else + return apr_pmemdup(pool, key, cache->klen); +} + +static svn_error_t * +inprocess_cache_get_internal(char **buffer, + apr_size_t *size, + inprocess_cache_t *cache, + const void *key, + apr_pool_t *result_pool) +{ + struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen); + + *buffer = NULL; + if (entry) + { + SVN_ERR(move_page_to_front(cache, entry->page)); + + /* duplicate the buffer entry */ + *buffer = apr_palloc(result_pool, entry->size); + memcpy(*buffer, entry->value, entry->size); + + *size = entry->size; + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +inprocess_cache_get(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + apr_pool_t *result_pool) +{ + inprocess_cache_t *cache = cache_void; + char* buffer = NULL; + apr_size_t size; + + if (key) + SVN_MUTEX__WITH_LOCK(cache->mutex, + inprocess_cache_get_internal(&buffer, + &size, + cache, + key, + result_pool)); + + /* deserialize the buffer content. Usually, this will directly + modify the buffer content directly. + */ + *value_p = NULL; + *found = buffer != NULL; + return buffer && size + ? cache->deserialize_func(value_p, buffer, size, result_pool) + : SVN_NO_ERROR; +} + +/* Removes PAGE from the LRU list, removes all of its entries from + * CACHE's hash, clears its pool, and sets its entry pointer to NULL. + * Finally, puts it in the "partial page" slot in the cache and sets + * partial_page_number_filled to 0. Must be called on a page actually + * in the list. */ +static void +erase_page(inprocess_cache_t *cache, + struct cache_page *page) +{ + struct cache_entry *e; + + remove_page_from_list(page); + + for (e = page->first_entry; + e; + e = e->next_entry) + { + cache->data_size -= e->size; + apr_hash_set(cache->hash, e->key, cache->klen, NULL); + } + + svn_pool_clear(page->page_pool); + + page->first_entry = NULL; + page->prev = NULL; + page->next = NULL; + + cache->partial_page = page; + cache->partial_page_number_filled = 0; +} + + +static svn_error_t * +inprocess_cache_set_internal(inprocess_cache_t *cache, + const void *key, + void *value, + apr_pool_t *scratch_pool) +{ + struct cache_entry *existing_entry; + + existing_entry = apr_hash_get(cache->hash, key, cache->klen); + + /* Is it already here, but we can do the one-item-per-page + * optimization? */ + if (existing_entry && cache->items_per_page == 1) + { + /* Special case! ENTRY is the *only* entry on this page, so + * why not wipe it (so as not to leak the previous value). + */ + struct cache_page *page = existing_entry->page; + + /* This can't be the partial page: items_per_page == 1 + * *never* has a partial page (except for in the temporary state + * that we're about to fake). */ + SVN_ERR_ASSERT(page->next != NULL); + SVN_ERR_ASSERT(cache->partial_page == NULL); + + erase_page(cache, page); + existing_entry = NULL; + } + + /* Is it already here, and we just have to leak the old value? */ + if (existing_entry) + { + struct cache_page *page = existing_entry->page; + + SVN_ERR(move_page_to_front(cache, page)); + cache->data_size -= existing_entry->size; + if (value) + { + SVN_ERR(cache->serialize_func(&existing_entry->value, + &existing_entry->size, + value, + page->page_pool)); + cache->data_size += existing_entry->size; + if (existing_entry->size == 0) + existing_entry->value = NULL; + } + else + { + existing_entry->value = NULL; + existing_entry->size = 0; + } + + return SVN_NO_ERROR; + } + + /* Do we not have a partial page to put it on, but we are allowed to + * allocate more? */ + if (cache->partial_page == NULL && cache->unallocated_pages > 0) + { + cache->partial_page = apr_pcalloc(cache->cache_pool, + sizeof(*(cache->partial_page))); + cache->partial_page->page_pool = svn_pool_create(cache->cache_pool); + cache->partial_page_number_filled = 0; + (cache->unallocated_pages)--; + } + + /* Do we really not have a partial page to put it on, even after the + * one-item-per-page optimization and checking the unallocated page + * count? */ + if (cache->partial_page == NULL) + { + struct cache_page *oldest_page = cache->sentinel->prev; + + SVN_ERR_ASSERT(oldest_page != cache->sentinel); + + /* Erase the page and put it in cache->partial_page. */ + erase_page(cache, oldest_page); + } + + SVN_ERR_ASSERT(cache->partial_page != NULL); + + { + struct cache_page *page = cache->partial_page; + struct cache_entry *new_entry = apr_pcalloc(page->page_pool, + sizeof(*new_entry)); + + /* Copy the key and value into the page's pool. */ + new_entry->key = duplicate_key(cache, key, page->page_pool); + if (value) + { + SVN_ERR(cache->serialize_func(&new_entry->value, + &new_entry->size, + value, + page->page_pool)); + cache->data_size += new_entry->size; + if (new_entry->size == 0) + new_entry->value = NULL; + } + else + { + new_entry->value = NULL; + new_entry->size = 0; + } + + /* Add the entry to the page's list. */ + new_entry->page = page; + new_entry->next_entry = page->first_entry; + page->first_entry = new_entry; + + /* Add the entry to the hash, using the *entry's* copy of the + * key. */ + apr_hash_set(cache->hash, new_entry->key, cache->klen, new_entry); + + /* We've added something else to the partial page. */ + (cache->partial_page_number_filled)++; + + /* Is it full? */ + if (cache->partial_page_number_filled >= cache->items_per_page) + { + insert_page(cache, page); + cache->partial_page = NULL; + } + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +inprocess_cache_set(void *cache_void, + const void *key, + void *value, + apr_pool_t *scratch_pool) +{ + inprocess_cache_t *cache = cache_void; + + if (key) + SVN_MUTEX__WITH_LOCK(cache->mutex, + inprocess_cache_set_internal(cache, + key, + value, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Baton type for svn_cache__iter. */ +struct cache_iter_baton { + svn_iter_apr_hash_cb_t user_cb; + void *user_baton; +}; + +/* Call the user's callback with the actual value, not the + cache_entry. Implements the svn_iter_apr_hash_cb_t + prototype. */ +static svn_error_t * +iter_cb(void *baton, + const void *key, + apr_ssize_t klen, + void *val, + apr_pool_t *pool) +{ + struct cache_iter_baton *b = baton; + struct cache_entry *entry = val; + return (b->user_cb)(b->user_baton, key, klen, entry->value, pool); +} + +static svn_error_t * +inprocess_cache_iter(svn_boolean_t *completed, + void *cache_void, + svn_iter_apr_hash_cb_t user_cb, + void *user_baton, + apr_pool_t *scratch_pool) +{ + inprocess_cache_t *cache = cache_void; + struct cache_iter_baton b; + b.user_cb = user_cb; + b.user_baton = user_baton; + + SVN_MUTEX__WITH_LOCK(cache->mutex, + svn_iter_apr_hash(completed, cache->hash, + iter_cb, &b, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +inprocess_cache_get_partial_internal(void **value_p, + svn_boolean_t *found, + inprocess_cache_t *cache, + const void *key, + svn_cache__partial_getter_func_t func, + void *baton, + apr_pool_t *result_pool) +{ + struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen); + if (! entry) + { + *found = FALSE; + return SVN_NO_ERROR; + } + + SVN_ERR(move_page_to_front(cache, entry->page)); + + *found = TRUE; + return func(value_p, entry->value, entry->size, baton, result_pool); +} + +static svn_error_t * +inprocess_cache_get_partial(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + svn_cache__partial_getter_func_t func, + void *baton, + apr_pool_t *result_pool) +{ + inprocess_cache_t *cache = cache_void; + + if (key) + SVN_MUTEX__WITH_LOCK(cache->mutex, + inprocess_cache_get_partial_internal(value_p, + found, + cache, + key, + func, + baton, + result_pool)); + else + *found = FALSE; + + return SVN_NO_ERROR; +} + +static svn_error_t * +inprocess_cache_set_partial_internal(inprocess_cache_t *cache, + const void *key, + svn_cache__partial_setter_func_t func, + void *baton, + apr_pool_t *scratch_pool) +{ + struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen); + if (entry) + { + SVN_ERR(move_page_to_front(cache, entry->page)); + + cache->data_size -= entry->size; + SVN_ERR(func(&entry->value, + &entry->size, + baton, + entry->page->page_pool)); + cache->data_size += entry->size; + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +inprocess_cache_set_partial(void *cache_void, + const void *key, + svn_cache__partial_setter_func_t func, + void *baton, + apr_pool_t *scratch_pool) +{ + inprocess_cache_t *cache = cache_void; + + if (key) + SVN_MUTEX__WITH_LOCK(cache->mutex, + inprocess_cache_set_partial_internal(cache, + key, + func, + baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_boolean_t +inprocess_cache_is_cachable(void *cache_void, apr_size_t size) +{ + /* Be relatively strict: per page we should not allocate more than + * we could spare as "unused" memory. + * But in most cases, nobody will ask anyway. And in no case, this + * will be used for checks _inside_ the cache code. + */ + inprocess_cache_t *cache = cache_void; + return size < SVN_ALLOCATOR_RECOMMENDED_MAX_FREE / cache->items_per_page; +} + +static svn_error_t * +inprocess_cache_get_info_internal(inprocess_cache_t *cache, + svn_cache__info_t *info, + apr_pool_t *result_pool) +{ + info->id = apr_pstrdup(result_pool, cache->id); + + info->used_entries = apr_hash_count(cache->hash); + info->total_entries = cache->items_per_page * cache->total_pages; + + info->used_size = cache->data_size; + info->data_size = cache->data_size; + info->total_size = cache->data_size + + cache->items_per_page * sizeof(struct cache_page) + + info->used_entries * sizeof(struct cache_entry); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +inprocess_cache_get_info(void *cache_void, + svn_cache__info_t *info, + svn_boolean_t reset, + apr_pool_t *result_pool) +{ + inprocess_cache_t *cache = cache_void; + + SVN_MUTEX__WITH_LOCK(cache->mutex, + inprocess_cache_get_info_internal(cache, + info, + result_pool)); + + return SVN_NO_ERROR; +} + +static svn_cache__vtable_t inprocess_cache_vtable = { + inprocess_cache_get, + inprocess_cache_set, + inprocess_cache_iter, + inprocess_cache_is_cachable, + inprocess_cache_get_partial, + inprocess_cache_set_partial, + inprocess_cache_get_info +}; + +svn_error_t * +svn_cache__create_inprocess(svn_cache__t **cache_p, + svn_cache__serialize_func_t serialize, + svn_cache__deserialize_func_t deserialize, + apr_ssize_t klen, + apr_int64_t pages, + apr_int64_t items_per_page, + svn_boolean_t thread_safe, + const char *id, + apr_pool_t *pool) +{ + svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper)); + inprocess_cache_t *cache = apr_pcalloc(pool, sizeof(*cache)); + + cache->id = apr_pstrdup(pool, id); + + SVN_ERR_ASSERT(klen == APR_HASH_KEY_STRING || klen >= 1); + + cache->hash = apr_hash_make(pool); + cache->klen = klen; + + cache->serialize_func = serialize; + cache->deserialize_func = deserialize; + + SVN_ERR_ASSERT(pages >= 1); + cache->total_pages = pages; + cache->unallocated_pages = pages; + SVN_ERR_ASSERT(items_per_page >= 1); + cache->items_per_page = items_per_page; + + cache->sentinel = apr_pcalloc(pool, sizeof(*(cache->sentinel))); + cache->sentinel->prev = cache->sentinel; + cache->sentinel->next = cache->sentinel; + /* The sentinel doesn't need a pool. (We're happy to crash if we + * accidentally try to treat it like a real page.) */ + + SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool)); + + cache->cache_pool = pool; + + wrapper->vtable = &inprocess_cache_vtable; + wrapper->cache_internal = cache; + + *cache_p = wrapper; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/cache-membuffer.c b/subversion/libsvn_subr/cache-membuffer.c new file mode 100644 index 000000000000..5f447b3fa9bd --- /dev/null +++ b/subversion/libsvn_subr/cache-membuffer.c @@ -0,0 +1,2369 @@ +/* + * cache-membuffer.c: in-memory caching for Subversion + * + * ==================================================================== + * 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 <assert.h> +#include <apr_md5.h> +#include <apr_thread_rwlock.h> + +#include "svn_pools.h" +#include "svn_checksum.h" +#include "md5.h" +#include "svn_private_config.h" +#include "cache.h" +#include "svn_string.h" +#include "private/svn_dep_compat.h" +#include "private/svn_mutex.h" +#include "private/svn_pseudo_md5.h" + +/* + * This svn_cache__t implementation actually consists of two parts: + * a shared (per-process) singleton membuffer cache instance and shallow + * svn_cache__t front-end instances that each use different key spaces. + * For data management, they all forward to the singleton membuffer cache. + * + * A membuffer cache consists of two parts: + * + * 1. A linear data buffer containing cached items in a serialized + * representation. There may be arbitrary gaps between entries. + * 2. A directory of cache entries. This is organized similar to CPU + * data caches: for every possible key, there is exactly one group + * of entries that may contain the header info for an item with + * that given key. The result is a GROUP_SIZE-way associative cache. + * + * Only the start address of these two data parts are given as a native + * pointer. All other references are expressed as offsets to these pointers. + * With that design, it is relatively easy to share the same data structure + * between different processes and / or to persist them on disk. These + * out-of-process features have not been implemented, yet. + * + * The data buffer usage information is implicitly given by the directory + * entries. Every USED entry has a reference to the previous and the next + * used dictionary entry and this double-linked list is ordered by the + * offsets of their item data within the data buffer. So removing data, + * for instance, is done simply by unlinking it from the chain, implicitly + * marking the entry as well as the data buffer section previously + * associated to it as unused. + * + * Insertion can occur at only one, sliding position. It is marked by its + * offset in the data buffer plus the index of the first used entry at or + * behind that position. If this gap is too small to accommodate the new + * item, the insertion window is extended as described below. The new entry + * will always be inserted at the bottom end of the window and since the + * next used entry is known, properly sorted insertion is possible. + * + * To make the cache perform robustly in a wide range of usage scenarios, + * a randomized variant of LFU is used (see ensure_data_insertable for + * details). Every item holds a read hit counter and there is a global read + * hit counter. The more hits an entry has in relation to the average, the + * more it is likely to be kept using a rand()-based condition. The test is + * applied only to the entry following the insertion window. If it doesn't + * get evicted, it is moved to the begin of that window and the window is + * moved. + * + * Moreover, the entry's hits get halved to make that entry more likely to + * be removed the next time the sliding insertion / removal window comes by. + * As a result, frequently used entries are likely not to be dropped until + * they get not used for a while. Also, even a cache thrashing situation + * about 50% of the content survives every 50% of the cache being re-written + * with new entries. For details on the fine-tuning involved, see the + * comments in ensure_data_insertable(). + * + * To limit the entry size and management overhead, not the actual item keys + * but only their MD5 checksums will not be stored. This is reasonably safe + * to do since users have only limited control over the full keys, even if + * these contain folder paths. So, it is very hard to deliberately construct + * colliding keys. Random checksum collisions can be shown to be extremely + * unlikely. + * + * All access to the cached data needs to be serialized. Because we want + * to scale well despite that bottleneck, we simply segment the cache into + * a number of independent caches (segments). Items will be multiplexed based + * on their hash key. + */ + +/* A 16-way associative cache seems to be a good compromise between + * performance (worst-case lookups) and efficiency-loss due to collisions. + * + * This value may be changed to any positive integer. + */ +#define GROUP_SIZE 16 + +/* For more efficient copy operations, let's align all data items properly. + * Must be a power of 2. + */ +#define ITEM_ALIGNMENT 16 + +/* By default, don't create cache segments smaller than this value unless + * the total cache size itself is smaller. + */ +#define DEFAULT_MIN_SEGMENT_SIZE APR_UINT64_C(0x2000000) + +/* The minimum segment size we will allow for multi-segmented caches + */ +#define MIN_SEGMENT_SIZE APR_UINT64_C(0x10000) + +/* The maximum number of segments allowed. Larger numbers reduce the size + * of each segment, in turn reducing the max size of a cachable item. + * Also, each segment gets its own lock object. The actual number supported + * by the OS may therefore be lower and svn_cache__membuffer_cache_create + * may return an error. + */ +#define MAX_SEGMENT_COUNT 0x10000 + +/* As of today, APR won't allocate chunks of 4GB or more. So, limit the + * segment size to slightly below that. + */ +#define MAX_SEGMENT_SIZE APR_UINT64_C(0xffff0000) + +/* We don't mark the initialization status for every group but initialize + * a number of groups at once. That will allow for a very small init flags + * vector that is likely to fit into the CPU caches even for fairly large + * membuffer caches. For instance, the default of 32 means 8x32 groups per + * byte, i.e. 8 flags/byte x 32 groups/flag x 8 entries/group x 40 index + * bytes/entry x 8 cache bytes/index byte = 1kB init vector / 640MB cache. + */ +#define GROUP_INIT_GRANULARITY 32 + +/* Invalid index reference value. Equivalent to APR_UINT32_T(-1) + */ +#define NO_INDEX APR_UINT32_MAX + +/* To save space in our group structure, we only use 32 bit size values + * and, therefore, limit the size of each entry to just below 4GB. + * Supporting larger items is not a good idea as the data transfer + * to and from the cache would block other threads for a very long time. + */ +#define MAX_ITEM_SIZE ((apr_uint32_t)(0 - ITEM_ALIGNMENT)) + +/* A 16 byte key type. We use that to identify cache entries. + * The notation as just two integer values will cause many compilers + * to create better code. + */ +typedef apr_uint64_t entry_key_t[2]; + +/* Debugging / corruption detection support. + * If you define this macro, the getter functions will performed expensive + * checks on the item data, requested keys and entry types. If there is + * a mismatch found in any of them when being compared with the values + * remembered in the setter function, an error will be returned. + */ +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + +/* The prefix passed to svn_cache__create_membuffer_cache() effectively + * defines the type of all items stored by that cache instance. We'll take + * the last 7 bytes + \0 as plaintext for easy identification by the dev. + */ +#define PREFIX_TAIL_LEN 8 + +/* This record will be attached to any cache entry. It tracks item data + * (content), key and type as hash values and is the baseline against which + * the getters will compare their results to detect inconsistencies. + */ +typedef struct entry_tag_t +{ + /* MD5 checksum over the serialized the item data. + */ + unsigned char content_hash [APR_MD5_DIGESTSIZE]; + + /* Hash value of the svn_cache_t instance that wrote the item + * (i.e. a combination of type and repository) + */ + unsigned char prefix_hash [APR_MD5_DIGESTSIZE]; + + /* Note that this only covers the variable part of the key, + * i.e. it will be different from the full key hash used for + * cache indexing. + */ + unsigned char key_hash [APR_MD5_DIGESTSIZE]; + + /* Last letters from of the key in human readable format + * (ends with the type identifier, e.g. "DAG") + */ + char prefix_tail[PREFIX_TAIL_LEN]; + + /* Length of the variable key part. + */ + apr_size_t key_len; + +} entry_tag_t; + +/* Per svn_cache_t instance initialization helper. + */ +static void get_prefix_tail(const char *prefix, char *prefix_tail) +{ + apr_size_t len = strlen(prefix); + apr_size_t to_copy = len > PREFIX_TAIL_LEN-1 ? PREFIX_TAIL_LEN-1 : len; + + memset(prefix_tail, 0, PREFIX_TAIL_LEN); + memcpy(prefix_tail, prefix + len - to_copy, to_copy); +} + +/* Initialize all members of TAG except for the content hash. + */ +static svn_error_t *store_key_part(entry_tag_t *tag, + entry_key_t prefix_hash, + char *prefix_tail, + const void *key, + apr_size_t key_len, + apr_pool_t *pool) +{ + svn_checksum_t *checksum; + SVN_ERR(svn_checksum(&checksum, + svn_checksum_md5, + key, + key_len, + pool)); + + memcpy(tag->prefix_hash, prefix_hash, sizeof(tag->prefix_hash)); + memcpy(tag->key_hash, checksum->digest, sizeof(tag->key_hash)); + memcpy(tag->prefix_tail, prefix_tail, sizeof(tag->prefix_tail)); + + tag->key_len = key_len; + + return SVN_NO_ERROR; +} + +/* Initialize the content hash member of TAG. + */ +static svn_error_t* store_content_part(entry_tag_t *tag, + const char *data, + apr_size_t size, + apr_pool_t *pool) +{ + svn_checksum_t *checksum; + SVN_ERR(svn_checksum(&checksum, + svn_checksum_md5, + data, + size, + pool)); + + memcpy(tag->content_hash, checksum->digest, sizeof(tag->content_hash)); + + return SVN_NO_ERROR; +} + +/* Compare two tags and fail with an assertion upon differences. + */ +static svn_error_t* assert_equal_tags(const entry_tag_t *lhs, + const entry_tag_t *rhs) +{ + SVN_ERR_ASSERT(memcmp(lhs->content_hash, rhs->content_hash, + sizeof(lhs->content_hash)) == 0); + SVN_ERR_ASSERT(memcmp(lhs->prefix_hash, rhs->prefix_hash, + sizeof(lhs->prefix_hash)) == 0); + SVN_ERR_ASSERT(memcmp(lhs->key_hash, rhs->key_hash, + sizeof(lhs->key_hash)) == 0); + SVN_ERR_ASSERT(memcmp(lhs->prefix_tail, rhs->prefix_tail, + sizeof(lhs->prefix_tail)) == 0); + + SVN_ERR_ASSERT(lhs->key_len == rhs->key_len); + + return SVN_NO_ERROR; +} + +/* Reoccurring code snippets. + */ + +#define DEBUG_CACHE_MEMBUFFER_TAG_ARG entry_tag_t *tag, + +#define DEBUG_CACHE_MEMBUFFER_TAG tag, + +#define DEBUG_CACHE_MEMBUFFER_INIT_TAG \ + entry_tag_t _tag; \ + entry_tag_t *tag = &_tag; \ + SVN_ERR(store_key_part(tag, \ + cache->prefix, \ + cache->prefix_tail, \ + key, \ + cache->key_len == APR_HASH_KEY_STRING \ + ? strlen((const char *) key) \ + : cache->key_len, \ + cache->pool)); + +#else + +/* Don't generate any checks if consistency checks have not been enabled. + */ +#define DEBUG_CACHE_MEMBUFFER_TAG_ARG +#define DEBUG_CACHE_MEMBUFFER_TAG +#define DEBUG_CACHE_MEMBUFFER_INIT_TAG + +#endif /* SVN_DEBUG_CACHE_MEMBUFFER */ + +/* A single dictionary entry. Since all entries will be allocated once + * during cache creation, those entries might be either used or unused. + * An entry is used if and only if it is contained in the doubly-linked + * list of used entries. + */ +typedef struct entry_t +{ + /* Identifying the data item. Only valid for used entries. + */ + entry_key_t key; + + /* The offset of the cached item's serialized data within the data buffer. + */ + apr_uint64_t offset; + + /* Size of the serialized item data. May be 0. + * Only valid for used entries. + */ + apr_size_t size; + + /* Number of (read) hits for this entry. Will be reset upon write. + * Only valid for used entries. + */ + apr_uint32_t hit_count; + + /* Reference to the next used entry in the order defined by offset. + * NO_INDEX indicates the end of the list; this entry must be referenced + * by the caches membuffer_cache_t.last member. NO_INDEX also implies + * that the data buffer is not used beyond offset+size. + * Only valid for used entries. + */ + apr_uint32_t next; + + /* Reference to the previous used entry in the order defined by offset. + * NO_INDEX indicates the end of the list; this entry must be referenced + * by the caches membuffer_cache_t.first member. + * Only valid for used entries. + */ + apr_uint32_t previous; + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + /* Remember type, content and key hashes. + */ + entry_tag_t tag; +#endif +} entry_t; + +/* We group dictionary entries to make this GROUP-SIZE-way associative. + */ +typedef struct entry_group_t +{ + /* number of entries used [0 .. USED-1] */ + apr_uint32_t used; + + /* the actual entries */ + entry_t entries[GROUP_SIZE]; +} entry_group_t; + +/* The cache header structure. + */ +struct svn_membuffer_t +{ + /* Number of cache segments. Must be a power of 2. + Please note that this structure represents only one such segment + and that all segments must / will report the same values here. */ + apr_uint32_t segment_count; + + /* The dictionary, GROUP_SIZE * group_count entries long. Never NULL. + */ + entry_group_t *directory; + + /* Flag array with group_count / GROUP_INIT_GRANULARITY _bit_ elements. + * Allows for efficiently marking groups as "not initialized". + */ + unsigned char *group_initialized; + + /* Size of dictionary in groups. Must be > 0. + */ + apr_uint32_t group_count; + + /* Reference to the first (defined by the order content in the data + * buffer) dictionary entry used by any data item. + * NO_INDEX for an empty cache. + */ + apr_uint32_t first; + + /* Reference to the last (defined by the order content in the data + * buffer) dictionary entry used by any data item. + * NO_INDEX for an empty cache. + */ + apr_uint32_t last; + + /* Reference to the first (defined by the order content in the data + * buffer) used dictionary entry behind the insertion position + * (current_data). If NO_INDEX, the data buffer is free starting at the + * current_data offset. + */ + apr_uint32_t next; + + + /* Pointer to the data buffer, data_size bytes long. Never NULL. + */ + unsigned char *data; + + /* Size of data buffer in bytes. Must be > 0. + */ + apr_uint64_t data_size; + + /* Offset in the data buffer where the next insertion shall occur. + */ + apr_uint64_t current_data; + + /* Total number of data buffer bytes in use. This is for statistics only. + */ + apr_uint64_t data_used; + + /* Largest entry size that we would accept. For total cache sizes + * less than 4TB (sic!), this is determined by the total cache size. + */ + apr_uint64_t max_entry_size; + + + /* Number of used dictionary entries, i.e. number of cached items. + * In conjunction with hit_count, this is used calculate the average + * hit count as part of the randomized LFU algorithm. + */ + apr_uint32_t used_entries; + + /* Sum of (read) hit counts of all used dictionary entries. + * In conjunction used_entries used_entries, this is used calculate + * the average hit count as part of the randomized LFU algorithm. + */ + apr_uint64_t hit_count; + + + /* Total number of calls to membuffer_cache_get. + * Purely statistical information that may be used for profiling. + */ + apr_uint64_t total_reads; + + /* Total number of calls to membuffer_cache_set. + * Purely statistical information that may be used for profiling. + */ + apr_uint64_t total_writes; + + /* Total number of hits since the cache's creation. + * Purely statistical information that may be used for profiling. + */ + apr_uint64_t total_hits; + +#if APR_HAS_THREADS + /* A lock for intra-process synchronization to the cache, or NULL if + * the cache's creator doesn't feel the cache needs to be + * thread-safe. + */ + apr_thread_rwlock_t *lock; + + /* If set, write access will wait until they get exclusive access. + * Otherwise, they will become no-ops if the segment is currently + * read-locked. + */ + svn_boolean_t allow_blocking_writes; +#endif +}; + +/* Align integer VALUE to the next ITEM_ALIGNMENT boundary. + */ +#define ALIGN_VALUE(value) (((value) + ITEM_ALIGNMENT-1) & -ITEM_ALIGNMENT) + +/* Align POINTER value to the next ITEM_ALIGNMENT boundary. + */ +#define ALIGN_POINTER(pointer) ((void*)ALIGN_VALUE((apr_size_t)(char*)(pointer))) + +/* If locking is supported for CACHE, acquire a read lock for it. + */ +static svn_error_t * +read_lock_cache(svn_membuffer_t *cache) +{ +#if APR_HAS_THREADS + if (cache->lock) + { + apr_status_t status = apr_thread_rwlock_rdlock(cache->lock); + if (status) + return svn_error_wrap_apr(status, _("Can't lock cache mutex")); + } +#endif + return SVN_NO_ERROR; +} + +/* If locking is supported for CACHE, acquire a write lock for it. + */ +static svn_error_t * +write_lock_cache(svn_membuffer_t *cache, svn_boolean_t *success) +{ +#if APR_HAS_THREADS + if (cache->lock) + { + apr_status_t status; + if (cache->allow_blocking_writes) + { + status = apr_thread_rwlock_wrlock(cache->lock); + } + else + { + status = apr_thread_rwlock_trywrlock(cache->lock); + if (SVN_LOCK_IS_BUSY(status)) + { + *success = FALSE; + status = APR_SUCCESS; + } + } + + if (status) + return svn_error_wrap_apr(status, + _("Can't write-lock cache mutex")); + } +#endif + return SVN_NO_ERROR; +} + +/* If locking is supported for CACHE, acquire an unconditional write lock + * for it. + */ +static svn_error_t * +force_write_lock_cache(svn_membuffer_t *cache) +{ +#if APR_HAS_THREADS + apr_status_t status = apr_thread_rwlock_wrlock(cache->lock); + if (status) + return svn_error_wrap_apr(status, + _("Can't write-lock cache mutex")); +#endif + return SVN_NO_ERROR; +} + +/* If locking is supported for CACHE, release the current lock + * (read or write). + */ +static svn_error_t * +unlock_cache(svn_membuffer_t *cache, svn_error_t *err) +{ +#if APR_HAS_THREADS + if (cache->lock) + { + apr_status_t status = apr_thread_rwlock_unlock(cache->lock); + if (err) + return err; + + if (status) + return svn_error_wrap_apr(status, _("Can't unlock cache mutex")); + } +#endif + return err; +} + +/* If supported, guard the execution of EXPR with a read lock to cache. + * Macro has been modeled after SVN_MUTEX__WITH_LOCK. + */ +#define WITH_READ_LOCK(cache, expr) \ +do { \ + SVN_ERR(read_lock_cache(cache)); \ + SVN_ERR(unlock_cache(cache, (expr))); \ +} while (0) + +/* If supported, guard the execution of EXPR with a write lock to cache. + * Macro has been modeled after SVN_MUTEX__WITH_LOCK. + * + * The write lock process is complicated if we don't allow to wait for + * the lock: If we didn't get the lock, we may still need to remove an + * existing entry for the given key because that content is now stale. + * Once we discovered such an entry, we unconditionally do a blocking + * wait for the write lock. In case no old content could be found, a + * failing lock attempt is simply a no-op and we exit the macro. + */ +#define WITH_WRITE_LOCK(cache, expr) \ +do { \ + svn_boolean_t got_lock = TRUE; \ + SVN_ERR(write_lock_cache(cache, &got_lock)); \ + if (!got_lock) \ + { \ + svn_boolean_t exists; \ + SVN_ERR(entry_exists(cache, group_index, key, &exists)); \ + if (exists) \ + SVN_ERR(force_write_lock_cache(cache)); \ + else \ + break; \ + } \ + SVN_ERR(unlock_cache(cache, (expr))); \ +} while (0) + +/* Resolve a dictionary entry reference, i.e. return the entry + * for the given IDX. + */ +static APR_INLINE entry_t * +get_entry(svn_membuffer_t *cache, apr_uint32_t idx) +{ + return &cache->directory[idx / GROUP_SIZE].entries[idx % GROUP_SIZE]; +} + +/* Get the entry references for the given ENTRY. + */ +static APR_INLINE apr_uint32_t +get_index(svn_membuffer_t *cache, entry_t *entry) +{ + apr_size_t group_index + = ((char *)entry - (char *)cache->directory) / sizeof(entry_group_t); + + return (apr_uint32_t)group_index * GROUP_SIZE + + (apr_uint32_t)(entry - cache->directory[group_index].entries); +} + +/* Remove the used ENTRY from the CACHE, i.e. make it "unused". + * In contrast to insertion, removal is possible for any entry. + */ +static void +drop_entry(svn_membuffer_t *cache, entry_t *entry) +{ + /* the group that ENTRY belongs to plus a number of useful index values + */ + apr_uint32_t idx = get_index(cache, entry); + apr_uint32_t group_index = idx / GROUP_SIZE; + entry_group_t *group = &cache->directory[group_index]; + apr_uint32_t last_in_group = group_index * GROUP_SIZE + group->used - 1; + + /* Only valid to be called for used entries. + */ + assert(idx <= last_in_group); + + /* update global cache usage counters + */ + cache->used_entries--; + cache->hit_count -= entry->hit_count; + cache->data_used -= entry->size; + + /* extend the insertion window, if the entry happens to border it + */ + if (idx == cache->next) + cache->next = entry->next; + else + if (entry->next == cache->next) + { + /* insertion window starts right behind the entry to remove + */ + if (entry->previous == NO_INDEX) + { + /* remove the first entry -> insertion may start at pos 0, now */ + cache->current_data = 0; + } + else + { + /* insertion may start right behind the previous entry */ + entry_t *previous = get_entry(cache, entry->previous); + cache->current_data = ALIGN_VALUE( previous->offset + + previous->size); + } + } + + /* unlink it from the chain of used entries + */ + if (entry->previous == NO_INDEX) + cache->first = entry->next; + else + get_entry(cache, entry->previous)->next = entry->next; + + if (entry->next == NO_INDEX) + cache->last = entry->previous; + else + get_entry(cache, entry->next)->previous = entry->previous; + + /* Move last entry into hole (if the removed one is not the last used). + * We need to do this since all used entries are at the beginning of + * the group's entries array. + */ + if (idx < last_in_group) + { + /* copy the last used entry to the removed entry's index + */ + *entry = group->entries[group->used-1]; + + /* update foreign links to new index + */ + if (last_in_group == cache->next) + cache->next = idx; + + if (entry->previous == NO_INDEX) + cache->first = idx; + else + get_entry(cache, entry->previous)->next = idx; + + if (entry->next == NO_INDEX) + cache->last = idx; + else + get_entry(cache, entry->next)->previous = idx; + } + + /* Update the number of used entries. + */ + group->used--; +} + +/* Insert ENTRY into the chain of used dictionary entries. The entry's + * offset and size members must already have been initialized. Also, + * the offset must match the beginning of the insertion window. + */ +static void +insert_entry(svn_membuffer_t *cache, entry_t *entry) +{ + /* the group that ENTRY belongs to plus a number of useful index values + */ + apr_uint32_t idx = get_index(cache, entry); + apr_uint32_t group_index = idx / GROUP_SIZE; + entry_group_t *group = &cache->directory[group_index]; + entry_t *next = cache->next == NO_INDEX + ? NULL + : get_entry(cache, cache->next); + + /* The entry must start at the beginning of the insertion window. + * It must also be the first unused entry in the group. + */ + assert(entry->offset == cache->current_data); + assert(idx == group_index * GROUP_SIZE + group->used); + cache->current_data = ALIGN_VALUE(entry->offset + entry->size); + + /* update usage counters + */ + cache->used_entries++; + cache->data_used += entry->size; + entry->hit_count = 0; + group->used++; + + /* update entry chain + */ + entry->next = cache->next; + if (cache->first == NO_INDEX) + { + /* insert as the first entry and only in the chain + */ + entry->previous = NO_INDEX; + cache->last = idx; + cache->first = idx; + } + else if (next == NULL) + { + /* insert as the last entry in the chain. + * Note that it cannot also be at the beginning of the chain. + */ + entry->previous = cache->last; + get_entry(cache, cache->last)->next = idx; + cache->last = idx; + } + else + { + /* insert either at the start of a non-empty list or + * somewhere in the middle + */ + entry->previous = next->previous; + next->previous = idx; + + if (entry->previous != NO_INDEX) + get_entry(cache, entry->previous)->next = idx; + else + cache->first = idx; + } + + /* The current insertion position must never point outside our + * data buffer. + */ + assert(cache->current_data <= cache->data_size); +} + +/* Map a KEY of 16 bytes to the CACHE and group that shall contain the + * respective item. + */ +static apr_uint32_t +get_group_index(svn_membuffer_t **cache, + entry_key_t key) +{ + svn_membuffer_t *segment0 = *cache; + + /* select the cache segment to use. they have all the same group_count */ + *cache = &segment0[key[0] & (segment0->segment_count -1)]; + return key[1] % segment0->group_count; +} + +/* Reduce the hit count of ENTRY and update the accumulated hit info + * in CACHE accordingly. + */ +static APR_INLINE void +let_entry_age(svn_membuffer_t *cache, entry_t *entry) +{ + apr_uint32_t hits_removed = (entry->hit_count + 1) >> 1; + + cache->hit_count -= hits_removed; + entry->hit_count -= hits_removed; +} + +/* Returns 0 if the entry group identified by GROUP_INDEX in CACHE has not + * been initialized, yet. In that case, this group can not data. Otherwise, + * a non-zero value is returned. + */ +static APR_INLINE unsigned char +is_group_initialized(svn_membuffer_t *cache, apr_uint32_t group_index) +{ + unsigned char flags + = cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)]; + unsigned char bit_mask + = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8)); + + return flags & bit_mask; +} + +/* Initializes the section of the directory in CACHE that contains + * the entry group identified by GROUP_INDEX. */ +static void +initialize_group(svn_membuffer_t *cache, apr_uint32_t group_index) +{ + unsigned char bit_mask; + apr_uint32_t i; + + /* range of groups to initialize due to GROUP_INIT_GRANULARITY */ + apr_uint32_t first_index = + (group_index / GROUP_INIT_GRANULARITY) * GROUP_INIT_GRANULARITY; + apr_uint32_t last_index = first_index + GROUP_INIT_GRANULARITY; + if (last_index > cache->group_count) + last_index = cache->group_count; + + for (i = first_index; i < last_index; ++i) + cache->directory[i].used = 0; + + /* set the "initialized" bit for these groups */ + bit_mask + = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8)); + cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)] + |= bit_mask; +} + +/* Given the GROUP_INDEX that shall contain an entry with the hash key + * TO_FIND, find that entry in the specified group. + * + * If FIND_EMPTY is not set, this function will return the one used entry + * that actually matches the hash or NULL, if no such entry exists. + * + * If FIND_EMPTY has been set, this function will drop the one used entry + * that actually matches the hash (i.e. make it fit to be replaced with + * new content), an unused entry or a forcibly removed entry (if all + * group entries are currently in use). The entries' hash value will be + * initialized with TO_FIND. + */ +static entry_t * +find_entry(svn_membuffer_t *cache, + apr_uint32_t group_index, + const apr_uint64_t to_find[2], + svn_boolean_t find_empty) +{ + entry_group_t *group; + entry_t *entry = NULL; + apr_size_t i; + + /* get the group that *must* contain the entry + */ + group = &cache->directory[group_index]; + + /* If the entry group has not been initialized, yet, there is no data. + */ + if (! is_group_initialized(cache, group_index)) + { + if (find_empty) + { + initialize_group(cache, group_index); + entry = &group->entries[0]; + + /* initialize entry for the new key */ + entry->key[0] = to_find[0]; + entry->key[1] = to_find[1]; + } + + return entry; + } + + /* try to find the matching entry + */ + for (i = 0; i < group->used; ++i) + if ( to_find[0] == group->entries[i].key[0] + && to_find[1] == group->entries[i].key[1]) + { + /* found it + */ + entry = &group->entries[i]; + if (find_empty) + drop_entry(cache, entry); + else + return entry; + } + + /* None found. Are we looking for a free entry? + */ + if (find_empty) + { + /* if there is no empty entry, delete the oldest entry + */ + if (group->used == GROUP_SIZE) + { + /* every entry gets the same chance of being removed. + * Otherwise, we free the first entry, fill it and + * remove it again on the next occasion without considering + * the other entries in this group. + */ + entry = &group->entries[rand() % GROUP_SIZE]; + for (i = 1; i < GROUP_SIZE; ++i) + if (entry->hit_count > group->entries[i].hit_count) + entry = &group->entries[i]; + + /* for the entries that don't have been removed, + * reduce their hit counts to put them at a relative + * disadvantage the next time. + */ + for (i = 0; i < GROUP_SIZE; ++i) + if (entry != &group->entries[i]) + let_entry_age(cache, entry); + + drop_entry(cache, entry); + } + + /* initialize entry for the new key + */ + entry = &group->entries[group->used]; + entry->key[0] = to_find[0]; + entry->key[1] = to_find[1]; + } + + return entry; +} + +/* Move a surviving ENTRY from just behind the insertion window to + * its beginning and move the insertion window up accordingly. + */ +static void +move_entry(svn_membuffer_t *cache, entry_t *entry) +{ + apr_size_t size = ALIGN_VALUE(entry->size); + + /* This entry survived this cleansing run. Reset half of its + * hit count so that its removal gets more likely in the next + * run unless someone read / hit this entry in the meantime. + */ + let_entry_age(cache, entry); + + /* Move the entry to the start of the empty / insertion section + * (if it isn't there already). Size-aligned moves are legal + * since all offsets and block sizes share this same alignment. + * Size-aligned moves tend to be faster than non-aligned ones + * because no "odd" bytes at the end need to special treatment. + */ + if (entry->offset != cache->current_data) + { + memmove(cache->data + cache->current_data, + cache->data + entry->offset, + size); + entry->offset = cache->current_data; + } + + /* The insertion position is now directly behind this entry. + */ + cache->current_data = entry->offset + size; + cache->next = entry->next; + + /* The current insertion position must never point outside our + * data buffer. + */ + assert(cache->current_data <= cache->data_size); +} + +/* If necessary, enlarge the insertion window until it is at least + * SIZE bytes long. SIZE must not exceed the data buffer size. + * Return TRUE if enough room could be found or made. A FALSE result + * indicates that the respective item shall not be added. + */ +static svn_boolean_t +ensure_data_insertable(svn_membuffer_t *cache, apr_size_t size) +{ + entry_t *entry; + apr_uint64_t average_hit_value; + apr_uint64_t threshold; + + /* accumulated size of the entries that have been removed to make + * room for the new one. + */ + apr_size_t drop_size = 0; + + /* This loop will eventually terminate because every cache entry + * would get dropped eventually: + * - hit counts become 0 after the got kept for 32 full scans + * - larger elements get dropped as soon as their hit count is 0 + * - smaller and smaller elements get removed as the average + * entry size drops (average drops by a factor of 8 per scan) + * - after no more than 43 full scans, all elements would be removed + * + * Since size is < 4th of the cache size and about 50% of all + * entries get removed by a scan, it is very unlikely that more + * than a fractional scan will be necessary. + */ + while (1) + { + /* first offset behind the insertion window + */ + apr_uint64_t end = cache->next == NO_INDEX + ? cache->data_size + : get_entry(cache, cache->next)->offset; + + /* leave function as soon as the insertion window is large enough + */ + if (end >= size + cache->current_data) + return TRUE; + + /* Don't be too eager to cache data. Smaller items will fit into + * the cache after dropping a single item. Of the larger ones, we + * will only accept about 50%. They are also likely to get evicted + * soon due to their notoriously low hit counts. + * + * As long as enough similarly or even larger sized entries already + * exist in the cache, much less insert requests will be rejected. + */ + if (2 * drop_size > size) + return FALSE; + + /* try to enlarge the insertion window + */ + if (cache->next == NO_INDEX) + { + /* We reached the end of the data buffer; restart at the beginning. + * Due to the randomized nature of our LFU implementation, very + * large data items may require multiple passes. Therefore, SIZE + * should be restricted to significantly less than data_size. + */ + cache->current_data = 0; + cache->next = cache->first; + } + else + { + entry = get_entry(cache, cache->next); + + /* Keep entries that are very small. Those are likely to be data + * headers or similar management structures. So, they are probably + * important while not occupying much space. + * But keep them only as long as they are a minority. + */ + if ( (apr_uint64_t)entry->size * cache->used_entries + < cache->data_used / 8) + { + move_entry(cache, entry); + } + else + { + svn_boolean_t keep; + + if (cache->hit_count > cache->used_entries) + { + /* Roll the dice and determine a threshold somewhere from 0 up + * to 2 times the average hit count. + */ + average_hit_value = cache->hit_count / cache->used_entries; + threshold = (average_hit_value+1) * (rand() % 4096) / 2048; + + keep = entry->hit_count >= threshold; + } + else + { + /* general hit count is low. Keep everything that got hit + * at all and assign some 50% survival chance to everything + * else. + */ + keep = (entry->hit_count > 0) || (rand() & 1); + } + + /* keepers or destroyers? */ + if (keep) + { + move_entry(cache, entry); + } + else + { + /* Drop the entry from the end of the insertion window, if it + * has been hit less than the threshold. Otherwise, keep it and + * move the insertion window one entry further. + */ + drop_size += entry->size; + drop_entry(cache, entry); + } + } + } + } + + /* This will never be reached. But if it was, "can't insert" was the + * right answer. */ +} + +/* Mimic apr_pcalloc in APR_POOL_DEBUG mode, i.e. handle failed allocations + * (e.g. OOM) properly: Allocate at least SIZE bytes from POOL and zero + * the content of the allocated memory if ZERO has been set. Return NULL + * upon failed allocations. + * + * Also, satisfy our buffer alignment needs for performance reasons. + */ +static void* secure_aligned_alloc(apr_pool_t *pool, + apr_size_t size, + svn_boolean_t zero) +{ + void* memory = apr_palloc(pool, size + ITEM_ALIGNMENT); + if (memory != NULL) + { + memory = ALIGN_POINTER(memory); + if (zero) + memset(memory, 0, size); + } + + return memory; +} + +svn_error_t * +svn_cache__membuffer_cache_create(svn_membuffer_t **cache, + apr_size_t total_size, + apr_size_t directory_size, + apr_size_t segment_count, + svn_boolean_t thread_safe, + svn_boolean_t allow_blocking_writes, + apr_pool_t *pool) +{ + svn_membuffer_t *c; + + apr_uint32_t seg; + apr_uint32_t group_count; + apr_uint32_t group_init_size; + apr_uint64_t data_size; + apr_uint64_t max_entry_size; + + /* Limit the total size (only relevant if we can address > 4GB) + */ +#if APR_SIZEOF_VOIDP > 4 + if (total_size > MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT) + total_size = MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT; +#endif + + /* Limit the segment count + */ + if (segment_count > MAX_SEGMENT_COUNT) + segment_count = MAX_SEGMENT_COUNT; + if (segment_count * MIN_SEGMENT_SIZE > total_size) + segment_count = total_size / MIN_SEGMENT_SIZE; + + /* The segment count must be a power of two. Round it down as necessary. + */ + while ((segment_count & (segment_count-1)) != 0) + segment_count &= segment_count-1; + + /* if the caller hasn't provided a reasonable segment count or the above + * limitations set it to 0, derive one from the absolute cache size + */ + if (segment_count < 1) + { + /* Determine a reasonable number of cache segments. Segmentation is + * only useful for multi-threaded / multi-core servers as it reduces + * lock contention on these systems. + * + * But on these systems, we can assume that ample memory has been + * allocated to this cache. Smaller caches should not be segmented + * as this severely limits the maximum size of cachable items. + * + * Segments should not be smaller than 32MB and max. cachable item + * size should grow as fast as segmentation. + */ + + apr_uint32_t segment_count_shift = 0; + while (((2 * DEFAULT_MIN_SEGMENT_SIZE) << (2 * segment_count_shift)) + < total_size) + ++segment_count_shift; + + segment_count = (apr_size_t)1 << segment_count_shift; + } + + /* If we have an extremely large cache (>512 GB), the default segment + * size may exceed the amount allocatable as one chunk. In that case, + * increase segmentation until we are under the threshold. + */ + while ( total_size / segment_count > MAX_SEGMENT_SIZE + && segment_count < MAX_SEGMENT_COUNT) + segment_count *= 2; + + /* allocate cache as an array of segments / cache objects */ + c = apr_palloc(pool, segment_count * sizeof(*c)); + + /* Split total cache size into segments of equal size + */ + total_size /= segment_count; + directory_size /= segment_count; + + /* prevent pathological conditions: ensure a certain minimum cache size + */ + if (total_size < 2 * sizeof(entry_group_t)) + total_size = 2 * sizeof(entry_group_t); + + /* adapt the dictionary size accordingly, if necessary: + * It must hold at least one group and must not exceed the cache size. + */ + if (directory_size > total_size - sizeof(entry_group_t)) + directory_size = total_size - sizeof(entry_group_t); + if (directory_size < sizeof(entry_group_t)) + directory_size = sizeof(entry_group_t); + + /* limit the data size to what we can address. + * Note that this cannot overflow since all values are of size_t. + * Also, make it a multiple of the item placement granularity to + * prevent subtle overflows. + */ + data_size = ALIGN_VALUE(total_size - directory_size + 1) - ITEM_ALIGNMENT; + + /* For cache sizes > 4TB, individual cache segments will be larger + * than 16GB allowing for >4GB entries. But caching chunks larger + * than 4GB is simply not supported. + */ + max_entry_size = data_size / 4 > MAX_ITEM_SIZE + ? MAX_ITEM_SIZE + : data_size / 4; + + /* to keep the entries small, we use 32 bit indexes only + * -> we need to ensure that no more then 4G entries exist. + * + * Note, that this limit could only be exceeded in a very + * theoretical setup with about 1EB of cache. + */ + group_count = directory_size / sizeof(entry_group_t) + >= (APR_UINT32_MAX / GROUP_SIZE) + ? (APR_UINT32_MAX / GROUP_SIZE) - 1 + : (apr_uint32_t)(directory_size / sizeof(entry_group_t)); + + group_init_size = 1 + group_count / (8 * GROUP_INIT_GRANULARITY); + for (seg = 0; seg < segment_count; ++seg) + { + /* allocate buffers and initialize cache members + */ + c[seg].segment_count = (apr_uint32_t)segment_count; + + c[seg].group_count = group_count; + c[seg].directory = apr_pcalloc(pool, + group_count * sizeof(entry_group_t)); + + /* Allocate and initialize directory entries as "not initialized", + hence "unused" */ + c[seg].group_initialized = apr_pcalloc(pool, group_init_size); + + c[seg].first = NO_INDEX; + c[seg].last = NO_INDEX; + c[seg].next = NO_INDEX; + + c[seg].data_size = data_size; + c[seg].data = secure_aligned_alloc(pool, (apr_size_t)data_size, FALSE); + c[seg].current_data = 0; + c[seg].data_used = 0; + c[seg].max_entry_size = max_entry_size; + + c[seg].used_entries = 0; + c[seg].hit_count = 0; + c[seg].total_reads = 0; + c[seg].total_writes = 0; + c[seg].total_hits = 0; + + /* were allocations successful? + * If not, initialize a minimal cache structure. + */ + if (c[seg].data == NULL || c[seg].directory == NULL) + { + /* We are OOM. There is no need to proceed with "half a cache". + */ + return svn_error_wrap_apr(APR_ENOMEM, "OOM"); + } + +#if APR_HAS_THREADS + /* A lock for intra-process synchronization to the cache, or NULL if + * the cache's creator doesn't feel the cache needs to be + * thread-safe. + */ + c[seg].lock = NULL; + if (thread_safe) + { + apr_status_t status = + apr_thread_rwlock_create(&(c[seg].lock), pool); + if (status) + return svn_error_wrap_apr(status, _("Can't create cache mutex")); + } + + /* Select the behavior of write operations. + */ + c[seg].allow_blocking_writes = allow_blocking_writes; +#endif + } + + /* done here + */ + *cache = c; + return SVN_NO_ERROR; +} + +/* Look for the cache entry in group GROUP_INDEX of CACHE, identified + * by the hash value TO_FIND and set *FOUND accordingly. + * + * Note: This function requires the caller to serialize access. + * Don't call it directly, call entry_exists instead. + */ +static svn_error_t * +entry_exists_internal(svn_membuffer_t *cache, + apr_uint32_t group_index, + entry_key_t to_find, + svn_boolean_t *found) +{ + *found = find_entry(cache, group_index, to_find, FALSE) != NULL; + return SVN_NO_ERROR; +} + +/* Look for the cache entry in group GROUP_INDEX of CACHE, identified + * by the hash value TO_FIND and set *FOUND accordingly. + */ +static svn_error_t * +entry_exists(svn_membuffer_t *cache, + apr_uint32_t group_index, + entry_key_t to_find, + svn_boolean_t *found) +{ + WITH_READ_LOCK(cache, + entry_exists_internal(cache, + group_index, + to_find, + found)); + + return SVN_NO_ERROR; +} + + +/* Try to insert the serialized item given in BUFFER with SIZE into + * the group GROUP_INDEX of CACHE and uniquely identify it by hash + * value TO_FIND. + * + * However, there is no guarantee that it will actually be put into + * the cache. If there is already some data associated with TO_FIND, + * it will be removed from the cache even if the new data cannot + * be inserted. + * + * Note: This function requires the caller to serialization access. + * Don't call it directly, call membuffer_cache_get_partial instead. + */ +static svn_error_t * +membuffer_cache_set_internal(svn_membuffer_t *cache, + entry_key_t to_find, + apr_uint32_t group_index, + char *buffer, + apr_size_t size, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *scratch_pool) +{ + /* first, look for a previous entry for the given key */ + entry_t *entry = find_entry(cache, group_index, to_find, FALSE); + + /* if there is an old version of that entry and the new data fits into + * the old spot, just re-use that space. */ + if (entry && ALIGN_VALUE(entry->size) >= size && buffer) + { + cache->data_used += size - entry->size; + entry->size = size; + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Remember original content, type and key (hashes) + */ + SVN_ERR(store_content_part(tag, buffer, size, scratch_pool)); + memcpy(&entry->tag, tag, sizeof(*tag)); + +#endif + + if (size) + memcpy(cache->data + entry->offset, buffer, size); + + cache->total_writes++; + return SVN_NO_ERROR; + } + + /* if necessary, enlarge the insertion window. + */ + if ( buffer != NULL + && cache->max_entry_size >= size + && ensure_data_insertable(cache, size)) + { + /* Remove old data for this key, if that exists. + * Get an unused entry for the key and and initialize it with + * the serialized item's (future) position within data buffer. + */ + entry = find_entry(cache, group_index, to_find, TRUE); + entry->size = size; + entry->offset = cache->current_data; + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Remember original content, type and key (hashes) + */ + SVN_ERR(store_content_part(tag, buffer, size, scratch_pool)); + memcpy(&entry->tag, tag, sizeof(*tag)); + +#endif + + /* Link the entry properly. + */ + insert_entry(cache, entry); + + /* Copy the serialized item data into the cache. + */ + if (size) + memcpy(cache->data + entry->offset, buffer, size); + + cache->total_writes++; + } + else + { + /* if there is already an entry for this key, drop it. + * Since ensure_data_insertable may have removed entries from + * ENTRY's group, re-do the lookup. + */ + entry = find_entry(cache, group_index, to_find, FALSE); + if (entry) + drop_entry(cache, entry); + } + + return SVN_NO_ERROR; +} + +/* Try to insert the ITEM and use the KEY to uniquely identify it. + * However, there is no guarantee that it will actually be put into + * the cache. If there is already some data associated to the KEY, + * it will be removed from the cache even if the new data cannot + * be inserted. + * + * The SERIALIZER is called to transform the ITEM into a single, + * flat data buffer. Temporary allocations may be done in POOL. + */ +static svn_error_t * +membuffer_cache_set(svn_membuffer_t *cache, + entry_key_t key, + void *item, + svn_cache__serialize_func_t serializer, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *scratch_pool) +{ + apr_uint32_t group_index; + void *buffer = NULL; + apr_size_t size = 0; + + /* find the entry group that will hold the key. + */ + group_index = get_group_index(&cache, key); + + /* Serialize data data. + */ + if (item) + SVN_ERR(serializer(&buffer, &size, item, scratch_pool)); + + /* The actual cache data access needs to sync'ed + */ + WITH_WRITE_LOCK(cache, + membuffer_cache_set_internal(cache, + key, + group_index, + buffer, + size, + DEBUG_CACHE_MEMBUFFER_TAG + scratch_pool)); + return SVN_NO_ERROR; +} + +/* Look for the cache entry in group GROUP_INDEX of CACHE, identified + * by the hash value TO_FIND. If no item has been stored for KEY, + * *BUFFER will be NULL. Otherwise, return a copy of the serialized + * data in *BUFFER and return its size in *ITEM_SIZE. Allocations will + * be done in POOL. + * + * Note: This function requires the caller to serialization access. + * Don't call it directly, call membuffer_cache_get_partial instead. + */ +static svn_error_t * +membuffer_cache_get_internal(svn_membuffer_t *cache, + apr_uint32_t group_index, + entry_key_t to_find, + char **buffer, + apr_size_t *item_size, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *result_pool) +{ + entry_t *entry; + apr_size_t size; + + /* The actual cache data access needs to sync'ed + */ + entry = find_entry(cache, group_index, to_find, FALSE); + cache->total_reads++; + if (entry == NULL) + { + /* no such entry found. + */ + *buffer = NULL; + *item_size = 0; + + return SVN_NO_ERROR; + } + + size = ALIGN_VALUE(entry->size); + *buffer = ALIGN_POINTER(apr_palloc(result_pool, size + ITEM_ALIGNMENT-1)); + memcpy(*buffer, (const char*)cache->data + entry->offset, size); + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Check for overlapping entries. + */ + SVN_ERR_ASSERT(entry->next == NO_INDEX || + entry->offset + size + <= get_entry(cache, entry->next)->offset); + + /* Compare original content, type and key (hashes) + */ + SVN_ERR(store_content_part(tag, *buffer, entry->size, result_pool)); + SVN_ERR(assert_equal_tags(&entry->tag, tag)); + +#endif + + /* update hit statistics + */ + entry->hit_count++; + cache->hit_count++; + cache->total_hits++; + + *item_size = entry->size; + + return SVN_NO_ERROR; +} + +/* Look for the *ITEM identified by KEY. If no item has been stored + * for KEY, *ITEM will be NULL. Otherwise, the DESERIALIZER is called + * re-construct the proper object from the serialized data. + * Allocations will be done in POOL. + */ +static svn_error_t * +membuffer_cache_get(svn_membuffer_t *cache, + entry_key_t key, + void **item, + svn_cache__deserialize_func_t deserializer, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *result_pool) +{ + apr_uint32_t group_index; + char *buffer; + apr_size_t size; + + /* find the entry group that will hold the key. + */ + group_index = get_group_index(&cache, key); + WITH_READ_LOCK(cache, + membuffer_cache_get_internal(cache, + group_index, + key, + &buffer, + &size, + DEBUG_CACHE_MEMBUFFER_TAG + result_pool)); + + /* re-construct the original data object from its serialized form. + */ + if (buffer == NULL) + { + *item = NULL; + return SVN_NO_ERROR; + } + + return deserializer(item, buffer, size, result_pool); +} + +/* Look for the cache entry in group GROUP_INDEX of CACHE, identified + * by the hash value TO_FIND. FOUND indicates whether that entry exists. + * If not found, *ITEM will be NULL. + * + * Otherwise, the DESERIALIZER is called with that entry and the BATON + * provided and will extract the desired information. The result is set + * in *ITEM. Allocations will be done in POOL. + * + * Note: This function requires the caller to serialization access. + * Don't call it directly, call membuffer_cache_get_partial instead. + */ +static svn_error_t * +membuffer_cache_get_partial_internal(svn_membuffer_t *cache, + apr_uint32_t group_index, + entry_key_t to_find, + void **item, + svn_boolean_t *found, + svn_cache__partial_getter_func_t deserializer, + void *baton, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *result_pool) +{ + entry_t *entry = find_entry(cache, group_index, to_find, FALSE); + cache->total_reads++; + if (entry == NULL) + { + *item = NULL; + *found = FALSE; + + return SVN_NO_ERROR; + } + else + { + *found = TRUE; + + entry->hit_count++; + cache->hit_count++; + cache->total_hits++; + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Check for overlapping entries. + */ + SVN_ERR_ASSERT(entry->next == NO_INDEX || + entry->offset + entry->size + <= get_entry(cache, entry->next)->offset); + + /* Compare original content, type and key (hashes) + */ + SVN_ERR(store_content_part(tag, + (const char*)cache->data + entry->offset, + entry->size, + result_pool)); + SVN_ERR(assert_equal_tags(&entry->tag, tag)); + +#endif + + return deserializer(item, + (const char*)cache->data + entry->offset, + entry->size, + baton, + result_pool); + } +} + +/* Look for the cache entry identified by KEY. FOUND indicates + * whether that entry exists. If not found, *ITEM will be NULL. Otherwise, + * the DESERIALIZER is called with that entry and the BATON provided + * and will extract the desired information. The result is set in *ITEM. + * Allocations will be done in POOL. + */ +static svn_error_t * +membuffer_cache_get_partial(svn_membuffer_t *cache, + entry_key_t key, + void **item, + svn_boolean_t *found, + svn_cache__partial_getter_func_t deserializer, + void *baton, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *result_pool) +{ + apr_uint32_t group_index = get_group_index(&cache, key); + + WITH_READ_LOCK(cache, + membuffer_cache_get_partial_internal + (cache, group_index, key, item, found, + deserializer, baton, DEBUG_CACHE_MEMBUFFER_TAG + result_pool)); + + return SVN_NO_ERROR; +} + +/* Look for the cache entry in group GROUP_INDEX of CACHE, identified + * by the hash value TO_FIND. If no entry has been found, the function + * returns without modifying the cache. + * + * Otherwise, FUNC is called with that entry and the BATON provided + * and may modify the cache entry. Allocations will be done in POOL. + * + * Note: This function requires the caller to serialization access. + * Don't call it directly, call membuffer_cache_set_partial instead. + */ +static svn_error_t * +membuffer_cache_set_partial_internal(svn_membuffer_t *cache, + apr_uint32_t group_index, + entry_key_t to_find, + svn_cache__partial_setter_func_t func, + void *baton, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *scratch_pool) +{ + /* cache item lookup + */ + entry_t *entry = find_entry(cache, group_index, to_find, FALSE); + cache->total_reads++; + + /* this function is a no-op if the item is not in cache + */ + if (entry != NULL) + { + svn_error_t *err; + + /* access the serialized cache item */ + char *data = (char*)cache->data + entry->offset; + char *orig_data = data; + apr_size_t size = entry->size; + + entry->hit_count++; + cache->hit_count++; + cache->total_writes++; + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Check for overlapping entries. + */ + SVN_ERR_ASSERT(entry->next == NO_INDEX || + entry->offset + size + <= get_entry(cache, entry->next)->offset); + + /* Compare original content, type and key (hashes) + */ + SVN_ERR(store_content_part(tag, data, size, scratch_pool)); + SVN_ERR(assert_equal_tags(&entry->tag, tag)); + +#endif + + /* modify it, preferably in-situ. + */ + err = func((void **)&data, &size, baton, scratch_pool); + + if (err) + { + /* Something somewhere when wrong while FUNC was modifying the + * changed item. Thus, it might have become invalid /corrupted. + * We better drop that. + */ + drop_entry(cache, entry); + } + else + { + /* if the modification caused a re-allocation, we need to remove + * the old entry and to copy the new data back into cache. + */ + if (data != orig_data) + { + /* Remove the old entry and try to make space for the new one. + */ + drop_entry(cache, entry); + if ( (cache->max_entry_size >= size) + && ensure_data_insertable(cache, size)) + { + /* Write the new entry. + */ + entry = find_entry(cache, group_index, to_find, TRUE); + entry->size = size; + entry->offset = cache->current_data; + if (size) + memcpy(cache->data + entry->offset, data, size); + + /* Link the entry properly. + */ + insert_entry(cache, entry); + } + } + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Remember original content, type and key (hashes) + */ + SVN_ERR(store_content_part(tag, data, size, scratch_pool)); + memcpy(&entry->tag, tag, sizeof(*tag)); + +#endif + } + } + + return SVN_NO_ERROR; +} + +/* Look for the cache entry identified by KEY. If no entry + * has been found, the function returns without modifying the cache. + * Otherwise, FUNC is called with that entry and the BATON provided + * and may modify the cache entry. Allocations will be done in POOL. + */ +static svn_error_t * +membuffer_cache_set_partial(svn_membuffer_t *cache, + entry_key_t key, + svn_cache__partial_setter_func_t func, + void *baton, + DEBUG_CACHE_MEMBUFFER_TAG_ARG + apr_pool_t *scratch_pool) +{ + /* cache item lookup + */ + apr_uint32_t group_index = get_group_index(&cache, key); + WITH_WRITE_LOCK(cache, + membuffer_cache_set_partial_internal + (cache, group_index, key, func, baton, + DEBUG_CACHE_MEMBUFFER_TAG + scratch_pool)); + + /* done here -> unlock the cache + */ + return SVN_NO_ERROR; +} + +/* Implement the svn_cache__t interface on top of a shared membuffer cache. + * + * Because membuffer caches tend to be very large, there will be rather few + * of them (usually only one). Thus, the same instance shall be used as the + * backend to many application-visible svn_cache__t instances. This should + * also achieve global resource usage fairness. + * + * To accommodate items from multiple resources, the individual keys must be + * unique over all sources. This is achieved by simply adding a prefix key + * that unambiguously identifies the item's context (e.g. path to the + * respective repository). The prefix will be set upon construction of the + * svn_cache__t instance. + */ + +/* Internal cache structure (used in svn_cache__t.cache_internal) basically + * holding the additional parameters needed to call the respective membuffer + * functions. + */ +typedef struct svn_membuffer_cache_t +{ + /* this is where all our data will end up in + */ + svn_membuffer_t *membuffer; + + /* use this conversion function when inserting an item into the memcache + */ + svn_cache__serialize_func_t serializer; + + /* use this conversion function when reading an item from the memcache + */ + svn_cache__deserialize_func_t deserializer; + + /* Prepend this byte sequence to any key passed to us. + * This makes (very likely) our keys different from all keys used + * by other svn_membuffer_cache_t instances. + */ + entry_key_t prefix; + + /* A copy of the unmodified prefix. It is being used as a user-visible + * ID for this cache instance. + */ + const char* full_prefix; + + /* length of the keys that will be passed to us through the + * svn_cache_t interface. May be APR_HASH_KEY_STRING. + */ + apr_ssize_t key_len; + + /* Temporary buffer containing the hash key for the current access + */ + entry_key_t combined_key; + + /* a pool for temporary allocations during get() and set() + */ + apr_pool_t *pool; + + /* an internal counter that is used to clear the pool from time to time + * but not too frequently. + */ + int alloc_counter; + + /* if enabled, this will serialize the access to this instance. + */ + svn_mutex__t *mutex; +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Invariant tag info for all items stored by this cache instance. + */ + char prefix_tail[PREFIX_TAIL_LEN]; + +#endif +} svn_membuffer_cache_t; + +/* After an estimated ALLOCATIONS_PER_POOL_CLEAR allocations, we should + * clear the svn_membuffer_cache_t.pool to keep memory consumption in check. + */ +#define ALLOCATIONS_PER_POOL_CLEAR 10 + + +/* Basically calculate a hash value for KEY of length KEY_LEN, combine it + * with the CACHE->PREFIX and write the result in CACHE->COMBINED_KEY. + */ +static void +combine_key(svn_membuffer_cache_t *cache, + const void *key, + apr_ssize_t key_len) +{ + if (key_len == APR_HASH_KEY_STRING) + key_len = strlen((const char *) key); + + if (key_len < 16) + { + apr_uint32_t data[4] = { 0 }; + memcpy(data, key, key_len); + + svn__pseudo_md5_15((apr_uint32_t *)cache->combined_key, data); + } + else if (key_len < 32) + { + apr_uint32_t data[8] = { 0 }; + memcpy(data, key, key_len); + + svn__pseudo_md5_31((apr_uint32_t *)cache->combined_key, data); + } + else if (key_len < 64) + { + apr_uint32_t data[16] = { 0 }; + memcpy(data, key, key_len); + + svn__pseudo_md5_63((apr_uint32_t *)cache->combined_key, data); + } + else + { + apr_md5((unsigned char*)cache->combined_key, key, key_len); + } + + cache->combined_key[0] ^= cache->prefix[0]; + cache->combined_key[1] ^= cache->prefix[1]; +} + +/* Implement svn_cache__vtable_t.get (not thread-safe) + */ +static svn_error_t * +svn_membuffer_cache_get(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + apr_pool_t *result_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + + DEBUG_CACHE_MEMBUFFER_INIT_TAG + + /* special case */ + if (key == NULL) + { + *value_p = NULL; + *found = FALSE; + + return SVN_NO_ERROR; + } + + /* construct the full, i.e. globally unique, key by adding + * this cache instances' prefix + */ + combine_key(cache, key, cache->key_len); + + /* Look the item up. */ + SVN_ERR(membuffer_cache_get(cache->membuffer, + cache->combined_key, + value_p, + cache->deserializer, + DEBUG_CACHE_MEMBUFFER_TAG + result_pool)); + + /* return result */ + *found = *value_p != NULL; + return SVN_NO_ERROR; +} + +/* Implement svn_cache__vtable_t.set (not thread-safe) + */ +static svn_error_t * +svn_membuffer_cache_set(void *cache_void, + const void *key, + void *value, + apr_pool_t *scratch_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + + DEBUG_CACHE_MEMBUFFER_INIT_TAG + + /* special case */ + if (key == NULL) + return SVN_NO_ERROR; + + /* we do some allocations below, so increase the allocation counter + * by a slightly larger amount. Free allocated memory every now and then. + */ + cache->alloc_counter += 3; + if (cache->alloc_counter > ALLOCATIONS_PER_POOL_CLEAR) + { + svn_pool_clear(cache->pool); + cache->alloc_counter = 0; + } + + /* construct the full, i.e. globally unique, key by adding + * this cache instances' prefix + */ + combine_key(cache, key, cache->key_len); + + /* (probably) add the item to the cache. But there is no real guarantee + * that the item will actually be cached afterwards. + */ + return membuffer_cache_set(cache->membuffer, + cache->combined_key, + value, + cache->serializer, + DEBUG_CACHE_MEMBUFFER_TAG + cache->pool); +} + +/* Implement svn_cache__vtable_t.iter as "not implemented" + */ +static svn_error_t * +svn_membuffer_cache_iter(svn_boolean_t *completed, + void *cache_void, + svn_iter_apr_hash_cb_t user_cb, + void *user_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can't iterate a membuffer-based cache")); +} + +/* Implement svn_cache__vtable_t.get_partial (not thread-safe) + */ +static svn_error_t * +svn_membuffer_cache_get_partial(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + svn_cache__partial_getter_func_t func, + void *baton, + apr_pool_t *result_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + + DEBUG_CACHE_MEMBUFFER_INIT_TAG + + if (key == NULL) + { + *value_p = NULL; + *found = FALSE; + + return SVN_NO_ERROR; + } + + combine_key(cache, key, cache->key_len); + SVN_ERR(membuffer_cache_get_partial(cache->membuffer, + cache->combined_key, + value_p, + found, + func, + baton, + DEBUG_CACHE_MEMBUFFER_TAG + result_pool)); + + return SVN_NO_ERROR; +} + +/* Implement svn_cache__vtable_t.set_partial (not thread-safe) + */ +static svn_error_t * +svn_membuffer_cache_set_partial(void *cache_void, + const void *key, + svn_cache__partial_setter_func_t func, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + + DEBUG_CACHE_MEMBUFFER_INIT_TAG + + if (key != NULL) + { + combine_key(cache, key, cache->key_len); + SVN_ERR(membuffer_cache_set_partial(cache->membuffer, + cache->combined_key, + func, + baton, + DEBUG_CACHE_MEMBUFFER_TAG + scratch_pool)); + } + return SVN_NO_ERROR; +} + +/* Implement svn_cache__vtable_t.is_cachable + * (thread-safe even without mutex) + */ +static svn_boolean_t +svn_membuffer_cache_is_cachable(void *cache_void, apr_size_t size) +{ + /* Don't allow extremely large element sizes. Otherwise, the cache + * might by thrashed by a few extremely large entries. And the size + * must be small enough to be stored in a 32 bit value. + */ + svn_membuffer_cache_t *cache = cache_void; + return size <= cache->membuffer->max_entry_size; +} + +/* Add statistics of SEGMENT to INFO. + */ +static svn_error_t * +svn_membuffer_get_segment_info(svn_membuffer_t *segment, + svn_cache__info_t *info) +{ + info->data_size += segment->data_size; + info->used_size += segment->data_used; + info->total_size += segment->data_size + + segment->group_count * GROUP_SIZE * sizeof(entry_t); + + info->used_entries += segment->used_entries; + info->total_entries += segment->group_count * GROUP_SIZE; + + return SVN_NO_ERROR; +} + +/* Implement svn_cache__vtable_t.get_info + * (thread-safe even without mutex) + */ +static svn_error_t * +svn_membuffer_cache_get_info(void *cache_void, + svn_cache__info_t *info, + svn_boolean_t reset, + apr_pool_t *result_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + apr_uint32_t i; + + /* cache front-end specific data */ + + info->id = apr_pstrdup(result_pool, cache->full_prefix); + + /* collect info from shared cache back-end */ + + info->data_size = 0; + info->used_size = 0; + info->total_size = 0; + + info->used_entries = 0; + info->total_entries = 0; + + for (i = 0; i < cache->membuffer->segment_count; ++i) + { + svn_membuffer_t *segment = cache->membuffer + i; + WITH_READ_LOCK(segment, + svn_membuffer_get_segment_info(segment, info)); + } + + return SVN_NO_ERROR; +} + + +/* the v-table for membuffer-based caches (single-threaded access) + */ +static svn_cache__vtable_t membuffer_cache_vtable = { + svn_membuffer_cache_get, + svn_membuffer_cache_set, + svn_membuffer_cache_iter, + svn_membuffer_cache_is_cachable, + svn_membuffer_cache_get_partial, + svn_membuffer_cache_set_partial, + svn_membuffer_cache_get_info +}; + +/* Implement svn_cache__vtable_t.get and serialize all cache access. + */ +static svn_error_t * +svn_membuffer_cache_get_synced(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + apr_pool_t *result_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + SVN_MUTEX__WITH_LOCK(cache->mutex, + svn_membuffer_cache_get(value_p, + found, + cache_void, + key, + result_pool)); + + return SVN_NO_ERROR; +} + +/* Implement svn_cache__vtable_t.set and serialize all cache access. + */ +static svn_error_t * +svn_membuffer_cache_set_synced(void *cache_void, + const void *key, + void *value, + apr_pool_t *scratch_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + SVN_MUTEX__WITH_LOCK(cache->mutex, + svn_membuffer_cache_set(cache_void, + key, + value, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Implement svn_cache__vtable_t.get_partial and serialize all cache access. + */ +static svn_error_t * +svn_membuffer_cache_get_partial_synced(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + svn_cache__partial_getter_func_t func, + void *baton, + apr_pool_t *result_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + SVN_MUTEX__WITH_LOCK(cache->mutex, + svn_membuffer_cache_get_partial(value_p, + found, + cache_void, + key, + func, + baton, + result_pool)); + + return SVN_NO_ERROR; +} + +/* Implement svn_cache__vtable_t.set_partial and serialize all cache access. + */ +static svn_error_t * +svn_membuffer_cache_set_partial_synced(void *cache_void, + const void *key, + svn_cache__partial_setter_func_t func, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_membuffer_cache_t *cache = cache_void; + SVN_MUTEX__WITH_LOCK(cache->mutex, + svn_membuffer_cache_set_partial(cache_void, + key, + func, + baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* the v-table for membuffer-based caches with multi-threading support) + */ +static svn_cache__vtable_t membuffer_cache_synced_vtable = { + svn_membuffer_cache_get_synced, + svn_membuffer_cache_set_synced, + svn_membuffer_cache_iter, /* no sync required */ + svn_membuffer_cache_is_cachable, /* no sync required */ + svn_membuffer_cache_get_partial_synced, + svn_membuffer_cache_set_partial_synced, + svn_membuffer_cache_get_info /* no sync required */ +}; + +/* standard serialization function for svn_stringbuf_t items. + * Implements svn_cache__serialize_func_t. + */ +static svn_error_t * +serialize_svn_stringbuf(void **buffer, + apr_size_t *buffer_size, + void *item, + apr_pool_t *result_pool) +{ + svn_stringbuf_t *value_str = item; + + *buffer = value_str->data; + *buffer_size = value_str->len + 1; + + return SVN_NO_ERROR; +} + +/* standard de-serialization function for svn_stringbuf_t items. + * Implements svn_cache__deserialize_func_t. + */ +static svn_error_t * +deserialize_svn_stringbuf(void **item, + void *buffer, + apr_size_t buffer_size, + apr_pool_t *result_pool) +{ + svn_stringbuf_t *value_str = apr_palloc(result_pool, sizeof(svn_stringbuf_t)); + + value_str->pool = result_pool; + value_str->blocksize = buffer_size; + value_str->data = buffer; + value_str->len = buffer_size-1; + *item = value_str; + + return SVN_NO_ERROR; +} + +/* Construct a svn_cache__t object on top of a shared memcache. + */ +svn_error_t * +svn_cache__create_membuffer_cache(svn_cache__t **cache_p, + svn_membuffer_t *membuffer, + svn_cache__serialize_func_t serializer, + svn_cache__deserialize_func_t deserializer, + apr_ssize_t klen, + const char *prefix, + svn_boolean_t thread_safe, + apr_pool_t *pool) +{ + svn_checksum_t *checksum; + + /* allocate the cache header structures + */ + svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper)); + svn_membuffer_cache_t *cache = apr_palloc(pool, sizeof(*cache)); + + /* initialize our internal cache header + */ + cache->membuffer = membuffer; + cache->serializer = serializer + ? serializer + : serialize_svn_stringbuf; + cache->deserializer = deserializer + ? deserializer + : deserialize_svn_stringbuf; + cache->full_prefix = apr_pstrdup(pool, prefix); + cache->key_len = klen; + cache->pool = svn_pool_create(pool); + cache->alloc_counter = 0; + + SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool)); + + /* for performance reasons, we don't actually store the full prefix but a + * hash value of it + */ + SVN_ERR(svn_checksum(&checksum, + svn_checksum_md5, + prefix, + strlen(prefix), + pool)); + memcpy(cache->prefix, checksum->digest, sizeof(cache->prefix)); + +#ifdef SVN_DEBUG_CACHE_MEMBUFFER + + /* Initialize cache debugging support. + */ + get_prefix_tail(prefix, cache->prefix_tail); + +#endif + + /* initialize the generic cache wrapper + */ + wrapper->vtable = thread_safe ? &membuffer_cache_synced_vtable + : &membuffer_cache_vtable; + wrapper->cache_internal = cache; + wrapper->error_handler = 0; + wrapper->error_baton = 0; + + *cache_p = wrapper; + return SVN_NO_ERROR; +} + diff --git a/subversion/libsvn_subr/cache-memcache.c b/subversion/libsvn_subr/cache-memcache.c new file mode 100644 index 000000000000..5332d04428e3 --- /dev/null +++ b/subversion/libsvn_subr/cache-memcache.c @@ -0,0 +1,583 @@ +/* + * cache-memcache.c: memcached caching for Subversion + * + * ==================================================================== + * 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_md5.h> + +#include "svn_pools.h" +#include "svn_base64.h" +#include "svn_path.h" + +#include "svn_private_config.h" +#include "private/svn_cache.h" +#include "private/svn_dep_compat.h" + +#include "cache.h" + +#ifdef SVN_HAVE_MEMCACHE + +#include <apr_memcache.h> + +/* A note on thread safety: + + The apr_memcache_t object does its own mutex handling, and nothing + else in memcache_t is ever modified, so this implementation should + be fully thread-safe. +*/ + +/* The (internal) cache object. */ +typedef struct memcache_t { + /* The memcached server set we're using. */ + apr_memcache_t *memcache; + + /* A prefix used to differentiate our data from any other data in + * the memcached (URI-encoded). */ + const char *prefix; + + /* The size of the key: either a fixed number of bytes or + * APR_HASH_KEY_STRING. */ + apr_ssize_t klen; + + + /* Used to marshal values in and out of the cache. */ + svn_cache__serialize_func_t serialize_func; + svn_cache__deserialize_func_t deserialize_func; +} memcache_t; + +/* The wrapper around apr_memcache_t. */ +struct svn_memcache_t { + apr_memcache_t *c; +}; + + +/* The memcached protocol says the maximum key length is 250. Let's + just say 249, to be safe. */ +#define MAX_MEMCACHED_KEY_LEN 249 +#define MEMCACHED_KEY_UNHASHED_LEN (MAX_MEMCACHED_KEY_LEN - \ + 2 * APR_MD5_DIGESTSIZE) + + +/* Set *MC_KEY to a memcache key for the given key KEY for CACHE, allocated + in POOL. */ +static svn_error_t * +build_key(const char **mc_key, + memcache_t *cache, + const void *raw_key, + apr_pool_t *pool) +{ + const char *encoded_suffix; + const char *long_key; + apr_size_t long_key_len; + + if (cache->klen == APR_HASH_KEY_STRING) + encoded_suffix = svn_path_uri_encode(raw_key, pool); + else + { + const svn_string_t *raw = svn_string_ncreate(raw_key, cache->klen, pool); + const svn_string_t *encoded = svn_base64_encode_string2(raw, FALSE, + pool); + encoded_suffix = encoded->data; + } + + long_key = apr_pstrcat(pool, "SVN:", cache->prefix, ":", encoded_suffix, + (char *)NULL); + long_key_len = strlen(long_key); + + /* We don't want to have a key that's too big. If it was going to + be too big, we MD5 the entire string, then replace the last bit + with the checksum. Note that APR_MD5_DIGESTSIZE is for the pure + binary digest; we have to double that when we convert to hex. + + Every key we use will either be at most + MEMCACHED_KEY_UNHASHED_LEN bytes long, or be exactly + MAX_MEMCACHED_KEY_LEN bytes long. */ + if (long_key_len > MEMCACHED_KEY_UNHASHED_LEN) + { + svn_checksum_t *checksum; + SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, long_key, long_key_len, + pool)); + + long_key = apr_pstrcat(pool, + apr_pstrmemdup(pool, long_key, + MEMCACHED_KEY_UNHASHED_LEN), + svn_checksum_to_cstring_display(checksum, pool), + (char *)NULL); + } + + *mc_key = long_key; + return SVN_NO_ERROR; +} + +/* Core functionality of our getter functions: fetch DATA from the memcached + * given by CACHE_VOID and identified by KEY. Indicate success in FOUND and + * use a tempoary sub-pool of POOL for allocations. + */ +static svn_error_t * +memcache_internal_get(char **data, + apr_size_t *size, + svn_boolean_t *found, + void *cache_void, + const void *key, + apr_pool_t *pool) +{ + memcache_t *cache = cache_void; + apr_status_t apr_err; + const char *mc_key; + apr_pool_t *subpool; + + if (key == NULL) + { + *found = FALSE; + return SVN_NO_ERROR; + } + + subpool = svn_pool_create(pool); + SVN_ERR(build_key(&mc_key, cache, key, subpool)); + + apr_err = apr_memcache_getp(cache->memcache, + pool, + mc_key, + data, + size, + NULL /* ignore flags */); + if (apr_err == APR_NOTFOUND) + { + *found = FALSE; + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } + else if (apr_err != APR_SUCCESS || !*data) + return svn_error_wrap_apr(apr_err, + _("Unknown memcached error while reading")); + + *found = TRUE; + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +static svn_error_t * +memcache_get(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + apr_pool_t *result_pool) +{ + memcache_t *cache = cache_void; + char *data; + apr_size_t data_len; + SVN_ERR(memcache_internal_get(&data, + &data_len, + found, + cache_void, + key, + result_pool)); + + /* If we found it, de-serialize it. */ + if (*found) + { + if (cache->deserialize_func) + { + SVN_ERR((cache->deserialize_func)(value_p, data, data_len, + result_pool)); + } + else + { + svn_string_t *value = apr_pcalloc(result_pool, sizeof(*value)); + value->data = data; + value->len = data_len; + *value_p = value; + } + } + + return SVN_NO_ERROR; +} + +/* Core functionality of our setter functions: store LENGH bytes of DATA + * to be identified by KEY in the memcached given by CACHE_VOID. Use POOL + * for temporary allocations. + */ +static svn_error_t * +memcache_internal_set(void *cache_void, + const void *key, + const char *data, + apr_size_t len, + apr_pool_t *scratch_pool) +{ + memcache_t *cache = cache_void; + const char *mc_key; + apr_status_t apr_err; + + SVN_ERR(build_key(&mc_key, cache, key, scratch_pool)); + apr_err = apr_memcache_set(cache->memcache, mc_key, (char *)data, len, 0, 0); + + /* ### Maybe write failures should be ignored (but logged)? */ + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, + _("Unknown memcached error while writing")); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +memcache_set(void *cache_void, + const void *key, + void *value, + apr_pool_t *scratch_pool) +{ + memcache_t *cache = cache_void; + apr_pool_t *subpool = svn_pool_create(scratch_pool); + void *data; + apr_size_t data_len; + svn_error_t *err; + + if (key == NULL) + return SVN_NO_ERROR; + + if (cache->serialize_func) + { + SVN_ERR((cache->serialize_func)(&data, &data_len, value, subpool)); + } + else + { + svn_stringbuf_t *value_str = value; + data = value_str->data; + data_len = value_str->len; + } + + err = memcache_internal_set(cache_void, key, data, data_len, subpool); + + svn_pool_destroy(subpool); + return err; +} + +static svn_error_t * +memcache_get_partial(void **value_p, + svn_boolean_t *found, + void *cache_void, + const void *key, + svn_cache__partial_getter_func_t func, + void *baton, + apr_pool_t *result_pool) +{ + svn_error_t *err = SVN_NO_ERROR; + + char *data; + apr_size_t size; + SVN_ERR(memcache_internal_get(&data, + &size, + found, + cache_void, + key, + result_pool)); + + /* If we found it, de-serialize it. */ + return *found + ? func(value_p, data, size, baton, result_pool) + : err; +} + + +static svn_error_t * +memcache_set_partial(void *cache_void, + const void *key, + svn_cache__partial_setter_func_t func, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err = SVN_NO_ERROR; + + void *data; + apr_size_t size; + svn_boolean_t found = FALSE; + + apr_pool_t *subpool = svn_pool_create(scratch_pool); + SVN_ERR(memcache_internal_get((char **)&data, + &size, + &found, + cache_void, + key, + subpool)); + + /* If we found it, modify it and write it back to cache */ + if (found) + { + SVN_ERR(func(&data, &size, baton, subpool)); + err = memcache_internal_set(cache_void, key, data, size, subpool); + } + + svn_pool_destroy(subpool); + return err; +} + + +static svn_error_t * +memcache_iter(svn_boolean_t *completed, + void *cache_void, + svn_iter_apr_hash_cb_t user_cb, + void *user_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can't iterate a memcached cache")); +} + +static svn_boolean_t +memcache_is_cachable(void *unused, apr_size_t size) +{ + (void)unused; /* silence gcc warning. */ + + /* The memcached cutoff seems to be a bit (header length?) under a megabyte. + * We round down a little to be safe. + */ + return size < 1000000; +} + +static svn_error_t * +memcache_get_info(void *cache_void, + svn_cache__info_t *info, + svn_boolean_t reset, + apr_pool_t *result_pool) +{ + memcache_t *cache = cache_void; + + info->id = apr_pstrdup(result_pool, cache->prefix); + + /* we don't have any memory allocation info */ + + info->used_size = 0; + info->total_size = 0; + info->data_size = 0; + info->used_entries = 0; + info->total_entries = 0; + + return SVN_NO_ERROR; +} + +static svn_cache__vtable_t memcache_vtable = { + memcache_get, + memcache_set, + memcache_iter, + memcache_is_cachable, + memcache_get_partial, + memcache_set_partial, + memcache_get_info +}; + +svn_error_t * +svn_cache__create_memcache(svn_cache__t **cache_p, + svn_memcache_t *memcache, + svn_cache__serialize_func_t serialize_func, + svn_cache__deserialize_func_t deserialize_func, + apr_ssize_t klen, + const char *prefix, + apr_pool_t *pool) +{ + svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper)); + memcache_t *cache = apr_pcalloc(pool, sizeof(*cache)); + + cache->serialize_func = serialize_func; + cache->deserialize_func = deserialize_func; + cache->klen = klen; + cache->prefix = svn_path_uri_encode(prefix, pool); + cache->memcache = memcache->c; + + wrapper->vtable = &memcache_vtable; + wrapper->cache_internal = cache; + wrapper->error_handler = 0; + wrapper->error_baton = 0; + + *cache_p = wrapper; + return SVN_NO_ERROR; +} + + +/*** Creating apr_memcache_t from svn_config_t. ***/ + +/* Baton for add_memcache_server. */ +struct ams_baton { + apr_memcache_t *memcache; + apr_pool_t *memcache_pool; + svn_error_t *err; +}; + +/* Implements svn_config_enumerator2_t. */ +static svn_boolean_t +add_memcache_server(const char *name, + const char *value, + void *baton, + apr_pool_t *pool) +{ + struct ams_baton *b = baton; + char *host, *scope; + apr_port_t port; + apr_status_t apr_err; + apr_memcache_server_t *server; + + apr_err = apr_parse_addr_port(&host, &scope, &port, + value, pool); + if (apr_err != APR_SUCCESS) + { + b->err = svn_error_wrap_apr(apr_err, + _("Error parsing memcache server '%s'"), + name); + return FALSE; + } + + if (scope) + { + b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL, + _("Scope not allowed in memcache server " + "'%s'"), + name); + return FALSE; + } + if (!host || !port) + { + b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL, + _("Must specify host and port for memcache " + "server '%s'"), + name); + return FALSE; + } + + /* Note: the four numbers here are only relevant when an + apr_memcache_t is being shared by multiple threads. */ + apr_err = apr_memcache_server_create(b->memcache_pool, + host, + port, + 0, /* min connections */ + 5, /* soft max connections */ + 10, /* hard max connections */ + /* time to live (in microseconds) */ + apr_time_from_sec(50), + &server); + if (apr_err != APR_SUCCESS) + { + b->err = svn_error_wrap_apr(apr_err, + _("Unknown error creating memcache server")); + return FALSE; + } + + apr_err = apr_memcache_add_server(b->memcache, server); + if (apr_err != APR_SUCCESS) + { + b->err = svn_error_wrap_apr(apr_err, + _("Unknown error adding server to memcache")); + return FALSE; + } + + return TRUE; +} + +#else /* ! SVN_HAVE_MEMCACHE */ + +/* Stubs for no apr memcache library. */ + +struct svn_memcache_t { + void *unused; /* Let's not have a size-zero struct. */ +}; + +svn_error_t * +svn_cache__create_memcache(svn_cache__t **cache_p, + svn_memcache_t *memcache, + svn_cache__serialize_func_t serialize_func, + svn_cache__deserialize_func_t deserialize_func, + apr_ssize_t klen, + const char *prefix, + apr_pool_t *pool) +{ + return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL); +} + +#endif /* SVN_HAVE_MEMCACHE */ + +/* Implements svn_config_enumerator2_t. Just used for the + entry-counting return value of svn_config_enumerate2. */ +static svn_boolean_t +nop_enumerator(const char *name, + const char *value, + void *baton, + apr_pool_t *pool) +{ + return TRUE; +} + +svn_error_t * +svn_cache__make_memcache_from_config(svn_memcache_t **memcache_p, + svn_config_t *config, + apr_pool_t *pool) +{ + int server_count; + apr_pool_t *subpool = svn_pool_create(pool); + + server_count = + svn_config_enumerate2(config, + SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS, + nop_enumerator, NULL, subpool); + + if (server_count == 0) + { + *memcache_p = NULL; + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } + + if (server_count > APR_INT16_MAX) + return svn_error_create(SVN_ERR_TOO_MANY_MEMCACHED_SERVERS, NULL, NULL); + +#ifdef SVN_HAVE_MEMCACHE + { + struct ams_baton b; + svn_memcache_t *memcache = apr_pcalloc(pool, sizeof(*memcache)); + apr_status_t apr_err = apr_memcache_create(pool, + (apr_uint16_t)server_count, + 0, /* flags */ + &(memcache->c)); + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, + _("Unknown error creating apr_memcache_t")); + + b.memcache = memcache->c; + b.memcache_pool = pool; + b.err = SVN_NO_ERROR; + svn_config_enumerate2(config, + SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS, + add_memcache_server, &b, + subpool); + + if (b.err) + return b.err; + + *memcache_p = memcache; + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; + } +#else /* ! SVN_HAVE_MEMCACHE */ + { + return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL); + } +#endif /* SVN_HAVE_MEMCACHE */ +} diff --git a/subversion/libsvn_subr/cache.c b/subversion/libsvn_subr/cache.c new file mode 100644 index 000000000000..70e189f16d93 --- /dev/null +++ b/subversion/libsvn_subr/cache.c @@ -0,0 +1,265 @@ +/* + * cache.c: cache interface for Subversion + * + * ==================================================================== + * 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 "cache.h" + +svn_error_t * +svn_cache__set_error_handler(svn_cache__t *cache, + svn_cache__error_handler_t handler, + void *baton, + apr_pool_t *scratch_pool) +{ + cache->error_handler = handler; + cache->error_baton = baton; + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_cache__is_cachable(svn_cache__t *cache, + apr_size_t size) +{ + /* having no cache means we can't cache anything */ + if (cache == NULL) + return FALSE; + + return cache->vtable->is_cachable(cache->cache_internal, size); +} + +/* Give the error handler callback a chance to replace or ignore the + error. */ +static svn_error_t * +handle_error(svn_cache__t *cache, + svn_error_t *err, + apr_pool_t *pool) +{ + if (err) + { + cache->failures++; + if (cache->error_handler) + err = (cache->error_handler)(err, cache->error_baton, pool); + } + + return err; +} + + +svn_error_t * +svn_cache__get(void **value_p, + svn_boolean_t *found, + svn_cache__t *cache, + const void *key, + apr_pool_t *result_pool) +{ + svn_error_t *err; + + /* In case any errors happen and are quelched, make sure we start + out with FOUND set to false. */ + *found = FALSE; +#ifdef SVN_DEBUG + if (getenv("SVN_X_DOES_NOT_MARK_THE_SPOT")) + return SVN_NO_ERROR; +#endif + + cache->reads++; + err = handle_error(cache, + (cache->vtable->get)(value_p, + found, + cache->cache_internal, + key, + result_pool), + result_pool); + + if (*found) + cache->hits++; + + return err; +} + +svn_error_t * +svn_cache__set(svn_cache__t *cache, + const void *key, + void *value, + apr_pool_t *scratch_pool) +{ + cache->writes++; + return handle_error(cache, + (cache->vtable->set)(cache->cache_internal, + key, + value, + scratch_pool), + scratch_pool); +} + + +svn_error_t * +svn_cache__iter(svn_boolean_t *completed, + svn_cache__t *cache, + svn_iter_apr_hash_cb_t user_cb, + void *user_baton, + apr_pool_t *scratch_pool) +{ +#ifdef SVN_DEBUG + if (getenv("SVN_X_DOES_NOT_MARK_THE_SPOT")) + /* Pretend CACHE is empty. */ + return SVN_NO_ERROR; +#endif + + return (cache->vtable->iter)(completed, + cache->cache_internal, + user_cb, + user_baton, + scratch_pool); +} + +svn_error_t * +svn_cache__get_partial(void **value, + svn_boolean_t *found, + svn_cache__t *cache, + const void *key, + svn_cache__partial_getter_func_t func, + void *baton, + apr_pool_t *result_pool) +{ + svn_error_t *err; + + /* In case any errors happen and are quelched, make sure we start + out with FOUND set to false. */ + *found = FALSE; +#ifdef SVN_DEBUG + if (getenv("SVN_X_DOES_NOT_MARK_THE_SPOT")) + return SVN_NO_ERROR; +#endif + + cache->reads++; + err = handle_error(cache, + (cache->vtable->get_partial)(value, + found, + cache->cache_internal, + key, + func, + baton, + result_pool), + result_pool); + + if (*found) + cache->hits++; + + return err; +} + +svn_error_t * +svn_cache__set_partial(svn_cache__t *cache, + const void *key, + svn_cache__partial_setter_func_t func, + void *baton, + apr_pool_t *scratch_pool) +{ + cache->writes++; + return handle_error(cache, + (cache->vtable->set_partial)(cache->cache_internal, + key, + func, + baton, + scratch_pool), + scratch_pool); +} + +svn_error_t * +svn_cache__get_info(svn_cache__t *cache, + svn_cache__info_t *info, + svn_boolean_t reset, + apr_pool_t *result_pool) +{ + /* write general statistics */ + + info->gets = cache->reads; + info->hits = cache->hits; + info->sets = cache->writes; + info->failures = cache->failures; + + /* Call the cache implementation for filling the blanks. + * It might also replace some of the general stats but + * this is currently not done. + */ + SVN_ERR((cache->vtable->get_info)(cache->cache_internal, + info, + reset, + result_pool)); + + /* reset statistics */ + + if (reset) + { + cache->reads = 0; + cache->hits = 0; + cache->writes = 0; + cache->failures = 0; + } + + return SVN_NO_ERROR; +} + +svn_string_t * +svn_cache__format_info(const svn_cache__info_t *info, + apr_pool_t *result_pool) +{ + enum { _1MB = 1024 * 1024 }; + + apr_uint64_t misses = info->gets - info->hits; + double hit_rate = (100.0 * (double)info->hits) + / (double)(info->gets ? info->gets : 1); + double write_rate = (100.0 * (double)info->sets) + / (double)(misses ? misses : 1); + double data_usage_rate = (100.0 * (double)info->used_size) + / (double)(info->data_size ? info->data_size : 1); + double data_entry_rate = (100.0 * (double)info->used_entries) + / (double)(info->total_entries ? info->total_entries : 1); + + return svn_string_createf(result_pool, + + "prefix : %s\n" + "gets : %" APR_UINT64_T_FMT + ", %" APR_UINT64_T_FMT " hits (%5.2f%%)\n" + "sets : %" APR_UINT64_T_FMT + " (%5.2f%% of misses)\n" + "failures: %" APR_UINT64_T_FMT "\n" + "used : %" APR_UINT64_T_FMT " MB (%5.2f%%)" + " of %" APR_UINT64_T_FMT " MB data cache" + " / %" APR_UINT64_T_FMT " MB total cache memory\n" + " %" APR_UINT64_T_FMT " entries (%5.2f%%)" + " of %" APR_UINT64_T_FMT " total\n", + + info->id, + + info->gets, + info->hits, hit_rate, + info->sets, write_rate, + info->failures, + + info->used_size / _1MB, data_usage_rate, + info->data_size / _1MB, + info->total_size / _1MB, + + info->used_entries, data_entry_rate, + info->total_entries); +} diff --git a/subversion/libsvn_subr/cache.h b/subversion/libsvn_subr/cache.h new file mode 100644 index 000000000000..5029cefe15a7 --- /dev/null +++ b/subversion/libsvn_subr/cache.h @@ -0,0 +1,109 @@ +/* + * cache.h: cache vtable interface + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_CACHE_H +#define SVN_LIBSVN_SUBR_CACHE_H + +#include "private/svn_cache.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct svn_cache__vtable_t { + /* See svn_cache__get(). */ + svn_error_t *(*get)(void **value, + svn_boolean_t *found, + void *cache_implementation, + const void *key, + apr_pool_t *result_pool); + + /* See svn_cache__set(). */ + svn_error_t *(*set)(void *cache_implementation, + const void *key, + void *value, + apr_pool_t *scratch_pool); + + /* See svn_cache__iter(). */ + svn_error_t *(*iter)(svn_boolean_t *completed, + void *cache_implementation, + svn_iter_apr_hash_cb_t func, + void *baton, + apr_pool_t *scratch_pool); + + /* See svn_cache__is_cachable(). */ + svn_boolean_t (*is_cachable)(void *cache_implementation, + apr_size_t size); + + /* See svn_cache__get_partial(). */ + svn_error_t *(*get_partial)(void **value, + svn_boolean_t *found, + void *cache_implementation, + const void *key, + svn_cache__partial_getter_func_t func, + void *baton, + apr_pool_t *result_pool); + + /* See svn_cache__set_partial(). */ + svn_error_t *(*set_partial)(void *cache_implementation, + const void *key, + svn_cache__partial_setter_func_t func, + void *baton, + apr_pool_t *scratch_pool); + + /* See svn_cache__get_info(). */ + svn_error_t *(*get_info)(void *cache_implementation, + svn_cache__info_t *info, + svn_boolean_t reset, + apr_pool_t *result_pool); +} svn_cache__vtable_t; + +struct svn_cache__t { + const svn_cache__vtable_t *vtable; + + /* See svn_cache__set_error_handler(). */ + svn_cache__error_handler_t error_handler; + void *error_baton; + + /* Private data for the cache implementation. */ + void *cache_internal; + + /* Total number of calls to getters. */ + apr_uint64_t reads; + + /* Total number of calls to set(). */ + apr_uint64_t writes; + + /* Total number of getter calls that returned a cached item. */ + apr_uint64_t hits; + + /* Total number of function calls that returned an error. */ + apr_uint64_t failures; +}; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_CACHE_H */ diff --git a/subversion/libsvn_subr/cache_config.c b/subversion/libsvn_subr/cache_config.c new file mode 100644 index 000000000000..86c12dc49dd8 --- /dev/null +++ b/subversion/libsvn_subr/cache_config.c @@ -0,0 +1,169 @@ +/* svn_cache_config.c : configuration of internal caches + * + * ==================================================================== + * 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_atomic.h> + +#include "svn_cache_config.h" +#include "private/svn_cache.h" + +#include "svn_pools.h" + +/* The cache settings as a process-wide singleton. + */ +static svn_cache_config_t cache_settings = + { + /* default configuration: + * + * Please note that the resources listed below will be allocated + * PER PROCESS. Thus, the defaults chosen here are kept deliberately + * low to still make a difference yet to ensure that pre-fork servers + * on machines with small amounts of RAM aren't severely impacted. + */ + 0x1000000, /* 16 MB for caches. + * If you are running a single server process, + * you may easily increase that to 50+% of your RAM + * using svn_fs_set_cache_config(). + */ + 16, /* up to 16 files kept open. + * Most OS restrict the number of open file handles to + * about 1000. To minimize I/O and OS overhead, values + * of 500+ can be beneficial (use svn_fs_set_cache_config() + * to change the configuration). + * When running with a huge in-process cache, this number + * has little impact on performance and a more modest + * value (< 100) may be more suitable. + */ +#if APR_HAS_THREADS + FALSE /* assume multi-threaded operation. + * Because this simply activates proper synchronization + * between threads, it is a safe default. + */ +#else + TRUE /* single-threaded is the only supported mode of operation */ +#endif +}; + +/* Get the current FSFS cache configuration. */ +const svn_cache_config_t * +svn_cache_config_get(void) +{ + return &cache_settings; +} + +/* Access the process-global (singleton) membuffer cache. The first call + * will automatically allocate the cache using the current cache config. + * NULL will be returned if the desired cache size is 0 or if the cache + * could not be created for some reason. + */ +svn_membuffer_t * +svn_cache__get_global_membuffer_cache(void) +{ + static svn_membuffer_t * volatile cache = NULL; + + apr_uint64_t cache_size = cache_settings.cache_size; + if (!cache && cache_size) + { + svn_error_t *err; + + svn_membuffer_t *old_cache = NULL; + svn_membuffer_t *new_cache = NULL; + + /* auto-allocate cache */ + apr_allocator_t *allocator = NULL; + apr_pool_t *pool = NULL; + + if (apr_allocator_create(&allocator)) + return NULL; + + /* Ensure that we free partially allocated data if we run OOM + * before the cache is complete: If the cache cannot be allocated + * in its full size, the create() function will clear the pool + * explicitly. The allocator will make sure that any memory no + * longer used by the pool will actually be returned to the OS. + * + * Please note that this pool and allocator is used *only* to + * allocate the large membuffer. All later dynamic allocations + * come from other, temporary pools and allocators. + */ + apr_allocator_max_free_set(allocator, 1); + + /* don't terminate upon OOM but make pool return a NULL pointer + * instead so we can disable caching gracefully and continue + * operation without membuffer caches. + */ + apr_pool_create_ex(&pool, NULL, NULL, allocator); + if (pool == NULL) + return NULL; + apr_allocator_owner_set(allocator, pool); + + err = svn_cache__membuffer_cache_create( + &new_cache, + (apr_size_t)cache_size, + (apr_size_t)(cache_size / 10), + 0, + ! svn_cache_config_get()->single_threaded, + FALSE, + pool); + + /* Some error occurred. Most likely it's an OOM error but we don't + * really care. Simply release all cache memory and disable caching + */ + if (err) + { + /* Memory and error cleanup */ + svn_error_clear(err); + svn_pool_destroy(pool); + + /* Prevent future attempts to create the cache. However, an + * existing cache instance (see next comment) remains valid. + */ + cache_settings.cache_size = 0; + + /* The current caller won't get the cache object. + * However, a concurrent call might have succeeded in creating + * the cache object. That call and all following ones will then + * use the successfully created cache instance. + */ + return NULL; + } + + /* Handle race condition: if we are the first to create a + * cache object, make it our global singleton. Otherwise, + * discard the new cache and keep the existing one. + * + * Cast is necessary because of APR bug: + * https://issues.apache.org/bugzilla/show_bug.cgi?id=50731 + */ + old_cache = apr_atomic_casptr((volatile void **)&cache, new_cache, NULL); + if (old_cache != NULL) + svn_pool_destroy(pool); + } + + return cache; +} + +void +svn_cache_config_set(const svn_cache_config_t *settings) +{ + cache_settings = *settings; +} + diff --git a/subversion/libsvn_subr/checksum.c b/subversion/libsvn_subr/checksum.c new file mode 100644 index 000000000000..e5d6a620ec9d --- /dev/null +++ b/subversion/libsvn_subr/checksum.c @@ -0,0 +1,500 @@ +/* + * checksum.c: checksum routines + * + * ==================================================================== + * 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 <ctype.h> + +#include <apr_md5.h> +#include <apr_sha1.h> + +#include "svn_checksum.h" +#include "svn_error.h" +#include "svn_ctype.h" + +#include "sha1.h" +#include "md5.h" + +#include "private/svn_subr_private.h" + +#include "svn_private_config.h" + + + +/* Returns the digest size of it's argument. */ +#define DIGESTSIZE(k) ((k) == svn_checksum_md5 ? APR_MD5_DIGESTSIZE : \ + (k) == svn_checksum_sha1 ? APR_SHA1_DIGESTSIZE : 0) + + +/* Check to see if KIND is something we recognize. If not, return + * SVN_ERR_BAD_CHECKSUM_KIND */ +static svn_error_t * +validate_kind(svn_checksum_kind_t kind) +{ + if (kind == svn_checksum_md5 || kind == svn_checksum_sha1) + return SVN_NO_ERROR; + else + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); +} + +/* Create a svn_checksum_t with everything but the contents of the + digest populated. */ +static svn_checksum_t * +checksum_create_without_digest(svn_checksum_kind_t kind, + apr_size_t digest_size, + apr_pool_t *pool) +{ + /* Use apr_palloc() instead of apr_pcalloc() so that the digest + * contents are only set once by the caller. */ + svn_checksum_t *checksum = apr_palloc(pool, sizeof(*checksum) + digest_size); + checksum->digest = (unsigned char *)checksum + sizeof(*checksum); + checksum->kind = kind; + return checksum; +} + +static svn_checksum_t * +checksum_create(svn_checksum_kind_t kind, + apr_size_t digest_size, + const unsigned char *digest, + apr_pool_t *pool) +{ + svn_checksum_t *checksum = checksum_create_without_digest(kind, digest_size, + pool); + memcpy((unsigned char *)checksum->digest, digest, digest_size); + return checksum; +} + +svn_checksum_t * +svn_checksum_create(svn_checksum_kind_t kind, + apr_pool_t *pool) +{ + svn_checksum_t *checksum; + apr_size_t digest_size; + + switch (kind) + { + case svn_checksum_md5: + digest_size = APR_MD5_DIGESTSIZE; + break; + case svn_checksum_sha1: + digest_size = APR_SHA1_DIGESTSIZE; + break; + default: + return NULL; + } + + checksum = checksum_create_without_digest(kind, digest_size, pool); + memset((unsigned char *) checksum->digest, 0, digest_size); + return checksum; +} + +svn_checksum_t * +svn_checksum__from_digest_md5(const unsigned char *digest, + apr_pool_t *result_pool) +{ + return checksum_create(svn_checksum_md5, APR_MD5_DIGESTSIZE, digest, + result_pool); +} + +svn_checksum_t * +svn_checksum__from_digest_sha1(const unsigned char *digest, + apr_pool_t *result_pool) +{ + return checksum_create(svn_checksum_sha1, APR_SHA1_DIGESTSIZE, digest, + result_pool); +} + +svn_error_t * +svn_checksum_clear(svn_checksum_t *checksum) +{ + SVN_ERR(validate_kind(checksum->kind)); + + memset((unsigned char *) checksum->digest, 0, DIGESTSIZE(checksum->kind)); + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_checksum_match(const svn_checksum_t *checksum1, + const svn_checksum_t *checksum2) +{ + if (checksum1 == NULL || checksum2 == NULL) + return TRUE; + + if (checksum1->kind != checksum2->kind) + return FALSE; + + switch (checksum1->kind) + { + case svn_checksum_md5: + return svn_md5__digests_match(checksum1->digest, checksum2->digest); + case svn_checksum_sha1: + return svn_sha1__digests_match(checksum1->digest, checksum2->digest); + default: + /* We really shouldn't get here, but if we do... */ + return FALSE; + } +} + +const char * +svn_checksum_to_cstring_display(const svn_checksum_t *checksum, + apr_pool_t *pool) +{ + switch (checksum->kind) + { + case svn_checksum_md5: + return svn_md5__digest_to_cstring_display(checksum->digest, pool); + case svn_checksum_sha1: + return svn_sha1__digest_to_cstring_display(checksum->digest, pool); + default: + /* We really shouldn't get here, but if we do... */ + return NULL; + } +} + +const char * +svn_checksum_to_cstring(const svn_checksum_t *checksum, + apr_pool_t *pool) +{ + if (checksum == NULL) + return NULL; + + switch (checksum->kind) + { + case svn_checksum_md5: + return svn_md5__digest_to_cstring(checksum->digest, pool); + case svn_checksum_sha1: + return svn_sha1__digest_to_cstring(checksum->digest, pool); + default: + /* We really shouldn't get here, but if we do... */ + return NULL; + } +} + + +const char * +svn_checksum_serialize(const svn_checksum_t *checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *ckind_str; + + SVN_ERR_ASSERT_NO_RETURN(checksum->kind == svn_checksum_md5 + || checksum->kind == svn_checksum_sha1); + ckind_str = (checksum->kind == svn_checksum_md5 ? "$md5 $" : "$sha1$"); + return apr_pstrcat(result_pool, + ckind_str, + svn_checksum_to_cstring(checksum, scratch_pool), + (char *)NULL); +} + + +svn_error_t * +svn_checksum_deserialize(const svn_checksum_t **checksum, + const char *data, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_checksum_kind_t ckind; + svn_checksum_t *parsed_checksum; + + /* "$md5 $..." or "$sha1$..." */ + SVN_ERR_ASSERT(strlen(data) > 6); + + ckind = (data[1] == 'm' ? svn_checksum_md5 : svn_checksum_sha1); + SVN_ERR(svn_checksum_parse_hex(&parsed_checksum, ckind, + data + 6, result_pool)); + *checksum = parsed_checksum; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_checksum_parse_hex(svn_checksum_t **checksum, + svn_checksum_kind_t kind, + const char *hex, + apr_pool_t *pool) +{ + int i, len; + char is_nonzero = '\0'; + char *digest; + static const char xdigitval[256] = + { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, /* 0-9 */ + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A-F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* a-f */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + + if (hex == NULL) + { + *checksum = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(validate_kind(kind)); + + *checksum = svn_checksum_create(kind, pool); + digest = (char *)(*checksum)->digest; + len = DIGESTSIZE(kind); + + for (i = 0; i < len; i++) + { + char x1 = xdigitval[(unsigned char)hex[i * 2]]; + char x2 = xdigitval[(unsigned char)hex[i * 2 + 1]]; + if (x1 == (char)-1 || x2 == (char)-1) + return svn_error_create(SVN_ERR_BAD_CHECKSUM_PARSE, NULL, NULL); + + digest[i] = (char)((x1 << 4) | x2); + is_nonzero |= (char)((x1 << 4) | x2); + } + + if (!is_nonzero) + *checksum = NULL; + + return SVN_NO_ERROR; +} + +svn_checksum_t * +svn_checksum_dup(const svn_checksum_t *checksum, + apr_pool_t *pool) +{ + /* The duplicate of a NULL checksum is a NULL... */ + if (checksum == NULL) + return NULL; + + /* Without this check on valid checksum kind a NULL svn_checksum_t + * pointer is returned which could cause a core dump at an + * indeterminate time in the future because callers are not + * expecting a NULL pointer. This commit forces an early abort() so + * it's easier to track down where the issue arose. */ + switch (checksum->kind) + { + case svn_checksum_md5: + return svn_checksum__from_digest_md5(checksum->digest, pool); + break; + case svn_checksum_sha1: + return svn_checksum__from_digest_sha1(checksum->digest, pool); + break; + default: + SVN_ERR_MALFUNCTION_NO_RETURN(); + break; + } +} + +svn_error_t * +svn_checksum(svn_checksum_t **checksum, + svn_checksum_kind_t kind, + const void *data, + apr_size_t len, + apr_pool_t *pool) +{ + apr_sha1_ctx_t sha1_ctx; + + SVN_ERR(validate_kind(kind)); + *checksum = svn_checksum_create(kind, pool); + + switch (kind) + { + case svn_checksum_md5: + apr_md5((unsigned char *)(*checksum)->digest, data, len); + break; + + case svn_checksum_sha1: + apr_sha1_init(&sha1_ctx); + apr_sha1_update(&sha1_ctx, data, (unsigned int)len); + apr_sha1_final((unsigned char *)(*checksum)->digest, &sha1_ctx); + break; + + default: + /* We really shouldn't get here, but if we do... */ + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); + } + + return SVN_NO_ERROR; +} + + +svn_checksum_t * +svn_checksum_empty_checksum(svn_checksum_kind_t kind, + apr_pool_t *pool) +{ + switch (kind) + { + case svn_checksum_md5: + return svn_checksum__from_digest_md5(svn_md5__empty_string_digest(), + pool); + + case svn_checksum_sha1: + return svn_checksum__from_digest_sha1(svn_sha1__empty_string_digest(), + pool); + + default: + /* We really shouldn't get here, but if we do... */ + SVN_ERR_MALFUNCTION_NO_RETURN(); + } +} + +struct svn_checksum_ctx_t +{ + void *apr_ctx; + svn_checksum_kind_t kind; +}; + +svn_checksum_ctx_t * +svn_checksum_ctx_create(svn_checksum_kind_t kind, + apr_pool_t *pool) +{ + svn_checksum_ctx_t *ctx = apr_palloc(pool, sizeof(*ctx)); + + ctx->kind = kind; + switch (kind) + { + case svn_checksum_md5: + ctx->apr_ctx = apr_palloc(pool, sizeof(apr_md5_ctx_t)); + apr_md5_init(ctx->apr_ctx); + break; + + case svn_checksum_sha1: + ctx->apr_ctx = apr_palloc(pool, sizeof(apr_sha1_ctx_t)); + apr_sha1_init(ctx->apr_ctx); + break; + + default: + SVN_ERR_MALFUNCTION_NO_RETURN(); + } + + return ctx; +} + +svn_error_t * +svn_checksum_update(svn_checksum_ctx_t *ctx, + const void *data, + apr_size_t len) +{ + switch (ctx->kind) + { + case svn_checksum_md5: + apr_md5_update(ctx->apr_ctx, data, len); + break; + + case svn_checksum_sha1: + apr_sha1_update(ctx->apr_ctx, data, (unsigned int)len); + break; + + default: + /* We really shouldn't get here, but if we do... */ + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_checksum_final(svn_checksum_t **checksum, + const svn_checksum_ctx_t *ctx, + apr_pool_t *pool) +{ + *checksum = svn_checksum_create(ctx->kind, pool); + + switch (ctx->kind) + { + case svn_checksum_md5: + apr_md5_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx); + break; + + case svn_checksum_sha1: + apr_sha1_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx); + break; + + default: + /* We really shouldn't get here, but if we do... */ + return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); + } + + return SVN_NO_ERROR; +} + +apr_size_t +svn_checksum_size(const svn_checksum_t *checksum) +{ + return DIGESTSIZE(checksum->kind); +} + +svn_error_t * +svn_checksum_mismatch_err(const svn_checksum_t *expected, + const svn_checksum_t *actual, + apr_pool_t *scratch_pool, + const char *fmt, + ...) +{ + va_list ap; + const char *desc; + + va_start(ap, fmt); + desc = apr_pvsprintf(scratch_pool, fmt, ap); + va_end(ap); + + return svn_error_createf(SVN_ERR_CHECKSUM_MISMATCH, NULL, + _("%s:\n" + " expected: %s\n" + " actual: %s\n"), + desc, + svn_checksum_to_cstring_display(expected, scratch_pool), + svn_checksum_to_cstring_display(actual, scratch_pool)); +} + +svn_boolean_t +svn_checksum_is_empty_checksum(svn_checksum_t *checksum) +{ + /* By definition, the NULL checksum matches all others, including the + empty one. */ + if (!checksum) + return TRUE; + + switch (checksum->kind) + { + case svn_checksum_md5: + return svn_md5__digests_match(checksum->digest, + svn_md5__empty_string_digest()); + + case svn_checksum_sha1: + return svn_sha1__digests_match(checksum->digest, + svn_sha1__empty_string_digest()); + + default: + /* We really shouldn't get here, but if we do... */ + SVN_ERR_MALFUNCTION_NO_RETURN(); + } +} diff --git a/subversion/libsvn_subr/cmdline.c b/subversion/libsvn_subr/cmdline.c new file mode 100644 index 000000000000..8484c96a3747 --- /dev/null +++ b/subversion/libsvn_subr/cmdline.c @@ -0,0 +1,1312 @@ +/* + * cmdline.c : Helpers for command-line programs. + * + * ==================================================================== + * 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 <stdlib.h> /* for atexit() */ +#include <stdio.h> /* for setvbuf() */ +#include <locale.h> /* for setlocale() */ + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#else +#include <crtdbg.h> +#include <io.h> +#endif + +#include <apr.h> /* for STDIN_FILENO */ +#include <apr_errno.h> /* for apr_strerror */ +#include <apr_general.h> /* for apr_initialize/apr_terminate */ +#include <apr_strings.h> /* for apr_snprintf */ +#include <apr_pools.h> + +#include "svn_cmdline.h" +#include "svn_ctype.h" +#include "svn_dso.h" +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_nls.h" +#include "svn_utf.h" +#include "svn_auth.h" +#include "svn_xml.h" +#include "svn_base64.h" +#include "svn_config.h" +#include "svn_sorts.h" +#include "svn_props.h" +#include "svn_subst.h" + +#include "private/svn_cmdline_private.h" +#include "private/svn_utf_private.h" +#include "private/svn_string_private.h" + +#include "svn_private_config.h" + +#include "win32_crashrpt.h" + +/* The stdin encoding. If null, it's the same as the native encoding. */ +static const char *input_encoding = NULL; + +/* The stdout encoding. If null, it's the same as the native encoding. */ +static const char *output_encoding = NULL; + + +int +svn_cmdline_init(const char *progname, FILE *error_stream) +{ + apr_status_t status; + apr_pool_t *pool; + svn_error_t *err; + char prefix_buf[64]; /* 64 is probably bigger than most program names */ + +#ifndef WIN32 + { + struct stat st; + + /* The following makes sure that file descriptors 0 (stdin), 1 + (stdout) and 2 (stderr) will not be "reused", because if + e.g. file descriptor 2 would be reused when opening a file, a + write to stderr would write to that file and most likely + corrupt it. */ + if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) || + (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) || + (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1)) + { + if (error_stream) + fprintf(error_stream, "%s: error: cannot open '/dev/null'\n", + progname); + return EXIT_FAILURE; + } + } +#endif + + /* Ignore any errors encountered while attempting to change stream + buffering, as the streams should retain their default buffering + modes. */ + if (error_stream) + setvbuf(error_stream, NULL, _IONBF, 0); +#ifndef WIN32 + setvbuf(stdout, NULL, _IOLBF, 0); +#endif + +#ifdef WIN32 +#if _MSC_VER < 1400 + /* Initialize the input and output encodings. */ + { + static char input_encoding_buffer[16]; + static char output_encoding_buffer[16]; + + apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer, + "CP%u", (unsigned) GetConsoleCP()); + input_encoding = input_encoding_buffer; + + apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer, + "CP%u", (unsigned) GetConsoleOutputCP()); + output_encoding = output_encoding_buffer; + } +#endif /* _MSC_VER < 1400 */ + +#ifdef SVN_USE_WIN32_CRASHHANDLER + /* Attach (but don't load) the crash handler */ + SetUnhandledExceptionFilter(svn__unhandled_exception_filter); + +#if _MSC_VER >= 1400 + /* ### This should work for VC++ 2002 (=1300) and later */ + /* Show the abort message on STDERR instead of a dialog to allow + scripts (e.g. our testsuite) to continue after an abort without + user intervention. Allow overriding for easier debugging. */ + if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT")) + { + /* In release mode: Redirect abort() errors to stderr */ + _set_error_mode(_OUT_TO_STDERR); + + /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr. + (Ignored in release builds) */ + _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + } +#endif /* _MSC_VER >= 1400 */ + +#endif /* SVN_USE_WIN32_CRASHHANDLER */ + +#endif /* WIN32 */ + + /* C programs default to the "C" locale. But because svn is supposed + to be i18n-aware, it should inherit the default locale of its + environment. */ + if (!setlocale(LC_ALL, "") + && !setlocale(LC_CTYPE, "")) + { + if (error_stream) + { + const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL }; + const char **env_var = &env_vars[0], *env_val = NULL; + while (*env_var) + { + env_val = getenv(*env_var); + if (env_val && env_val[0]) + break; + ++env_var; + } + + if (!*env_var) + { + /* Unlikely. Can setlocale fail if no env vars are set? */ + --env_var; + env_val = "not set"; + } + + fprintf(error_stream, + "%s: warning: cannot set LC_CTYPE locale\n" + "%s: warning: environment variable %s is %s\n" + "%s: warning: please check that your locale name is correct\n", + progname, progname, *env_var, env_val, progname); + } + } + + /* Initialize the APR subsystem, and register an atexit() function + to Uninitialize that subsystem at program exit. */ + status = apr_initialize(); + if (status) + { + if (error_stream) + { + char buf[1024]; + apr_strerror(status, buf, sizeof(buf) - 1); + fprintf(error_stream, + "%s: error: cannot initialize APR: %s\n", + progname, buf); + } + return EXIT_FAILURE; + } + + strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3); + prefix_buf[sizeof(prefix_buf) - 3] = '\0'; + strcat(prefix_buf, ": "); + + /* DSO pool must be created before any other pools used by the + application so that pool cleanup doesn't unload DSOs too + early. See docstring of svn_dso_initialize2(). */ + if ((err = svn_dso_initialize2())) + { + if (error_stream) + svn_handle_error2(err, error_stream, TRUE, prefix_buf); + + svn_error_clear(err); + return EXIT_FAILURE; + } + + if (0 > atexit(apr_terminate)) + { + if (error_stream) + fprintf(error_stream, + "%s: error: atexit registration failed\n", + progname); + return EXIT_FAILURE; + } + + /* Create a pool for use by the UTF-8 routines. It will be cleaned + up by APR at exit time. */ + pool = svn_pool_create(NULL); + svn_utf_initialize2(FALSE, pool); + + if ((err = svn_nls_init())) + { + if (error_stream) + svn_handle_error2(err, error_stream, TRUE, prefix_buf); + + svn_error_clear(err); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + + +svn_error_t * +svn_cmdline_cstring_from_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + if (output_encoding == NULL) + return svn_utf_cstring_from_utf8(dest, src, pool); + else + return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool); +} + + +const char * +svn_cmdline_cstring_from_utf8_fuzzy(const char *src, + apr_pool_t *pool) +{ + return svn_utf__cstring_from_utf8_fuzzy(src, pool, + svn_cmdline_cstring_from_utf8); +} + + +svn_error_t * +svn_cmdline_cstring_to_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + if (input_encoding == NULL) + return svn_utf_cstring_to_utf8(dest, src, pool); + else + return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool); +} + + +svn_error_t * +svn_cmdline_path_local_style_from_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + return svn_cmdline_cstring_from_utf8(dest, + svn_dirent_local_style(src, pool), + pool); +} + +svn_error_t * +svn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...) +{ + const char *message; + va_list ap; + + /* A note about encoding issues: + * APR uses the execution character set, but here we give it UTF-8 strings, + * both the fmt argument and any other string arguments. Since apr_pvsprintf + * only cares about and produces ASCII characters, this works under the + * assumption that all supported platforms use an execution character set + * with ASCII as a subset. + */ + + va_start(ap, fmt); + message = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + + return svn_cmdline_fputs(message, stdout, pool); +} + +svn_error_t * +svn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...) +{ + const char *message; + va_list ap; + + /* See svn_cmdline_printf () for a note about character encoding issues. */ + + va_start(ap, fmt); + message = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + + return svn_cmdline_fputs(message, stream, pool); +} + +svn_error_t * +svn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool) +{ + svn_error_t *err; + const char *out; + + err = svn_cmdline_cstring_from_utf8(&out, string, pool); + + if (err) + { + svn_error_clear(err); + out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool); + } + + /* On POSIX systems, errno will be set on an error in fputs, but this might + not be the case on other platforms. We reset errno and only + use it if it was set by the below fputs call. Else, we just return + a generic error. */ + errno = 0; + + if (fputs(out, stream) == EOF) + { + if (apr_get_os_error()) /* is errno on POSIX */ + { + /* ### Issue #3014: Return a specific error for broken pipes, + * ### with a single element in the error chain. */ + if (APR_STATUS_IS_EPIPE(apr_get_os_error())) + return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); + else + return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); + } + else + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline_fflush(FILE *stream) +{ + /* See comment in svn_cmdline_fputs about use of errno and stdio. */ + errno = 0; + if (fflush(stream) == EOF) + { + if (apr_get_os_error()) /* is errno on POSIX */ + { + /* ### Issue #3014: Return a specific error for broken pipes, + * ### with a single element in the error chain. */ + if (APR_STATUS_IS_EPIPE(apr_get_os_error())) + return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); + else + return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); + } + else + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); + } + + return SVN_NO_ERROR; +} + +const char *svn_cmdline_output_encoding(apr_pool_t *pool) +{ + if (output_encoding) + return apr_pstrdup(pool, output_encoding); + else + return SVN_APR_LOCALE_CHARSET; +} + +int +svn_cmdline_handle_exit_error(svn_error_t *err, + apr_pool_t *pool, + const char *prefix) +{ + /* Issue #3014: + * Don't print anything on broken pipes. The pipe was likely + * closed by the process at the other end. We expect that + * process to perform error reporting as necessary. + * + * ### This assumes that there is only one error in a chain for + * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ + if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) + svn_handle_error2(err, stderr, FALSE, prefix); + svn_error_clear(err); + if (pool) + svn_pool_destroy(pool); + return EXIT_FAILURE; +} + +/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. + + Don't actually prompt. Instead, set *CRED_P to valid credentials + iff FAILURES is empty or is exactly SVN_AUTH_SSL_UNKNOWNCA. If + there are any other failure bits, then set *CRED_P to null (that + is, reject the cert). + + Ignore MAY_SAVE; we don't save certs we never prompted for. + + Ignore BATON, REALM, and CERT_INFO, + + Ignore any further films by George Lucas. */ +static svn_error_t * +ssl_trust_unknown_server_cert + (svn_auth_cred_ssl_server_trust_t **cred_p, + void *baton, + const char *realm, + apr_uint32_t failures, + const svn_auth_ssl_server_cert_info_t *cert_info, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + *cred_p = NULL; + + if (failures == 0 || failures == SVN_AUTH_SSL_UNKNOWNCA) + { + *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); + (*cred_p)->may_save = FALSE; + (*cred_p)->accepted_failures = failures; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline_create_auth_baton(svn_auth_baton_t **ab, + svn_boolean_t non_interactive, + const char *auth_username, + const char *auth_password, + const char *config_dir, + svn_boolean_t no_auth_cache, + svn_boolean_t trust_server_cert, + svn_config_t *cfg, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_boolean_t store_password_val = TRUE; + svn_boolean_t store_auth_creds_val = TRUE; + svn_auth_provider_object_t *provider; + svn_cmdline_prompt_baton2_t *pb = NULL; + + /* The whole list of registered providers */ + apr_array_header_t *providers; + + /* Populate the registered providers with the platform-specific providers */ + SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers, + cfg, pool)); + + /* If we have a cancellation function, cram it and the stuff it + needs into the prompt baton. */ + if (cancel_func) + { + pb = apr_palloc(pool, sizeof(*pb)); + pb->cancel_func = cancel_func; + pb->cancel_baton = cancel_baton; + pb->config_dir = config_dir; + } + + if (!non_interactive) + { + /* This provider doesn't prompt the user in order to get creds; + it prompts the user regarding the caching of creds. */ + svn_auth_get_simple_provider2(&provider, + svn_cmdline_auth_plaintext_prompt, + pb, pool); + } + else + { + svn_auth_get_simple_provider2(&provider, NULL, NULL, pool); + } + + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + svn_auth_get_username_provider(&provider, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + /* The server-cert, client-cert, and client-cert-password providers. */ + SVN_ERR(svn_auth_get_platform_specific_provider(&provider, + "windows", + "ssl_server_trust", + pool)); + + if (provider) + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + svn_auth_get_ssl_server_trust_file_provider(&provider, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + svn_auth_get_ssl_client_cert_file_provider(&provider, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + if (!non_interactive) + { + /* This provider doesn't prompt the user in order to get creds; + it prompts the user regarding the caching of creds. */ + svn_auth_get_ssl_client_cert_pw_file_provider2 + (&provider, svn_cmdline_auth_plaintext_passphrase_prompt, + pb, pool); + } + else + { + svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL, + pool); + } + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + if (!non_interactive) + { + svn_boolean_t ssl_client_cert_file_prompt; + + SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT, + FALSE)); + + /* Two basic prompt providers: username/password, and just username. */ + svn_auth_get_simple_prompt_provider(&provider, + svn_cmdline_auth_simple_prompt, + pb, + 2, /* retry limit */ + pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + svn_auth_get_username_prompt_provider + (&provider, svn_cmdline_auth_username_prompt, pb, + 2, /* retry limit */ pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + /* SSL prompt providers: server-certs and client-cert-passphrases. */ + svn_auth_get_ssl_server_trust_prompt_provider + (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + svn_auth_get_ssl_client_cert_pw_prompt_provider + (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + + /* If configuration allows, add a provider for client-cert path + prompting, too. */ + if (ssl_client_cert_file_prompt) + { + svn_auth_get_ssl_client_cert_prompt_provider + (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + } + } + else if (trust_server_cert) + { + /* Remember, only register this provider if non_interactive. */ + svn_auth_get_ssl_server_trust_prompt_provider + (&provider, ssl_trust_unknown_server_cert, NULL, pool); + APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider; + } + + /* Build an authentication baton to give to libsvn_client. */ + svn_auth_open(ab, providers, pool); + + /* Place any default --username or --password credentials into the + auth_baton's run-time parameter hash. */ + if (auth_username) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME, + auth_username); + if (auth_password) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD, + auth_password); + + /* Same with the --non-interactive option. */ + if (non_interactive) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, ""); + + if (config_dir) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR, + config_dir); + + /* Determine whether storing passwords in any form is allowed. + * This is the deprecated location for this option, the new + * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may + * override the value we set here. */ + SVN_ERR(svn_config_get_bool(cfg, &store_password_val, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_STORE_PASSWORDS, + SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS)); + + if (! store_password_val) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, ""); + + /* Determine whether we are allowed to write to the auth/ area. + * This is the deprecated location for this option, the new + * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may + * override the value we set here. */ + SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val, + SVN_CONFIG_SECTION_AUTH, + SVN_CONFIG_OPTION_STORE_AUTH_CREDS, + SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS)); + + if (no_auth_cache || ! store_auth_creds_val) + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, ""); + +#ifdef SVN_HAVE_GNOME_KEYRING + svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC, + &svn_cmdline__auth_gnome_keyring_unlock_prompt); +#endif /* SVN_HAVE_GNOME_KEYRING */ + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline__getopt_init(apr_getopt_t **os, + int argc, + const char *argv[], + apr_pool_t *pool) +{ + apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Error initializing command line arguments")); + return SVN_NO_ERROR; +} + + +void +svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr, + const char* propname, + svn_string_t *propval, + svn_boolean_t inherited_prop, + apr_pool_t *pool) +{ + const char *xml_safe; + const char *encoding = NULL; + + if (*outstr == NULL) + *outstr = svn_stringbuf_create_empty(pool); + + if (svn_xml_is_xml_safe(propval->data, propval->len)) + { + svn_stringbuf_t *xml_esc = NULL; + svn_xml_escape_cdata_string(&xml_esc, propval, pool); + xml_safe = xml_esc->data; + } + else + { + const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE, + pool); + encoding = "base64"; + xml_safe = base64ed->data; + } + + if (encoding) + svn_xml_make_open_tag( + outstr, pool, svn_xml_protect_pcdata, + inherited_prop ? "inherited_property" : "property", + "name", propname, + "encoding", encoding, NULL); + else + svn_xml_make_open_tag( + outstr, pool, svn_xml_protect_pcdata, + inherited_prop ? "inherited_property" : "property", + "name", propname, NULL); + + svn_stringbuf_appendcstr(*outstr, xml_safe); + + svn_xml_make_close_tag( + outstr, pool, + inherited_prop ? "inherited_property" : "property"); + + return; +} + +svn_error_t * +svn_cmdline__parse_config_option(apr_array_header_t *config_options, + const char *opt_arg, + apr_pool_t *pool) +{ + svn_cmdline__config_argument_t *config_option; + const char *first_colon, *second_colon, *equals_sign; + apr_size_t len = strlen(opt_arg); + if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg)) + { + if ((second_colon = strchr(first_colon + 1, ':')) && + (second_colon != first_colon + 1)) + { + if ((equals_sign = strchr(second_colon + 1, '=')) && + (equals_sign != second_colon + 1)) + { + config_option = apr_pcalloc(pool, sizeof(*config_option)); + config_option->file = apr_pstrndup(pool, opt_arg, + first_colon - opt_arg); + config_option->section = apr_pstrndup(pool, first_colon + 1, + second_colon - first_colon - 1); + config_option->option = apr_pstrndup(pool, second_colon + 1, + equals_sign - second_colon - 1); + + if (! (strchr(config_option->option, ':'))) + { + config_option->value = apr_pstrndup(pool, equals_sign + 1, + opt_arg + len - equals_sign - 1); + APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *) + = config_option; + return SVN_NO_ERROR; + } + } + } + } + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Invalid syntax of argument of --config-option")); +} + +svn_error_t * +svn_cmdline__apply_config_options(apr_hash_t *config, + const apr_array_header_t *config_options, + const char *prefix, + const char *argument_name) +{ + int i; + + for (i = 0; i < config_options->nelts; i++) + { + svn_config_t *cfg; + svn_cmdline__config_argument_t *arg = + APR_ARRAY_IDX(config_options, i, + svn_cmdline__config_argument_t *); + + cfg = svn_hash_gets(config, arg->file); + + if (cfg) + { + svn_config_set(cfg, arg->section, arg->option, arg->value); + } + else + { + svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Unrecognized file in argument of %s"), argument_name); + + svn_handle_warning2(stderr, err, prefix); + svn_error_clear(err); + } + } + + return SVN_NO_ERROR; +} + +/* Return a copy, allocated in POOL, of the next line of text from *STR + * up to and including a CR and/or an LF. Change *STR to point to the + * remainder of the string after the returned part. If there are no + * characters to be returned, return NULL; never return an empty string. + */ +static const char * +next_line(const char **str, apr_pool_t *pool) +{ + const char *start = *str; + const char *p = *str; + + /* n.b. Throughout this fn, we never read any character after a '\0'. */ + /* Skip over all non-EOL characters, if any. */ + while (*p != '\r' && *p != '\n' && *p != '\0') + p++; + /* Skip over \r\n or \n\r or \r or \n, if any. */ + if (*p == '\r' || *p == '\n') + { + char c = *p++; + + if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r')) + p++; + } + + /* Now p points after at most one '\n' and/or '\r'. */ + *str = p; + + if (p == start) + return NULL; + + return svn_string_ncreate(start, p - start, pool)->data; +} + +const char * +svn_cmdline__indent_string(const char *str, + const char *indent, + apr_pool_t *pool) +{ + svn_stringbuf_t *out = svn_stringbuf_create_empty(pool); + const char *line; + + while ((line = next_line(&str, pool))) + { + svn_stringbuf_appendcstr(out, indent); + svn_stringbuf_appendcstr(out, line); + } + return out->data; +} + +svn_error_t * +svn_cmdline__print_prop_hash(svn_stream_t *out, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + + sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, + pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *pname = item.key; + svn_string_t *propval = item.value; + const char *pname_stdout; + + if (svn_prop_needs_translation(pname)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, pool)); + + SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool)); + + if (out) + { + pname_stdout = apr_psprintf(pool, " %s\n", pname_stdout); + SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout, + APR_EOL_STR, /* 'native' eol */ + FALSE, /* no repair */ + NULL, /* no keywords */ + FALSE, /* no expansion */ + pool)); + + SVN_ERR(svn_stream_puts(out, pname_stdout)); + } + else + { + /* ### We leave these printfs for now, since if propval wasn't + translated above, we don't know anything about its encoding. + In fact, it might be binary data... */ + printf(" %s\n", pname_stdout); + } + + if (!names_only) + { + /* Add an extra newline to the value before indenting, so that + * every line of output has the indentation whether the value + * already ended in a newline or not. */ + const char *newval = apr_psprintf(pool, "%s\n", propval->data); + const char *indented_newval = svn_cmdline__indent_string(newval, + " ", + pool); + if (out) + { + SVN_ERR(svn_stream_puts(out, indented_newval)); + } + else + { + printf("%s", indented_newval); + } + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr, + apr_hash_t *prop_hash, + svn_boolean_t names_only, + svn_boolean_t inherited_props, + apr_pool_t *pool) +{ + apr_array_header_t *sorted_props; + int i; + + if (*outstr == NULL) + *outstr = svn_stringbuf_create_empty(pool); + + sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically, + pool); + for (i = 0; i < sorted_props->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); + const char *pname = item.key; + svn_string_t *propval = item.value; + + if (names_only) + { + svn_xml_make_open_tag( + outstr, pool, svn_xml_self_closing, + inherited_props ? "inherited_property" : "property", + "name", pname, NULL); + } + else + { + const char *pname_out; + + if (svn_prop_needs_translation(pname)) + SVN_ERR(svn_subst_detranslate_string(&propval, propval, + TRUE, pool)); + + SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool)); + + svn_cmdline__print_xml_prop(outstr, pname_out, propval, + inherited_props, pool); + } + } + + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_cmdline__be_interactive(svn_boolean_t non_interactive, + svn_boolean_t force_interactive) +{ + /* If neither --non-interactive nor --force-interactive was passed, + * be interactive if stdin is a terminal. + * If --force-interactive was passed, always be interactive. */ + if (!force_interactive && !non_interactive) + { +#ifdef WIN32 + return (_isatty(STDIN_FILENO) != 0); +#else + return (isatty(STDIN_FILENO) != 0); +#endif + } + else if (force_interactive) + return TRUE; + + return !non_interactive; +} + + +/* Helper for the next two functions. Set *EDITOR to some path to an + editor binary. Sources to search include: the EDITOR_CMD argument + (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG + is not NULL), $VISUAL, $EDITOR. Return + SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */ +static svn_error_t * +find_editor_binary(const char **editor, + const char *editor_cmd, + apr_hash_t *config) +{ + const char *e; + struct svn_config_t *cfg; + + /* Use the editor specified on the command line via --editor-cmd, if any. */ + e = editor_cmd; + + /* Otherwise look for the Subversion-specific environment variable. */ + if (! e) + e = getenv("SVN_EDITOR"); + + /* If not found then fall back on the config file. */ + if (! e) + { + cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; + svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_EDITOR_CMD, NULL); + } + + /* If not found yet then try general purpose environment variables. */ + if (! e) + e = getenv("VISUAL"); + if (! e) + e = getenv("EDITOR"); + +#ifdef SVN_CLIENT_EDITOR + /* If still not found then fall back on the hard-coded default. */ + if (! e) + e = SVN_CLIENT_EDITOR; +#endif + + /* Error if there is no editor specified */ + if (e) + { + const char *c; + + for (c = e; *c; c++) + if (!svn_ctype_isspace(*c)) + break; + + if (! *c) + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, + _("The EDITOR, SVN_EDITOR or VISUAL environment variable or " + "'editor-cmd' run-time configuration option is empty or " + "consists solely of whitespace. Expected a shell command.")); + } + else + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL, + _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are " + "set, and no 'editor-cmd' run-time configuration option was found")); + + *editor = e; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cmdline__edit_file_externally(const char *path, + const char *editor_cmd, + apr_hash_t *config, + apr_pool_t *pool) +{ + const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr; + char *old_cwd; + int sys_err; + apr_status_t apr_err; + + svn_dirent_split(&base_dir, &file_name, path, pool); + + SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + + apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get working directory")); + + /* APR doesn't like "" directories */ + if (base_dir[0] == '\0') + base_dir_apr = "."; + else + SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); + + apr_err = apr_filepath_set(base_dir_apr, pool); + if (apr_err) + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + + cmd = apr_psprintf(pool, "%s %s", editor, file_name); + sys_err = system(cmd); + + apr_err = apr_filepath_set(old_cwd, pool); + if (apr_err) + svn_handle_error2(svn_error_wrap_apr + (apr_err, _("Can't restore working directory")), + stderr, TRUE /* fatal */, "svn: "); + + if (sys_err) + /* Extracting any meaning from sys_err is platform specific, so just + use the raw value. */ + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("system('%s') returned %d"), cmd, sys_err); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, + const char **tmpfile_left /* UTF-8! */, + const char *editor_cmd, + const char *base_dir /* UTF-8! */, + const svn_string_t *contents /* UTF-8! */, + const char *filename, + apr_hash_t *config, + svn_boolean_t as_text, + const char *encoding, + apr_pool_t *pool) +{ + const char *editor; + const char *cmd; + apr_file_t *tmp_file; + const char *tmpfile_name; + const char *tmpfile_native; + const char *tmpfile_apr, *base_dir_apr; + svn_string_t *translated_contents; + apr_status_t apr_err, apr_err2; + apr_size_t written; + apr_finfo_t finfo_before, finfo_after; + svn_error_t *err = SVN_NO_ERROR, *err2; + char *old_cwd; + int sys_err; + svn_boolean_t remove_file = TRUE; + + SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + + /* Convert file contents from UTF-8/LF if desired. */ + if (as_text) + { + const char *translated; + SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated, + APR_EOL_STR, FALSE, + NULL, FALSE, pool)); + translated_contents = svn_string_create_empty(pool); + if (encoding) + SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data, + translated, encoding, pool)); + else + SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data, + translated, pool)); + translated_contents->len = strlen(translated_contents->data); + } + else + translated_contents = svn_string_dup(contents, pool); + + /* Move to BASE_DIR to avoid getting characters that need quoting + into tmpfile_name */ + apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get working directory")); + + /* APR doesn't like "" directories */ + if (base_dir[0] == '\0') + base_dir_apr = "."; + else + SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool)); + apr_err = apr_filepath_set(base_dir_apr, pool); + if (apr_err) + { + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + } + + /*** From here on, any problems that occur require us to cd back!! ***/ + + /* Ask the working copy for a temporary file named FILENAME-something. */ + err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, + "" /* dirpath */, + filename, + ".tmp", + svn_io_file_del_none, pool, pool); + + if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS)) + { + const char *temp_dir_apr; + + svn_error_clear(err); + + SVN_ERR(svn_io_temp_dir(&base_dir, pool)); + + SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool)); + apr_err = apr_filepath_set(temp_dir_apr, pool); + if (apr_err) + { + return svn_error_wrap_apr + (apr_err, _("Can't change working directory to '%s'"), base_dir); + } + + err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name, + "" /* dirpath */, + filename, + ".tmp", + svn_io_file_del_none, pool, pool); + } + + if (err) + goto cleanup2; + + /*** From here on, any problems that occur require us to cleanup + the file we just created!! ***/ + + /* Dump initial CONTENTS to TMP_FILE. */ + apr_err = apr_file_write_full(tmp_file, translated_contents->data, + translated_contents->len, &written); + + apr_err2 = apr_file_close(tmp_file); + if (! apr_err) + apr_err = apr_err2; + + /* Make sure the whole CONTENTS were written, else return an error. */ + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"), + tmpfile_name); + goto cleanup; + } + + err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool); + if (err) + goto cleanup; + + /* Get information about the temporary file before the user has + been allowed to edit its contents. */ + apr_err = apr_stat(&finfo_before, tmpfile_apr, + APR_FINFO_MTIME, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* Backdate the file a little bit in case the editor is very fast + and doesn't change the size. (Use two seconds, since some + filesystems have coarse granularity.) It's OK if this call + fails, so we don't check its return value.*/ + apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool); + + /* Stat it again to get the mtime we actually set. */ + apr_err = apr_stat(&finfo_before, tmpfile_apr, + APR_FINFO_MTIME | APR_FINFO_SIZE, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* Prepare the editor command line. */ + err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool); + if (err) + goto cleanup; + cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native); + + /* If the caller wants us to leave the file around, return the path + of the file we'll use, and make a note not to destroy it. */ + if (tmpfile_left) + { + *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool); + remove_file = FALSE; + } + + /* Now, run the editor command line. */ + sys_err = system(cmd); + if (sys_err != 0) + { + /* Extracting any meaning from sys_err is platform specific, so just + use the raw value. */ + err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("system('%s') returned %d"), cmd, sys_err); + goto cleanup; + } + + /* Get information about the temporary file after the assumed editing. */ + apr_err = apr_stat(&finfo_after, tmpfile_apr, + APR_FINFO_MTIME | APR_FINFO_SIZE, pool); + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name); + goto cleanup; + } + + /* If the file looks changed... */ + if ((finfo_before.mtime != finfo_after.mtime) || + (finfo_before.size != finfo_after.size)) + { + svn_stringbuf_t *edited_contents_s; + err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool); + if (err) + goto cleanup; + + *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s); + + /* Translate back to UTF8/LF if desired. */ + if (as_text) + { + err = svn_subst_translate_string2(edited_contents, FALSE, FALSE, + *edited_contents, encoding, FALSE, + pool, pool); + if (err) + { + err = svn_error_quick_wrap + (err, + _("Error normalizing edited contents to internal format")); + goto cleanup; + } + } + } + else + { + /* No edits seem to have been made */ + *edited_contents = NULL; + } + + cleanup: + if (remove_file) + { + /* Remove the file from disk. */ + err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool); + + /* Only report remove error if there was no previous error. */ + if (! err && err2) + err = err2; + else + svn_error_clear(err2); + } + + cleanup2: + /* If we against all probability can't cd back, all further relative + file references would be screwed up, so we have to abort. */ + apr_err = apr_filepath_set(old_cwd, pool); + if (apr_err) + { + svn_handle_error2(svn_error_wrap_apr + (apr_err, _("Can't restore working directory")), + stderr, TRUE /* fatal */, "svn: "); + } + + return svn_error_trace(err); +} diff --git a/subversion/libsvn_subr/compat.c b/subversion/libsvn_subr/compat.c new file mode 100644 index 000000000000..2089828d8dd8 --- /dev/null +++ b/subversion/libsvn_subr/compat.c @@ -0,0 +1,159 @@ +/* + * compat.c : Wrappers and callbacks for compatibility. + * + * ==================================================================== + * 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_strings.h> + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_error.h" +#include "svn_compat.h" +#include "svn_props.h" + + +/* Baton for use with svn_compat_wrap_commit_callback */ +struct commit_wrapper_baton { + void *baton; + svn_commit_callback_t callback; +}; + +/* This implements svn_commit_callback2_t. */ +static svn_error_t * +commit_wrapper_callback(const svn_commit_info_t *commit_info, + void *baton, apr_pool_t *pool) +{ + struct commit_wrapper_baton *cwb = baton; + + if (cwb->callback) + return cwb->callback(commit_info->revision, + commit_info->date, + commit_info->author, + cwb->baton); + + return SVN_NO_ERROR; +} + +void +svn_compat_wrap_commit_callback(svn_commit_callback2_t *callback2, + void **callback2_baton, + svn_commit_callback_t callback, + void *callback_baton, + apr_pool_t *pool) +{ + struct commit_wrapper_baton *cwb = apr_palloc(pool, sizeof(*cwb)); + + /* Set the user provided old format callback in the baton */ + cwb->baton = callback_baton; + cwb->callback = callback; + + *callback2_baton = cwb; + *callback2 = commit_wrapper_callback; +} + + +void +svn_compat_log_revprops_clear(apr_hash_t *revprops) +{ + if (revprops) + { + svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR, NULL); + svn_hash_sets(revprops, SVN_PROP_REVISION_DATE, NULL); + svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL); + } +} + +apr_array_header_t * +svn_compat_log_revprops_in(apr_pool_t *pool) +{ + apr_array_header_t *revprops = apr_array_make(pool, 3, sizeof(char *)); + + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; + APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; + + return revprops; +} + +void +svn_compat_log_revprops_out(const char **author, const char **date, + const char **message, apr_hash_t *revprops) +{ + svn_string_t *author_s, *date_s, *message_s; + + *author = *date = *message = NULL; + if (revprops) + { + if ((author_s = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR))) + *author = author_s->data; + if ((date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE))) + *date = date_s->data; + if ((message_s = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))) + *message = message_s->data; + } +} + +/* Baton for use with svn_compat_wrap_log_receiver */ +struct log_wrapper_baton { + void *baton; + svn_log_message_receiver_t receiver; +}; + +/* This implements svn_log_entry_receiver_t. */ +static svn_error_t * +log_wrapper_callback(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + struct log_wrapper_baton *lwb = baton; + + if (lwb->receiver && SVN_IS_VALID_REVNUM(log_entry->revision)) + { + const char *author, *date, *message; + svn_compat_log_revprops_out(&author, &date, &message, + log_entry->revprops); + return lwb->receiver(lwb->baton, + log_entry->changed_paths2, + log_entry->revision, + author, date, message, + pool); + } + + return SVN_NO_ERROR; +} + +void +svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2, + void **receiver2_baton, + svn_log_message_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + struct log_wrapper_baton *lwb = apr_palloc(pool, sizeof(*lwb)); + + /* Set the user provided old format callback in the baton. */ + lwb->baton = receiver_baton; + lwb->receiver = receiver; + + *receiver2_baton = lwb; + *receiver2 = log_wrapper_callback; +} diff --git a/subversion/libsvn_subr/config.c b/subversion/libsvn_subr/config.c new file mode 100644 index 000000000000..94aecd303e5d --- /dev/null +++ b/subversion/libsvn_subr/config.c @@ -0,0 +1,1208 @@ +/* + * config.c : reading configuration information + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include <apr_want.h> + +#include <apr_general.h> +#include <apr_lib.h> +#include "svn_hash.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "config_impl.h" + +#include "svn_private_config.h" +#include "private/svn_dep_compat.h" + + + + +/* Section table entries. */ +typedef struct cfg_section_t cfg_section_t; +struct cfg_section_t +{ + /* The section name. */ + const char *name; + + /* Table of cfg_option_t's. */ + apr_hash_t *options; +}; + + +/* Option table entries. */ +typedef struct cfg_option_t cfg_option_t; +struct cfg_option_t +{ + /* The option name. */ + const char *name; + + /* The option name, converted into a hash key. */ + const char *hash_key; + + /* The unexpanded option value. */ + const char *value; + + /* The expanded option value. */ + const char *x_value; + + /* Expansion flag. If this is TRUE, this value has already been expanded. + In this case, if x_value is NULL, no expansions were necessary, + and value should be used directly. */ + svn_boolean_t expanded; +}; + + + +svn_error_t * +svn_config_create2(svn_config_t **cfgp, + svn_boolean_t section_names_case_sensitive, + svn_boolean_t option_names_case_sensitive, + apr_pool_t *result_pool) +{ + svn_config_t *cfg = apr_palloc(result_pool, sizeof(*cfg)); + + cfg->sections = apr_hash_make(result_pool); + cfg->pool = result_pool; + cfg->x_pool = svn_pool_create(result_pool); + cfg->x_values = FALSE; + cfg->tmp_key = svn_stringbuf_create_empty(result_pool); + cfg->tmp_value = svn_stringbuf_create_empty(result_pool); + cfg->section_names_case_sensitive = section_names_case_sensitive; + cfg->option_names_case_sensitive = option_names_case_sensitive; + + *cfgp = cfg; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_config_read3(svn_config_t **cfgp, const char *file, + svn_boolean_t must_exist, + svn_boolean_t section_names_case_sensitive, + svn_boolean_t option_names_case_sensitive, + apr_pool_t *result_pool) +{ + svn_config_t *cfg; + svn_error_t *err; + + SVN_ERR(svn_config_create2(&cfg, + section_names_case_sensitive, + option_names_case_sensitive, + result_pool)); + + /* Yes, this is platform-specific code in Subversion, but there's no + practical way to migrate it into APR, as it's simultaneously + Subversion-specific and Windows-specific. Even if we eventually + want to have APR offer a generic config-reading interface, it + makes sense to test it here first and migrate it later. */ +#ifdef WIN32 + if (0 == strncmp(file, SVN_REGISTRY_PREFIX, SVN_REGISTRY_PREFIX_LEN)) + err = svn_config__parse_registry(cfg, file + SVN_REGISTRY_PREFIX_LEN, + must_exist, result_pool); + else +#endif /* WIN32 */ + err = svn_config__parse_file(cfg, file, must_exist, result_pool); + + if (err != SVN_NO_ERROR) + return err; + else + *cfgp = cfg; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_config_parse(svn_config_t **cfgp, svn_stream_t *stream, + svn_boolean_t section_names_case_sensitive, + svn_boolean_t option_names_case_sensitive, + apr_pool_t *result_pool) +{ + svn_config_t *cfg; + svn_error_t *err; + apr_pool_t *scratch_pool = svn_pool_create(result_pool); + + err = svn_config_create2(&cfg, + section_names_case_sensitive, + option_names_case_sensitive, + result_pool); + + if (err == SVN_NO_ERROR) + err = svn_config__parse_stream(cfg, stream, result_pool, scratch_pool); + + if (err == SVN_NO_ERROR) + *cfgp = cfg; + + svn_pool_destroy(scratch_pool); + + return err; +} + +/* Read various configuration sources into *CFGP, in this order, with + * later reads overriding the results of earlier ones: + * + * 1. SYS_REGISTRY_PATH (only on Win32, but ignored if NULL) + * + * 2. SYS_FILE_PATH (everywhere, but ignored if NULL) + * + * 3. USR_REGISTRY_PATH (only on Win32, but ignored if NULL) + * + * 4. USR_FILE_PATH (everywhere, but ignored if NULL) + * + * Allocate *CFGP in POOL. Even if no configurations are read, + * allocate an empty *CFGP. + */ +static svn_error_t * +read_all(svn_config_t **cfgp, + const char *sys_registry_path, + const char *usr_registry_path, + const char *sys_file_path, + const char *usr_file_path, + apr_pool_t *pool) +{ + svn_boolean_t red_config = FALSE; /* "red" is the past tense of "read" */ + + /*** Read system-wide configurations first... ***/ + +#ifdef WIN32 + if (sys_registry_path) + { + SVN_ERR(svn_config_read2(cfgp, sys_registry_path, FALSE, FALSE, pool)); + red_config = TRUE; + } +#endif /* WIN32 */ + + if (sys_file_path) + { + if (red_config) + SVN_ERR(svn_config_merge(*cfgp, sys_file_path, FALSE)); + else + { + SVN_ERR(svn_config_read3(cfgp, sys_file_path, + FALSE, FALSE, FALSE, pool)); + red_config = TRUE; + } + } + + /*** ...followed by per-user configurations. ***/ + +#ifdef WIN32 + if (usr_registry_path) + { + if (red_config) + SVN_ERR(svn_config_merge(*cfgp, usr_registry_path, FALSE)); + else + { + SVN_ERR(svn_config_read2(cfgp, usr_registry_path, + FALSE, FALSE, pool)); + red_config = TRUE; + } + } +#endif /* WIN32 */ + + if (usr_file_path) + { + if (red_config) + SVN_ERR(svn_config_merge(*cfgp, usr_file_path, FALSE)); + else + { + SVN_ERR(svn_config_read3(cfgp, usr_file_path, + FALSE, FALSE, FALSE, pool)); + red_config = TRUE; + } + } + + if (! red_config) + SVN_ERR(svn_config_create2(cfgp, FALSE, FALSE, pool)); + + return SVN_NO_ERROR; +} + + +/* CONFIG_DIR provides an override for the default behavior of reading + the default set of overlay files described by read_all()'s doc + string. */ +static svn_error_t * +get_category_config(svn_config_t **cfg, + const char *config_dir, + const char *category, + apr_pool_t *pool) +{ + const char *usr_reg_path = NULL, *sys_reg_path = NULL; + const char *usr_cfg_path, *sys_cfg_path; + svn_error_t *err = NULL; + + *cfg = NULL; + + if (! config_dir) + { +#ifdef WIN32 + sys_reg_path = apr_pstrcat(pool, SVN_REGISTRY_SYS_CONFIG_PATH, + category, NULL); + usr_reg_path = apr_pstrcat(pool, SVN_REGISTRY_USR_CONFIG_PATH, + category, NULL); +#endif /* WIN32 */ + + err = svn_config__sys_config_path(&sys_cfg_path, category, pool); + if ((err) && (err->apr_err == SVN_ERR_BAD_FILENAME)) + { + sys_cfg_path = NULL; + svn_error_clear(err); + } + else if (err) + return err; + } + else + sys_cfg_path = NULL; + + SVN_ERR(svn_config_get_user_config_path(&usr_cfg_path, config_dir, category, + pool)); + return read_all(cfg, sys_reg_path, usr_reg_path, + sys_cfg_path, usr_cfg_path, pool); +} + + +svn_error_t * +svn_config_get_config(apr_hash_t **cfg_hash, + const char *config_dir, + apr_pool_t *pool) +{ + svn_config_t *cfg; + *cfg_hash = apr_hash_make(pool); + +#define CATLEN (sizeof(SVN_CONFIG_CATEGORY_SERVERS) - 1) + SVN_ERR(get_category_config(&cfg, config_dir, SVN_CONFIG_CATEGORY_SERVERS, + pool)); + if (cfg) + apr_hash_set(*cfg_hash, SVN_CONFIG_CATEGORY_SERVERS, CATLEN, cfg); +#undef CATLEN + +#define CATLEN (sizeof(SVN_CONFIG_CATEGORY_CONFIG) - 1) + SVN_ERR(get_category_config(&cfg, config_dir, SVN_CONFIG_CATEGORY_CONFIG, + pool)); + if (cfg) + apr_hash_set(*cfg_hash, SVN_CONFIG_CATEGORY_CONFIG, CATLEN, cfg); +#undef CATLEN + + return SVN_NO_ERROR; +} + + + +/* Iterate through CFG, passing BATON to CALLBACK for every (SECTION, OPTION) + pair. Stop if CALLBACK returns TRUE. Allocate from POOL. */ +static void +for_each_option(svn_config_t *cfg, void *baton, apr_pool_t *pool, + svn_boolean_t callback(void *same_baton, + cfg_section_t *section, + cfg_option_t *option)) +{ + apr_hash_index_t *sec_ndx; + for (sec_ndx = apr_hash_first(pool, cfg->sections); + sec_ndx != NULL; + sec_ndx = apr_hash_next(sec_ndx)) + { + void *sec_ptr; + cfg_section_t *sec; + apr_hash_index_t *opt_ndx; + + apr_hash_this(sec_ndx, NULL, NULL, &sec_ptr); + sec = sec_ptr; + + for (opt_ndx = apr_hash_first(pool, sec->options); + opt_ndx != NULL; + opt_ndx = apr_hash_next(opt_ndx)) + { + void *opt_ptr; + cfg_option_t *opt; + + apr_hash_this(opt_ndx, NULL, NULL, &opt_ptr); + opt = opt_ptr; + + if (callback(baton, sec, opt)) + return; + } + } +} + + + +static svn_boolean_t +merge_callback(void *baton, cfg_section_t *section, cfg_option_t *option) +{ + svn_config_set(baton, section->name, option->name, option->value); + return FALSE; +} + +svn_error_t * +svn_config_merge(svn_config_t *cfg, const char *file, + svn_boolean_t must_exist) +{ + /* The original config hash shouldn't change if there's an error + while reading the confguration, so read into a temporary table. + ### We could use a tmp subpool for this, since merge_cfg is going + to be tossed afterwards. Premature optimization, though? */ + svn_config_t *merge_cfg; + SVN_ERR(svn_config_read3(&merge_cfg, file, must_exist, + cfg->section_names_case_sensitive, + cfg->option_names_case_sensitive, + cfg->pool)); + + /* Now copy the new options into the original table. */ + for_each_option(merge_cfg, cfg, merge_cfg->pool, merge_callback); + return SVN_NO_ERROR; +} + + + +/* Remove variable expansions from CFG. Walk through the options tree, + killing all expanded values, then clear the expanded value pool. */ +static svn_boolean_t +rmex_callback(void *baton, cfg_section_t *section, cfg_option_t *option) +{ + /* Only clear the `expanded' flag if the value actually contains + variable expansions. */ + if (option->expanded && option->x_value != NULL) + { + option->x_value = NULL; + option->expanded = FALSE; + } + + return FALSE; +} + +static void +remove_expansions(svn_config_t *cfg) +{ + if (!cfg->x_values) + return; + + for_each_option(cfg, NULL, cfg->x_pool, rmex_callback); + svn_pool_clear(cfg->x_pool); + cfg->x_values = FALSE; +} + + + +/* Canonicalize a string for hashing. Modifies KEY in place. */ +static APR_INLINE char * +make_hash_key(char *key) +{ + register char *p; + for (p = key; *p != 0; ++p) + *p = (char)apr_tolower(*p); + return key; +} + + +/* Return a pointer to an option in CFG, or NULL if it doesn't exist. + if SECTIONP is non-null, return a pointer to the option's section. + OPTION may be NULL. */ +static cfg_option_t * +find_option(svn_config_t *cfg, const char *section, const char *option, + cfg_section_t **sectionp) +{ + void *sec_ptr; + + /* Canonicalize the hash key */ + svn_stringbuf_set(cfg->tmp_key, section); + if (! cfg->section_names_case_sensitive) + make_hash_key(cfg->tmp_key->data); + + sec_ptr = apr_hash_get(cfg->sections, cfg->tmp_key->data, + cfg->tmp_key->len); + if (sectionp != NULL) + *sectionp = sec_ptr; + + if (sec_ptr != NULL && option != NULL) + { + cfg_section_t *sec = sec_ptr; + cfg_option_t *opt; + + /* Canonicalize the option key */ + svn_stringbuf_set(cfg->tmp_key, option); + if (! cfg->option_names_case_sensitive) + make_hash_key(cfg->tmp_key->data); + + opt = apr_hash_get(sec->options, cfg->tmp_key->data, + cfg->tmp_key->len); + /* NOTE: ConfigParser's sections are case sensitive. */ + if (opt == NULL + && apr_strnatcasecmp(section, SVN_CONFIG__DEFAULT_SECTION) != 0) + /* Options which aren't found in the requested section are + also sought after in the default section. */ + opt = find_option(cfg, SVN_CONFIG__DEFAULT_SECTION, option, &sec); + return opt; + } + + return NULL; +} + + +/* Has a bi-directional dependency with make_string_from_option(). */ +static void +expand_option_value(svn_config_t *cfg, cfg_section_t *section, + const char *opt_value, const char **opt_x_valuep, + apr_pool_t *x_pool); + + +/* Set *VALUEP according to the OPT's value. A value for X_POOL must + only ever be passed into this function by expand_option_value(). */ +static void +make_string_from_option(const char **valuep, svn_config_t *cfg, + cfg_section_t *section, cfg_option_t *opt, + apr_pool_t* x_pool) +{ + /* Expand the option value if necessary. */ + if (!opt->expanded) + { + /* before attempting to expand an option, check for the placeholder. + * If none is there, there is no point in calling expand_option_value. + */ + if (opt->value && strchr(opt->value, '%')) + { + apr_pool_t *tmp_pool = (x_pool ? x_pool : svn_pool_create(cfg->x_pool)); + + expand_option_value(cfg, section, opt->value, &opt->x_value, tmp_pool); + opt->expanded = TRUE; + + if (!x_pool) + { + /* Grab the fully expanded value from tmp_pool before its + disappearing act. */ + if (opt->x_value) + opt->x_value = apr_pstrmemdup(cfg->x_pool, opt->x_value, + strlen(opt->x_value)); + svn_pool_destroy(tmp_pool); + } + } + else + { + opt->expanded = TRUE; + } + } + + if (opt->x_value) + *valuep = opt->x_value; + else + *valuep = opt->value; +} + + +/* Start of variable-replacement placeholder */ +#define FMT_START "%(" +#define FMT_START_LEN (sizeof(FMT_START) - 1) + +/* End of variable-replacement placeholder */ +#define FMT_END ")s" +#define FMT_END_LEN (sizeof(FMT_END) - 1) + + +/* Expand OPT_VALUE (which may be NULL) in SECTION into *OPT_X_VALUEP. + If no variable replacements are done, set *OPT_X_VALUEP to + NULL. Allocate from X_POOL. */ +static void +expand_option_value(svn_config_t *cfg, cfg_section_t *section, + const char *opt_value, const char **opt_x_valuep, + apr_pool_t *x_pool) +{ + svn_stringbuf_t *buf = NULL; + const char *parse_from = opt_value; + const char *copy_from = parse_from; + const char *name_start, *name_end; + + while (parse_from != NULL + && *parse_from != '\0' + && (name_start = strstr(parse_from, FMT_START)) != NULL) + { + name_start += FMT_START_LEN; + if (*name_start == '\0') + /* FMT_START at end of opt_value. */ + break; + + name_end = strstr(name_start, FMT_END); + if (name_end != NULL) + { + cfg_option_t *x_opt; + apr_size_t len = name_end - name_start; + char *name = apr_pstrmemdup(x_pool, name_start, len); + + x_opt = find_option(cfg, section->name, name, NULL); + + if (x_opt != NULL) + { + const char *cstring; + + /* Pass back the sub-pool originally provided by + make_string_from_option() as an indication of when it + should terminate. */ + make_string_from_option(&cstring, cfg, section, x_opt, x_pool); + + /* Append the plain text preceding the expansion. */ + len = name_start - FMT_START_LEN - copy_from; + if (buf == NULL) + { + buf = svn_stringbuf_ncreate(copy_from, len, x_pool); + cfg->x_values = TRUE; + } + else + svn_stringbuf_appendbytes(buf, copy_from, len); + + /* Append the expansion and adjust parse pointers. */ + svn_stringbuf_appendcstr(buf, cstring); + parse_from = name_end + FMT_END_LEN; + copy_from = parse_from; + } + else + /* Though ConfigParser considers the failure to resolve + the requested expansion an exception condition, we + consider it to be plain text, and look for the start of + the next one. */ + parse_from = name_end + FMT_END_LEN; + } + else + /* Though ConfigParser treats unterminated format specifiers + as an exception condition, we consider them to be plain + text. The fact that there are no more format specifier + endings means we're done parsing. */ + parse_from = NULL; + } + + if (buf != NULL) + { + /* Copy the remainder of the plain text. */ + svn_stringbuf_appendcstr(buf, copy_from); + *opt_x_valuep = buf->data; + } + else + *opt_x_valuep = NULL; +} + +static cfg_section_t * +svn_config_addsection(svn_config_t *cfg, + const char *section) +{ + cfg_section_t *s; + const char *hash_key; + + s = apr_palloc(cfg->pool, sizeof(cfg_section_t)); + s->name = apr_pstrdup(cfg->pool, section); + if(cfg->section_names_case_sensitive) + hash_key = s->name; + else + hash_key = make_hash_key(apr_pstrdup(cfg->pool, section)); + s->options = apr_hash_make(cfg->pool); + svn_hash_sets(cfg->sections, hash_key, s); + + return s; +} + +static void +svn_config_create_option(cfg_option_t **opt, + const char *option, + const char *value, + svn_boolean_t option_names_case_sensitive, + apr_pool_t *pool) +{ + cfg_option_t *o; + + o = apr_palloc(pool, sizeof(cfg_option_t)); + o->name = apr_pstrdup(pool, option); + if(option_names_case_sensitive) + o->hash_key = o->name; + else + o->hash_key = make_hash_key(apr_pstrdup(pool, option)); + + o->value = apr_pstrdup(pool, value); + o->x_value = NULL; + o->expanded = FALSE; + + *opt = o; +} + + +void +svn_config_get(svn_config_t *cfg, const char **valuep, + const char *section, const char *option, + const char *default_value) +{ + *valuep = default_value; + if (cfg) + { + cfg_section_t *sec; + cfg_option_t *opt = find_option(cfg, section, option, &sec); + if (opt != NULL) + { + make_string_from_option(valuep, cfg, sec, opt, NULL); + } + else + /* before attempting to expand an option, check for the placeholder. + * If none is there, there is no point in calling expand_option_value. + */ + if (default_value && strchr(default_value, '%')) + { + apr_pool_t *tmp_pool = svn_pool_create(cfg->x_pool); + const char *x_default; + expand_option_value(cfg, sec, default_value, &x_default, tmp_pool); + if (x_default) + { + svn_stringbuf_set(cfg->tmp_value, x_default); + *valuep = cfg->tmp_value->data; + } + svn_pool_destroy(tmp_pool); + } + } +} + + + +void +svn_config_set(svn_config_t *cfg, + const char *section, const char *option, + const char *value) +{ + cfg_section_t *sec; + cfg_option_t *opt; + + remove_expansions(cfg); + + opt = find_option(cfg, section, option, &sec); + if (opt != NULL) + { + /* Replace the option's value. */ + opt->value = apr_pstrdup(cfg->pool, value); + opt->expanded = FALSE; + return; + } + + /* Create a new option */ + svn_config_create_option(&opt, option, value, + cfg->option_names_case_sensitive, + cfg->pool); + + if (sec == NULL) + { + /* Even the section doesn't exist. Create it. */ + sec = svn_config_addsection(cfg, section); + } + + svn_hash_sets(sec->options, opt->hash_key, opt); +} + + + +/* Set *BOOLP to true or false depending (case-insensitively) on INPUT. + If INPUT is null, set *BOOLP to DEFAULT_VALUE. + + INPUT is a string indicating truth or falsehood in any of the usual + ways: "true"/"yes"/"on"/etc, "false"/"no"/"off"/etc. + + If INPUT is neither NULL nor a recognized string, return an error + with code SVN_ERR_BAD_CONFIG_VALUE; use SECTION and OPTION in + constructing the error string. */ +static svn_error_t * +get_bool(svn_boolean_t *boolp, const char *input, svn_boolean_t default_value, + const char *section, const char *option) +{ + svn_tristate_t value = svn_tristate__from_word(input); + + if (value == svn_tristate_true) + *boolp = TRUE; + else if (value == svn_tristate_false) + *boolp = FALSE; + else if (input == NULL) /* no value provided */ + *boolp = default_value; + + else if (section) /* unrecognized value */ + return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, + _("Config error: invalid boolean " + "value '%s' for '[%s] %s'"), + input, section, option); + else + return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, + _("Config error: invalid boolean " + "value '%s' for '%s'"), + input, option); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_config_get_bool(svn_config_t *cfg, svn_boolean_t *valuep, + const char *section, const char *option, + svn_boolean_t default_value) +{ + const char *tmp_value; + svn_config_get(cfg, &tmp_value, section, option, NULL); + return get_bool(valuep, tmp_value, default_value, section, option); +} + + + +void +svn_config_set_bool(svn_config_t *cfg, + const char *section, const char *option, + svn_boolean_t value) +{ + svn_config_set(cfg, section, option, + (value ? SVN_CONFIG_TRUE : SVN_CONFIG_FALSE)); +} + +svn_error_t * +svn_config_get_int64(svn_config_t *cfg, + apr_int64_t *valuep, + const char *section, + const char *option, + apr_int64_t default_value) +{ + const char *tmp_value; + svn_config_get(cfg, &tmp_value, section, option, NULL); + if (tmp_value) + return svn_cstring_strtoi64(valuep, tmp_value, + APR_INT64_MIN, APR_INT64_MAX, 10); + + *valuep = default_value; + return SVN_NO_ERROR; +} + +void +svn_config_set_int64(svn_config_t *cfg, + const char *section, + const char *option, + apr_int64_t value) +{ + svn_config_set(cfg, section, option, + apr_psprintf(cfg->pool, "%" APR_INT64_T_FMT, value)); +} + +svn_error_t * +svn_config_get_yes_no_ask(svn_config_t *cfg, const char **valuep, + const char *section, const char *option, + const char* default_value) +{ + const char *tmp_value; + + svn_config_get(cfg, &tmp_value, section, option, NULL); + + if (! tmp_value) + tmp_value = default_value; + + if (tmp_value && (0 == svn_cstring_casecmp(tmp_value, SVN_CONFIG_ASK))) + { + *valuep = SVN_CONFIG_ASK; + } + else + { + svn_boolean_t bool_val; + /* We already incorporated default_value into tmp_value if + necessary, so the FALSE below will be ignored unless the + caller is doing something it shouldn't be doing. */ + SVN_ERR(get_bool(&bool_val, tmp_value, FALSE, section, option)); + *valuep = bool_val ? SVN_CONFIG_TRUE : SVN_CONFIG_FALSE; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_config_get_tristate(svn_config_t *cfg, svn_tristate_t *valuep, + const char *section, const char *option, + const char *unknown_value, + svn_tristate_t default_value) +{ + const char *tmp_value; + + svn_config_get(cfg, &tmp_value, section, option, NULL); + + if (! tmp_value) + { + *valuep = default_value; + } + else if (0 == svn_cstring_casecmp(tmp_value, unknown_value)) + { + *valuep = svn_tristate_unknown; + } + else + { + svn_boolean_t bool_val; + /* We already incorporated default_value into tmp_value if + necessary, so the FALSE below will be ignored unless the + caller is doing something it shouldn't be doing. */ + SVN_ERR(get_bool(&bool_val, tmp_value, FALSE, section, option)); + *valuep = bool_val ? svn_tristate_true : svn_tristate_false; + } + + return SVN_NO_ERROR; +} + +int +svn_config_enumerate_sections(svn_config_t *cfg, + svn_config_section_enumerator_t callback, + void *baton) +{ + apr_hash_index_t *sec_ndx; + int count = 0; + apr_pool_t *subpool = svn_pool_create(cfg->x_pool); + + for (sec_ndx = apr_hash_first(subpool, cfg->sections); + sec_ndx != NULL; + sec_ndx = apr_hash_next(sec_ndx)) + { + void *sec_ptr; + cfg_section_t *sec; + + apr_hash_this(sec_ndx, NULL, NULL, &sec_ptr); + sec = sec_ptr; + ++count; + if (!callback(sec->name, baton)) + break; + } + + svn_pool_destroy(subpool); + return count; +} + + +int +svn_config_enumerate_sections2(svn_config_t *cfg, + svn_config_section_enumerator2_t callback, + void *baton, apr_pool_t *pool) +{ + apr_hash_index_t *sec_ndx; + apr_pool_t *iteration_pool; + int count = 0; + + iteration_pool = svn_pool_create(pool); + for (sec_ndx = apr_hash_first(pool, cfg->sections); + sec_ndx != NULL; + sec_ndx = apr_hash_next(sec_ndx)) + { + void *sec_ptr; + cfg_section_t *sec; + + apr_hash_this(sec_ndx, NULL, NULL, &sec_ptr); + sec = sec_ptr; + ++count; + svn_pool_clear(iteration_pool); + if (!callback(sec->name, baton, iteration_pool)) + break; + } + svn_pool_destroy(iteration_pool); + + return count; +} + + + +int +svn_config_enumerate(svn_config_t *cfg, const char *section, + svn_config_enumerator_t callback, void *baton) +{ + cfg_section_t *sec; + apr_hash_index_t *opt_ndx; + int count; + apr_pool_t *subpool; + + find_option(cfg, section, NULL, &sec); + if (sec == NULL) + return 0; + + subpool = svn_pool_create(cfg->x_pool); + count = 0; + for (opt_ndx = apr_hash_first(subpool, sec->options); + opt_ndx != NULL; + opt_ndx = apr_hash_next(opt_ndx)) + { + void *opt_ptr; + cfg_option_t *opt; + const char *temp_value; + + apr_hash_this(opt_ndx, NULL, NULL, &opt_ptr); + opt = opt_ptr; + + ++count; + make_string_from_option(&temp_value, cfg, sec, opt, NULL); + if (!callback(opt->name, temp_value, baton)) + break; + } + + svn_pool_destroy(subpool); + return count; +} + + +int +svn_config_enumerate2(svn_config_t *cfg, const char *section, + svn_config_enumerator2_t callback, void *baton, + apr_pool_t *pool) +{ + cfg_section_t *sec; + apr_hash_index_t *opt_ndx; + apr_pool_t *iteration_pool; + int count; + + find_option(cfg, section, NULL, &sec); + if (sec == NULL) + return 0; + + iteration_pool = svn_pool_create(pool); + count = 0; + for (opt_ndx = apr_hash_first(pool, sec->options); + opt_ndx != NULL; + opt_ndx = apr_hash_next(opt_ndx)) + { + void *opt_ptr; + cfg_option_t *opt; + const char *temp_value; + + apr_hash_this(opt_ndx, NULL, NULL, &opt_ptr); + opt = opt_ptr; + + ++count; + make_string_from_option(&temp_value, cfg, sec, opt, NULL); + svn_pool_clear(iteration_pool); + if (!callback(opt->name, temp_value, baton, iteration_pool)) + break; + } + svn_pool_destroy(iteration_pool); + + return count; +} + + + +/* Baton for search_groups() */ +struct search_groups_baton +{ + const char *key; /* Provided by caller of svn_config_find_group */ + const char *match; /* Filled in by search_groups */ + apr_pool_t *pool; +}; + + +/* This is an `svn_config_enumerator_t' function, and BATON is a + * `struct search_groups_baton *'. + */ +static svn_boolean_t search_groups(const char *name, + const char *value, + void *baton, + apr_pool_t *pool) +{ + struct search_groups_baton *b = baton; + apr_array_header_t *list; + + list = svn_cstring_split(value, ",", TRUE, pool); + if (svn_cstring_match_glob_list(b->key, list)) + { + /* Fill in the match and return false, to stop enumerating. */ + b->match = apr_pstrdup(b->pool, name); + return FALSE; + } + else + return TRUE; +} + + +const char *svn_config_find_group(svn_config_t *cfg, const char *key, + const char *master_section, + apr_pool_t *pool) +{ + struct search_groups_baton gb; + + gb.key = key; + gb.match = NULL; + gb.pool = pool; + (void) svn_config_enumerate2(cfg, master_section, search_groups, &gb, pool); + return gb.match; +} + + +const char* +svn_config_get_server_setting(svn_config_t *cfg, + const char* server_group, + const char* option_name, + const char* default_value) +{ + const char *retval; + svn_config_get(cfg, &retval, SVN_CONFIG_SECTION_GLOBAL, + option_name, default_value); + if (server_group) + { + svn_config_get(cfg, &retval, server_group, option_name, retval); + } + return retval; +} + + +svn_error_t * +svn_config_dup(svn_config_t **cfgp, + svn_config_t *src, + apr_pool_t *pool) +{ + apr_hash_index_t *sectidx; + apr_hash_index_t *optidx; + + *cfgp = 0; + SVN_ERR(svn_config_create2(cfgp, FALSE, FALSE, pool)); + + (*cfgp)->x_values = src->x_values; + (*cfgp)->section_names_case_sensitive = src->section_names_case_sensitive; + (*cfgp)->option_names_case_sensitive = src->option_names_case_sensitive; + + for (sectidx = apr_hash_first(pool, src->sections); + sectidx != NULL; + sectidx = apr_hash_next(sectidx)) + { + const void *sectkey; + void *sectval; + apr_ssize_t sectkeyLength; + cfg_section_t * srcsect; + cfg_section_t * destsec; + + apr_hash_this(sectidx, §key, §keyLength, §val); + srcsect = sectval; + + destsec = svn_config_addsection(*cfgp, srcsect->name); + + for (optidx = apr_hash_first(pool, srcsect->options); + optidx != NULL; + optidx = apr_hash_next(optidx)) + { + const void *optkey; + void *optval; + apr_ssize_t optkeyLength; + cfg_option_t *srcopt; + cfg_option_t *destopt; + + apr_hash_this(optidx, &optkey, &optkeyLength, &optval); + srcopt = optval; + + svn_config_create_option(&destopt, srcopt->name, srcopt->value, + (*cfgp)->option_names_case_sensitive, + pool); + + destopt->value = apr_pstrdup(pool, srcopt->value); + destopt->x_value = apr_pstrdup(pool, srcopt->x_value); + destopt->expanded = srcopt->expanded; + apr_hash_set(destsec->options, + apr_pstrdup(pool, (const char*)optkey), + optkeyLength, destopt); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_config_copy_config(apr_hash_t **cfg_hash, + apr_hash_t *src_hash, + apr_pool_t *pool) +{ + apr_hash_index_t *cidx; + + *cfg_hash = apr_hash_make(pool); + for (cidx = apr_hash_first(pool, src_hash); + cidx != NULL; + cidx = apr_hash_next(cidx)) + { + const void *ckey; + void *cval; + apr_ssize_t ckeyLength; + svn_config_t * srcconfig; + svn_config_t * destconfig; + + apr_hash_this(cidx, &ckey, &ckeyLength, &cval); + srcconfig = cval; + + SVN_ERR(svn_config_dup(&destconfig, srcconfig, pool)); + + apr_hash_set(*cfg_hash, + apr_pstrdup(pool, (const char*)ckey), + ckeyLength, destconfig); + } + + return SVN_NO_ERROR; +} + +svn_error_t* +svn_config_get_server_setting_int(svn_config_t *cfg, + const char *server_group, + const char *option_name, + apr_int64_t default_value, + apr_int64_t *result_value, + apr_pool_t *pool) +{ + const char* tmp_value; + char *end_pos; + + tmp_value = svn_config_get_server_setting(cfg, server_group, + option_name, NULL); + if (tmp_value == NULL) + *result_value = default_value; + else + { + /* read tmp_value as an int now */ + *result_value = apr_strtoi64(tmp_value, &end_pos, 0); + + if (*end_pos != 0) + { + return svn_error_createf + (SVN_ERR_BAD_CONFIG_VALUE, NULL, + _("Config error: invalid integer value '%s'"), + tmp_value); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_config_get_server_setting_bool(svn_config_t *cfg, + svn_boolean_t *valuep, + const char *server_group, + const char *option_name, + svn_boolean_t default_value) +{ + const char* tmp_value; + tmp_value = svn_config_get_server_setting(cfg, server_group, + option_name, NULL); + return get_bool(valuep, tmp_value, default_value, + server_group, option_name); +} + + +svn_boolean_t +svn_config_has_section(svn_config_t *cfg, const char *section) +{ + cfg_section_t *sec; + + /* Canonicalize the hash key */ + svn_stringbuf_set(cfg->tmp_key, section); + if (! cfg->section_names_case_sensitive) + make_hash_key(cfg->tmp_key->data); + + sec = svn_hash_gets(cfg->sections, cfg->tmp_key->data); + return sec != NULL; +} diff --git a/subversion/libsvn_subr/config_auth.c b/subversion/libsvn_subr/config_auth.c new file mode 100644 index 000000000000..d53403c44b48 --- /dev/null +++ b/subversion/libsvn_subr/config_auth.c @@ -0,0 +1,277 @@ +/* + * config_auth.c : authentication files in the user config area + * + * ==================================================================== + * 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_dirent_uri.h" +#include "svn_hash.h" +#include "svn_io.h" +#include "svn_pools.h" +#include "config_impl.h" + +#include "auth.h" + +#include "svn_private_config.h" + +#include "private/svn_auth_private.h" + +/* Helper for svn_config_{read|write}_auth_data. Return a path to a + file within ~/.subversion/auth/ that holds CRED_KIND credentials + within REALMSTRING. If no path is available *PATH will be set to + NULL. */ +svn_error_t * +svn_auth__file_path(const char **path, + const char *cred_kind, + const char *realmstring, + const char *config_dir, + apr_pool_t *pool) +{ + const char *authdir_path, *hexname; + svn_checksum_t *checksum; + + /* Construct the path to the directory containing the creds files, + e.g. "~/.subversion/auth/svn.simple". The last component is + simply the cred_kind. */ + SVN_ERR(svn_config_get_user_config_path(&authdir_path, config_dir, + SVN_CONFIG__AUTH_SUBDIR, pool)); + if (authdir_path) + { + authdir_path = svn_dirent_join(authdir_path, cred_kind, pool); + + /* Construct the basename of the creds file. It's just the + realmstring converted into an md5 hex string. */ + SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, realmstring, + strlen(realmstring), pool)); + hexname = svn_checksum_to_cstring(checksum, pool); + + *path = svn_dirent_join(authdir_path, hexname, pool); + } + else + *path = NULL; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_config_read_auth_data(apr_hash_t **hash, + const char *cred_kind, + const char *realmstring, + const char *config_dir, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + const char *auth_path; + + *hash = NULL; + + SVN_ERR(svn_auth__file_path(&auth_path, cred_kind, realmstring, config_dir, + pool)); + if (! auth_path) + return SVN_NO_ERROR; + + SVN_ERR(svn_io_check_path(auth_path, &kind, pool)); + if (kind == svn_node_file) + { + svn_stream_t *stream; + + SVN_ERR_W(svn_stream_open_readonly(&stream, auth_path, pool, pool), + _("Unable to open auth file for reading")); + + *hash = apr_hash_make(pool); + + SVN_ERR_W(svn_hash_read2(*hash, stream, SVN_HASH_TERMINATOR, pool), + apr_psprintf(pool, _("Error parsing '%s'"), + svn_dirent_local_style(auth_path, pool))); + + SVN_ERR(svn_stream_close(stream)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_config_write_auth_data(apr_hash_t *hash, + const char *cred_kind, + const char *realmstring, + const char *config_dir, + apr_pool_t *pool) +{ + apr_file_t *authfile = NULL; + svn_stream_t *stream; + const char *auth_path; + + SVN_ERR(svn_auth__file_path(&auth_path, cred_kind, realmstring, config_dir, + pool)); + if (! auth_path) + return svn_error_create(SVN_ERR_NO_AUTH_FILE_PATH, NULL, + _("Unable to locate auth file")); + + /* Add the realmstring to the hash, so programs (or users) can + verify exactly which set of credentials this file holds. */ + svn_hash_sets(hash, SVN_CONFIG_REALMSTRING_KEY, + svn_string_create(realmstring, pool)); + + SVN_ERR_W(svn_io_file_open(&authfile, auth_path, + (APR_WRITE | APR_CREATE | APR_TRUNCATE + | APR_BUFFERED), + APR_OS_DEFAULT, pool), + _("Unable to open auth file for writing")); + + stream = svn_stream_from_aprfile2(authfile, FALSE, pool); + SVN_ERR_W(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool), + apr_psprintf(pool, _("Error writing hash to '%s'"), + svn_dirent_local_style(auth_path, pool))); + + SVN_ERR(svn_stream_close(stream)); + + /* To be nice, remove the realmstring from the hash again, just in + case the caller wants their hash unchanged. */ + svn_hash_sets(hash, SVN_CONFIG_REALMSTRING_KEY, NULL); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_config_walk_auth_data(const char *config_dir, + svn_config_auth_walk_func_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + int i; + apr_pool_t *iterpool; + svn_boolean_t finished = FALSE; + const char *cred_kinds[] = + { + SVN_AUTH_CRED_SIMPLE, + SVN_AUTH_CRED_USERNAME, + SVN_AUTH_CRED_SSL_CLIENT_CERT, + SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, + SVN_AUTH_CRED_SSL_SERVER_TRUST, + NULL + }; + + if (! config_dir) + { + /* Can't locate the cache to clear */ + return SVN_NO_ERROR; + } + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; cred_kinds[i]; i++) + { + const char *item_path; + const char *dir_path; + apr_hash_t *nodes; + svn_error_t *err; + apr_pool_t *itempool; + apr_hash_index_t *hi; + + svn_pool_clear(iterpool); + + if (finished) + break; + + SVN_ERR(svn_auth__file_path(&item_path, cred_kinds[i], "!", config_dir, + iterpool)); + + dir_path = svn_dirent_dirname(item_path, iterpool); + + err = svn_io_get_dirents3(&nodes, dir_path, TRUE, iterpool, iterpool); + if (err) + { + if (!APR_STATUS_IS_ENOENT(err->apr_err) + && !SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)) + return svn_error_trace(err); + + svn_error_clear(err); + continue; + } + + itempool = svn_pool_create(iterpool); + for (hi = apr_hash_first(iterpool, nodes); hi; hi = apr_hash_next(hi)) + { + svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); + svn_stream_t *stream; + apr_hash_t *creds_hash; + const svn_string_t *realm; + svn_boolean_t delete_file = FALSE; + + if (finished) + break; + + if (dirent->kind != svn_node_file) + continue; + + svn_pool_clear(itempool); + + item_path = svn_dirent_join(dir_path, svn__apr_hash_index_key(hi), + itempool); + + err = svn_stream_open_readonly(&stream, item_path, + itempool, itempool); + if (err) + { + /* Ignore this file. There are no credentials in it anyway */ + svn_error_clear(err); + continue; + } + + creds_hash = apr_hash_make(itempool); + err = svn_hash_read2(creds_hash, stream, + SVN_HASH_TERMINATOR, itempool); + err = svn_error_compose_create(err, svn_stream_close(stream)); + if (err) + { + /* Ignore this file. There are no credentials in it anyway */ + svn_error_clear(err); + continue; + } + + realm = svn_hash_gets(creds_hash, SVN_CONFIG_REALMSTRING_KEY); + if (! realm) + continue; /* Not an auth file */ + + err = walk_func(&delete_file, walk_baton, cred_kinds[i], + realm->data, creds_hash, itempool); + if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + finished = TRUE; + } + SVN_ERR(err); + + if (delete_file) + { + /* Delete the file on disk */ + SVN_ERR(svn_io_remove_file2(item_path, TRUE, itempool)); + } + } + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/config_file.c b/subversion/libsvn_subr/config_file.c new file mode 100644 index 000000000000..9d15f6b149f8 --- /dev/null +++ b/subversion/libsvn_subr/config_file.c @@ -0,0 +1,1260 @@ +/* + * config_file.c : parsing configuration files + * + * ==================================================================== + * 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_lib.h> +#include <apr_env.h> +#include "config_impl.h" +#include "svn_io.h" +#include "svn_types.h" +#include "svn_dirent_uri.h" +#include "svn_auth.h" +#include "svn_subst.h" +#include "svn_utf.h" +#include "svn_pools.h" +#include "svn_user.h" +#include "svn_ctype.h" + +#include "svn_private_config.h" + +#ifdef __HAIKU__ +# include <FindDirectory.h> +# include <StorageDefs.h> +#endif + +/* Used to terminate lines in large multi-line string literals. */ +#define NL APR_EOL_STR + + +/* File parsing context */ +typedef struct parse_context_t +{ + /* This config struct */ + svn_config_t *cfg; + + /* The stream struct */ + svn_stream_t *stream; + + /* The current line in the file */ + int line; + + /* Emulate an ungetc */ + int ungotten_char; + + /* Temporary strings */ + svn_stringbuf_t *section; + svn_stringbuf_t *option; + svn_stringbuf_t *value; + + /* Parser buffer for getc() to avoid call overhead into several libraries + for every character */ + char parser_buffer[SVN_STREAM_CHUNK_SIZE]; /* Larger than most config files */ + size_t buffer_pos; /* Current position within parser_buffer */ + size_t buffer_size; /* parser_buffer contains this many bytes */ +} parse_context_t; + + + +/* Emulate getc() because streams don't support it. + * + * In order to be able to ungetc(), use the CXT instead of the stream + * to be able to store the 'ungotton' character. + * + */ +static APR_INLINE svn_error_t * +parser_getc(parse_context_t *ctx, int *c) +{ + do + { + if (ctx->ungotten_char != EOF) + { + *c = ctx->ungotten_char; + ctx->ungotten_char = EOF; + } + else if (ctx->buffer_pos < ctx->buffer_size) + { + *c = ctx->parser_buffer[ctx->buffer_pos]; + ctx->buffer_pos++; + } + else + { + ctx->buffer_pos = 0; + ctx->buffer_size = sizeof(ctx->parser_buffer); + + SVN_ERR(svn_stream_read(ctx->stream, ctx->parser_buffer, + &(ctx->buffer_size))); + + if (ctx->buffer_pos < ctx->buffer_size) + { + *c = ctx->parser_buffer[ctx->buffer_pos]; + ctx->buffer_pos++; + } + else + *c = EOF; + } + } + while (*c == '\r'); + + return SVN_NO_ERROR; +} + +/* Simplified version of parser_getc() to be used inside skipping loops. + * It will not check for 'ungotton' chars and may or may not ignore '\r'. + * + * In a 'while(cond) getc();' loop, the first iteration must call + * parser_getc to handle all the special cases. Later iterations should + * use parser_getc_plain for maximum performance. + */ +static APR_INLINE svn_error_t * +parser_getc_plain(parse_context_t *ctx, int *c) +{ + if (ctx->buffer_pos < ctx->buffer_size) + { + *c = ctx->parser_buffer[ctx->buffer_pos]; + ctx->buffer_pos++; + + return SVN_NO_ERROR; + } + + return parser_getc(ctx, c); +} + +/* Emulate ungetc() because streams don't support it. + * + * Use CTX to store the ungotten character C. + */ +static APR_INLINE svn_error_t * +parser_ungetc(parse_context_t *ctx, int c) +{ + ctx->ungotten_char = c; + + return SVN_NO_ERROR; +} + +/* Eat chars from STREAM until encounter non-whitespace, newline, or EOF. + Set *PCOUNT to the number of characters eaten, not counting the + last one, and return the last char read (the one that caused the + break). */ +static APR_INLINE svn_error_t * +skip_whitespace(parse_context_t *ctx, int *c, int *pcount) +{ + int ch = 0; + int count = 0; + + SVN_ERR(parser_getc(ctx, &ch)); + while (svn_ctype_isspace(ch) && ch != '\n' && ch != EOF) + { + ++count; + SVN_ERR(parser_getc_plain(ctx, &ch)); + } + *pcount = count; + *c = ch; + return SVN_NO_ERROR; +} + + +/* Skip to the end of the line (or file). Returns the char that ended + the line; the char is either EOF or newline. */ +static APR_INLINE svn_error_t * +skip_to_eoln(parse_context_t *ctx, int *c) +{ + int ch; + + SVN_ERR(parser_getc(ctx, &ch)); + while (ch != '\n' && ch != EOF) + SVN_ERR(parser_getc_plain(ctx, &ch)); + + *c = ch; + return SVN_NO_ERROR; +} + + +/* Parse a single option value */ +static svn_error_t * +parse_value(int *pch, parse_context_t *ctx) +{ + svn_boolean_t end_of_val = FALSE; + int ch; + + /* Read the first line of the value */ + svn_stringbuf_setempty(ctx->value); + SVN_ERR(parser_getc(ctx, &ch)); + while (ch != EOF && ch != '\n') + /* last ch seen was ':' or '=' in parse_option. */ + { + const char char_from_int = (char)ch; + svn_stringbuf_appendbyte(ctx->value, char_from_int); + SVN_ERR(parser_getc(ctx, &ch)); + } + /* Leading and trailing whitespace is ignored. */ + svn_stringbuf_strip_whitespace(ctx->value); + + /* Look for any continuation lines. */ + for (;;) + { + + if (ch == EOF || end_of_val) + { + /* At end of file. The value is complete, there can't be + any continuation lines. */ + svn_config_set(ctx->cfg, ctx->section->data, + ctx->option->data, ctx->value->data); + break; + } + else + { + int count; + ++ctx->line; + SVN_ERR(skip_whitespace(ctx, &ch, &count)); + + switch (ch) + { + case '\n': + /* The next line was empty. Ergo, it can't be a + continuation line. */ + ++ctx->line; + end_of_val = TRUE; + continue; + + case EOF: + /* This is also an empty line. */ + end_of_val = TRUE; + continue; + + default: + if (count == 0) + { + /* This line starts in the first column. That means + it's either a section, option or comment. Put + the char back into the stream, because it doesn't + belong to us. */ + SVN_ERR(parser_ungetc(ctx, ch)); + end_of_val = TRUE; + } + else + { + /* This is a continuation line. Read it. */ + svn_stringbuf_appendbyte(ctx->value, ' '); + + while (ch != EOF && ch != '\n') + { + const char char_from_int = (char)ch; + svn_stringbuf_appendbyte(ctx->value, char_from_int); + SVN_ERR(parser_getc(ctx, &ch)); + } + /* Trailing whitespace is ignored. */ + svn_stringbuf_strip_whitespace(ctx->value); + } + } + } + } + + *pch = ch; + return SVN_NO_ERROR; +} + + +/* Parse a single option */ +static svn_error_t * +parse_option(int *pch, parse_context_t *ctx, apr_pool_t *scratch_pool) +{ + svn_error_t *err = SVN_NO_ERROR; + int ch; + + svn_stringbuf_setempty(ctx->option); + ch = *pch; /* Yes, the first char is relevant. */ + while (ch != EOF && ch != ':' && ch != '=' && ch != '\n') + { + const char char_from_int = (char)ch; + svn_stringbuf_appendbyte(ctx->option, char_from_int); + SVN_ERR(parser_getc(ctx, &ch)); + } + + if (ch != ':' && ch != '=') + { + ch = EOF; + err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, + "line %d: Option must end with ':' or '='", + ctx->line); + } + else + { + /* Whitespace around the name separator is ignored. */ + svn_stringbuf_strip_whitespace(ctx->option); + err = parse_value(&ch, ctx); + } + + *pch = ch; + return err; +} + + +/* Read chars until enounter ']', then skip everything to the end of + * the line. Set *PCH to the character that ended the line (either + * newline or EOF), and set CTX->section to the string of characters + * seen before ']'. + * + * This is meant to be called immediately after reading the '[' that + * starts a section name. + */ +static svn_error_t * +parse_section_name(int *pch, parse_context_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err = SVN_NO_ERROR; + int ch; + + svn_stringbuf_setempty(ctx->section); + SVN_ERR(parser_getc(ctx, &ch)); + while (ch != EOF && ch != ']' && ch != '\n') + { + const char char_from_int = (char)ch; + svn_stringbuf_appendbyte(ctx->section, char_from_int); + SVN_ERR(parser_getc(ctx, &ch)); + } + + if (ch != ']') + { + ch = EOF; + err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, + "line %d: Section header must end with ']'", + ctx->line); + } + else + { + /* Everything from the ']' to the end of the line is ignored. */ + SVN_ERR(skip_to_eoln(ctx, &ch)); + if (ch != EOF) + ++ctx->line; + } + + *pch = ch; + return err; +} + + +svn_error_t * +svn_config__sys_config_path(const char **path_p, + const char *fname, + apr_pool_t *pool) +{ + *path_p = NULL; + + /* Note that even if fname is null, svn_dirent_join_many will DTRT. */ + +#ifdef WIN32 + { + const char *folder; + SVN_ERR(svn_config__win_config_path(&folder, TRUE, pool)); + *path_p = svn_dirent_join_many(pool, folder, + SVN_CONFIG__SUBDIRECTORY, fname, NULL); + } + +#elif defined(__HAIKU__) + { + char folder[B_PATH_NAME_LENGTH]; + + status_t error = find_directory(B_COMMON_SETTINGS_DIRECTORY, -1, false, + folder, sizeof(folder)); + if (error) + return SVN_NO_ERROR; + + *path_p = svn_dirent_join_many(pool, folder, + SVN_CONFIG__SYS_DIRECTORY, fname, NULL); + } +#else /* ! WIN32 && !__HAIKU__ */ + + *path_p = svn_dirent_join_many(pool, SVN_CONFIG__SYS_DIRECTORY, fname, NULL); + +#endif /* WIN32 */ + + return SVN_NO_ERROR; +} + + +/*** Exported interfaces. ***/ + + +svn_error_t * +svn_config__parse_file(svn_config_t *cfg, const char *file, + svn_boolean_t must_exist, apr_pool_t *result_pool) +{ + svn_error_t *err = SVN_NO_ERROR; + svn_stream_t *stream; + apr_pool_t *scratch_pool = svn_pool_create(result_pool); + + err = svn_stream_open_readonly(&stream, file, scratch_pool, scratch_pool); + + if (! must_exist && err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + err = svn_config__parse_stream(cfg, stream, result_pool, scratch_pool); + + if (err != SVN_NO_ERROR) + { + /* Add the filename to the error stack. */ + err = svn_error_createf(err->apr_err, err, + "Error while parsing config file: %s:", + svn_dirent_local_style(file, scratch_pool)); + } + + /* Close the streams (and other cleanup): */ + svn_pool_destroy(scratch_pool); + + return err; +} + +svn_error_t * +svn_config__parse_stream(svn_config_t *cfg, svn_stream_t *stream, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) +{ + parse_context_t *ctx; + int ch, count; + + ctx = apr_palloc(scratch_pool, sizeof(*ctx)); + + ctx->cfg = cfg; + ctx->stream = stream; + ctx->line = 1; + ctx->ungotten_char = EOF; + ctx->section = svn_stringbuf_create_empty(scratch_pool); + ctx->option = svn_stringbuf_create_empty(scratch_pool); + ctx->value = svn_stringbuf_create_empty(scratch_pool); + ctx->buffer_pos = 0; + ctx->buffer_size = 0; + + do + { + SVN_ERR(skip_whitespace(ctx, &ch, &count)); + + switch (ch) + { + case '[': /* Start of section header */ + if (count == 0) + SVN_ERR(parse_section_name(&ch, ctx, scratch_pool)); + else + return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, + "line %d: Section header" + " must start in the first column", + ctx->line); + break; + + case '#': /* Comment */ + if (count == 0) + { + SVN_ERR(skip_to_eoln(ctx, &ch)); + ++(ctx->line); + } + else + return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, + "line %d: Comment" + " must start in the first column", + ctx->line); + break; + + case '\n': /* Empty line */ + ++(ctx->line); + break; + + case EOF: /* End of file or read error */ + break; + + default: + if (svn_stringbuf_isempty(ctx->section)) + return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, + "line %d: Section header expected", + ctx->line); + else if (count != 0) + return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, + "line %d: Option expected", + ctx->line); + else + SVN_ERR(parse_option(&ch, ctx, scratch_pool)); + break; + } + } + while (ch != EOF); + + return SVN_NO_ERROR; +} + + +/* Helper for ensure_auth_dirs: create SUBDIR under AUTH_DIR, iff + SUBDIR does not already exist, but ignore any errors. Use POOL for + temporary allocation. */ +static void +ensure_auth_subdir(const char *auth_dir, + const char *subdir, + apr_pool_t *pool) +{ + svn_error_t *err; + const char *subdir_full_path; + svn_node_kind_t kind; + + subdir_full_path = svn_dirent_join(auth_dir, subdir, pool); + err = svn_io_check_path(subdir_full_path, &kind, pool); + if (err || kind == svn_node_none) + { + svn_error_clear(err); + svn_error_clear(svn_io_dir_make(subdir_full_path, APR_OS_DEFAULT, pool)); + } +} + +/* Helper for svn_config_ensure: see if ~/.subversion/auth/ and its + subdirs exist, try to create them, but don't throw errors on + failure. PATH is assumed to be a path to the user's private config + directory. */ +static void +ensure_auth_dirs(const char *path, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + const char *auth_dir; + svn_error_t *err; + + /* Ensure ~/.subversion/auth/ */ + auth_dir = svn_dirent_join(path, SVN_CONFIG__AUTH_SUBDIR, pool); + err = svn_io_check_path(auth_dir, &kind, pool); + if (err || kind == svn_node_none) + { + svn_error_clear(err); + /* 'chmod 700' permissions: */ + err = svn_io_dir_make(auth_dir, + (APR_UREAD | APR_UWRITE | APR_UEXECUTE), + pool); + if (err) + { + /* Don't try making subdirs if we can't make the top-level dir. */ + svn_error_clear(err); + return; + } + } + + /* If a provider exists that wants to store credentials in + ~/.subversion, a subdirectory for the cred_kind must exist. */ + ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SIMPLE, pool); + ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_USERNAME, pool); + ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SSL_SERVER_TRUST, pool); + ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, pool); +} + + +svn_error_t * +svn_config_ensure(const char *config_dir, apr_pool_t *pool) +{ + const char *path; + svn_node_kind_t kind; + svn_error_t *err; + + /* Ensure that the user-specific config directory exists. */ + SVN_ERR(svn_config_get_user_config_path(&path, config_dir, NULL, pool)); + + if (! path) + return SVN_NO_ERROR; + + err = svn_io_check_resolved_path(path, &kind, pool); + if (err) + { + /* Don't throw an error, but don't continue. */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + + if (kind == svn_node_none) + { + err = svn_io_dir_make(path, APR_OS_DEFAULT, pool); + if (err) + { + /* Don't throw an error, but don't continue. */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + } + else if (kind == svn_node_file) + { + /* Somebody put a file where the config directory should be. + Wacky. Let's bail. */ + return SVN_NO_ERROR; + } + + /* Else, there's a configuration directory. */ + + /* If we get errors trying to do things below, just stop and return + success. There's no _need_ to init a config directory if + something's preventing it. */ + + /** If non-existent, try to create a number of auth/ subdirectories. */ + ensure_auth_dirs(path, pool); + + /** Ensure that the `README.txt' file exists. **/ + SVN_ERR(svn_config_get_user_config_path + (&path, config_dir, SVN_CONFIG__USR_README_FILE, pool)); + + if (! path) /* highly unlikely, since a previous call succeeded */ + return SVN_NO_ERROR; + + err = svn_io_check_path(path, &kind, pool); + if (err) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + + if (kind == svn_node_none) + { + apr_file_t *f; + const char *contents = + "This directory holds run-time configuration information for Subversion" NL + "clients. The configuration files all share the same syntax, but you" NL + "should examine a particular file to learn what configuration" NL + "directives are valid for that file." NL + "" NL + "The syntax is standard INI format:" NL + "" NL + " - Empty lines, and lines starting with '#', are ignored." NL + " The first significant line in a file must be a section header." NL + "" NL + " - A section starts with a section header, which must start in" NL + " the first column:" NL + "" NL + " [section-name]" NL + "" NL + " - An option, which must always appear within a section, is a pair" NL + " (name, value). There are two valid forms for defining an" NL + " option, both of which must start in the first column:" NL + "" NL + " name: value" NL + " name = value" NL + "" NL + " Whitespace around the separator (:, =) is optional." NL + "" NL + " - Section and option names are case-insensitive, but case is" NL + " preserved." NL + "" NL + " - An option's value may be broken into several lines. The value" NL + " continuation lines must start with at least one whitespace." NL + " Trailing whitespace in the previous line, the newline character" NL + " and the leading whitespace in the continuation line is compressed" NL + " into a single space character." NL + "" NL + " - All leading and trailing whitespace around a value is trimmed," NL + " but the whitespace within a value is preserved, with the" NL + " exception of whitespace around line continuations, as" NL + " described above." NL + "" NL + " - When a value is a boolean, any of the following strings are" NL + " recognised as truth values (case does not matter):" NL + "" NL + " true false" NL + " yes no" NL + " on off" NL + " 1 0" NL + "" NL + " - When a value is a list, it is comma-separated. Again, the" NL + " whitespace around each element of the list is trimmed." NL + "" NL + " - Option values may be expanded within a value by enclosing the" NL + " option name in parentheses, preceded by a percent sign and" NL + " followed by an 's':" NL + "" NL + " %(name)s" NL + "" NL + " The expansion is performed recursively and on demand, during" NL + " svn_option_get. The name is first searched for in the same" NL + " section, then in the special [DEFAULT] section. If the name" NL + " is not found, the whole '%(name)s' placeholder is left" NL + " unchanged." NL + "" NL + " Any modifications to the configuration data invalidate all" NL + " previously expanded values, so that the next svn_option_get" NL + " will take the modifications into account." NL + "" NL + "The syntax of the configuration files is a subset of the one used by" NL + "Python's ConfigParser module; see" NL + "" NL + " http://www.python.org/doc/current/lib/module-ConfigParser.html" NL + "" NL + "Configuration data in the Windows registry" NL + "==========================================" NL + "" NL + "On Windows, configuration data may also be stored in the registry. The" NL + "functions svn_config_read and svn_config_merge will read from the" NL + "registry when passed file names of the form:" NL + "" NL + " REGISTRY:<hive>/path/to/config-key" NL + "" NL + "The REGISTRY: prefix must be in upper case. The <hive> part must be" NL + "one of:" NL + "" NL + " HKLM for HKEY_LOCAL_MACHINE" NL + " HKCU for HKEY_CURRENT_USER" NL + "" NL + "The values in config-key represent the options in the [DEFAULT] section."NL + "The keys below config-key represent other sections, and their values" NL + "represent the options. Only values of type REG_SZ whose name doesn't" NL + "start with a '#' will be used; other values, as well as the keys'" NL + "default values, will be ignored." NL + "" NL + "" NL + "File locations" NL + "==============" NL + "" NL + "Typically, Subversion uses two config directories, one for site-wide" NL + "configuration," NL + "" NL + " Unix:" NL + " /etc/subversion/servers" NL + " /etc/subversion/config" NL + " /etc/subversion/hairstyles" NL + " Windows:" NL + " %ALLUSERSPROFILE%\\Application Data\\Subversion\\servers" NL + " %ALLUSERSPROFILE%\\Application Data\\Subversion\\config" NL + " %ALLUSERSPROFILE%\\Application Data\\Subversion\\hairstyles" NL + " REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Servers" NL + " REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Config" NL + " REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Hairstyles" NL + "" NL + "and one for per-user configuration:" NL + "" NL + " Unix:" NL + " ~/.subversion/servers" NL + " ~/.subversion/config" NL + " ~/.subversion/hairstyles" NL + " Windows:" NL + " %APPDATA%\\Subversion\\servers" NL + " %APPDATA%\\Subversion\\config" NL + " %APPDATA%\\Subversion\\hairstyles" NL + " REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Servers" NL + " REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Config" NL + " REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Hairstyles" NL + "" NL; + + err = svn_io_file_open(&f, path, + (APR_WRITE | APR_CREATE | APR_EXCL), + APR_OS_DEFAULT, + pool); + + if (! err) + { + SVN_ERR(svn_io_file_write_full(f, contents, + strlen(contents), NULL, pool)); + SVN_ERR(svn_io_file_close(f, pool)); + } + + svn_error_clear(err); + } + + /** Ensure that the `servers' file exists. **/ + SVN_ERR(svn_config_get_user_config_path + (&path, config_dir, SVN_CONFIG_CATEGORY_SERVERS, pool)); + + if (! path) /* highly unlikely, since a previous call succeeded */ + return SVN_NO_ERROR; + + err = svn_io_check_path(path, &kind, pool); + if (err) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + + if (kind == svn_node_none) + { + apr_file_t *f; + const char *contents = + "### This file specifies server-specific parameters," NL + "### including HTTP proxy information, HTTP timeout settings," NL + "### and authentication settings." NL + "###" NL + "### The currently defined server options are:" NL + "### http-proxy-host Proxy host for HTTP connection" NL + "### http-proxy-port Port number of proxy host service" NL + "### http-proxy-username Username for auth to proxy service"NL + "### http-proxy-password Password for auth to proxy service"NL + "### http-proxy-exceptions List of sites that do not use proxy" + NL + "### http-timeout Timeout for HTTP requests in seconds" + NL + "### http-compression Whether to compress HTTP requests" NL + "### http-max-connections Maximum number of parallel server" NL + "### connections to use for any given" NL + "### HTTP operation." NL + "### neon-debug-mask Debug mask for Neon HTTP library" NL + "### ssl-authority-files List of files, each of a trusted CA" + NL + "### ssl-trust-default-ca Trust the system 'default' CAs" NL + "### ssl-client-cert-file PKCS#12 format client certificate file" + NL + "### ssl-client-cert-password Client Key password, if needed." NL + "### ssl-pkcs11-provider Name of PKCS#11 provider to use." NL + "### http-library Which library to use for http/https" + NL + "### connections." NL + "### http-bulk-updates Whether to request bulk update" NL + "### responses or to fetch each file" NL + "### in an individual request. " NL + "### store-passwords Specifies whether passwords used" NL + "### to authenticate against a" NL + "### Subversion server may be cached" NL + "### to disk in any way." NL +#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + "### store-plaintext-passwords Specifies whether passwords may" NL + "### be cached on disk unencrypted." NL +#endif + "### store-ssl-client-cert-pp Specifies whether passphrase used" NL + "### to authenticate against a client" NL + "### certificate may be cached to disk" NL + "### in any way" NL +#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + "### store-ssl-client-cert-pp-plaintext" NL + "### Specifies whether client cert" NL + "### passphrases may be cached on disk" NL + "### unencrypted (i.e., as plaintext)." NL +#endif + "### store-auth-creds Specifies whether any auth info" NL + "### (passwords, server certs, etc.)" NL + "### may be cached to disk." NL + "### username Specifies the default username." NL + "###" NL + "### Set store-passwords to 'no' to avoid storing passwords on disk" NL + "### in any way, including in password stores. It defaults to" NL + "### 'yes', but Subversion will never save your password to disk in" NL + "### plaintext unless explicitly configured to do so." NL + "### Note that this option only prevents saving of *new* passwords;" NL + "### it doesn't invalidate existing passwords. (To do that, remove" NL + "### the cache files by hand as described in the Subversion book.)" NL + "###" NL +#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + "### Set store-plaintext-passwords to 'no' to avoid storing" NL + "### passwords in unencrypted form in the auth/ area of your config" NL + "### directory. Set it to 'yes' to allow Subversion to store" NL + "### unencrypted passwords in the auth/ area. The default is" NL + "### 'ask', which means that Subversion will ask you before" NL + "### saving a password to disk in unencrypted form. Note that" NL + "### this option has no effect if either 'store-passwords' or " NL + "### 'store-auth-creds' is set to 'no'." NL + "###" NL +#endif + "### Set store-ssl-client-cert-pp to 'no' to avoid storing ssl" NL + "### client certificate passphrases in the auth/ area of your" NL + "### config directory. It defaults to 'yes', but Subversion will" NL + "### never save your passphrase to disk in plaintext unless" NL + "### explicitly configured to do so." NL + "###" NL + "### Note store-ssl-client-cert-pp only prevents the saving of *new*"NL + "### passphrases; it doesn't invalidate existing passphrases. To do"NL + "### that, remove the cache files by hand as described in the" NL + "### Subversion book at http://svnbook.red-bean.com/nightly/en/\\" NL + "### svn.serverconfig.netmodel.html\\" NL + "### #svn.serverconfig.netmodel.credcache" NL + "###" NL +#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + "### Set store-ssl-client-cert-pp-plaintext to 'no' to avoid storing"NL + "### passphrases in unencrypted form in the auth/ area of your" NL + "### config directory. Set it to 'yes' to allow Subversion to" NL + "### store unencrypted passphrases in the auth/ area. The default" NL + "### is 'ask', which means that Subversion will prompt before" NL + "### saving a passphrase to disk in unencrypted form. Note that" NL + "### this option has no effect if either 'store-auth-creds' or " NL + "### 'store-ssl-client-cert-pp' is set to 'no'." NL + "###" NL +#endif + "### Set store-auth-creds to 'no' to avoid storing any Subversion" NL + "### credentials in the auth/ area of your config directory." NL + "### Note that this includes SSL server certificates." NL + "### It defaults to 'yes'. Note that this option only prevents" NL + "### saving of *new* credentials; it doesn't invalidate existing" NL + "### caches. (To do that, remove the cache files by hand.)" NL + "###" NL + "### HTTP timeouts, if given, are specified in seconds. A timeout" NL + "### of 0, i.e. zero, causes a builtin default to be used." NL + "###" NL + "### Most users will not need to explicitly set the http-library" NL + "### option, but valid values for the option include:" NL + "### 'serf': Serf-based module (Subversion 1.5 - present)" NL + "### 'neon': Neon-based module (Subversion 1.0 - 1.7)" NL + "### Availability of these modules may depend on your specific" NL + "### Subversion distribution." NL + "###" NL + "### The commented-out examples below are intended only to" NL + "### demonstrate how to use this file; any resemblance to actual" NL + "### servers, living or dead, is entirely coincidental." NL + "" NL + "### In the 'groups' section, the URL of the repository you're" NL + "### trying to access is matched against the patterns on the right." NL + "### If a match is found, the server options are taken from the" NL + "### section with the corresponding name on the left." NL + "" NL + "[groups]" NL + "# group1 = *.collab.net" NL + "# othergroup = repository.blarggitywhoomph.com" NL + "# thirdgroup = *.example.com" NL + "" NL + "### Information for the first group:" NL + "# [group1]" NL + "# http-proxy-host = proxy1.some-domain-name.com" NL + "# http-proxy-port = 80" NL + "# http-proxy-username = blah" NL + "# http-proxy-password = doubleblah" NL + "# http-timeout = 60" NL + "# neon-debug-mask = 130" NL +#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + "# store-plaintext-passwords = no" NL +#endif + "# username = harry" NL + "" NL + "### Information for the second group:" NL + "# [othergroup]" NL + "# http-proxy-host = proxy2.some-domain-name.com" NL + "# http-proxy-port = 9000" NL + "# No username and password for the proxy, so use the defaults below." + NL + "" NL + "### You can set default parameters in the 'global' section." NL + "### These parameters apply if no corresponding parameter is set in" NL + "### a specifically matched group as shown above. Thus, if you go" NL + "### through the same proxy server to reach every site on the" NL + "### Internet, you probably just want to put that server's" NL + "### information in the 'global' section and not bother with" NL + "### 'groups' or any other sections." NL + "###" NL + "### Most people might want to configure password caching" NL + "### parameters here, but you can also configure them per server" NL + "### group (per-group settings override global settings)." NL + "###" NL + "### If you go through a proxy for all but a few sites, you can" NL + "### list those exceptions under 'http-proxy-exceptions'. This only"NL + "### overrides defaults, not explicitly matched server names." NL + "###" NL + "### 'ssl-authority-files' is a semicolon-delimited list of files," NL + "### each pointing to a PEM-encoded Certificate Authority (CA) " NL + "### SSL certificate. See details above for overriding security " NL + "### due to SSL." NL + "[global]" NL + "# http-proxy-exceptions = *.exception.com, www.internal-site.org" NL + "# http-proxy-host = defaultproxy.whatever.com" NL + "# http-proxy-port = 7000" NL + "# http-proxy-username = defaultusername" NL + "# http-proxy-password = defaultpassword" NL + "# http-compression = no" NL + "# No http-timeout, so just use the builtin default." NL + "# No neon-debug-mask, so neon debugging is disabled." NL + "# ssl-authority-files = /path/to/CAcert.pem;/path/to/CAcert2.pem" NL + "#" NL + "# Password / passphrase caching parameters:" NL + "# store-passwords = no" NL + "# store-ssl-client-cert-pp = no" NL +#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + "# store-plaintext-passwords = no" NL + "# store-ssl-client-cert-pp-plaintext = no" NL +#endif + ; + + err = svn_io_file_open(&f, path, + (APR_WRITE | APR_CREATE | APR_EXCL), + APR_OS_DEFAULT, + pool); + + if (! err) + { + SVN_ERR(svn_io_file_write_full(f, contents, + strlen(contents), NULL, pool)); + SVN_ERR(svn_io_file_close(f, pool)); + } + + svn_error_clear(err); + } + + /** Ensure that the `config' file exists. **/ + SVN_ERR(svn_config_get_user_config_path + (&path, config_dir, SVN_CONFIG_CATEGORY_CONFIG, pool)); + + if (! path) /* highly unlikely, since a previous call succeeded */ + return SVN_NO_ERROR; + + err = svn_io_check_path(path, &kind, pool); + if (err) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + + if (kind == svn_node_none) + { + apr_file_t *f; + const char *contents = + "### This file configures various client-side behaviors." NL + "###" NL + "### The commented-out examples below are intended to demonstrate" NL + "### how to use this file." NL + "" NL + "### Section for authentication and authorization customizations." NL + "[auth]" NL + "### Set password stores used by Subversion. They should be" NL + "### delimited by spaces or commas. The order of values determines" NL + "### the order in which password stores are used." NL + "### Valid password stores:" NL + "### gnome-keyring (Unix-like systems)" NL + "### kwallet (Unix-like systems)" NL + "### gpg-agent (Unix-like systems)" NL + "### keychain (Mac OS X)" NL + "### windows-cryptoapi (Windows)" NL +#ifdef SVN_HAVE_KEYCHAIN_SERVICES + "# password-stores = keychain" NL +#elif defined(WIN32) && !defined(__MINGW32__) + "# password-stores = windows-cryptoapi" NL +#else + "# password-stores = gpg-agent,gnome-keyring,kwallet" NL +#endif + "### To disable all password stores, use an empty list:" NL + "# password-stores =" NL +#ifdef SVN_HAVE_KWALLET + "###" NL + "### Set KWallet wallet used by Subversion. If empty or unset," NL + "### then the default network wallet will be used." NL + "# kwallet-wallet =" NL + "###" NL + "### Include PID (Process ID) in Subversion application name when" NL + "### using KWallet. It defaults to 'no'." NL + "# kwallet-svn-application-name-with-pid = yes" NL +#endif + "###" NL + "### Set ssl-client-cert-file-prompt to 'yes' to cause the client" NL + "### to prompt for a path to a client cert file when the server" NL + "### requests a client cert but no client cert file is found in the" NL + "### expected place (see the 'ssl-client-cert-file' option in the" NL + "### 'servers' configuration file). Defaults to 'no'." NL + "# ssl-client-cert-file-prompt = no" NL + "###" NL + "### The rest of the [auth] section in this file has been deprecated." + NL + "### Both 'store-passwords' and 'store-auth-creds' can now be" NL + "### specified in the 'servers' file in your config directory" NL + "### and are documented there. Anything specified in this section " NL + "### is overridden by settings specified in the 'servers' file." NL + "# store-passwords = no" NL + "# store-auth-creds = no" NL + "" NL + "### Section for configuring external helper applications." NL + "[helpers]" NL + "### Set editor-cmd to the command used to invoke your text editor." NL + "### This will override the environment variables that Subversion" NL + "### examines by default to find this information ($EDITOR, " NL + "### et al)." NL + "# editor-cmd = editor (vi, emacs, notepad, etc.)" NL + "### Set diff-cmd to the absolute path of your 'diff' program." NL + "### This will override the compile-time default, which is to use" NL + "### Subversion's internal diff implementation." NL + "# diff-cmd = diff_program (diff, gdiff, etc.)" NL + "### Diff-extensions are arguments passed to an external diff" NL + "### program or to Subversion's internal diff implementation." NL + "### Set diff-extensions to override the default arguments ('-u')." NL + "# diff-extensions = -u -p" NL + "### Set diff3-cmd to the absolute path of your 'diff3' program." NL + "### This will override the compile-time default, which is to use" NL + "### Subversion's internal diff3 implementation." NL + "# diff3-cmd = diff3_program (diff3, gdiff3, etc.)" NL + "### Set diff3-has-program-arg to 'yes' if your 'diff3' program" NL + "### accepts the '--diff-program' option." NL + "# diff3-has-program-arg = [yes | no]" NL + "### Set merge-tool-cmd to the command used to invoke your external" NL + "### merging tool of choice. Subversion will pass 5 arguments to" NL + "### the specified command: base theirs mine merged wcfile" NL + "# merge-tool-cmd = merge_command" NL + "" NL + "### Section for configuring tunnel agents." NL + "[tunnels]" NL + "### Configure svn protocol tunnel schemes here. By default, only" NL + "### the 'ssh' scheme is defined. You can define other schemes to" NL + "### be used with 'svn+scheme://hostname/path' URLs. A scheme" NL + "### definition is simply a command, optionally prefixed by an" NL + "### environment variable name which can override the command if it" NL + "### is defined. The command (or environment variable) may contain" NL + "### arguments, using standard shell quoting for arguments with" NL + "### spaces. The command will be invoked as:" NL + "### <command> <hostname> svnserve -t" NL + "### (If the URL includes a username, then the hostname will be" NL + "### passed to the tunnel agent as <user>@<hostname>.) If the" NL + "### built-in ssh scheme were not predefined, it could be defined" NL + "### as:" NL + "# ssh = $SVN_SSH ssh -q" NL + "### If you wanted to define a new 'rsh' scheme, to be used with" NL + "### 'svn+rsh:' URLs, you could do so as follows:" NL + "# rsh = rsh" NL + "### Or, if you wanted to specify a full path and arguments:" NL + "# rsh = /path/to/rsh -l myusername" NL + "### On Windows, if you are specifying a full path to a command," NL + "### use a forward slash (/) or a paired backslash (\\\\) as the" NL + "### path separator. A single backslash will be treated as an" NL + "### escape for the following character." NL + "" NL + "### Section for configuring miscellaneous Subversion options." NL + "[miscellany]" NL + "### Set global-ignores to a set of whitespace-delimited globs" NL + "### which Subversion will ignore in its 'status' output, and" NL + "### while importing or adding files and directories." NL + "### '*' matches leading dots, e.g. '*.rej' matches '.foo.rej'." NL + "# global-ignores = " SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_1 NL + "# " SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_2 NL + "### Set log-encoding to the default encoding for log messages" NL + "# log-encoding = latin1" NL + "### Set use-commit-times to make checkout/update/switch/revert" NL + "### put last-committed timestamps on every file touched." NL + "# use-commit-times = yes" NL + "### Set no-unlock to prevent 'svn commit' from automatically" NL + "### releasing locks on files." NL + "# no-unlock = yes" NL + "### Set mime-types-file to a MIME type registry file, used to" NL + "### provide hints to Subversion's MIME type auto-detection" NL + "### algorithm." NL + "# mime-types-file = /path/to/mime.types" NL + "### Set preserved-conflict-file-exts to a whitespace-delimited" NL + "### list of patterns matching file extensions which should be" NL + "### preserved in generated conflict file names. By default," NL + "### conflict files use custom extensions." NL + "# preserved-conflict-file-exts = doc ppt xls od?" NL + "### Set enable-auto-props to 'yes' to enable automatic properties" NL + "### for 'svn add' and 'svn import', it defaults to 'no'." NL + "### Automatic properties are defined in the section 'auto-props'." NL + "# enable-auto-props = yes" NL + "### Set interactive-conflicts to 'no' to disable interactive" NL + "### conflict resolution prompting. It defaults to 'yes'." NL + "# interactive-conflicts = no" NL + "### Set memory-cache-size to define the size of the memory cache" NL + "### used by the client when accessing a FSFS repository via" NL + "### ra_local (the file:// scheme). The value represents the number" NL + "### of MB used by the cache." NL + "# memory-cache-size = 16" NL + "" NL + "### Section for configuring automatic properties." NL + "[auto-props]" NL + "### The format of the entries is:" NL + "### file-name-pattern = propname[=value][;propname[=value]...]" NL + "### The file-name-pattern can contain wildcards (such as '*' and" NL + "### '?'). All entries which match (case-insensitively) will be" NL + "### applied to the file. Note that auto-props functionality" NL + "### must be enabled, which is typically done by setting the" NL + "### 'enable-auto-props' option." NL + "# *.c = svn:eol-style=native" NL + "# *.cpp = svn:eol-style=native" NL + "# *.h = svn:keywords=Author Date Id Rev URL;svn:eol-style=native" NL + "# *.dsp = svn:eol-style=CRLF" NL + "# *.dsw = svn:eol-style=CRLF" NL + "# *.sh = svn:eol-style=native;svn:executable" NL + "# *.txt = svn:eol-style=native;svn:keywords=Author Date Id Rev URL;"NL + "# *.png = svn:mime-type=image/png" NL + "# *.jpg = svn:mime-type=image/jpeg" NL + "# Makefile = svn:eol-style=native" NL + "" NL + "### Section for configuring working copies." NL + "[working-copy]" NL + "### Set to a list of the names of specific clients that should use" NL + "### exclusive SQLite locking of working copies. This increases the"NL + "### performance of the client but prevents concurrent access by" NL + "### other clients. Third-party clients may also support this" NL + "### option." NL + "### Possible values:" NL + "### svn (the command line client)" NL + "# exclusive-locking-clients =" NL + "### Set to true to enable exclusive SQLite locking of working" NL + "### copies by all clients using the 1.8 APIs. Enabling this may" NL + "### cause some clients to fail to work properly. This does not have"NL + "### to be set for exclusive-locking-clients to work." NL + "# exclusive-locking = false" NL; + + err = svn_io_file_open(&f, path, + (APR_WRITE | APR_CREATE | APR_EXCL), + APR_OS_DEFAULT, + pool); + + if (! err) + { + SVN_ERR(svn_io_file_write_full(f, contents, + strlen(contents), NULL, pool)); + SVN_ERR(svn_io_file_close(f, pool)); + } + + svn_error_clear(err); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_config_get_user_config_path(const char **path, + const char *config_dir, + const char *fname, + apr_pool_t *pool) +{ + *path= NULL; + + /* Note that even if fname is null, svn_dirent_join_many will DTRT. */ + + if (config_dir) + { + *path = svn_dirent_join_many(pool, config_dir, fname, NULL); + return SVN_NO_ERROR; + } + +#ifdef WIN32 + { + const char *folder; + SVN_ERR(svn_config__win_config_path(&folder, FALSE, pool)); + *path = svn_dirent_join_many(pool, folder, + SVN_CONFIG__SUBDIRECTORY, fname, NULL); + } + +#elif defined(__HAIKU__) + { + char folder[B_PATH_NAME_LENGTH]; + + status_t error = find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, + folder, sizeof(folder)); + if (error) + return SVN_NO_ERROR; + + *path = svn_dirent_join_many(pool, folder, + SVN_CONFIG__USR_DIRECTORY, fname, NULL); + } +#else /* ! WIN32 && !__HAIKU__ */ + + { + const char *homedir = svn_user_get_homedir(pool); + if (! homedir) + return SVN_NO_ERROR; + *path = svn_dirent_join_many(pool, + svn_dirent_canonicalize(homedir, pool), + SVN_CONFIG__USR_DIRECTORY, fname, NULL); + } +#endif /* WIN32 */ + + return SVN_NO_ERROR; +} + diff --git a/subversion/libsvn_subr/config_impl.h b/subversion/libsvn_subr/config_impl.h new file mode 100644 index 000000000000..a3ab8fa1a614 --- /dev/null +++ b/subversion/libsvn_subr/config_impl.h @@ -0,0 +1,161 @@ +/* + * config_impl.h : private header for the config file implementation. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#ifndef SVN_LIBSVN_SUBR_CONFIG_IMPL_H +#define SVN_LIBSVN_SUBR_CONFIG_IMPL_H + +#define APR_WANT_STDIO +#include <apr_want.h> + +#include <apr_hash.h> +#include "svn_types.h" +#include "svn_string.h" +#include "svn_io.h" +#include "svn_config.h" +#include "svn_private_config.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* The configuration data. This is a superhash of sections and options. */ +struct svn_config_t +{ + /* Table of cfg_section_t's. */ + apr_hash_t *sections; + + /* Pool for hash tables, table entries and unexpanded values */ + apr_pool_t *pool; + + /* Pool for expanded values -- this is separate, so that we can + clear it when modifying the config data. */ + apr_pool_t *x_pool; + + /* Indicates that some values in the configuration have been expanded. */ + svn_boolean_t x_values; + + /* Temporary string used for lookups. (Using a stringbuf so that + frequent resetting is efficient.) */ + svn_stringbuf_t *tmp_key; + + /* Temporary value used for expanded default values in svn_config_get. + (Using a stringbuf so that frequent resetting is efficient.) */ + svn_stringbuf_t *tmp_value; + + /* Specifies whether section names are populated case sensitively. */ + svn_boolean_t section_names_case_sensitive; + + /* Specifies whether option names are populated case sensitively. */ + svn_boolean_t option_names_case_sensitive; +}; + + +/* Read sections and options from a file. */ +svn_error_t *svn_config__parse_file(svn_config_t *cfg, + const char *file, + svn_boolean_t must_exist, + apr_pool_t *pool); + +/* Read sections and options from a stream. */ +svn_error_t *svn_config__parse_stream(svn_config_t *cfg, + svn_stream_t *stream, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* The name of the magic [DEFAULT] section. */ +#define SVN_CONFIG__DEFAULT_SECTION "DEFAULT" + + +#ifdef WIN32 +/* Get the common or user-specific AppData folder */ +svn_error_t *svn_config__win_config_path(const char **folder, + int system_path, + apr_pool_t *pool); + +/* Read sections and options from the Windows Registry. */ +svn_error_t *svn_config__parse_registry(svn_config_t *cfg, + const char *file, + svn_boolean_t must_exist, + apr_pool_t *pool); + +/* ### It's unclear to me whether this registry stuff should get the + double underscore or not, and if so, where the extra underscore + would go. Thoughts? -kff */ +# define SVN_REGISTRY_PREFIX "REGISTRY:" +# define SVN_REGISTRY_PREFIX_LEN ((sizeof(SVN_REGISTRY_PREFIX)) - 1) +# define SVN_REGISTRY_HKLM "HKLM\\" +# define SVN_REGISTRY_HKLM_LEN ((sizeof(SVN_REGISTRY_HKLM)) - 1) +# define SVN_REGISTRY_HKCU "HKCU\\" +# define SVN_REGISTRY_HKCU_LEN ((sizeof(SVN_REGISTRY_HKCU)) - 1) +# define SVN_REGISTRY_PATH "Software\\Tigris.org\\Subversion\\" +# define SVN_REGISTRY_PATH_LEN ((sizeof(SVN_REGISTRY_PATH)) - 1) +# define SVN_REGISTRY_SYS_CONFIG_PATH \ + SVN_REGISTRY_PREFIX \ + SVN_REGISTRY_HKLM \ + SVN_REGISTRY_PATH +# define SVN_REGISTRY_USR_CONFIG_PATH \ + SVN_REGISTRY_PREFIX \ + SVN_REGISTRY_HKCU \ + SVN_REGISTRY_PATH +#endif /* WIN32 */ + +/* System-wide and configuration subdirectory names. + NOTE: Don't use these directly; call svn_config__sys_config_path() + or svn_config_get_user_config_path() instead. */ +#ifdef WIN32 +# define SVN_CONFIG__SUBDIRECTORY "Subversion" +#elif defined __HAIKU__ /* HAIKU */ +# define SVN_CONFIG__SYS_DIRECTORY "subversion" +# define SVN_CONFIG__USR_DIRECTORY "subversion" +#else /* ! WIN32 && ! __HAIKU__ */ +# define SVN_CONFIG__SYS_DIRECTORY "/etc/subversion" +# define SVN_CONFIG__USR_DIRECTORY ".subversion" +#endif /* WIN32 */ + +/* The description/instructions file in the config directory. */ +#define SVN_CONFIG__USR_README_FILE "README.txt" + +/* The name of the main authentication subdir in the config directory */ +#define SVN_CONFIG__AUTH_SUBDIR "auth" + +/* Set *PATH_P to the path to config file FNAME in the system + configuration area, allocated in POOL. If FNAME is NULL, set + *PATH_P to the directory name of the system config area, either + allocated in POOL or a static constant string. + + If the system configuration area cannot be located (possible under + Win32), set *PATH_P to NULL regardless of FNAME. */ +svn_error_t * +svn_config__sys_config_path(const char **path_p, + const char *fname, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_CONFIG_IMPL_H */ diff --git a/subversion/libsvn_subr/config_win.c b/subversion/libsvn_subr/config_win.c new file mode 100644 index 000000000000..0a1512916ea8 --- /dev/null +++ b/subversion/libsvn_subr/config_win.c @@ -0,0 +1,259 @@ +/* + * config_win.c : parsing configuration data from the registry + * + * ==================================================================== + * 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_private_config.h" + +#ifdef WIN32 +/* We must include windows.h ourselves or apr.h includes it for us with + many ignore options set. Including Winsock is required to resolve IPv6 + compilation errors. APR_HAVE_IPV6 is only defined after including + apr.h, so we can't detect this case here. */ + +#define WIN32_LEAN_AND_MEAN +/* winsock2.h includes windows.h */ +#include <winsock2.h> +#include <Ws2tcpip.h> + +#include <shlobj.h> + +#include <apr_file_info.h> + +#include "svn_error.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_utf.h" + +svn_error_t * +svn_config__win_config_path(const char **folder, int system_path, + apr_pool_t *pool) +{ + /* ### Adding CSIDL_FLAG_CREATE here, because those folders really + must exist. I'm not too sure about the SHGFP_TYPE_CURRENT + semancics, though; maybe we should use ..._DEFAULT instead? */ + const int csidl = ((system_path ? CSIDL_COMMON_APPDATA : CSIDL_APPDATA) + | CSIDL_FLAG_CREATE); + + WCHAR folder_ucs2[MAX_PATH]; + int inwords, outbytes, outlength; + char *folder_utf8; + + if (S_OK != SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, + folder_ucs2)) + return svn_error_create(SVN_ERR_BAD_FILENAME, NULL, + (system_path + ? "Can't determine the system config path" + : "Can't determine the user's config path")); + + /* ### When mapping from UCS-2 to UTF-8, we need at most 3 bytes + per wide char, plus extra space for the nul terminator. */ + inwords = lstrlenW(folder_ucs2); + outbytes = outlength = 3 * (inwords + 1); + + folder_utf8 = apr_palloc(pool, outlength); + + outbytes = WideCharToMultiByte(CP_UTF8, 0, folder_ucs2, inwords, + folder_utf8, outbytes, NULL, NULL); + + if (outbytes == 0) + return svn_error_wrap_apr(apr_get_os_error(), + "Can't convert config path to UTF-8"); + + /* Note that WideCharToMultiByte does _not_ terminate the + outgoing buffer. */ + folder_utf8[outbytes] = '\0'; + *folder = folder_utf8; + + return SVN_NO_ERROR; +} + + +#include "config_impl.h" + +/* ### These constants are insanely large, but (a) we want to avoid + reallocating strings if possible, and (b) the realloc logic might + not actually work -- you never know with Win32 ... */ +#define SVN_REG_DEFAULT_NAME_SIZE 2048 +#define SVN_REG_DEFAULT_VALUE_SIZE 8192 + +static svn_error_t * +parse_section(svn_config_t *cfg, HKEY hkey, const char *section, + svn_stringbuf_t *option, svn_stringbuf_t *value) +{ + DWORD option_len, type, index; + LONG err; + + /* Start with a reasonable size for the buffers. */ + svn_stringbuf_ensure(option, SVN_REG_DEFAULT_NAME_SIZE); + svn_stringbuf_ensure(value, SVN_REG_DEFAULT_VALUE_SIZE); + for (index = 0; ; ++index) + { + option_len = (DWORD)option->blocksize; + err = RegEnumValue(hkey, index, option->data, &option_len, + NULL, &type, NULL, NULL); + if (err == ERROR_NO_MORE_ITEMS) + break; + if (err == ERROR_INSUFFICIENT_BUFFER) + { + svn_stringbuf_ensure(option, option_len); + err = RegEnumValue(hkey, index, option->data, &option_len, + NULL, &type, NULL, NULL); + } + if (err != ERROR_SUCCESS) + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + "Can't enumerate registry values"); + + /* Ignore option names that start with '#', see + http://subversion.tigris.org/issues/show_bug.cgi?id=671 */ + if (type == REG_SZ && option->data[0] != '#') + { + DWORD value_len = (DWORD)value->blocksize; + err = RegQueryValueEx(hkey, option->data, NULL, NULL, + (LPBYTE)value->data, &value_len); + if (err == ERROR_MORE_DATA) + { + svn_stringbuf_ensure(value, value_len); + err = RegQueryValueEx(hkey, option->data, NULL, NULL, + (LPBYTE)value->data, &value_len); + } + if (err != ERROR_SUCCESS) + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + "Can't read registry value data"); + + svn_config_set(cfg, section, option->data, value->data); + } + } + + return SVN_NO_ERROR; +} + + + +/*** Exported interface. ***/ + +svn_error_t * +svn_config__parse_registry(svn_config_t *cfg, const char *file, + svn_boolean_t must_exist, apr_pool_t *pool) +{ + apr_pool_t *subpool; + svn_stringbuf_t *section, *option, *value; + svn_error_t *svn_err = SVN_NO_ERROR; + HKEY base_hkey, hkey; + DWORD index; + LONG err; + + if (0 == strncmp(file, SVN_REGISTRY_HKLM, SVN_REGISTRY_HKLM_LEN)) + { + base_hkey = HKEY_LOCAL_MACHINE; + file += SVN_REGISTRY_HKLM_LEN; + } + else if (0 == strncmp(file, SVN_REGISTRY_HKCU, SVN_REGISTRY_HKCU_LEN)) + { + base_hkey = HKEY_CURRENT_USER; + file += SVN_REGISTRY_HKCU_LEN; + } + else + { + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + "Unrecognised registry path '%s'", + svn_dirent_local_style(file, pool)); + } + + err = RegOpenKeyEx(base_hkey, file, 0, + KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, + &hkey); + if (err != ERROR_SUCCESS) + { + const int is_enoent = APR_STATUS_IS_ENOENT(APR_FROM_OS_ERROR(err)); + if (!is_enoent) + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + "Can't open registry key '%s'", + svn_dirent_local_style(file, pool)); + else if (must_exist && is_enoent) + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + "Can't find registry key '%s'", + svn_dirent_local_style(file, pool)); + else + return SVN_NO_ERROR; + } + + + subpool = svn_pool_create(pool); + section = svn_stringbuf_create_empty(subpool); + option = svn_stringbuf_create_empty(subpool); + value = svn_stringbuf_create_empty(subpool); + + /* The top-level values belong to the [DEFAULT] section */ + svn_err = parse_section(cfg, hkey, SVN_CONFIG__DEFAULT_SECTION, + option, value); + if (svn_err) + goto cleanup; + + /* Now enumerate the rest of the keys. */ + svn_stringbuf_ensure(section, SVN_REG_DEFAULT_NAME_SIZE); + for (index = 0; ; ++index) + { + DWORD section_len = (DWORD)section->blocksize; + HKEY sub_hkey; + + err = RegEnumKeyEx(hkey, index, section->data, §ion_len, + NULL, NULL, NULL, NULL); + if (err == ERROR_NO_MORE_ITEMS) + break; + if (err == ERROR_MORE_DATA) + { + svn_stringbuf_ensure(section, section_len); + err = RegEnumKeyEx(hkey, index, section->data, §ion_len, + NULL, NULL, NULL, NULL); + } + if (err != ERROR_SUCCESS) + { + svn_err = svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + "Can't enumerate registry keys"); + goto cleanup; + } + + err = RegOpenKeyEx(hkey, section->data, 0, + KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, + &sub_hkey); + if (err != ERROR_SUCCESS) + { + svn_err = svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + "Can't open existing subkey"); + goto cleanup; + } + + svn_err = parse_section(cfg, sub_hkey, section->data, option, value); + RegCloseKey(sub_hkey); + if (svn_err) + goto cleanup; + } + + cleanup: + RegCloseKey(hkey); + svn_pool_destroy(subpool); + return svn_err; +} + +#endif /* WIN32 */ diff --git a/subversion/libsvn_subr/crypto.c b/subversion/libsvn_subr/crypto.c new file mode 100644 index 000000000000..f3611a1e050a --- /dev/null +++ b/subversion/libsvn_subr/crypto.c @@ -0,0 +1,705 @@ +/* + * crypto.c : cryptographic routines + * + * ==================================================================== + * 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 "crypto.h" + +#ifdef SVN_HAVE_CRYPTO +#include <apr_random.h> +#include <apr_crypto.h> +#endif /* SVN_HAVE_CRYPTO */ + +#include "svn_types.h" +#include "svn_checksum.h" + +#include "svn_private_config.h" +#include "private/svn_atomic.h" + + +/* 1000 iterations is the recommended minimum, per RFC 2898, section 4.2. */ +#define NUM_ITERATIONS 1000 + + +/* Size (in bytes) of the random data we'll prepend to encrypted data. */ +#define RANDOM_PREFIX_LEN 4 + + +/* A structure for containing Subversion's cryptography-related bits + (so we can avoid passing around APR-isms outside this module). */ +struct svn_crypto__ctx_t { +#ifdef SVN_HAVE_CRYPTO + apr_crypto_t *crypto; /* APR cryptography context. */ + +#if 0 + /* ### For now, we will use apr_generate_random_bytes(). If we need + ### more strength, then we can set this member using + ### apr_random_standard_new(), then use + ### apr_generate_random_bytes() to generate entropy for seeding + ### apr_random_t. See httpd/server/core.c:ap_init_rng() */ + apr_random_t *rand; +#endif /* 0 */ +#else /* SVN_HAVE_CRYPTO */ + int unused_but_required_to_satisfy_c_compilers; +#endif /* SVN_HAVE_CRYPTO */ +}; + + + +/*** Helper Functions ***/ +#ifdef SVN_HAVE_CRYPTO + + +/* One-time initialization of the cryptography subsystem. */ +static volatile svn_atomic_t crypto_init_state = 0; + + +#define CRYPTO_INIT(scratch_pool) \ + SVN_ERR(svn_atomic__init_once(&crypto_init_state, \ + crypto_init, NULL, (scratch_pool))) + + +/* Initialize the APR cryptography subsystem (if available), using + ANY_POOL's ancestor root pool for the registration of cleanups, + shutdowns, etc. */ +/* Don't call this function directly! Use svn_atomic__init_once(). */ +static svn_error_t * +crypto_init(void *baton, apr_pool_t *any_pool) +{ + /* NOTE: this function will locate the topmost ancestor of ANY_POOL + for its cleanup handlers. We don't have to worry about ANY_POOL + being cleared. */ + apr_status_t apr_err = apr_crypto_init(any_pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Failed to initialize cryptography " + "subsystem")); + + return SVN_NO_ERROR; +} + + +/* If APU_ERR is non-NULL, create and return a Subversion error using + APR_ERR and APU_ERR. */ +static svn_error_t * +err_from_apu_err(apr_status_t apr_err, + const apu_err_t *apu_err) +{ + if (apu_err) + return svn_error_createf(apr_err, NULL, + _("code (%d), reason (\"%s\"), msg (\"%s\")"), + apu_err->rc, + apu_err->reason ? apu_err->reason : "", + apu_err->msg ? apu_err->msg : ""); + return SVN_NO_ERROR; +} + + +/* Generate a Subversion error which describes the state reflected by + APR_ERR and any crypto errors registered with CTX. */ +static svn_error_t * +crypto_error_create(svn_crypto__ctx_t *ctx, + apr_status_t apr_err, + const char *msg) +{ + const apu_err_t *apu_err; + apr_status_t rv = apr_crypto_error(&apu_err, ctx->crypto); + svn_error_t *child; + + /* Ugh. The APIs are a bit slippery, so be wary. */ + if (apr_err == APR_SUCCESS) + apr_err = APR_EGENERAL; + + if (rv == APR_SUCCESS) + child = err_from_apu_err(apr_err, apu_err); + else + child = svn_error_wrap_apr(rv, _("Fetching error from APR")); + + return svn_error_create(apr_err, child, msg); +} + + +/* Set RAND_BYTES to a block of bytes containing random data RAND_LEN + long and allocated from RESULT_POOL. */ +static svn_error_t * +get_random_bytes(const unsigned char **rand_bytes, + svn_crypto__ctx_t *ctx, + apr_size_t rand_len, + apr_pool_t *result_pool) +{ + apr_status_t apr_err; + unsigned char *bytes; + + bytes = apr_palloc(result_pool, rand_len); + apr_err = apr_generate_random_bytes(bytes, rand_len); + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, _("Error obtaining random data")); + + *rand_bytes = bytes; + return SVN_NO_ERROR; +} + + +/* Return an svn_string_t allocated from RESULT_POOL, with its .data + and .len members set to DATA and LEN, respective. + + WARNING: No lifetime management of DATA is offered here, so you + probably want to ensure that that information is allocated in a + sufficiently long-lived pool (such as, for example, RESULT_POOL). */ +static const svn_string_t * +wrap_as_string(const unsigned char *data, + apr_size_t len, + apr_pool_t *result_pool) +{ + svn_string_t *s = apr_palloc(result_pool, sizeof(*s)); + + s->data = (const char *)data; /* better already be in RESULT_POOL */ + s->len = len; + return s; +} + + +#endif /* SVN_HAVE_CRYPTO */ + + + +/*** Semi-public APIs ***/ + +/* Return TRUE iff Subversion's cryptographic support is available. */ +svn_boolean_t svn_crypto__is_available(void) +{ +#ifdef SVN_HAVE_CRYPTO + return TRUE; +#else /* SVN_HAVE_CRYPTO */ + return FALSE; +#endif /* SVN_HAVE_CRYPTO */ +} + + +/* Set CTX to a Subversion cryptography context allocated from + RESULT_POOL. */ +svn_error_t * +svn_crypto__context_create(svn_crypto__ctx_t **ctx, + apr_pool_t *result_pool) +{ +#ifdef SVN_HAVE_CRYPTO + apr_status_t apr_err; + const apu_err_t *apu_err = NULL; + apr_crypto_t *apr_crypto; + const apr_crypto_driver_t *driver; + + CRYPTO_INIT(result_pool); + + /* Load the crypto driver. + + ### TODO: For the sake of flexibility, should we use + ### APU_CRYPTO_RECOMMENDED_DRIVER instead of hard coding + ### "openssl" here? + + NOTE: Potential bugs in get_driver() imply we might get + APR_SUCCESS and NULL. Sigh. Just be a little more careful in + error generation here. */ + apr_err = apr_crypto_get_driver(&driver, "openssl", NULL, &apu_err, + result_pool); + if (apr_err != APR_SUCCESS) + return svn_error_create(apr_err, err_from_apu_err(apr_err, apu_err), + _("OpenSSL crypto driver error")); + if (driver == NULL) + return svn_error_create(APR_EGENERAL, + err_from_apu_err(APR_EGENERAL, apu_err), + _("Bad return value while loading crypto " + "driver")); + + apr_err = apr_crypto_make(&apr_crypto, driver, NULL, result_pool); + if (apr_err != APR_SUCCESS || apr_crypto == NULL) + return svn_error_create(apr_err, NULL, + _("Error creating OpenSSL crypto context")); + + /* Allocate and initialize our crypto context. */ + *ctx = apr_palloc(result_pool, sizeof(**ctx)); + (*ctx)->crypto = apr_crypto; + + return SVN_NO_ERROR; +#else /* SVN_HAVE_CRYPTO */ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + "Cryptographic support is not available"); +#endif /* SVN_HAVE_CRYPTO */ +} + + +svn_error_t * +svn_crypto__encrypt_password(const svn_string_t **ciphertext, + const svn_string_t **iv, + const svn_string_t **salt, + svn_crypto__ctx_t *ctx, + const char *password, + const svn_string_t *master, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ +#ifdef SVN_HAVE_CRYPTO + svn_error_t *err = SVN_NO_ERROR; + const unsigned char *salt_vector; + const unsigned char *iv_vector; + apr_size_t iv_len; + apr_crypto_key_t *key = NULL; + apr_status_t apr_err; + const unsigned char *prefix; + apr_crypto_block_t *block_ctx = NULL; + apr_size_t block_size; + unsigned char *assembled; + apr_size_t password_len, assembled_len = 0; + apr_size_t result_len; + unsigned char *result; + apr_size_t ignored_result_len = 0; + + SVN_ERR_ASSERT(ctx != NULL); + + /* Generate the salt. */ +#define SALT_LEN 8 + SVN_ERR(get_random_bytes(&salt_vector, ctx, SALT_LEN, result_pool)); + + /* Initialize the passphrase. */ + apr_err = apr_crypto_passphrase(&key, &iv_len, + master->data, master->len, + salt_vector, SALT_LEN, + APR_KEY_AES_256, APR_MODE_CBC, + FALSE /* doPad */, NUM_ITERATIONS, + ctx->crypto, + scratch_pool); + if (apr_err != APR_SUCCESS) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error creating derived key"))); + if (! key) + return svn_error_create(APR_EGENERAL, NULL, + _("Error creating derived key")); + if (iv_len == 0) + return svn_error_create(APR_EGENERAL, NULL, + _("Unexpected IV length returned")); + + /* Generate the proper length IV. */ + SVN_ERR(get_random_bytes(&iv_vector, ctx, iv_len, result_pool)); + + /* Initialize block encryption. */ + apr_err = apr_crypto_block_encrypt_init(&block_ctx, &iv_vector, key, + &block_size, scratch_pool); + if ((apr_err != APR_SUCCESS) || (! block_ctx)) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error initializing block encryption"))); + + /* Generate a 4-byte prefix. */ + SVN_ERR(get_random_bytes(&prefix, ctx, RANDOM_PREFIX_LEN, scratch_pool)); + + /* Combine our prefix, original password, and appropriate padding. + We won't bother padding if the prefix and password combined + perfectly align on the block boundary. If they don't, + however, we'll drop a NUL byte after the password and pad with + random stuff after that to the block boundary. */ + password_len = strlen(password); + assembled_len = RANDOM_PREFIX_LEN + password_len; + if ((assembled_len % block_size) == 0) + { + assembled = apr_palloc(scratch_pool, assembled_len); + memcpy(assembled, prefix, RANDOM_PREFIX_LEN); + memcpy(assembled + RANDOM_PREFIX_LEN, password, password_len); + } + else + { + const unsigned char *padding; + apr_size_t pad_len = block_size - (assembled_len % block_size) - 1; + + SVN_ERR(get_random_bytes(&padding, ctx, pad_len, scratch_pool)); + assembled_len = assembled_len + 1 + pad_len; + assembled = apr_palloc(scratch_pool, assembled_len); + memcpy(assembled, prefix, RANDOM_PREFIX_LEN); + memcpy(assembled + RANDOM_PREFIX_LEN, password, password_len); + *(assembled + RANDOM_PREFIX_LEN + password_len) = '\0'; + memcpy(assembled + RANDOM_PREFIX_LEN + password_len + 1, + padding, pad_len); + } + + /* Get the length that we need to allocate. */ + apr_err = apr_crypto_block_encrypt(NULL, &result_len, assembled, + assembled_len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error fetching result length")); + goto cleanup; + } + + /* Allocate our result buffer. */ + result = apr_palloc(result_pool, result_len); + + /* Encrypt the block. */ + apr_err = apr_crypto_block_encrypt(&result, &result_len, assembled, + assembled_len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error during block encryption")); + goto cleanup; + } + + /* Finalize the block encryption. Since we padded everything, this should + not produce any more encrypted output. */ + apr_err = apr_crypto_block_encrypt_finish(NULL, + &ignored_result_len, + block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error finalizing block encryption")); + goto cleanup; + } + + *ciphertext = wrap_as_string(result, result_len, result_pool); + *iv = wrap_as_string(iv_vector, iv_len, result_pool); + *salt = wrap_as_string(salt_vector, SALT_LEN, result_pool); + + cleanup: + apr_crypto_block_cleanup(block_ctx); + return err; +#else /* SVN_HAVE_CRYPTO */ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + "Cryptographic support is not available"); +#endif /* SVN_HAVE_CRYPTO */ +} + + +svn_error_t * +svn_crypto__decrypt_password(const char **plaintext, + svn_crypto__ctx_t *ctx, + const svn_string_t *ciphertext, + const svn_string_t *iv, + const svn_string_t *salt, + const svn_string_t *master, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ +#ifdef SVN_HAVE_CRYPTO + svn_error_t *err = SVN_NO_ERROR; + apr_status_t apr_err; + apr_crypto_block_t *block_ctx = NULL; + apr_size_t block_size, iv_len; + apr_crypto_key_t *key = NULL; + unsigned char *result; + apr_size_t result_len = 0, final_len = 0; + + /* Initialize the passphrase. */ + apr_err = apr_crypto_passphrase(&key, &iv_len, + master->data, master->len, + (unsigned char *)salt->data, salt->len, + APR_KEY_AES_256, APR_MODE_CBC, + FALSE /* doPad */, NUM_ITERATIONS, + ctx->crypto, scratch_pool); + if (apr_err != APR_SUCCESS) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error creating derived key"))); + if (! key) + return svn_error_create(APR_EGENERAL, NULL, + _("Error creating derived key")); + if (iv_len == 0) + return svn_error_create(APR_EGENERAL, NULL, + _("Unexpected IV length returned")); + if (iv_len != iv->len) + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Provided IV has incorrect length")); + + apr_err = apr_crypto_block_decrypt_init(&block_ctx, &block_size, + (unsigned char *)iv->data, + key, scratch_pool); + if ((apr_err != APR_SUCCESS) || (! block_ctx)) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error initializing block decryption"))); + + apr_err = apr_crypto_block_decrypt(NULL, &result_len, + (unsigned char *)ciphertext->data, + ciphertext->len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error fetching result length")); + goto cleanup; + } + + result = apr_palloc(scratch_pool, result_len); + apr_err = apr_crypto_block_decrypt(&result, &result_len, + (unsigned char *)ciphertext->data, + ciphertext->len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error during block decryption")); + goto cleanup; + } + + apr_err = apr_crypto_block_decrypt_finish(result + result_len, &final_len, + block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error finalizing block decryption")); + goto cleanup; + } + + /* Copy the non-random bits of the resulting plaintext, skipping the + prefix and ignoring any trailing padding. */ + *plaintext = apr_pstrndup(result_pool, + (const char *)(result + RANDOM_PREFIX_LEN), + result_len + final_len - RANDOM_PREFIX_LEN); + + cleanup: + apr_crypto_block_cleanup(block_ctx); + return err; +#else /* SVN_HAVE_CRYPTO */ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + "Cryptographic support is not available"); +#endif /* SVN_HAVE_CRYPTO */ +} + + +svn_error_t * +svn_crypto__generate_secret_checktext(const svn_string_t **ciphertext, + const svn_string_t **iv, + const svn_string_t **salt, + const char **checktext, + svn_crypto__ctx_t *ctx, + const svn_string_t *master, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ +#ifdef SVN_HAVE_CRYPTO + svn_error_t *err = SVN_NO_ERROR; + const unsigned char *salt_vector; + const unsigned char *iv_vector; + const unsigned char *stuff_vector; + apr_size_t iv_len; + apr_crypto_key_t *key = NULL; + apr_status_t apr_err; + apr_crypto_block_t *block_ctx = NULL; + apr_size_t block_size; + apr_size_t result_len; + unsigned char *result; + apr_size_t ignored_result_len = 0; + apr_size_t stuff_len; + svn_checksum_t *stuff_sum; + + SVN_ERR_ASSERT(ctx != NULL); + + /* Generate the salt. */ + SVN_ERR(get_random_bytes(&salt_vector, ctx, SALT_LEN, result_pool)); + + /* Initialize the passphrase. */ + apr_err = apr_crypto_passphrase(&key, &iv_len, + master->data, master->len, + salt_vector, SALT_LEN, + APR_KEY_AES_256, APR_MODE_CBC, + FALSE /* doPad */, NUM_ITERATIONS, + ctx->crypto, + scratch_pool); + if (apr_err != APR_SUCCESS) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error creating derived key"))); + if (! key) + return svn_error_create(APR_EGENERAL, NULL, + _("Error creating derived key")); + if (iv_len == 0) + return svn_error_create(APR_EGENERAL, NULL, + _("Unexpected IV length returned")); + + /* Generate the proper length IV. */ + SVN_ERR(get_random_bytes(&iv_vector, ctx, iv_len, result_pool)); + + /* Initialize block encryption. */ + apr_err = apr_crypto_block_encrypt_init(&block_ctx, &iv_vector, key, + &block_size, scratch_pool); + if ((apr_err != APR_SUCCESS) || (! block_ctx)) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error initializing block encryption"))); + + /* Generate a blob of random data, block-aligned per the + requirements of the encryption algorithm, but with a minimum size + of our choosing. */ +#define MIN_STUFF_LEN 32 + if (MIN_STUFF_LEN % block_size) + stuff_len = MIN_STUFF_LEN + (block_size - (MIN_STUFF_LEN % block_size)); + else + stuff_len = MIN_STUFF_LEN; + SVN_ERR(get_random_bytes(&stuff_vector, ctx, stuff_len, scratch_pool)); + + /* ### FIXME: This should be a SHA-256. */ + SVN_ERR(svn_checksum(&stuff_sum, svn_checksum_sha1, stuff_vector, + stuff_len, scratch_pool)); + + /* Get the length that we need to allocate. */ + apr_err = apr_crypto_block_encrypt(NULL, &result_len, stuff_vector, + stuff_len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error fetching result length")); + goto cleanup; + } + + /* Allocate our result buffer. */ + result = apr_palloc(result_pool, result_len); + + /* Encrypt the block. */ + apr_err = apr_crypto_block_encrypt(&result, &result_len, stuff_vector, + stuff_len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error during block encryption")); + goto cleanup; + } + + /* Finalize the block encryption. Since we padded everything, this should + not produce any more encrypted output. */ + apr_err = apr_crypto_block_encrypt_finish(NULL, + &ignored_result_len, + block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error finalizing block encryption")); + goto cleanup; + } + + *ciphertext = wrap_as_string(result, result_len, result_pool); + *iv = wrap_as_string(iv_vector, iv_len, result_pool); + *salt = wrap_as_string(salt_vector, SALT_LEN, result_pool); + *checktext = svn_checksum_to_cstring(stuff_sum, result_pool); + + cleanup: + apr_crypto_block_cleanup(block_ctx); + return err; +#else /* SVN_HAVE_CRYPTO */ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + "Cryptographic support is not available"); +#endif /* SVN_HAVE_CRYPTO */ +} + + +svn_error_t * +svn_crypto__verify_secret(svn_boolean_t *is_valid, + svn_crypto__ctx_t *ctx, + const svn_string_t *master, + const svn_string_t *ciphertext, + const svn_string_t *iv, + const svn_string_t *salt, + const char *checktext, + apr_pool_t *scratch_pool) +{ +#ifdef SVN_HAVE_CRYPTO + svn_error_t *err = SVN_NO_ERROR; + apr_status_t apr_err; + apr_crypto_block_t *block_ctx = NULL; + apr_size_t block_size, iv_len; + apr_crypto_key_t *key = NULL; + unsigned char *result; + apr_size_t result_len = 0, final_len = 0; + svn_checksum_t *result_sum; + + *is_valid = FALSE; + + /* Initialize the passphrase. */ + apr_err = apr_crypto_passphrase(&key, &iv_len, + master->data, master->len, + (unsigned char *)salt->data, salt->len, + APR_KEY_AES_256, APR_MODE_CBC, + FALSE /* doPad */, NUM_ITERATIONS, + ctx->crypto, scratch_pool); + if (apr_err != APR_SUCCESS) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error creating derived key"))); + if (! key) + return svn_error_create(APR_EGENERAL, NULL, + _("Error creating derived key")); + if (iv_len == 0) + return svn_error_create(APR_EGENERAL, NULL, + _("Unexpected IV length returned")); + if (iv_len != iv->len) + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Provided IV has incorrect length")); + + apr_err = apr_crypto_block_decrypt_init(&block_ctx, &block_size, + (unsigned char *)iv->data, + key, scratch_pool); + if ((apr_err != APR_SUCCESS) || (! block_ctx)) + return svn_error_trace(crypto_error_create( + ctx, apr_err, + _("Error initializing block decryption"))); + + apr_err = apr_crypto_block_decrypt(NULL, &result_len, + (unsigned char *)ciphertext->data, + ciphertext->len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error fetching result length")); + goto cleanup; + } + + result = apr_palloc(scratch_pool, result_len); + apr_err = apr_crypto_block_decrypt(&result, &result_len, + (unsigned char *)ciphertext->data, + ciphertext->len, block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error during block decryption")); + goto cleanup; + } + + apr_err = apr_crypto_block_decrypt_finish(result + result_len, &final_len, + block_ctx); + if (apr_err != APR_SUCCESS) + { + err = crypto_error_create(ctx, apr_err, + _("Error finalizing block decryption")); + goto cleanup; + } + + /* ### FIXME: This should be a SHA-256. */ + SVN_ERR(svn_checksum(&result_sum, svn_checksum_sha1, result, + result_len + final_len, scratch_pool)); + + *is_valid = strcmp(checktext, + svn_checksum_to_cstring(result_sum, scratch_pool)) == 0; + + cleanup: + apr_crypto_block_cleanup(block_ctx); + return err; +#else /* SVN_HAVE_CRYPTO */ + *is_valid = FALSE; + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + "Cryptographic support is not available"); +#endif /* SVN_HAVE_CRYPTO */ +} diff --git a/subversion/libsvn_subr/crypto.h b/subversion/libsvn_subr/crypto.h new file mode 100644 index 000000000000..5e7be8699355 --- /dev/null +++ b/subversion/libsvn_subr/crypto.h @@ -0,0 +1,141 @@ +/* + * crypto.h : cryptographic routines + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_CRYPTO_H +#define SVN_LIBSVN_SUBR_CRYPTO_H + +/* Test for APR crypto and RNG support */ +#undef SVN_HAVE_CRYPTO +#include <apr.h> +#include <apu.h> +#if APR_HAS_RANDOM +#if defined(APU_HAVE_CRYPTO) && APU_HAVE_CRYPTO +#define SVN_HAVE_CRYPTO +#endif +#endif + +#include "svn_types.h" +#include "svn_string.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Opaque context for cryptographic operations. */ +typedef struct svn_crypto__ctx_t svn_crypto__ctx_t; + + +/* Return TRUE iff Subversion's cryptographic support is available. */ +svn_boolean_t svn_crypto__is_available(void); + + +/* Set *CTX to new Subversion cryptographic context, based on an + APR-managed OpenSSL cryptography context object allocated + within RESULT_POOL. */ +/* ### TODO: Should this be something done once with the resulting + ### svn_crypto__ctx_t object stored in svn_client_ctx_t? */ +svn_error_t * +svn_crypto__context_create(svn_crypto__ctx_t **ctx, + apr_pool_t *result_pool); + + +/* Using a PBKDF2 derivative key based on MASTER, encrypt PLAINTEXT. + The salt used for PBKDF2 is returned in SALT, and the IV used for + the (AES-256/CBC) encryption is returned in IV. The resulting + encrypted data is returned in CIPHERTEXT. + + Note that MASTER may be the plaintext obtained from the user or + some other OS-provided cryptographic store, or it can be a derivation + such as SHA1(plaintext). As long as the same octets are passed to + the decryption function, everything works just fine. (the SHA1 + approach is suggested, to avoid keeping the plaintext master in + the process' memory space) */ +svn_error_t * +svn_crypto__encrypt_password(const svn_string_t **ciphertext, + const svn_string_t **iv, + const svn_string_t **salt, + svn_crypto__ctx_t *ctx, + const char *plaintext, + const svn_string_t *master, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Given the CIPHERTEXT which was encrypted using (AES-256/CBC) with + initialization vector given by IV, and a key derived using PBKDF2 + with SALT and MASTER... return the decrypted password in PLAINTEXT. */ +svn_error_t * +svn_crypto__decrypt_password(const char **plaintext, + svn_crypto__ctx_t *ctx, + const svn_string_t *ciphertext, + const svn_string_t *iv, + const svn_string_t *salt, + const svn_string_t *master, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Generate the stuff Subversion needs to store in order to validate a + user-provided MASTER password: + + Set *CIPHERTEXT to a block of encrypted data. + + Set *IV and *SALT to the initialization vector and salt used for + encryption. + + Set *CHECKTEXT to the check text used for validation. + + CTX is a Subversion cryptographic context. MASTER is the + encryption secret. +*/ +svn_error_t * +svn_crypto__generate_secret_checktext(const svn_string_t **ciphertext, + const svn_string_t **iv, + const svn_string_t **salt, + const char **checktext, + svn_crypto__ctx_t *ctx, + const svn_string_t *master, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *IS_VALID to TRUE iff the encryption secret MASTER successfully + validates using Subversion cryptographic context CTX against + CIPHERTEXT, IV, SALT, and CHECKTEXT (which where probably generated + via previous call to svn_crypto__generate_secret_checktext()). + + Use SCRATCH_POOL for necessary allocations. */ +svn_error_t * +svn_crypto__verify_secret(svn_boolean_t *is_valid, + svn_crypto__ctx_t *ctx, + const svn_string_t *master, + const svn_string_t *ciphertext, + const svn_string_t *iv, + const svn_string_t *salt, + const char *checktext, + apr_pool_t *scratch_pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_CRYPTO_H */ diff --git a/subversion/libsvn_subr/ctype.c b/subversion/libsvn_subr/ctype.c new file mode 100644 index 000000000000..0dd5d5b15d05 --- /dev/null +++ b/subversion/libsvn_subr/ctype.c @@ -0,0 +1,319 @@ +/* + * ctype.c: Character classification routines + * + * ==================================================================== + * 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_ctype.h" + +const apr_uint32_t svn_ctype_table_internal[256] = + { + /* **** DO NOT EDIT! **** + This table was generated by genctype.py, make changes there. */ + /* nul */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* soh */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* stx */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* etx */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* eot */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* enq */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* ack */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* bel */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* bs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* ht */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE, + /* nl */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE, + /* vt */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE, + /* np */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE, + /* cr */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE, + /* so */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* si */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* dle */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* dc1 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* dc2 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* dc3 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* dc4 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* nak */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* syn */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* etb */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* can */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* em */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* sub */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* esc */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* fs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* gs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* rs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* us */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* sp */ SVN_CTYPE_ASCII | SVN_CTYPE_SPACE, + /* ! */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* " */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* # */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* $ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* % */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* & */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ' */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ( */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ) */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* * */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* + */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* , */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* - */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* . */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* / */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* 0 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 1 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 2 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 3 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 4 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 5 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 6 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 7 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 8 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* 9 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT, + /* : */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ; */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* < */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* = */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* > */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ? */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* @ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* A */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA, + /* B */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA, + /* C */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA, + /* D */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA, + /* E */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA, + /* F */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA, + /* G */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* H */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* I */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* J */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* K */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* L */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* M */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* N */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* O */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* P */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* Q */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* R */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* S */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* T */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* U */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* V */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* W */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* X */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* Y */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* Z */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER, + /* [ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* \ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ] */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ^ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* _ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ` */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* a */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA, + /* b */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA, + /* c */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA, + /* d */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA, + /* e */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA, + /* f */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA, + /* g */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* h */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* i */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* j */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* k */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* l */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* m */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* n */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* o */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* p */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* q */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* r */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* s */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* t */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* u */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* v */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* w */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* x */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* y */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* z */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER, + /* { */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* | */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* } */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* ~ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT, + /* del */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL, + /* x80 */ SVN_CTYPE_UTF8CONT, + /* x81 */ SVN_CTYPE_UTF8CONT, + /* x82 */ SVN_CTYPE_UTF8CONT, + /* x83 */ SVN_CTYPE_UTF8CONT, + /* x84 */ SVN_CTYPE_UTF8CONT, + /* x85 */ SVN_CTYPE_UTF8CONT, + /* x86 */ SVN_CTYPE_UTF8CONT, + /* x87 */ SVN_CTYPE_UTF8CONT, + /* x88 */ SVN_CTYPE_UTF8CONT, + /* x89 */ SVN_CTYPE_UTF8CONT, + /* x8a */ SVN_CTYPE_UTF8CONT, + /* x8b */ SVN_CTYPE_UTF8CONT, + /* x8c */ SVN_CTYPE_UTF8CONT, + /* x8d */ SVN_CTYPE_UTF8CONT, + /* x8e */ SVN_CTYPE_UTF8CONT, + /* x8f */ SVN_CTYPE_UTF8CONT, + /* x90 */ SVN_CTYPE_UTF8CONT, + /* x91 */ SVN_CTYPE_UTF8CONT, + /* x92 */ SVN_CTYPE_UTF8CONT, + /* x93 */ SVN_CTYPE_UTF8CONT, + /* x94 */ SVN_CTYPE_UTF8CONT, + /* x95 */ SVN_CTYPE_UTF8CONT, + /* x96 */ SVN_CTYPE_UTF8CONT, + /* x97 */ SVN_CTYPE_UTF8CONT, + /* x98 */ SVN_CTYPE_UTF8CONT, + /* x99 */ SVN_CTYPE_UTF8CONT, + /* x9a */ SVN_CTYPE_UTF8CONT, + /* x9b */ SVN_CTYPE_UTF8CONT, + /* x9c */ SVN_CTYPE_UTF8CONT, + /* x9d */ SVN_CTYPE_UTF8CONT, + /* x9e */ SVN_CTYPE_UTF8CONT, + /* x9f */ SVN_CTYPE_UTF8CONT, + /* xa0 */ SVN_CTYPE_UTF8CONT, + /* xa1 */ SVN_CTYPE_UTF8CONT, + /* xa2 */ SVN_CTYPE_UTF8CONT, + /* xa3 */ SVN_CTYPE_UTF8CONT, + /* xa4 */ SVN_CTYPE_UTF8CONT, + /* xa5 */ SVN_CTYPE_UTF8CONT, + /* xa6 */ SVN_CTYPE_UTF8CONT, + /* xa7 */ SVN_CTYPE_UTF8CONT, + /* xa8 */ SVN_CTYPE_UTF8CONT, + /* xa9 */ SVN_CTYPE_UTF8CONT, + /* xaa */ SVN_CTYPE_UTF8CONT, + /* xab */ SVN_CTYPE_UTF8CONT, + /* xac */ SVN_CTYPE_UTF8CONT, + /* xad */ SVN_CTYPE_UTF8CONT, + /* xae */ SVN_CTYPE_UTF8CONT, + /* xaf */ SVN_CTYPE_UTF8CONT, + /* xb0 */ SVN_CTYPE_UTF8CONT, + /* xb1 */ SVN_CTYPE_UTF8CONT, + /* xb2 */ SVN_CTYPE_UTF8CONT, + /* xb3 */ SVN_CTYPE_UTF8CONT, + /* xb4 */ SVN_CTYPE_UTF8CONT, + /* xb5 */ SVN_CTYPE_UTF8CONT, + /* xb6 */ SVN_CTYPE_UTF8CONT, + /* xb7 */ SVN_CTYPE_UTF8CONT, + /* xb8 */ SVN_CTYPE_UTF8CONT, + /* xb9 */ SVN_CTYPE_UTF8CONT, + /* xba */ SVN_CTYPE_UTF8CONT, + /* xbb */ SVN_CTYPE_UTF8CONT, + /* xbc */ SVN_CTYPE_UTF8CONT, + /* xbd */ SVN_CTYPE_UTF8CONT, + /* xbe */ SVN_CTYPE_UTF8CONT, + /* xbf */ SVN_CTYPE_UTF8CONT, + /* xc0 */ 0, + /* xc1 */ SVN_CTYPE_UTF8LEAD, + /* xc2 */ SVN_CTYPE_UTF8LEAD, + /* xc3 */ SVN_CTYPE_UTF8LEAD, + /* xc4 */ SVN_CTYPE_UTF8LEAD, + /* xc5 */ SVN_CTYPE_UTF8LEAD, + /* xc6 */ SVN_CTYPE_UTF8LEAD, + /* xc7 */ SVN_CTYPE_UTF8LEAD, + /* xc8 */ SVN_CTYPE_UTF8LEAD, + /* xc9 */ SVN_CTYPE_UTF8LEAD, + /* xca */ SVN_CTYPE_UTF8LEAD, + /* xcb */ SVN_CTYPE_UTF8LEAD, + /* xcc */ SVN_CTYPE_UTF8LEAD, + /* xcd */ SVN_CTYPE_UTF8LEAD, + /* xce */ SVN_CTYPE_UTF8LEAD, + /* xcf */ SVN_CTYPE_UTF8LEAD, + /* xd0 */ SVN_CTYPE_UTF8LEAD, + /* xd1 */ SVN_CTYPE_UTF8LEAD, + /* xd2 */ SVN_CTYPE_UTF8LEAD, + /* xd3 */ SVN_CTYPE_UTF8LEAD, + /* xd4 */ SVN_CTYPE_UTF8LEAD, + /* xd5 */ SVN_CTYPE_UTF8LEAD, + /* xd6 */ SVN_CTYPE_UTF8LEAD, + /* xd7 */ SVN_CTYPE_UTF8LEAD, + /* xd8 */ SVN_CTYPE_UTF8LEAD, + /* xd9 */ SVN_CTYPE_UTF8LEAD, + /* xda */ SVN_CTYPE_UTF8LEAD, + /* xdb */ SVN_CTYPE_UTF8LEAD, + /* xdc */ SVN_CTYPE_UTF8LEAD, + /* xdd */ SVN_CTYPE_UTF8LEAD, + /* xde */ SVN_CTYPE_UTF8LEAD, + /* xdf */ SVN_CTYPE_UTF8LEAD, + /* xe0 */ 0, + /* xe1 */ SVN_CTYPE_UTF8LEAD, + /* xe2 */ SVN_CTYPE_UTF8LEAD, + /* xe3 */ SVN_CTYPE_UTF8LEAD, + /* xe4 */ SVN_CTYPE_UTF8LEAD, + /* xe5 */ SVN_CTYPE_UTF8LEAD, + /* xe6 */ SVN_CTYPE_UTF8LEAD, + /* xe7 */ SVN_CTYPE_UTF8LEAD, + /* xe8 */ SVN_CTYPE_UTF8LEAD, + /* xe9 */ SVN_CTYPE_UTF8LEAD, + /* xea */ SVN_CTYPE_UTF8LEAD, + /* xeb */ SVN_CTYPE_UTF8LEAD, + /* xec */ SVN_CTYPE_UTF8LEAD, + /* xed */ SVN_CTYPE_UTF8LEAD, + /* xee */ SVN_CTYPE_UTF8LEAD, + /* xef */ SVN_CTYPE_UTF8LEAD, + /* xf0 */ 0, + /* xf1 */ SVN_CTYPE_UTF8LEAD, + /* xf2 */ SVN_CTYPE_UTF8LEAD, + /* xf3 */ SVN_CTYPE_UTF8LEAD, + /* xf4 */ SVN_CTYPE_UTF8LEAD, + /* xf5 */ SVN_CTYPE_UTF8LEAD, + /* xf6 */ SVN_CTYPE_UTF8LEAD, + /* xf7 */ SVN_CTYPE_UTF8LEAD, + /* xf8 */ 0, + /* xf9 */ SVN_CTYPE_UTF8LEAD, + /* xfa */ SVN_CTYPE_UTF8LEAD, + /* xfb */ SVN_CTYPE_UTF8LEAD, + /* xfc */ 0, + /* xfd */ SVN_CTYPE_UTF8LEAD, + /* xfe */ 0, + /* xff */ 0 + }; + +const apr_uint32_t *const svn_ctype_table = svn_ctype_table_internal; + +static const unsigned char casefold_table[256] = + { + /* Identity, except {97:122} => {65:90} */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 + }; + +int +svn_ctype_casecmp(int a, int b) +{ + const int A = casefold_table[(unsigned char)a]; + const int B = casefold_table[(unsigned char)b]; + return A - B; +} diff --git a/subversion/libsvn_subr/date.c b/subversion/libsvn_subr/date.c new file mode 100644 index 000000000000..60356451e352 --- /dev/null +++ b/subversion/libsvn_subr/date.c @@ -0,0 +1,393 @@ +/* date.c: date parsing for Subversion + * + * ==================================================================== + * 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_time.h" +#include "svn_error.h" +#include "svn_string.h" + +#include "svn_private_config.h" +#include "private/svn_token.h" + +/* Valid rule actions */ +enum rule_action { + ACCUM, /* Accumulate a decimal value */ + MICRO, /* Accumulate microseconds */ + TZIND, /* Handle +, -, Z */ + NOOP, /* Do nothing */ + SKIPFROM, /* If at end-of-value, accept the match. Otherwise, + if the next template character matches the current + value character, continue processing as normal. + Otherwise, attempt to complete matching starting + immediately after the first subsequent occurrance of + ']' in the template. */ + SKIP, /* Ignore this template character */ + ACCEPT /* Accept the value */ +}; + +/* How to handle a particular character in a template */ +typedef struct rule +{ + char key; /* The template char that this rule matches */ + const char *valid; /* String of valid chars for this rule */ + enum rule_action action; /* What action to take when the rule is matched */ + int offset; /* Where to store the any results of the action, + expressed in terms of bytes relative to the + base of a match_state object. */ +} rule; + +/* The parsed values, before localtime/gmt processing */ +typedef struct match_state +{ + apr_time_exp_t base; + apr_int32_t offhours; + apr_int32_t offminutes; +} match_state; + +#define DIGITS "0123456789" + +/* A declarative specification of how each template character + should be processed, using a rule for each valid symbol. */ +static const rule +rules[] = +{ + { 'Y', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_year) }, + { 'M', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_mon) }, + { 'D', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_mday) }, + { 'h', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_hour) }, + { 'm', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_min) }, + { 's', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_sec) }, + { 'u', DIGITS, MICRO, APR_OFFSETOF(match_state, base.tm_usec) }, + { 'O', DIGITS, ACCUM, APR_OFFSETOF(match_state, offhours) }, + { 'o', DIGITS, ACCUM, APR_OFFSETOF(match_state, offminutes) }, + { '+', "-+", TZIND, 0 }, + { 'Z', "Z", TZIND, 0 }, + { ':', ":", NOOP, 0 }, + { '-', "-", NOOP, 0 }, + { 'T', "T", NOOP, 0 }, + { ' ', " ", NOOP, 0 }, + { '.', ".,", NOOP, 0 }, + { '[', NULL, SKIPFROM, 0 }, + { ']', NULL, SKIP, 0 }, + { '\0', NULL, ACCEPT, 0 }, +}; + +/* Return the rule associated with TCHAR, or NULL if there + is no such rule. */ +static const rule * +find_rule(char tchar) +{ + int i = sizeof(rules)/sizeof(rules[0]); + while (i--) + if (rules[i].key == tchar) + return &rules[i]; + return NULL; +} + +/* Attempt to match the date-string in VALUE to the provided TEMPLATE, + using the rules defined above. Return TRUE on successful match, + FALSE otherwise. On successful match, fill in *EXP with the + matched values and set *LOCALTZ to TRUE if the local time zone + should be used to interpret the match (i.e. if no time zone + information was provided), or FALSE if not. */ +static svn_boolean_t +template_match(apr_time_exp_t *expt, svn_boolean_t *localtz, + const char *template, const char *value) +{ + int multiplier = 100000; + int tzind = 0; + match_state ms; + char *base = (char *)&ms; + + memset(&ms, 0, sizeof(ms)); + + for (;;) + { + const rule *match = find_rule(*template++); + char vchar = *value++; + apr_int32_t *place; + + if (!match || (match->valid + && (!vchar || !strchr(match->valid, vchar)))) + return FALSE; + + /* Compute the address of memory location affected by this + rule by adding match->offset bytes to the address of ms. + Because this is a byte-quantity, it is necessary to cast + &ms to char *. */ + place = (apr_int32_t *)(base + match->offset); + switch (match->action) + { + case ACCUM: + *place = *place * 10 + vchar - '0'; + continue; + case MICRO: + *place += (vchar - '0') * multiplier; + multiplier /= 10; + continue; + case TZIND: + tzind = vchar; + continue; + case SKIP: + value--; + continue; + case NOOP: + continue; + case SKIPFROM: + if (!vchar) + break; + match = find_rule(*template); + if (!strchr(match->valid, vchar)) + template = strchr(template, ']') + 1; + value--; + continue; + case ACCEPT: + if (vchar) + return FALSE; + break; + } + + break; + } + + /* Validate gmt offset here, since we can't reliably do it later. */ + if (ms.offhours > 23 || ms.offminutes > 59) + return FALSE; + + /* tzind will be '+' or '-' for an explicit time zone, 'Z' to + indicate UTC, or 0 to indicate local time. */ + switch (tzind) + { + case '+': + ms.base.tm_gmtoff = ms.offhours * 3600 + ms.offminutes * 60; + break; + case '-': + ms.base.tm_gmtoff = -(ms.offhours * 3600 + ms.offminutes * 60); + break; + } + + *expt = ms.base; + *localtz = (tzind == 0); + return TRUE; +} + +static struct unit_words_table { + const char *word; + apr_time_t value; +} unit_words_table[] = { + /* Word matching does not concern itself with exact days of the month + * or leap years so these amounts are always fixed. */ + { "years", apr_time_from_sec(60 * 60 * 24 * 365) }, + { "months", apr_time_from_sec(60 * 60 * 24 * 30) }, + { "weeks", apr_time_from_sec(60 * 60 * 24 * 7) }, + { "days", apr_time_from_sec(60 * 60 * 24) }, + { "hours", apr_time_from_sec(60 * 60) }, + { "minutes", apr_time_from_sec(60) }, + { "mins", apr_time_from_sec(60) }, + { NULL , 0 } +}; + +static svn_token_map_t number_words_map[] = { + { "zero", 0 }, { "one", 1 }, { "two", 2 }, { "three", 3 }, { "four", 4 }, + { "five", 5 }, { "six", 6 }, { "seven", 7 }, { "eight", 8 }, { "nine", 9 }, + { "ten", 10 }, { "eleven", 11 }, { "twelve", 12 }, { NULL, 0 } +}; + +/* Attempt to match the date-string in TEXT according to the following rules: + * + * "N years|months|weeks|days|hours|minutes ago" resolve to the most recent + * revision prior to the specified time. N may either be a word from + * NUMBER_WORDS_TABLE defined above, or a non-negative digit. + * + * Return TRUE on successful match, FALSE otherwise. On successful match, + * fill in *EXP with the matched value and set *LOCALTZ to TRUE (this + * function always uses local time). Use POOL for temporary allocations. */ +static svn_boolean_t +words_match(apr_time_exp_t *expt, svn_boolean_t *localtz, + apr_time_t now, const char *text, apr_pool_t *pool) +{ + apr_time_t t = -1; + const char *word; + apr_array_header_t *words; + int i; + int n = -1; + const char *unit_str; + + words = svn_cstring_split(text, " ", TRUE /* chop_whitespace */, pool); + + if (words->nelts != 3) + return FALSE; + + word = APR_ARRAY_IDX(words, 0, const char *); + + /* Try to parse a number word. */ + n = svn_token__from_word(number_words_map, word); + + if (n == SVN_TOKEN_UNKNOWN) + { + svn_error_t *err; + + /* Try to parse a digit. */ + err = svn_cstring_atoi(&n, word); + if (err) + { + svn_error_clear(err); + return FALSE; + } + if (n < 0) + return FALSE; + } + + /* Try to parse a unit. */ + word = APR_ARRAY_IDX(words, 1, const char *); + for (i = 0, unit_str = unit_words_table[i].word; + unit_str = unit_words_table[i].word, unit_str != NULL; i++) + { + /* Tolerate missing trailing 's' from unit. */ + if (!strcmp(word, unit_str) || + !strncmp(word, unit_str, strlen(unit_str) - 1)) + { + t = now - (n * unit_words_table[i].value); + break; + } + } + + if (t < 0) + return FALSE; + + /* Require trailing "ago". */ + word = APR_ARRAY_IDX(words, 2, const char *); + if (strcmp(word, "ago")) + return FALSE; + + if (apr_time_exp_lt(expt, t) != APR_SUCCESS) + return FALSE; + + *localtz = TRUE; + return TRUE; +} + +static int +valid_days_by_month[] = { + 31, 29, 31, 30, + 31, 30, 31, 31, + 30, 31, 30, 31 +}; + +svn_error_t * +svn_parse_date(svn_boolean_t *matched, apr_time_t *result, const char *text, + apr_time_t now, apr_pool_t *pool) +{ + apr_time_exp_t expt, expnow; + apr_status_t apr_err; + svn_boolean_t localtz; + + *matched = FALSE; + + apr_err = apr_time_exp_lt(&expnow, now); + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, _("Can't manipulate current date")); + + if (template_match(&expt, &localtz, /* ISO-8601 extended, date only */ + "YYYY-M[M]-D[D]", + text) + || template_match(&expt, &localtz, /* ISO-8601 extended, UTC */ + "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u][Z]", + text) + || template_match(&expt, &localtz, /* ISO-8601 extended, with offset */ + "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[:oo]", + text) + || template_match(&expt, &localtz, /* ISO-8601 basic, date only */ + "YYYYMMDD", + text) + || template_match(&expt, &localtz, /* ISO-8601 basic, UTC */ + "YYYYMMDDThhmm[ss[.u[u[u[u[u[u][Z]", + text) + || template_match(&expt, &localtz, /* ISO-8601 basic, with offset */ + "YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo]", + text) + || template_match(&expt, &localtz, /* "svn log" format */ + "YYYY-M[M]-D[D] h[h]:mm[:ss[.u[u[u[u[u[u][ +OO[oo]", + text) + || template_match(&expt, &localtz, /* GNU date's iso-8601 */ + "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[oo]", + text)) + { + expt.tm_year -= 1900; + expt.tm_mon -= 1; + } + else if (template_match(&expt, &localtz, /* Just a time */ + "h[h]:mm[:ss[.u[u[u[u[u[u]", + text)) + { + expt.tm_year = expnow.tm_year; + expt.tm_mon = expnow.tm_mon; + expt.tm_mday = expnow.tm_mday; + } + else if (!words_match(&expt, &localtz, now, text, pool)) + return SVN_NO_ERROR; + + /* Range validation, allowing for leap seconds */ + if (expt.tm_mon < 0 || expt.tm_mon > 11 + || expt.tm_mday > valid_days_by_month[expt.tm_mon] + || expt.tm_mday < 1 + || expt.tm_hour > 23 + || expt.tm_min > 59 + || expt.tm_sec > 60) + return SVN_NO_ERROR; + + /* february/leap-year day checking. tm_year is bias-1900, so centuries + that equal 100 (mod 400) are multiples of 400. */ + if (expt.tm_mon == 1 + && expt.tm_mday == 29 + && (expt.tm_year % 4 != 0 + || (expt.tm_year % 100 == 0 && expt.tm_year % 400 != 100))) + return SVN_NO_ERROR; + + if (localtz) + { + apr_time_t candidate; + apr_time_exp_t expthen; + + /* We need to know the GMT offset of the requested time, not the + current time. In some cases, that quantity is ambiguous, + since at the end of daylight saving's time, an hour's worth + of local time happens twice. For those cases, we should + prefer DST if we are currently in DST, and standard time if + not. So, calculate the time value using the current time's + GMT offset and use the GMT offset of the resulting time. */ + expt.tm_gmtoff = expnow.tm_gmtoff; + apr_err = apr_time_exp_gmt_get(&candidate, &expt); + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, + _("Can't calculate requested date")); + apr_err = apr_time_exp_lt(&expthen, candidate); + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, _("Can't expand time")); + expt.tm_gmtoff = expthen.tm_gmtoff; + } + apr_err = apr_time_exp_gmt_get(result, &expt); + if (apr_err != APR_SUCCESS) + return svn_error_wrap_apr(apr_err, _("Can't calculate requested date")); + + *matched = TRUE; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/debug.c b/subversion/libsvn_subr/debug.c new file mode 100644 index 000000000000..be331ed77555 --- /dev/null +++ b/subversion/libsvn_subr/debug.c @@ -0,0 +1,155 @@ +/* + * debug.c : small functions to help SVN developers + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* These functions are only available to SVN developers and should never + be used in release code. One of the reasons to avoid this code in release + builds is that this code is not thread-safe. */ +#include <stdarg.h> +#include <assert.h> + +#include <apr_pools.h> +#include <apr_strings.h> +#include "svn_types.h" +#include "svn_string.h" + +#ifndef SVN_DBG__PROTOTYPES +#define SVN_DBG__PROTOTYPES +#endif +#include "private/svn_debug.h" + + +#define DBG_FLAG "DBG: " + +/* This will be tweaked by the preamble code. */ +static const char *debug_file = NULL; +static long debug_line = 0; +static FILE * volatile debug_output = NULL; + + +static svn_boolean_t +quiet_mode(void) +{ + return getenv("SVN_DBG_QUIET") != NULL; +} + + +void +svn_dbg__preamble(const char *file, long line, FILE *output) +{ + debug_output = output; + + if (output != NULL && !quiet_mode()) + { + /* Quick and dirty basename() code. */ + const char *slash = strrchr(file, '/'); + + if (slash == NULL) + slash = strrchr(file, '\\'); + if (slash) + debug_file = slash + 1; + else + debug_file = file; + } + debug_line = line; +} + + +/* Print a formatted string using format FMT and argument-list AP, + * prefixing each line of output with a debug header. */ +static void +debug_vprintf(const char *fmt, va_list ap) +{ + FILE *output = debug_output; + char prefix[80], buffer[1000]; + char *s = buffer; + int n; + + if (output == NULL || quiet_mode()) + return; + + n = apr_snprintf(prefix, sizeof(prefix), DBG_FLAG "%s:%4ld: ", + debug_file, debug_line); + assert(n < sizeof(prefix) - 1); + n = apr_vsnprintf(buffer, sizeof(buffer), fmt, ap); + assert(n < sizeof(buffer) - 1); + do + { + char *newline = strchr(s, '\n'); + if (newline) + *newline = '\0'; + + fputs(prefix, output); + fputs(s, output); + fputc('\n', output); + + if (! newline) + break; + s = newline + 1; + } + while (*s); /* print another line, except after a final newline */ +} + + +void +svn_dbg__printf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + debug_vprintf(fmt, ap); + va_end(ap); +} + + +void +svn_dbg__print_props(apr_hash_t *props, + const char *header_fmt, + ...) +{ +/* We only build this code if SVN_DEBUG is defined. */ +#ifdef SVN_DEBUG + + apr_hash_index_t *hi; + va_list ap; + + va_start(ap, header_fmt); + debug_vprintf(header_fmt, ap); + va_end(ap); + + if (props == NULL) + { + svn_dbg__printf(" (null)\n"); + return; + } + + for (hi = apr_hash_first(apr_hash_pool_get(props), props); hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + svn_string_t *val = svn__apr_hash_index_val(hi); + + svn_dbg__printf(" '%s' -> '%s'\n", name, val->data); + } +#endif /* SVN_DEBUG */ +} + diff --git a/subversion/libsvn_subr/deprecated.c b/subversion/libsvn_subr/deprecated.c new file mode 100644 index 000000000000..378b3f828edf --- /dev/null +++ b/subversion/libsvn_subr/deprecated.c @@ -0,0 +1,1304 @@ +/* + * deprecated.c: holding file for all deprecated APIs. + * "we can't lose 'em, but we can shun 'em!" + * + * ==================================================================== + * 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 <assert.h> + +/* We define this here to remove any further warnings about the usage of + deprecated functions in this file. */ +#define SVN_DEPRECATED + +#include "svn_hash.h" +#include "svn_subst.h" +#include "svn_path.h" +#include "svn_opt.h" +#include "svn_cmdline.h" +#include "svn_version.h" +#include "svn_pools.h" +#include "svn_dso.h" +#include "svn_mergeinfo.h" +#include "svn_utf.h" +#include "svn_xml.h" + +#include "opt.h" +#include "private/svn_opt_private.h" +#include "private/svn_mergeinfo_private.h" + +#include "svn_private_config.h" + + + + +/*** Code. ***/ + +/*** From subst.c ***/ +/* Convert an old-style svn_subst_keywords_t struct * into a new-style + * keywords hash. Keyword values are shallow copies, so the produced + * hash must not be assumed to have lifetime longer than the struct it + * is based on. A NULL input causes a NULL output. */ +static apr_hash_t * +kwstruct_to_kwhash(const svn_subst_keywords_t *kwstruct, + apr_pool_t *pool) +{ + apr_hash_t *kwhash; + + if (kwstruct == NULL) + return NULL; + + kwhash = apr_hash_make(pool); + + if (kwstruct->revision) + { + svn_hash_sets(kwhash, SVN_KEYWORD_REVISION_LONG, kwstruct->revision); + svn_hash_sets(kwhash, SVN_KEYWORD_REVISION_MEDIUM, kwstruct->revision); + svn_hash_sets(kwhash, SVN_KEYWORD_REVISION_SHORT, kwstruct->revision); + } + if (kwstruct->date) + { + svn_hash_sets(kwhash, SVN_KEYWORD_DATE_LONG, kwstruct->date); + svn_hash_sets(kwhash, SVN_KEYWORD_DATE_SHORT, kwstruct->date); + } + if (kwstruct->author) + { + svn_hash_sets(kwhash, SVN_KEYWORD_AUTHOR_LONG, kwstruct->author); + svn_hash_sets(kwhash, SVN_KEYWORD_AUTHOR_SHORT, kwstruct->author); + } + if (kwstruct->url) + { + svn_hash_sets(kwhash, SVN_KEYWORD_URL_LONG, kwstruct->url); + svn_hash_sets(kwhash, SVN_KEYWORD_URL_SHORT, kwstruct->url); + } + if (kwstruct->id) + { + svn_hash_sets(kwhash, SVN_KEYWORD_ID, kwstruct->id); + } + + return kwhash; +} + + +svn_error_t * +svn_subst_translate_stream3(svn_stream_t *src_stream, + svn_stream_t *dst_stream, + const char *eol_str, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + apr_pool_t *pool) +{ + /* The docstring requires that *some* translation be requested. */ + SVN_ERR_ASSERT(eol_str || keywords); + + /* We don't want the copy3 to close the provided streams. */ + src_stream = svn_stream_disown(src_stream, pool); + dst_stream = svn_stream_disown(dst_stream, pool); + + /* Wrap the destination stream with our translation stream. It is more + efficient than wrapping the source stream. */ + dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair, + keywords, expand, pool); + + return svn_error_trace(svn_stream_copy3(src_stream, dst_stream, + NULL, NULL, pool)); +} + +svn_error_t * +svn_subst_translate_stream2(svn_stream_t *s, /* src stream */ + svn_stream_t *d, /* dst stream */ + const char *eol_str, + svn_boolean_t repair, + const svn_subst_keywords_t *keywords, + svn_boolean_t expand, + apr_pool_t *pool) +{ + apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool); + + return svn_error_trace(svn_subst_translate_stream3(s, d, eol_str, repair, + kh, expand, pool)); +} + +svn_error_t * +svn_subst_translate_stream(svn_stream_t *s, /* src stream */ + svn_stream_t *d, /* dst stream */ + const char *eol_str, + svn_boolean_t repair, + const svn_subst_keywords_t *keywords, + svn_boolean_t expand) +{ + apr_pool_t *pool = svn_pool_create(NULL); + svn_error_t *err = svn_subst_translate_stream2(s, d, eol_str, repair, + keywords, expand, pool); + svn_pool_destroy(pool); + return svn_error_trace(err); +} + +svn_error_t * +svn_subst_translate_cstring(const char *src, + const char **dst, + const char *eol_str, + svn_boolean_t repair, + const svn_subst_keywords_t *keywords, + svn_boolean_t expand, + apr_pool_t *pool) +{ + apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool); + + return svn_error_trace(svn_subst_translate_cstring2(src, dst, eol_str, + repair, kh, expand, + pool)); +} + +svn_error_t * +svn_subst_copy_and_translate(const char *src, + const char *dst, + const char *eol_str, + svn_boolean_t repair, + const svn_subst_keywords_t *keywords, + svn_boolean_t expand, + apr_pool_t *pool) +{ + return svn_error_trace(svn_subst_copy_and_translate2(src, dst, eol_str, + repair, keywords, + expand, FALSE, pool)); +} + +svn_error_t * +svn_subst_copy_and_translate2(const char *src, + const char *dst, + const char *eol_str, + svn_boolean_t repair, + const svn_subst_keywords_t *keywords, + svn_boolean_t expand, + svn_boolean_t special, + apr_pool_t *pool) +{ + apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool); + + return svn_error_trace(svn_subst_copy_and_translate3(src, dst, eol_str, + repair, kh, expand, + special, pool)); +} + +svn_error_t * +svn_subst_copy_and_translate3(const char *src, + const char *dst, + const char *eol_str, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + svn_boolean_t special, + apr_pool_t *pool) +{ + return svn_error_trace(svn_subst_copy_and_translate4(src, dst, eol_str, + repair, keywords, + expand, special, + NULL, NULL, + pool)); +} + + +svn_error_t * +svn_subst_stream_translated_to_normal_form(svn_stream_t **stream, + svn_stream_t *source, + svn_subst_eol_style_t eol_style, + const char *eol_str, + svn_boolean_t always_repair_eols, + apr_hash_t *keywords, + apr_pool_t *pool) +{ + if (eol_style == svn_subst_eol_style_native) + eol_str = SVN_SUBST_NATIVE_EOL_STR; + else if (! (eol_style == svn_subst_eol_style_fixed + || eol_style == svn_subst_eol_style_none)) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + + *stream = svn_subst_stream_translated(source, eol_str, + eol_style == svn_subst_eol_style_fixed + || always_repair_eols, + keywords, FALSE, pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_subst_translate_string(svn_string_t **new_value, + const svn_string_t *value, + const char *encoding, + apr_pool_t *pool) +{ + return svn_subst_translate_string2(new_value, NULL, NULL, value, + encoding, FALSE, pool, pool); +} + +svn_error_t * +svn_subst_stream_detranslated(svn_stream_t **stream_p, + const char *src, + svn_subst_eol_style_t eol_style, + const char *eol_str, + svn_boolean_t always_repair_eols, + apr_hash_t *keywords, + svn_boolean_t special, + apr_pool_t *pool) +{ + svn_stream_t *src_stream; + + if (special) + return svn_subst_read_specialfile(stream_p, src, pool, pool); + + /* This will be closed by svn_subst_stream_translated_to_normal_form + when the returned stream is closed. */ + SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool)); + + return svn_error_trace(svn_subst_stream_translated_to_normal_form( + stream_p, src_stream, + eol_style, eol_str, + always_repair_eols, + keywords, pool)); +} + +svn_error_t * +svn_subst_translate_to_normal_form(const char *src, + const char *dst, + svn_subst_eol_style_t eol_style, + const char *eol_str, + svn_boolean_t always_repair_eols, + apr_hash_t *keywords, + svn_boolean_t special, + apr_pool_t *pool) +{ + + if (eol_style == svn_subst_eol_style_native) + eol_str = SVN_SUBST_NATIVE_EOL_STR; + else if (! (eol_style == svn_subst_eol_style_fixed + || eol_style == svn_subst_eol_style_none)) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + + return svn_error_trace(svn_subst_copy_and_translate3( + src, dst, eol_str, + eol_style == svn_subst_eol_style_fixed + || always_repair_eols, + keywords, + FALSE /* contract keywords */, + special, + pool)); +} + + +/*** From opt.c ***/ +/* Same as print_command_info2(), but with deprecated struct revision. */ +static svn_error_t * +print_command_info(const svn_opt_subcommand_desc_t *cmd, + const apr_getopt_option_t *options_table, + svn_boolean_t help, + apr_pool_t *pool, + FILE *stream) +{ + svn_boolean_t first_time; + apr_size_t i; + + /* Print the canonical command name. */ + SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool)); + + /* Print the list of aliases. */ + first_time = TRUE; + for (i = 0; i < SVN_OPT_MAX_ALIASES; i++) + { + if (cmd->aliases[i] == NULL) + break; + + if (first_time) { + SVN_ERR(svn_cmdline_fputs(" (", stream, pool)); + first_time = FALSE; + } + else + SVN_ERR(svn_cmdline_fputs(", ", stream, pool)); + + SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool)); + } + + if (! first_time) + SVN_ERR(svn_cmdline_fputs(")", stream, pool)); + + if (help) + { + const apr_getopt_option_t *option; + svn_boolean_t have_options = FALSE; + + SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help))); + + /* Loop over all valid option codes attached to the subcommand */ + for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) + { + if (cmd->valid_options[i]) + { + if (!have_options) + { + SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"), + stream, pool)); + have_options = TRUE; + } + + /* convert each option code into an option */ + option = + svn_opt_get_option_from_code2(cmd->valid_options[i], + options_table, NULL, pool); + + /* print the option's docstring */ + if (option && option->description) + { + const char *optstr; + svn_opt_format_option(&optstr, option, TRUE, pool); + SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n", + optstr)); + } + } + } + + if (have_options) + SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n")); + } + + return SVN_NO_ERROR; +} + +const svn_opt_subcommand_desc_t * +svn_opt_get_canonical_subcommand(const svn_opt_subcommand_desc_t *table, + const char *cmd_name) +{ + int i = 0; + + if (cmd_name == NULL) + return NULL; + + while (table[i].name) { + int j; + if (strcmp(cmd_name, table[i].name) == 0) + return table + i; + for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++) + if (strcmp(cmd_name, table[i].aliases[j]) == 0) + return table + i; + + i++; + } + + /* If we get here, there was no matching subcommand name or alias. */ + return NULL; +} + +void +svn_opt_subcommand_help2(const char *subcommand, + const svn_opt_subcommand_desc2_t *table, + const apr_getopt_option_t *options_table, + apr_pool_t *pool) +{ + svn_opt_subcommand_help3(subcommand, table, options_table, + NULL, pool); +} + +void +svn_opt_subcommand_help(const char *subcommand, + const svn_opt_subcommand_desc_t *table, + const apr_getopt_option_t *options_table, + apr_pool_t *pool) +{ + const svn_opt_subcommand_desc_t *cmd = + svn_opt_get_canonical_subcommand(table, subcommand); + svn_error_t *err; + + if (cmd) + err = print_command_info(cmd, options_table, TRUE, pool, stdout); + else + err = svn_cmdline_fprintf(stderr, pool, + _("\"%s\": unknown command.\n\n"), subcommand); + + if (err) { + svn_handle_error2(err, stderr, FALSE, "svn: "); + svn_error_clear(err); + } +} + +svn_error_t * +svn_opt_args_to_target_array3(apr_array_header_t **targets_p, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + apr_pool_t *pool) +{ + return svn_error_trace(svn_opt__args_to_target_array(targets_p, os, + known_targets, pool)); +} + +svn_error_t * +svn_opt_args_to_target_array2(apr_array_header_t **targets_p, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + apr_pool_t *pool) +{ + svn_error_t *err = svn_opt_args_to_target_array3(targets_p, os, + known_targets, pool); + + if (err && err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + + return err; +} + +svn_error_t * +svn_opt_args_to_target_array(apr_array_header_t **targets_p, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + svn_opt_revision_t *start_revision, + svn_opt_revision_t *end_revision, + svn_boolean_t extract_revisions, + apr_pool_t *pool) +{ + apr_array_header_t *output_targets; + + SVN_ERR(svn_opt_args_to_target_array2(&output_targets, os, + known_targets, pool)); + + if (extract_revisions) + { + svn_opt_revision_t temprev; + const char *path; + + if (output_targets->nelts > 0) + { + path = APR_ARRAY_IDX(output_targets, 0, const char *); + SVN_ERR(svn_opt_parse_path(&temprev, &path, path, pool)); + if (temprev.kind != svn_opt_revision_unspecified) + { + APR_ARRAY_IDX(output_targets, 0, const char *) = path; + start_revision->kind = temprev.kind; + start_revision->value = temprev.value; + } + } + if (output_targets->nelts > 1) + { + path = APR_ARRAY_IDX(output_targets, 1, const char *); + SVN_ERR(svn_opt_parse_path(&temprev, &path, path, pool)); + if (temprev.kind != svn_opt_revision_unspecified) + { + APR_ARRAY_IDX(output_targets, 1, const char *) = path; + end_revision->kind = temprev.kind; + end_revision->value = temprev.value; + } + } + } + + *targets_p = output_targets; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt_print_help3(apr_getopt_t *os, + const char *pgm_name, + svn_boolean_t print_version, + svn_boolean_t quiet, + const char *version_footer, + const char *header, + const svn_opt_subcommand_desc2_t *cmd_table, + const apr_getopt_option_t *option_table, + const int *global_options, + const char *footer, + apr_pool_t *pool) +{ + return svn_error_trace(svn_opt_print_help4(os, + pgm_name, + print_version, + quiet, + FALSE, + version_footer, + header, + cmd_table, + option_table, + global_options, + footer, + pool)); +} + +svn_error_t * +svn_opt_print_help2(apr_getopt_t *os, + const char *pgm_name, + svn_boolean_t print_version, + svn_boolean_t quiet, + const char *version_footer, + const char *header, + const svn_opt_subcommand_desc2_t *cmd_table, + const apr_getopt_option_t *option_table, + const char *footer, + apr_pool_t *pool) +{ + return svn_error_trace(svn_opt_print_help4(os, + pgm_name, + print_version, + quiet, + FALSE, + version_footer, + header, + cmd_table, + option_table, + NULL, + footer, + pool)); +} + +svn_error_t * +svn_opt_print_help(apr_getopt_t *os, + const char *pgm_name, + svn_boolean_t print_version, + svn_boolean_t quiet, + const char *version_footer, + const char *header, + const svn_opt_subcommand_desc_t *cmd_table, + const apr_getopt_option_t *option_table, + const char *footer, + apr_pool_t *pool) +{ + apr_array_header_t *targets = NULL; + + if (os) + SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); + + if (os && targets->nelts) /* help on subcommand(s) requested */ + { + int i; + + for (i = 0; i < targets->nelts; i++) + { + svn_opt_subcommand_help(APR_ARRAY_IDX(targets, i, const char *), + cmd_table, option_table, pool); + } + } + else if (print_version) /* just --version */ + { + SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, + svn_version_extended(FALSE, pool), + quiet, FALSE, pool)); + } + else if (os && !targets->nelts) /* `-h', `--help', or `help' */ + svn_opt_print_generic_help(header, + cmd_table, + option_table, + footer, + pool, + stdout); + else /* unknown option or cmd */ + SVN_ERR(svn_cmdline_fprintf(stderr, pool, + _("Type '%s help' for usage.\n"), pgm_name)); + + return SVN_NO_ERROR; +} + +void +svn_opt_print_generic_help(const char *header, + const svn_opt_subcommand_desc_t *cmd_table, + const apr_getopt_option_t *opt_table, + const char *footer, + apr_pool_t *pool, FILE *stream) +{ + int i = 0; + svn_error_t *err; + + if (header) + if ((err = svn_cmdline_fputs(header, stream, pool))) + goto print_error; + + while (cmd_table[i].name) + { + if ((err = svn_cmdline_fputs(" ", stream, pool)) + || (err = print_command_info(cmd_table + i, opt_table, FALSE, + pool, stream)) + || (err = svn_cmdline_fputs("\n", stream, pool))) + goto print_error; + i++; + } + + if ((err = svn_cmdline_fputs("\n", stream, pool))) + goto print_error; + + if (footer) + if ((err = svn_cmdline_fputs(footer, stream, pool))) + goto print_error; + + return; + + print_error: + svn_handle_error2(err, stderr, FALSE, "svn: "); + svn_error_clear(err); +} + +/*** From io.c ***/ +svn_error_t * +svn_io_open_unique_file2(apr_file_t **file, + const char **temp_path, + const char *path, + const char *suffix, + svn_io_file_del_t delete_when, + apr_pool_t *pool) +{ + const char *dirpath; + const char *filename; + + svn_path_split(path, &dirpath, &filename, pool); + return svn_error_trace(svn_io_open_uniquely_named(file, temp_path, + dirpath, filename, suffix, + delete_when, + pool, pool)); +} + +svn_error_t * +svn_io_open_unique_file(apr_file_t **file, + const char **temp_path, + const char *path, + const char *suffix, + svn_boolean_t delete_on_close, + apr_pool_t *pool) +{ + return svn_error_trace(svn_io_open_unique_file2(file, temp_path, + path, suffix, + delete_on_close + ? svn_io_file_del_on_close + : svn_io_file_del_none, + pool)); +} + +svn_error_t * +svn_io_run_diff(const char *dir, + const char *const *user_args, + int num_user_args, + const char *label1, + const char *label2, + const char *from, + const char *to, + int *pexitcode, + apr_file_t *outfile, + apr_file_t *errfile, + const char *diff_cmd, + apr_pool_t *pool) +{ + SVN_ERR(svn_path_cstring_to_utf8(&diff_cmd, diff_cmd, pool)); + + return svn_error_trace(svn_io_run_diff2(dir, user_args, num_user_args, + label1, label2, + from, to, pexitcode, + outfile, errfile, diff_cmd, + pool)); +} + +svn_error_t * +svn_io_run_diff3_2(int *exitcode, + const char *dir, + const char *mine, + const char *older, + const char *yours, + const char *mine_label, + const char *older_label, + const char *yours_label, + apr_file_t *merged, + const char *diff3_cmd, + const apr_array_header_t *user_args, + apr_pool_t *pool) +{ + SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); + + return svn_error_trace(svn_io_run_diff3_3(exitcode, dir, + mine, older, yours, + mine_label, older_label, + yours_label, merged, + diff3_cmd, user_args, pool)); +} + +svn_error_t * +svn_io_run_diff3(const char *dir, + const char *mine, + const char *older, + const char *yours, + const char *mine_label, + const char *older_label, + const char *yours_label, + apr_file_t *merged, + int *exitcode, + const char *diff3_cmd, + apr_pool_t *pool) +{ + return svn_error_trace(svn_io_run_diff3_2(exitcode, dir, mine, older, yours, + mine_label, older_label, + yours_label, + merged, diff3_cmd, NULL, pool)); +} + +svn_error_t * +svn_io_remove_file(const char *path, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_io_remove_file2(path, FALSE, scratch_pool)); +} + +svn_error_t *svn_io_file_lock(const char *lock_file, + svn_boolean_t exclusive, + apr_pool_t *pool) +{ + return svn_io_file_lock2(lock_file, exclusive, FALSE, pool); +} + +svn_error_t * +svn_io_get_dirents2(apr_hash_t **dirents, + const char *path, + apr_pool_t *pool) +{ + /* Note that the first part of svn_io_dirent2_t is identical + to svn_io_dirent_t to allow this construct */ + return svn_error_trace( + svn_io_get_dirents3(dirents, path, FALSE, pool, pool)); +} + +svn_error_t * +svn_io_get_dirents(apr_hash_t **dirents, + const char *path, + apr_pool_t *pool) +{ + /* Note that in C, padding is not allowed at the beginning of structs, + so this is actually portable, since the kind field of svn_io_dirent_t + is first in that struct. */ + return svn_io_get_dirents2(dirents, path, pool); +} + +svn_error_t * +svn_io_start_cmd2(apr_proc_t *cmd_proc, + const char *path, + const char *cmd, + const char *const *args, + svn_boolean_t inherit, + svn_boolean_t infile_pipe, + apr_file_t *infile, + svn_boolean_t outfile_pipe, + apr_file_t *outfile, + svn_boolean_t errfile_pipe, + apr_file_t *errfile, + apr_pool_t *pool) +{ + return svn_io_start_cmd3(cmd_proc, path, cmd, args, NULL, inherit, + infile_pipe, infile, outfile_pipe, outfile, + errfile_pipe, errfile, pool); +} + +svn_error_t * +svn_io_start_cmd(apr_proc_t *cmd_proc, + const char *path, + const char *cmd, + const char *const *args, + svn_boolean_t inherit, + apr_file_t *infile, + apr_file_t *outfile, + apr_file_t *errfile, + apr_pool_t *pool) +{ + return svn_io_start_cmd2(cmd_proc, path, cmd, args, inherit, FALSE, + infile, FALSE, outfile, FALSE, errfile, pool); +} + +svn_error_t * +svn_io_file_read_full(apr_file_t *file, void *buf, + apr_size_t nbytes, apr_size_t *bytes_read, + apr_pool_t *pool) +{ + return svn_io_file_read_full2(file, buf, nbytes, bytes_read, NULL, pool); +} + +struct walk_func_filter_baton_t +{ + svn_io_walk_func_t walk_func; + void *walk_baton; +}; + +/* Implements svn_io_walk_func_t, but only allows APR_DIR and APR_REG + finfo types through to the wrapped function/baton. */ +static svn_error_t * +walk_func_filter_func(void *baton, + const char *path, + const apr_finfo_t *finfo, + apr_pool_t *pool) +{ + struct walk_func_filter_baton_t *b = baton; + + if (finfo->filetype == APR_DIR || finfo->filetype == APR_REG) + SVN_ERR(b->walk_func(b->walk_baton, path, finfo, pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_dir_walk(const char *dirname, + apr_int32_t wanted, + svn_io_walk_func_t walk_func, + void *walk_baton, + apr_pool_t *pool) +{ + struct walk_func_filter_baton_t baton; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + return svn_error_trace(svn_io_dir_walk2(dirname, wanted, + walk_func_filter_func, + &baton, pool)); +} + +svn_error_t * +svn_io_stat_dirent(const svn_io_dirent2_t **dirent_p, + const char *path, + svn_boolean_t ignore_enoent, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_io_stat_dirent2(dirent_p, + path, + FALSE, + ignore_enoent, + result_pool, + scratch_pool)); +} + +/*** From constructors.c ***/ +svn_log_changed_path_t * +svn_log_changed_path_dup(const svn_log_changed_path_t *changed_path, + apr_pool_t *pool) +{ + svn_log_changed_path_t *new_changed_path + = apr_palloc(pool, sizeof(*new_changed_path)); + + *new_changed_path = *changed_path; + + if (new_changed_path->copyfrom_path) + new_changed_path->copyfrom_path = + apr_pstrdup(pool, new_changed_path->copyfrom_path); + + return new_changed_path; +} + +/*** From cmdline.c ***/ +svn_error_t * +svn_cmdline_prompt_user(const char **result, + const char *prompt_str, + apr_pool_t *pool) +{ + return svn_error_trace(svn_cmdline_prompt_user2(result, prompt_str, NULL, + pool)); +} + +svn_error_t * +svn_cmdline_setup_auth_baton(svn_auth_baton_t **ab, + svn_boolean_t non_interactive, + const char *auth_username, + const char *auth_password, + const char *config_dir, + svn_boolean_t no_auth_cache, + svn_config_t *cfg, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + return svn_error_trace(svn_cmdline_create_auth_baton( + ab, non_interactive, + auth_username, auth_password, + config_dir, no_auth_cache, FALSE, + cfg, cancel_func, cancel_baton, pool)); +} + +/*** From dso.c ***/ +void +svn_dso_initialize(void) +{ + svn_error_t *err = svn_dso_initialize2(); + if (err) + { + svn_error_clear(err); + abort(); + } +} + +/*** From simple_providers.c ***/ +void +svn_auth_get_simple_provider(svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_get_simple_provider2(provider, NULL, NULL, pool); +} + +/*** From ssl_client_cert_pw_providers.c ***/ +void +svn_auth_get_ssl_client_cert_pw_file_provider + (svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_get_ssl_client_cert_pw_file_provider2(provider, NULL, NULL, pool); +} + +/*** From path.c ***/ + +#define SVN_EMPTY_PATH "" + +const char * +svn_path_url_add_component(const char *url, + const char *component, + apr_pool_t *pool) +{ + /* URL can have trailing '/' */ + url = svn_path_canonicalize(url, pool); + + return svn_path_url_add_component2(url, component, pool); +} + +void +svn_path_split(const char *path, + const char **dirpath, + const char **base_name, + apr_pool_t *pool) +{ + assert(dirpath != base_name); + + if (dirpath) + *dirpath = svn_path_dirname(path, pool); + + if (base_name) + *base_name = svn_path_basename(path, pool); +} + + +svn_error_t * +svn_path_split_if_file(const char *path, + const char **pdirectory, + const char **pfile, + apr_pool_t *pool) +{ + apr_finfo_t finfo; + svn_error_t *err; + + SVN_ERR_ASSERT(svn_path_is_canonical(path, pool)); + + err = svn_io_stat(&finfo, path, APR_FINFO_TYPE, pool); + if (err && ! APR_STATUS_IS_ENOENT(err->apr_err)) + return err; + + if (err || finfo.filetype == APR_REG) + { + svn_error_clear(err); + svn_path_split(path, pdirectory, pfile, pool); + } + else if (finfo.filetype == APR_DIR) + { + *pdirectory = path; + *pfile = SVN_EMPTY_PATH; + } + else + { + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("'%s' is neither a file nor a directory name"), + svn_path_local_style(path, pool)); + } + + return SVN_NO_ERROR; +} + +/*** From stream.c ***/ +svn_error_t *svn_stream_copy2(svn_stream_t *from, svn_stream_t *to, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_stream_copy3( + svn_stream_disown(from, scratch_pool), + svn_stream_disown(to, scratch_pool), + cancel_func, cancel_baton, scratch_pool)); +} + +svn_error_t *svn_stream_copy(svn_stream_t *from, svn_stream_t *to, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_stream_copy3( + svn_stream_disown(from, scratch_pool), + svn_stream_disown(to, scratch_pool), + NULL, NULL, scratch_pool)); +} + +svn_stream_t * +svn_stream_from_aprfile(apr_file_t *file, apr_pool_t *pool) +{ + return svn_stream_from_aprfile2(file, TRUE, pool); +} + +svn_error_t * +svn_stream_contents_same(svn_boolean_t *same, + svn_stream_t *stream1, + svn_stream_t *stream2, + apr_pool_t *pool) +{ + return svn_error_trace(svn_stream_contents_same2( + same, + svn_stream_disown(stream1, pool), + svn_stream_disown(stream2, pool), + pool)); +} + +/*** From path.c ***/ + +const char * +svn_path_internal_style(const char *path, apr_pool_t *pool) +{ + if (svn_path_is_url(path)) + return svn_uri_canonicalize(path, pool); + else + return svn_dirent_internal_style(path, pool); +} + + +const char * +svn_path_local_style(const char *path, apr_pool_t *pool) +{ + if (svn_path_is_url(path)) + return apr_pstrdup(pool, path); + else + return svn_dirent_local_style(path, pool); +} + +const char * +svn_path_canonicalize(const char *path, apr_pool_t *pool) +{ + if (svn_path_is_url(path)) + return svn_uri_canonicalize(path, pool); + else + return svn_dirent_canonicalize(path, pool); +} + + +/*** From mergeinfo.c ***/ + +svn_error_t * +svn_mergeinfo_inheritable(svn_mergeinfo_t *output, + svn_mergeinfo_t mergeinfo, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + apr_pool_t *pool) +{ + return svn_error_trace(svn_mergeinfo_inheritable2(output, mergeinfo, path, + start, end, + TRUE, pool, pool)); +} + +svn_error_t * +svn_rangelist_inheritable(svn_rangelist_t **inheritable_rangelist, + const svn_rangelist_t *rangelist, + svn_revnum_t start, + svn_revnum_t end, + apr_pool_t *pool) +{ + return svn_error_trace(svn_rangelist_inheritable2(inheritable_rangelist, + rangelist, + start, end, TRUE, + pool, pool)); +} + +svn_error_t * +svn_rangelist_merge(svn_rangelist_t **rangelist, + const svn_rangelist_t *changes, + apr_pool_t *pool) +{ + SVN_ERR(svn_rangelist_merge2(*rangelist, changes, + pool, pool)); + + return svn_error_trace( + svn_rangelist__combine_adjacent_ranges(*rangelist, pool)); +} + +svn_error_t * +svn_mergeinfo_diff(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added, + svn_mergeinfo_t from, svn_mergeinfo_t to, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + return svn_error_trace(svn_mergeinfo_diff2(deleted, added, from, to, + consider_inheritance, pool, + pool)); +} + +svn_error_t * +svn_mergeinfo_merge(svn_mergeinfo_t mergeinfo, + svn_mergeinfo_t changes, + apr_pool_t *pool) +{ + return svn_error_trace(svn_mergeinfo_merge2(mergeinfo, changes, pool, + pool)); +} + +svn_error_t * +svn_mergeinfo_remove(svn_mergeinfo_t *mergeinfo, svn_mergeinfo_t eraser, + svn_mergeinfo_t whiteboard, apr_pool_t *pool) +{ + return svn_mergeinfo_remove2(mergeinfo, eraser, whiteboard, TRUE, pool, + pool); +} + +svn_error_t * +svn_mergeinfo_intersect(svn_mergeinfo_t *mergeinfo, + svn_mergeinfo_t mergeinfo1, + svn_mergeinfo_t mergeinfo2, + apr_pool_t *pool) +{ + return svn_mergeinfo_intersect2(mergeinfo, mergeinfo1, mergeinfo2, + TRUE, pool, pool); +} + +/*** From config.c ***/ +svn_error_t * +svn_config_create(svn_config_t **cfgp, + svn_boolean_t section_names_case_sensitive, + apr_pool_t *result_pool) +{ + return svn_error_trace(svn_config_create2(cfgp, + section_names_case_sensitive, + FALSE, + result_pool)); +} + +svn_error_t * +svn_config_read2(svn_config_t **cfgp, const char *file, + svn_boolean_t must_exist, + svn_boolean_t section_names_case_sensitive, + apr_pool_t *result_pool) +{ + return svn_error_trace(svn_config_read3(cfgp, file, + must_exist, + section_names_case_sensitive, + FALSE, + result_pool)); +} + +svn_error_t * +svn_config_read(svn_config_t **cfgp, const char *file, + svn_boolean_t must_exist, + apr_pool_t *result_pool) +{ + return svn_error_trace(svn_config_read3(cfgp, file, + must_exist, + FALSE, FALSE, + result_pool)); +} + +#ifdef SVN_DISABLE_FULL_VERSION_MATCH +/* This double underscore name is used by the 1.6 command line client. + Keeping this name is sufficient for the 1.6 client to use the 1.7 + libraries at runtime. */ +svn_error_t * +svn_opt__eat_peg_revisions(apr_array_header_t **true_targets_p, + apr_array_header_t *targets, + apr_pool_t *pool); +svn_error_t * +svn_opt__eat_peg_revisions(apr_array_header_t **true_targets_p, + apr_array_header_t *targets, + apr_pool_t *pool) +{ + unsigned int i; + apr_array_header_t *true_targets; + + true_targets = apr_array_make(pool, 5, sizeof(const char *)); + + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *true_target; + + SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, NULL, + target, pool)); + APR_ARRAY_PUSH(true_targets, const char *) = true_target; + } + + SVN_ERR_ASSERT(true_targets_p); + *true_targets_p = true_targets; + + return SVN_NO_ERROR; +} +#endif + +void +svn_xml_make_header(svn_stringbuf_t **str, apr_pool_t *pool) +{ + svn_xml_make_header2(str, NULL, pool); +} + +void +svn_utf_initialize(apr_pool_t *pool) +{ + svn_utf_initialize2(FALSE, pool); +} + +svn_error_t * +svn_subst_build_keywords(svn_subst_keywords_t *kw, + const char *keywords_val, + const char *rev, + const char *url, + apr_time_t date, + const char *author, + apr_pool_t *pool) +{ + apr_hash_t *kwhash; + const svn_string_t *val; + + SVN_ERR(svn_subst_build_keywords2(&kwhash, keywords_val, rev, + url, date, author, pool)); + + /* The behaviour of pre-1.3 svn_subst_build_keywords, which we are + * replicating here, is to write to a slot in the svn_subst_keywords_t + * only if the relevant keyword was present in keywords_val, otherwise + * leaving that slot untouched. */ + + val = svn_hash_gets(kwhash, SVN_KEYWORD_REVISION_LONG); + if (val) + kw->revision = val; + + val = svn_hash_gets(kwhash, SVN_KEYWORD_DATE_LONG); + if (val) + kw->date = val; + + val = svn_hash_gets(kwhash, SVN_KEYWORD_AUTHOR_LONG); + if (val) + kw->author = val; + + val = svn_hash_gets(kwhash, SVN_KEYWORD_URL_LONG); + if (val) + kw->url = val; + + val = svn_hash_gets(kwhash, SVN_KEYWORD_ID); + if (val) + kw->id = val; + + return SVN_NO_ERROR; +} + + diff --git a/subversion/libsvn_subr/dirent_uri.c b/subversion/libsvn_subr/dirent_uri.c new file mode 100644 index 000000000000..2b51e7aa2aed --- /dev/null +++ b/subversion/libsvn_subr/dirent_uri.c @@ -0,0 +1,2597 @@ +/* + * dirent_uri.c: a library to manipulate URIs and directory entries. + * + * ==================================================================== + * 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 <string.h> +#include <assert.h> +#include <ctype.h> + +#include <apr_uri.h> +#include <apr_lib.h> + +#include "svn_private_config.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_ctype.h" + +#include "dirent_uri.h" +#include "private/svn_fspath.h" + +/* The canonical empty path. Can this be changed? Well, change the empty + test below and the path library will work, not so sure about the fs/wc + libraries. */ +#define SVN_EMPTY_PATH "" + +/* TRUE if s is the canonical empty path, FALSE otherwise */ +#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0') + +/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can + this be changed? Well, the path library will work, not so sure about + the OS! */ +#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.') + +/* This check must match the check on top of dirent_uri-tests.c and + path-tests.c */ +#if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__) +#define SVN_USE_DOS_PATHS +#endif + +/* Path type definition. Used only by internal functions. */ +typedef enum path_type_t { + type_uri, + type_dirent, + type_relpath +} path_type_t; + + +/**** Forward declarations *****/ + +static svn_boolean_t +relpath_is_canonical(const char *relpath); + + +/**** Internal implementation functions *****/ + +/* Return an internal-style new path based on PATH, allocated in POOL. + * + * "Internal-style" means that separators are all '/'. + */ +static const char * +internal_style(const char *path, apr_pool_t *pool) +{ +#if '/' != SVN_PATH_LOCAL_SEPARATOR + { + char *p = apr_pstrdup(pool, path); + path = p; + + /* Convert all local-style separators to the canonical ones. */ + for (; *p != '\0'; ++p) + if (*p == SVN_PATH_LOCAL_SEPARATOR) + *p = '/'; + } +#endif + + return path; +} + +/* Locale insensitive tolower() for converting parts of dirents and urls + while canonicalizing */ +static char +canonicalize_to_lower(char c) +{ + if (c < 'A' || c > 'Z') + return c; + else + return (char)(c - 'A' + 'a'); +} + +/* Locale insensitive toupper() for converting parts of dirents and urls + while canonicalizing */ +static char +canonicalize_to_upper(char c) +{ + if (c < 'a' || c > 'z') + return c; + else + return (char)(c - 'a' + 'A'); +} + +/* Calculates the length of the dirent absolute or non absolute root in + DIRENT, return 0 if dirent is not rooted */ +static apr_size_t +dirent_root_length(const char *dirent, apr_size_t len) +{ +#ifdef SVN_USE_DOS_PATHS + if (len >= 2 && dirent[1] == ':' && + ((dirent[0] >= 'A' && dirent[0] <= 'Z') || + (dirent[0] >= 'a' && dirent[0] <= 'z'))) + { + return (len > 2 && dirent[2] == '/') ? 3 : 2; + } + + if (len > 2 && dirent[0] == '/' && dirent[1] == '/') + { + apr_size_t i = 2; + + while (i < len && dirent[i] != '/') + i++; + + if (i == len) + return len; /* Cygwin drive alias, invalid path on WIN32 */ + + i++; /* Skip '/' */ + + while (i < len && dirent[i] != '/') + i++; + + return i; + } +#endif /* SVN_USE_DOS_PATHS */ + if (len >= 1 && dirent[0] == '/') + return 1; + + return 0; +} + + +/* Return the length of substring necessary to encompass the entire + * previous dirent segment in DIRENT, which should be a LEN byte string. + * + * A trailing slash will not be included in the returned length except + * in the case in which DIRENT is absolute and there are no more + * previous segments. + */ +static apr_size_t +dirent_previous_segment(const char *dirent, + apr_size_t len) +{ + if (len == 0) + return 0; + + --len; + while (len > 0 && dirent[len] != '/' +#ifdef SVN_USE_DOS_PATHS + && (dirent[len] != ':' || len != 1) +#endif /* SVN_USE_DOS_PATHS */ + ) + --len; + + /* check if the remaining segment including trailing '/' is a root dirent */ + if (dirent_root_length(dirent, len+1) == len + 1) + return len + 1; + else + return len; +} + +/* Calculates the length occupied by the schema defined root of URI */ +static apr_size_t +uri_schema_root_length(const char *uri, apr_size_t len) +{ + apr_size_t i; + + for (i = 0; i < len; i++) + { + if (uri[i] == '/') + { + if (i > 0 && uri[i-1] == ':' && i < len-1 && uri[i+1] == '/') + { + /* We have an absolute uri */ + if (i == 5 && strncmp("file", uri, 4) == 0) + return 7; /* file:// */ + else + { + for (i += 2; i < len; i++) + if (uri[i] == '/') + return i; + + return len; /* Only a hostname is found */ + } + } + else + return 0; + } + } + + return 0; +} + +/* Returns TRUE if svn_dirent_is_absolute(dirent) or when dirent has + a non absolute root. (E.g. '/' or 'F:' on Windows) */ +static svn_boolean_t +dirent_is_rooted(const char *dirent) +{ + if (! dirent) + return FALSE; + + /* Root on all systems */ + if (dirent[0] == '/') + return TRUE; + + /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/' + where 'H' is any letter. */ +#ifdef SVN_USE_DOS_PATHS + if (((dirent[0] >= 'A' && dirent[0] <= 'Z') || + (dirent[0] >= 'a' && dirent[0] <= 'z')) && + (dirent[1] == ':')) + return TRUE; +#endif /* SVN_USE_DOS_PATHS */ + + return FALSE; +} + +/* Return the length of substring necessary to encompass the entire + * previous relpath segment in RELPATH, which should be a LEN byte string. + * + * A trailing slash will not be included in the returned length. + */ +static apr_size_t +relpath_previous_segment(const char *relpath, + apr_size_t len) +{ + if (len == 0) + return 0; + + --len; + while (len > 0 && relpath[len] != '/') + --len; + + return len; +} + +/* Return the length of substring necessary to encompass the entire + * previous uri segment in URI, which should be a LEN byte string. + * + * A trailing slash will not be included in the returned length except + * in the case in which URI is absolute and there are no more + * previous segments. + */ +static apr_size_t +uri_previous_segment(const char *uri, + apr_size_t len) +{ + apr_size_t root_length; + apr_size_t i = len; + if (len == 0) + return 0; + + root_length = uri_schema_root_length(uri, len); + + --i; + while (len > root_length && uri[i] != '/') + --i; + + if (i == 0 && len > 1 && *uri == '/') + return 1; + + return i; +} + +/* Return the canonicalized version of PATH, of type TYPE, allocated in + * POOL. + */ +static const char * +canonicalize(path_type_t type, const char *path, apr_pool_t *pool) +{ + char *canon, *dst; + const char *src; + apr_size_t seglen; + apr_size_t schemelen = 0; + apr_size_t canon_segments = 0; + svn_boolean_t url = FALSE; + char *schema_data = NULL; + + /* "" is already canonical, so just return it; note that later code + depends on path not being zero-length. */ + if (SVN_PATH_IS_EMPTY(path)) + { + assert(type != type_uri); + return ""; + } + + dst = canon = apr_pcalloc(pool, strlen(path) + 1); + + /* If this is supposed to be an URI, it should start with + "scheme://". We'll copy the scheme, host name, etc. to DST and + set URL = TRUE. */ + src = path; + if (type == type_uri) + { + assert(*src != '/'); + + while (*src && (*src != '/') && (*src != ':')) + src++; + + if (*src == ':' && *(src+1) == '/' && *(src+2) == '/') + { + const char *seg; + + url = TRUE; + + /* Found a scheme, convert to lowercase and copy to dst. */ + src = path; + while (*src != ':') + { + *(dst++) = canonicalize_to_lower((*src++)); + schemelen++; + } + *(dst++) = ':'; + *(dst++) = '/'; + *(dst++) = '/'; + src += 3; + schemelen += 3; + + /* This might be the hostname */ + seg = src; + while (*src && (*src != '/') && (*src != '@')) + src++; + + if (*src == '@') + { + /* Copy the username & password. */ + seglen = src - seg + 1; + memcpy(dst, seg, seglen); + dst += seglen; + src++; + } + else + src = seg; + + /* Found a hostname, convert to lowercase and copy to dst. */ + if (*src == '[') + { + *(dst++) = *(src++); /* Copy '[' */ + + while (*src == ':' + || (*src >= '0' && (*src <= '9')) + || (*src >= 'a' && (*src <= 'f')) + || (*src >= 'A' && (*src <= 'F'))) + { + *(dst++) = canonicalize_to_lower((*src++)); + } + + if (*src == ']') + *(dst++) = *(src++); /* Copy ']' */ + } + else + while (*src && (*src != '/') && (*src != ':')) + *(dst++) = canonicalize_to_lower((*src++)); + + if (*src == ':') + { + /* We probably have a port number: Is it a default portnumber + which doesn't belong in a canonical url? */ + if (src[1] == '8' && src[2] == '0' + && (src[3]== '/'|| !src[3]) + && !strncmp(canon, "http:", 5)) + { + src += 3; + } + else if (src[1] == '4' && src[2] == '4' && src[3] == '3' + && (src[4]== '/'|| !src[4]) + && !strncmp(canon, "https:", 6)) + { + src += 4; + } + else if (src[1] == '3' && src[2] == '6' + && src[3] == '9' && src[4] == '0' + && (src[5]== '/'|| !src[5]) + && !strncmp(canon, "svn:", 4)) + { + src += 5; + } + else if (src[1] == '/' || !src[1]) + { + src += 1; + } + + while (*src && (*src != '/')) + *(dst++) = canonicalize_to_lower((*src++)); + } + + /* Copy trailing slash, or null-terminator. */ + *(dst) = *(src); + + /* Move src and dst forward only if we are not + * at null-terminator yet. */ + if (*src) + { + src++; + dst++; + schema_data = dst; + } + + canon_segments = 1; + } + } + + /* Copy to DST any separator or drive letter that must come before the + first regular path segment. */ + if (! url && type != type_relpath) + { + src = path; + /* If this is an absolute path, then just copy over the initial + separator character. */ + if (*src == '/') + { + *(dst++) = *(src++); + +#ifdef SVN_USE_DOS_PATHS + /* On Windows permit two leading separator characters which means an + * UNC path. */ + if ((type == type_dirent) && *src == '/') + *(dst++) = *(src++); +#endif /* SVN_USE_DOS_PATHS */ + } +#ifdef SVN_USE_DOS_PATHS + /* On Windows the first segment can be a drive letter, which we normalize + to upper case. */ + else if (type == type_dirent && + ((*src >= 'a' && *src <= 'z') || + (*src >= 'A' && *src <= 'Z')) && + (src[1] == ':')) + { + *(dst++) = canonicalize_to_upper(*(src++)); + /* Leave the ':' to be processed as (or as part of) a path segment + by the following code block, so we need not care whether it has + a slash after it. */ + } +#endif /* SVN_USE_DOS_PATHS */ + } + + while (*src) + { + /* Parse each segment, finding the closing '/' (which might look + like '%2F' for URIs). */ + const char *next = src; + apr_size_t slash_len = 0; + + while (*next + && (next[0] != '/') + && (! (type == type_uri && next[0] == '%' && next[1] == '2' && + canonicalize_to_upper(next[2]) == 'F'))) + { + ++next; + } + + /* Record how long our "slash" is. */ + if (next[0] == '/') + slash_len = 1; + else if (type == type_uri && next[0] == '%') + slash_len = 3; + + seglen = next - src; + + if (seglen == 0 + || (seglen == 1 && src[0] == '.') + || (type == type_uri && seglen == 3 && src[0] == '%' && src[1] == '2' + && canonicalize_to_upper(src[2]) == 'E')) + { + /* Empty or noop segment, so do nothing. (For URIs, '%2E' + is equivalent to '.'). */ + } +#ifdef SVN_USE_DOS_PATHS + /* If this is the first path segment of a file:// URI and it contains a + windows drive letter, convert the drive letter to upper case. */ + else if (url && canon_segments == 1 && seglen == 2 && + (strncmp(canon, "file:", 5) == 0) && + src[0] >= 'a' && src[0] <= 'z' && src[1] == ':') + { + *(dst++) = canonicalize_to_upper(src[0]); + *(dst++) = ':'; + if (*next) + *(dst++) = *next; + canon_segments++; + } +#endif /* SVN_USE_DOS_PATHS */ + else + { + /* An actual segment, append it to the destination path */ + memcpy(dst, src, seglen); + dst += seglen; + if (slash_len) + *(dst++) = '/'; + canon_segments++; + } + + /* Skip over trailing slash to the next segment. */ + src = next + slash_len; + } + + /* Remove the trailing slash if there was at least one + * canonical segment and the last segment ends with a slash. + * + * But keep in mind that, for URLs, the scheme counts as a + * canonical segment -- so if path is ONLY a scheme (such + * as "https://") we should NOT remove the trailing slash. */ + if ((canon_segments > 0 && *(dst - 1) == '/') + && ! (url && path[schemelen] == '\0')) + { + dst --; + } + + *dst = '\0'; + +#ifdef SVN_USE_DOS_PATHS + /* Skip leading double slashes when there are less than 2 + * canon segments. UNC paths *MUST* have two segments. */ + if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/') + { + if (canon_segments < 2) + return canon + 1; + else + { + /* Now we're sure this is a valid UNC path, convert the server name + (the first path segment) to lowercase as Windows treats it as case + insensitive. + Note: normally the share name is treated as case insensitive too, + but it seems to be possible to configure Samba to treat those as + case sensitive, so better leave that alone. */ + for (dst = canon + 2; *dst && *dst != '/'; dst++) + *dst = canonicalize_to_lower(*dst); + } + } +#endif /* SVN_USE_DOS_PATHS */ + + /* Check the normalization of characters in a uri */ + if (schema_data) + { + int need_extra = 0; + src = schema_data; + + while (*src) + { + switch (*src) + { + case '/': + break; + case '%': + if (!svn_ctype_isxdigit(*(src+1)) || + !svn_ctype_isxdigit(*(src+2))) + need_extra += 2; + else + src += 2; + break; + default: + if (!svn_uri__char_validity[(unsigned char)*src]) + need_extra += 2; + break; + } + src++; + } + + if (need_extra > 0) + { + apr_size_t pre_schema_size = (apr_size_t)(schema_data - canon); + + dst = apr_palloc(pool, (apr_size_t)(src - canon) + need_extra + 1); + memcpy(dst, canon, pre_schema_size); + canon = dst; + + dst += pre_schema_size; + } + else + dst = schema_data; + + src = schema_data; + + while (*src) + { + switch (*src) + { + case '/': + *(dst++) = '/'; + break; + case '%': + if (!svn_ctype_isxdigit(*(src+1)) || + !svn_ctype_isxdigit(*(src+2))) + { + *(dst++) = '%'; + *(dst++) = '2'; + *(dst++) = '5'; + } + else + { + char digitz[3]; + int val; + + digitz[0] = *(++src); + digitz[1] = *(++src); + digitz[2] = 0; + + val = (int)strtol(digitz, NULL, 16); + + if (svn_uri__char_validity[(unsigned char)val]) + *(dst++) = (char)val; + else + { + *(dst++) = '%'; + *(dst++) = canonicalize_to_upper(digitz[0]); + *(dst++) = canonicalize_to_upper(digitz[1]); + } + } + break; + default: + if (!svn_uri__char_validity[(unsigned char)*src]) + { + apr_snprintf(dst, 4, "%%%02X", (unsigned char)*src); + dst += 3; + } + else + *(dst++) = *src; + break; + } + src++; + } + *dst = '\0'; + } + + return canon; +} + +/* Return the string length of the longest common ancestor of PATH1 and PATH2. + * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if + * PATH1 and PATH2 are regular paths. + * + * If the two paths do not share a common ancestor, return 0. + * + * New strings are allocated in POOL. + */ +static apr_size_t +get_longest_ancestor_length(path_type_t types, + const char *path1, + const char *path2, + apr_pool_t *pool) +{ + apr_size_t path1_len, path2_len; + apr_size_t i = 0; + apr_size_t last_dirsep = 0; +#ifdef SVN_USE_DOS_PATHS + svn_boolean_t unc = FALSE; +#endif + + path1_len = strlen(path1); + path2_len = strlen(path2); + + if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2)) + return 0; + + while (path1[i] == path2[i]) + { + /* Keep track of the last directory separator we hit. */ + if (path1[i] == '/') + last_dirsep = i; + + i++; + + /* If we get to the end of either path, break out. */ + if ((i == path1_len) || (i == path2_len)) + break; + } + + /* two special cases: + 1. '/' is the longest common ancestor of '/' and '/foo' */ + if (i == 1 && path1[0] == '/' && path2[0] == '/') + return 1; + /* 2. '' is the longest common ancestor of any non-matching + * strings 'foo' and 'bar' */ + if (types == type_dirent && i == 0) + return 0; + + /* Handle some windows specific cases */ +#ifdef SVN_USE_DOS_PATHS + if (types == type_dirent) + { + /* don't count the '//' from UNC paths */ + if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/') + { + last_dirsep = 0; + unc = TRUE; + } + + /* X:/ and X:/foo */ + if (i == 3 && path1[2] == '/' && path1[1] == ':') + return i; + + /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry. + * Note that this assertion triggers only if the code above has + * been broken. The code below relies on this assertion, because + * it uses [i - 1] as index. */ + assert(i > 0); + + /* X: and X:/ */ + if ((path1[i - 1] == ':' && path2[i] == '/') || + (path2[i - 1] == ':' && path1[i] == '/')) + return 0; + /* X: and X:foo */ + if (path1[i - 1] == ':' || path2[i - 1] == ':') + return i; + } +#endif /* SVN_USE_DOS_PATHS */ + + /* last_dirsep is now the offset of the last directory separator we + crossed before reaching a non-matching byte. i is the offset of + that non-matching byte, and is guaranteed to be <= the length of + whichever path is shorter. + If one of the paths is the common part return that. */ + if (((i == path1_len) && (path2[i] == '/')) + || ((i == path2_len) && (path1[i] == '/')) + || ((i == path1_len) && (i == path2_len))) + return i; + else + { + /* Nothing in common but the root folder '/' or 'X:/' for Windows + dirents. */ +#ifdef SVN_USE_DOS_PATHS + if (! unc) + { + /* X:/foo and X:/bar returns X:/ */ + if ((types == type_dirent) && + last_dirsep == 2 && path1[1] == ':' && path1[2] == '/' + && path2[1] == ':' && path2[2] == '/') + return 3; +#endif /* SVN_USE_DOS_PATHS */ + if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/') + return 1; +#ifdef SVN_USE_DOS_PATHS + } +#endif + } + + return last_dirsep; +} + +/* Determine whether PATH2 is a child of PATH1. + * + * PATH2 is a child of PATH1 if + * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path. + * or + * 2) PATH2 is has n components, PATH1 has x < n components, + * and PATH1 matches PATH2 in all its x components. + * Components are separated by a slash, '/'. + * + * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if + * PATH1 and PATH2 are regular paths. + * + * If PATH2 is not a child of PATH1, return NULL. + * + * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy + * of the child part of PATH2 in POOL and return a pointer to the + * newly allocated child part. + * + * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer + * pointing to the child part of PATH2. + * */ +static const char * +is_child(path_type_t type, const char *path1, const char *path2, + apr_pool_t *pool) +{ + apr_size_t i; + + /* Allow "" and "foo" or "H:foo" to be parent/child */ + if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */ + { + if (SVN_PATH_IS_EMPTY(path2)) /* "" not a child */ + return NULL; + + /* check if this is an absolute path */ + if ((type == type_uri) || + (type == type_dirent && dirent_is_rooted(path2))) + return NULL; + else + /* everything else is child */ + return pool ? apr_pstrdup(pool, path2) : path2; + } + + /* Reach the end of at least one of the paths. How should we handle + things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't + appear to arise in the current Subversion code, it's not clear to me + if they should be parent/child or not. */ + /* Hmmm... aren't paths assumed to be canonical in this function? + * How can "foo///bar" even happen if the paths are canonical? */ + for (i = 0; path1[i] && path2[i]; i++) + if (path1[i] != path2[i]) + return NULL; + + /* FIXME: This comment does not really match + * the checks made in the code it refers to: */ + /* There are two cases that are parent/child + ... path1[i] == '\0' + .../foo path2[i] == '/' + or + / path1[i] == '\0' + /foo path2[i] != '/' + + Other root paths (like X:/) fall under the former case: + X:/ path1[i] == '\0' + X:/foo path2[i] != '/' + + Check for '//' to avoid matching '/' and '//srv'. + */ + if (path1[i] == '\0' && path2[i]) + { + if (path1[i - 1] == '/' +#ifdef SVN_USE_DOS_PATHS + || ((type == type_dirent) && path1[i - 1] == ':') +#endif + ) + { + if (path2[i] == '/') + /* .../ + * ..../ + * i */ + return NULL; + else + /* .../ + * .../foo + * i */ + return pool ? apr_pstrdup(pool, path2 + i) : path2 + i; + } + else if (path2[i] == '/') + { + if (path2[i + 1]) + /* ... + * .../foo + * i */ + return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1; + else + /* ... + * .../ + * i */ + return NULL; + } + } + + /* Otherwise, path2 isn't a child. */ + return NULL; +} + + +/**** Public API functions ****/ + +const char * +svn_dirent_internal_style(const char *dirent, apr_pool_t *pool) +{ + return svn_dirent_canonicalize(internal_style(dirent, pool), pool); +} + +const char * +svn_dirent_local_style(const char *dirent, apr_pool_t *pool) +{ + /* Internally, Subversion represents the current directory with the + empty string. But users like to see "." . */ + if (SVN_PATH_IS_EMPTY(dirent)) + return "."; + +#if '/' != SVN_PATH_LOCAL_SEPARATOR + { + char *p = apr_pstrdup(pool, dirent); + dirent = p; + + /* Convert all canonical separators to the local-style ones. */ + for (; *p != '\0'; ++p) + if (*p == '/') + *p = SVN_PATH_LOCAL_SEPARATOR; + } +#endif + + return dirent; +} + +const char * +svn_relpath__internal_style(const char *relpath, + apr_pool_t *pool) +{ + return svn_relpath_canonicalize(internal_style(relpath, pool), pool); +} + + +/* We decided against using apr_filepath_root here because of the negative + performance impact (creating a pool and converting strings ). */ +svn_boolean_t +svn_dirent_is_root(const char *dirent, apr_size_t len) +{ +#ifdef SVN_USE_DOS_PATHS + /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter) + are also root directories */ + if ((len == 2 || ((len == 3) && (dirent[2] == '/'))) && + (dirent[1] == ':') && + ((dirent[0] >= 'A' && dirent[0] <= 'Z') || + (dirent[0] >= 'a' && dirent[0] <= 'z'))) + return TRUE; + + /* On Windows and Cygwin //server/share is a root directory, + and on Cygwin //drive is a drive alias */ + if (len >= 2 && dirent[0] == '/' && dirent[1] == '/' + && dirent[len - 1] != '/') + { + int segments = 0; + apr_size_t i; + for (i = len; i >= 2; i--) + { + if (dirent[i] == '/') + { + segments ++; + if (segments > 1) + return FALSE; + } + } +#ifdef __CYGWIN__ + return (segments <= 1); +#else + return (segments == 1); /* //drive is invalid on plain Windows */ +#endif + } +#endif + + /* directory is root if it's equal to '/' */ + if (len == 1 && dirent[0] == '/') + return TRUE; + + return FALSE; +} + +svn_boolean_t +svn_uri_is_root(const char *uri, apr_size_t len) +{ + assert(svn_uri_is_canonical(uri, NULL)); + return (len == uri_schema_root_length(uri, len)); +} + +char *svn_dirent_join(const char *base, + const char *component, + apr_pool_t *pool) +{ + apr_size_t blen = strlen(base); + apr_size_t clen = strlen(component); + char *dirent; + int add_separator; + + assert(svn_dirent_is_canonical(base, pool)); + assert(svn_dirent_is_canonical(component, pool)); + + /* If the component is absolute, then return it. */ + if (svn_dirent_is_absolute(component)) + return apr_pmemdup(pool, component, clen + 1); + + /* If either is empty return the other */ + if (SVN_PATH_IS_EMPTY(base)) + return apr_pmemdup(pool, component, clen + 1); + if (SVN_PATH_IS_EMPTY(component)) + return apr_pmemdup(pool, base, blen + 1); + +#ifdef SVN_USE_DOS_PATHS + if (component[0] == '/') + { + /* '/' is drive relative on Windows, not absolute like on Posix */ + if (dirent_is_rooted(base)) + { + /* Join component without '/' to root-of(base) */ + blen = dirent_root_length(base, blen); + component++; + clen--; + + if (blen == 2 && base[1] == ':') /* "C:" case */ + { + char *root = apr_pmemdup(pool, base, 3); + root[2] = '/'; /* We don't need the final '\0' */ + + base = root; + blen = 3; + } + + if (clen == 0) + return apr_pstrndup(pool, base, blen); + } + else + return apr_pmemdup(pool, component, clen + 1); + } + else if (dirent_is_rooted(component)) + return apr_pmemdup(pool, component, clen + 1); +#endif /* SVN_USE_DOS_PATHS */ + + /* if last character of base is already a separator, don't add a '/' */ + add_separator = 1; + if (base[blen - 1] == '/' +#ifdef SVN_USE_DOS_PATHS + || base[blen - 1] == ':' +#endif + ) + add_separator = 0; + + /* Construct the new, combined dirent. */ + dirent = apr_palloc(pool, blen + add_separator + clen + 1); + memcpy(dirent, base, blen); + if (add_separator) + dirent[blen] = '/'; + memcpy(dirent + blen + add_separator, component, clen + 1); + + return dirent; +} + +char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...) +{ +#define MAX_SAVED_LENGTHS 10 + apr_size_t saved_lengths[MAX_SAVED_LENGTHS]; + apr_size_t total_len; + int nargs; + va_list va; + const char *s; + apr_size_t len; + char *dirent; + char *p; + int add_separator; + int base_arg = 0; + + total_len = strlen(base); + + assert(svn_dirent_is_canonical(base, pool)); + + /* if last character of base is already a separator, don't add a '/' */ + add_separator = 1; + if (total_len == 0 + || base[total_len - 1] == '/' +#ifdef SVN_USE_DOS_PATHS + || base[total_len - 1] == ':' +#endif + ) + add_separator = 0; + + saved_lengths[0] = total_len; + + /* Compute the length of the resulting string. */ + + nargs = 0; + va_start(va, base); + while ((s = va_arg(va, const char *)) != NULL) + { + len = strlen(s); + + assert(svn_dirent_is_canonical(s, pool)); + + if (SVN_PATH_IS_EMPTY(s)) + continue; + + if (nargs++ < MAX_SAVED_LENGTHS) + saved_lengths[nargs] = len; + + if (dirent_is_rooted(s)) + { + total_len = len; + base_arg = nargs; + +#ifdef SVN_USE_DOS_PATHS + if (!svn_dirent_is_absolute(s)) /* Handle non absolute roots */ + { + /* Set new base and skip the current argument */ + base = s = svn_dirent_join(base, s, pool); + base_arg++; + saved_lengths[0] = total_len = len = strlen(s); + } + else +#endif /* SVN_USE_DOS_PATHS */ + { + base = ""; /* Don't add base */ + saved_lengths[0] = 0; + } + + add_separator = 1; + if (s[len - 1] == '/' +#ifdef SVN_USE_DOS_PATHS + || s[len - 1] == ':' +#endif + ) + add_separator = 0; + } + else if (nargs <= base_arg + 1) + { + total_len += add_separator + len; + } + else + { + total_len += 1 + len; + } + } + va_end(va); + + /* base == "/" and no further components. just return that. */ + if (add_separator == 0 && total_len == 1) + return apr_pmemdup(pool, "/", 2); + + /* we got the total size. allocate it, with room for a NULL character. */ + dirent = p = apr_palloc(pool, total_len + 1); + + /* if we aren't supposed to skip forward to an absolute component, and if + this is not an empty base that we are skipping, then copy the base + into the output. */ + if (! SVN_PATH_IS_EMPTY(base)) + { + memcpy(p, base, len = saved_lengths[0]); + p += len; + } + + nargs = 0; + va_start(va, base); + while ((s = va_arg(va, const char *)) != NULL) + { + if (SVN_PATH_IS_EMPTY(s)) + continue; + + if (++nargs < base_arg) + continue; + + if (nargs < MAX_SAVED_LENGTHS) + len = saved_lengths[nargs]; + else + len = strlen(s); + + /* insert a separator if we aren't copying in the first component + (which can happen when base_arg is set). also, don't put in a slash + if the prior character is a slash (occurs when prior component + is "/"). */ + if (p != dirent && + ( ! (nargs - 1 <= base_arg) || add_separator)) + *p++ = '/'; + + /* copy the new component and advance the pointer */ + memcpy(p, s, len); + p += len; + } + va_end(va); + + *p = '\0'; + assert((apr_size_t)(p - dirent) == total_len); + + return dirent; +} + +char * +svn_relpath_join(const char *base, + const char *component, + apr_pool_t *pool) +{ + apr_size_t blen = strlen(base); + apr_size_t clen = strlen(component); + char *path; + + assert(relpath_is_canonical(base)); + assert(relpath_is_canonical(component)); + + /* If either is empty return the other */ + if (blen == 0) + return apr_pmemdup(pool, component, clen + 1); + if (clen == 0) + return apr_pmemdup(pool, base, blen + 1); + + path = apr_palloc(pool, blen + 1 + clen + 1); + memcpy(path, base, blen); + path[blen] = '/'; + memcpy(path + blen + 1, component, clen + 1); + + return path; +} + +char * +svn_dirent_dirname(const char *dirent, apr_pool_t *pool) +{ + apr_size_t len = strlen(dirent); + + assert(svn_dirent_is_canonical(dirent, pool)); + + if (len == dirent_root_length(dirent, len)) + return apr_pstrmemdup(pool, dirent, len); + else + return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len)); +} + +const char * +svn_dirent_basename(const char *dirent, apr_pool_t *pool) +{ + apr_size_t len = strlen(dirent); + apr_size_t start; + + assert(!pool || svn_dirent_is_canonical(dirent, pool)); + + if (svn_dirent_is_root(dirent, len)) + return ""; + else + { + start = len; + while (start > 0 && dirent[start - 1] != '/' +#ifdef SVN_USE_DOS_PATHS + && dirent[start - 1] != ':' +#endif + ) + --start; + } + + if (pool) + return apr_pstrmemdup(pool, dirent + start, len - start); + else + return dirent + start; +} + +void +svn_dirent_split(const char **dirpath, + const char **base_name, + const char *dirent, + apr_pool_t *pool) +{ + assert(dirpath != base_name); + + if (dirpath) + *dirpath = svn_dirent_dirname(dirent, pool); + + if (base_name) + *base_name = svn_dirent_basename(dirent, pool); +} + +char * +svn_relpath_dirname(const char *relpath, + apr_pool_t *pool) +{ + apr_size_t len = strlen(relpath); + + assert(relpath_is_canonical(relpath)); + + return apr_pstrmemdup(pool, relpath, + relpath_previous_segment(relpath, len)); +} + +const char * +svn_relpath_basename(const char *relpath, + apr_pool_t *pool) +{ + apr_size_t len = strlen(relpath); + apr_size_t start; + + assert(relpath_is_canonical(relpath)); + + start = len; + while (start > 0 && relpath[start - 1] != '/') + --start; + + if (pool) + return apr_pstrmemdup(pool, relpath + start, len - start); + else + return relpath + start; +} + +void +svn_relpath_split(const char **dirpath, + const char **base_name, + const char *relpath, + apr_pool_t *pool) +{ + assert(dirpath != base_name); + + if (dirpath) + *dirpath = svn_relpath_dirname(relpath, pool); + + if (base_name) + *base_name = svn_relpath_basename(relpath, pool); +} + +char * +svn_uri_dirname(const char *uri, apr_pool_t *pool) +{ + apr_size_t len = strlen(uri); + + assert(svn_uri_is_canonical(uri, pool)); + + if (svn_uri_is_root(uri, len)) + return apr_pstrmemdup(pool, uri, len); + else + return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len)); +} + +const char * +svn_uri_basename(const char *uri, apr_pool_t *pool) +{ + apr_size_t len = strlen(uri); + apr_size_t start; + + assert(svn_uri_is_canonical(uri, NULL)); + + if (svn_uri_is_root(uri, len)) + return ""; + + start = len; + while (start > 0 && uri[start - 1] != '/') + --start; + + return svn_path_uri_decode(uri + start, pool); +} + +void +svn_uri_split(const char **dirpath, + const char **base_name, + const char *uri, + apr_pool_t *pool) +{ + assert(dirpath != base_name); + + if (dirpath) + *dirpath = svn_uri_dirname(uri, pool); + + if (base_name) + *base_name = svn_uri_basename(uri, pool); +} + +char * +svn_dirent_get_longest_ancestor(const char *dirent1, + const char *dirent2, + apr_pool_t *pool) +{ + return apr_pstrndup(pool, dirent1, + get_longest_ancestor_length(type_dirent, dirent1, + dirent2, pool)); +} + +char * +svn_relpath_get_longest_ancestor(const char *relpath1, + const char *relpath2, + apr_pool_t *pool) +{ + assert(relpath_is_canonical(relpath1)); + assert(relpath_is_canonical(relpath2)); + + return apr_pstrndup(pool, relpath1, + get_longest_ancestor_length(type_relpath, relpath1, + relpath2, pool)); +} + +char * +svn_uri_get_longest_ancestor(const char *uri1, + const char *uri2, + apr_pool_t *pool) +{ + apr_size_t uri_ancestor_len; + apr_size_t i = 0; + + assert(svn_uri_is_canonical(uri1, NULL)); + assert(svn_uri_is_canonical(uri2, NULL)); + + /* Find ':' */ + while (1) + { + /* No shared protocol => no common prefix */ + if (uri1[i] != uri2[i]) + return apr_pmemdup(pool, SVN_EMPTY_PATH, + sizeof(SVN_EMPTY_PATH)); + + if (uri1[i] == ':') + break; + + /* They're both URLs, so EOS can't come before ':' */ + assert((uri1[i] != '\0') && (uri2[i] != '\0')); + + i++; + } + + i += 3; /* Advance past '://' */ + + uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i, + uri2 + i, pool); + + if (uri_ancestor_len == 0 || + (uri_ancestor_len == 1 && (uri1 + i)[0] == '/')) + return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH)); + else + return apr_pstrndup(pool, uri1, uri_ancestor_len + i); +} + +const char * +svn_dirent_is_child(const char *parent_dirent, + const char *child_dirent, + apr_pool_t *pool) +{ + return is_child(type_dirent, parent_dirent, child_dirent, pool); +} + +const char * +svn_dirent_skip_ancestor(const char *parent_dirent, + const char *child_dirent) +{ + apr_size_t len = strlen(parent_dirent); + apr_size_t root_len; + + if (0 != strncmp(parent_dirent, child_dirent, len)) + return NULL; /* parent_dirent is no ancestor of child_dirent */ + + if (child_dirent[len] == 0) + return ""; /* parent_dirent == child_dirent */ + + /* Child == parent + more-characters */ + + root_len = dirent_root_length(child_dirent, strlen(child_dirent)); + if (root_len > len) + /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */ + return NULL; + + /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters. + * It must be one of the following forms. + * + * rlen parent child bad? rlen=len? c[len]=/? + * 0 "" "foo" * + * 0 "b" "bad" ! + * 0 "b" "b/foo" * + * 1 "/" "/foo" * + * 1 "/b" "/bad" ! + * 1 "/b" "/b/foo" * + * 2 "a:" "a:foo" * + * 2 "a:b" "a:bad" ! + * 2 "a:b" "a:b/foo" * + * 3 "a:/" "a:/foo" * + * 3 "a:/b" "a:/bad" ! + * 3 "a:/b" "a:/b/foo" * + * 5 "//s/s" "//s/s/foo" * * + * 5 "//s/s/b" "//s/s/bad" ! + * 5 "//s/s/b" "//s/s/b/foo" * + */ + + if (child_dirent[len] == '/') + /* "parent|child" is one of: + * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */ + return child_dirent + len + 1; + + if (root_len == len) + /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */ + return child_dirent + len; + + return NULL; +} + +const char * +svn_relpath_skip_ancestor(const char *parent_relpath, + const char *child_relpath) +{ + apr_size_t len = strlen(parent_relpath); + + assert(relpath_is_canonical(parent_relpath)); + assert(relpath_is_canonical(child_relpath)); + + if (len == 0) + return child_relpath; + + if (0 != strncmp(parent_relpath, child_relpath, len)) + return NULL; /* parent_relpath is no ancestor of child_relpath */ + + if (child_relpath[len] == 0) + return ""; /* parent_relpath == child_relpath */ + + if (child_relpath[len] == '/') + return child_relpath + len + 1; + + return NULL; +} + + +/* */ +static const char * +uri_skip_ancestor(const char *parent_uri, + const char *child_uri) +{ + apr_size_t len = strlen(parent_uri); + + assert(svn_uri_is_canonical(parent_uri, NULL)); + assert(svn_uri_is_canonical(child_uri, NULL)); + + if (0 != strncmp(parent_uri, child_uri, len)) + return NULL; /* parent_uri is no ancestor of child_uri */ + + if (child_uri[len] == 0) + return ""; /* parent_uri == child_uri */ + + if (child_uri[len] == '/') + return child_uri + len + 1; + + return NULL; +} + +const char * +svn_uri_skip_ancestor(const char *parent_uri, + const char *child_uri, + apr_pool_t *result_pool) +{ + const char *result = uri_skip_ancestor(parent_uri, child_uri); + + return result ? svn_path_uri_decode(result, result_pool) : NULL; +} + +svn_boolean_t +svn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent) +{ + return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL; +} + +svn_boolean_t +svn_uri__is_ancestor(const char *parent_uri, const char *child_uri) +{ + return uri_skip_ancestor(parent_uri, child_uri) != NULL; +} + + +svn_boolean_t +svn_dirent_is_absolute(const char *dirent) +{ + if (! dirent) + return FALSE; + + /* dirent is absolute if it starts with '/' on non-Windows platforms + or with '//' on Windows platforms */ + if (dirent[0] == '/' +#ifdef SVN_USE_DOS_PATHS + && dirent[1] == '/' /* Single '/' depends on current drive */ +#endif + ) + return TRUE; + + /* On Windows, dirent is also absolute when it starts with 'H:/' + where 'H' is any letter. */ +#ifdef SVN_USE_DOS_PATHS + if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) && + (dirent[1] == ':') && (dirent[2] == '/')) + return TRUE; +#endif /* SVN_USE_DOS_PATHS */ + + return FALSE; +} + +svn_error_t * +svn_dirent_get_absolute(const char **pabsolute, + const char *relative, + apr_pool_t *pool) +{ + char *buffer; + apr_status_t apr_err; + const char *path_apr; + + SVN_ERR_ASSERT(! svn_path_is_url(relative)); + + /* Merge the current working directory with the relative dirent. */ + SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool)); + + apr_err = apr_filepath_merge(&buffer, NULL, + path_apr, + APR_FILEPATH_NOTRELATIVE, + pool); + if (apr_err) + { + /* In some cases when the passed path or its ancestor(s) do not exist + or no longer exist apr returns an error. + + In many of these cases we would like to return a path anyway, when the + passed path was already a safe absolute path. So check for that now to + avoid an error. + + svn_dirent_is_absolute() doesn't perform the necessary checks to see + if the path doesn't need post processing to be in the canonical absolute + format. + */ + + if (svn_dirent_is_absolute(relative) + && svn_dirent_is_canonical(relative, pool) + && !svn_path_is_backpath_present(relative)) + { + *pabsolute = apr_pstrdup(pool, relative); + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_BAD_FILENAME, + svn_error_create(apr_err, NULL, NULL), + _("Couldn't determine absolute path of '%s'"), + svn_dirent_local_style(relative, pool)); + } + + SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool)); + *pabsolute = svn_dirent_canonicalize(*pabsolute, pool); + return SVN_NO_ERROR; +} + +const char * +svn_uri_canonicalize(const char *uri, apr_pool_t *pool) +{ + return canonicalize(type_uri, uri, pool); +} + +const char * +svn_relpath_canonicalize(const char *relpath, apr_pool_t *pool) +{ + return canonicalize(type_relpath, relpath, pool); +} + +const char * +svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool) +{ + const char *dst = canonicalize(type_dirent, dirent, pool); + +#ifdef SVN_USE_DOS_PATHS + /* Handle a specific case on Windows where path == "X:/". Here we have to + append the final '/', as svn_path_canonicalize will chop this of. */ + if (((dirent[0] >= 'A' && dirent[0] <= 'Z') || + (dirent[0] >= 'a' && dirent[0] <= 'z')) && + dirent[1] == ':' && dirent[2] == '/' && + dst[3] == '\0') + { + char *dst_slash = apr_pcalloc(pool, 4); + dst_slash[0] = canonicalize_to_upper(dirent[0]); + dst_slash[1] = ':'; + dst_slash[2] = '/'; + dst_slash[3] = '\0'; + + return dst_slash; + } +#endif /* SVN_USE_DOS_PATHS */ + + return dst; +} + +svn_boolean_t +svn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool) +{ + const char *ptr = dirent; + if (*ptr == '/') + { + ptr++; +#ifdef SVN_USE_DOS_PATHS + /* Check for UNC paths */ + if (*ptr == '/') + { + /* TODO: Scan hostname and sharename and fall back to part code */ + + /* ### Fall back to old implementation */ + return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool)) + == 0); + } +#endif /* SVN_USE_DOS_PATHS */ + } +#ifdef SVN_USE_DOS_PATHS + else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) && + (ptr[1] == ':')) + { + /* The only canonical drive names are "A:"..."Z:", no lower case */ + if (*ptr < 'A' || *ptr > 'Z') + return FALSE; + + ptr += 2; + + if (*ptr == '/') + ptr++; + } +#endif /* SVN_USE_DOS_PATHS */ + + return relpath_is_canonical(ptr); +} + +static svn_boolean_t +relpath_is_canonical(const char *relpath) +{ + const char *ptr = relpath, *seg = relpath; + + /* RELPATH is canonical if it has: + * - no '.' segments + * - no start and closing '/' + * - no '//' + */ + + if (*relpath == '\0') + return TRUE; + + if (*ptr == '/') + return FALSE; + + /* Now validate the rest of the path. */ + while(1) + { + apr_size_t seglen = ptr - seg; + + if (seglen == 1 && *seg == '.') + return FALSE; /* /./ */ + + if (*ptr == '/' && *(ptr+1) == '/') + return FALSE; /* // */ + + if (! *ptr && *(ptr - 1) == '/') + return FALSE; /* foo/ */ + + if (! *ptr) + break; + + if (*ptr == '/') + ptr++; + seg = ptr; + + while (*ptr && (*ptr != '/')) + ptr++; + } + + return TRUE; +} + +svn_boolean_t +svn_relpath_is_canonical(const char *relpath) +{ + return relpath_is_canonical(relpath); +} + +svn_boolean_t +svn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool) +{ + const char *ptr = uri, *seg = uri; + const char *schema_data = NULL; + + /* URI is canonical if it has: + * - lowercase URL scheme + * - lowercase URL hostname + * - no '.' segments + * - no closing '/' + * - no '//' + * - uppercase hex-encoded pair digits ("%AB", not "%ab") + */ + + if (*uri == '\0') + return FALSE; + + if (! svn_path_is_url(uri)) + return FALSE; + + /* Skip the scheme. */ + while (*ptr && (*ptr != '/') && (*ptr != ':')) + ptr++; + + /* No scheme? No good. */ + if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/')) + return FALSE; + + /* Found a scheme, check that it's all lowercase. */ + ptr = uri; + while (*ptr != ':') + { + if (*ptr >= 'A' && *ptr <= 'Z') + return FALSE; + ptr++; + } + /* Skip :// */ + ptr += 3; + + /* Scheme only? That works. */ + if (! *ptr) + return TRUE; + + /* This might be the hostname */ + seg = ptr; + while (*ptr && (*ptr != '/') && (*ptr != '@')) + ptr++; + + if (*ptr == '@') + seg = ptr + 1; + + /* Found a hostname, check that it's all lowercase. */ + ptr = seg; + + if (*ptr == '[') + { + ptr++; + while (*ptr == ':' + || (*ptr >= '0' && *ptr <= '9') + || (*ptr >= 'a' && *ptr <= 'f')) + { + ptr++; + } + + if (*ptr != ']') + return FALSE; + ptr++; + } + else + while (*ptr && *ptr != '/' && *ptr != ':') + { + if (*ptr >= 'A' && *ptr <= 'Z') + return FALSE; + ptr++; + } + + /* Found a portnumber */ + if (*ptr == ':') + { + apr_int64_t port = 0; + + ptr++; + schema_data = ptr; + + while (*ptr >= '0' && *ptr <= '9') + { + port = 10 * port + (*ptr - '0'); + ptr++; + } + + if (ptr == schema_data) + return FALSE; /* Fail on "http://host:" */ + + if (*ptr && *ptr != '/') + return FALSE; /* Not a port number */ + + if (port == 80 && strncmp(uri, "http:", 5) == 0) + return FALSE; + else if (port == 443 && strncmp(uri, "https:", 6) == 0) + return FALSE; + else if (port == 3690 && strncmp(uri, "svn:", 4) == 0) + return FALSE; + } + + schema_data = ptr; + +#ifdef SVN_USE_DOS_PATHS + if (schema_data && *ptr == '/') + { + /* If this is a file url, ptr now points to the third '/' in + file:///C:/path. Check that if we have such a URL the drive + letter is in uppercase. */ + if (strncmp(uri, "file:", 5) == 0 && + ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') && + *(ptr+2) == ':') + return FALSE; + } +#endif /* SVN_USE_DOS_PATHS */ + + /* Now validate the rest of the URI. */ + while(1) + { + apr_size_t seglen = ptr - seg; + + if (seglen == 1 && *seg == '.') + return FALSE; /* /./ */ + + if (*ptr == '/' && *(ptr+1) == '/') + return FALSE; /* // */ + + if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri) + return FALSE; /* foo/ */ + + if (! *ptr) + break; + + if (*ptr == '/') + ptr++; + seg = ptr; + + + while (*ptr && (*ptr != '/')) + ptr++; + } + + ptr = schema_data; + + while (*ptr) + { + if (*ptr == '%') + { + char digitz[3]; + int val; + + /* Can't usesvn_ctype_isxdigit() because lower case letters are + not in our canonical format */ + if (((*(ptr+1) < '0' || *(ptr+1) > '9')) + && (*(ptr+1) < 'A' || *(ptr+1) > 'F')) + return FALSE; + else if (((*(ptr+2) < '0' || *(ptr+2) > '9')) + && (*(ptr+2) < 'A' || *(ptr+2) > 'F')) + return FALSE; + + digitz[0] = *(++ptr); + digitz[1] = *(++ptr); + digitz[2] = '\0'; + val = (int)strtol(digitz, NULL, 16); + + if (svn_uri__char_validity[val]) + return FALSE; /* Should not have been escaped */ + } + else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr]) + return FALSE; /* Character should have been escaped */ + ptr++; + } + + return TRUE; +} + +svn_error_t * +svn_dirent_condense_targets(const char **pcommon, + apr_array_header_t **pcondensed_targets, + const apr_array_header_t *targets, + svn_boolean_t remove_redundancies, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i, num_condensed = targets->nelts; + svn_boolean_t *removed; + apr_array_header_t *abs_targets; + + /* Early exit when there's no data to work on. */ + if (targets->nelts <= 0) + { + *pcommon = NULL; + if (pcondensed_targets) + *pcondensed_targets = NULL; + return SVN_NO_ERROR; + } + + /* Get the absolute path of the first target. */ + SVN_ERR(svn_dirent_get_absolute(pcommon, + APR_ARRAY_IDX(targets, 0, const char *), + scratch_pool)); + + /* Early exit when there's only one dirent to work on. */ + if (targets->nelts == 1) + { + *pcommon = apr_pstrdup(result_pool, *pcommon); + if (pcondensed_targets) + *pcondensed_targets = apr_array_make(result_pool, 0, + sizeof(const char *)); + return SVN_NO_ERROR; + } + + /* Copy the targets array, but with absolute dirents instead of + relative. Also, find the pcommon argument by finding what is + common in all of the absolute dirents. NOTE: This is not as + efficient as it could be. The calculation of the basedir could + be done in the loop below, which would save some calls to + svn_dirent_get_longest_ancestor. I decided to do it this way + because I thought it would be simpler, since this way, we don't + even do the loop if we don't need to condense the targets. */ + + removed = apr_pcalloc(scratch_pool, (targets->nelts * + sizeof(svn_boolean_t))); + abs_targets = apr_array_make(scratch_pool, targets->nelts, + sizeof(const char *)); + + APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon; + + for (i = 1; i < targets->nelts; ++i) + { + const char *rel = APR_ARRAY_IDX(targets, i, const char *); + const char *absolute; + SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool)); + APR_ARRAY_PUSH(abs_targets, const char *) = absolute; + *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute, + scratch_pool); + } + + *pcommon = apr_pstrdup(result_pool, *pcommon); + + if (pcondensed_targets != NULL) + { + size_t basedir_len; + + if (remove_redundancies) + { + /* Find the common part of each pair of targets. If + common part is equal to one of the dirents, the other + is a child of it, and can be removed. If a target is + equal to *pcommon, it can also be removed. */ + + /* First pass: when one non-removed target is a child of + another non-removed target, remove the child. */ + for (i = 0; i < abs_targets->nelts; ++i) + { + int j; + + if (removed[i]) + continue; + + for (j = i + 1; j < abs_targets->nelts; ++j) + { + const char *abs_targets_i; + const char *abs_targets_j; + const char *ancestor; + + if (removed[j]) + continue; + + abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *); + abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *); + + ancestor = svn_dirent_get_longest_ancestor + (abs_targets_i, abs_targets_j, scratch_pool); + + if (*ancestor == '\0') + continue; + + if (strcmp(ancestor, abs_targets_i) == 0) + { + removed[j] = TRUE; + num_condensed--; + } + else if (strcmp(ancestor, abs_targets_j) == 0) + { + removed[i] = TRUE; + num_condensed--; + } + } + } + + /* Second pass: when a target is the same as *pcommon, + remove the target. */ + for (i = 0; i < abs_targets->nelts; ++i) + { + const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i, + const char *); + + if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i])) + { + removed[i] = TRUE; + num_condensed--; + } + } + } + + /* Now create the return array, and copy the non-removed items */ + basedir_len = strlen(*pcommon); + *pcondensed_targets = apr_array_make(result_pool, num_condensed, + sizeof(const char *)); + + for (i = 0; i < abs_targets->nelts; ++i) + { + const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *); + + /* Skip this if it's been removed. */ + if (removed[i]) + continue; + + /* If a common prefix was found, condensed_targets are given + relative to that prefix. */ + if (basedir_len > 0) + { + /* Only advance our pointer past a dirent separator if + REL_ITEM isn't the same as *PCOMMON. + + If *PCOMMON is a root dirent, basedir_len will already + include the closing '/', so never advance the pointer + here. + */ + rel_item += basedir_len; + if (rel_item[0] && + ! svn_dirent_is_root(*pcommon, basedir_len)) + rel_item++; + } + + APR_ARRAY_PUSH(*pcondensed_targets, const char *) + = apr_pstrdup(result_pool, rel_item); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_uri_condense_targets(const char **pcommon, + apr_array_header_t **pcondensed_targets, + const apr_array_header_t *targets, + svn_boolean_t remove_redundancies, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i, num_condensed = targets->nelts; + apr_array_header_t *uri_targets; + svn_boolean_t *removed; + + /* Early exit when there's no data to work on. */ + if (targets->nelts <= 0) + { + *pcommon = NULL; + if (pcondensed_targets) + *pcondensed_targets = NULL; + return SVN_NO_ERROR; + } + + *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *), + scratch_pool); + + /* Early exit when there's only one uri to work on. */ + if (targets->nelts == 1) + { + *pcommon = apr_pstrdup(result_pool, *pcommon); + if (pcondensed_targets) + *pcondensed_targets = apr_array_make(result_pool, 0, + sizeof(const char *)); + return SVN_NO_ERROR; + } + + /* Find the pcommon argument by finding what is common in all of the + uris. NOTE: This is not as efficient as it could be. The calculation + of the basedir could be done in the loop below, which would + save some calls to svn_uri_get_longest_ancestor. I decided to do it + this way because I thought it would be simpler, since this way, we don't + even do the loop if we don't need to condense the targets. */ + + removed = apr_pcalloc(scratch_pool, (targets->nelts * + sizeof(svn_boolean_t))); + uri_targets = apr_array_make(scratch_pool, targets->nelts, + sizeof(const char *)); + + APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon; + + for (i = 1; i < targets->nelts; ++i) + { + const char *uri = svn_uri_canonicalize( + APR_ARRAY_IDX(targets, i, const char *), + scratch_pool); + APR_ARRAY_PUSH(uri_targets, const char *) = uri; + + /* If the commonmost ancestor so far is empty, there's no point + in continuing to search for a common ancestor at all. But + we'll keep looping for the sake of canonicalizing the + targets, I suppose. */ + if (**pcommon != '\0') + *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri, + scratch_pool); + } + + *pcommon = apr_pstrdup(result_pool, *pcommon); + + if (pcondensed_targets != NULL) + { + size_t basedir_len; + + if (remove_redundancies) + { + /* Find the common part of each pair of targets. If + common part is equal to one of the dirents, the other + is a child of it, and can be removed. If a target is + equal to *pcommon, it can also be removed. */ + + /* First pass: when one non-removed target is a child of + another non-removed target, remove the child. */ + for (i = 0; i < uri_targets->nelts; ++i) + { + int j; + + if (removed[i]) + continue; + + for (j = i + 1; j < uri_targets->nelts; ++j) + { + const char *uri_i; + const char *uri_j; + const char *ancestor; + + if (removed[j]) + continue; + + uri_i = APR_ARRAY_IDX(uri_targets, i, const char *); + uri_j = APR_ARRAY_IDX(uri_targets, j, const char *); + + ancestor = svn_uri_get_longest_ancestor(uri_i, + uri_j, + scratch_pool); + + if (*ancestor == '\0') + continue; + + if (strcmp(ancestor, uri_i) == 0) + { + removed[j] = TRUE; + num_condensed--; + } + else if (strcmp(ancestor, uri_j) == 0) + { + removed[i] = TRUE; + num_condensed--; + } + } + } + + /* Second pass: when a target is the same as *pcommon, + remove the target. */ + for (i = 0; i < uri_targets->nelts; ++i) + { + const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i, + const char *); + + if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i])) + { + removed[i] = TRUE; + num_condensed--; + } + } + } + + /* Now create the return array, and copy the non-removed items */ + basedir_len = strlen(*pcommon); + *pcondensed_targets = apr_array_make(result_pool, num_condensed, + sizeof(const char *)); + + for (i = 0; i < uri_targets->nelts; ++i) + { + const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *); + + /* Skip this if it's been removed. */ + if (removed[i]) + continue; + + /* If a common prefix was found, condensed_targets are given + relative to that prefix. */ + if (basedir_len > 0) + { + /* Only advance our pointer past a dirent separator if + REL_ITEM isn't the same as *PCOMMON. + + If *PCOMMON is a root dirent, basedir_len will already + include the closing '/', so never advance the pointer + here. + */ + rel_item += basedir_len; + if ((rel_item[0] == '/') || + (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len))) + { + rel_item++; + } + } + + APR_ARRAY_PUSH(*pcondensed_targets, const char *) + = svn_path_uri_decode(rel_item, result_pool); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_dirent_is_under_root(svn_boolean_t *under_root, + const char **result_path, + const char *base_path, + const char *path, + apr_pool_t *result_pool) +{ + apr_status_t status; + char *full_path; + + *under_root = FALSE; + if (result_path) + *result_path = NULL; + + status = apr_filepath_merge(&full_path, + base_path, + path, + APR_FILEPATH_NOTABOVEROOT + | APR_FILEPATH_SECUREROOTTEST, + result_pool); + + if (status == APR_SUCCESS) + { + if (result_path) + *result_path = svn_dirent_canonicalize(full_path, result_pool); + *under_root = TRUE; + return SVN_NO_ERROR; + } + else if (status == APR_EABOVEROOT) + { + *under_root = FALSE; + return SVN_NO_ERROR; + } + + return svn_error_wrap_apr(status, NULL); +} + +svn_error_t * +svn_uri_get_dirent_from_file_url(const char **dirent, + const char *url, + apr_pool_t *pool) +{ + const char *hostname, *path; + + SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool)); + + /* Verify that the URL is well-formed (loosely) */ + + /* First, check for the "file://" prefix. */ + if (strncmp(url, "file://", 7) != 0) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Local URL '%s' does not contain 'file://' " + "prefix"), url); + + /* Find the HOSTNAME portion and the PATH portion of the URL. The host + name is between the "file://" prefix and the next occurence of '/'. We + are considering everything from that '/' until the end of the URL to be + the absolute path portion of the URL. + If we got just "file://", treat it the same as "file:///". */ + hostname = url + 7; + path = strchr(hostname, '/'); + if (path) + hostname = apr_pstrmemdup(pool, hostname, path - hostname); + else + path = "/"; + + /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */ + if (*hostname == '\0') + hostname = NULL; + else + { + hostname = svn_path_uri_decode(hostname, pool); + if (strcmp(hostname, "localhost") == 0) + hostname = NULL; + } + + /* Duplicate the URL, starting at the top of the path. + At the same time, we URI-decode the path. */ +#ifdef SVN_USE_DOS_PATHS + /* On Windows, we'll typically have to skip the leading / if the + path starts with a drive letter. Like most Web browsers, We + support two variants of this scheme: + + file:///X:/path and + file:///X|/path + + Note that, at least on WinNT and above, file:////./X:/path will + also work, so we must make sure the transformation doesn't break + that, and file:///path (that looks within the current drive + only) should also keep working. + If we got a non-empty hostname other than localhost, we convert this + into an UNC path. In this case, we obviously don't strip the slash + even if the path looks like it starts with a drive letter. + */ + { + static const char valid_drive_letters[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + /* Casting away const! */ + char *dup_path = (char *)svn_path_uri_decode(path, pool); + + /* This check assumes ':' and '|' are already decoded! */ + if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1]) + && (dup_path[2] == ':' || dup_path[2] == '|')) + { + /* Skip the leading slash. */ + ++dup_path; + + if (dup_path[1] == '|') + dup_path[1] = ':'; + + if (dup_path[2] == '/' || dup_path[2] == '\0') + { + if (dup_path[2] == '\0') + { + /* A valid dirent for the driveroot must be like "C:/" instead of + just "C:" or svn_dirent_join() will use the current directory + on the drive instead */ + char *new_path = apr_pcalloc(pool, 4); + new_path[0] = dup_path[0]; + new_path[1] = ':'; + new_path[2] = '/'; + new_path[3] = '\0'; + dup_path = new_path; + } + } + } + if (hostname) + { + if (dup_path[0] == '/' && dup_path[1] == '\0') + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Local URL '%s' contains only a hostname, " + "no path"), url); + + /* We still know that the path starts with a slash. */ + *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL); + } + else + *dirent = dup_path; + } +#else /* !SVN_USE_DOS_PATHS */ + /* Currently, the only hostnames we are allowing on non-Win32 platforms + are the empty string and 'localhost'. */ + if (hostname) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Local URL '%s' contains unsupported hostname"), + url); + + *dirent = svn_path_uri_decode(path, pool); +#endif /* SVN_USE_DOS_PATHS */ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_uri_get_file_url_from_dirent(const char **url, + const char *dirent, + apr_pool_t *pool) +{ + assert(svn_dirent_is_canonical(dirent, pool)); + + SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool)); + + dirent = svn_path_uri_encode(dirent, pool); + +#ifndef SVN_USE_DOS_PATHS + if (dirent[0] == '/' && dirent[1] == '\0') + dirent = NULL; /* "file://" is the canonical form of "file:///" */ + + *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL); +#else + if (dirent[0] == '/') + { + /* Handle UNC paths //server/share -> file://server/share */ + assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */ + + *url = apr_pstrcat(pool, "file:", dirent, NULL); + } + else + { + char *uri = apr_pstrcat(pool, "file:///", dirent, NULL); + apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent); + + /* "C:/" is a canonical dirent on Windows, + but "file:///C:/" is not a canonical uri */ + if (uri[len-1] == '/') + uri[len-1] = '\0'; + + *url = uri; + } +#endif + + return SVN_NO_ERROR; +} + + + +/* -------------- The fspath API (see private/svn_fspath.h) -------------- */ + +svn_boolean_t +svn_fspath__is_canonical(const char *fspath) +{ + return fspath[0] == '/' && relpath_is_canonical(fspath + 1); +} + + +const char * +svn_fspath__canonicalize(const char *fspath, + apr_pool_t *pool) +{ + if ((fspath[0] == '/') && (fspath[1] == '\0')) + return "/"; + + return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool), + (char *)NULL); +} + + +svn_boolean_t +svn_fspath__is_root(const char *fspath, apr_size_t len) +{ + /* directory is root if it's equal to '/' */ + return (len == 1 && fspath[0] == '/'); +} + + +const char * +svn_fspath__skip_ancestor(const char *parent_fspath, + const char *child_fspath) +{ + assert(svn_fspath__is_canonical(parent_fspath)); + assert(svn_fspath__is_canonical(child_fspath)); + + return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1); +} + + +const char * +svn_fspath__dirname(const char *fspath, + apr_pool_t *pool) +{ + assert(svn_fspath__is_canonical(fspath)); + + if (fspath[0] == '/' && fspath[1] == '\0') + return apr_pstrdup(pool, fspath); + else + return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool), + (char *)NULL); +} + + +const char * +svn_fspath__basename(const char *fspath, + apr_pool_t *pool) +{ + const char *result; + assert(svn_fspath__is_canonical(fspath)); + + result = svn_relpath_basename(fspath + 1, pool); + + assert(strchr(result, '/') == NULL); + return result; +} + +void +svn_fspath__split(const char **dirpath, + const char **base_name, + const char *fspath, + apr_pool_t *result_pool) +{ + assert(dirpath != base_name); + + if (dirpath) + *dirpath = svn_fspath__dirname(fspath, result_pool); + + if (base_name) + *base_name = svn_fspath__basename(fspath, result_pool); +} + +char * +svn_fspath__join(const char *fspath, + const char *relpath, + apr_pool_t *result_pool) +{ + char *result; + assert(svn_fspath__is_canonical(fspath)); + assert(svn_relpath_is_canonical(relpath)); + + if (relpath[0] == '\0') + result = apr_pstrdup(result_pool, fspath); + else if (fspath[1] == '\0') + result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL); + else + result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL); + + assert(svn_fspath__is_canonical(result)); + return result; +} + +char * +svn_fspath__get_longest_ancestor(const char *fspath1, + const char *fspath2, + apr_pool_t *result_pool) +{ + char *result; + assert(svn_fspath__is_canonical(fspath1)); + assert(svn_fspath__is_canonical(fspath2)); + + result = apr_pstrcat(result_pool, "/", + svn_relpath_get_longest_ancestor(fspath1 + 1, + fspath2 + 1, + result_pool), + (char *)NULL); + + assert(svn_fspath__is_canonical(result)); + return result; +} + + + + +/* -------------- The urlpath API (see private/svn_fspath.h) ------------- */ + +const char * +svn_urlpath__canonicalize(const char *uri, + apr_pool_t *pool) +{ + if (svn_path_is_url(uri)) + { + uri = svn_uri_canonicalize(uri, pool); + } + else + { + uri = svn_fspath__canonicalize(uri, pool); + /* Do a little dance to normalize hex encoding. */ + uri = svn_path_uri_decode(uri, pool); + uri = svn_path_uri_encode(uri, pool); + } + return uri; +} diff --git a/subversion/libsvn_subr/dirent_uri.h b/subversion/libsvn_subr/dirent_uri.h new file mode 100644 index 000000000000..660fb344ba35 --- /dev/null +++ b/subversion/libsvn_subr/dirent_uri.h @@ -0,0 +1,40 @@ +/* + * dirent_uri.h : private header for the dirent, uri, relpath implementation. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_LIBSVN_SUBR_DIRENT_URI_H +#define SVN_LIBSVN_SUBR_DIRENT_URI_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Character map used to check which characters need escaping when + used in a uri. See path.c for more details */ +extern const char svn_uri__char_validity[256]; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_DIRENT_URI_H */ diff --git a/subversion/libsvn_subr/dso.c b/subversion/libsvn_subr/dso.c new file mode 100644 index 000000000000..3fa251798953 --- /dev/null +++ b/subversion/libsvn_subr/dso.c @@ -0,0 +1,117 @@ +/* + * ==================================================================== + * 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_thread_mutex.h> +#include <apr_hash.h> + +#include "svn_hash.h" +#include "svn_dso.h" +#include "svn_pools.h" +#include "svn_private_config.h" + +#include "private/svn_mutex.h" + +/* A mutex to protect our global pool and cache. */ +static svn_mutex__t *dso_mutex = NULL; + +/* Global pool to allocate DSOs in. */ +static apr_pool_t *dso_pool; + +/* Global cache for storing DSO objects. */ +static apr_hash_t *dso_cache; + +/* Just an arbitrary location in memory... */ +static int not_there_sentinel; + +/* A specific value we store in the dso_cache to indicate that the + library wasn't found. This keeps us from allocating extra memory + from dso_pool when trying to find libraries we already know aren't + there. */ +#define NOT_THERE ((void *) ¬_there_sentinel) + +svn_error_t * +svn_dso_initialize2(void) +{ + if (dso_pool) + return SVN_NO_ERROR; + + dso_pool = svn_pool_create(NULL); + + SVN_ERR(svn_mutex__init(&dso_mutex, TRUE, dso_pool)); + + dso_cache = apr_hash_make(dso_pool); + return SVN_NO_ERROR; +} + +#if APR_HAS_DSO +static svn_error_t * +svn_dso_load_internal(apr_dso_handle_t **dso, const char *fname) +{ + *dso = svn_hash_gets(dso_cache, fname); + + /* First check to see if we've been through this before... We do this + to avoid calling apr_dso_load multiple times for a given library, + which would result in wasting small amounts of memory each time. */ + if (*dso == NOT_THERE) + { + *dso = NULL; + return SVN_NO_ERROR; + } + + /* If we got nothing back from the cache, try and load the library. */ + if (! *dso) + { + apr_status_t status = apr_dso_load(dso, fname, dso_pool); + if (status) + { +#ifdef SVN_DEBUG_DSO + char buf[1024]; + fprintf(stderr, + "Dynamic loading of '%s' failed with the following error:\n%s\n", + fname, + apr_dso_error(*dso, buf, 1024)); +#endif + *dso = NULL; + + /* It wasn't found, so set the special "we didn't find it" value. */ + svn_hash_sets(dso_cache, apr_pstrdup(dso_pool, fname), NOT_THERE); + + return SVN_NO_ERROR; + } + + /* Stash the dso so we can use it next time. */ + svn_hash_sets(dso_cache, apr_pstrdup(dso_pool, fname), *dso); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_dso_load(apr_dso_handle_t **dso, const char *fname) +{ + if (! dso_pool) + SVN_ERR(svn_dso_initialize2()); + + SVN_MUTEX__WITH_LOCK(dso_mutex, svn_dso_load_internal(dso, fname)); + + return SVN_NO_ERROR; +} +#endif /* APR_HAS_DSO */ diff --git a/subversion/libsvn_subr/eol.c b/subversion/libsvn_subr/eol.c new file mode 100644 index 000000000000..88a6a376b61e --- /dev/null +++ b/subversion/libsvn_subr/eol.c @@ -0,0 +1,108 @@ +/* + * eol.c : generic eol/keyword routines + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC + +#include <apr_file_io.h> +#include "svn_io.h" +#include "private/svn_eol_private.h" +#include "private/svn_dep_compat.h" + +/* Machine-word-sized masks used in svn_eol__find_eol_start. + */ +char * +svn_eol__find_eol_start(char *buf, apr_size_t len) +{ +#if !SVN_UNALIGNED_ACCESS_IS_OK + + /* On some systems, we need to make sure that buf is properly aligned + * for chunky data access. This overhead is still justified because + * only lines tend to be tens of chars long. + */ + for (; (len > 0) && ((apr_uintptr_t)buf) & (sizeof(apr_uintptr_t)-1) + ; ++buf, --len) + { + if (*buf == '\n' || *buf == '\r') + return buf; + } + +#endif + + /* Scan the input one machine word at a time. */ + for (; len > sizeof(apr_uintptr_t) + ; buf += sizeof(apr_uintptr_t), len -= sizeof(apr_uintptr_t)) + { + /* This is a variant of the well-known strlen test: */ + apr_uintptr_t chunk = *(const apr_uintptr_t *)buf; + + /* A byte in SVN__R_TEST is \0, iff it was \r in *BUF. + * Similarly, SVN__N_TEST is an indicator for \n. */ + apr_uintptr_t r_test = chunk ^ SVN__R_MASK; + apr_uintptr_t n_test = chunk ^ SVN__N_MASK; + + /* A byte in SVN__R_TEST can by < 0x80, iff it has been \0 before + * (i.e. \r in *BUF). Dito for SVN__N_TEST. */ + r_test |= (r_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET; + n_test |= (n_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET; + + /* Check whether at least one of the words contains a byte <0x80 + * (if one is detected, there was a \r or \n in CHUNK). */ + if ((r_test & n_test & SVN__BIT_7_SET) != SVN__BIT_7_SET) + break; + } + + /* The remaining odd bytes will be examined the naive way: */ + for (; len > 0; ++buf, --len) + { + if (*buf == '\n' || *buf == '\r') + return buf; + } + + return NULL; +} + +const char * +svn_eol__detect_eol(char *buf, apr_size_t len, char **eolp) +{ + char *eol; + + eol = svn_eol__find_eol_start(buf, len); + if (eol) + { + if (eolp) + *eolp = eol; + + if (*eol == '\n') + return "\n"; + + /* We found a CR. */ + ++eol; + if (eol == buf + len || *eol != '\n') + return "\r"; + return "\r\n"; + } + + return NULL; +} diff --git a/subversion/libsvn_subr/error.c b/subversion/libsvn_subr/error.c new file mode 100644 index 000000000000..2f04320c5bee --- /dev/null +++ b/subversion/libsvn_subr/error.c @@ -0,0 +1,800 @@ +/* error.c: common exception handling for Subversion + * + * ==================================================================== + * 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 <stdarg.h> + +#include <apr_general.h> +#include <apr_pools.h> +#include <apr_strings.h> + +#include <zlib.h> + +#ifndef SVN_ERR__TRACING +#define SVN_ERR__TRACING +#endif +#include "svn_cmdline.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_utf.h" + +#ifdef SVN_DEBUG +/* XXX FIXME: These should be protected by a thread mutex. + svn_error__locate and make_error_internal should cooperate + in locking and unlocking it. */ + +/* XXX TODO: Define mutex here #if APR_HAS_THREADS */ +static const char * volatile error_file = NULL; +static long volatile error_line = -1; + +/* file_line for the non-debug case. */ +static const char SVN_FILE_LINE_UNDEFINED[] = "svn:<undefined>"; +#endif /* SVN_DEBUG */ + +#include "svn_private_config.h" +#include "private/svn_error_private.h" + + +/* + * Undefine the helpers for creating errors. + * + * *NOTE*: Any use of these functions in any other function may need + * to call svn_error__locate() because the macro that would otherwise + * do this is being undefined and the filename and line number will + * not be properly set in the static error_file and error_line + * variables. + */ +#undef svn_error_create +#undef svn_error_createf +#undef svn_error_quick_wrap +#undef svn_error_wrap_apr + +/* Note: Although this is a "__" function, it was historically in the + * public ABI, so we can never change it or remove its signature, even + * though it is now only used in SVN_DEBUG mode. */ +void +svn_error__locate(const char *file, long line) +{ +#if defined(SVN_DEBUG) + /* XXX TODO: Lock mutex here */ + error_file = file; + error_line = line; +#endif +} + + +/* Cleanup function for errors. svn_error_clear () removes this so + errors that are properly handled *don't* hit this code. */ +#if defined(SVN_DEBUG) +static apr_status_t err_abort(void *data) +{ + svn_error_t *err = data; /* For easy viewing in a debugger */ + err = err; /* Fake a use for the variable to avoid compiler warnings */ + + if (!getenv("SVN_DBG_NO_ABORT_ON_ERROR_LEAK")) + abort(); + return APR_SUCCESS; +} +#endif + + +static svn_error_t * +make_error_internal(apr_status_t apr_err, + svn_error_t *child) +{ + apr_pool_t *pool; + svn_error_t *new_error; + + /* Reuse the child's pool, or create our own. */ + if (child) + pool = child->pool; + else + { + if (apr_pool_create(&pool, NULL)) + abort(); + } + + /* Create the new error structure */ + new_error = apr_pcalloc(pool, sizeof(*new_error)); + + /* Fill 'er up. */ + new_error->apr_err = apr_err; + new_error->child = child; + new_error->pool = pool; +#if defined(SVN_DEBUG) + new_error->file = error_file; + new_error->line = error_line; + /* XXX TODO: Unlock mutex here */ + + if (! child) + apr_pool_cleanup_register(pool, new_error, + err_abort, + apr_pool_cleanup_null); +#endif + + return new_error; +} + + + +/*** Creating and destroying errors. ***/ + +svn_error_t * +svn_error_create(apr_status_t apr_err, + svn_error_t *child, + const char *message) +{ + svn_error_t *err; + + err = make_error_internal(apr_err, child); + + if (message) + err->message = apr_pstrdup(err->pool, message); + + return err; +} + + +svn_error_t * +svn_error_createf(apr_status_t apr_err, + svn_error_t *child, + const char *fmt, + ...) +{ + svn_error_t *err; + va_list ap; + + err = make_error_internal(apr_err, child); + + va_start(ap, fmt); + err->message = apr_pvsprintf(err->pool, fmt, ap); + va_end(ap); + + return err; +} + + +svn_error_t * +svn_error_wrap_apr(apr_status_t status, + const char *fmt, + ...) +{ + svn_error_t *err, *utf8_err; + va_list ap; + char errbuf[255]; + const char *msg_apr, *msg; + + err = make_error_internal(status, NULL); + + if (fmt) + { + /* Grab the APR error message. */ + apr_strerror(status, errbuf, sizeof(errbuf)); + utf8_err = svn_utf_cstring_to_utf8(&msg_apr, errbuf, err->pool); + if (utf8_err) + msg_apr = NULL; + svn_error_clear(utf8_err); + + /* Append it to the formatted message. */ + va_start(ap, fmt); + msg = apr_pvsprintf(err->pool, fmt, ap); + va_end(ap); + if (msg_apr) + { + err->message = apr_pstrcat(err->pool, msg, ": ", msg_apr, NULL); + } + else + { + err->message = msg; + } + } + + return err; +} + + +svn_error_t * +svn_error_quick_wrap(svn_error_t *child, const char *new_msg) +{ + if (child == SVN_NO_ERROR) + return SVN_NO_ERROR; + + return svn_error_create(child->apr_err, + child, + new_msg); +} + +/* Messages in tracing errors all point to this static string. */ +static const char error_tracing_link[] = "traced call"; + +svn_error_t * +svn_error__trace(const char *file, long line, svn_error_t *err) +{ +#ifndef SVN_DEBUG + + /* We shouldn't even be here, but whatever. Just return the error as-is. */ + return err; + +#else + + /* Only do the work when an error occurs. */ + if (err) + { + svn_error_t *trace; + svn_error__locate(file, line); + trace = make_error_internal(err->apr_err, err); + trace->message = error_tracing_link; + return trace; + } + return SVN_NO_ERROR; + +#endif +} + + +svn_error_t * +svn_error_compose_create(svn_error_t *err1, + svn_error_t *err2) +{ + if (err1 && err2) + { + svn_error_compose(err1, + svn_error_quick_wrap(err2, + _("Additional errors:"))); + return err1; + } + return err1 ? err1 : err2; +} + + +void +svn_error_compose(svn_error_t *chain, svn_error_t *new_err) +{ + apr_pool_t *pool = chain->pool; + apr_pool_t *oldpool = new_err->pool; + + while (chain->child) + chain = chain->child; + +#if defined(SVN_DEBUG) + /* Kill existing handler since the end of the chain is going to change */ + apr_pool_cleanup_kill(pool, chain, err_abort); +#endif + + /* Copy the new error chain into the old chain's pool. */ + while (new_err) + { + chain->child = apr_palloc(pool, sizeof(*chain->child)); + chain = chain->child; + *chain = *new_err; + if (chain->message) + chain->message = apr_pstrdup(pool, new_err->message); + chain->pool = pool; +#if defined(SVN_DEBUG) + if (! new_err->child) + apr_pool_cleanup_kill(oldpool, new_err, err_abort); +#endif + new_err = new_err->child; + } + +#if defined(SVN_DEBUG) + apr_pool_cleanup_register(pool, chain, + err_abort, + apr_pool_cleanup_null); +#endif + + /* Destroy the new error chain. */ + svn_pool_destroy(oldpool); +} + +svn_error_t * +svn_error_root_cause(svn_error_t *err) +{ + while (err) + { + if (err->child) + err = err->child; + else + break; + } + + return err; +} + +svn_error_t * +svn_error_find_cause(svn_error_t *err, apr_status_t apr_err) +{ + svn_error_t *child; + + for (child = err; child; child = child->child) + if (child->apr_err == apr_err) + return child; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_error_dup(svn_error_t *err) +{ + apr_pool_t *pool; + svn_error_t *new_err = NULL, *tmp_err = NULL; + + if (apr_pool_create(&pool, NULL)) + abort(); + + for (; err; err = err->child) + { + if (! new_err) + { + new_err = apr_palloc(pool, sizeof(*new_err)); + tmp_err = new_err; + } + else + { + tmp_err->child = apr_palloc(pool, sizeof(*tmp_err->child)); + tmp_err = tmp_err->child; + } + *tmp_err = *err; + tmp_err->pool = pool; + if (tmp_err->message) + tmp_err->message = apr_pstrdup(pool, tmp_err->message); + } + +#if defined(SVN_DEBUG) + apr_pool_cleanup_register(pool, tmp_err, + err_abort, + apr_pool_cleanup_null); +#endif + + return new_err; +} + +void +svn_error_clear(svn_error_t *err) +{ + if (err) + { +#if defined(SVN_DEBUG) + while (err->child) + err = err->child; + apr_pool_cleanup_kill(err->pool, err, err_abort); +#endif + svn_pool_destroy(err->pool); + } +} + +svn_boolean_t +svn_error__is_tracing_link(svn_error_t *err) +{ +#ifdef SVN_ERR__TRACING + /* ### A strcmp()? Really? I think it's the best we can do unless + ### we add a boolean field to svn_error_t that's set only for + ### these "placeholder error chain" items. Not such a bad idea, + ### really... */ + return (err && err->message && !strcmp(err->message, error_tracing_link)); +#else + return FALSE; +#endif +} + +svn_error_t * +svn_error_purge_tracing(svn_error_t *err) +{ +#ifdef SVN_ERR__TRACING + svn_error_t *new_err = NULL, *new_err_leaf = NULL; + + if (! err) + return SVN_NO_ERROR; + + do + { + svn_error_t *tmp_err; + + /* Skip over any trace-only links. */ + while (err && svn_error__is_tracing_link(err)) + err = err->child; + + /* The link must be a real link in the error chain, otherwise an + error chain with trace only links would map into SVN_NO_ERROR. */ + if (! err) + return svn_error_create( + SVN_ERR_ASSERTION_ONLY_TRACING_LINKS, + svn_error_compose_create( + svn_error__malfunction(TRUE, __FILE__, __LINE__, + NULL /* ### say something? */), + err), + NULL); + + /* Copy the current error except for its child error pointer + into the new error. Share any message and source filename + strings from the error. */ + tmp_err = apr_palloc(err->pool, sizeof(*tmp_err)); + *tmp_err = *err; + tmp_err->child = NULL; + + /* Add a new link to the new chain (creating the chain if necessary). */ + if (! new_err) + { + new_err = tmp_err; + new_err_leaf = tmp_err; + } + else + { + new_err_leaf->child = tmp_err; + new_err_leaf = tmp_err; + } + + /* Advance to the next link in the original chain. */ + err = err->child; + } while (err); + + return new_err; +#else /* SVN_ERR__TRACING */ + return err; +#endif /* SVN_ERR__TRACING */ +} + +/* ### The logic around omitting (sic) apr_err= in maintainer mode is tightly + ### coupled to the current sole caller.*/ +static void +print_error(svn_error_t *err, FILE *stream, const char *prefix) +{ + char errbuf[256]; + const char *err_string; + svn_error_t *temp_err = NULL; /* ensure initialized even if + err->file == NULL */ + /* Pretty-print the error */ + /* Note: we can also log errors here someday. */ + +#ifdef SVN_DEBUG + /* Note: err->file is _not_ in UTF-8, because it's expanded from + the __FILE__ preprocessor macro. */ + const char *file_utf8; + + if (err->file + && !(temp_err = svn_utf_cstring_to_utf8(&file_utf8, err->file, + err->pool))) + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + "%s:%ld", err->file, err->line)); + else + { + svn_error_clear(svn_cmdline_fputs(SVN_FILE_LINE_UNDEFINED, + stream, err->pool)); + svn_error_clear(temp_err); + } + + { + const char *symbolic_name; + if (svn_error__is_tracing_link(err)) + /* Skip it; the error code will be printed by the real link. */ + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, ",\n")); + else if ((symbolic_name = svn_error_symbolic_name(err->apr_err))) + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + ": (apr_err=%s)\n", symbolic_name)); + else + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + ": (apr_err=%d)\n", err->apr_err)); + } +#endif /* SVN_DEBUG */ + + /* "traced call" */ + if (svn_error__is_tracing_link(err)) + { + /* Skip it. We already printed the file-line coordinates. */ + } + /* Only print the same APR error string once. */ + else if (err->message) + { + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + "%sE%06d: %s\n", + prefix, err->apr_err, err->message)); + } + else + { + /* Is this a Subversion-specific error code? */ + if ((err->apr_err > APR_OS_START_USEERR) + && (err->apr_err <= APR_OS_START_CANONERR)) + err_string = svn_strerror(err->apr_err, errbuf, sizeof(errbuf)); + /* Otherwise, this must be an APR error code. */ + else if ((temp_err = svn_utf_cstring_to_utf8 + (&err_string, apr_strerror(err->apr_err, errbuf, + sizeof(errbuf)), err->pool))) + { + svn_error_clear(temp_err); + err_string = _("Can't recode error string from APR"); + } + + svn_error_clear(svn_cmdline_fprintf(stream, err->pool, + "%sE%06d: %s\n", + prefix, err->apr_err, err_string)); + } +} + +void +svn_handle_error(svn_error_t *err, FILE *stream, svn_boolean_t fatal) +{ + svn_handle_error2(err, stream, fatal, "svn: "); +} + +void +svn_handle_error2(svn_error_t *err, + FILE *stream, + svn_boolean_t fatal, + const char *prefix) +{ + /* In a long error chain, there may be multiple errors with the same + error code and no custom message. We only want to print the + default message for that code once; printing it multiple times + would add no useful information. The 'empties' array below + remembers the codes of empty errors already seen in the chain. + + We could allocate it in err->pool, but there's no telling how + long err will live or how many times it will get handled. So we + use a subpool. */ + apr_pool_t *subpool; + apr_array_header_t *empties; + svn_error_t *tmp_err; + + /* ### The rest of this file carefully avoids using svn_pool_*(), + preferring apr_pool_*() instead. I can't remember why -- it may + be an artifact of r843793, or it may be for some deeper reason -- + but I'm playing it safe and using apr_pool_*() here too. */ + apr_pool_create(&subpool, err->pool); + empties = apr_array_make(subpool, 0, sizeof(apr_status_t)); + + tmp_err = err; + while (tmp_err) + { + svn_boolean_t printed_already = FALSE; + + if (! tmp_err->message) + { + int i; + + for (i = 0; i < empties->nelts; i++) + { + if (tmp_err->apr_err == APR_ARRAY_IDX(empties, i, apr_status_t) ) + { + printed_already = TRUE; + break; + } + } + } + + if (! printed_already) + { + print_error(tmp_err, stream, prefix); + if (! tmp_err->message) + { + APR_ARRAY_PUSH(empties, apr_status_t) = tmp_err->apr_err; + } + } + + tmp_err = tmp_err->child; + } + + svn_pool_destroy(subpool); + + fflush(stream); + if (fatal) + { + /* Avoid abort()s in maintainer mode. */ + svn_error_clear(err); + + /* We exit(1) here instead of abort()ing so that atexit handlers + get called. */ + exit(EXIT_FAILURE); + } +} + + +void +svn_handle_warning(FILE *stream, svn_error_t *err) +{ + svn_handle_warning2(stream, err, "svn: "); +} + +void +svn_handle_warning2(FILE *stream, svn_error_t *err, const char *prefix) +{ + char buf[256]; + + svn_error_clear(svn_cmdline_fprintf + (stream, err->pool, + _("%swarning: W%06d: %s\n"), + prefix, err->apr_err, + svn_err_best_message(err, buf, sizeof(buf)))); + fflush(stream); +} + +const char * +svn_err_best_message(svn_error_t *err, char *buf, apr_size_t bufsize) +{ + /* Skip over any trace records. */ + while (svn_error__is_tracing_link(err)) + err = err->child; + if (err->message) + return err->message; + else + return svn_strerror(err->apr_err, buf, bufsize); +} + + +/* svn_strerror() and helpers */ + +/* Duplicate of the same typedef in tests/libsvn_subr/error-code-test.c */ +typedef struct err_defn { + svn_errno_t errcode; /* 160004 */ + const char *errname; /* SVN_ERR_FS_CORRUPT */ + const char *errdesc; /* default message */ +} err_defn; + +/* To understand what is going on here, read svn_error_codes.h. */ +#define SVN_ERROR_BUILD_ARRAY +#include "svn_error_codes.h" + +char * +svn_strerror(apr_status_t statcode, char *buf, apr_size_t bufsize) +{ + const err_defn *defn; + + for (defn = error_table; defn->errdesc != NULL; ++defn) + if (defn->errcode == (svn_errno_t)statcode) + { + apr_cpystrn(buf, _(defn->errdesc), bufsize); + return buf; + } + + return apr_strerror(statcode, buf, bufsize); +} + +const char * +svn_error_symbolic_name(apr_status_t statcode) +{ + const err_defn *defn; + + for (defn = error_table; defn->errdesc != NULL; ++defn) + if (defn->errcode == (svn_errno_t)statcode) + return defn->errname; + + /* "No error" is not in error_table. */ + if (statcode == SVN_NO_ERROR) + return "SVN_NO_ERROR"; + + return NULL; +} + + + +/* Malfunctions. */ + +svn_error_t * +svn_error_raise_on_malfunction(svn_boolean_t can_return, + const char *file, int line, + const char *expr) +{ + if (!can_return) + abort(); /* Nothing else we can do as a library */ + + /* The filename and line number of the error source needs to be set + here because svn_error_createf() is not the macro defined in + svn_error.h but the real function. */ + svn_error__locate(file, line); + + if (expr) + return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, + _("In file '%s' line %d: assertion failed (%s)"), + file, line, expr); + else + return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, + _("In file '%s' line %d: internal malfunction"), + file, line); +} + +svn_error_t * +svn_error_abort_on_malfunction(svn_boolean_t can_return, + const char *file, int line, + const char *expr) +{ + svn_error_t *err = svn_error_raise_on_malfunction(TRUE, file, line, expr); + + svn_handle_error2(err, stderr, FALSE, "svn: "); + abort(); + return err; /* Not reached. */ +} + +/* The current handler for reporting malfunctions, and its default setting. */ +static svn_error_malfunction_handler_t malfunction_handler + = svn_error_abort_on_malfunction; + +svn_error_malfunction_handler_t +svn_error_set_malfunction_handler(svn_error_malfunction_handler_t func) +{ + svn_error_malfunction_handler_t old_malfunction_handler + = malfunction_handler; + + malfunction_handler = func; + return old_malfunction_handler; +} + +/* Note: Although this is a "__" function, it is in the public ABI, so + * we can never remove it or change its signature. */ +svn_error_t * +svn_error__malfunction(svn_boolean_t can_return, + const char *file, int line, + const char *expr) +{ + return malfunction_handler(can_return, file, line, expr); +} + + +/* Misc. */ + +svn_error_t * +svn_error__wrap_zlib(int zerr, const char *function, const char *message) +{ + apr_status_t status; + const char *zmsg; + + if (zerr == Z_OK) + return SVN_NO_ERROR; + + switch (zerr) + { + case Z_STREAM_ERROR: + status = SVN_ERR_STREAM_MALFORMED_DATA; + zmsg = _("stream error"); + break; + + case Z_MEM_ERROR: + status = APR_ENOMEM; + zmsg = _("out of memory"); + break; + + case Z_BUF_ERROR: + status = APR_ENOMEM; + zmsg = _("buffer error"); + break; + + case Z_VERSION_ERROR: + status = SVN_ERR_STREAM_UNRECOGNIZED_DATA; + zmsg = _("version error"); + break; + + case Z_DATA_ERROR: + status = SVN_ERR_STREAM_MALFORMED_DATA; + zmsg = _("corrupt data"); + break; + + default: + status = SVN_ERR_STREAM_UNRECOGNIZED_DATA; + zmsg = _("unknown error"); + break; + } + + if (message != NULL) + return svn_error_createf(status, NULL, "zlib (%s): %s: %s", function, + zmsg, message); + else + return svn_error_createf(status, NULL, "zlib (%s): %s", function, zmsg); +} diff --git a/subversion/libsvn_subr/genctype.py b/subversion/libsvn_subr/genctype.py new file mode 100755 index 000000000000..21638ba8fc31 --- /dev/null +++ b/subversion/libsvn_subr/genctype.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# +# +# 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. +# +# +"""getctype.py - Generate the svn_ctype character classification table. +""" + +# Table of ASCII character names +names = ('nul', 'soh', 'stx', 'etx', 'eot', 'enq', 'ack', 'bel', + 'bs', 'ht', 'nl', 'vt', 'np', 'cr', 'so', 'si', + 'dle', 'dc1', 'dc2', 'dc3', 'dc4', 'nak', 'syn', 'etb', + 'can', 'em', 'sub', 'esc', 'fs', 'gs', 'rs', 'us', + 'sp', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', 'del') + +# All whitespace characters: +# horizontal tab, vertical tab, new line, form feed, carriage return, space +whitespace = (9, 10, 11, 12, 13, 32) + +# Bytes not valid in UTF-8 sequences +utf8_invalid = (0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF) + +print(' /* **** DO NOT EDIT! ****') +print(' This table was generated by genctype.py, make changes there. */') + +for c in range(256): + bits = [] + + # Ascii subrange + if c < 128: + bits.append('SVN_CTYPE_ASCII') + + if len(names[c]) == 1: + name = names[c].center(3) + else: + name = names[c].ljust(3) + + # Control characters + if c < 32 or c == 127: + bits.append('SVN_CTYPE_CNTRL') + + # Whitespace characters + if c in whitespace: + bits.append('SVN_CTYPE_SPACE') + + # Punctuation marks + if c >= 33 and c < 48 \ + or c >= 58 and c < 65 \ + or c >= 91 and c < 97 \ + or c >= 123 and c < 127: + bits.append('SVN_CTYPE_PUNCT') + + # Decimal digits + elif c >= 48 and c < 58: + bits.append('SVN_CTYPE_DIGIT') + + # Uppercase letters + elif c >= 65 and c < 91: + bits.append('SVN_CTYPE_UPPER') + # Hexadecimal digits + if c <= 70: + bits.append('SVN_CTYPE_XALPHA') + + # Lowercase letters + elif c >= 97 and c < 123: + bits.append('SVN_CTYPE_LOWER') + # Hexadecimal digits + if c <= 102: + bits.append('SVN_CTYPE_XALPHA') + + # UTF-8 multibyte sequences + else: + name = hex(c)[1:] + + # Lead bytes (start of sequence) + if c > 0xC0 and c < 0xFE and c not in utf8_invalid: + bits.append('SVN_CTYPE_UTF8LEAD') + + # Continuation bytes + elif (c & 0xC0) == 0x80: + bits.append('SVN_CTYPE_UTF8CONT') + + if len(bits) == 0: + flags = '0' + else: + flags = ' | '.join(bits) + print(' /* %s */ %s,' % (name, flags)) diff --git a/subversion/libsvn_subr/gpg_agent.c b/subversion/libsvn_subr/gpg_agent.c new file mode 100644 index 000000000000..f0395c00c750 --- /dev/null +++ b/subversion/libsvn_subr/gpg_agent.c @@ -0,0 +1,463 @@ +/* + * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_* + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + +/* This auth provider stores a plaintext password in memory managed by + * a running gpg-agent. In contrast to other password store providers + * it does not save the password to disk. + * + * Prompting is performed by the gpg-agent using a "pinentry" program + * which needs to be installed separately. There are several pinentry + * implementations with different front-ends (e.g. qt, gtk, ncurses). + * + * The gpg-agent will let the password time out after a while, + * or immediately when it receives the SIGHUP signal. + * When the password has timed out it will automatically prompt the + * user for the password again. This is transparent to Subversion. + * + * SECURITY CONSIDERATIONS: + * + * Communication to the agent happens over a UNIX socket, which is located + * in a directory which only the user running Subversion can access. + * However, any program the user runs could access this socket and get + * the Subversion password if the program knows the "cache ID" Subversion + * uses for the password. + * The cache ID is very easy to obtain for programs running as the same user. + * Subversion uses the MD5 of the realmstring as cache ID, and these checksums + * are also used as filenames within ~/.subversion/auth/svn.simple. + * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for + * permission if another program attempts to access the password. + * + * Therefore, while the gpg-agent is running and has the password cached, + * this provider is no more secure than a file storing the password in + * plaintext. + */ + + +/*** Includes. ***/ + +#ifndef WIN32 + +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include <apr_pools.h> +#include "svn_auth.h" +#include "svn_config.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_cmdline.h" +#include "svn_checksum.h" +#include "svn_string.h" + +#include "private/svn_auth_private.h" + +#include "svn_private_config.h" + +#ifdef SVN_HAVE_GPG_AGENT + +#define BUFFER_SIZE 1024 + +/* Modify STR in-place such that blanks are escaped as required by the + * gpg-agent protocol. Return a pointer to STR. */ +static char * +escape_blanks(char *str) +{ + char *s = str; + + while (*s) + { + if (*s == ' ') + *s = '+'; + s++; + } + + return str; +} + +/* Attempt to read a gpg-agent response message from the socket SD into + * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response + * message could be read that fits into the buffer. Else return FALSE. + * If a message could be read it will always be NUL-terminated and the + * trailing newline is retained. */ +static svn_boolean_t +receive_from_gpg_agent(int sd, char *buf, size_t n) +{ + int i = 0; + size_t recvd; + char c; + + /* Clear existing buffer content before reading response. */ + if (n > 0) + *buf = '\0'; + + /* Require the message to fit into the buffer and be terminated + * with a newline. */ + while (i < n) + { + recvd = read(sd, &c, 1); + if (recvd == -1) + return FALSE; + buf[i] = c; + i++; + if (i < n && c == '\n') + { + buf[i] = '\0'; + return TRUE; + } + } + + return FALSE; +} + +/* Using socket SD, send the option OPTION with the specified VALUE + * to the gpg agent. Store the response in BUF, assumed to be N bytes + * in size, and evaluate the response. Return TRUE if the agent liked + * the smell of the option, if there is such a thing, and doesn't feel + * saturated by it. Else return FALSE. + * Do temporary allocations in scratch_pool. */ +static svn_boolean_t +send_option(int sd, char *buf, size_t n, const char *option, const char *value, + apr_pool_t *scratch_pool) +{ + const char *request; + + request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value); + + if (write(sd, request, strlen(request)) == -1) + return FALSE; + + if (!receive_from_gpg_agent(sd, buf, n)) + return FALSE; + + return (strncmp(buf, "OK", 2) == 0); +} + +/* Implementation of svn_auth__password_get_t that retrieves the password + from gpg-agent */ +static svn_error_t * +password_get_gpg_agent(svn_boolean_t *done, + const char **password, + apr_hash_t *creds, + const char *realmstring, + const char *username, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + int sd; + char *gpg_agent_info = NULL; + const char *p = NULL; + char *ep = NULL; + char *buffer; + + apr_array_header_t *socket_details; + const char *request = NULL; + const char *cache_id = NULL; + struct sockaddr_un addr; + const char *tty_name; + const char *tty_type; + const char *lc_ctype; + const char *display; + const char *socket_name = NULL; + svn_checksum_t *digest = NULL; + char *password_prompt; + char *realm_prompt; + + *done = FALSE; + + gpg_agent_info = getenv("GPG_AGENT_INFO"); + if (gpg_agent_info != NULL) + { + socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE, + pool); + socket_name = APR_ARRAY_IDX(socket_details, 0, const char *); + } + else + return SVN_NO_ERROR; + + if (socket_name != NULL) + { + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + + sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sd == -1) + return SVN_NO_ERROR; + + if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + close(sd); + return SVN_NO_ERROR; + } + } + else + return SVN_NO_ERROR; + + /* Receive the connection status from the gpg-agent daemon. */ + buffer = apr_palloc(pool, BUFFER_SIZE); + if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) + { + close(sd); + return SVN_NO_ERROR; + } + + if (strncmp(buffer, "OK", 2) != 0) + { + close(sd); + return SVN_NO_ERROR; + } + + /* The GPG-Agent documentation says: + * "Clients should deny to access an agent with a socket name which does + * not match its own configuration". */ + request = "GETINFO socket_name\n"; + if (write(sd, request, strlen(request)) == -1) + { + close(sd); + return SVN_NO_ERROR; + } + if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) + { + close(sd); + return SVN_NO_ERROR; + } + if (strncmp(buffer, "D", 1) == 0) + p = &buffer[2]; + if (!p) + { + close(sd); + return SVN_NO_ERROR; + } + ep = strchr(p, '\n'); + if (ep != NULL) + *ep = '\0'; + if (strcmp(socket_name, p) != 0) + { + close(sd); + return SVN_NO_ERROR; + } + /* The agent will terminate its response with "OK". */ + if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) + { + close(sd); + return SVN_NO_ERROR; + } + if (strncmp(buffer, "OK", 2) != 0) + { + close(sd); + return SVN_NO_ERROR; + } + + /* Send TTY_NAME to the gpg-agent daemon. */ + tty_name = getenv("GPG_TTY"); + if (tty_name != NULL) + { + if (!send_option(sd, buffer, BUFFER_SIZE, "ttyname", tty_name, pool)) + { + close(sd); + return SVN_NO_ERROR; + } + } + else + { + close(sd); + return SVN_NO_ERROR; + } + + /* Send TTY_TYPE to the gpg-agent daemon. */ + tty_type = getenv("TERM"); + if (tty_type != NULL) + { + if (!send_option(sd, buffer, BUFFER_SIZE, "ttytype", tty_type, pool)) + { + close(sd); + return SVN_NO_ERROR; + } + } + else + { + close(sd); + return SVN_NO_ERROR; + } + + /* Compute LC_CTYPE. */ + lc_ctype = getenv("LC_ALL"); + if (lc_ctype == NULL) + lc_ctype = getenv("LC_CTYPE"); + if (lc_ctype == NULL) + lc_ctype = getenv("LANG"); + + /* Send LC_CTYPE to the gpg-agent daemon. */ + if (lc_ctype != NULL) + { + if (!send_option(sd, buffer, BUFFER_SIZE, "lc-ctype", lc_ctype, pool)) + { + close(sd); + return SVN_NO_ERROR; + } + } + + /* Send DISPLAY to the gpg-agent daemon. */ + display = getenv("DISPLAY"); + if (display != NULL) + { + if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool)) + { + close(sd); + return SVN_NO_ERROR; + } + } + + /* Create the CACHE_ID which will be generated based on REALMSTRING similar + to other password caching mechanisms. */ + SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring, + strlen(realmstring), pool)); + cache_id = svn_checksum_to_cstring(digest, pool); + + password_prompt = apr_psprintf(pool, _("Password for '%s': "), username); + realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"), + realmstring); + request = apr_psprintf(pool, + "GET_PASSPHRASE --data %s--repeat=1 " + "%s X %s %s\n", + non_interactive ? "--no-ask " : "", + cache_id, + escape_blanks(password_prompt), + escape_blanks(realm_prompt)); + + if (write(sd, request, strlen(request)) == -1) + { + close(sd); + return SVN_NO_ERROR; + } + if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) + { + close(sd); + return SVN_NO_ERROR; + } + + close(sd); + + if (strncmp(buffer, "ERR", 3) == 0) + return SVN_NO_ERROR; + + p = NULL; + if (strncmp(buffer, "D", 1) == 0) + p = &buffer[2]; + + if (!p) + return SVN_NO_ERROR; + + ep = strchr(p, '\n'); + if (ep != NULL) + *ep = '\0'; + + *password = p; + + *done = TRUE; + return SVN_NO_ERROR; +} + + +/* Implementation of svn_auth__password_set_t that would store the + password in GPG Agent if that's how this particular integration + worked. But it isn't. GPG Agent stores the password provided by + the user via the pinentry program immediately upon its provision + (and regardless of its accuracy as passwords go), so there's + nothing really to do here. */ +static svn_error_t * +password_set_gpg_agent(svn_boolean_t *done, + apr_hash_t *creds, + const char *realmstring, + const char *username, + const char *password, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + *done = TRUE; + + return SVN_NO_ERROR; +} + + +/* An implementation of svn_auth_provider_t::first_credentials() */ +static svn_error_t * +simple_gpg_agent_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_get(credentials, iter_baton, + provider_baton, parameters, + realmstring, password_get_gpg_agent, + SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, + pool); +} + + +/* An implementation of svn_auth_provider_t::save_credentials() */ +static svn_error_t * +simple_gpg_agent_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_set(saved, credentials, + provider_baton, parameters, + realmstring, password_set_gpg_agent, + SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, + pool); +} + + +static const svn_auth_provider_t gpg_agent_simple_provider = { + SVN_AUTH_CRED_SIMPLE, + simple_gpg_agent_first_creds, + NULL, + simple_gpg_agent_save_creds +}; + + +/* Public API */ +void +svn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &gpg_agent_simple_provider; + *provider = po; +} + +#endif /* SVN_HAVE_GPG_AGENT */ +#endif /* !WIN32 */ diff --git a/subversion/libsvn_subr/hash.c b/subversion/libsvn_subr/hash.c new file mode 100644 index 000000000000..7868cac8fb59 --- /dev/null +++ b/subversion/libsvn_subr/hash.c @@ -0,0 +1,642 @@ +/* + * hash.c : dumping and reading hash tables to/from files. + * + * ==================================================================== + * 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 <stdlib.h> +#include <limits.h> + +#include <apr_version.h> +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_file_io.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_hash.h" +#include "svn_sorts.h" +#include "svn_io.h" +#include "svn_pools.h" + +#include "private/svn_dep_compat.h" +#include "private/svn_subr_private.h" + +#include "svn_private_config.h" + + + + +/* + * The format of a dumped hash table is: + * + * K <nlength> + * name (a string of <nlength> bytes, followed by a newline) + * V <vlength> + * val (a string of <vlength> bytes, followed by a newline) + * [... etc, etc ...] + * END + * + * + * (Yes, there is a newline after END.) + * + * For example: + * + * K 5 + * color + * V 3 + * red + * K 11 + * wine review + * V 376 + * A forthright entrance, yet coquettish on the tongue, its deceptively + * fruity exterior hides the warm mahagony undercurrent that is the + * hallmark of Chateau Fraisant-Pitre. Connoisseurs of the region will + * be pleased to note the familiar, subtle hints of mulberries and + * carburator fluid. Its confident finish is marred only by a barely + * detectable suggestion of rancid squid ink. + * K 5 + * price + * V 8 + * US $6.50 + * END + * + */ + + + + +/*** Dumping and loading hash files. */ + +/* Implements svn_hash_read2 and svn_hash_read_incremental. */ +static svn_error_t * +hash_read(apr_hash_t *hash, svn_stream_t *stream, const char *terminator, + svn_boolean_t incremental, apr_pool_t *pool) +{ + svn_stringbuf_t *buf; + svn_boolean_t eof; + apr_size_t len, keylen, vallen; + char c, *keybuf, *valbuf; + apr_pool_t *iterpool = svn_pool_create(pool); + + while (1) + { + svn_error_t *err; + apr_uint64_t ui64; + + svn_pool_clear(iterpool); + + /* Read a key length line. Might be END, though. */ + SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool)); + + /* Check for the end of the hash. */ + if ((!terminator && eof && buf->len == 0) + || (terminator && (strcmp(buf->data, terminator) == 0))) + break; + + /* Check for unexpected end of stream */ + if (eof) + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Serialized hash missing terminator")); + + if ((buf->len >= 3) && (buf->data[0] == 'K') && (buf->data[1] == ' ')) + { + /* Get the length of the key */ + err = svn_cstring_strtoui64(&ui64, buf->data + 2, + 0, APR_SIZE_MAX, 10); + if (err) + return svn_error_create(SVN_ERR_MALFORMED_FILE, err, + _("Serialized hash malformed")); + keylen = (apr_size_t)ui64; + + /* Now read that much into a buffer. */ + keybuf = apr_palloc(pool, keylen + 1); + SVN_ERR(svn_stream_read(stream, keybuf, &keylen)); + keybuf[keylen] = '\0'; + + /* Suck up extra newline after key data */ + len = 1; + SVN_ERR(svn_stream_read(stream, &c, &len)); + if (c != '\n') + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Serialized hash malformed")); + + /* Read a val length line */ + SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool)); + + if ((buf->data[0] == 'V') && (buf->data[1] == ' ')) + { + err = svn_cstring_strtoui64(&ui64, buf->data + 2, + 0, APR_SIZE_MAX, 10); + if (err) + return svn_error_create(SVN_ERR_MALFORMED_FILE, err, + _("Serialized hash malformed")); + vallen = (apr_size_t)ui64; + + valbuf = apr_palloc(iterpool, vallen + 1); + SVN_ERR(svn_stream_read(stream, valbuf, &vallen)); + valbuf[vallen] = '\0'; + + /* Suck up extra newline after val data */ + len = 1; + SVN_ERR(svn_stream_read(stream, &c, &len)); + if (c != '\n') + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Serialized hash malformed")); + + /* Add a new hash entry. */ + apr_hash_set(hash, keybuf, keylen, + svn_string_ncreate(valbuf, vallen, pool)); + } + else + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Serialized hash malformed")); + } + else if (incremental && (buf->len >= 3) + && (buf->data[0] == 'D') && (buf->data[1] == ' ')) + { + /* Get the length of the key */ + err = svn_cstring_strtoui64(&ui64, buf->data + 2, + 0, APR_SIZE_MAX, 10); + if (err) + return svn_error_create(SVN_ERR_MALFORMED_FILE, err, + _("Serialized hash malformed")); + keylen = (apr_size_t)ui64; + + /* Now read that much into a buffer. */ + keybuf = apr_palloc(iterpool, keylen + 1); + SVN_ERR(svn_stream_read(stream, keybuf, &keylen)); + keybuf[keylen] = '\0'; + + /* Suck up extra newline after key data */ + len = 1; + SVN_ERR(svn_stream_read(stream, &c, &len)); + if (c != '\n') + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Serialized hash malformed")); + + /* Remove this hash entry. */ + apr_hash_set(hash, keybuf, keylen, NULL); + } + else + { + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Serialized hash malformed")); + } + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +/* Implements svn_hash_write2 and svn_hash_write_incremental. */ +static svn_error_t * +hash_write(apr_hash_t *hash, apr_hash_t *oldhash, svn_stream_t *stream, + const char *terminator, apr_pool_t *pool) +{ + apr_pool_t *subpool; + apr_size_t len; + apr_array_header_t *list; + int i; + + subpool = svn_pool_create(pool); + + list = svn_sort__hash(hash, svn_sort_compare_items_lexically, pool); + for (i = 0; i < list->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t); + svn_string_t *valstr = item->value; + + svn_pool_clear(subpool); + + /* Don't output entries equal to the ones in oldhash, if present. */ + if (oldhash) + { + svn_string_t *oldstr = apr_hash_get(oldhash, item->key, item->klen); + + if (oldstr && svn_string_compare(valstr, oldstr)) + continue; + } + + if (item->klen < 0) + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Cannot serialize negative length")); + + /* Write it out. */ + SVN_ERR(svn_stream_printf(stream, subpool, + "K %" APR_SIZE_T_FMT "\n%s\n" + "V %" APR_SIZE_T_FMT "\n", + (apr_size_t) item->klen, + (const char *) item->key, + valstr->len)); + len = valstr->len; + SVN_ERR(svn_stream_write(stream, valstr->data, &len)); + SVN_ERR(svn_stream_puts(stream, "\n")); + } + + if (oldhash) + { + /* Output a deletion entry for each property in oldhash but not hash. */ + list = svn_sort__hash(oldhash, svn_sort_compare_items_lexically, + pool); + for (i = 0; i < list->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t); + + svn_pool_clear(subpool); + + /* If it's not present in the new hash, write out a D entry. */ + if (! apr_hash_get(hash, item->key, item->klen)) + SVN_ERR(svn_stream_printf(stream, subpool, + "D %" APR_SSIZE_T_FMT "\n%s\n", + item->klen, (const char *) item->key)); + } + } + + if (terminator) + SVN_ERR(svn_stream_printf(stream, subpool, "%s\n", terminator)); + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +svn_error_t *svn_hash_read2(apr_hash_t *hash, svn_stream_t *stream, + const char *terminator, apr_pool_t *pool) +{ + return hash_read(hash, stream, terminator, FALSE, pool); +} + + +svn_error_t *svn_hash_read_incremental(apr_hash_t *hash, + svn_stream_t *stream, + const char *terminator, + apr_pool_t *pool) +{ + return hash_read(hash, stream, terminator, TRUE, pool); +} + + +svn_error_t * +svn_hash_write2(apr_hash_t *hash, svn_stream_t *stream, + const char *terminator, apr_pool_t *pool) +{ + return hash_write(hash, NULL, stream, terminator, pool); +} + + +svn_error_t * +svn_hash_write_incremental(apr_hash_t *hash, apr_hash_t *oldhash, + svn_stream_t *stream, const char *terminator, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(oldhash != NULL); + return hash_write(hash, oldhash, stream, terminator, pool); +} + + +svn_error_t * +svn_hash_write(apr_hash_t *hash, apr_file_t *destfile, apr_pool_t *pool) +{ + return hash_write(hash, NULL, svn_stream_from_aprfile2(destfile, TRUE, pool), + SVN_HASH_TERMINATOR, pool); +} + + +/* There are enough quirks in the deprecated svn_hash_read that we + should just preserve its implementation. */ +svn_error_t * +svn_hash_read(apr_hash_t *hash, + apr_file_t *srcfile, + apr_pool_t *pool) +{ + svn_error_t *err; + char buf[SVN_KEYLINE_MAXLEN]; + apr_size_t num_read; + char c; + int first_time = 1; + + + while (1) + { + /* Read a key length line. Might be END, though. */ + apr_size_t len = sizeof(buf); + + err = svn_io_read_length_line(srcfile, buf, &len, pool); + if (err && APR_STATUS_IS_EOF(err->apr_err) && first_time) + { + /* We got an EOF on our very first attempt to read, which + means it's a zero-byte file. No problem, just go home. */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + else if (err) + /* Any other circumstance is a genuine error. */ + return err; + + first_time = 0; + + if (((len == 3) && (buf[0] == 'E') && (buf[1] == 'N') && (buf[2] == 'D')) + || ((len == 9) + && (buf[0] == 'P') + && (buf[1] == 'R') /* We formerly used just "END" to */ + && (buf[2] == 'O') /* end a property hash, but later */ + && (buf[3] == 'P') /* we added "PROPS-END", so that */ + && (buf[4] == 'S') /* the fs dump format would be */ + && (buf[5] == '-') /* more human-readable. That's */ + && (buf[6] == 'E') /* why we accept either way here. */ + && (buf[7] == 'N') + && (buf[8] == 'D'))) + { + /* We've reached the end of the dumped hash table, so leave. */ + return SVN_NO_ERROR; + } + else if ((buf[0] == 'K') && (buf[1] == ' ')) + { + size_t keylen; + int parsed_len; + void *keybuf; + + /* Get the length of the key */ + SVN_ERR(svn_cstring_atoi(&parsed_len, buf + 2)); + keylen = parsed_len; + + /* Now read that much into a buffer, + 1 byte for null terminator */ + keybuf = apr_palloc(pool, keylen + 1); + SVN_ERR(svn_io_file_read_full2(srcfile, + keybuf, keylen, + &num_read, NULL, pool)); + ((char *) keybuf)[keylen] = '\0'; + + /* Suck up extra newline after key data */ + SVN_ERR(svn_io_file_getc(&c, srcfile, pool)); + if (c != '\n') + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL); + + /* Read a val length line */ + len = sizeof(buf); + SVN_ERR(svn_io_read_length_line(srcfile, buf, &len, pool)); + + if ((buf[0] == 'V') && (buf[1] == ' ')) + { + svn_string_t *value = apr_palloc(pool, sizeof(*value)); + apr_size_t vallen; + void *valbuf; + + /* Get the length of the value */ + SVN_ERR(svn_cstring_atoi(&parsed_len, buf + 2)); + vallen = parsed_len; + + /* Again, 1 extra byte for the null termination. */ + valbuf = apr_palloc(pool, vallen + 1); + SVN_ERR(svn_io_file_read_full2(srcfile, + valbuf, vallen, + &num_read, NULL, pool)); + ((char *) valbuf)[vallen] = '\0'; + + /* Suck up extra newline after val data */ + SVN_ERR(svn_io_file_getc(&c, srcfile, pool)); + if (c != '\n') + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL); + + value->data = valbuf; + value->len = vallen; + + /* The Grand Moment: add a new hash entry! */ + apr_hash_set(hash, keybuf, keylen, value); + } + else + { + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL); + } + } + else + { + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL); + } + } /* while (1) */ +} + + + +/*** Diffing hashes ***/ + +svn_error_t * +svn_hash_diff(apr_hash_t *hash_a, + apr_hash_t *hash_b, + svn_hash_diff_func_t diff_func, + void *diff_func_baton, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + if (hash_a) + for (hi = apr_hash_first(pool, hash_a); hi; hi = apr_hash_next(hi)) + { + const void *key; + apr_ssize_t klen; + + apr_hash_this(hi, &key, &klen, NULL); + + if (hash_b && (apr_hash_get(hash_b, key, klen))) + SVN_ERR((*diff_func)(key, klen, svn_hash_diff_key_both, + diff_func_baton)); + else + SVN_ERR((*diff_func)(key, klen, svn_hash_diff_key_a, + diff_func_baton)); + } + + if (hash_b) + for (hi = apr_hash_first(pool, hash_b); hi; hi = apr_hash_next(hi)) + { + const void *key; + apr_ssize_t klen; + + apr_hash_this(hi, &key, &klen, NULL); + + if (! (hash_a && apr_hash_get(hash_a, key, klen))) + SVN_ERR((*diff_func)(key, klen, svn_hash_diff_key_b, + diff_func_baton)); + } + + return SVN_NO_ERROR; +} + + +/*** Misc. hash APIs ***/ + +svn_error_t * +svn_hash_keys(apr_array_header_t **array, + apr_hash_t *hash, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + *array = apr_array_make(pool, apr_hash_count(hash), sizeof(const char *)); + + for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi)) + { + APR_ARRAY_PUSH(*array, const char *) = svn__apr_hash_index_key(hi); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_hash_from_cstring_keys(apr_hash_t **hash_p, + const apr_array_header_t *keys, + apr_pool_t *pool) +{ + int i; + apr_hash_t *hash = svn_hash__make(pool); + for (i = 0; i < keys->nelts; i++) + { + const char *key = + apr_pstrdup(pool, APR_ARRAY_IDX(keys, i, const char *)); + svn_hash_sets(hash, key, key); + } + *hash_p = hash; + return SVN_NO_ERROR; +} + + +#if !APR_VERSION_AT_LEAST(1, 3, 0) +void +svn_hash__clear(apr_hash_t *hash) +{ + apr_hash_index_t *hi; + const void *key; + apr_ssize_t klen; + + for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi)) + { + apr_hash_this(hi, &key, &klen, NULL); + apr_hash_set(hash, key, klen, NULL); + } +} +#endif + + + +/*** Specialized getter APIs ***/ + +const char * +svn_hash__get_cstring(apr_hash_t *hash, + const char *key, + const char *default_value) +{ + if (hash) + { + const char *value = svn_hash_gets(hash, key); + return value ? value : default_value; + } + + return default_value; +} + + +svn_boolean_t +svn_hash__get_bool(apr_hash_t *hash, const char *key, + svn_boolean_t default_value) +{ + const char *tmp_value = svn_hash__get_cstring(hash, key, NULL); + svn_tristate_t value = svn_tristate__from_word(tmp_value); + + if (value == svn_tristate_true) + return TRUE; + else if (value == svn_tristate_false) + return FALSE; + + return default_value; +} + + + +/*** Optimized hash function ***/ + +/* Optimized version of apr_hashfunc_default in APR 1.4.5 and earlier. + * It assumes that the CPU has 32-bit multiplications with high throughput + * of at least 1 operation every 3 cycles. Latency is not an issue. Another + * optimization is a mildly unrolled main loop and breaking the dependency + * chain within the loop. + * + * Note that most CPUs including Intel Atom, VIA Nano, ARM feature the + * assumed pipelined multiplication circuitry. They can do one MUL every + * or every other cycle. + * + * The performance is ultimately limited by the fact that most CPUs can + * do only one LOAD and only one BRANCH operation per cycle. The best we + * can do is to process one character per cycle - provided the processor + * is wide enough to do 1 LOAD, COMPARE, BRANCH, MUL and ADD per cycle. + */ +static unsigned int +hashfunc_compatible(const char *char_key, apr_ssize_t *klen) +{ + unsigned int hash = 0; + const unsigned char *key = (const unsigned char *)char_key; + const unsigned char *p; + apr_ssize_t i; + + if (*klen == APR_HASH_KEY_STRING) + { + for (p = key; ; p+=4) + { + unsigned int new_hash = hash * 33 * 33 * 33 * 33; + if (!p[0]) break; + new_hash += p[0] * 33 * 33 * 33; + if (!p[1]) break; + new_hash += p[1] * 33 * 33; + if (!p[2]) break; + new_hash += p[2] * 33; + if (!p[3]) break; + hash = new_hash + p[3]; + } + for (; *p; p++) + hash = hash * 33 + *p; + + *klen = p - key; + } + else + { + for (p = key, i = *klen; i >= 4; i-=4, p+=4) + { + hash = hash * 33 * 33 * 33 * 33 + + p[0] * 33 * 33 * 33 + + p[1] * 33 * 33 + + p[2] * 33 + + p[3]; + } + for (; i; i--, p++) + hash = hash * 33 + *p; + } + + return hash; +} + +apr_hash_t * +svn_hash__make(apr_pool_t *pool) +{ + return apr_hash_make_custom(pool, hashfunc_compatible); +} diff --git a/subversion/libsvn_subr/internal_statements.h b/subversion/libsvn_subr/internal_statements.h new file mode 100644 index 000000000000..fc429b3f37c2 --- /dev/null +++ b/subversion/libsvn_subr/internal_statements.h @@ -0,0 +1,76 @@ +/* This file is automatically generated from internal_statements.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_subr/token-map.h. + * Do not edit this file -- edit the source and rerun gen-make.py */ + +#define STMT_INTERNAL_SAVEPOINT_SVN 0 +#define STMT_0_INFO {"STMT_INTERNAL_SAVEPOINT_SVN", NULL} +#define STMT_0 \ + "SAVEPOINT svn " \ + "" + +#define STMT_INTERNAL_RELEASE_SAVEPOINT_SVN 1 +#define STMT_1_INFO {"STMT_INTERNAL_RELEASE_SAVEPOINT_SVN", NULL} +#define STMT_1 \ + "RELEASE SAVEPOINT svn " \ + "" + +#define STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN 2 +#define STMT_2_INFO {"STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN", NULL} +#define STMT_2 \ + "ROLLBACK TO SAVEPOINT svn " \ + "" + +#define STMT_INTERNAL_BEGIN_TRANSACTION 3 +#define STMT_3_INFO {"STMT_INTERNAL_BEGIN_TRANSACTION", NULL} +#define STMT_3 \ + "BEGIN TRANSACTION " \ + "" + +#define STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION 4 +#define STMT_4_INFO {"STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION", NULL} +#define STMT_4 \ + "BEGIN IMMEDIATE TRANSACTION " \ + "" + +#define STMT_INTERNAL_COMMIT_TRANSACTION 5 +#define STMT_5_INFO {"STMT_INTERNAL_COMMIT_TRANSACTION", NULL} +#define STMT_5 \ + "COMMIT TRANSACTION " \ + "" + +#define STMT_INTERNAL_ROLLBACK_TRANSACTION 6 +#define STMT_6_INFO {"STMT_INTERNAL_ROLLBACK_TRANSACTION", NULL} +#define STMT_6 \ + "ROLLBACK TRANSACTION " \ + "" + +#define STMT_INTERNAL_LAST 7 +#define STMT_7_INFO {"STMT_INTERNAL_LAST", NULL} +#define STMT_7 \ + "; " \ + "" + +#define INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENTS(varname) \ + static const char * const varname[] = { \ + STMT_0, \ + STMT_1, \ + STMT_2, \ + STMT_3, \ + STMT_4, \ + STMT_5, \ + STMT_6, \ + STMT_7, \ + NULL \ + } + +#define INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENT_INFO(varname) \ + static const char * const varname[][2] = { \ + STMT_0_INFO, \ + STMT_1_INFO, \ + STMT_2_INFO, \ + STMT_3_INFO, \ + STMT_4_INFO, \ + STMT_5_INFO, \ + STMT_6_INFO, \ + STMT_7_INFO, \ + {NULL, NULL} \ + } diff --git a/subversion/libsvn_subr/internal_statements.sql b/subversion/libsvn_subr/internal_statements.sql new file mode 100644 index 000000000000..c78e855ac1b7 --- /dev/null +++ b/subversion/libsvn_subr/internal_statements.sql @@ -0,0 +1,47 @@ +/* sqlite.sql -- queries used by the Subversion SQLite interface + * This is intended for use with SQLite 3 + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +-- STMT_INTERNAL_SAVEPOINT_SVN +SAVEPOINT svn + +-- STMT_INTERNAL_RELEASE_SAVEPOINT_SVN +RELEASE SAVEPOINT svn + +-- STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN +ROLLBACK TO SAVEPOINT svn + +-- STMT_INTERNAL_BEGIN_TRANSACTION +BEGIN TRANSACTION + +-- STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION +BEGIN IMMEDIATE TRANSACTION + +-- STMT_INTERNAL_COMMIT_TRANSACTION +COMMIT TRANSACTION + +-- STMT_INTERNAL_ROLLBACK_TRANSACTION +ROLLBACK TRANSACTION + +/* Dummmy statement to determine the number of internal statements */ +-- STMT_INTERNAL_LAST +; diff --git a/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c new file mode 100644 index 000000000000..58bc5403653b --- /dev/null +++ b/subversion/libsvn_subr/io.c @@ -0,0 +1,4768 @@ +/* + * io.c: shared file reading, writing, and probing code. + * + * ==================================================================== + * 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 <stdio.h> + +#ifndef WIN32 +#include <unistd.h> +#endif + +#ifndef APR_STATUS_IS_EPERM +#include <errno.h> +#ifdef EPERM +#define APR_STATUS_IS_EPERM(s) ((s) == EPERM) +#else +#define APR_STATUS_IS_EPERM(s) (0) +#endif +#endif + +#include <apr_lib.h> +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_file_info.h> +#include <apr_general.h> +#include <apr_strings.h> +#include <apr_portable.h> +#include <apr_md5.h> + +#ifdef WIN32 +#include <arch/win32/apr_arch_file_io.h> +#endif + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_pools.h" +#include "svn_utf.h" +#include "svn_config.h" +#include "svn_private_config.h" +#include "svn_ctype.h" + +#include "private/svn_atomic.h" +#include "private/svn_io_private.h" + +#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS" + +/* + Windows is 'aided' by a number of types of applications that + follow other applications around and open up files they have + changed for various reasons (the most intrusive are virus + scanners). So, if one of these other apps has glommed onto + our file we may get an 'access denied' error. + + This retry loop does not completely solve the problem (who + knows how long the other app is going to hold onto it for), but + goes a long way towards minimizing it. It is not an infinite + loop because there might really be an error. + + Another reason for retrying delete operations on Windows + is that they are asynchronous -- the file or directory is not + actually deleted until the last handle to it is closed. The + retry loop cannot completely solve this problem either, but can + help mitigate it. +*/ +#define RETRY_MAX_ATTEMPTS 100 +#define RETRY_INITIAL_SLEEP 1000 +#define RETRY_MAX_SLEEP 128000 + +#define RETRY_LOOP(err, expr, retry_test, sleep_test) \ + do \ + { \ + apr_status_t os_err = APR_TO_OS_ERROR(err); \ + int sleep_count = RETRY_INITIAL_SLEEP; \ + int retries; \ + for (retries = 0; \ + retries < RETRY_MAX_ATTEMPTS && (retry_test); \ + os_err = APR_TO_OS_ERROR(err)) \ + { \ + if (sleep_test) \ + { \ + ++retries; \ + apr_sleep(sleep_count); \ + if (sleep_count < RETRY_MAX_SLEEP) \ + sleep_count *= 2; \ + } \ + (err) = (expr); \ + } \ + } \ + while (0) + +#if defined(EDEADLK) && APR_HAS_THREADS +#define FILE_LOCK_RETRY_LOOP(err, expr) \ + RETRY_LOOP(err, \ + expr, \ + (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK), \ + (!APR_STATUS_IS_EINTR(err))) +#else +#define FILE_LOCK_RETRY_LOOP(err, expr) \ + RETRY_LOOP(err, \ + expr, \ + (APR_STATUS_IS_EINTR(err)), \ + 0) +#endif + +#ifndef WIN32_RETRY_LOOP +#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP) +#define WIN32_RETRY_LOOP(err, expr) \ + RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED \ + || os_err == ERROR_SHARING_VIOLATION \ + || os_err == ERROR_DIR_NOT_EMPTY), \ + 1) +#else +#define WIN32_RETRY_LOOP(err, expr) ((void)0) +#endif +#endif + +/* Forward declaration */ +static apr_status_t +dir_is_empty(const char *dir, apr_pool_t *pool); +static APR_INLINE svn_error_t * +do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status, + const char *msg, const char *msg_no_name, + apr_pool_t *pool); + +/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on + * operating systems where APR always uses utf-8 as native path format */ +static svn_error_t * +cstring_to_utf8(const char **path_utf8, + const char *path_apr, + apr_pool_t *pool) +{ +#if defined(WIN32) || defined(DARWIN) + *path_utf8 = path_apr; + return SVN_NO_ERROR; +#else + return svn_path_cstring_to_utf8(path_utf8, path_apr, pool); +#endif +} + +/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on + * operating systems where APR always uses utf-8 as native path format */ +static svn_error_t * +cstring_from_utf8(const char **path_apr, + const char *path_utf8, + apr_pool_t *pool) +{ +#if defined(WIN32) || defined(DARWIN) + *path_apr = path_utf8; + return SVN_NO_ERROR; +#else + return svn_path_cstring_from_utf8(path_apr, path_utf8, pool); +#endif +} + +/* Helper function that allows to convert an APR-level PATH to something + * that we can pass the svn_error_wrap_apr. Since we use it in context + * of error reporting, having *some* path info may be more useful than + * having none. Therefore, we use a best effort approach here. + * + * This is different from svn_io_file_name_get in that it uses a different + * signature style and will never fail. + */ +static const char * +try_utf8_from_internal_style(const char *path, apr_pool_t *pool) +{ + svn_error_t *error; + const char *path_utf8; + + /* Special case. */ + if (path == NULL) + return "(NULL)"; + + /* (try to) convert PATH to UTF-8. If that fails, continue with the plain + * PATH because it is the best we have. It may actually be UTF-8 already. + */ + error = cstring_to_utf8(&path_utf8, path, pool); + if (error) + { + /* fallback to best representation we have */ + + svn_error_clear(error); + path_utf8 = path; + } + + /* Toggle (back-)slashes etc. as necessary. + */ + return svn_dirent_local_style(path_utf8, pool); +} + + +/* Set *NAME_P to the UTF-8 representation of directory entry NAME. + * NAME is in the internal encoding used by APR; PARENT is in + * UTF-8 and in internal (not local) style. + * + * Use PARENT only for generating an error string if the conversion + * fails because NAME could not be represented in UTF-8. In that + * case, return a two-level error in which the outer error's message + * mentions PARENT, but the inner error's message does not mention + * NAME (except possibly in hex) since NAME may not be printable. + * Such a compound error at least allows the user to go looking in the + * right directory for the problem. + * + * If there is any other error, just return that error directly. + * + * If there is any error, the effect on *NAME_P is undefined. + * + * *NAME_P and NAME may refer to the same storage. + */ +static svn_error_t * +entry_name_to_utf8(const char **name_p, + const char *name, + const char *parent, + apr_pool_t *pool) +{ +#if defined(WIN32) || defined(DARWIN) + *name_p = apr_pstrdup(pool, name); + return SVN_NO_ERROR; +#else + svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool); + if (err && err->apr_err == APR_EINVAL) + { + return svn_error_createf(err->apr_err, err, + _("Error converting entry " + "in directory '%s' to UTF-8"), + svn_dirent_local_style(parent, pool)); + } + return err; +#endif +} + + + +static void +map_apr_finfo_to_node_kind(svn_node_kind_t *kind, + svn_boolean_t *is_special, + apr_finfo_t *finfo) +{ + *is_special = FALSE; + + if (finfo->filetype == APR_REG) + *kind = svn_node_file; + else if (finfo->filetype == APR_DIR) + *kind = svn_node_dir; + else if (finfo->filetype == APR_LNK) + { + *is_special = TRUE; + *kind = svn_node_file; + } + else + *kind = svn_node_unknown; +} + +/* Helper for svn_io_check_path() and svn_io_check_resolved_path(); + essentially the same semantics as those two, with the obvious + interpretation for RESOLVE_SYMLINKS. */ +static svn_error_t * +io_check_path(const char *path, + svn_boolean_t resolve_symlinks, + svn_boolean_t *is_special_p, + svn_node_kind_t *kind, + apr_pool_t *pool) +{ + apr_int32_t flags; + apr_finfo_t finfo; + apr_status_t apr_err; + const char *path_apr; + svn_boolean_t is_special = FALSE; + + if (path[0] == '\0') + path = "."; + + /* Not using svn_io_stat() here because we want to check the + apr_err return explicitly. */ + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + + flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK); + apr_err = apr_stat(&finfo, path_apr, flags, pool); + + if (APR_STATUS_IS_ENOENT(apr_err)) + *kind = svn_node_none; + else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err)) + *kind = svn_node_none; + else if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"), + svn_dirent_local_style(path, pool)); + else + map_apr_finfo_to_node_kind(kind, &is_special, &finfo); + + *is_special_p = is_special; + + return SVN_NO_ERROR; +} + + +/* Wrapper for apr_file_open(), taking an APR-encoded filename. */ +static apr_status_t +file_open(apr_file_t **f, + const char *fname_apr, + apr_int32_t flag, + apr_fileperms_t perm, + svn_boolean_t retry_on_failure, + apr_pool_t *pool) +{ + apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool); + + if (retry_on_failure) + { + WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool)); + } + return status; +} + + +svn_error_t * +svn_io_check_resolved_path(const char *path, + svn_node_kind_t *kind, + apr_pool_t *pool) +{ + svn_boolean_t ignored; + return io_check_path(path, TRUE, &ignored, kind, pool); +} + +svn_error_t * +svn_io_check_path(const char *path, + svn_node_kind_t *kind, + apr_pool_t *pool) +{ + svn_boolean_t ignored; + return io_check_path(path, FALSE, &ignored, kind, pool); +} + +svn_error_t * +svn_io_check_special_path(const char *path, + svn_node_kind_t *kind, + svn_boolean_t *is_special, + apr_pool_t *pool) +{ + return io_check_path(path, FALSE, is_special, kind, pool); +} + +struct temp_file_cleanup_s +{ + apr_pool_t *pool; + /* The (APR-encoded) full path of the file to be removed, or NULL if + * nothing to do. */ + const char *fname_apr; +}; + + +static apr_status_t +temp_file_plain_cleanup_handler(void *baton) +{ + struct temp_file_cleanup_s *b = baton; + apr_status_t apr_err = APR_SUCCESS; + + if (b->fname_apr) + { + apr_err = apr_file_remove(b->fname_apr, b->pool); + WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool)); + } + + return apr_err; +} + + +static apr_status_t +temp_file_child_cleanup_handler(void *baton) +{ + struct temp_file_cleanup_s *b = baton; + + apr_pool_cleanup_kill(b->pool, b, + temp_file_plain_cleanup_handler); + + return APR_SUCCESS; +} + + +svn_error_t * +svn_io_open_uniquely_named(apr_file_t **file, + const char **unique_path, + const char *dirpath, + const char *filename, + const char *suffix, + svn_io_file_del_t delete_when, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *path; + unsigned int i; + struct temp_file_cleanup_s *baton = NULL; + + /* At the beginning, we don't know whether unique_path will need + UTF8 conversion */ + svn_boolean_t needs_utf8_conversion = TRUE; + + SVN_ERR_ASSERT(file || unique_path); + + if (dirpath == NULL) + SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool)); + if (filename == NULL) + filename = "tempfile"; + if (suffix == NULL) + suffix = ".tmp"; + + path = svn_dirent_join(dirpath, filename, scratch_pool); + + if (delete_when == svn_io_file_del_on_pool_cleanup) + { + baton = apr_palloc(result_pool, sizeof(*baton)); + + baton->pool = result_pool; + baton->fname_apr = NULL; + + /* Because cleanups are run LIFO, we need to make sure to register + our cleanup before the apr_file_close cleanup: + + On Windows, you can't remove an open file. + */ + apr_pool_cleanup_register(result_pool, baton, + temp_file_plain_cleanup_handler, + temp_file_child_cleanup_handler); + } + + for (i = 1; i <= 99999; i++) + { + const char *unique_name; + const char *unique_name_apr; + apr_file_t *try_file; + apr_status_t apr_err; + apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL + | APR_BUFFERED | APR_BINARY); + + if (delete_when == svn_io_file_del_on_close) + flag |= APR_DELONCLOSE; + + /* Special case the first attempt -- if we can avoid having a + generated numeric portion at all, that's best. So first we + try with just the suffix; then future tries add a number + before the suffix. (A do-while loop could avoid the repeated + conditional, but it's not worth the clarity loss.) + + If the first attempt fails, the first number will be "2". + This is good, since "1" would misleadingly imply that + the second attempt was actually the first... and if someone's + got conflicts on their conflicts, we probably don't want to + add to their confusion :-). */ + if (i == 1) + unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix); + else + unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix); + + /* Hmmm. Ideally, we would append to a native-encoding buf + before starting iteration, then convert back to UTF-8 for + return. But I suppose that would make the appending code + sensitive to i18n in a way it shouldn't be... Oh well. */ + if (needs_utf8_conversion) + { + SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, + scratch_pool)); + if (i == 1) + { + /* The variable parts of unique_name will not require UTF8 + conversion. Therefore, if UTF8 conversion had no effect + on it in the first iteration, it won't require conversion + in any future iteration. */ + needs_utf8_conversion = strcmp(unique_name_apr, unique_name); + } + } + else + unique_name_apr = unique_name; + + apr_err = file_open(&try_file, unique_name_apr, flag, + APR_OS_DEFAULT, FALSE, result_pool); + + if (APR_STATUS_IS_EEXIST(apr_err)) + continue; + else if (apr_err) + { + /* On Win32, CreateFile fails with an "Access Denied" error + code, rather than "File Already Exists", if the colliding + name belongs to a directory. */ + if (APR_STATUS_IS_EACCES(apr_err)) + { + apr_finfo_t finfo; + apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, + APR_FINFO_TYPE, scratch_pool); + + if (!apr_err_2 && finfo.filetype == APR_DIR) + continue; + +#ifdef WIN32 + apr_err_2 = APR_TO_OS_ERROR(apr_err); + + if (apr_err_2 == ERROR_ACCESS_DENIED || + apr_err_2 == ERROR_SHARING_VIOLATION) + { + /* The file is in use by another process or is hidden; + create a new name, but don't do this 99999 times in + case the folder is not writable */ + i += 797; + continue; + } +#endif + + /* Else fall through and return the original error. */ + } + + if (file) + *file = NULL; + if (unique_path) + *unique_path = NULL; + return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), + svn_dirent_local_style(unique_name, + scratch_pool)); + } + else + { + if (delete_when == svn_io_file_del_on_pool_cleanup) + baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr); + + if (file) + *file = try_file; + else + apr_file_close(try_file); + if (unique_path) + *unique_path = apr_pstrdup(result_pool, unique_name); + + return SVN_NO_ERROR; + } + } + + if (file) + *file = NULL; + if (unique_path) + *unique_path = NULL; + return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, + NULL, + _("Unable to make name for '%s'"), + svn_dirent_local_style(path, scratch_pool)); +} + +svn_error_t * +svn_io_create_unique_link(const char **unique_name_p, + const char *path, + const char *dest, + const char *suffix, + apr_pool_t *pool) +{ +#ifdef HAVE_SYMLINK + unsigned int i; + const char *unique_name; + const char *unique_name_apr; + const char *dest_apr; + int rv; + + SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool)); + for (i = 1; i <= 99999; i++) + { + apr_status_t apr_err; + + /* Special case the first attempt -- if we can avoid having a + generated numeric portion at all, that's best. So first we + try with just the suffix; then future tries add a number + before the suffix. (A do-while loop could avoid the repeated + conditional, but it's not worth the clarity loss.) + + If the first attempt fails, the first number will be "2". + This is good, since "1" would misleadingly imply that + the second attempt was actually the first... and if someone's + got conflicts on their conflicts, we probably don't want to + add to their confusion :-). */ + if (i == 1) + unique_name = apr_psprintf(pool, "%s%s", path, suffix); + else + unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix); + + /* Hmmm. Ideally, we would append to a native-encoding buf + before starting iteration, then convert back to UTF-8 for + return. But I suppose that would make the appending code + sensitive to i18n in a way it shouldn't be... Oh well. */ + SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool)); + do { + rv = symlink(dest_apr, unique_name_apr); + } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); + + apr_err = apr_get_os_error(); + + if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err)) + continue; + else if (rv == -1 && apr_err) + { + /* On Win32, CreateFile fails with an "Access Denied" error + code, rather than "File Already Exists", if the colliding + name belongs to a directory. */ + if (APR_STATUS_IS_EACCES(apr_err)) + { + apr_finfo_t finfo; + apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, + APR_FINFO_TYPE, pool); + + if (!apr_err_2 + && (finfo.filetype == APR_DIR)) + continue; + + /* Else ignore apr_err_2; better to fall through and + return the original error. */ + } + + *unique_name_p = NULL; + return svn_error_wrap_apr(apr_err, + _("Can't create symbolic link '%s'"), + svn_dirent_local_style(unique_name, pool)); + } + else + { + *unique_name_p = unique_name; + return SVN_NO_ERROR; + } + } + + *unique_name_p = NULL; + return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, + NULL, + _("Unable to make name for '%s'"), + svn_dirent_local_style(path, pool)); +#else + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Symbolic links are not supported on this " + "platform")); +#endif +} + +svn_error_t * +svn_io_read_link(svn_string_t **dest, + const char *path, + apr_pool_t *pool) +{ +#ifdef HAVE_READLINK + svn_string_t dest_apr; + const char *path_apr; + char buf[1025]; + ssize_t rv; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + do { + rv = readlink(path_apr, buf, sizeof(buf) - 1); + } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); + + if (rv == -1) + return svn_error_wrap_apr(apr_get_os_error(), + _("Can't read contents of link")); + + buf[rv] = '\0'; + dest_apr.data = buf; + dest_apr.len = rv; + + /* ### Cast needed, one of these interfaces is wrong */ + return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool); +#else + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Symbolic links are not supported on this " + "platform")); +#endif +} + + +svn_error_t * +svn_io_copy_link(const char *src, + const char *dst, + apr_pool_t *pool) + +{ +#ifdef HAVE_READLINK + svn_string_t *link_dest; + const char *dst_tmp; + + /* Notice what the link is pointing at... */ + SVN_ERR(svn_io_read_link(&link_dest, src, pool)); + + /* Make a tmp-link pointing at the same thing. */ + SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data, + ".tmp", pool)); + + /* Move the tmp-link to link. */ + return svn_io_file_rename(dst_tmp, dst, pool); + +#else + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Symbolic links are not supported on this " + "platform")); +#endif +} + +/* Temporary directory name cache for svn_io_temp_dir() */ +static volatile svn_atomic_t temp_dir_init_state = 0; +static const char *temp_dir; + +/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */ +static svn_error_t * +init_temp_dir(void *baton, apr_pool_t *scratch_pool) +{ + /* Global pool for the temp path */ + apr_pool_t *global_pool = svn_pool_create(NULL); + const char *dir; + + apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool); + + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory")); + + SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool)); + + dir = svn_dirent_internal_style(dir, scratch_pool); + + SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_temp_dir(const char **dir, + apr_pool_t *pool) +{ + SVN_ERR(svn_atomic__init_once(&temp_dir_init_state, + init_temp_dir, NULL, pool)); + + *dir = apr_pstrdup(pool, temp_dir); + + return SVN_NO_ERROR; +} + + + + +/*** Creating, copying and appending files. ***/ + +/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary + * allocations. + * + * NOTE: We don't use apr_copy_file() for this, since it takes filenames + * as parameters. Since we want to copy to a temporary file + * and rename for atomicity (see below), this would require an extra + * close/open pair, which can be expensive, especially on + * remote file systems. + */ +static apr_status_t +copy_contents(apr_file_t *from_file, + apr_file_t *to_file, + apr_pool_t *pool) +{ + /* Copy bytes till the cows come home. */ + while (1) + { + char buf[SVN__STREAM_CHUNK_SIZE]; + apr_size_t bytes_this_time = sizeof(buf); + apr_status_t read_err; + apr_status_t write_err; + + /* Read 'em. */ + read_err = apr_file_read(from_file, buf, &bytes_this_time); + if (read_err && !APR_STATUS_IS_EOF(read_err)) + { + return read_err; + } + + /* Write 'em. */ + write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL); + if (write_err) + { + return write_err; + } + + if (read_err && APR_STATUS_IS_EOF(read_err)) + { + /* Return the results of this close: an error, or success. */ + return APR_SUCCESS; + } + } + /* NOTREACHED */ +} + + +svn_error_t * +svn_io_copy_file(const char *src, + const char *dst, + svn_boolean_t copy_perms, + apr_pool_t *pool) +{ + apr_file_t *from_file, *to_file; + apr_status_t apr_err; + const char *dst_tmp; + svn_error_t *err; + + /* ### NOTE: sometimes src == dst. In this case, because we copy to a + ### temporary file, and then rename over the top of the destination, + ### the net result is resetting the permissions on src/dst. + ### + ### Note: specifically, this can happen during a switch when the desired + ### permissions for a file change from one branch to another. See + ### switch_tests 17. + ### + ### ... yes, we should avoid copying to the same file, and we should + ### make the "reset perms" explicit. The switch *happens* to work + ### because of this copy-to-temp-then-rename implementation. If it + ### weren't for that, the switch would break. + */ +#ifdef CHECK_FOR_SAME_FILE + if (strcmp(src, dst) == 0) + return SVN_NO_ERROR; +#endif + + SVN_ERR(svn_io_file_open(&from_file, src, APR_READ, + APR_OS_DEFAULT, pool)); + + /* For atomicity, we copy to a tmp file and then rename the tmp + file over the real destination. */ + + SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp, + svn_dirent_dirname(dst, pool), + svn_io_file_del_none, pool, pool)); + + apr_err = copy_contents(from_file, to_file, pool); + + if (apr_err) + { + err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"), + svn_dirent_local_style(src, pool), + svn_dirent_local_style(dst_tmp, pool)); + } + else + err = NULL; + + err = svn_error_compose_create(err, + svn_io_file_close(from_file, pool)); + + err = svn_error_compose_create(err, + svn_io_file_close(to_file, pool)); + + if (err) + { + return svn_error_compose_create( + err, + svn_io_remove_file2(dst_tmp, TRUE, pool)); + } + + /* If copying perms, set the perms on dst_tmp now, so they will be + atomically inherited in the upcoming rename. But note that we + had to wait until now to set perms, because if they say + read-only, then we'd have failed filling dst_tmp's contents. */ + if (copy_perms) + SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool)); + + return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool)); +} + +#if !defined(WIN32) && !defined(__OS2__) +/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */ +static svn_error_t * +file_perms_set(const char *fname, apr_fileperms_t perms, + apr_pool_t *pool) +{ + const char *fname_apr; + apr_status_t status; + + SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); + + status = apr_file_perms_set(fname_apr, perms); + if (status) + return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"), + fname); + else + return SVN_NO_ERROR; +} + +/* Set permissions PERMS on the FILE. This is a cheaper variant of the + * file_perms_set wrapper() function because no locale-dependent string + * conversion is required. POOL will be used for allocations. + */ +static svn_error_t * +file_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool) +{ + const char *fname_apr; + apr_status_t status; + + status = apr_file_name_get(&fname_apr, file); + if (status) + return svn_error_wrap_apr(status, _("Can't get file name")); + + status = apr_file_perms_set(fname_apr, perms); + if (status) + return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"), + try_utf8_from_internal_style(fname_apr, pool)); + else + return SVN_NO_ERROR; +} + +#endif /* !WIN32 && !__OS2__ */ + +svn_error_t * +svn_io_copy_perms(const char *src, + const char *dst, + apr_pool_t *pool) +{ + /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL, + and the path passed to apr_file_perms_set must be encoded + in the platform-specific path encoding; not necessary UTF-8. + We need a platform-specific implementation to get the + permissions right. */ + +#if !defined(WIN32) && !defined(__OS2__) + { + apr_finfo_t finfo; + svn_node_kind_t kind; + svn_boolean_t is_special; + svn_error_t *err; + + /* If DST is a symlink, don't bother copying permissions. */ + SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool)); + if (is_special) + return SVN_NO_ERROR; + + SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool)); + err = file_perms_set(dst, finfo.protection, pool); + if (err) + { + /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL + here under normal circumstances, because the perms themselves + came from a call to apr_file_info_get(), and we already know + this is the non-Win32 case. But if it does happen, it's not + an error. */ + if (APR_STATUS_IS_INCOMPLETE(err->apr_err) || + APR_STATUS_IS_ENOTIMPL(err->apr_err)) + svn_error_clear(err); + else + { + const char *message; + message = apr_psprintf(pool, _("Can't set permissions on '%s'"), + svn_dirent_local_style(dst, pool)); + return svn_error_quick_wrap(err, message); + } + } + } +#endif /* !WIN32 && !__OS2__ */ + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_append_file(const char *src, const char *dst, apr_pool_t *pool) +{ + apr_status_t apr_err; + const char *src_apr, *dst_apr; + + SVN_ERR(cstring_from_utf8(&src_apr, src, pool)); + SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool)); + + apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool); + + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"), + svn_dirent_local_style(src, pool), + svn_dirent_local_style(dst, pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t *svn_io_copy_dir_recursively(const char *src, + const char *dst_parent, + const char *dst_basename, + svn_boolean_t copy_perms, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + apr_status_t status; + const char *dst_path; + apr_dir_t *this_dir; + apr_finfo_t this_entry; + apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; + + /* Make a subpool for recursion */ + apr_pool_t *subpool = svn_pool_create(pool); + + /* The 'dst_path' is simply dst_parent/dst_basename */ + dst_path = svn_dirent_join(dst_parent, dst_basename, pool); + + /* Sanity checks: SRC and DST_PARENT are directories, and + DST_BASENAME doesn't already exist in DST_PARENT. */ + SVN_ERR(svn_io_check_path(src, &kind, subpool)); + if (kind != svn_node_dir) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Source '%s' is not a directory"), + svn_dirent_local_style(src, pool)); + + SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool)); + if (kind != svn_node_dir) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Destination '%s' is not a directory"), + svn_dirent_local_style(dst_parent, pool)); + + SVN_ERR(svn_io_check_path(dst_path, &kind, subpool)); + if (kind != svn_node_none) + return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, + _("Destination '%s' already exists"), + svn_dirent_local_style(dst_path, pool)); + + /* Create the new directory. */ + /* ### TODO: copy permissions (needs apr_file_attrs_get()) */ + SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool)); + + /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */ + SVN_ERR(svn_io_dir_open(&this_dir, src, subpool)); + + for (status = apr_dir_read(&this_entry, flags, this_dir); + status == APR_SUCCESS; + status = apr_dir_read(&this_entry, flags, this_dir)) + { + if ((this_entry.name[0] == '.') + && ((this_entry.name[1] == '\0') + || ((this_entry.name[1] == '.') + && (this_entry.name[2] == '\0')))) + { + continue; + } + else + { + const char *src_target, *entryname_utf8; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name, + src, subpool)); + src_target = svn_dirent_join(src, entryname_utf8, subpool); + + if (this_entry.filetype == APR_REG) /* regular file */ + { + const char *dst_target = svn_dirent_join(dst_path, + entryname_utf8, + subpool); + SVN_ERR(svn_io_copy_file(src_target, dst_target, + copy_perms, subpool)); + } + else if (this_entry.filetype == APR_LNK) /* symlink */ + { + const char *dst_target = svn_dirent_join(dst_path, + entryname_utf8, + subpool); + SVN_ERR(svn_io_copy_link(src_target, dst_target, + subpool)); + } + else if (this_entry.filetype == APR_DIR) /* recurse */ + { + /* Prevent infinite recursion by filtering off our + newly created destination path. */ + if (strcmp(src, dst_parent) == 0 + && strcmp(entryname_utf8, dst_basename) == 0) + continue; + + SVN_ERR(svn_io_copy_dir_recursively + (src_target, + dst_path, + entryname_utf8, + copy_perms, + cancel_func, + cancel_baton, + subpool)); + } + /* ### support other APR node types someday?? */ + + } + } + + if (! (APR_STATUS_IS_ENOENT(status))) + return svn_error_wrap_apr(status, _("Can't read directory '%s'"), + svn_dirent_local_style(src, pool)); + + status = apr_dir_close(this_dir); + if (status) + return svn_error_wrap_apr(status, _("Error closing directory '%s'"), + svn_dirent_local_style(src, pool)); + + /* Free any memory used by recursion */ + svn_pool_destroy(subpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_make_dir_recursively(const char *path, apr_pool_t *pool) +{ + const char *path_apr; + apr_status_t apr_err; + + if (svn_path_is_empty(path)) + /* Empty path (current dir) is assumed to always exist, + so we do nothing, per docs. */ + return SVN_NO_ERROR; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + + apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool); + WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr, + APR_OS_DEFAULT, pool)); + + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"), + svn_dirent_local_style(path, pool)); + + return SVN_NO_ERROR; +} + +svn_error_t *svn_io_file_create(const char *file, + const char *contents, + apr_pool_t *pool) +{ + apr_file_t *f; + apr_size_t written; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR(svn_io_file_open(&f, file, + (APR_WRITE | APR_CREATE | APR_EXCL), + APR_OS_DEFAULT, + pool)); + if (contents && *contents) + err = svn_io_file_write_full(f, contents, strlen(contents), + &written, pool); + + + return svn_error_trace( + svn_error_compose_create(err, + svn_io_file_close(f, pool))); +} + +svn_error_t *svn_io_dir_file_copy(const char *src_path, + const char *dest_path, + const char *file, + apr_pool_t *pool) +{ + const char *file_dest_path = svn_dirent_join(dest_path, file, pool); + const char *file_src_path = svn_dirent_join(src_path, file, pool); + + return svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool); +} + + +/*** Modtime checking. ***/ + +svn_error_t * +svn_io_file_affected_time(apr_time_t *apr_time, + const char *path, + apr_pool_t *pool) +{ + apr_finfo_t finfo; + + SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool)); + + *apr_time = finfo.mtime; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_set_file_affected_time(apr_time_t apr_time, + const char *path, + apr_pool_t *pool) +{ + apr_status_t status; + const char *native_path; + + SVN_ERR(cstring_from_utf8(&native_path, path, pool)); + status = apr_file_mtime_set(native_path, apr_time, pool); + + if (status) + return svn_error_wrap_apr(status, _("Can't set access time of '%s'"), + svn_dirent_local_style(path, pool)); + + return SVN_NO_ERROR; +} + + +void +svn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool) +{ + apr_time_t now, then; + svn_error_t *err; + char *sleep_env_var; + + sleep_env_var = getenv(SVN_SLEEP_ENV_VAR); + + if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0) + return; /* Allow skipping for testing */ + + now = apr_time_now(); + + /* Calculate 0.02 seconds after the next second wallclock tick. */ + then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50); + + /* Worst case is waiting one second, so we can use that time to determine + if we can sleep shorter than that */ + if (path) + { + apr_finfo_t finfo; + + err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool); + + if (err) + { + svn_error_clear(err); /* Fall back on original behavior */ + } + else if (finfo.mtime % APR_USEC_PER_SEC) + { + /* Very simplistic but safe approach: + If the filesystem has < sec mtime we can be reasonably sure + that the filesystem has <= millisecond precision. + + ## Perhaps find a better algorithm here. This will fail once + in every 1000 cases on a millisecond precision filesystem. + + But better to fail once in every thousand cases than every + time, like we did before. + (All tested filesystems I know have at least microsecond precision.) + + Note for further research on algorithm: + FAT32 has < 1 sec precision on ctime, but 2 sec on mtime */ + + /* Sleep for at least 1 millisecond. + (t < 1000 will be round to 0 in apr) */ + apr_sleep(1000); + + return; + } + + now = apr_time_now(); /* Extract the time used for the path stat */ + + if (now >= then) + return; /* Passing negative values may suspend indefinitely (Windows) */ + } + + apr_sleep(then - now); +} + + +svn_error_t * +svn_io_filesizes_different_p(svn_boolean_t *different_p, + const char *file1, + const char *file2, + apr_pool_t *pool) +{ + apr_finfo_t finfo1; + apr_finfo_t finfo2; + apr_status_t status; + const char *file1_apr, *file2_apr; + + /* Not using svn_io_stat() because don't want to generate + svn_error_t objects for non-error conditions. */ + + SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool)); + SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool)); + + /* Stat both files */ + status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool); + if (status) + { + /* If we got an error stat'ing a file, it could be because the + file was removed... or who knows. Whatever the case, we + don't know if the filesizes are definitely different, so + assume that they're not. */ + *different_p = FALSE; + return SVN_NO_ERROR; + } + + status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool); + if (status) + { + /* See previous comment. */ + *different_p = FALSE; + return SVN_NO_ERROR; + } + + /* Examine file sizes */ + if (finfo1.size == finfo2.size) + *different_p = FALSE; + else + *different_p = TRUE; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_filesizes_three_different_p(svn_boolean_t *different_p12, + svn_boolean_t *different_p23, + svn_boolean_t *different_p13, + const char *file1, + const char *file2, + const char *file3, + apr_pool_t *scratch_pool) +{ + apr_finfo_t finfo1, finfo2, finfo3; + apr_status_t status1, status2, status3; + const char *file1_apr, *file2_apr, *file3_apr; + + /* Not using svn_io_stat() because don't want to generate + svn_error_t objects for non-error conditions. */ + + SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool)); + SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool)); + SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool)); + + /* Stat all three files */ + status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool); + status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool); + status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool); + + /* If we got an error stat'ing a file, it could be because the + file was removed... or who knows. Whatever the case, we + don't know if the filesizes are definitely different, so + assume that they're not. */ + *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size; + *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size; + *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_file_checksum2(svn_checksum_t **checksum, + const char *file, + svn_checksum_kind_t kind, + apr_pool_t *pool) +{ + svn_stream_t *file_stream; + svn_stream_t *checksum_stream; + apr_file_t* f; + + SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool)); + file_stream = svn_stream_from_aprfile2(f, FALSE, pool); + checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind, + TRUE, pool); + + /* Because the checksummed stream will force the reading (and + checksumming) of all the file's bytes, we can just close the stream + and let its magic work. */ + return svn_stream_close(checksum_stream); +} + + +svn_error_t * +svn_io_file_checksum(unsigned char digest[], + const char *file, + apr_pool_t *pool) +{ + svn_checksum_t *checksum; + + SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool)); + memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE); + + return SVN_NO_ERROR; +} + + + +/*** Permissions and modes. ***/ + +#if !defined(WIN32) && !defined(__OS2__) +/* Given the file specified by PATH, attempt to create an + identical version of it owned by the current user. This is done by + moving it to a temporary location, copying the file back to its old + path, then deleting the temporarily moved version. All temporary + allocations are done in POOL. */ +static svn_error_t * +reown_file(const char *path, + apr_pool_t *pool) +{ + const char *unique_name; + + SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name, + svn_dirent_dirname(path, pool), + svn_io_file_del_none, pool, pool)); + SVN_ERR(svn_io_file_rename(path, unique_name, pool)); + SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool)); + return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool)); +} + +/* Determine what the PERMS for a new file should be by looking at the + permissions of a temporary file that we create. + Unfortunately, umask() as defined in POSIX provides no thread-safe way + to get at the current value of the umask, so what we're doing here is + the only way we have to determine which combination of write bits + (User/Group/World) should be set by default. + Make temporary allocations in SCRATCH_POOL. */ +static svn_error_t * +get_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool) +{ + /* the default permissions as read from the temp folder */ + static apr_fileperms_t default_perms = 0; + + /* Technically, this "racy": Multiple threads may use enter here and + try to figure out the default permission concurrently. That's fine + since they will end up with the same results. Even more technical, + apr_fileperms_t is an atomic type on 32+ bit machines. + */ + if (default_perms == 0) + { + apr_finfo_t finfo; + apr_file_t *fd; + const char *fname_base, *fname; + apr_uint32_t randomish; + svn_error_t *err; + + /* Get the perms for a newly created file to find out what bits + should be set. + + Explictly delete the file because we want this file to be as + short-lived as possible since its presence means other + processes may have to try multiple names. + + Using svn_io_open_uniquely_named() here because other tempfile + creation functions tweak the permission bits of files they create. + */ + randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool + + (apr_uint32_t)apr_time_now()); + fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish); + + SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base, + NULL, svn_io_file_del_none, + scratch_pool, scratch_pool)); + err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool); + err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool)); + err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE, + scratch_pool)); + SVN_ERR(err); + *perms = finfo.protection; + default_perms = finfo.protection; + } + else + *perms = default_perms; + + return SVN_NO_ERROR; +} + +/* OR together permission bits of the file FD and the default permissions + of a file as determined by get_default_file_perms(). Do temporary + allocations in SCRATCH_POOL. */ +static svn_error_t * +merge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms, + apr_pool_t *scratch_pool) +{ + apr_finfo_t finfo; + apr_fileperms_t default_perms; + + SVN_ERR(get_default_file_perms(&default_perms, scratch_pool)); + SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool)); + + /* Glom the perms together. */ + *perms = default_perms | finfo.protection; + return SVN_NO_ERROR; +} + +/* This is a helper function for the svn_io_set_file_read* functions + that attempts to honor the users umask when dealing with + permission changes. It is a no-op when invoked on a symlink. */ +static svn_error_t * +io_set_file_perms(const char *path, + svn_boolean_t change_readwrite, + svn_boolean_t enable_write, + svn_boolean_t change_executable, + svn_boolean_t executable, + svn_boolean_t ignore_enoent, + apr_pool_t *pool) +{ + apr_status_t status; + const char *path_apr; + apr_finfo_t finfo; + apr_fileperms_t perms_to_set; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + + /* Try to change only a minimal amount of the perms first + by getting the current perms and adding bits + only on where read perms are granted. If this fails + fall through to just setting file attributes. */ + status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool); + if (status) + { + if (ignore_enoent && APR_STATUS_IS_ENOENT(status)) + return SVN_NO_ERROR; + else if (status != APR_ENOTIMPL) + return svn_error_wrap_apr(status, + _("Can't change perms of file '%s'"), + svn_dirent_local_style(path, pool)); + return SVN_NO_ERROR; + } + + if (finfo.filetype == APR_LNK) + return SVN_NO_ERROR; + + perms_to_set = finfo.protection; + if (change_readwrite) + { + if (enable_write) /* Make read-write. */ + { + apr_file_t *fd; + + /* Get the perms for the original file so we'll have any other bits + * that were already set (like the execute bits, for example). */ + SVN_ERR(svn_io_file_open(&fd, path, APR_READ, + APR_OS_DEFAULT, pool)); + SVN_ERR(merge_default_file_perms(fd, &perms_to_set, pool)); + SVN_ERR(svn_io_file_close(fd, pool)); + } + else + { + if (finfo.protection & APR_UREAD) + perms_to_set &= ~APR_UWRITE; + if (finfo.protection & APR_GREAD) + perms_to_set &= ~APR_GWRITE; + if (finfo.protection & APR_WREAD) + perms_to_set &= ~APR_WWRITE; + } + } + + if (change_executable) + { + if (executable) + { + if (finfo.protection & APR_UREAD) + perms_to_set |= APR_UEXECUTE; + if (finfo.protection & APR_GREAD) + perms_to_set |= APR_GEXECUTE; + if (finfo.protection & APR_WREAD) + perms_to_set |= APR_WEXECUTE; + } + else + { + if (finfo.protection & APR_UREAD) + perms_to_set &= ~APR_UEXECUTE; + if (finfo.protection & APR_GREAD) + perms_to_set &= ~APR_GEXECUTE; + if (finfo.protection & APR_WREAD) + perms_to_set &= ~APR_WEXECUTE; + } + } + + /* If we aren't changing anything then just return, this saves + some system calls and helps with shared working copies */ + if (perms_to_set == finfo.protection) + return SVN_NO_ERROR; + + status = apr_file_perms_set(path_apr, perms_to_set); + if (!status) + return SVN_NO_ERROR; + + if (APR_STATUS_IS_EPERM(status)) + { + /* We don't have permissions to change the + permissions! Try a move, copy, and delete + workaround to see if we can get the file owned by + us. If these succeed, try the permissions set + again. + + Note that we only attempt this in the + stat-available path. This assumes that the + move-copy workaround will only be helpful on + platforms that implement apr_stat. */ + SVN_ERR(reown_file(path, pool)); + status = apr_file_perms_set(path_apr, perms_to_set); + } + + if (!status) + return SVN_NO_ERROR; + + if (ignore_enoent && APR_STATUS_IS_ENOENT(status)) + return SVN_NO_ERROR; + else if (status == APR_ENOTIMPL) + { + /* At least try to set the attributes. */ + apr_fileattrs_t attrs = 0; + apr_fileattrs_t attrs_values = 0; + + if (change_readwrite) + { + attrs = APR_FILE_ATTR_READONLY; + if (!enable_write) + attrs_values = APR_FILE_ATTR_READONLY; + } + if (change_executable) + { + attrs = APR_FILE_ATTR_EXECUTABLE; + if (executable) + attrs_values = APR_FILE_ATTR_EXECUTABLE; + } + status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool); + } + + return svn_error_wrap_apr(status, + _("Can't change perms of file '%s'"), + svn_dirent_local_style(path, pool)); +} +#endif /* !WIN32 && !__OS2__ */ + +#ifdef WIN32 +#if APR_HAS_UNICODE_FS +/* copy of the apr function utf8_to_unicode_path since apr doesn't export this one */ +static apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retlen, + const char* srcstr) +{ + /* TODO: The computations could preconvert the string to determine + * the true size of the retstr, but that's a memory over speed + * tradeoff that isn't appropriate this early in development. + * + * Allocate the maximum string length based on leading 4 + * characters of \\?\ (allowing nearly unlimited path lengths) + * plus the trailing null, then transform /'s into \\'s since + * the \\?\ form doesn't allow '/' path separators. + * + * Note that the \\?\ form only works for local drive paths, and + * \\?\UNC\ is needed UNC paths. + */ + apr_size_t srcremains = strlen(srcstr) + 1; + apr_wchar_t *t = retstr; + apr_status_t rv; + + /* This is correct, we don't twist the filename if it will + * definitely be shorter than 248 characters. It merits some + * performance testing to see if this has any effect, but there + * seem to be applications that get confused by the resulting + * Unicode \\?\ style file names, especially if they use argv[0] + * or call the Win32 API functions such as GetModuleName, etc. + * Not every application is prepared to handle such names. + * + * Note also this is shorter than MAX_PATH, as directory paths + * are actually limited to 248 characters. + * + * Note that a utf-8 name can never result in more wide chars + * than the original number of utf-8 narrow chars. + */ + if (srcremains > 248) { + if (srcstr[1] == ':' && (srcstr[2] == '/' || srcstr[2] == '\\')) { + wcscpy (retstr, L"\\\\?\\"); + retlen -= 4; + t += 4; + } + else if ((srcstr[0] == '/' || srcstr[0] == '\\') + && (srcstr[1] == '/' || srcstr[1] == '\\') + && (srcstr[2] != '?')) { + /* Skip the slashes */ + srcstr += 2; + srcremains -= 2; + wcscpy (retstr, L"\\\\?\\UNC\\"); + retlen -= 8; + t += 8; + } + } + + if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) { + return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv; + } + if (srcremains) { + return APR_ENAMETOOLONG; + } + for (; *t; ++t) + if (*t == L'/') + *t = L'\\'; + return APR_SUCCESS; +} +#endif + +static apr_status_t io_win_file_attrs_set(const char *fname, + DWORD attributes, + DWORD attr_mask, + apr_pool_t *pool) +{ + /* this is an implementation of apr_file_attrs_set() but one + that uses the proper Windows attributes instead of the apr + attributes. This way, we can apply any Windows file and + folder attributes even if apr doesn't implement them */ + DWORD flags; + apr_status_t rv; +#if APR_HAS_UNICODE_FS + apr_wchar_t wfname[APR_PATH_MAX]; +#endif + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + if (rv = io_utf8_to_unicode_path(wfname, + sizeof(wfname) / sizeof(wfname[0]), + fname)) + return rv; + flags = GetFileAttributesW(wfname); + } +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + flags = GetFileAttributesA(fname); + } +#endif + + if (flags == 0xFFFFFFFF) + return apr_get_os_error(); + + flags &= ~attr_mask; + flags |= (attributes & attr_mask); + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + rv = SetFileAttributesW(wfname, flags); + } +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + rv = SetFileAttributesA(fname, flags); + } +#endif + + if (rv == 0) + return apr_get_os_error(); + + return APR_SUCCESS; +} + +#endif + +svn_error_t * +svn_io_set_file_read_write_carefully(const char *path, + svn_boolean_t enable_write, + svn_boolean_t ignore_enoent, + apr_pool_t *pool) +{ + if (enable_write) + return svn_io_set_file_read_write(path, ignore_enoent, pool); + return svn_io_set_file_read_only(path, ignore_enoent, pool); +} + +svn_error_t * +svn_io_set_file_read_only(const char *path, + svn_boolean_t ignore_enoent, + apr_pool_t *pool) +{ + /* On Windows and OS/2, just set the file attributes -- on unix call + our internal function which attempts to honor the umask. */ +#if !defined(WIN32) && !defined(__OS2__) + return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE, + ignore_enoent, pool); +#else + apr_status_t status; + const char *path_apr; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + + status = apr_file_attrs_set(path_apr, + APR_FILE_ATTR_READONLY, + APR_FILE_ATTR_READONLY, + pool); + + if (status && status != APR_ENOTIMPL) + if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status)) + return svn_error_wrap_apr(status, + _("Can't set file '%s' read-only"), + svn_dirent_local_style(path, pool)); + + return SVN_NO_ERROR; +#endif +} + + +svn_error_t * +svn_io_set_file_read_write(const char *path, + svn_boolean_t ignore_enoent, + apr_pool_t *pool) +{ + /* On Windows and OS/2, just set the file attributes -- on unix call + our internal function which attempts to honor the umask. */ +#if !defined(WIN32) && !defined(__OS2__) + return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE, + ignore_enoent, pool); +#else + apr_status_t status; + const char *path_apr; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + + status = apr_file_attrs_set(path_apr, + 0, + APR_FILE_ATTR_READONLY, + pool); + + if (status && status != APR_ENOTIMPL) + if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status)) + return svn_error_wrap_apr(status, + _("Can't set file '%s' read-write"), + svn_dirent_local_style(path, pool)); + + return SVN_NO_ERROR; +#endif +} + +svn_error_t * +svn_io_set_file_executable(const char *path, + svn_boolean_t executable, + svn_boolean_t ignore_enoent, + apr_pool_t *pool) +{ + /* On Windows and OS/2, just exit -- on unix call our internal function + which attempts to honor the umask. */ +#if (!defined(WIN32) && !defined(__OS2__)) + return io_set_file_perms(path, FALSE, FALSE, TRUE, executable, + ignore_enoent, pool); +#else + return SVN_NO_ERROR; +#endif +} + + +svn_error_t * +svn_io__is_finfo_read_only(svn_boolean_t *read_only, + apr_finfo_t *file_info, + apr_pool_t *pool) +{ +#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) + apr_status_t apr_err; + apr_uid_t uid; + apr_gid_t gid; + + *read_only = FALSE; + + apr_err = apr_uid_current(&uid, &gid, pool); + + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); + + /* Check write bit for current user. */ + if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) + *read_only = !(file_info->protection & APR_UWRITE); + + else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) + *read_only = !(file_info->protection & APR_GWRITE); + + else + *read_only = !(file_info->protection & APR_WWRITE); + +#else /* WIN32 || __OS2__ || !APR_HAS_USER */ + *read_only = (file_info->protection & APR_FREADONLY); +#endif + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io__is_finfo_executable(svn_boolean_t *executable, + apr_finfo_t *file_info, + apr_pool_t *pool) +{ +#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) + apr_status_t apr_err; + apr_uid_t uid; + apr_gid_t gid; + + *executable = FALSE; + + apr_err = apr_uid_current(&uid, &gid, pool); + + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); + + /* Check executable bit for current user. */ + if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) + *executable = (file_info->protection & APR_UEXECUTE); + + else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) + *executable = (file_info->protection & APR_GEXECUTE); + + else + *executable = (file_info->protection & APR_WEXECUTE); + +#else /* WIN32 || __OS2__ || !APR_HAS_USER */ + *executable = FALSE; +#endif + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_is_file_executable(svn_boolean_t *executable, + const char *path, + apr_pool_t *pool) +{ +#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) + apr_finfo_t file_info; + + SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER, + pool)); + SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool)); + +#else /* WIN32 || __OS2__ || !APR_HAS_USER */ + *executable = FALSE; +#endif + + return SVN_NO_ERROR; +} + + +/*** File locking. ***/ +#if !defined(WIN32) && !defined(__OS2__) +/* Clear all outstanding locks on ARG, an open apr_file_t *. */ +static apr_status_t +file_clear_locks(void *arg) +{ + apr_status_t apr_err; + apr_file_t *f = arg; + + /* Remove locks. */ + apr_err = apr_file_unlock(f); + if (apr_err) + return apr_err; + + return 0; +} +#endif + +svn_error_t * +svn_io_lock_open_file(apr_file_t *lockfile_handle, + svn_boolean_t exclusive, + svn_boolean_t nonblocking, + apr_pool_t *pool) +{ + int locktype = APR_FLOCK_SHARED; + apr_status_t apr_err; + const char *fname; + + if (exclusive) + locktype = APR_FLOCK_EXCLUSIVE; + if (nonblocking) + locktype |= APR_FLOCK_NONBLOCK; + + /* We need this only in case of an error but this is cheap to get - + * so we do it here for clarity. */ + apr_err = apr_file_name_get(&fname, lockfile_handle); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get file name")); + + /* Get lock on the filehandle. */ + apr_err = apr_file_lock(lockfile_handle, locktype); + + /* In deployments with two or more multithreaded servers running on + the same system serving two or more fsfs repositories it is + possible for a deadlock to occur when getting a write lock on + db/txn-current-lock: + + Process 1 Process 2 + --------- --------- + thread 1: get lock in repos A + thread 1: get lock in repos B + thread 2: block getting lock in repos A + thread 2: try to get lock in B *** deadlock *** + + Retry for a while for the deadlock to clear. */ + FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype)); + + if (apr_err) + { + switch (locktype & APR_FLOCK_TYPEMASK) + { + case APR_FLOCK_SHARED: + return svn_error_wrap_apr(apr_err, + _("Can't get shared lock on file '%s'"), + try_utf8_from_internal_style(fname, pool)); + case APR_FLOCK_EXCLUSIVE: + return svn_error_wrap_apr(apr_err, + _("Can't get exclusive lock on file '%s'"), + try_utf8_from_internal_style(fname, pool)); + default: + SVN_ERR_MALFUNCTION(); + } + } + +/* On Windows and OS/2 file locks are automatically released when + the file handle closes */ +#if !defined(WIN32) && !defined(__OS2__) + apr_pool_cleanup_register(pool, lockfile_handle, + file_clear_locks, + apr_pool_cleanup_null); +#endif + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_unlock_open_file(apr_file_t *lockfile_handle, + apr_pool_t *pool) +{ + const char *fname; + apr_status_t apr_err; + + /* We need this only in case of an error but this is cheap to get - + * so we do it here for clarity. */ + apr_err = apr_file_name_get(&fname, lockfile_handle); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't get file name")); + + /* The actual unlock attempt. */ + apr_err = apr_file_unlock(lockfile_handle); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"), + try_utf8_from_internal_style(fname, pool)); + +/* On Windows and OS/2 file locks are automatically released when + the file handle closes */ +#if !defined(WIN32) && !defined(__OS2__) + apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks); +#endif + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_file_lock2(const char *lock_file, + svn_boolean_t exclusive, + svn_boolean_t nonblocking, + apr_pool_t *pool) +{ + int locktype = APR_FLOCK_SHARED; + apr_file_t *lockfile_handle; + apr_int32_t flags; + + if (exclusive) + locktype = APR_FLOCK_EXCLUSIVE; + + flags = APR_READ; + if (locktype == APR_FLOCK_EXCLUSIVE) + flags |= APR_WRITE; + + /* locktype is never read after this block, so we don't need to bother + setting it. If that were to ever change, uncomment the following + block. + if (nonblocking) + locktype |= APR_FLOCK_NONBLOCK; + */ + + SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags, + APR_OS_DEFAULT, + pool)); + + /* Get lock on the filehandle. */ + return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool); +} + + + +/* Data consistency/coherency operations. */ + +svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, + apr_pool_t *pool) +{ + apr_os_file_t filehand; + + /* First make sure that any user-space buffered data is flushed. */ + SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file), + N_("Can't flush file '%s'"), + N_("Can't flush stream"), + pool)); + + apr_os_file_get(&filehand, file); + + /* Call the operating system specific function to actually force the + data to disk. */ + { +#ifdef WIN32 + + if (! FlushFileBuffers(filehand)) + return svn_error_wrap_apr(apr_get_os_error(), + _("Can't flush file to disk")); + +#else + int rv; + + do { + rv = fsync(filehand); + } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); + + /* If the file is in a memory filesystem, fsync() may return + EINVAL. Presumably the user knows the risks, and we can just + ignore the error. */ + if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error())) + return SVN_NO_ERROR; + + if (rv == -1) + return svn_error_wrap_apr(apr_get_os_error(), + _("Can't flush file to disk")); + +#endif + } + return SVN_NO_ERROR; +} + + + +/* TODO write test for these two functions, then refactor. */ + +/* Set RESULT to an svn_stringbuf_t containing the contents of FILE. + FILENAME is the FILE's on-disk APR-safe name, or NULL if that name + isn't known. If CHECK_SIZE is TRUE, the function will attempt to + first stat() the file to determine it's size before sucking its + contents into the stringbuf. (Doing so can prevent unnecessary + memory usage, an unwanted side effect of the stringbuf growth and + reallocation mechanism.) */ +static svn_error_t * +stringbuf_from_aprfile(svn_stringbuf_t **result, + const char *filename, + apr_file_t *file, + svn_boolean_t check_size, + apr_pool_t *pool) +{ + apr_size_t len; + svn_error_t *err; + svn_stringbuf_t *res = NULL; + apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE; + char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); + + /* If our caller wants us to check the size of the file for + efficient memory handling, we'll try to do so. */ + if (check_size) + { + apr_status_t status; + + /* If our caller didn't tell us the file's name, we'll ask APR + if it knows the name. No problem if we can't figure it out. */ + if (! filename) + { + const char *filename_apr; + if (! (status = apr_file_name_get(&filename_apr, file))) + filename = filename_apr; + } + + /* If we now know the filename, try to stat(). If we succeed, + we know how to allocate our stringbuf. */ + if (filename) + { + apr_finfo_t finfo; + if (! (status = apr_stat(&finfo, filename, APR_FINFO_MIN, pool))) + res_initial_len = (apr_size_t)finfo.size; + } + } + + + /* XXX: We should check the incoming data for being of type binary. */ + + res = svn_stringbuf_create_ensure(res_initial_len, pool); + + /* apr_file_read will not return data and eof in the same call. So this loop + * is safe from missing read data. */ + len = SVN__STREAM_CHUNK_SIZE; + err = svn_io_file_read(file, buf, &len, pool); + while (! err) + { + svn_stringbuf_appendbytes(res, buf, len); + len = SVN__STREAM_CHUNK_SIZE; + err = svn_io_file_read(file, buf, &len, pool); + } + + /* Having read all the data we *expect* EOF */ + if (err && !APR_STATUS_IS_EOF(err->apr_err)) + return err; + svn_error_clear(err); + + *result = res; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_stringbuf_from_file2(svn_stringbuf_t **result, + const char *filename, + apr_pool_t *pool) +{ + apr_file_t *f; + + if (filename[0] == '-' && filename[1] == '\0') + { + apr_status_t apr_err; + if ((apr_err = apr_file_open_stdin(&f, pool))) + return svn_error_wrap_apr(apr_err, _("Can't open stdin")); + SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool)); + } + else + { + SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool)); + SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool)); + } + return svn_io_file_close(f, pool); +} + + +svn_error_t * +svn_stringbuf_from_file(svn_stringbuf_t **result, + const char *filename, + apr_pool_t *pool) +{ + if (filename[0] == '-' && filename[1] == '\0') + return svn_error_create + (SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Reading from stdin is disallowed")); + return svn_stringbuf_from_file2(result, filename, pool); +} + +svn_error_t * +svn_stringbuf_from_aprfile(svn_stringbuf_t **result, + apr_file_t *file, + apr_pool_t *pool) +{ + return stringbuf_from_aprfile(result, NULL, file, TRUE, pool); +} + + + +/* Deletion. */ + +svn_error_t * +svn_io_remove_file2(const char *path, + svn_boolean_t ignore_enoent, + apr_pool_t *scratch_pool) +{ + apr_status_t apr_err; + const char *path_apr; + + SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool)); + + apr_err = apr_file_remove(path_apr, scratch_pool); + if (!apr_err + || (ignore_enoent + && (APR_STATUS_IS_ENOENT(apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(apr_err)))) + return SVN_NO_ERROR; + +#ifdef WIN32 + /* If the target is read only NTFS reports EACCESS and FAT/FAT32 + reports EEXIST */ + if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err)) + { + /* Set the destination file writable because Windows will not + allow us to delete when path is read-only */ + SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool)); + apr_err = apr_file_remove(path_apr, scratch_pool); + + if (!apr_err) + return SVN_NO_ERROR; + } + + { + apr_status_t os_err = APR_TO_OS_ERROR(apr_err); + /* Check to make sure we aren't trying to delete a directory */ + if (os_err == ERROR_ACCESS_DENIED || os_err == ERROR_SHARING_VIOLATION) + { + apr_finfo_t finfo; + + if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool) + && finfo.filetype == APR_REG) + { + WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr, + scratch_pool)); + } + } + + /* Just return the delete error */ + } +#endif + + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"), + svn_dirent_local_style(path, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_remove_dir(const char *path, apr_pool_t *pool) +{ + return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool); +} + +/* + Mac OS X has a bug where if you're reading the contents of a + directory via readdir in a loop, and you remove one of the entries in + the directory and the directory has 338 or more files in it you will + skip over some of the entries in the directory. Needless to say, + this causes problems if you are using this kind of loop inside a + function that is recursively deleting a directory, because when you + get around to removing the directory it will still have something in + it. A similar problem has been observed in other BSDs. This bug has + since been fixed. See http://www.vnode.ch/fixing_seekdir for details. + + The workaround is to delete the files only _after_ the initial + directory scan. A previous workaround involving rewinddir is + problematic on Win32 and some NFS clients, notably NetBSD. + + See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and + http://subversion.tigris.org/issues/show_bug.cgi?id=3501. +*/ + +/* Neither windows nor unix allows us to delete a non-empty + directory. + + This is a function to perform the equivalent of 'rm -rf'. */ +svn_error_t * +svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent, + svn_cancel_func_t cancel_func, void *cancel_baton, + apr_pool_t *pool) +{ + svn_error_t *err; + apr_pool_t *subpool; + apr_hash_t *dirents; + apr_hash_index_t *hi; + + /* Check for pending cancellation request. + If we need to bail out, do so early. */ + + if (cancel_func) + SVN_ERR((*cancel_func)(cancel_baton)); + + subpool = svn_pool_create(pool); + + err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool); + if (err) + { + /* if the directory doesn't exist, our mission is accomplished */ + if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + return svn_error_trace(err); + } + + for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); + const char *fullpath; + + fullpath = svn_dirent_join(path, name, subpool); + if (dirent->kind == svn_node_dir) + { + /* Don't check for cancellation, the callee will immediately do so */ + SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func, + cancel_baton, subpool)); + } + else + { + if (cancel_func) + SVN_ERR((*cancel_func)(cancel_baton)); + + err = svn_io_remove_file2(fullpath, FALSE, subpool); + if (err) + return svn_error_createf + (err->apr_err, err, _("Can't remove '%s'"), + svn_dirent_local_style(fullpath, subpool)); + } + } + + svn_pool_destroy(subpool); + + return svn_io_dir_remove_nonrecursive(path, pool); +} + +svn_error_t * +svn_io_get_dir_filenames(apr_hash_t **dirents, + const char *path, + apr_pool_t *pool) +{ + return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE, + pool, pool)); +} + +svn_io_dirent2_t * +svn_io_dirent2_create(apr_pool_t *result_pool) +{ + svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent)); + + /*dirent->kind = svn_node_none; + dirent->special = FALSE;*/ + dirent->filesize = SVN_INVALID_FILESIZE; + /*dirent->mtime = 0;*/ + + return dirent; +} + +svn_io_dirent2_t * +svn_io_dirent2_dup(const svn_io_dirent2_t *item, + apr_pool_t *result_pool) +{ + return apr_pmemdup(result_pool, + item, + sizeof(*item)); +} + +svn_error_t * +svn_io_get_dirents3(apr_hash_t **dirents, + const char *path, + svn_boolean_t only_check_type, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_status_t status; + apr_dir_t *this_dir; + apr_finfo_t this_entry; + apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; + + if (!only_check_type) + flags |= APR_FINFO_SIZE | APR_FINFO_MTIME; + + *dirents = apr_hash_make(result_pool); + + SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool)); + + for (status = apr_dir_read(&this_entry, flags, this_dir); + status == APR_SUCCESS; + status = apr_dir_read(&this_entry, flags, this_dir)) + { + if ((this_entry.name[0] == '.') + && ((this_entry.name[1] == '\0') + || ((this_entry.name[1] == '.') + && (this_entry.name[2] == '\0')))) + { + continue; + } + else + { + const char *name; + svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool); + + SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool)); + + map_apr_finfo_to_node_kind(&(dirent->kind), + &(dirent->special), + &this_entry); + + if (!only_check_type) + { + dirent->filesize = this_entry.size; + dirent->mtime = this_entry.mtime; + } + + svn_hash_sets(*dirents, name, dirent); + } + } + + if (! (APR_STATUS_IS_ENOENT(status))) + return svn_error_wrap_apr(status, _("Can't read directory '%s'"), + svn_dirent_local_style(path, scratch_pool)); + + status = apr_dir_close(this_dir); + if (status) + return svn_error_wrap_apr(status, _("Error closing directory '%s'"), + svn_dirent_local_style(path, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p, + const char *path, + svn_boolean_t verify_truename, + svn_boolean_t ignore_enoent, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_finfo_t finfo; + svn_io_dirent2_t *dirent; + svn_error_t *err; + apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK + | APR_FINFO_SIZE | APR_FINFO_MTIME; + +#if defined(WIN32) || defined(__OS2__) + if (verify_truename) + wanted |= APR_FINFO_NAME; +#endif + + err = svn_io_stat(&finfo, path, wanted, scratch_pool); + + if (err && ignore_enoent && + (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + dirent = svn_io_dirent2_create(result_pool); + SVN_ERR_ASSERT(dirent->kind == svn_node_none); + + *dirent_p = dirent; + return SVN_NO_ERROR; + } + SVN_ERR(err); + +#if defined(WIN32) || defined(__OS2__) || defined(DARWIN) + if (verify_truename) + { + const char *requested_name = svn_dirent_basename(path, NULL); + + if (requested_name[0] == '\0') + { + /* No parent directory. No need to stat/verify */ + } +#if defined(WIN32) || defined(__OS2__) + else if (finfo.name) + { + const char *name_on_disk; + SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path, + scratch_pool)); + + if (strcmp(name_on_disk, requested_name) /* != 0 */) + { + if (ignore_enoent) + { + *dirent_p = svn_io_dirent2_create(result_pool); + return SVN_NO_ERROR; + } + else + return svn_error_createf(APR_ENOENT, NULL, + _("Path '%s' not found, case obstructed by '%s'"), + svn_dirent_local_style(path, scratch_pool), + name_on_disk); + } + } +#elif defined(DARWIN) + /* Currently apr doesn't set finfo.name on DARWIN, returning + APR_INCOMPLETE. + ### Can we optimize this in another way? */ + else + { + apr_hash_t *dirents; + + err = svn_io_get_dirents3(&dirents, + svn_dirent_dirname(path, scratch_pool), + TRUE /* only_check_type */, + scratch_pool, scratch_pool); + + if (err && ignore_enoent + && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + + *dirent_p = svn_io_dirent2_create(result_pool); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + if (! svn_hash_gets(dirents, requested_name)) + { + if (ignore_enoent) + { + *dirent_p = svn_io_dirent2_create(result_pool); + return SVN_NO_ERROR; + } + else + return svn_error_createf(APR_ENOENT, NULL, + _("Path '%s' not found"), + svn_dirent_local_style(path, scratch_pool)); + } + } +#endif + } +#endif + + dirent = svn_io_dirent2_create(result_pool); + map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo); + + dirent->filesize = finfo.size; + dirent->mtime = finfo.mtime; + + *dirent_p = dirent; + + return SVN_NO_ERROR; +} + +/* Pool userdata key for the error file passed to svn_io_start_cmd(). */ +#define ERRFILE_KEY "svn-io-start-cmd-errfile" + +/* Handle an error from the child process (before command execution) by + printing DESC and the error string corresponding to STATUS to stderr. */ +static void +handle_child_process_error(apr_pool_t *pool, apr_status_t status, + const char *desc) +{ + char errbuf[256]; + apr_file_t *errfile; + void *p; + + /* We can't do anything if we get an error here, so just return. */ + if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool)) + return; + errfile = p; + + if (errfile) + /* What we get from APR is in native encoding. */ + apr_file_printf(errfile, "%s: %s", + desc, apr_strerror(status, errbuf, + sizeof(errbuf))); +} + + +svn_error_t * +svn_io_start_cmd3(apr_proc_t *cmd_proc, + const char *path, + const char *cmd, + const char *const *args, + const char *const *env, + svn_boolean_t inherit, + svn_boolean_t infile_pipe, + apr_file_t *infile, + svn_boolean_t outfile_pipe, + apr_file_t *outfile, + svn_boolean_t errfile_pipe, + apr_file_t *errfile, + apr_pool_t *pool) +{ + apr_status_t apr_err; + apr_procattr_t *cmdproc_attr; + int num_args; + const char **args_native; + const char *cmd_apr; + + SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe)); + SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe)); + SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe)); + + /* Create the process attributes. */ + apr_err = apr_procattr_create(&cmdproc_attr, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't create process '%s' attributes"), + cmd); + + /* Make sure we invoke cmd directly, not through a shell. */ + apr_err = apr_procattr_cmdtype_set(cmdproc_attr, + inherit ? APR_PROGRAM_PATH : APR_PROGRAM); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"), + cmd); + + /* Set the process's working directory. */ + if (path) + { + const char *path_apr; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't set process '%s' directory"), + cmd); + } + + /* Use requested inputs and outputs. + + ### Unfortunately each of these apr functions creates a pipe and then + overwrites the pipe file descriptor with the descriptor we pass + in. The pipes can then never be closed. This is an APR bug. */ + if (infile) + { + apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't set process '%s' child input"), + cmd); + } + if (outfile) + { + apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't set process '%s' child outfile"), + cmd); + } + if (errfile) + { + apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't set process '%s' child errfile"), + cmd); + } + + /* Forward request for pipes to APR. */ + if (infile_pipe || outfile_pipe || errfile_pipe) + { + apr_err = apr_procattr_io_set(cmdproc_attr, + infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, + outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE, + errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE); + + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't set process '%s' stdio pipes"), + cmd); + } + + /* Have the child print any problems executing its program to errfile. */ + apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't set process '%s' child errfile for " + "error handler"), + cmd); + apr_err = apr_procattr_child_errfn_set(cmdproc_attr, + handle_child_process_error); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't set process '%s' error handler"), + cmd); + + /* Convert cmd and args from UTF-8 */ + SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool)); + for (num_args = 0; args[num_args]; num_args++) + ; + args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *)); + args_native[num_args] = NULL; + while (num_args--) + { + /* ### Well, it turns out that on APR on Windows expects all + program args to be in UTF-8. Callers of svn_io_run_cmd + should be aware of that. */ + SVN_ERR(cstring_from_utf8(&args_native[num_args], + args[num_args], pool)); + } + + + /* Start the cmd command. */ + apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native, + inherit ? NULL : env, cmdproc_attr, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd); + + return SVN_NO_ERROR; +} + +#undef ERRFILE_KEY + +svn_error_t * +svn_io_wait_for_cmd(apr_proc_t *cmd_proc, + const char *cmd, + int *exitcode, + apr_exit_why_e *exitwhy, + apr_pool_t *pool) +{ + apr_status_t apr_err; + apr_exit_why_e exitwhy_val; + int exitcode_val; + + /* The Win32 apr_proc_wait doesn't set this... */ + exitwhy_val = APR_PROC_EXIT; + + /* Wait for the cmd command to finish. */ + apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT); + if (!APR_STATUS_IS_CHILD_DONE(apr_err)) + return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"), + cmd); + + if (exitwhy) + *exitwhy = exitwhy_val; + else if (APR_PROC_CHECK_SIGNALED(exitwhy_val) + && APR_PROC_CHECK_CORE_DUMP(exitwhy_val)) + return svn_error_createf + (SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("Process '%s' failed (signal %d, core dumped)"), + cmd, exitcode_val); + else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)) + return svn_error_createf + (SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("Process '%s' failed (signal %d)"), + cmd, exitcode_val); + else if (! APR_PROC_CHECK_EXIT(exitwhy_val)) + /* Don't really know what happened here. */ + return svn_error_createf + (SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("Process '%s' failed (exitwhy %d, exitcode %d)"), + cmd, exitwhy_val, exitcode_val); + + if (exitcode) + *exitcode = exitcode_val; + else if (exitcode_val != 0) + return svn_error_createf + (SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("Process '%s' returned error exitcode %d"), cmd, exitcode_val); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_run_cmd(const char *path, + const char *cmd, + const char *const *args, + int *exitcode, + apr_exit_why_e *exitwhy, + svn_boolean_t inherit, + apr_file_t *infile, + apr_file_t *outfile, + apr_file_t *errfile, + apr_pool_t *pool) +{ + apr_proc_t cmd_proc; + + SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit, + FALSE, infile, FALSE, outfile, FALSE, errfile, + pool)); + + return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool); +} + + +svn_error_t * +svn_io_run_diff2(const char *dir, + const char *const *user_args, + int num_user_args, + const char *label1, + const char *label2, + const char *from, + const char *to, + int *pexitcode, + apr_file_t *outfile, + apr_file_t *errfile, + const char *diff_cmd, + apr_pool_t *pool) +{ + const char **args; + int i; + int exitcode; + int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */ + apr_pool_t *subpool = svn_pool_create(pool); + + if (pexitcode == NULL) + pexitcode = &exitcode; + + if (user_args != NULL) + nargs += num_user_args; + else + nargs += 1; /* -u */ + + if (label1 != NULL) + nargs += 2; /* the -L and the label itself */ + if (label2 != NULL) + nargs += 2; /* the -L and the label itself */ + + args = apr_palloc(subpool, nargs * sizeof(char *)); + + i = 0; + args[i++] = diff_cmd; + + if (user_args != NULL) + { + int j; + for (j = 0; j < num_user_args; ++j) + args[i++] = user_args[j]; + } + else + args[i++] = "-u"; /* assume -u if the user didn't give us any args */ + + if (label1 != NULL) + { + args[i++] = "-L"; + args[i++] = label1; + } + if (label2 != NULL) + { + args[i++] = "-L"; + args[i++] = label2; + } + + args[i++] = svn_dirent_local_style(from, subpool); + args[i++] = svn_dirent_local_style(to, subpool); + args[i++] = NULL; + + SVN_ERR_ASSERT(i == nargs); + + SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE, + NULL, outfile, errfile, subpool)); + + /* The man page for (GNU) diff describes the return value as: + + "An exit status of 0 means no differences were found, 1 means + some differences were found, and 2 means trouble." + + A return value of 2 typically occurs when diff cannot read its input + or write to its output, but in any case we probably ought to return an + error for anything other than 0 or 1 as the output is likely to be + corrupt. + */ + if (*pexitcode != 0 && *pexitcode != 1) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("'%s' returned %d"), + svn_dirent_local_style(diff_cmd, pool), + *pexitcode); + + svn_pool_destroy(subpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_run_diff3_3(int *exitcode, + const char *dir, + const char *mine, + const char *older, + const char *yours, + const char *mine_label, + const char *older_label, + const char *yours_label, + apr_file_t *merged, + const char *diff3_cmd, + const apr_array_header_t *user_args, + apr_pool_t *pool) +{ + const char **args = apr_palloc(pool, + sizeof(char*) * (13 + + (user_args + ? user_args->nelts + : 1))); +#ifndef NDEBUG + int nargs = 12; +#endif + int i = 0; + + /* Labels fall back to sensible defaults if not specified. */ + if (mine_label == NULL) + mine_label = ".working"; + if (older_label == NULL) + older_label = ".old"; + if (yours_label == NULL) + yours_label = ".new"; + + /* Set up diff3 command line. */ + args[i++] = diff3_cmd; + if (user_args) + { + int j; + for (j = 0; j < user_args->nelts; ++j) + args[i++] = APR_ARRAY_IDX(user_args, j, const char *); +#ifndef NDEBUG + nargs += user_args->nelts; +#endif + } + else + { + args[i++] = "-E"; /* We tried "-A" here, but that caused + overlapping identical changes to + conflict. See issue #682. */ +#ifndef NDEBUG + ++nargs; +#endif + } + args[i++] = "-m"; + args[i++] = "-L"; + args[i++] = mine_label; + args[i++] = "-L"; + args[i++] = older_label; /* note: this label is ignored if + using 2-part markers, which is the + case with "-E". */ + args[i++] = "-L"; + args[i++] = yours_label; +#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG + { + svn_boolean_t has_arg; + + /* ### FIXME: we really shouldn't be reading the config here; + instead, the necessary bits should be passed in by the caller. + But should we add another parameter to this function, when the + whole external diff3 thing might eventually go away? */ + apr_hash_t *config; + svn_config_t *cfg; + + SVN_ERR(svn_config_get_config(&config, pool)); + cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; + SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG, + TRUE)); + if (has_arg) + { + const char *diff_cmd, *diff_utf8; + svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF); + SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool)); + args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL); +#ifndef NDEBUG + ++nargs; +#endif + } + } +#endif + args[i++] = svn_dirent_local_style(mine, pool); + args[i++] = svn_dirent_local_style(older, pool); + args[i++] = svn_dirent_local_style(yours, pool); + args[i++] = NULL; +#ifndef NDEBUG + SVN_ERR_ASSERT(i == nargs); +#endif + + /* Run diff3, output the merged text into the scratch file. */ + SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args, + exitcode, NULL, + TRUE, /* keep environment */ + NULL, merged, NULL, + pool)); + + /* According to the diff3 docs, a '0' means the merge was clean, and + '1' means conflict markers were found. Anything else is real + error. */ + if ((*exitcode != 0) && (*exitcode != 1)) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("Error running '%s': exitcode was %d, " + "args were:" + "\nin directory '%s', basenames:\n%s\n%s\n%s"), + svn_dirent_local_style(diff3_cmd, pool), + *exitcode, + svn_dirent_local_style(dir, pool), + /* Don't call svn_path_local_style() on + the basenames. We don't want them to + be absolute, and we don't need the + separator conversion. */ + mine, older, yours); + + return SVN_NO_ERROR; +} + + +/* Canonicalize a string for hashing. Modifies KEY in place. */ +static APR_INLINE char * +fileext_tolower(char *key) +{ + register char *p; + for (p = key; *p != 0; ++p) + *p = (char)apr_tolower(*p); + return key; +} + + +svn_error_t * +svn_io_parse_mimetypes_file(apr_hash_t **type_map, + const char *mimetypes_file, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + apr_hash_t *types = apr_hash_make(pool); + svn_boolean_t eof = FALSE; + svn_stringbuf_t *buf; + apr_pool_t *subpool = svn_pool_create(pool); + apr_file_t *types_file; + svn_stream_t *mimetypes_stream; + + SVN_ERR(svn_io_file_open(&types_file, mimetypes_file, + APR_READ, APR_OS_DEFAULT, pool)); + mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool); + + while (1) + { + apr_array_header_t *tokens; + const char *type; + + svn_pool_clear(subpool); + + /* Read a line. */ + if ((err = svn_stream_readline(mimetypes_stream, &buf, + APR_EOL_STR, &eof, subpool))) + break; + + /* Only pay attention to non-empty, non-comment lines. */ + if (buf->len) + { + int i; + + if (buf->data[0] == '#') + continue; + + /* Tokenize (into our return pool). */ + tokens = svn_cstring_split(buf->data, " \t", TRUE, pool); + if (tokens->nelts < 2) + continue; + + /* The first token in a multi-token line is the media type. + Subsequent tokens are filename extensions associated with + that media type. */ + type = APR_ARRAY_IDX(tokens, 0, const char *); + for (i = 1; i < tokens->nelts; i++) + { + /* We can safely address 'ext' as a non-const string because + * we know svn_cstring_split() allocated it in 'pool' for us. */ + char *ext = APR_ARRAY_IDX(tokens, i, char *); + fileext_tolower(ext); + svn_hash_sets(types, ext, type); + } + } + if (eof) + break; + } + svn_pool_destroy(subpool); + + /* If there was an error above, close the file (ignoring any error + from *that*) and return the originally error. */ + if (err) + { + svn_error_clear(svn_stream_close(mimetypes_stream)); + return err; + } + + /* Close the stream (which closes the underlying file, too). */ + SVN_ERR(svn_stream_close(mimetypes_stream)); + + *type_map = types; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_detect_mimetype2(const char **mimetype, + const char *file, + apr_hash_t *mimetype_map, + apr_pool_t *pool) +{ + static const char * const generic_binary = "application/octet-stream"; + + svn_node_kind_t kind; + apr_file_t *fh; + svn_error_t *err; + unsigned char block[1024]; + apr_size_t amt_read = sizeof(block); + + /* Default return value is NULL. */ + *mimetype = NULL; + + /* If there is a mimetype_map provided, we'll first try to look up + our file's extension in the map. Failing that, we'll run the + heuristic. */ + if (mimetype_map) + { + const char *type_from_map; + char *path_ext; /* Can point to physical const memory but only when + svn_path_splitext sets it to "". */ + svn_path_splitext(NULL, (const char **)&path_ext, file, pool); + fileext_tolower(path_ext); + if ((type_from_map = svn_hash_gets(mimetype_map, path_ext))) + { + *mimetype = type_from_map; + return SVN_NO_ERROR; + } + } + + /* See if this file even exists, and make sure it really is a file. */ + SVN_ERR(svn_io_check_path(file, &kind, pool)); + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("Can't detect MIME type of non-file '%s'"), + svn_dirent_local_style(file, pool)); + + SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool)); + + /* Read a block of data from FILE. */ + err = svn_io_file_read(fh, block, &amt_read, pool); + if (err && ! APR_STATUS_IS_EOF(err->apr_err)) + return err; + svn_error_clear(err); + + /* Now close the file. No use keeping it open any more. */ + SVN_ERR(svn_io_file_close(fh, pool)); + + if (svn_io_is_binary_data(block, amt_read)) + *mimetype = generic_binary; + + return SVN_NO_ERROR; +} + + +svn_boolean_t +svn_io_is_binary_data(const void *data, apr_size_t len) +{ + const unsigned char *buf = data; + + if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) + { + /* This is an empty UTF-8 file which only contains the UTF-8 BOM. + * Treat it as plain text. */ + return FALSE; + } + + /* Right now, this function is going to be really stupid. It's + going to examine the block of data, and make sure that 15% + of the bytes are such that their value is in the ranges 0x07-0x0D + or 0x20-0x7F, and that none of those bytes is 0x00. If those + criteria are not met, we're calling it binary. + + NOTE: Originally, I intended to target 85% of the bytes being in + the specified ranges, but I flubbed the condition. At any rate, + folks aren't complaining, so I'm not sure that it's worth + adjusting this retroactively now. --cmpilato */ + if (len > 0) + { + apr_size_t i; + apr_size_t binary_count = 0; + + /* Run through the data we've read, counting the 'binary-ish' + bytes. HINT: If we see a 0x00 byte, we'll set our count to its + max and stop reading the file. */ + for (i = 0; i < len; i++) + { + if (buf[i] == 0) + { + binary_count = len; + break; + } + if ((buf[i] < 0x07) + || ((buf[i] > 0x0D) && (buf[i] < 0x20)) + || (buf[i] > 0x7F)) + { + binary_count++; + } + } + + return (((binary_count * 1000) / len) > 850); + } + + return FALSE; +} + + +svn_error_t * +svn_io_detect_mimetype(const char **mimetype, + const char *file, + apr_pool_t *pool) +{ + return svn_io_detect_mimetype2(mimetype, file, NULL, pool); +} + + +svn_error_t * +svn_io_file_open(apr_file_t **new_file, const char *fname, + apr_int32_t flag, apr_fileperms_t perm, + apr_pool_t *pool) +{ + const char *fname_apr; + apr_status_t status; + + SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); + status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE, + pool); + + if (status) + return svn_error_wrap_apr(status, _("Can't open file '%s'"), + svn_dirent_local_style(fname, pool)); + else + return SVN_NO_ERROR; +} + + +static APR_INLINE svn_error_t * +do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status, + const char *msg, const char *msg_no_name, + apr_pool_t *pool) +{ + const char *name; + svn_error_t *err; + + if (! status) + return SVN_NO_ERROR; + + err = svn_io_file_name_get(&name, file, pool); + if (err) + name = NULL; + svn_error_clear(err); + + /* ### Issue #3014: Return a specific error for broken pipes, + * ### with a single element in the error chain. */ + if (APR_STATUS_IS_EPIPE(status)) + return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL); + + if (name) + return svn_error_wrap_apr(status, _(msg), + try_utf8_from_internal_style(name, pool)); + else + return svn_error_wrap_apr(status, "%s", _(msg_no_name)); +} + + +svn_error_t * +svn_io_file_close(apr_file_t *file, apr_pool_t *pool) +{ + return do_io_file_wrapper_cleanup(file, apr_file_close(file), + N_("Can't close file '%s'"), + N_("Can't close stream"), + pool); +} + + +svn_error_t * +svn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool) +{ + return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file), + N_("Can't read file '%s'"), + N_("Can't read stream"), + pool); +} + + +svn_error_t * +svn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool) +{ + return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file), + N_("Can't write file '%s'"), + N_("Can't write stream"), + pool); +} + + +svn_error_t * +svn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted, + apr_file_t *file, apr_pool_t *pool) +{ + /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ + wanted &= ~SVN__APR_FINFO_MASK_OUT; + + return do_io_file_wrapper_cleanup( + file, apr_file_info_get(finfo, wanted, file), + N_("Can't get attribute information from file '%s'"), + N_("Can't get attribute information from stream"), + pool); +} + + +svn_error_t * +svn_io_file_read(apr_file_t *file, void *buf, + apr_size_t *nbytes, apr_pool_t *pool) +{ + return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes), + N_("Can't read file '%s'"), + N_("Can't read stream"), + pool); +} + + +svn_error_t * +svn_io_file_read_full2(apr_file_t *file, void *buf, + apr_size_t nbytes, apr_size_t *bytes_read, + svn_boolean_t *hit_eof, + apr_pool_t *pool) +{ + apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read); + if (hit_eof) + { + if (APR_STATUS_IS_EOF(status)) + { + *hit_eof = TRUE; + return SVN_NO_ERROR; + } + else + *hit_eof = FALSE; + } + + return do_io_file_wrapper_cleanup(file, status, + N_("Can't read file '%s'"), + N_("Can't read stream"), + pool); +} + + +svn_error_t * +svn_io_file_seek(apr_file_t *file, apr_seek_where_t where, + apr_off_t *offset, apr_pool_t *pool) +{ + return do_io_file_wrapper_cleanup( + file, apr_file_seek(file, where, offset), + N_("Can't set position pointer in file '%s'"), + N_("Can't set position pointer in stream"), + pool); +} + + +svn_error_t * +svn_io_file_write(apr_file_t *file, const void *buf, + apr_size_t *nbytes, apr_pool_t *pool) +{ + return svn_error_trace(do_io_file_wrapper_cleanup( + file, apr_file_write(file, buf, nbytes), + N_("Can't write to file '%s'"), + N_("Can't write to stream"), + pool)); +} + + +svn_error_t * +svn_io_file_write_full(apr_file_t *file, const void *buf, + apr_size_t nbytes, apr_size_t *bytes_written, + apr_pool_t *pool) +{ + /* We cannot simply call apr_file_write_full on Win32 as it may fail + for larger values of NBYTES. In that case, we have to emulate the + "_full" part here. Thus, always call apr_file_write directly on + Win32 as this minimizes overhead for small data buffers. */ +#ifdef WIN32 +#define MAXBUFSIZE 30*1024 + apr_size_t bw = nbytes; + apr_size_t to_write = nbytes; + + /* try a simple "write everything at once" first */ + apr_status_t rv = apr_file_write(file, buf, &bw); + buf = (char *)buf + bw; + to_write -= bw; + + /* if the OS cannot handle that, use smaller chunks */ + if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY) + && nbytes > MAXBUFSIZE) + { + do { + bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write; + rv = apr_file_write(file, buf, &bw); + buf = (char *)buf + bw; + to_write -= bw; + } while (rv == APR_SUCCESS && to_write > 0); + } + + /* bytes_written may actually be NULL */ + if (bytes_written) + *bytes_written = nbytes - to_write; +#undef MAXBUFSIZE +#else + apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written); +#endif + + return svn_error_trace(do_io_file_wrapper_cleanup( + file, rv, + N_("Can't write to file '%s'"), + N_("Can't write to stream"), + pool)); +} + + +svn_error_t * +svn_io_write_unique(const char **tmp_path, + const char *dirpath, + const void *buf, + apr_size_t nbytes, + svn_io_file_del_t delete_when, + apr_pool_t *pool) +{ + apr_file_t *new_file; + svn_error_t *err; + + SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath, + delete_when, pool, pool)); + + err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool); + + if (!err) + err = svn_io_file_flush_to_disk(new_file, pool); + + return svn_error_trace( + svn_error_compose_create(err, + svn_io_file_close(new_file, pool))); +} + + +svn_error_t * +svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool) +{ + /* This is a work-around. APR would flush the write buffer + _after_ truncating the file causing now invalid buffered + data to be written behind OFFSET. */ + SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file), + N_("Can't flush file '%s'"), + N_("Can't flush stream"), + pool)); + + return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset), + N_("Can't truncate file '%s'"), + N_("Can't truncate stream"), + pool); +} + + +svn_error_t * +svn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit, + apr_pool_t *pool) +{ + /* variables */ + apr_size_t total_read = 0; + svn_boolean_t eof = FALSE; + const char *name; + svn_error_t *err; + apr_size_t buf_size = *limit; + + while (buf_size > 0) + { + /* read a fair chunk of data at once. But don't get too ambitious + * as that would result in too much waste. Also make sure we can + * put a NUL after the last byte read. + */ + apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128; + apr_size_t bytes_read = 0; + char *eol; + + /* read data block (or just a part of it) */ + SVN_ERR(svn_io_file_read_full2(file, buf, to_read, + &bytes_read, &eof, pool)); + + /* look or a newline char */ + buf[bytes_read] = 0; + eol = strchr(buf, '\n'); + if (eol) + { + apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read; + + *eol = 0; + *limit = total_read + (eol - buf); + + /* correct the file pointer: + * appear as though we just had read the newline char + */ + SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); + + return SVN_NO_ERROR; + } + else if (eof) + { + /* no EOL found but we hit the end of the file. + * Generate a nice EOF error object and return it. + */ + char dummy; + SVN_ERR(svn_io_file_getc(&dummy, file, pool)); + } + + /* next data chunk */ + buf_size -= bytes_read; + buf += bytes_read; + total_read += bytes_read; + } + + /* buffer limit has been exceeded without finding the EOL */ + err = svn_io_file_name_get(&name, file, pool); + if (err) + name = NULL; + svn_error_clear(err); + + if (name) + return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL, + _("Can't read length line in file '%s'"), + svn_dirent_local_style(name, pool)); + else + return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, + _("Can't read length line in stream")); +} + + +svn_error_t * +svn_io_stat(apr_finfo_t *finfo, const char *fname, + apr_int32_t wanted, apr_pool_t *pool) +{ + apr_status_t status; + const char *fname_apr; + + /* APR doesn't like "" directories */ + if (fname[0] == '\0') + fname = "."; + + SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool)); + + /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ + wanted &= ~SVN__APR_FINFO_MASK_OUT; + + status = apr_stat(finfo, fname_apr, wanted, pool); + if (status) + return svn_error_wrap_apr(status, _("Can't stat '%s'"), + svn_dirent_local_style(fname, pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_file_rename(const char *from_path, const char *to_path, + apr_pool_t *pool) +{ + apr_status_t status = APR_SUCCESS; + const char *from_path_apr, *to_path_apr; + + SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool)); + SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool)); + + status = apr_file_rename(from_path_apr, to_path_apr, pool); + +#if defined(WIN32) || defined(__OS2__) + /* If the target file is read only NTFS reports EACCESS and + FAT/FAT32 reports EEXIST */ + if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status)) + { + /* Set the destination file writable because Windows will not + allow us to rename when to_path is read-only, but will + allow renaming when from_path is read only. */ + SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool)); + + status = apr_file_rename(from_path_apr, to_path_apr, pool); + } + WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool)); +#endif /* WIN32 || __OS2__ */ + + if (status) + return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"), + svn_dirent_local_style(from_path, pool), + svn_dirent_local_style(to_path, pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_file_move(const char *from_path, const char *to_path, + apr_pool_t *pool) +{ + svn_error_t *err = svn_io_file_rename(from_path, to_path, pool); + + if (err && APR_STATUS_IS_EXDEV(err->apr_err)) + { + const char *tmp_to_path; + + svn_error_clear(err); + + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path, + svn_dirent_dirname(to_path, pool), + svn_io_file_del_none, + pool, pool)); + + err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool); + if (err) + goto failed_tmp; + + err = svn_io_file_rename(tmp_to_path, to_path, pool); + if (err) + goto failed_tmp; + + err = svn_io_remove_file2(from_path, FALSE, pool); + if (! err) + return SVN_NO_ERROR; + + svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool)); + + return err; + + failed_tmp: + svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool)); + } + + return err; +} + +/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden. + HIDDEN determines if the hidden attribute + should be set on the newly created directory. */ +static svn_error_t * +dir_make(const char *path, apr_fileperms_t perm, + svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool) +{ + apr_status_t status; + const char *path_apr; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + + /* APR doesn't like "" directories */ + if (path_apr[0] == '\0') + path_apr = "."; + +#if (APR_OS_DEFAULT & APR_WSTICKY) + /* The APR shipped with httpd 2.0.50 contains a bug where + APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits. + There is a special case for file creation, but not directory + creation, so directories wind up getting created with the sticky + bit set. (There is no such thing as a setuid directory, and the + setgid bit is apparently ignored at mkdir() time.) If we detect + this problem, work around it by unsetting those bits if we are + passed APR_OS_DEFAULT. */ + if (perm == APR_OS_DEFAULT) + perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY); +#endif + + status = apr_dir_make(path_apr, perm, pool); + WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool)); + + if (status) + return svn_error_wrap_apr(status, _("Can't create directory '%s'"), + svn_dirent_local_style(path, pool)); + +#ifdef APR_FILE_ATTR_HIDDEN + if (hidden) + { +#ifndef WIN32 + status = apr_file_attrs_set(path_apr, + APR_FILE_ATTR_HIDDEN, + APR_FILE_ATTR_HIDDEN, + pool); +#else + /* on Windows, use our wrapper so we can also set the + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */ + status = io_win_file_attrs_set(path_apr, + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, + pool); + +#endif + if (status) + return svn_error_wrap_apr(status, _("Can't hide directory '%s'"), + svn_dirent_local_style(path, pool)); + } +#endif + +/* Windows does not implement sgid. Skip here because retrieving + the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented + to be 'incredibly expensive'. */ +#ifndef WIN32 + if (sgid) + { + apr_finfo_t finfo; + + /* Per our contract, don't do error-checking. Some filesystems + * don't support the sgid bit, and that's okay. */ + status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool); + + if (!status) + apr_file_perms_set(path_apr, finfo.protection | APR_GSETID); + } +#endif + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool) +{ + return dir_make(path, perm, FALSE, FALSE, pool); +} + +svn_error_t * +svn_io_dir_make_hidden(const char *path, apr_fileperms_t perm, + apr_pool_t *pool) +{ + return dir_make(path, perm, TRUE, FALSE, pool); +} + +svn_error_t * +svn_io_dir_make_sgid(const char *path, apr_fileperms_t perm, + apr_pool_t *pool) +{ + return dir_make(path, perm, FALSE, TRUE, pool); +} + + +svn_error_t * +svn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool) +{ + apr_status_t status; + const char *dirname_apr; + + /* APR doesn't like "" directories */ + if (dirname[0] == '\0') + dirname = "."; + + SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); + + status = apr_dir_open(new_dir, dirname_apr, pool); + if (status) + return svn_error_wrap_apr(status, _("Can't open directory '%s'"), + svn_dirent_local_style(dirname, pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool) +{ + apr_status_t status; + const char *dirname_apr; + + SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); + + status = apr_dir_remove(dirname_apr, pool); + +#ifdef WIN32 + { + svn_boolean_t retry = TRUE; + + if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY) + { + apr_status_t empty_status = dir_is_empty(dirname_apr, pool); + + if (APR_STATUS_IS_ENOTEMPTY(empty_status)) + retry = FALSE; + } + + if (retry) + { + WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool)); + } + } +#endif + if (status) + return svn_error_wrap_apr(status, _("Can't remove directory '%s'"), + svn_dirent_local_style(dirname, pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_dir_read(apr_finfo_t *finfo, + apr_int32_t wanted, + apr_dir_t *thedir, + apr_pool_t *pool) +{ + apr_status_t status; + + status = apr_dir_read(finfo, wanted, thedir); + + if (status) + return svn_error_wrap_apr(status, _("Can't read directory")); + + /* It would be nice to use entry_name_to_utf8() below, but can we + get the dir's path out of an apr_dir_t? I don't see a reliable + way to do it. */ + + if (finfo->fname) + SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool)); + + if (finfo->name) + SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_dir_close(apr_dir_t *thedir) +{ + apr_status_t apr_err = apr_dir_close(thedir); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Error closing directory")); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_dir_walk2(const char *dirname, + apr_int32_t wanted, + svn_io_walk_func_t walk_func, + void *walk_baton, + apr_pool_t *pool) +{ + apr_status_t apr_err; + apr_dir_t *handle; + apr_pool_t *subpool; + const char *dirname_apr; + apr_finfo_t finfo; + + wanted |= APR_FINFO_TYPE | APR_FINFO_NAME; + + /* Quoting APR: On NT this request is incredibly expensive, but accurate. */ + wanted &= ~SVN__APR_FINFO_MASK_OUT; + + /* The documentation for apr_dir_read used to state that "." and ".." + will be returned as the first two files, but it doesn't + work that way in practice, in particular ext3 on Linux-2.6 doesn't + follow the rules. For details see + http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666 + + If APR ever does implement "dot-first" then it would be possible to + remove the svn_io_stat and walk_func calls and use the walk_func + inside the loop. + + Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is + documented to provide it, so we have to do a bit extra. */ + SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool)); + SVN_ERR(cstring_from_utf8(&finfo.name, + svn_dirent_basename(dirname, pool), + pool)); + finfo.valid |= APR_FINFO_NAME; + SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool)); + + SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool)); + + /* APR doesn't like "" directories */ + if (dirname_apr[0] == '\0') + dirname_apr = "."; + + apr_err = apr_dir_open(&handle, dirname_apr, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"), + svn_dirent_local_style(dirname, pool)); + + /* iteration subpool */ + subpool = svn_pool_create(pool); + + while (1) + { + const char *name_utf8; + const char *full_path; + + svn_pool_clear(subpool); + + apr_err = apr_dir_read(&finfo, wanted, handle); + if (APR_STATUS_IS_ENOENT(apr_err)) + break; + else if (apr_err) + { + return svn_error_wrap_apr(apr_err, + _("Can't read directory entry in '%s'"), + svn_dirent_local_style(dirname, pool)); + } + + if (finfo.filetype == APR_DIR) + { + if (finfo.name[0] == '.' + && (finfo.name[1] == '\0' + || (finfo.name[1] == '.' && finfo.name[2] == '\0'))) + /* skip "." and ".." */ + continue; + + /* some other directory. recurse. it will be passed to the + callback inside the recursion. */ + SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, + subpool)); + full_path = svn_dirent_join(dirname, name_utf8, subpool); + SVN_ERR(svn_io_dir_walk2(full_path, + wanted, + walk_func, + walk_baton, + subpool)); + } + else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) + { + /* some other directory. pass it to the callback. */ + SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname, + subpool)); + full_path = svn_dirent_join(dirname, name_utf8, subpool); + SVN_ERR((*walk_func)(walk_baton, + full_path, + &finfo, + subpool)); + } + /* else: + Some other type of file; skip it for now. We've reserved the + right to expand our coverage here in the future, though, + without revving this API. + */ + } + + svn_pool_destroy(subpool); + + apr_err = apr_dir_close(handle); + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"), + svn_dirent_local_style(dirname, pool)); + + return SVN_NO_ERROR; +} + + + +/** + * Determine if a directory is empty or not. + * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not. + * @param path The directory. + * @param pool Used for temporary allocation. + * @remark If path is not a directory, or some other error occurs, + * then return the appropriate apr status code. + * + * (This function is written in APR style, in anticipation of + * perhaps someday being moved to APR as 'apr_dir_is_empty'.) + */ +static apr_status_t +dir_is_empty(const char *dir, apr_pool_t *pool) +{ + apr_status_t apr_err; + apr_dir_t *dir_handle; + apr_finfo_t finfo; + apr_status_t retval = APR_SUCCESS; + + /* APR doesn't like "" directories */ + if (dir[0] == '\0') + dir = "."; + + apr_err = apr_dir_open(&dir_handle, dir, pool); + if (apr_err != APR_SUCCESS) + return apr_err; + + for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle); + apr_err == APR_SUCCESS; + apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle)) + { + /* Ignore entries for this dir and its parent, robustly. + (APR promises that they'll come first, so technically + this guard could be moved outside the loop. But Ryan Bloom + says he doesn't believe it, and I believe him. */ + if (! (finfo.name[0] == '.' + && (finfo.name[1] == '\0' + || (finfo.name[1] == '.' && finfo.name[2] == '\0')))) + { + retval = APR_ENOTEMPTY; + break; + } + } + + /* Make sure we broke out of the loop for the right reason. */ + if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err)) + return apr_err; + + apr_err = apr_dir_close(dir_handle); + if (apr_err != APR_SUCCESS) + return apr_err; + + return retval; +} + + +svn_error_t * +svn_io_dir_empty(svn_boolean_t *is_empty_p, + const char *path, + apr_pool_t *pool) +{ + apr_status_t status; + const char *path_apr; + + SVN_ERR(cstring_from_utf8(&path_apr, path, pool)); + + status = dir_is_empty(path_apr, pool); + + if (!status) + *is_empty_p = TRUE; + else if (APR_STATUS_IS_ENOTEMPTY(status)) + *is_empty_p = FALSE; + else + return svn_error_wrap_apr(status, _("Can't check directory '%s'"), + svn_dirent_local_style(path, pool)); + + return SVN_NO_ERROR; +} + + + +/*** Version/format files ***/ + +svn_error_t * +svn_io_write_version_file(const char *path, + int version, + apr_pool_t *pool) +{ + const char *path_tmp; + const char *format_contents = apr_psprintf(pool, "%d\n", version); + + SVN_ERR_ASSERT(version >= 0); + + SVN_ERR(svn_io_write_unique(&path_tmp, + svn_dirent_dirname(path, pool), + format_contents, strlen(format_contents), + svn_io_file_del_none, pool)); + +#if defined(WIN32) || defined(__OS2__) + /* make the destination writable, but only on Windows, because + Windows does not let us replace read-only files. */ + SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool)); +#endif /* WIN32 || __OS2__ */ + + /* rename the temp file as the real destination */ + SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); + + /* And finally remove the perms to make it read only */ + return svn_io_set_file_read_only(path, FALSE, pool); +} + + +svn_error_t * +svn_io_read_version_file(int *version, + const char *path, + apr_pool_t *pool) +{ + apr_file_t *format_file; + char buf[80]; + apr_size_t len; + svn_error_t *err; + + /* Read a chunk of data from PATH */ + SVN_ERR(svn_io_file_open(&format_file, path, APR_READ, + APR_OS_DEFAULT, pool)); + len = sizeof(buf); + err = svn_io_file_read(format_file, buf, &len, pool); + + /* Close the file. */ + SVN_ERR(svn_error_compose_create(err, + svn_io_file_close(format_file, pool))); + + /* If there was no data in PATH, return an error. */ + if (len == 0) + return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, + _("Reading '%s'"), + svn_dirent_local_style(path, pool)); + + /* Check that the first line contains only digits. */ + { + apr_size_t i; + + for (i = 0; i < len; ++i) + { + char c = buf[i]; + + if (i > 0 && (c == '\r' || c == '\n')) + { + buf[i] = '\0'; + break; + } + if (! svn_ctype_isdigit(c)) + return svn_error_createf + (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL, + _("First line of '%s' contains non-digit"), + svn_dirent_local_style(path, pool)); + } + } + + /* Convert to integer. */ + SVN_ERR(svn_cstring_atoi(version, buf)); + + return SVN_NO_ERROR; +} + + + +/* Do a byte-for-byte comparison of FILE1 and FILE2. */ +static svn_error_t * +contents_identical_p(svn_boolean_t *identical_p, + const char *file1, + const char *file2, + apr_pool_t *pool) +{ + svn_error_t *err; + apr_size_t bytes_read1, bytes_read2; + char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); + char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); + apr_file_t *file1_h; + apr_file_t *file2_h; + svn_boolean_t eof1 = FALSE; + svn_boolean_t eof2 = FALSE; + + SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, + pool)); + + err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, + pool); + + if (err) + return svn_error_trace( + svn_error_compose_create(err, + svn_io_file_close(file1_h, pool))); + + *identical_p = TRUE; /* assume TRUE, until disproved below */ + while (!err && !eof1 && !eof2) + { + err = svn_io_file_read_full2(file1_h, buf1, + SVN__STREAM_CHUNK_SIZE, &bytes_read1, + &eof1, pool); + if (err) + break; + + err = svn_io_file_read_full2(file2_h, buf2, + SVN__STREAM_CHUNK_SIZE, &bytes_read2, + &eof2, pool); + if (err) + break; + + if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1)) + { + *identical_p = FALSE; + break; + } + } + + /* Special case: one file being a prefix of the other and the shorter + * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */ + if (!err && (eof1 != eof2)) + *identical_p = FALSE; + + return svn_error_trace( + svn_error_compose_create( + err, + svn_error_compose_create(svn_io_file_close(file1_h, pool), + svn_io_file_close(file2_h, pool)))); +} + + + +/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */ +static svn_error_t * +contents_three_identical_p(svn_boolean_t *identical_p12, + svn_boolean_t *identical_p23, + svn_boolean_t *identical_p13, + const char *file1, + const char *file2, + const char *file3, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + apr_size_t bytes_read1, bytes_read2, bytes_read3; + char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); + char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); + char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); + apr_file_t *file1_h; + apr_file_t *file2_h; + apr_file_t *file3_h; + svn_boolean_t eof1 = FALSE; + svn_boolean_t eof2 = FALSE; + svn_boolean_t eof3 = FALSE; + svn_boolean_t read_1, read_2, read_3; + + SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT, + scratch_pool)); + + err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT, + scratch_pool); + + if (err) + return svn_error_trace( + svn_error_compose_create(err, + svn_io_file_close(file1_h, scratch_pool))); + + err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT, + scratch_pool); + + if (err) + return svn_error_trace( + svn_error_compose_create( + err, + svn_error_compose_create(svn_io_file_close(file1_h, + scratch_pool), + svn_io_file_close(file2_h, + scratch_pool)))); + + /* assume TRUE, until disproved below */ + *identical_p12 = *identical_p23 = *identical_p13 = TRUE; + /* We need to read as long as no error occurs, and as long as one of the + * flags could still change due to a read operation */ + while (!err + && ((*identical_p12 && !eof1 && !eof2) + || (*identical_p23 && !eof2 && !eof3) + || (*identical_p13 && !eof1 && !eof3))) + { + read_1 = read_2 = read_3 = FALSE; + + /* As long as a file is not at the end yet, and it is still + * potentially identical to another file, we read the next chunk.*/ + if (!eof1 && (identical_p12 || identical_p13)) + { + err = svn_io_file_read_full2(file1_h, buf1, + SVN__STREAM_CHUNK_SIZE, &bytes_read1, + &eof1, scratch_pool); + if (err) + break; + read_1 = TRUE; + } + + if (!eof2 && (identical_p12 || identical_p23)) + { + err = svn_io_file_read_full2(file2_h, buf2, + SVN__STREAM_CHUNK_SIZE, &bytes_read2, + &eof2, scratch_pool); + if (err) + break; + read_2 = TRUE; + } + + if (!eof3 && (identical_p13 || identical_p23)) + { + err = svn_io_file_read_full2(file3_h, buf3, + SVN__STREAM_CHUNK_SIZE, &bytes_read3, + &eof3, scratch_pool); + if (err) + break; + read_3 = TRUE; + } + + /* If the files are still marked identical, and at least one of them + * is not at the end of file, we check whether they differ, and set + * their flag to false then. */ + if (*identical_p12 + && (read_1 || read_2) + && ((eof1 != eof2) + || (bytes_read1 != bytes_read2) + || memcmp(buf1, buf2, bytes_read1))) + { + *identical_p12 = FALSE; + } + + if (*identical_p23 + && (read_2 || read_3) + && ((eof2 != eof3) + || (bytes_read2 != bytes_read3) + || memcmp(buf2, buf3, bytes_read2))) + { + *identical_p23 = FALSE; + } + + if (*identical_p13 + && (read_1 || read_3) + && ((eof1 != eof3) + || (bytes_read1 != bytes_read3) + || memcmp(buf1, buf3, bytes_read3))) + { + *identical_p13 = FALSE; + } + } + + return svn_error_trace( + svn_error_compose_create( + err, + svn_error_compose_create( + svn_io_file_close(file1_h, scratch_pool), + svn_error_compose_create( + svn_io_file_close(file2_h, scratch_pool), + svn_io_file_close(file3_h, scratch_pool))))); +} + + + +svn_error_t * +svn_io_files_contents_same_p(svn_boolean_t *same, + const char *file1, + const char *file2, + apr_pool_t *pool) +{ + svn_boolean_t q; + + SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool)); + + if (q) + { + *same = FALSE; + return SVN_NO_ERROR; + } + + SVN_ERR(contents_identical_p(&q, file1, file2, pool)); + + if (q) + *same = TRUE; + else + *same = FALSE; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_files_contents_three_same_p(svn_boolean_t *same12, + svn_boolean_t *same23, + svn_boolean_t *same13, + const char *file1, + const char *file2, + const char *file3, + apr_pool_t *scratch_pool) +{ + svn_boolean_t diff_size12, diff_size23, diff_size13; + + SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12, + &diff_size23, + &diff_size13, + file1, + file2, + file3, + scratch_pool)); + + if (diff_size12 && diff_size23 && diff_size13) + { + *same12 = *same23 = *same13 = FALSE; + } + else if (diff_size12 && diff_size23) + { + *same12 = *same23 = FALSE; + SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool)); + } + else if (diff_size23 && diff_size13) + { + *same23 = *same13 = FALSE; + SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool)); + } + else if (diff_size12 && diff_size13) + { + *same12 = *same13 = FALSE; + SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool)); + } + else + { + SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13); + SVN_ERR(contents_three_identical_p(same12, same23, same13, + file1, file2, file3, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +#ifdef WIN32 +/* Counter value of file_mktemp request (used in a threadsafe way), to make + sure that a single process normally never generates the same tempname + twice */ +static volatile apr_uint32_t tempname_counter = 0; +#endif + +/* Creates a new temporary file in DIRECTORY with apr flags FLAGS. + Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name. + Perform temporary allocations in SCRATCH_POOL and the result in + RESULT_POOL. */ +static svn_error_t * +temp_file_create(apr_file_t **new_file, + const char **new_file_name, + const char *directory, + apr_int32_t flags, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ +#ifndef WIN32 + const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool); + const char *templ_apr; + apr_status_t status; + + SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool)); + + /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the + data available in POOL and we need a non-const pointer here, + as apr changes the template to return the new filename. */ + status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool); + + if (status) + return svn_error_wrap_apr(status, _("Can't create temporary file from " + "template '%s'"), templ); + + /* Translate the returned path back to utf-8 before returning it */ + return svn_error_trace(svn_path_cstring_to_utf8(new_file_name, + templ_apr, + result_pool)); +#else + /* The Windows implementation of apr_file_mktemp doesn't handle access + denied errors correctly. Therefore we implement our own temp file + creation function here. */ + + /* ### Most of this is borrowed from the svn_io_open_uniquely_named(), + ### the function we used before. But we try to guess a more unique + ### name before trying if it exists. */ + + /* Offset by some time value and a unique request nr to make the number + +- unique for both this process and on the computer */ + int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter) + + GetCurrentProcessId(); + int i; + + /* ### Maybe use an iterpool? */ + for (i = 0; i <= 99999; i++) + { + apr_uint32_t unique_nr; + const char *unique_name; + const char *unique_name_apr; + apr_file_t *try_file; + apr_status_t apr_err; + + /* Generate a number that should be unique for this application and + usually for the entire computer to reduce the number of cycles + through this loop. (A bit of calculation is much cheaper then + disk io) */ + unique_nr = baseNr + 3 * i; + + unique_name = svn_dirent_join(directory, + apr_psprintf(scratch_pool, "svn-%X", + unique_nr), + scratch_pool); + + SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool)); + + apr_err = file_open(&try_file, unique_name_apr, flags, + APR_OS_DEFAULT, FALSE, scratch_pool); + + if (APR_STATUS_IS_EEXIST(apr_err)) + continue; + else if (apr_err) + { + /* On Win32, CreateFile fails with an "Access Denied" error + code, rather than "File Already Exists", if the colliding + name belongs to a directory. */ + + if (APR_STATUS_IS_EACCES(apr_err)) + { + apr_finfo_t finfo; + apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr, + APR_FINFO_TYPE, scratch_pool); + + if (!apr_err_2 && finfo.filetype == APR_DIR) + continue; + + apr_err_2 = APR_TO_OS_ERROR(apr_err); + + if (apr_err_2 == ERROR_ACCESS_DENIED || + apr_err_2 == ERROR_SHARING_VIOLATION) + { + /* The file is in use by another process or is hidden; + create a new name, but don't do this 99999 times in + case the folder is not writable */ + i += 797; + continue; + } + + /* Else fall through and return the original error. */ + } + + return svn_error_wrap_apr(apr_err, _("Can't open '%s'"), + svn_dirent_local_style(unique_name, + scratch_pool)); + } + else + { + /* Move file to the right pool */ + apr_err = apr_file_setaside(new_file, try_file, result_pool); + + if (apr_err) + return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"), + svn_dirent_local_style(unique_name, + scratch_pool)); + + *new_file_name = apr_pstrdup(result_pool, unique_name); + + return SVN_NO_ERROR; + } + } + + return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED, + NULL, + _("Unable to make name in '%s'"), + svn_dirent_local_style(directory, scratch_pool)); +#endif +} + +/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */ +svn_error_t * +svn_io_file_name_get(const char **filename, + apr_file_t *file, + apr_pool_t *pool) +{ + const char *fname_apr; + apr_status_t status; + + status = apr_file_name_get(&fname_apr, file); + if (status) + return svn_error_wrap_apr(status, _("Can't get file name")); + + if (fname_apr) + SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool)); + else + *filename = NULL; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_io_open_unique_file3(apr_file_t **file, + const char **unique_path, + const char *dirpath, + svn_io_file_del_t delete_when, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *tempfile; + const char *tempname; + struct temp_file_cleanup_s *baton = NULL; + apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL | + APR_BUFFERED | APR_BINARY); +#if !defined(WIN32) && !defined(__OS2__) + apr_fileperms_t perms; + svn_boolean_t using_system_temp_dir = FALSE; +#endif + + SVN_ERR_ASSERT(file || unique_path); + if (file) + *file = NULL; + if (unique_path) + *unique_path = NULL; + + if (dirpath == NULL) + { +#if !defined(WIN32) && !defined(__OS2__) + using_system_temp_dir = TRUE; +#endif + SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool)); + } + + switch (delete_when) + { + case svn_io_file_del_on_pool_cleanup: + baton = apr_palloc(result_pool, sizeof(*baton)); + baton->pool = result_pool; + baton->fname_apr = NULL; + + /* Because cleanups are run LIFO, we need to make sure to register + our cleanup before the apr_file_close cleanup: + + On Windows, you can't remove an open file. + */ + apr_pool_cleanup_register(result_pool, baton, + temp_file_plain_cleanup_handler, + temp_file_child_cleanup_handler); + + break; + case svn_io_file_del_on_close: + flags |= APR_DELONCLOSE; + break; + default: + break; + } + + SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags, + result_pool, scratch_pool)); + +#if !defined(WIN32) && !defined(__OS2__) + /* apr_file_mktemp() creates files with mode 0600. + * This is appropriate if we're using a system temp dir since we don't + * want to leak sensitive data into temp files other users can read. + * If we're not using a system temp dir we're probably using the + * .svn/tmp area and it's likely that the tempfile will end up being + * copied or renamed into the working copy. + * This would cause working files having mode 0600 while users might + * expect to see 0644 or 0664. So we tweak perms of the tempfile in this + * case, but only if the umask allows it. */ + if (!using_system_temp_dir) + { + SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool)); + SVN_ERR(file_perms_set2(tempfile, perms, scratch_pool)); + } +#endif + + if (file) + *file = tempfile; + else + SVN_ERR(svn_io_file_close(tempfile, scratch_pool)); + + if (unique_path) + *unique_path = tempname; /* Was allocated in result_pool */ + + if (baton) + SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_io_file_readline(apr_file_t *file, + svn_stringbuf_t **stringbuf, + const char **eol, + svn_boolean_t *eof, + apr_size_t max_len, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *str; + const char *eol_str; + apr_size_t numbytes; + char c; + apr_size_t len; + svn_boolean_t found_eof; + + str = svn_stringbuf_create_ensure(80, result_pool); + + /* Read bytes into STR up to and including, but not storing, + * the next EOL sequence. */ + eol_str = NULL; + numbytes = 1; + len = 0; + found_eof = FALSE; + while (!found_eof) + { + if (len < max_len) + SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, + &found_eof, scratch_pool)); + len++; + if (numbytes != 1 || len > max_len) + { + found_eof = TRUE; + break; + } + + if (c == '\n') + { + eol_str = "\n"; + } + else if (c == '\r') + { + eol_str = "\r"; + + if (!found_eof && len < max_len) + { + apr_off_t pos; + + /* Check for "\r\n" by peeking at the next byte. */ + pos = 0; + SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); + SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, + &found_eof, scratch_pool)); + if (numbytes == 1 && c == '\n') + { + eol_str = "\r\n"; + len++; + } + else + { + /* Pretend we never peeked. */ + SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); + found_eof = FALSE; + numbytes = 1; + } + } + } + else + svn_stringbuf_appendbyte(str, c); + + if (eol_str) + break; + } + + if (eol) + *eol = eol_str; + if (eof) + *eof = found_eof; + *stringbuf = str; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/iter.c b/subversion/libsvn_subr/iter.c new file mode 100644 index 000000000000..45ec489250d0 --- /dev/null +++ b/subversion/libsvn_subr/iter.c @@ -0,0 +1,216 @@ +/* iter.c : iteration drivers + * + * ==================================================================== + * 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_iter.h" +#include "svn_pools.h" +#include "private/svn_dep_compat.h" + +#include "svn_error_codes.h" + +static svn_error_t internal_break_error = + { + SVN_ERR_ITER_BREAK, /* APR status */ + NULL, /* message */ + NULL, /* child error */ + NULL, /* pool */ + __FILE__, /* file name */ + __LINE__ /* line number */ + }; + +#if APR_VERSION_AT_LEAST(1, 4, 0) +struct hash_do_baton +{ + void *baton; + svn_iter_apr_hash_cb_t func; + svn_error_t *err; + apr_pool_t *iterpool; +}; + +static +int hash_do_callback(void *baton, + const void *key, + apr_ssize_t klen, + const void *value) +{ + struct hash_do_baton *hdb = baton; + + svn_pool_clear(hdb->iterpool); + hdb->err = (*hdb->func)(hdb->baton, key, klen, (void *)value, hdb->iterpool); + + return hdb->err == SVN_NO_ERROR; +} +#endif + +svn_error_t * +svn_iter_apr_hash(svn_boolean_t *completed, + apr_hash_t *hash, + svn_iter_apr_hash_cb_t func, + void *baton, + apr_pool_t *pool) +{ +#if APR_VERSION_AT_LEAST(1, 4, 0) + struct hash_do_baton hdb; + svn_boolean_t error_received; + + hdb.func = func; + hdb.baton = baton; + hdb.iterpool = svn_pool_create(pool); + + error_received = !apr_hash_do(hash_do_callback, &hdb, hash); + + svn_pool_destroy(hdb.iterpool); + + if (completed) + *completed = !error_received; + + if (!error_received) + return SVN_NO_ERROR; + + if (hdb.err->apr_err == SVN_ERR_ITER_BREAK + && hdb.err != &internal_break_error) + { + /* Errors - except those created by svn_iter_break() - + need to be cleared when not further propagated. */ + svn_error_clear(hdb.err); + + hdb.err = SVN_NO_ERROR; + } + + return hdb.err; +#else + svn_error_t *err = SVN_NO_ERROR; + apr_pool_t *iterpool = svn_pool_create(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, hash); + ! err && hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + apr_ssize_t len; + + svn_pool_clear(iterpool); + + apr_hash_this(hi, &key, &len, &val); + err = (*func)(baton, key, len, val, iterpool); + } + + if (completed) + *completed = ! err; + + if (err && err->apr_err == SVN_ERR_ITER_BREAK) + { + if (err != &internal_break_error) + /* Errors - except those created by svn_iter_break() - + need to be cleared when not further propagated. */ + svn_error_clear(err); + + err = SVN_NO_ERROR; + } + + /* Clear iterpool, because callers may clear the error but have no way + to clear the iterpool with potentially lots of allocated memory */ + svn_pool_destroy(iterpool); + + return err; +#endif +} + +svn_error_t * +svn_iter_apr_array(svn_boolean_t *completed, + const apr_array_header_t *array, + svn_iter_apr_array_cb_t func, + void *baton, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + apr_pool_t *iterpool = svn_pool_create(pool); + int i; + + for (i = 0; (! err) && i < array->nelts; ++i) + { + void *item = array->elts + array->elt_size*i; + + svn_pool_clear(iterpool); + + err = (*func)(baton, item, iterpool); + } + + if (completed) + *completed = ! err; + + if (err && err->apr_err == SVN_ERR_ITER_BREAK) + { + if (err != &internal_break_error) + /* Errors - except those created by svn_iter_break() - + need to be cleared when not further propagated. */ + svn_error_clear(err); + + err = SVN_NO_ERROR; + } + + /* Clear iterpool, because callers may clear the error but have no way + to clear the iterpool with potentially lots of allocated memory */ + svn_pool_destroy(iterpool); + + return err; +} + +/* Note: Although this is a "__" function, it is in the public ABI, so + * we can never remove it or change its signature. */ +svn_error_t * +svn_iter__break(void) +{ + return &internal_break_error; +} + +/* Note about the type casts: apr_hash_this() does not expect a const hash + * index pointer even though it does not modify the hash index. In + * Subversion we're trying to be const-correct, so these functions all take + * a const hash index and we cast away the const when passing it down to + * APR. (A compiler may warn about casting away 'const', but at least this + * cast is explicit and gathered in one place.) */ + +const void *svn__apr_hash_index_key(const apr_hash_index_t *hi) +{ + const void *key; + + apr_hash_this((apr_hash_index_t *)hi, &key, NULL, NULL); + return key; +} + +apr_ssize_t svn__apr_hash_index_klen(const apr_hash_index_t *hi) +{ + apr_ssize_t klen; + + apr_hash_this((apr_hash_index_t *)hi, NULL, &klen, NULL); + return klen; +} + +void *svn__apr_hash_index_val(const apr_hash_index_t *hi) +{ + void *val; + + apr_hash_this((apr_hash_index_t *)hi, NULL, NULL, &val); + return val; +} diff --git a/subversion/libsvn_subr/lock.c b/subversion/libsvn_subr/lock.c new file mode 100644 index 000000000000..71dd8c7ff69b --- /dev/null +++ b/subversion/libsvn_subr/lock.c @@ -0,0 +1,60 @@ +/* + * lock.c: routines for svn_lock_t 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_strings.h> + +#include "svn_types.h" + + +/*** Code. ***/ + +svn_lock_t * +svn_lock_create(apr_pool_t *pool) +{ + return apr_pcalloc(pool, sizeof(svn_lock_t)); +} + +svn_lock_t * +svn_lock_dup(const svn_lock_t *lock, apr_pool_t *pool) +{ + svn_lock_t *new_l; + + if (lock == NULL) + return NULL; + + new_l = apr_palloc(pool, sizeof(*new_l)); + *new_l = *lock; + + new_l->path = apr_pstrdup(pool, new_l->path); + new_l->token = apr_pstrdup(pool, new_l->token); + new_l->owner = apr_pstrdup(pool, new_l->owner); + new_l->comment = apr_pstrdup(pool, new_l->comment); + + return new_l; +} diff --git a/subversion/libsvn_subr/log.c b/subversion/libsvn_subr/log.c new file mode 100644 index 000000000000..9e0b22a29ab7 --- /dev/null +++ b/subversion/libsvn_subr/log.c @@ -0,0 +1,396 @@ +/* + * log.c : Functions for logging Subversion operations + * + * ==================================================================== + * 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 <stdarg.h> + +#define APR_WANT_STRFUNC +#include <apr_want.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_mergeinfo.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_string.h" + +#include "private/svn_log.h" + + +static const char * +log_depth(svn_depth_t depth, apr_pool_t *pool) +{ + if (depth == svn_depth_unknown) + return ""; + return apr_pstrcat(pool, " depth=", svn_depth_to_word(depth), (char *)NULL); +} + +static const char * +log_include_merged_revisions(svn_boolean_t include_merged_revisions) +{ + if (include_merged_revisions) + return " include-merged-revisions"; + return ""; +} + + +const char * +svn_log__reparent(const char *path, apr_pool_t *pool) +{ + return apr_psprintf(pool, "reparent %s", svn_path_uri_encode(path, pool)); + +} + +const char * +svn_log__change_rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool) +{ + return apr_psprintf(pool, "change-rev-prop r%ld %s", rev, + svn_path_uri_encode(name, pool)); +} + +const char * +svn_log__rev_proplist(svn_revnum_t rev, apr_pool_t *pool) +{ + return apr_psprintf(pool, "rev-proplist r%ld", rev); +} + +const char * +svn_log__rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool) +{ + return apr_psprintf(pool, "rev-prop r%ld %s", rev, + svn_path_uri_encode(name, pool)); +} + +const char * +svn_log__commit(svn_revnum_t rev, apr_pool_t *pool) +{ + return apr_psprintf(pool, "commit r%ld", rev); +} + +const char * +svn_log__get_file(const char *path, svn_revnum_t rev, + svn_boolean_t want_contents, svn_boolean_t want_props, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-file %s r%ld%s%s", + svn_path_uri_encode(path, pool), rev, + want_contents ? " text" : "", + want_props ? " props" : ""); +} + +const char * +svn_log__get_dir(const char *path, svn_revnum_t rev, + svn_boolean_t want_contents, svn_boolean_t want_props, + apr_uint64_t dirent_fields, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-dir %s r%ld%s%s", + svn_path_uri_encode(path, pool), rev, + want_contents ? " text" : "", + want_props ? " props" : ""); +} + +const char * +svn_log__get_mergeinfo(const apr_array_header_t *paths, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_descendants, + apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "get-mergeinfo (%s) %s%s", + space_separated_paths->data, + svn_inheritance_to_word(inherit), + include_descendants ? " include-descendants" : ""); +} + +const char * +svn_log__checkout(const char *path, svn_revnum_t rev, svn_depth_t depth, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "checkout-or-export %s r%ld%s", + svn_path_uri_encode(path, pool), rev, + log_depth(depth, pool)); +} + +const char * +svn_log__update(const char *path, svn_revnum_t rev, svn_depth_t depth, + svn_boolean_t send_copyfrom_args, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "update %s r%ld%s%s", + svn_path_uri_encode(path, pool), rev, + log_depth(depth, pool), + (send_copyfrom_args + ? " send-copyfrom-args" + : "")); +} + +const char * +svn_log__switch(const char *path, const char *dst_path, svn_revnum_t revnum, + svn_depth_t depth, apr_pool_t *pool) +{ + return apr_psprintf(pool, "switch %s %s@%ld%s", + svn_path_uri_encode(path, pool), + svn_path_uri_encode(dst_path, pool), revnum, + log_depth(depth, pool)); +} + +const char * +svn_log__status(const char *path, svn_revnum_t rev, svn_depth_t depth, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "status %s r%ld%s", + svn_path_uri_encode(path, pool), rev, + log_depth(depth, pool)); +} + +const char * +svn_log__diff(const char *path, svn_revnum_t from_revnum, + const char *dst_path, svn_revnum_t revnum, + svn_depth_t depth, svn_boolean_t ignore_ancestry, + apr_pool_t *pool) +{ + const char *log_ignore_ancestry = (ignore_ancestry + ? " ignore-ancestry" + : ""); + if (strcmp(path, dst_path) == 0) + return apr_psprintf(pool, "diff %s r%ld:%ld%s%s", + svn_path_uri_encode(path, pool), from_revnum, revnum, + log_depth(depth, pool), log_ignore_ancestry); + return apr_psprintf(pool, "diff %s@%ld %s@%ld%s%s", + svn_path_uri_encode(path, pool), from_revnum, + svn_path_uri_encode(dst_path, pool), revnum, + log_depth(depth, pool), log_ignore_ancestry); +} + +const char * +svn_log__log(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, apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool); + svn_stringbuf_t *options = svn_stringbuf_create_empty(pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + + if (limit) + { + const char *tmp = apr_psprintf(pool, " limit=%d", limit); + svn_stringbuf_appendcstr(options, tmp); + } + if (discover_changed_paths) + svn_stringbuf_appendcstr(options, " discover-changed-paths"); + if (strict_node_history) + svn_stringbuf_appendcstr(options, " strict"); + if (include_merged_revisions) + svn_stringbuf_appendcstr(options, + log_include_merged_revisions(include_merged_revisions)); + if (revprops == NULL) + svn_stringbuf_appendcstr(options, " revprops=all"); + else if (revprops->nelts > 0) + { + svn_stringbuf_appendcstr(options, " revprops=("); + for (i = 0; i < revprops->nelts; i++) + { + const char *name = APR_ARRAY_IDX(revprops, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(options, " "); + svn_stringbuf_appendcstr(options, svn_path_uri_encode(name, + iterpool)); + } + svn_stringbuf_appendcstr(options, ")"); + } + svn_pool_destroy(iterpool); + return apr_psprintf(pool, "log (%s) r%ld:%ld%s", + space_separated_paths->data, start, end, + options->data); +} + +const char * +svn_log__get_locations(const char *path, svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + apr_pool_t *pool) +{ + const svn_revnum_t *revision_ptr, *revision_ptr_start, *revision_ptr_end; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_revnums = svn_stringbuf_create_empty(pool); + + revision_ptr_start = (const svn_revnum_t *)location_revisions->elts; + revision_ptr = revision_ptr_start; + revision_ptr_end = revision_ptr + location_revisions->nelts; + while (revision_ptr < revision_ptr_end) + { + svn_pool_clear(iterpool); + if (revision_ptr != revision_ptr_start) + svn_stringbuf_appendcstr(space_separated_revnums, " "); + svn_stringbuf_appendcstr(space_separated_revnums, + apr_psprintf(iterpool, "%ld", *revision_ptr)); + ++revision_ptr; + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "get-locations %s@%ld (%s)", + svn_path_uri_encode(path, pool), + peg_revision, space_separated_revnums->data); +} + +const char * +svn_log__get_location_segments(const char *path, svn_revnum_t peg_revision, + svn_revnum_t start, svn_revnum_t end, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-location-segments %s@%ld r%ld:%ld", + svn_path_uri_encode(path, pool), + peg_revision, start, end); +} + +const char * +svn_log__get_file_revs(const char *path, svn_revnum_t start, svn_revnum_t end, + svn_boolean_t include_merged_revisions, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-file-revs %s r%ld:%ld%s", + svn_path_uri_encode(path, pool), start, end, + log_include_merged_revisions(include_merged_revisions)); +} + +const char * +svn_log__lock(const apr_array_header_t *paths, + svn_boolean_t steal, apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "lock (%s)%s", space_separated_paths->data, + steal ? " steal" : ""); +} + +const char * +svn_log__unlock(const apr_array_header_t *paths, + svn_boolean_t break_lock, apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "unlock (%s)%s", space_separated_paths->data, + break_lock ? " break" : ""); +} + +const char * +svn_log__lock_one_path(const char *path, svn_boolean_t steal, + apr_pool_t *pool) +{ + apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path)); + APR_ARRAY_PUSH(paths, const char *) = path; + return svn_log__lock(paths, steal, pool); +} + +const char * +svn_log__unlock_one_path(const char *path, svn_boolean_t break_lock, + apr_pool_t *pool) +{ + apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path)); + APR_ARRAY_PUSH(paths, const char *) = path; + return svn_log__unlock(paths, break_lock, pool); +} + +const char * +svn_log__replay(const char *path, svn_revnum_t rev, apr_pool_t *pool) +{ + const char *log_path; + + if (path && path[0] != '\0') + log_path = svn_path_uri_encode(path, pool); + else + log_path = "/"; + return apr_psprintf(pool, "replay %s r%ld", log_path, rev); +} + +const char * +svn_log__get_inherited_props(const char *path, + svn_revnum_t rev, + apr_pool_t *pool) +{ + const char *log_path; + + if (path && path[0] != '\0') + log_path = svn_path_uri_encode(path, pool); + else + log_path = "/"; + return apr_psprintf(pool, "get-inherited-props %s r%ld", log_path, rev); +} diff --git a/subversion/libsvn_subr/macos_keychain.c b/subversion/libsvn_subr/macos_keychain.c new file mode 100644 index 000000000000..f15324e50400 --- /dev/null +++ b/subversion/libsvn_subr/macos_keychain.c @@ -0,0 +1,263 @@ +/* + * macos_keychain.c: Mac OS keychain providers for SVN_AUTH_* + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + +/*** Includes. ***/ + +#include <apr_pools.h> +#include "svn_auth.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_config.h" +#include "svn_user.h" + +#include "private/svn_auth_private.h" + +#include "svn_private_config.h" + +#ifdef SVN_HAVE_KEYCHAIN_SERVICES + +#include <Security/Security.h> + +/*-----------------------------------------------------------------------*/ +/* keychain simple provider, puts passwords in the KeyChain */ +/*-----------------------------------------------------------------------*/ + +/* + * XXX (2005-12-07): If no GUI is available (e.g. over a SSH session), + * you won't be prompted for credentials with which to unlock your + * keychain. Apple recognizes lack of TTY prompting as a known + * problem. + * + * + * XXX (2005-12-07): SecKeychainSetUserInteractionAllowed(FALSE) does + * not appear to actually prevent all user interaction. Specifically, + * if the executable changes (for example, if it is rebuilt), the + * system prompts the user to okay the use of the new executable. + * + * Worse than that, the interactivity setting is global per app (not + * process/thread), meaning that there is a race condition in the + * implementation below between calls to + * SecKeychainSetUserInteractionAllowed() when multiple instances of + * the same Subversion auth provider-based app run concurrently. + */ + +/* Implementation of svn_auth__password_set_t that stores + the password in the OS X KeyChain. */ +static svn_error_t * +keychain_password_set(svn_boolean_t *done, + apr_hash_t *creds, + const char *realmstring, + const char *username, + const char *password, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + OSStatus status; + SecKeychainItemRef item; + + if (non_interactive) + SecKeychainSetUserInteractionAllowed(FALSE); + + status = SecKeychainFindGenericPassword(NULL, (int) strlen(realmstring), + realmstring, username == NULL + ? 0 + : (int) strlen(username), + username, 0, NULL, &item); + if (status) + { + if (status == errSecItemNotFound) + status = SecKeychainAddGenericPassword(NULL, (int) strlen(realmstring), + realmstring, username == NULL + ? 0 + : (int) strlen(username), + username, (int) strlen(password), + password, NULL); + } + else + { + status = SecKeychainItemModifyAttributesAndData(item, NULL, + (int) strlen(password), + password); + CFRelease(item); + } + + if (non_interactive) + SecKeychainSetUserInteractionAllowed(TRUE); + + *done = (status == 0); + + return SVN_NO_ERROR; +} + +/* Implementation of svn_auth__password_get_t that retrieves + the password from the OS X KeyChain. */ +static svn_error_t * +keychain_password_get(svn_boolean_t *done, + const char **password, + apr_hash_t *creds, + const char *realmstring, + const char *username, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + OSStatus status; + UInt32 length; + void *data; + + *done = FALSE; + + if (non_interactive) + SecKeychainSetUserInteractionAllowed(FALSE); + + status = SecKeychainFindGenericPassword(NULL, (int) strlen(realmstring), + realmstring, username == NULL + ? 0 + : (int) strlen(username), + username, &length, &data, NULL); + + if (non_interactive) + SecKeychainSetUserInteractionAllowed(TRUE); + + if (status != 0) + return SVN_NO_ERROR; + + *password = apr_pstrmemdup(pool, data, length); + SecKeychainItemFreeContent(NULL, data); + *done = TRUE; + return SVN_NO_ERROR; +} + +/* Get cached encrypted credentials from the simple provider's cache. */ +static svn_error_t * +keychain_simple_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_get(credentials, + iter_baton, + provider_baton, + parameters, + realmstring, + keychain_password_get, + SVN_AUTH__KEYCHAIN_PASSWORD_TYPE, + pool); +} + +/* Save encrypted credentials to the simple provider's cache. */ +static svn_error_t * +keychain_simple_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_set(saved, credentials, + provider_baton, + parameters, + realmstring, + keychain_password_set, + SVN_AUTH__KEYCHAIN_PASSWORD_TYPE, + pool); +} + +static const svn_auth_provider_t keychain_simple_provider = { + SVN_AUTH_CRED_SIMPLE, + keychain_simple_first_creds, + NULL, + keychain_simple_save_creds +}; + +/* Get cached encrypted credentials from the ssl client cert password + provider's cache. */ +static svn_error_t * +keychain_ssl_client_cert_pw_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__ssl_client_cert_pw_cache_get(credentials, + iter_baton, provider_baton, + parameters, realmstring, + keychain_password_get, + SVN_AUTH__KEYCHAIN_PASSWORD_TYPE, + pool); +} + +/* Save encrypted credentials to the ssl client cert password provider's + cache. */ +static svn_error_t * +keychain_ssl_client_cert_pw_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__ssl_client_cert_pw_cache_set(saved, credentials, + provider_baton, parameters, + realmstring, + keychain_password_set, + SVN_AUTH__KEYCHAIN_PASSWORD_TYPE, + pool); +} + +static const svn_auth_provider_t keychain_ssl_client_cert_pw_provider = { + SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, + keychain_ssl_client_cert_pw_first_creds, + NULL, + keychain_ssl_client_cert_pw_save_creds +}; + + +/* Public API */ +void +svn_auth_get_keychain_simple_provider(svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &keychain_simple_provider; + *provider = po; +} + +void +svn_auth_get_keychain_ssl_client_cert_pw_provider + (svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &keychain_ssl_client_cert_pw_provider; + *provider = po; +} +#endif /* SVN_HAVE_KEYCHAIN_SERVICES */ diff --git a/subversion/libsvn_subr/magic.c b/subversion/libsvn_subr/magic.c new file mode 100644 index 000000000000..812a2636e09f --- /dev/null +++ b/subversion/libsvn_subr/magic.c @@ -0,0 +1,161 @@ +/* + * magic.c: wrappers around libmagic + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + +/*** Includes. ***/ + +#include <apr_lib.h> +#include <apr_file_info.h> + +#include "svn_io.h" +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_error.h" + +#include "svn_private_config.h" + +#include "private/svn_magic.h" + +#ifdef SVN_HAVE_LIBMAGIC +#include <magic.h> +#endif + +struct svn_magic__cookie_t { +#ifdef SVN_HAVE_LIBMAGIC + magic_t magic; +#else + char dummy; +#endif +}; + +#ifdef SVN_HAVE_LIBMAGIC +/* Close the magic database. */ +static apr_status_t +close_magic_cookie(void *baton) +{ + svn_magic__cookie_t *mc = (svn_magic__cookie_t*)baton; + magic_close(mc->magic); + return APR_SUCCESS; +} +#endif + +void +svn_magic__init(svn_magic__cookie_t **magic_cookie, + apr_pool_t *result_pool) +{ + + svn_magic__cookie_t *mc = NULL; + +#ifdef SVN_HAVE_LIBMAGIC + mc = apr_palloc(result_pool, sizeof(*mc)); + + /* Initialise libmagic. */ +#ifndef MAGIC_MIME_TYPE + /* Some old versions of libmagic don't support MAGIC_MIME_TYPE. + * We can use MAGIC_MIME instead. It returns more than we need + * but we can work around that (see below). */ + mc->magic = magic_open(MAGIC_MIME | MAGIC_ERROR); +#else + mc->magic = magic_open(MAGIC_MIME_TYPE | MAGIC_ERROR); +#endif + if (mc->magic) + { + /* This loads the default magic database. + * Point the MAGIC environment variable at your favourite .mgc + * file to load a non-default database. */ + if (magic_load(mc->magic, NULL) == -1) + { + magic_close(mc->magic); + mc = NULL; + } + else + apr_pool_cleanup_register(result_pool, mc, close_magic_cookie, + apr_pool_cleanup_null); + } +#endif + + *magic_cookie = mc; +} + +svn_error_t * +svn_magic__detect_binary_mimetype(const char **mimetype, + const char *local_abspath, + svn_magic__cookie_t *magic_cookie, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *magic_mimetype = NULL; +#ifdef SVN_HAVE_LIBMAGIC + apr_finfo_t finfo; + + /* Do not ask libmagic for the mime-types of empty files. + * This prevents mime-types like "application/x-empty" from making + * Subversion treat empty files as binary. */ + SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_SIZE, scratch_pool)); + if (finfo.size > 0) + { + magic_mimetype = magic_file(magic_cookie->magic, local_abspath); + if (magic_mimetype) + { + /* Only return binary mime-types. */ + if (strncmp(magic_mimetype, "text/", 5) == 0) + magic_mimetype = NULL; + else + { + svn_error_t *err; +#ifndef MAGIC_MIME_TYPE + char *p; + + /* Strip off trailing stuff like " charset=ascii". */ + p = strchr(magic_mimetype, ' '); + if (p) + *p = '\0'; +#endif + /* Make sure we got a valid mime type. */ + err = svn_mime_type_validate(magic_mimetype, scratch_pool); + if (err) + { + if (err->apr_err == SVN_ERR_BAD_MIME_TYPE) + { + svn_error_clear(err); + magic_mimetype = NULL; + } + else + return svn_error_trace(err); + } + else + { + /* The string is allocated from memory managed by libmagic + * so we must copy it to the result pool. */ + magic_mimetype = apr_pstrdup(result_pool, magic_mimetype); + } + } + } + } +#endif + + *mimetype = magic_mimetype; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/md5.c b/subversion/libsvn_subr/md5.c new file mode 100644 index 000000000000..a707a71729d8 --- /dev/null +++ b/subversion/libsvn_subr/md5.c @@ -0,0 +1,110 @@ +/* + * md5.c: checksum routines + * + * ==================================================================== + * 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_md5.h> +#include "md5.h" +#include "svn_md5.h" + + + +/* The MD5 digest for the empty string. */ +static const unsigned char svn_md5__empty_string_digest_array[] = { + 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, + 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e +}; + +const unsigned char * +svn_md5__empty_string_digest(void) +{ + return svn_md5__empty_string_digest_array; +} + + +const char * +svn_md5__digest_to_cstring_display(const unsigned char digest[], + apr_pool_t *pool) +{ + static const char *hex = "0123456789abcdef"; + char *str = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1); + int i; + + for (i = 0; i < APR_MD5_DIGESTSIZE; i++) + { + str[i*2] = hex[digest[i] >> 4]; + str[i*2+1] = hex[digest[i] & 0x0f]; + } + str[i*2] = '\0'; + + return str; +} + + +const char * +svn_md5__digest_to_cstring(const unsigned char digest[], apr_pool_t *pool) +{ + static const unsigned char zeros_digest[APR_MD5_DIGESTSIZE] = { 0 }; + + if (memcmp(digest, zeros_digest, APR_MD5_DIGESTSIZE) != 0) + return svn_md5__digest_to_cstring_display(digest, pool); + else + return NULL; +} + + +svn_boolean_t +svn_md5__digests_match(const unsigned char d1[], const unsigned char d2[]) +{ + static const unsigned char zeros[APR_MD5_DIGESTSIZE] = { 0 }; + + return ((memcmp(d1, zeros, APR_MD5_DIGESTSIZE) == 0) + || (memcmp(d2, zeros, APR_MD5_DIGESTSIZE) == 0) + || (memcmp(d1, d2, APR_MD5_DIGESTSIZE) == 0)); +} + +/* These are all deprecated, and just wrap the internal functions defined + above. */ +const unsigned char * +svn_md5_empty_string_digest(void) +{ + return svn_md5__empty_string_digest(); +} + +const char * +svn_md5_digest_to_cstring_display(const unsigned char digest[], + apr_pool_t *pool) +{ + return svn_md5__digest_to_cstring_display(digest, pool); +} + +const char * +svn_md5_digest_to_cstring(const unsigned char digest[], apr_pool_t *pool) +{ + return svn_md5__digest_to_cstring(digest, pool); +} + +svn_boolean_t +svn_md5_digests_match(const unsigned char d1[], const unsigned char d2[]) +{ + return svn_md5__digests_match(d1, d2); +} diff --git a/subversion/libsvn_subr/md5.h b/subversion/libsvn_subr/md5.h new file mode 100644 index 000000000000..0d83539a098a --- /dev/null +++ b/subversion/libsvn_subr/md5.h @@ -0,0 +1,71 @@ +/* + * md5.h: Converting and comparing MD5 checksums + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_MD5_H +#define SVN_LIBSVN_SUBR_MD5_H + +#include <apr_pools.h> + +#include "svn_types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/* The MD5 digest for the empty string. */ +const unsigned char * +svn_md5__empty_string_digest(void); + + +/* Return the hex representation of DIGEST, which must be + * APR_MD5_DIGESTSIZE bytes long, allocating the string in POOL. + */ +const char * +svn_md5__digest_to_cstring_display(const unsigned char digest[], + apr_pool_t *pool); + + +/* Return the hex representation of DIGEST, which must be + * APR_MD5_DIGESTSIZE bytes long, allocating the string in POOL. + * If DIGEST is all zeros, then return NULL. + */ +const char * +svn_md5__digest_to_cstring(const unsigned char digest[], + apr_pool_t *pool); + + +/** Compare digests D1 and D2, each APR_MD5_DIGESTSIZE bytes long. + * If neither is all zeros, and they do not match, then return FALSE; + * else return TRUE. + */ +svn_boolean_t +svn_md5__digests_match(const unsigned char d1[], + const unsigned char d2[]); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_MD5_H */ diff --git a/subversion/libsvn_subr/mergeinfo.c b/subversion/libsvn_subr/mergeinfo.c new file mode 100644 index 000000000000..63496ae2252e --- /dev/null +++ b/subversion/libsvn_subr/mergeinfo.c @@ -0,0 +1,2631 @@ +/* + * mergeinfo.c: Mergeinfo parsing and handling + * + * ==================================================================== + * 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 <assert.h> +#include <ctype.h> + +#include "svn_path.h" +#include "svn_types.h" +#include "svn_ctype.h" +#include "svn_pools.h" +#include "svn_sorts.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_string.h" +#include "svn_mergeinfo.h" +#include "private/svn_fspath.h" +#include "private/svn_mergeinfo_private.h" +#include "private/svn_string_private.h" +#include "private/svn_subr_private.h" +#include "svn_private_config.h" +#include "svn_hash.h" +#include "private/svn_dep_compat.h" + +/* Attempt to combine two ranges, IN1 and IN2. If they are adjacent or + overlapping, and their inheritability allows them to be combined, put + the result in OUTPUT and return TRUE, otherwise return FALSE. + + CONSIDER_INHERITANCE determines how to account for the inheritability + of IN1 and IN2 when trying to combine ranges. If ranges with different + inheritability are combined (CONSIDER_INHERITANCE must be FALSE for this + to happen) the result is inheritable. If both ranges are inheritable the + result is inheritable. Only and if both ranges are non-inheritable is + the result is non-inheritable. + + Range overlapping detection algorithm from + http://c2.com/cgi-bin/wiki/fullSearch?TestIfDateRangesOverlap +*/ +static svn_boolean_t +combine_ranges(svn_merge_range_t *output, + const svn_merge_range_t *in1, + const svn_merge_range_t *in2, + svn_boolean_t consider_inheritance) +{ + if (in1->start <= in2->end && in2->start <= in1->end) + { + if (!consider_inheritance + || (consider_inheritance + && (in1->inheritable == in2->inheritable))) + { + output->start = MIN(in1->start, in2->start); + output->end = MAX(in1->end, in2->end); + output->inheritable = (in1->inheritable || in2->inheritable); + return TRUE; + } + } + return FALSE; +} + +/* pathname -> PATHNAME */ +static svn_error_t * +parse_pathname(const char **input, + const char *end, + const char **pathname, + apr_pool_t *pool) +{ + const char *curr = *input; + const char *last_colon = NULL; + + /* A pathname may contain colons, so find the last colon before END + or newline. We'll consider this the divider between the pathname + and the revisionlist. */ + while (curr < end && *curr != '\n') + { + if (*curr == ':') + last_colon = curr; + curr++; + } + + if (!last_colon) + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Pathname not terminated by ':'")); + if (last_colon == *input) + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("No pathname preceding ':'")); + + /* Tolerate relative repository paths, but convert them to absolute. + ### Efficiency? 1 string duplication here, 2 in canonicalize. */ + *pathname = svn_fspath__canonicalize(apr_pstrndup(pool, *input, + last_colon - *input), + pool); + + *input = last_colon; + + return SVN_NO_ERROR; +} + +/* Return TRUE iff (svn_merge_range_t *) RANGE describes a valid, forward + * revision range. + * + * Note: The smallest valid value of RANGE->start is 0 because it is an + * exclusive endpoint, being one less than the revision number of the first + * change described by the range, and the oldest possible change is "r1" as + * there cannot be a change "r0". */ +#define IS_VALID_FORWARD_RANGE(range) \ + (SVN_IS_VALID_REVNUM((range)->start) && ((range)->start < (range)->end)) + +/* Ways in which two svn_merge_range_t can intersect or adjoin, if at all. */ +typedef enum intersection_type_t +{ + /* Ranges don't intersect and don't adjoin. */ + svn__no_intersection, + + /* Ranges are equal. */ + svn__equal_intersection, + + /* Ranges adjoin but don't overlap. */ + svn__adjoining_intersection, + + /* Ranges overlap but neither is a subset of the other. */ + svn__overlapping_intersection, + + /* One range is a proper subset of the other. */ + svn__proper_subset_intersection +} intersection_type_t; + +/* Given ranges R1 and R2, both of which must be forward merge ranges, + set *INTERSECTION_TYPE to describe how the ranges intersect, if they + do at all. The inheritance type of the ranges is not considered. */ +static svn_error_t * +get_type_of_intersection(const svn_merge_range_t *r1, + const svn_merge_range_t *r2, + intersection_type_t *intersection_type) +{ + SVN_ERR_ASSERT(r1); + SVN_ERR_ASSERT(r2); + SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(r1)); + SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(r2)); + + if (!(r1->start <= r2->end && r2->start <= r1->end)) + *intersection_type = svn__no_intersection; + else if (r1->start == r2->start && r1->end == r2->end) + *intersection_type = svn__equal_intersection; + else if (r1->end == r2->start || r2->end == r1->start) + *intersection_type = svn__adjoining_intersection; + else if (r1->start <= r2->start && r1->end >= r2->end) + *intersection_type = svn__proper_subset_intersection; + else if (r2->start <= r1->start && r2->end >= r1->end) + *intersection_type = svn__proper_subset_intersection; + else + *intersection_type = svn__overlapping_intersection; + + return SVN_NO_ERROR; +} + +/* Modify or extend RANGELIST (a list of merge ranges) to incorporate + NEW_RANGE. RANGELIST is a "rangelist" as defined in svn_mergeinfo.h. + + OVERVIEW + + Determine the minimal set of non-overlapping merge ranges required to + represent the combination of RANGELIST and NEW_RANGE. The result depends + on whether and how NEW_RANGE overlaps any merge range[*] in RANGELIST, + and also on any differences in the inheritability of each range, + according to the rules described below. Modify RANGELIST to represent + this result, by adjusting the last range in it and/or appending one or + two more ranges. + + ([*] Due to the simplifying assumption below, only the last range in + RANGELIST is considered.) + + DETAILS + + If RANGELIST is not empty assume NEW_RANGE does not intersect with any + range before the last one in RANGELIST. + + If RANGELIST is empty or NEW_RANGE does not intersect with the lastrange + in RANGELIST, then append a copy of NEW_RANGE, allocated in RESULT_POOL, + to RANGELIST. + + If NEW_RANGE intersects with the last range in RANGELIST then combine + these two ranges as described below: + + If the intersecting ranges have the same inheritability then simply + combine the ranges in place. Otherwise, if the ranges intersect but + differ in inheritability, then merge the ranges as dictated by + CONSIDER_INHERITANCE: + + If CONSIDER_INHERITANCE is false then intersecting ranges are combined + into a single range. The inheritability of the resulting range is + non-inheritable *only* if both ranges are non-inheritable, otherwise the + combined range is inheritable, e.g.: + + Last range in NEW_RANGE RESULTING RANGES + RANGELIST + ------------- --------- ---------------- + 4-10* 6-13 4-13 + 4-10 6-13* 4-13 + 4-10* 6-13* 4-13* + + If CONSIDER_INHERITANCE is true, then only the intersection between the + two ranges is combined, with the inheritability of the resulting range + non-inheritable only if both ranges were non-inheritable. The + non-intersecting portions are added as separate ranges allocated in + RESULT_POOL, e.g.: + + Last range in NEW_RANGE RESULTING RANGES + RANGELIST + ------------- --------- ---------------- + 4-10* 6 4-5*, 6, 7-10* + 4-10* 6-12 4-5*, 6-12 + + Note that the standard rules for rangelists still apply and overlapping + ranges are not allowed. So if the above would result in overlapping + ranges of the same inheritance, the overlapping ranges are merged into a + single range, e.g.: + + Last range in NEW_RANGE RESULTING RANGES + RANGELIST + ------------- --------- ---------------- + 4-10 6* 4-10 (Not 4-5, 6, 7-10) + + When replacing the last range in RANGELIST, either allocate a new range in + RESULT_POOL or modify the existing range in place. Any new ranges added + to RANGELIST are allocated in RESULT_POOL. +*/ +static svn_error_t * +combine_with_lastrange(const svn_merge_range_t *new_range, + svn_rangelist_t *rangelist, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool) +{ + svn_merge_range_t *lastrange; + svn_merge_range_t combined_range; + + /* We don't accept a NULL RANGELIST. */ + SVN_ERR_ASSERT(rangelist); + + if (rangelist->nelts > 0) + lastrange = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1, svn_merge_range_t *); + else + lastrange = NULL; + + if (!lastrange) + { + /* No *LASTRANGE so push NEW_RANGE onto RANGELIST and we are done. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + } + else if (!consider_inheritance) + { + /* We are not considering inheritance so we can merge intersecting + ranges of different inheritability. Of course if the ranges + don't intersect at all we simply push NEW_RANGE only RANGELIST. */ + if (combine_ranges(&combined_range, lastrange, new_range, FALSE)) + { + *lastrange = combined_range; + } + else + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + } + } + else /* Considering inheritance */ + { + if (combine_ranges(&combined_range, lastrange, new_range, TRUE)) + { + /* Even when considering inheritance two intersection ranges + of the same inheritability can simply be combined. */ + *lastrange = combined_range; + } + else + { + /* If we are here then the ranges either don't intersect or do + intersect but have differing inheritability. Check for the + first case as that is easy to handle. */ + intersection_type_t intersection_type; + svn_boolean_t sorted = FALSE; + + SVN_ERR(get_type_of_intersection(new_range, lastrange, + &intersection_type)); + + switch (intersection_type) + { + case svn__no_intersection: + /* NEW_RANGE and *LASTRANGE *really* don't intersect so + just push NEW_RANGE only RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + sorted = (svn_sort_compare_ranges(&lastrange, + &new_range) < 0); + break; + + case svn__equal_intersection: + /* They range are equal so all we do is force the + inheritability of lastrange to true. */ + lastrange->inheritable = TRUE; + sorted = TRUE; + break; + + case svn__adjoining_intersection: + /* They adjoin but don't overlap so just push NEW_RANGE + onto RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + sorted = (svn_sort_compare_ranges(&lastrange, + &new_range) < 0); + break; + + case svn__overlapping_intersection: + /* They ranges overlap but neither is a proper subset of + the other. We'll end up pusing two new ranges onto + RANGELIST, the intersecting part and the part unique to + NEW_RANGE.*/ + { + svn_merge_range_t *r1 = svn_merge_range_dup(lastrange, + result_pool); + svn_merge_range_t *r2 = svn_merge_range_dup(new_range, + result_pool); + + /* Pop off *LASTRANGE to make our manipulations + easier. */ + apr_array_pop(rangelist); + + /* Ensure R1 is the older range. */ + if (r2->start < r1->start) + { + /* Swap R1 and R2. */ + *r2 = *r1; + *r1 = *new_range; + } + + /* Absorb the intersecting ranges into the + inheritable range. */ + if (r1->inheritable) + r2->start = r1->end; + else + r1->end = r2->start; + + /* Push everything back onto RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r1; + sorted = (svn_sort_compare_ranges(&lastrange, + &r1) < 0); + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r2; + if (sorted) + sorted = (svn_sort_compare_ranges(&r1, &r2) < 0); + break; + } + + default: /* svn__proper_subset_intersection */ + { + /* One range is a proper subset of the other. */ + svn_merge_range_t *r1 = svn_merge_range_dup(lastrange, + result_pool); + svn_merge_range_t *r2 = svn_merge_range_dup(new_range, + result_pool); + svn_merge_range_t *r3 = NULL; + + /* Pop off *LASTRANGE to make our manipulations + easier. */ + apr_array_pop(rangelist); + + /* Ensure R1 is the superset. */ + if (r2->start < r1->start || r2->end > r1->end) + { + /* Swap R1 and R2. */ + *r2 = *r1; + *r1 = *new_range; + } + + if (r1->inheritable) + { + /* The simple case: The superset is inheritable, so + just combine r1 and r2. */ + r1->start = MIN(r1->start, r2->start); + r1->end = MAX(r1->end, r2->end); + r2 = NULL; + } + else if (r1->start == r2->start) + { + svn_revnum_t tmp_revnum; + + /* *LASTRANGE and NEW_RANGE share an end point. */ + tmp_revnum = r1->end; + r1->end = r2->end; + r2->inheritable = r1->inheritable; + r1->inheritable = TRUE; + r2->start = r1->end; + r2->end = tmp_revnum; + } + else if (r1->end == r2->end) + { + /* *LASTRANGE and NEW_RANGE share an end point. */ + r1->end = r2->start; + r2->inheritable = TRUE; + } + else + { + /* NEW_RANGE and *LASTRANGE share neither start + nor end points. */ + r3 = apr_pcalloc(result_pool, sizeof(*r3)); + r3->start = r2->end; + r3->end = r1->end; + r3->inheritable = r1->inheritable; + r2->inheritable = TRUE; + r1->end = r2->start; + } + + /* Push everything back onto RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r1; + sorted = (svn_sort_compare_ranges(&lastrange, &r1) < 0); + if (r2) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r2; + if (sorted) + sorted = (svn_sort_compare_ranges(&r1, &r2) < 0); + } + if (r3) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r3; + if (sorted) + { + if (r2) + sorted = (svn_sort_compare_ranges(&r2, + &r3) < 0); + else + sorted = (svn_sort_compare_ranges(&r1, + &r3) < 0); + } + } + break; + } + } + + /* Some of the above cases might have put *RANGELIST out of + order, so re-sort.*/ + if (!sorted) + qsort(rangelist->elts, rangelist->nelts, rangelist->elt_size, + svn_sort_compare_ranges); + } + } + + return SVN_NO_ERROR; +} + +/* Convert a single svn_merge_range_t *RANGE back into a string. */ +static char * +range_to_string(const svn_merge_range_t *range, + apr_pool_t *pool) +{ + const char *mark + = range->inheritable ? "" : SVN_MERGEINFO_NONINHERITABLE_STR; + + if (range->start == range->end - 1) + return apr_psprintf(pool, "%ld%s", range->end, mark); + else if (range->start - 1 == range->end) + return apr_psprintf(pool, "-%ld%s", range->start, mark); + else if (range->start < range->end) + return apr_psprintf(pool, "%ld-%ld%s", range->start + 1, range->end, mark); + else + return apr_psprintf(pool, "%ld-%ld%s", range->start, range->end + 1, mark); +} + +/* Helper for svn_mergeinfo_parse() + Append revision ranges onto the array RANGELIST to represent the range + descriptions found in the string *INPUT. Read only as far as a newline + or the position END, whichever comes first. Set *INPUT to the position + after the last character of INPUT that was used. + + revisionlist -> (revisionelement)(COMMA revisionelement)* + revisionrange -> REVISION "-" REVISION("*") + revisionelement -> revisionrange | REVISION("*") +*/ +static svn_error_t * +parse_rangelist(const char **input, const char *end, + svn_rangelist_t *rangelist, + apr_pool_t *pool) +{ + const char *curr = *input; + + /* Eat any leading horizontal white-space before the rangelist. */ + while (curr < end && *curr != '\n' && isspace(*curr)) + curr++; + + if (*curr == '\n' || curr == end) + { + /* Empty range list. */ + *input = curr; + return SVN_NO_ERROR; + } + + while (curr < end && *curr != '\n') + { + /* Parse individual revisions or revision ranges. */ + svn_merge_range_t *mrange = apr_pcalloc(pool, sizeof(*mrange)); + svn_revnum_t firstrev; + + SVN_ERR(svn_revnum_parse(&firstrev, curr, &curr)); + if (*curr != '-' && *curr != '\n' && *curr != ',' && *curr != '*' + && curr != end) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid character '%c' found in revision " + "list"), *curr); + mrange->start = firstrev - 1; + mrange->end = firstrev; + mrange->inheritable = TRUE; + + if (firstrev == 0) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid revision number '0' found in " + "range list")); + + if (*curr == '-') + { + svn_revnum_t secondrev; + + curr++; + SVN_ERR(svn_revnum_parse(&secondrev, curr, &curr)); + if (firstrev > secondrev) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Unable to parse reversed revision " + "range '%ld-%ld'"), + firstrev, secondrev); + else if (firstrev == secondrev) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Unable to parse revision range " + "'%ld-%ld' with same start and end " + "revisions"), firstrev, secondrev); + mrange->end = secondrev; + } + + if (*curr == '\n' || curr == end) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange; + *input = curr; + return SVN_NO_ERROR; + } + else if (*curr == ',') + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange; + curr++; + } + else if (*curr == '*') + { + mrange->inheritable = FALSE; + curr++; + if (*curr == ',' || *curr == '\n' || curr == end) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange; + if (*curr == ',') + { + curr++; + } + else + { + *input = curr; + return SVN_NO_ERROR; + } + } + else + { + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid character '%c' found in " + "range list"), *curr); + } + } + else + { + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid character '%c' found in " + "range list"), *curr); + } + + } + if (*curr != '\n') + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Range list parsing ended before hitting " + "newline")); + *input = curr; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_rangelist__parse(svn_rangelist_t **rangelist, + const char *str, + apr_pool_t *result_pool) +{ + const char *s = str; + + *rangelist = apr_array_make(result_pool, 1, sizeof(svn_merge_range_t *)); + SVN_ERR(parse_rangelist(&s, s + strlen(s), *rangelist, result_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_rangelist__combine_adjacent_ranges(svn_rangelist_t *rangelist, + apr_pool_t *scratch_pool) +{ + int i; + svn_merge_range_t *range, *lastrange; + + lastrange = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); + + for (i = 1; i < rangelist->nelts; i++) + { + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + if (lastrange->start <= range->end + && range->start <= lastrange->end) + { + /* The ranges are adjacent or intersect. */ + + /* svn_mergeinfo_parse promises to combine overlapping + ranges as long as their inheritability is the same. */ + if (range->start < lastrange->end + && range->inheritable != lastrange->inheritable) + { + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Unable to parse overlapping " + "revision ranges '%s' and '%s' " + "with different inheritance " + "types"), + range_to_string(lastrange, + scratch_pool), + range_to_string(range, + scratch_pool)); + } + + /* Combine overlapping or adjacent ranges with the + same inheritability. */ + if (lastrange->inheritable == range->inheritable) + { + lastrange->end = MAX(range->end, lastrange->end); + svn_sort__array_delete(rangelist, i, 1); + i--; + } + } + lastrange = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + } + + return SVN_NO_ERROR; +} + +/* revisionline -> PATHNAME COLON revisionlist */ +static svn_error_t * +parse_revision_line(const char **input, const char *end, svn_mergeinfo_t hash, + apr_pool_t *scratch_pool) +{ + const char *pathname = ""; + apr_ssize_t klen; + svn_rangelist_t *existing_rangelist; + svn_rangelist_t *rangelist = apr_array_make(scratch_pool, 1, + sizeof(svn_merge_range_t *)); + + SVN_ERR(parse_pathname(input, end, &pathname, scratch_pool)); + + if (*(*input) != ':') + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Pathname not terminated by ':'")); + + *input = *input + 1; + + SVN_ERR(parse_rangelist(input, end, rangelist, scratch_pool)); + + if (rangelist->nelts == 0) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Mergeinfo for '%s' maps to an " + "empty revision range"), pathname); + if (*input != end && *(*input) != '\n') + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Could not find end of line in range list line " + "in '%s'"), *input); + + if (*input != end) + *input = *input + 1; + + /* Sort the rangelist, combine adjacent ranges into single ranges, + and make sure there are no overlapping ranges. */ + if (rangelist->nelts > 1) + { + qsort(rangelist->elts, rangelist->nelts, rangelist->elt_size, + svn_sort_compare_ranges); + + SVN_ERR(svn_rangelist__combine_adjacent_ranges(rangelist, scratch_pool)); + } + + /* Handle any funky mergeinfo with relative merge source paths that + might exist due to issue #3547. It's possible that this issue allowed + the creation of mergeinfo with path keys that differ only by a + leading slash, e.g. "trunk:4033\n/trunk:4039-4995". In the event + we encounter this we merge the rangelists together under a single + absolute path key. */ + klen = strlen(pathname); + existing_rangelist = apr_hash_get(hash, pathname, klen); + if (existing_rangelist) + SVN_ERR(svn_rangelist_merge2(rangelist, existing_rangelist, + scratch_pool, scratch_pool)); + + apr_hash_set(hash, apr_pstrmemdup(apr_hash_pool_get(hash), pathname, klen), + klen, svn_rangelist_dup(rangelist, apr_hash_pool_get(hash))); + + return SVN_NO_ERROR; +} + +/* top -> revisionline (NEWLINE revisionline)* */ +static svn_error_t * +parse_top(const char **input, const char *end, svn_mergeinfo_t hash, + apr_pool_t *pool) +{ + apr_pool_t *iterpool = svn_pool_create(pool); + + while (*input < end) + { + svn_pool_clear(iterpool); + SVN_ERR(parse_revision_line(input, end, hash, iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_parse(svn_mergeinfo_t *mergeinfo, + const char *input, + apr_pool_t *pool) +{ + svn_error_t *err; + + *mergeinfo = svn_hash__make(pool); + err = parse_top(&input, input + strlen(input), *mergeinfo, pool); + + /* Always return SVN_ERR_MERGEINFO_PARSE_ERROR as the topmost error. */ + if (err && err->apr_err != SVN_ERR_MERGEINFO_PARSE_ERROR) + err = svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, err, + _("Could not parse mergeinfo string '%s'"), + input); + return err; +} + +/* Cleanup after svn_rangelist_merge2 when it modifies the ending range of + a single rangelist element in-place. + + If *RANGE_INDEX is not a valid element in RANGELIST do nothing. Otherwise + ensure that RANGELIST[*RANGE_INDEX]->END does not adjoin or overlap any + subsequent ranges in RANGELIST. + + If overlap is found, then remove, modify, and/or add elements to RANGELIST + as per the invariants for rangelists documented in svn_mergeinfo.h. If + RANGELIST[*RANGE_INDEX]->END adjoins a subsequent element then combine the + elements if their inheritability permits -- The inheritance of intersecting + and adjoining ranges is handled as per svn_mergeinfo_merge2. Upon return + set *RANGE_INDEX to the index of the youngest element modified, added, or + adjoined to RANGELIST[*RANGE_INDEX]. + + Note: Adjoining rangelist elements are those where the end rev of the older + element is equal to the start rev of the younger element. + + Any new elements inserted into RANGELIST are allocated in RESULT_POOL.*/ +static void +adjust_remaining_ranges(svn_rangelist_t *rangelist, + int *range_index, + apr_pool_t *result_pool) +{ + int i; + int starting_index; + int elements_to_delete = 0; + svn_merge_range_t *modified_range; + + if (*range_index >= rangelist->nelts) + return; + + starting_index = *range_index + 1; + modified_range = APR_ARRAY_IDX(rangelist, *range_index, svn_merge_range_t *); + + for (i = *range_index + 1; i < rangelist->nelts; i++) + { + svn_merge_range_t *next_range = APR_ARRAY_IDX(rangelist, i, + svn_merge_range_t *); + + /* If MODIFIED_RANGE doesn't adjoin or overlap the next range in + RANGELIST then we are finished. */ + if (modified_range->end < next_range->start) + break; + + /* Does MODIFIED_RANGE adjoin NEXT_RANGE? */ + if (modified_range->end == next_range->start) + { + if (modified_range->inheritable == next_range->inheritable) + { + /* Combine adjoining ranges with the same inheritability. */ + modified_range->end = next_range->end; + elements_to_delete++; + } + else + { + /* Cannot join because inheritance differs. */ + (*range_index)++; + } + break; + } + + /* Alright, we know MODIFIED_RANGE overlaps NEXT_RANGE, but how? */ + if (modified_range->end > next_range->end) + { + /* NEXT_RANGE is a proper subset of MODIFIED_RANGE and the two + don't share the same end range. */ + if (modified_range->inheritable + || (modified_range->inheritable == next_range->inheritable)) + { + /* MODIFIED_RANGE absorbs NEXT_RANGE. */ + elements_to_delete++; + } + else + { + /* NEXT_RANGE is a proper subset MODIFIED_RANGE but + MODIFIED_RANGE is non-inheritable and NEXT_RANGE is + inheritable. This means MODIFIED_RANGE is truncated, + NEXT_RANGE remains, and the portion of MODIFIED_RANGE + younger than NEXT_RANGE is added as a separate range: + ______________________________________________ + | | + M MODIFIED_RANGE N + | (!inhertiable) | + |______________________________________________| + | | + O NEXT_RANGE P + | (inheritable)| + |______________| + | + V + _______________________________________________ + | | | | + M MODIFIED_RANGE O NEXT_RANGE P NEW_RANGE N + | (!inhertiable) | (inheritable)| (!inheritable)| + |________________|______________|_______________| + */ + svn_merge_range_t *new_modified_range = + apr_palloc(result_pool, sizeof(*new_modified_range)); + new_modified_range->start = next_range->end; + new_modified_range->end = modified_range->end; + new_modified_range->inheritable = FALSE; + modified_range->end = next_range->start; + (*range_index)+=2; + svn_sort__array_insert(&new_modified_range, rangelist, + *range_index); + /* Recurse with the new range. */ + adjust_remaining_ranges(rangelist, range_index, result_pool); + break; + } + } + else if (modified_range->end == next_range->end) + { + /* NEXT_RANGE is a proper subset MODIFIED_RANGE and share + the same end range. */ + if (modified_range->inheritable + || (modified_range->inheritable == next_range->inheritable)) + { + /* MODIFIED_RANGE absorbs NEXT_RANGE. */ + elements_to_delete++; + } + else + { + /* The intersection between MODIFIED_RANGE and NEXT_RANGE is + absorbed by the latter. */ + modified_range->end = next_range->start; + (*range_index)++; + } + break; + } + else + { + /* NEXT_RANGE and MODIFIED_RANGE intersect but NEXT_RANGE is not + a proper subset of MODIFIED_RANGE, nor do the two share the + same end revision, i.e. they overlap. */ + if (modified_range->inheritable == next_range->inheritable) + { + /* Combine overlapping ranges with the same inheritability. */ + modified_range->end = next_range->end; + elements_to_delete++; + } + else if (modified_range->inheritable) + { + /* MODIFIED_RANGE absorbs the portion of NEXT_RANGE it overlaps + and NEXT_RANGE is truncated. */ + next_range->start = modified_range->end; + (*range_index)++; + } + else + { + /* NEXT_RANGE absorbs the portion of MODIFIED_RANGE it overlaps + and MODIFIED_RANGE is truncated. */ + modified_range->end = next_range->start; + (*range_index)++; + } + break; + } + } + + if (elements_to_delete) + svn_sort__array_delete(rangelist, starting_index, elements_to_delete); +} + +svn_error_t * +svn_rangelist_merge2(svn_rangelist_t *rangelist, + const svn_rangelist_t *changes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i = 0; + int j = 0; + + /* We may modify CHANGES, so make a copy in SCRATCH_POOL. */ + changes = svn_rangelist_dup(changes, scratch_pool); + + while (i < rangelist->nelts && j < changes->nelts) + { + svn_merge_range_t *range = + APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + svn_merge_range_t *change = + APR_ARRAY_IDX(changes, j, svn_merge_range_t *); + int res = svn_sort_compare_ranges(&range, &change); + + if (res == 0) + { + /* Only when merging two non-inheritable ranges is the result also + non-inheritable. In all other cases ensure an inheritiable + result. */ + if (range->inheritable || change->inheritable) + range->inheritable = TRUE; + i++; + j++; + } + else if (res < 0) /* CHANGE is younger than RANGE */ + { + if (range->end < change->start) + { + /* RANGE is older than CHANGE and the two do not + adjoin or overlap */ + i++; + } + else if (range->end == change->start) + { + /* RANGE and CHANGE adjoin */ + if (range->inheritable == change->inheritable) + { + /* RANGE and CHANGE have the same inheritability so + RANGE expands to absord CHANGE. */ + range->end = change->end; + adjust_remaining_ranges(rangelist, &i, result_pool); + j++; + } + else + { + /* RANGE and CHANGE adjoin, but have different + inheritability. Since RANGE is older, just + move on to the next RANGE. */ + i++; + } + } + else + { + /* RANGE and CHANGE overlap, but how? */ + if ((range->inheritable == change->inheritable) + || range->inheritable) + { + /* If CHANGE is a proper subset of RANGE, it absorbs RANGE + with no adjustment otherwise only the intersection is + absorbed and CHANGE is truncated. */ + if (range->end >= change->end) + j++; + else + change->start = range->end; + } + else + { + /* RANGE is non-inheritable and CHANGE is inheritable. */ + if (range->start < change->start) + { + /* CHANGE absorbs intersection with RANGE and RANGE + is truncated. */ + svn_merge_range_t *range_copy = + svn_merge_range_dup(range, result_pool); + range_copy->end = change->start; + range->start = change->start; + svn_sort__array_insert(&range_copy, rangelist, i++); + } + else + { + /* CHANGE and RANGE share the same start rev, but + RANGE is considered older because its end rev + is older. */ + range->inheritable = TRUE; + change->start = range->end; + } + } + } + } + else /* res > 0, CHANGE is older than RANGE */ + { + if (change->end < range->start) + { + /* CHANGE is older than RANGE and the two do not + adjoin or overlap, so insert a copy of CHANGE + into RANGELIST. */ + svn_merge_range_t *change_copy = + svn_merge_range_dup(change, result_pool); + svn_sort__array_insert(&change_copy, rangelist, i++); + j++; + } + else if (change->end == range->start) + { + /* RANGE and CHANGE adjoin */ + if (range->inheritable == change->inheritable) + { + /* RANGE and CHANGE have the same inheritability so we + can simply combine the two in place. */ + range->start = change->start; + j++; + } + else + { + /* RANGE and CHANGE have different inheritability so insert + a copy of CHANGE into RANGELIST. */ + svn_merge_range_t *change_copy = + svn_merge_range_dup(change, result_pool); + svn_sort__array_insert(&change_copy, rangelist, i); + j++; + } + } + else + { + /* RANGE and CHANGE overlap. */ + if (range->inheritable == change->inheritable) + { + /* RANGE and CHANGE have the same inheritability so we + can simply combine the two in place... */ + range->start = change->start; + if (range->end < change->end) + { + /* ...but if RANGE is expanded ensure that we don't + violate any rangelist invariants. */ + range->end = change->end; + adjust_remaining_ranges(rangelist, &i, result_pool); + } + j++; + } + else if (range->inheritable) + { + if (change->start < range->start) + { + /* RANGE is inheritable so absorbs any part of CHANGE + it overlaps. CHANGE is truncated and the remainder + inserted into RANGELIST. */ + svn_merge_range_t *change_copy = + svn_merge_range_dup(change, result_pool); + change_copy->end = range->start; + change->start = range->start; + svn_sort__array_insert(&change_copy, rangelist, i++); + } + else + { + /* CHANGE and RANGE share the same start rev, but + CHANGE is considered older because CHANGE->END is + older than RANGE->END. */ + j++; + } + } + else + { + /* RANGE is non-inheritable and CHANGE is inheritable. */ + if (change->start < range->start) + { + if (change->end == range->end) + { + /* RANGE is a proper subset of CHANGE and share the + same end revision, so set RANGE equal to CHANGE. */ + range->start = change->start; + range->inheritable = TRUE; + j++; + } + else if (change->end > range->end) + { + /* RANGE is a proper subset of CHANGE and CHANGE has + a younger end revision, so set RANGE equal to its + intersection with CHANGE and truncate CHANGE. */ + range->start = change->start; + range->inheritable = TRUE; + change->start = range->end; + } + else + { + /* CHANGE and RANGE overlap. Set RANGE equal to its + intersection with CHANGE and take the remainder + of RANGE and insert it into RANGELIST. */ + svn_merge_range_t *range_copy = + svn_merge_range_dup(range, result_pool); + range_copy->start = change->end; + range->start = change->start; + range->end = change->end; + range->inheritable = TRUE; + svn_sort__array_insert(&range_copy, rangelist, ++i); + j++; + } + } + else + { + /* CHANGE and RANGE share the same start rev, but + CHANGE is considered older because its end rev + is older. + + Insert the intersection of RANGE and CHANGE into + RANGELIST and then set RANGE to the non-intersecting + portion of RANGE. */ + svn_merge_range_t *range_copy = + svn_merge_range_dup(range, result_pool); + range_copy->end = change->end; + range_copy->inheritable = TRUE; + range->start = change->end; + svn_sort__array_insert(&range_copy, rangelist, i++); + j++; + } + } + } + } + } + + /* Copy any remaining elements in CHANGES into RANGELIST. */ + for (; j < (changes)->nelts; j++) + { + svn_merge_range_t *change = + APR_ARRAY_IDX(changes, j, svn_merge_range_t *); + svn_merge_range_t *change_copy = svn_merge_range_dup(change, + result_pool); + svn_sort__array_insert(&change_copy, rangelist, rangelist->nelts); + } + + return SVN_NO_ERROR; +} + +/* Return TRUE iff the forward revision ranges FIRST and SECOND overlap and + * (if CONSIDER_INHERITANCE is TRUE) have the same inheritability. */ +static svn_boolean_t +range_intersect(const svn_merge_range_t *first, const svn_merge_range_t *second, + svn_boolean_t consider_inheritance) +{ + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first)); + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second)); + + return (first->start + 1 <= second->end) + && (second->start + 1 <= first->end) + && (!consider_inheritance + || (!(first->inheritable) == !(second->inheritable))); +} + +/* Return TRUE iff the forward revision range FIRST wholly contains the + * forward revision range SECOND and (if CONSIDER_INHERITANCE is TRUE) has + * the same inheritability. */ +static svn_boolean_t +range_contains(const svn_merge_range_t *first, const svn_merge_range_t *second, + svn_boolean_t consider_inheritance) +{ + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first)); + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second)); + + return (first->start <= second->start) && (second->end <= first->end) + && (!consider_inheritance + || (!(first->inheritable) == !(second->inheritable))); +} + +/* Swap start and end fields of RANGE. */ +static void +range_swap_endpoints(svn_merge_range_t *range) +{ + svn_revnum_t swap = range->start; + range->start = range->end; + range->end = swap; +} + +svn_error_t * +svn_rangelist_reverse(svn_rangelist_t *rangelist, apr_pool_t *pool) +{ + int i; + + svn_sort__array_reverse(rangelist, pool); + + for (i = 0; i < rangelist->nelts; i++) + { + range_swap_endpoints(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *)); + } + + return SVN_NO_ERROR; +} + +void +svn_rangelist__set_inheritance(svn_rangelist_t *rangelist, + svn_boolean_t inheritable) +{ + if (rangelist) + { + int i; + svn_merge_range_t *range; + + for (i = 0; i < rangelist->nelts; i++) + { + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + range->inheritable = inheritable; + } + } + return; +} + +void +svn_mergeinfo__set_inheritance(svn_mergeinfo_t mergeinfo, + svn_boolean_t inheritable, + apr_pool_t *scratch_pool) +{ + if (mergeinfo) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist) + svn_rangelist__set_inheritance(rangelist, inheritable); + } + } + return; +} + +/* If DO_REMOVE is true, then remove any overlapping ranges described by + RANGELIST1 from RANGELIST2 and place the results in *OUTPUT. When + DO_REMOVE is true, RANGELIST1 is effectively the "eraser" and RANGELIST2 + the "whiteboard". + + If DO_REMOVE is false, then capture the intersection between RANGELIST1 + and RANGELIST2 and place the results in *OUTPUT. The ordering of + RANGELIST1 and RANGELIST2 doesn't matter when DO_REMOVE is false. + + If CONSIDER_INHERITANCE is true, then take the inheritance of the + ranges in RANGELIST1 and RANGELIST2 into account when comparing them + for intersection, see the doc string for svn_rangelist_intersect(). + + If CONSIDER_INHERITANCE is false, then ranges with differing inheritance + may intersect, but the resulting intersection is non-inheritable only + if both ranges were non-inheritable, e.g.: + + RANGELIST1 RANGELIST2 CONSIDER DO_REMOVE *OUTPUT + INHERITANCE + ---------- ------ ----------- --------- ------- + + 90-420* 1-100 TRUE FALSE Empty Rangelist + 90-420 1-100* TRUE FALSE Empty Rangelist + 90-420 1-100 TRUE FALSE 90-100 + 90-420* 1-100* TRUE FALSE 90-100* + + 90-420* 1-100 FALSE FALSE 90-100 + 90-420 1-100* FALSE FALSE 90-100 + 90-420 1-100 FALSE FALSE 90-100 + 90-420* 1-100* FALSE FALSE 90-100* + + Allocate the contents of *OUTPUT in POOL. */ +static svn_error_t * +rangelist_intersect_or_remove(svn_rangelist_t **output, + const svn_rangelist_t *rangelist1, + const svn_rangelist_t *rangelist2, + svn_boolean_t do_remove, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + int i1, i2, lasti2; + svn_merge_range_t working_elt2; + + *output = apr_array_make(pool, 1, sizeof(svn_merge_range_t *)); + + i1 = 0; + i2 = 0; + lasti2 = -1; /* Initialized to a value that "i2" will never be. */ + + while (i1 < rangelist1->nelts && i2 < rangelist2->nelts) + { + svn_merge_range_t *elt1, *elt2; + + elt1 = APR_ARRAY_IDX(rangelist1, i1, svn_merge_range_t *); + + /* Instead of making a copy of the entire array of rangelist2 + elements, we just keep a copy of the current rangelist2 element + that needs to be used, and modify our copy if necessary. */ + if (i2 != lasti2) + { + working_elt2 = + *(APR_ARRAY_IDX(rangelist2, i2, svn_merge_range_t *)); + lasti2 = i2; + } + + elt2 = &working_elt2; + + /* If the rangelist2 range is contained completely in the + rangelist1, we increment the rangelist2. + If the ranges intersect, and match exactly, we increment both + rangelist1 and rangelist2. + Otherwise, we have to generate a range for the left part of + the removal of rangelist1 from rangelist2, and possibly change + the rangelist2 to the remaining portion of the right part of + the removal, to test against. */ + if (range_contains(elt1, elt2, consider_inheritance)) + { + if (!do_remove) + { + svn_merge_range_t tmp_range; + tmp_range.start = elt2->start; + tmp_range.end = elt2->end; + /* The intersection of two ranges is non-inheritable only + if both ranges are non-inheritable. */ + tmp_range.inheritable = + (elt2->inheritable || elt1->inheritable); + SVN_ERR(combine_with_lastrange(&tmp_range, *output, + consider_inheritance, + pool)); + } + + i2++; + + if (elt2->start == elt1->start && elt2->end == elt1->end) + i1++; + } + else if (range_intersect(elt1, elt2, consider_inheritance)) + { + if (elt2->start < elt1->start) + { + /* The rangelist2 range starts before the rangelist1 range. */ + svn_merge_range_t tmp_range; + if (do_remove) + { + /* Retain the range that falls before the rangelist1 + start. */ + tmp_range.start = elt2->start; + tmp_range.end = elt1->start; + tmp_range.inheritable = elt2->inheritable; + } + else + { + /* Retain the range that falls between the rangelist1 + start and rangelist2 end. */ + tmp_range.start = elt1->start; + tmp_range.end = MIN(elt2->end, elt1->end); + /* The intersection of two ranges is non-inheritable only + if both ranges are non-inheritable. */ + tmp_range.inheritable = + (elt2->inheritable || elt1->inheritable); + } + + SVN_ERR(combine_with_lastrange(&tmp_range, + *output, consider_inheritance, + pool)); + } + + /* Set up the rest of the rangelist2 range for further + processing. */ + if (elt2->end > elt1->end) + { + /* The rangelist2 range ends after the rangelist1 range. */ + if (!do_remove) + { + /* Partial overlap. */ + svn_merge_range_t tmp_range; + tmp_range.start = MAX(elt2->start, elt1->start); + tmp_range.end = elt1->end; + /* The intersection of two ranges is non-inheritable only + if both ranges are non-inheritable. */ + tmp_range.inheritable = + (elt2->inheritable || elt1->inheritable); + SVN_ERR(combine_with_lastrange(&tmp_range, + *output, + consider_inheritance, + pool)); + } + + working_elt2.start = elt1->end; + working_elt2.end = elt2->end; + } + else + i2++; + } + else /* ranges don't intersect */ + { + /* See which side of the rangelist2 the rangelist1 is on. If it + is on the left side, we need to move the rangelist1. + + If it is on past the rangelist2 on the right side, we + need to output the rangelist2 and increment the + rangelist2. */ + if (svn_sort_compare_ranges(&elt1, &elt2) < 0) + i1++; + else + { + svn_merge_range_t *lastrange; + + if ((*output)->nelts > 0) + lastrange = APR_ARRAY_IDX(*output, (*output)->nelts - 1, + svn_merge_range_t *); + else + lastrange = NULL; + + if (do_remove && !(lastrange && + combine_ranges(lastrange, lastrange, elt2, + consider_inheritance))) + { + lastrange = svn_merge_range_dup(elt2, pool); + APR_ARRAY_PUSH(*output, svn_merge_range_t *) = lastrange; + } + i2++; + } + } + } + + if (do_remove) + { + /* Copy the current rangelist2 element if we didn't hit the end + of the rangelist2, and we still had it around. This element + may have been touched, so we can't just walk the rangelist2 + array, we have to use our copy. This case only happens when + we ran out of rangelist1 before rangelist2, *and* we had changed + the rangelist2 element. */ + if (i2 == lasti2 && i2 < rangelist2->nelts) + { + SVN_ERR(combine_with_lastrange(&working_elt2, *output, + consider_inheritance, pool)); + i2++; + } + + /* Copy any other remaining untouched rangelist2 elements. */ + for (; i2 < rangelist2->nelts; i2++) + { + svn_merge_range_t *elt = APR_ARRAY_IDX(rangelist2, i2, + svn_merge_range_t *); + + SVN_ERR(combine_with_lastrange(elt, *output, + consider_inheritance, pool)); + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_rangelist_intersect(svn_rangelist_t **output, + const svn_rangelist_t *rangelist1, + const svn_rangelist_t *rangelist2, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + return rangelist_intersect_or_remove(output, rangelist1, rangelist2, FALSE, + consider_inheritance, pool); +} + +svn_error_t * +svn_rangelist_remove(svn_rangelist_t **output, + const svn_rangelist_t *eraser, + const svn_rangelist_t *whiteboard, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + return rangelist_intersect_or_remove(output, eraser, whiteboard, TRUE, + consider_inheritance, pool); +} + +svn_error_t * +svn_rangelist_diff(svn_rangelist_t **deleted, svn_rangelist_t **added, + const svn_rangelist_t *from, const svn_rangelist_t *to, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + /* The following diagrams illustrate some common range delta scenarios: + + (from) deleted + r0 <===========(=========)============[=========]===========> rHEAD + [to] added + + (from) deleted deleted + r0 <===========(=========[============]=========)===========> rHEAD + [to] + + (from) deleted + r0 <===========(=========[============)=========]===========> rHEAD + [to] added + + (from) deleted + r0 <===========[=========(============]=========)===========> rHEAD + [to] added + + (from) + r0 <===========[=========(============)=========]===========> rHEAD + [to] added added + + (from) d d d + r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD + [to] a a a a a a a + */ + + /* The items that are present in from, but not in to, must have been + deleted. */ + SVN_ERR(svn_rangelist_remove(deleted, to, from, consider_inheritance, + pool)); + /* The items that are present in to, but not in from, must have been + added. */ + return svn_rangelist_remove(added, from, to, consider_inheritance, pool); +} + +struct mergeinfo_diff_baton +{ + svn_mergeinfo_t from; + svn_mergeinfo_t to; + svn_mergeinfo_t deleted; + svn_mergeinfo_t added; + svn_boolean_t consider_inheritance; + apr_pool_t *pool; +}; + +/* This implements the 'svn_hash_diff_func_t' interface. + BATON is of type 'struct mergeinfo_diff_baton *'. +*/ +static svn_error_t * +mergeinfo_hash_diff_cb(const void *key, apr_ssize_t klen, + enum svn_hash_diff_key_status status, + void *baton) +{ + /* hash_a is FROM mergeinfo, + hash_b is TO mergeinfo. */ + struct mergeinfo_diff_baton *cb = baton; + svn_rangelist_t *from_rangelist, *to_rangelist; + const char *path = key; + if (status == svn_hash_diff_key_both) + { + /* Record any deltas (additions or deletions). */ + svn_rangelist_t *deleted_rangelist, *added_rangelist; + from_rangelist = apr_hash_get(cb->from, path, klen); + to_rangelist = apr_hash_get(cb->to, path, klen); + SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist, + from_rangelist, to_rangelist, + cb->consider_inheritance, cb->pool)); + if (cb->deleted && deleted_rangelist->nelts > 0) + apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen), + klen, deleted_rangelist); + if (cb->added && added_rangelist->nelts > 0) + apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen), + klen, added_rangelist); + } + else if ((status == svn_hash_diff_key_a) && cb->deleted) + { + from_rangelist = apr_hash_get(cb->from, path, klen); + apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen), klen, + svn_rangelist_dup(from_rangelist, cb->pool)); + } + else if ((status == svn_hash_diff_key_b) && cb->added) + { + to_rangelist = apr_hash_get(cb->to, path, klen); + apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen), klen, + svn_rangelist_dup(to_rangelist, cb->pool)); + } + return SVN_NO_ERROR; +} + +/* Record deletions and additions of entire range lists (by path + presence), and delegate to svn_rangelist_diff() for delta + calculations on a specific path. */ +static svn_error_t * +walk_mergeinfo_hash_for_diff(svn_mergeinfo_t from, svn_mergeinfo_t to, + svn_mergeinfo_t deleted, svn_mergeinfo_t added, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct mergeinfo_diff_baton mdb; + mdb.from = from; + mdb.to = to; + mdb.deleted = deleted; + mdb.added = added; + mdb.consider_inheritance = consider_inheritance; + mdb.pool = result_pool; + + return svn_hash_diff(from, to, mergeinfo_hash_diff_cb, &mdb, scratch_pool); +} + +svn_error_t * +svn_mergeinfo_diff2(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added, + svn_mergeinfo_t from, svn_mergeinfo_t to, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (from && to == NULL) + { + *deleted = svn_mergeinfo_dup(from, result_pool); + *added = svn_hash__make(result_pool); + } + else if (from == NULL && to) + { + *deleted = svn_hash__make(result_pool); + *added = svn_mergeinfo_dup(to, result_pool); + } + else + { + *deleted = svn_hash__make(result_pool); + *added = svn_hash__make(result_pool); + + if (from && to) + { + SVN_ERR(walk_mergeinfo_hash_for_diff(from, to, *deleted, *added, + consider_inheritance, + result_pool, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__equals(svn_boolean_t *is_equal, + svn_mergeinfo_t info1, + svn_mergeinfo_t info2, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + *is_equal = FALSE; + + /* special cases: at least one side has no merge info */ + if (info1 == NULL && info2 == NULL) + { + *is_equal = TRUE; + return SVN_NO_ERROR; + } + + if (info1 == NULL || info2 == NULL) + return SVN_NO_ERROR; + + /* trivial case: different number of paths -> unequal */ + if (apr_hash_count(info1) != apr_hash_count(info2)) + return SVN_NO_ERROR; + + /* compare range lists for all paths */ + for (hi = apr_hash_first(pool, info1); hi; hi = apr_hash_next(hi)) + { + const char *key; + apr_ssize_t key_length; + svn_rangelist_t *lhs, *rhs; + int i; + svn_rangelist_t *deleted, *added; + + /* get both path lists */ + apr_hash_this(hi, (const void**)&key, &key_length, (void **)&lhs); + rhs = apr_hash_get(info2, key, key_length); + + /* missing on one side? */ + if (rhs == NULL) + return SVN_NO_ERROR; + + /* quick compare: the range lists will often be a perfect match */ + if (lhs->nelts == rhs->nelts) + { + for (i = 0; i < lhs->nelts; ++i) + { + svn_merge_range_t *lrange + = APR_ARRAY_IDX(lhs, i, svn_merge_range_t *); + svn_merge_range_t *rrange + = APR_ARRAY_IDX(rhs, i, svn_merge_range_t *); + + /* range mismatch? -> needs detailed comparison */ + if ( lrange->start != rrange->start + || lrange->end != rrange->end) + break; + + /* inheritance mismatch? -> merge info differs */ + if ( consider_inheritance + && lrange->inheritable != rrange->inheritable) + return SVN_NO_ERROR; + } + + /* all ranges found to match -> next path */ + if (i == lhs->nelts) + continue; + } + + /* range lists differ but there are many ways to sort and aggregate + revisions into ranges. Do a full diff on them. */ + SVN_ERR(svn_rangelist_diff(&deleted, &added, lhs, rhs, + consider_inheritance, pool)); + if (deleted->nelts || added->nelts) + return SVN_NO_ERROR; + } + + /* no mismatch found */ + *is_equal = TRUE; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_merge2(svn_mergeinfo_t mergeinfo, + svn_mergeinfo_t changes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + if (!apr_hash_count(changes)) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi)) + { + const char *key; + apr_ssize_t klen; + svn_rangelist_t *to_insert; + svn_rangelist_t *target; + + /* get ranges to insert and the target ranges list of that insertion */ + apr_hash_this(hi, (const void**)&key, &klen, (void*)&to_insert); + target = apr_hash_get(mergeinfo, key, klen); + + /* if range list exists, just expand on it. + * Otherwise, add new hash entry. */ + if (target) + { + SVN_ERR(svn_rangelist_merge2(target, to_insert, result_pool, + iterpool)); + svn_pool_clear(iterpool); + } + else + apr_hash_set(mergeinfo, key, klen, to_insert); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_catalog_merge(svn_mergeinfo_catalog_t mergeinfo_cat, + svn_mergeinfo_catalog_t changes_cat, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i = 0; + int j = 0; + apr_array_header_t *sorted_cat = + svn_sort__hash(mergeinfo_cat, svn_sort_compare_items_as_paths, + scratch_pool); + apr_array_header_t *sorted_changes = + svn_sort__hash(changes_cat, svn_sort_compare_items_as_paths, + scratch_pool); + + while (i < sorted_cat->nelts && j < sorted_changes->nelts) + { + svn_sort__item_t cat_elt, change_elt; + int res; + + cat_elt = APR_ARRAY_IDX(sorted_cat, i, svn_sort__item_t); + change_elt = APR_ARRAY_IDX(sorted_changes, j, svn_sort__item_t); + res = svn_sort_compare_items_as_paths(&cat_elt, &change_elt); + + if (res == 0) /* Both catalogs have mergeinfo for a given path. */ + { + svn_mergeinfo_t mergeinfo = cat_elt.value; + svn_mergeinfo_t changes_mergeinfo = change_elt.value; + + SVN_ERR(svn_mergeinfo_merge2(mergeinfo, changes_mergeinfo, + result_pool, scratch_pool)); + apr_hash_set(mergeinfo_cat, cat_elt.key, cat_elt.klen, mergeinfo); + i++; + j++; + } + else if (res < 0) /* Only MERGEINFO_CAT has mergeinfo for this path. */ + { + i++; + } + else /* Only CHANGES_CAT has mergeinfo for this path. */ + { + apr_hash_set(mergeinfo_cat, + apr_pstrdup(result_pool, change_elt.key), + change_elt.klen, + svn_mergeinfo_dup(change_elt.value, result_pool)); + j++; + } + } + + /* Copy back any remaining elements from the CHANGES_CAT catalog. */ + for (; j < sorted_changes->nelts; j++) + { + svn_sort__item_t elt = APR_ARRAY_IDX(sorted_changes, j, + svn_sort__item_t); + apr_hash_set(mergeinfo_cat, + apr_pstrdup(result_pool, elt.key), + elt.klen, + svn_mergeinfo_dup(elt.value, result_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_intersect2(svn_mergeinfo_t *mergeinfo, + svn_mergeinfo_t mergeinfo1, + svn_mergeinfo_t mergeinfo2, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + *mergeinfo = apr_hash_make(result_pool); + iterpool = svn_pool_create(scratch_pool); + + /* ### TODO(reint): Do we care about the case when a path in one + ### mergeinfo hash has inheritable mergeinfo, and in the other + ### has non-inhertiable mergeinfo? It seems like that path + ### itself should really be an intersection, while child paths + ### should not be... */ + for (hi = apr_hash_first(scratch_pool, mergeinfo1); + hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist1 = svn__apr_hash_index_val(hi); + svn_rangelist_t *rangelist2; + + svn_pool_clear(iterpool); + rangelist2 = svn_hash_gets(mergeinfo2, path); + if (rangelist2) + { + SVN_ERR(svn_rangelist_intersect(&rangelist2, rangelist1, rangelist2, + consider_inheritance, iterpool)); + if (rangelist2->nelts > 0) + svn_hash_sets(*mergeinfo, apr_pstrdup(result_pool, path), + svn_rangelist_dup(rangelist2, result_pool)); + } + } + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_remove2(svn_mergeinfo_t *mergeinfo, + svn_mergeinfo_t eraser, + svn_mergeinfo_t whiteboard, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *mergeinfo = apr_hash_make(result_pool); + return walk_mergeinfo_hash_for_diff(whiteboard, eraser, *mergeinfo, NULL, + consider_inheritance, result_pool, + scratch_pool); +} + +svn_error_t * +svn_rangelist_to_string(svn_string_t **output, + const svn_rangelist_t *rangelist, + apr_pool_t *pool) +{ + svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool); + + if (rangelist->nelts > 0) + { + int i; + svn_merge_range_t *range; + + /* Handle the elements that need commas at the end. */ + for (i = 0; i < rangelist->nelts - 1; i++) + { + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + svn_stringbuf_appendcstr(buf, range_to_string(range, pool)); + svn_stringbuf_appendcstr(buf, ","); + } + + /* Now handle the last element, which needs no comma. */ + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + svn_stringbuf_appendcstr(buf, range_to_string(range, pool)); + } + + *output = svn_stringbuf__morph_into_string(buf); + + return SVN_NO_ERROR; +} + +/* Converts a mergeinfo INPUT to an unparsed mergeinfo in OUTPUT. If PREFIX + is not NULL then prepend PREFIX to each line in OUTPUT. If INPUT contains + no elements, return the empty string. If INPUT contains any merge source + path keys that are relative then convert these to absolute paths in + *OUTPUT. + */ +static svn_error_t * +mergeinfo_to_stringbuf(svn_stringbuf_t **output, + svn_mergeinfo_t input, + const char *prefix, + apr_pool_t *pool) +{ + *output = svn_stringbuf_create_empty(pool); + + if (apr_hash_count(input) > 0) + { + apr_array_header_t *sorted = + svn_sort__hash(input, svn_sort_compare_items_as_paths, pool); + int i; + + for (i = 0; i < sorted->nelts; i++) + { + svn_sort__item_t elt = APR_ARRAY_IDX(sorted, i, svn_sort__item_t); + svn_string_t *revlist; + + SVN_ERR(svn_rangelist_to_string(&revlist, elt.value, pool)); + svn_stringbuf_appendcstr( + *output, + apr_psprintf(pool, "%s%s%s:%s", + prefix ? prefix : "", + *((const char *) elt.key) == '/' ? "" : "/", + (const char *) elt.key, + revlist->data)); + if (i < sorted->nelts - 1) + svn_stringbuf_appendcstr(*output, "\n"); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_to_string(svn_string_t **output, svn_mergeinfo_t input, + apr_pool_t *pool) +{ + svn_stringbuf_t *mergeinfo_buf; + + SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_buf, input, NULL, pool)); + *output = svn_stringbuf__morph_into_string(mergeinfo_buf); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_sort(svn_mergeinfo_t input, apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi)) + { + apr_array_header_t *rl = svn__apr_hash_index_val(hi); + + qsort(rl->elts, rl->nelts, rl->elt_size, svn_sort_compare_ranges); + } + return SVN_NO_ERROR; +} + +svn_mergeinfo_catalog_t +svn_mergeinfo_catalog_dup(svn_mergeinfo_catalog_t mergeinfo_catalog, + apr_pool_t *pool) +{ + svn_mergeinfo_t new_mergeinfo_catalog = apr_hash_make(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, mergeinfo_catalog); + hi; + hi = apr_hash_next(hi)) + { + const char *key = svn__apr_hash_index_key(hi); + svn_mergeinfo_t val = svn__apr_hash_index_val(hi); + + svn_hash_sets(new_mergeinfo_catalog, apr_pstrdup(pool, key), + svn_mergeinfo_dup(val, pool)); + } + + return new_mergeinfo_catalog; +} + +svn_mergeinfo_t +svn_mergeinfo_dup(svn_mergeinfo_t mergeinfo, apr_pool_t *pool) +{ + svn_mergeinfo_t new_mergeinfo = svn_hash__make(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + apr_ssize_t pathlen = svn__apr_hash_index_klen(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + apr_hash_set(new_mergeinfo, apr_pstrmemdup(pool, path, pathlen), pathlen, + svn_rangelist_dup(rangelist, pool)); + } + + return new_mergeinfo; +} + +svn_error_t * +svn_mergeinfo_inheritable2(svn_mergeinfo_t *output, + svn_mergeinfo_t mergeinfo, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + svn_mergeinfo_t inheritable_mergeinfo = apr_hash_make(result_pool); + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + const char *key = svn__apr_hash_index_key(hi); + apr_ssize_t keylen = svn__apr_hash_index_klen(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + svn_rangelist_t *inheritable_rangelist; + + if (!path || svn_path_compare_paths(path, key) == 0) + SVN_ERR(svn_rangelist_inheritable2(&inheritable_rangelist, rangelist, + start, end, inheritable, + result_pool, scratch_pool)); + else + inheritable_rangelist = svn_rangelist_dup(rangelist, result_pool); + + /* Only add this rangelist if some ranges remain. A rangelist with + a path mapped to an empty rangelist is not syntactically valid */ + if (inheritable_rangelist->nelts) + apr_hash_set(inheritable_mergeinfo, + apr_pstrmemdup(result_pool, key, keylen), keylen, + inheritable_rangelist); + } + *output = inheritable_mergeinfo; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_rangelist_inheritable2(svn_rangelist_t **inheritable_rangelist, + const svn_rangelist_t *rangelist, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *inheritable_rangelist = apr_array_make(result_pool, 1, + sizeof(svn_merge_range_t *)); + if (rangelist->nelts) + { + if (!SVN_IS_VALID_REVNUM(start) + || !SVN_IS_VALID_REVNUM(end) + || end < start) + { + int i; + /* We want all non-inheritable ranges removed. */ + for (i = 0; i < rangelist->nelts; i++) + { + svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, + svn_merge_range_t *); + if (range->inheritable == inheritable) + { + svn_merge_range_t *inheritable_range = + apr_palloc(result_pool, sizeof(*inheritable_range)); + inheritable_range->start = range->start; + inheritable_range->end = range->end; + inheritable_range->inheritable = TRUE; + APR_ARRAY_PUSH(*inheritable_rangelist, + svn_merge_range_t *) = range; + } + } + } + else + { + /* We want only the non-inheritable ranges bound by START + and END removed. */ + svn_rangelist_t *ranges_inheritable = + svn_rangelist__initialize(start, end, inheritable, scratch_pool); + + if (rangelist->nelts) + SVN_ERR(svn_rangelist_remove(inheritable_rangelist, + ranges_inheritable, + rangelist, + TRUE, + result_pool)); + } + } + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + svn_boolean_t removed_some_ranges = FALSE; + + if (mergeinfo) + { + for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist->nelts == 0) + { + svn_hash_sets(mergeinfo, path, NULL); + removed_some_ranges = TRUE; + } + } + } + return removed_some_ranges; +} + +svn_error_t * +svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t *out_catalog, + svn_mergeinfo_catalog_t in_catalog, + const char *prefix_path, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + SVN_ERR_ASSERT(prefix_path[0] == '/'); + + *out_catalog = apr_hash_make(pool); + + for (hi = apr_hash_first(pool, in_catalog); hi; hi = apr_hash_next(hi)) + { + const char *original_path = svn__apr_hash_index_key(hi); + svn_mergeinfo_t value = svn__apr_hash_index_val(hi); + const char *new_path; + + new_path = svn_fspath__skip_ancestor(prefix_path, original_path); + SVN_ERR_ASSERT(new_path); + + svn_hash_sets(*out_catalog, new_path, value); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__add_prefix_to_catalog(svn_mergeinfo_catalog_t *out_catalog, + svn_mergeinfo_catalog_t in_catalog, + const char *prefix_path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + *out_catalog = apr_hash_make(result_pool); + + for (hi = apr_hash_first(scratch_pool, in_catalog); + hi; + hi = apr_hash_next(hi)) + { + const char *original_path = svn__apr_hash_index_key(hi); + svn_mergeinfo_t value = svn__apr_hash_index_val(hi); + + if (original_path[0] == '/') + original_path++; + + svn_hash_sets(*out_catalog, + svn_dirent_join(prefix_path, original_path, result_pool), + value); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__add_suffix_to_mergeinfo(svn_mergeinfo_t *out_mergeinfo, + svn_mergeinfo_t mergeinfo, + const char *suffix_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + SVN_ERR_ASSERT(suffix_relpath && svn_relpath_is_canonical(suffix_relpath)); + + *out_mergeinfo = apr_hash_make(result_pool); + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + const char *fspath = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + svn_hash_sets(*out_mergeinfo, + svn_fspath__join(fspath, suffix_relpath, result_pool), + rangelist); + } + + return SVN_NO_ERROR; +} + +svn_rangelist_t * +svn_rangelist_dup(const svn_rangelist_t *rangelist, apr_pool_t *pool) +{ + svn_rangelist_t *new_rl = apr_array_make(pool, rangelist->nelts, + sizeof(svn_merge_range_t *)); + + /* allocate target range buffer with a single operation */ + svn_merge_range_t *copy = apr_palloc(pool, sizeof(*copy) * rangelist->nelts); + int i; + + /* fill it iteratively and link it into the range list */ + for (i = 0; i < rangelist->nelts; i++) + { + memcpy(copy + i, + APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *), + sizeof(*copy)); + APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) = copy + i; + } + + return new_rl; +} + +svn_merge_range_t * +svn_merge_range_dup(const svn_merge_range_t *range, apr_pool_t *pool) +{ + svn_merge_range_t *new_range = apr_palloc(pool, sizeof(*new_range)); + memcpy(new_range, range, sizeof(*new_range)); + return new_range; +} + +svn_boolean_t +svn_merge_range_contains_rev(const svn_merge_range_t *range, svn_revnum_t rev) +{ + assert(SVN_IS_VALID_REVNUM(range->start)); + assert(SVN_IS_VALID_REVNUM(range->end)); + assert(range->start != range->end); + + if (range->start < range->end) + return rev > range->start && rev <= range->end; + else + return rev > range->end && rev <= range->start; +} + +svn_error_t * +svn_mergeinfo__catalog_to_formatted_string(svn_string_t **output, + svn_mergeinfo_catalog_t catalog, + const char *key_prefix, + const char *val_prefix, + apr_pool_t *pool) +{ + svn_stringbuf_t *output_buf = NULL; + + if (catalog && apr_hash_count(catalog)) + { + int i; + apr_array_header_t *sorted_catalog = + svn_sort__hash(catalog, svn_sort_compare_items_as_paths, pool); + + output_buf = svn_stringbuf_create_empty(pool); + for (i = 0; i < sorted_catalog->nelts; i++) + { + svn_sort__item_t elt = + APR_ARRAY_IDX(sorted_catalog, i, svn_sort__item_t); + const char *path1; + svn_mergeinfo_t mergeinfo; + svn_stringbuf_t *mergeinfo_output_buf; + + path1 = elt.key; + mergeinfo = elt.value; + if (key_prefix) + svn_stringbuf_appendcstr(output_buf, key_prefix); + svn_stringbuf_appendcstr(output_buf, path1); + svn_stringbuf_appendcstr(output_buf, "\n"); + SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_output_buf, mergeinfo, + val_prefix ? val_prefix : "", pool)); + svn_stringbuf_appendstr(output_buf, mergeinfo_output_buf); + svn_stringbuf_appendcstr(output_buf, "\n"); + } + } +#if SVN_DEBUG + else if (!catalog) + { + output_buf = svn_stringbuf_create(key_prefix ? key_prefix : "", pool); + svn_stringbuf_appendcstr(output_buf, _("NULL mergeinfo catalog\n")); + } + else if (apr_hash_count(catalog) == 0) + { + output_buf = svn_stringbuf_create(key_prefix ? key_prefix : "", pool); + svn_stringbuf_appendcstr(output_buf, _("empty mergeinfo catalog\n")); + } +#endif + + /* If we have an output_buf, convert it to an svn_string_t; + otherwise, return a new string containing only a newline + character. */ + if (output_buf) + *output = svn_stringbuf__morph_into_string(output_buf); + else + *output = svn_string_create("\n", pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__get_range_endpoints(svn_revnum_t *youngest_rev, + svn_revnum_t *oldest_rev, + svn_mergeinfo_t mergeinfo, + apr_pool_t *pool) +{ + *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM; + if (mergeinfo) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) + { + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist->nelts) + { + svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, + rangelist->nelts - 1, + svn_merge_range_t *); + if (!SVN_IS_VALID_REVNUM(*youngest_rev) + || (range->end > *youngest_rev)) + *youngest_rev = range->end; + + range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); + if (!SVN_IS_VALID_REVNUM(*oldest_rev) + || (range->start < *oldest_rev)) + *oldest_rev = range->start; + } + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__filter_catalog_by_ranges(svn_mergeinfo_catalog_t *filtered_cat, + svn_mergeinfo_catalog_t catalog, + svn_revnum_t youngest_rev, + svn_revnum_t oldest_rev, + svn_boolean_t include_range, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + *filtered_cat = apr_hash_make(result_pool); + for (hi = apr_hash_first(scratch_pool, catalog); + hi; + hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_mergeinfo_t mergeinfo = svn__apr_hash_index_val(hi); + svn_mergeinfo_t filtered_mergeinfo; + + SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&filtered_mergeinfo, + mergeinfo, + youngest_rev, + oldest_rev, + include_range, + result_pool, + scratch_pool)); + if (apr_hash_count(filtered_mergeinfo)) + svn_hash_sets(*filtered_cat, + apr_pstrdup(result_pool, path), filtered_mergeinfo); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__filter_mergeinfo_by_ranges(svn_mergeinfo_t *filtered_mergeinfo, + svn_mergeinfo_t mergeinfo, + svn_revnum_t youngest_rev, + svn_revnum_t oldest_rev, + svn_boolean_t include_range, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(oldest_rev)); + SVN_ERR_ASSERT(oldest_rev < youngest_rev); + + *filtered_mergeinfo = apr_hash_make(result_pool); + + if (mergeinfo) + { + apr_hash_index_t *hi; + svn_rangelist_t *filter_rangelist = + svn_rangelist__initialize(oldest_rev, youngest_rev, TRUE, + scratch_pool); + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist->nelts) + { + svn_rangelist_t *new_rangelist; + + SVN_ERR(rangelist_intersect_or_remove( + &new_rangelist, filter_rangelist, rangelist, + ! include_range, FALSE, result_pool)); + + if (new_rangelist->nelts) + svn_hash_sets(*filtered_mergeinfo, + apr_pstrdup(result_pool, path), new_rangelist); + } + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__adjust_mergeinfo_rangelists(svn_mergeinfo_t *adjusted_mergeinfo, + svn_mergeinfo_t mergeinfo, + svn_revnum_t offset, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + *adjusted_mergeinfo = apr_hash_make(result_pool); + + if (mergeinfo) + { + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + int i; + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + svn_rangelist_t *adjusted_rangelist = + apr_array_make(result_pool, rangelist->nelts, + sizeof(svn_merge_range_t *)); + + for (i = 0; i < rangelist->nelts; i++) + { + svn_merge_range_t *range = + APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + + if (range->start + offset > 0 && range->end + offset > 0) + { + if (range->start + offset < 0) + range->start = 0; + else + range->start = range->start + offset; + + if (range->end + offset < 0) + range->end = 0; + else + range->end = range->end + offset; + APR_ARRAY_PUSH(adjusted_rangelist, svn_merge_range_t *) = + range; + } + } + + if (adjusted_rangelist->nelts) + svn_hash_sets(*adjusted_mergeinfo, apr_pstrdup(result_pool, path), + adjusted_rangelist); + } + } + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_mergeinfo__is_noninheritable(svn_mergeinfo_t mergeinfo, + apr_pool_t *scratch_pool) +{ + if (mergeinfo) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + int i; + + for (i = 0; i < rangelist->nelts; i++) + { + svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, + svn_merge_range_t *); + if (!range->inheritable) + return TRUE; + } + } + } + return FALSE; +} + +svn_rangelist_t * +svn_rangelist__initialize(svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *result_pool) +{ + svn_rangelist_t *rangelist = + apr_array_make(result_pool, 1, sizeof(svn_merge_range_t *)); + svn_merge_range_t *range = apr_pcalloc(result_pool, sizeof(*range)); + + range->start = start; + range->end = end; + range->inheritable = inheritable; + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range; + return rangelist; +} + +svn_error_t * +svn_mergeinfo__mergeinfo_from_segments(svn_mergeinfo_t *mergeinfo_p, + const apr_array_header_t *segments, + apr_pool_t *pool) +{ + svn_mergeinfo_t mergeinfo = apr_hash_make(pool); + int i; + + /* Translate location segments into merge sources and ranges. */ + for (i = 0; i < segments->nelts; i++) + { + svn_location_segment_t *segment = + APR_ARRAY_IDX(segments, i, svn_location_segment_t *); + svn_rangelist_t *path_ranges; + svn_merge_range_t *range; + const char *source_path; + + /* No path segment? Skip it. */ + if (! segment->path) + continue; + + /* Prepend a leading slash to our path. */ + source_path = apr_pstrcat(pool, "/", segment->path, (char *)NULL); + + /* See if we already stored ranges for this path. If not, make + a new list. */ + path_ranges = svn_hash_gets(mergeinfo, source_path); + if (! path_ranges) + path_ranges = apr_array_make(pool, 1, sizeof(range)); + + /* A svn_location_segment_t may have legitimately describe only + revision 0, but there is no corresponding representation for + this in a svn_merge_range_t. */ + if (segment->range_start == 0 && segment->range_end == 0) + continue; + + /* Build a merge range, push it onto the list of ranges, and for + good measure, (re)store it in the hash. */ + range = apr_pcalloc(pool, sizeof(*range)); + range->start = MAX(segment->range_start - 1, 0); + range->end = segment->range_end; + range->inheritable = TRUE; + APR_ARRAY_PUSH(path_ranges, svn_merge_range_t *) = range; + svn_hash_sets(mergeinfo, source_path, path_ranges); + } + + *mergeinfo_p = mergeinfo; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_rangelist__merge_many(svn_rangelist_t *merged_rangelist, + svn_mergeinfo_t merge_history, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (apr_hash_count(merge_history)) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, merge_history); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *subtree_rangelist = svn__apr_hash_index_val(hi); + + svn_pool_clear(iterpool); + SVN_ERR(svn_rangelist_merge2(merged_rangelist, subtree_rangelist, + result_pool, iterpool)); + } + svn_pool_destroy(iterpool); + } + return SVN_NO_ERROR; +} + + +const char * +svn_inheritance_to_word(svn_mergeinfo_inheritance_t inherit) +{ + switch (inherit) + { + case svn_mergeinfo_inherited: + return "inherited"; + case svn_mergeinfo_nearest_ancestor: + return "nearest-ancestor"; + default: + return "explicit"; + } +} + +svn_mergeinfo_inheritance_t +svn_inheritance_from_word(const char *word) +{ + if (strcmp(word, "inherited") == 0) + return svn_mergeinfo_inherited; + if (strcmp(word, "nearest-ancestor") == 0) + return svn_mergeinfo_nearest_ancestor; + return svn_mergeinfo_explicit; +} diff --git a/subversion/libsvn_subr/mutex.c b/subversion/libsvn_subr/mutex.c new file mode 100644 index 000000000000..04988ebd06e5 --- /dev/null +++ b/subversion/libsvn_subr/mutex.c @@ -0,0 +1,83 @@ +/* + * svn_mutex.c: routines for mutual exclusion. + * + * ==================================================================== + * 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_private_config.h" +#include "private/svn_mutex.h" + +svn_error_t * +svn_mutex__init(svn_mutex__t **mutex_p, + svn_boolean_t mutex_required, + apr_pool_t *result_pool) +{ + /* always initialize the mutex pointer, even though it is not + strictly necessary if APR_HAS_THREADS has not been set */ + *mutex_p = NULL; + +#if APR_HAS_THREADS + if (mutex_required) + { + apr_thread_mutex_t *apr_mutex; + apr_status_t status = + apr_thread_mutex_create(&apr_mutex, + APR_THREAD_MUTEX_DEFAULT, + result_pool); + if (status) + return svn_error_wrap_apr(status, _("Can't create mutex")); + + *mutex_p = apr_mutex; + } +#endif + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mutex__lock(svn_mutex__t *mutex) +{ +#if APR_HAS_THREADS + if (mutex) + { + apr_status_t status = apr_thread_mutex_lock(mutex); + if (status) + return svn_error_wrap_apr(status, _("Can't lock mutex")); + } +#endif + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mutex__unlock(svn_mutex__t *mutex, + svn_error_t *err) +{ +#if APR_HAS_THREADS + if (mutex) + { + apr_status_t status = apr_thread_mutex_unlock(mutex); + if (status && !err) + return svn_error_wrap_apr(status, _("Can't unlock mutex")); + } +#endif + + return err; +} diff --git a/subversion/libsvn_subr/named_atomic.c b/subversion/libsvn_subr/named_atomic.c new file mode 100644 index 000000000000..d07e7424f638 --- /dev/null +++ b/subversion/libsvn_subr/named_atomic.c @@ -0,0 +1,655 @@ +/* + * svn_named_atomic.c: routines for machine-wide named atomics. + * + * ==================================================================== + * 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 "private/svn_named_atomic.h" + +#include <apr_global_mutex.h> +#include <apr_mmap.h> + +#include "svn_private_config.h" +#include "private/svn_atomic.h" +#include "private/svn_mutex.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_io.h" + +/* Implementation aspects. + * + * We use a single shared memory block (memory mapped file) that will be + * created by the first user and merely mapped by all subsequent ones. + * The memory block contains an short header followed by a fixed-capacity + * array of named atomics. The number of entries currently in use is stored + * in the header part. + * + * Finding / creating the MMAP object as well as adding new array entries + * is being guarded by an APR global mutex. Since releasing the MMAP + * structure and closing the underlying does not affect other users of the + * same, cleanup will not be synchronized. + * + * The array is append-only. Once a process mapped the block into its + * address space, it may freely access any of the used entries. However, + * it must synchronize access to the volatile data within the entries. + * On Windows and where otherwise supported by GCC, lightweight "lock-free" + * synchronization will be used. Other targets serialize all access using + * a global mutex. + * + * Atomics will be identified by their name (a short string) and lookup + * takes linear time. But even that takes only about 10 microseconds for a + * full array scan -- which is in the same order of magnitude than e.g. a + * single global mutex lock / unlock pair. + */ + +/* Capacity of our shared memory object, i.e. max number of named atomics + * that may be created. Should have the form 2**N - 1. + */ +#define MAX_ATOMIC_COUNT 1023 + +/* We choose the size of a single named atomic object to fill a complete + * cache line (on most architectures). Thereby, we minimize the cache + * sync. overhead between different CPU cores. + */ +#define CACHE_LINE_LENGTH 64 + +/* We need 8 bytes for the actual value and the remainder is used to + * store the NUL-terminated name. + * + * Must not be smaller than SVN_NAMED_ATOMIC__MAX_NAME_LENGTH. + */ +#define MAX_NAME_LENGTH (CACHE_LINE_LENGTH - sizeof(apr_int64_t) - 1) + +/* Particle that will be appended to the namespace name to form the + * name of the mutex / lock file used for that namespace. + */ +#define MUTEX_NAME_SUFFIX ".mutex" + +/* Particle that will be appended to the namespace name to form the + * name of the shared memory file that backs that namespace. + */ +#define SHM_NAME_SUFFIX ".shm" + +/* Platform-dependent implementations of our basic atomic operations. + * NA_SYNCHRONIZE(op) will ensure that the OP gets executed atomically. + * This will be zero-overhead if OP itself is already atomic. + * + * (We don't call it SYNCHRONIZE because Windows has a preprocess macro by + * that name.) + * + * The default implementation will use the same mutex for initialization + * as well as any type of data access. This is quite expensive and we + * can do much better on most platforms. + */ +#if defined(WIN32) && ((_WIN32_WINNT >= 0x0502) || defined(InterlockedExchangeAdd64)) + +/* Interlocked API / intrinsics guarantee full data synchronization + */ +#define synched_read(mem) *mem +#define synched_write(mem, value) InterlockedExchange64(mem, value) +#define synched_add(mem, delta) InterlockedExchangeAdd64(mem, delta) +#define synched_cmpxchg(mem, value, comperand) \ + InterlockedCompareExchange64(mem, value, comperand) + +#define NA_SYNCHRONIZE(_atomic,op) op; +#define NA_SYNCHRONIZE_IS_FAST TRUE + +#elif SVN_HAS_ATOMIC_BUILTINS + +/* GCC provides atomic intrinsics for most common CPU types + */ +#define synched_read(mem) *mem +#define synched_write(mem, value) __sync_lock_test_and_set(mem, value) +#define synched_add(mem, delta) __sync_add_and_fetch(mem, delta) +#define synched_cmpxchg(mem, value, comperand) \ + __sync_val_compare_and_swap(mem, comperand, value) + +#define NA_SYNCHRONIZE(_atomic,op) op; +#define NA_SYNCHRONIZE_IS_FAST TRUE + +#else + +/* Default implementation + */ +static apr_int64_t +synched_read(volatile apr_int64_t *mem) +{ + return *mem; +} + +static apr_int64_t +synched_write(volatile apr_int64_t *mem, apr_int64_t value) +{ + apr_int64_t old_value = *mem; + *mem = value; + + return old_value; +} + +static apr_int64_t +synched_add(volatile apr_int64_t *mem, apr_int64_t delta) +{ + return *mem += delta; +} + +static apr_int64_t +synched_cmpxchg(volatile apr_int64_t *mem, + apr_int64_t value, + apr_int64_t comperand) +{ + apr_int64_t old_value = *mem; + if (old_value == comperand) + *mem = value; + + return old_value; +} + +#define NA_SYNCHRONIZE(_atomic,op)\ + do{\ + SVN_ERR(lock(_atomic->mutex));\ + op;\ + SVN_ERR(unlock(_atomic->mutex,SVN_NO_ERROR));\ + }while(0) + +#define NA_SYNCHRONIZE_IS_FAST FALSE + +#endif + +/* Structure describing a single atomic: its VALUE and NAME. + */ +struct named_atomic_data_t +{ + volatile apr_int64_t value; + char name[MAX_NAME_LENGTH + 1]; +}; + +/* Content of our shared memory buffer. COUNT is the number + * of used entries in ATOMICS. Insertion is append-only. + * PADDING is used to align the header information with the + * atomics to create a favorable data alignment. + */ +struct shared_data_t +{ + volatile apr_uint32_t count; + char padding [sizeof(struct named_atomic_data_t) - sizeof(apr_uint32_t)]; + + struct named_atomic_data_t atomics[MAX_ATOMIC_COUNT]; +}; + +/* Structure combining all objects that we need for access serialization. + */ +struct mutex_t +{ + /* Inter-process sync. is handled by through lock file. */ + apr_file_t *lock_file; + + /* Pool to be used with lock / unlock functions */ + apr_pool_t *pool; +}; + +/* API structure combining the atomic data and the access mutex + */ +struct svn_named_atomic__t +{ + /* pointer into the shared memory */ + struct named_atomic_data_t *data; + + /* sync. object; never NULL (even if unused) */ + struct mutex_t *mutex; +}; + +/* This is intended to be a singleton struct. It contains all + * information necessary to initialize and access the shared + * memory. + */ +struct svn_atomic_namespace__t +{ + /* Pointer to the shared data mapped into our process */ + struct shared_data_t *data; + + /* Last time we checked, this was the number of used + * (i.e. fully initialized) items. I.e. we can read + * their names without further sync. */ + volatile svn_atomic_t min_used; + + /* for each atomic in the shared memory, we hand out + * at most one API-level object. */ + struct svn_named_atomic__t atomics[MAX_ATOMIC_COUNT]; + + /* Synchronization object for this namespace */ + struct mutex_t mutex; +}; + +/* On most operating systems APR implements file locks per process, not + * per file. I.e. the lock file will only sync. among processes but within + * a process, we must use a mutex to sync the threads. */ +/* Compare ../libsvn_fs_fs/fs.h:SVN_FS_FS__USE_LOCK_MUTEX */ +#if APR_HAS_THREADS && !defined(WIN32) +#define USE_THREAD_MUTEX 1 +#else +#define USE_THREAD_MUTEX 0 +#endif + +/* Used for process-local thread sync. + */ +static svn_mutex__t *thread_mutex = NULL; + +/* Initialization flag for the above used by svn_atomic__init_once. + */ +static volatile svn_atomic_t mutex_initialized = FALSE; + +/* Initialize the thread sync. structures. + * To be called by svn_atomic__init_once. + */ +static svn_error_t * +init_thread_mutex(void *baton, apr_pool_t *pool) +{ + /* let the mutex live as long as the APR */ + apr_pool_t *global_pool = svn_pool_create(NULL); + + return svn_mutex__init(&thread_mutex, USE_THREAD_MUTEX, global_pool); +} + +/* Utility that acquires our global mutex and converts error types. + */ +static svn_error_t * +lock(struct mutex_t *mutex) +{ + svn_error_t *err; + + /* Get lock on the filehandle. */ + SVN_ERR(svn_mutex__lock(thread_mutex)); + err = svn_io_lock_open_file(mutex->lock_file, TRUE, FALSE, mutex->pool); + + return err + ? svn_mutex__unlock(thread_mutex, err) + : err; +} + +/* Utility that releases the lock previously acquired via lock(). If the + * unlock succeeds and OUTER_ERR is not NULL, OUTER_ERR will be returned. + * Otherwise, return the result of the unlock operation. + */ +static svn_error_t * +unlock(struct mutex_t *mutex, svn_error_t * outer_err) +{ + svn_error_t *unlock_err + = svn_io_unlock_open_file(mutex->lock_file, mutex->pool); + return svn_mutex__unlock(thread_mutex, + svn_error_compose_create(outer_err, + unlock_err)); +} + +/* The last user to close a particular namespace should also remove the + * lock file. Failure to do so, however, does not affect further uses + * of the same namespace. + */ +static apr_status_t +delete_lock_file(void *arg) +{ + struct mutex_t *mutex = arg; + const char *lock_name = NULL; + + /* locks have already been cleaned up. Simply close the file */ + apr_status_t status = apr_file_close(mutex->lock_file); + + /* Remove the file from disk. This will fail if there ares still other + * users of this lock file, i.e. namespace. */ + apr_file_name_get(&lock_name, mutex->lock_file); + if (lock_name) + apr_file_remove(lock_name, mutex->pool); + + return status; +} + +/* Validate the ATOMIC parameter, i.e it's address. Correct code will + * never need this but if someone should accidentally to use a NULL or + * incomplete structure, let's catch that here instead of segfaulting. + */ +static svn_error_t * +validate(svn_named_atomic__t *atomic) +{ + return atomic && atomic->data && atomic->mutex + ? SVN_NO_ERROR + : svn_error_create(SVN_ERR_BAD_ATOMIC, 0, _("Not a valid atomic")); +} + +/* Auto-initialize and return in *ATOMIC the API-level object for the + * atomic with index I within NS. */ +static void +return_atomic(svn_named_atomic__t **atomic, + svn_atomic_namespace__t *ns, + int i) +{ + *atomic = &ns->atomics[i]; + if (ns->atomics[i].data == NULL) + { + (*atomic)->mutex = &ns->mutex; + (*atomic)->data = &ns->data->atomics[i]; + } +} + +/* Implement API */ + +svn_boolean_t +svn_named_atomic__is_supported(void) +{ +#ifdef _WIN32 + static svn_tristate_t result = svn_tristate_unknown; + + if (result == svn_tristate_unknown) + { + /* APR SHM implementation requires the creation of global objects */ + HANDLE handle = CreateFileMappingA(INVALID_HANDLE_VALUE, + NULL, + PAGE_READONLY, + 0, + 1, + "Global\\__RandomXZY_svn"); + if (handle != NULL) + { + CloseHandle(handle); + result = svn_tristate_true; + } + else + result = svn_tristate_false; + } + + return result == svn_tristate_true; +#else + return TRUE; +#endif +} + +svn_boolean_t +svn_named_atomic__is_efficient(void) +{ + return NA_SYNCHRONIZE_IS_FAST; +} + +svn_error_t * +svn_atomic_namespace__create(svn_atomic_namespace__t **ns, + const char *name, + apr_pool_t *result_pool) +{ + apr_status_t apr_err; + svn_error_t *err; + apr_file_t *file; + apr_mmap_t *mmap; + const char *shm_name, *lock_name; + apr_finfo_t finfo; + + apr_pool_t *subpool = svn_pool_create(result_pool); + + /* allocate the namespace data structure + */ + svn_atomic_namespace__t *new_ns = apr_pcalloc(result_pool, sizeof(**ns)); + + /* construct the names of the system objects that we need + */ + shm_name = apr_pstrcat(subpool, name, SHM_NAME_SUFFIX, NULL); + lock_name = apr_pstrcat(subpool, name, MUTEX_NAME_SUFFIX, NULL); + + /* initialize the lock objects + */ + SVN_ERR(svn_atomic__init_once(&mutex_initialized, init_thread_mutex, NULL, + result_pool)); + + new_ns->mutex.pool = result_pool; + SVN_ERR(svn_io_file_open(&new_ns->mutex.lock_file, lock_name, + APR_READ | APR_WRITE | APR_CREATE, + APR_OS_DEFAULT, + result_pool)); + + /* Make sure the last user of our lock file will actually remove it. + * Please note that only the last file handle begin closed will actually + * remove the underlying file (see docstring for apr_file_remove). + */ + apr_pool_cleanup_register(result_pool, &new_ns->mutex, + delete_lock_file, + apr_pool_cleanup_null); + + /* Prevent concurrent initialization. + */ + SVN_ERR(lock(&new_ns->mutex)); + + /* First, make sure that the underlying file exists. If it doesn't + * exist, create one and initialize its content. + */ + err = svn_io_file_open(&file, shm_name, + APR_READ | APR_WRITE | APR_CREATE, + APR_OS_DEFAULT, + result_pool); + if (!err) + { + err = svn_io_stat(&finfo, shm_name, APR_FINFO_SIZE, subpool); + if (!err && finfo.size < sizeof(struct shared_data_t)) + { + /* Zero all counters, values and names. + */ + struct shared_data_t initial_data; + memset(&initial_data, 0, sizeof(initial_data)); + err = svn_io_file_write_full(file, &initial_data, + sizeof(initial_data), NULL, + subpool); + } + } + + /* Now, map it into memory. + */ + if (!err) + { + apr_err = apr_mmap_create(&mmap, file, 0, sizeof(*new_ns->data), + APR_MMAP_READ | APR_MMAP_WRITE , result_pool); + if (!apr_err) + new_ns->data = mmap->mm; + else + err = svn_error_createf(apr_err, NULL, + _("MMAP failed for file '%s'"), shm_name); + } + + svn_pool_destroy(subpool); + + if (!err && new_ns->data) + { + /* Detect severe cases of corruption (i.e. when some outsider messed + * with our data file) + */ + if (new_ns->data->count > MAX_ATOMIC_COUNT) + return svn_error_create(SVN_ERR_CORRUPTED_ATOMIC_STORAGE, 0, + _("Number of atomics in namespace is too large.")); + + /* Cache the number of existing, complete entries. There can't be + * incomplete ones from other processes because we hold the mutex. + * Our process will also not access this information since we are + * either being called from within svn_atomic__init_once or by + * svn_atomic_namespace__create for a new object. + */ + new_ns->min_used = new_ns->data->count; + *ns = new_ns; + } + + /* Unlock to allow other processes may access the shared memory as well. + */ + return unlock(&new_ns->mutex, err); +} + +svn_error_t * +svn_atomic_namespace__cleanup(const char *name, + apr_pool_t *pool) +{ + const char *shm_name, *lock_name; + + /* file names used for the specified namespace */ + shm_name = apr_pstrcat(pool, name, SHM_NAME_SUFFIX, NULL); + lock_name = apr_pstrcat(pool, name, MUTEX_NAME_SUFFIX, NULL); + + /* remove these files if they exist */ + SVN_ERR(svn_io_remove_file2(shm_name, TRUE, pool)); + SVN_ERR(svn_io_remove_file2(lock_name, TRUE, pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_named_atomic__get(svn_named_atomic__t **atomic, + svn_atomic_namespace__t *ns, + const char *name, + svn_boolean_t auto_create) +{ + apr_uint32_t i, count; + svn_error_t *error = SVN_NO_ERROR; + apr_size_t len = strlen(name); + + /* Check parameters and make sure we return a NULL atomic + * in case of failure. + */ + *atomic = NULL; + if (len > SVN_NAMED_ATOMIC__MAX_NAME_LENGTH) + return svn_error_create(SVN_ERR_BAD_ATOMIC, 0, + _("Atomic's name is too long.")); + + /* If no namespace has been provided, bail out. + */ + if (ns == NULL || ns->data == NULL) + return svn_error_create(SVN_ERR_BAD_ATOMIC, 0, + _("Namespace has not been initialized.")); + + /* Optimistic lookup. + * Because we never change the name of existing atomics and may only + * append new ones, we can safely compare the name of existing ones + * with the name that we are looking for. + */ + for (i = 0, count = svn_atomic_read(&ns->min_used); i < count; ++i) + if (strncmp(ns->data->atomics[i].name, name, len + 1) == 0) + { + return_atomic(atomic, ns, i); + return SVN_NO_ERROR; + } + + /* Try harder: + * Serialize all lookup and insert the item, if necessary and allowed. + */ + SVN_ERR(lock(&ns->mutex)); + + /* We only need to check for new entries. + */ + for (i = count; i < ns->data->count; ++i) + if (strncmp(ns->data->atomics[i].name, name, len + 1) == 0) + { + return_atomic(atomic, ns, i); + + /* Update our cached number of complete entries. */ + svn_atomic_set(&ns->min_used, ns->data->count); + + return unlock(&ns->mutex, error); + } + + /* Not found. Append a new entry, if allowed & possible. + */ + if (auto_create) + { + if (ns->data->count < MAX_ATOMIC_COUNT) + { + ns->data->atomics[ns->data->count].value = 0; + memcpy(ns->data->atomics[ns->data->count].name, + name, + len+1); + + return_atomic(atomic, ns, ns->data->count); + ++ns->data->count; + } + else + error = svn_error_create(SVN_ERR_BAD_ATOMIC, 0, + _("Out of slots for named atomic.")); + } + + /* We are mainly done here. Let others continue their work. + */ + SVN_ERR(unlock(&ns->mutex, error)); + + /* Only now can we be sure that a full memory barrier has been set + * and that the new entry has been written to memory in full. + */ + svn_atomic_set(&ns->min_used, ns->data->count); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_named_atomic__read(apr_int64_t *value, + svn_named_atomic__t *atomic) +{ + SVN_ERR(validate(atomic)); + NA_SYNCHRONIZE(atomic, *value = synched_read(&atomic->data->value)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_named_atomic__write(apr_int64_t *old_value, + apr_int64_t new_value, + svn_named_atomic__t *atomic) +{ + apr_int64_t temp; + + SVN_ERR(validate(atomic)); + NA_SYNCHRONIZE(atomic, temp = synched_write(&atomic->data->value, new_value)); + + if (old_value) + *old_value = temp; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_named_atomic__add(apr_int64_t *new_value, + apr_int64_t delta, + svn_named_atomic__t *atomic) +{ + apr_int64_t temp; + + SVN_ERR(validate(atomic)); + NA_SYNCHRONIZE(atomic, temp = synched_add(&atomic->data->value, delta)); + + if (new_value) + *new_value = temp; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_named_atomic__cmpxchg(apr_int64_t *old_value, + apr_int64_t new_value, + apr_int64_t comperand, + svn_named_atomic__t *atomic) +{ + apr_int64_t temp; + + SVN_ERR(validate(atomic)); + NA_SYNCHRONIZE(atomic, temp = synched_cmpxchg(&atomic->data->value, + new_value, + comperand)); + + if (old_value) + *old_value = temp; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/nls.c b/subversion/libsvn_subr/nls.c new file mode 100644 index 000000000000..b026e3917df4 --- /dev/null +++ b/subversion/libsvn_subr/nls.c @@ -0,0 +1,132 @@ +/* + * nls.c : Helpers for NLS programs. + * + * ==================================================================== + * 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 <stdlib.h> + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#endif + +#include <apr_errno.h> + +#include "svn_nls.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_path.h" + +#include "svn_private_config.h" + +svn_error_t * +svn_nls_init(void) +{ + svn_error_t *err = SVN_NO_ERROR; + +#ifdef ENABLE_NLS + if (getenv("SVN_LOCALE_DIR")) + { + bindtextdomain(PACKAGE_NAME, getenv("SVN_LOCALE_DIR")); + } + else + { +#ifdef WIN32 + WCHAR ucs2_path[MAX_PATH]; + char* utf8_path; + const char* internal_path; + apr_pool_t* pool; + apr_size_t inwords, outbytes, outlength; + + apr_pool_create(&pool, 0); + /* get exe name - our locale info will be in '../share/locale' */ + inwords = GetModuleFileNameW(0, ucs2_path, + sizeof(ucs2_path) / sizeof(ucs2_path[0])); + if (! inwords) + { + /* We must be on a Win9x machine, so attempt to get an ANSI path, + and convert it to Unicode. */ + CHAR ansi_path[MAX_PATH]; + + if (GetModuleFileNameA(0, ansi_path, sizeof(ansi_path))) + { + inwords = + MultiByteToWideChar(CP_ACP, 0, ansi_path, -1, ucs2_path, + sizeof(ucs2_path) / sizeof(ucs2_path[0])); + if (! inwords) + { + err = + svn_error_createf(APR_EINVAL, NULL, + _("Can't convert string to UCS-2: '%s'"), + ansi_path); + } + } + else + { + err = svn_error_create(APR_EINVAL, NULL, + _("Can't get module file name")); + } + } + + if (! err) + { + outbytes = outlength = 3 * (inwords + 1); + utf8_path = apr_palloc(pool, outlength); + + outbytes = WideCharToMultiByte(CP_UTF8, 0, ucs2_path, inwords, + utf8_path, outbytes, NULL, NULL); + + if (outbytes == 0) + { + err = svn_error_wrap_apr(apr_get_os_error(), + _("Can't convert module path " + "to UTF-8 from UCS-2: '%s'"), + ucs2_path); + } + else + { + utf8_path[outlength - outbytes] = '\0'; + internal_path = svn_dirent_internal_style(utf8_path, pool); + /* get base path name */ + internal_path = svn_dirent_dirname(internal_path, pool); + internal_path = svn_dirent_join(internal_path, + SVN_LOCALE_RELATIVE_PATH, + pool); + bindtextdomain(PACKAGE_NAME, internal_path); + } + } + svn_pool_destroy(pool); + } +#else /* ! WIN32 */ + bindtextdomain(PACKAGE_NAME, SVN_LOCALE_DIR); + } +#endif /* WIN32 */ + +#ifdef HAVE_BIND_TEXTDOMAIN_CODESET + bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"); +#endif /* HAVE_BIND_TEXTDOMAIN_CODESET */ + +#endif /* ENABLE_NLS */ + + return err; +} diff --git a/subversion/libsvn_subr/opt.c b/subversion/libsvn_subr/opt.c new file mode 100644 index 000000000000..28ffed178725 --- /dev/null +++ b/subversion/libsvn_subr/opt.c @@ -0,0 +1,1240 @@ +/* + * opt.c : option and argument parsing for Subversion command lines + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <apr_pools.h> +#include <apr_general.h> +#include <apr_lib.h> +#include <apr_file_info.h> + +#include "svn_hash.h" +#include "svn_cmdline.h" +#include "svn_version.h" +#include "svn_types.h" +#include "svn_opt.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_utf.h" +#include "svn_time.h" +#include "svn_props.h" +#include "svn_ctype.h" + +#include "private/svn_opt_private.h" + +#include "opt.h" +#include "svn_private_config.h" + + +/*** Code. ***/ + +const svn_opt_subcommand_desc2_t * +svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table, + const char *cmd_name) +{ + int i = 0; + + if (cmd_name == NULL) + return NULL; + + while (table[i].name) { + int j; + if (strcmp(cmd_name, table[i].name) == 0) + return table + i; + for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++) + if (strcmp(cmd_name, table[i].aliases[j]) == 0) + return table + i; + + i++; + } + + /* If we get here, there was no matching subcommand name or alias. */ + return NULL; +} + +const apr_getopt_option_t * +svn_opt_get_option_from_code2(int code, + const apr_getopt_option_t *option_table, + const svn_opt_subcommand_desc2_t *command, + apr_pool_t *pool) +{ + apr_size_t i; + + for (i = 0; option_table[i].optch; i++) + if (option_table[i].optch == code) + { + if (command) + { + int j; + + for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) && + command->desc_overrides[j].optch); j++) + if (command->desc_overrides[j].optch == code) + { + apr_getopt_option_t *tmpopt = + apr_palloc(pool, sizeof(*tmpopt)); + *tmpopt = option_table[i]; + tmpopt->description = command->desc_overrides[j].desc; + return tmpopt; + } + } + return &(option_table[i]); + } + + return NULL; +} + + +const apr_getopt_option_t * +svn_opt_get_option_from_code(int code, + const apr_getopt_option_t *option_table) +{ + apr_size_t i; + + for (i = 0; option_table[i].optch; i++) + if (option_table[i].optch == code) + return &(option_table[i]); + + return NULL; +} + + +/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second + * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that + * second name, else set it to NULL. */ +static const apr_getopt_option_t * +get_option_from_code(const char **long_alias, + int code, + const apr_getopt_option_t *option_table, + const svn_opt_subcommand_desc2_t *command, + apr_pool_t *pool) +{ + const apr_getopt_option_t *i; + const apr_getopt_option_t *opt + = svn_opt_get_option_from_code2(code, option_table, command, pool); + + /* Find a long alias in the table, if there is one. */ + *long_alias = NULL; + for (i = option_table; i->optch; i++) + { + if (i->optch == code && i->name != opt->name) + { + *long_alias = i->name; + break; + } + } + + return opt; +} + + +/* Print an option OPT nicely into a STRING allocated in POOL. + * If OPT has a single-character short form, then print OPT->name (if not + * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias. + * If DOC is set, include the generic documentation string of OPT, + * localized to the current locale if a translation is available. + */ +static void +format_option(const char **string, + const apr_getopt_option_t *opt, + const char *long_alias, + svn_boolean_t doc, + apr_pool_t *pool) +{ + char *opts; + + if (opt == NULL) + { + *string = "?"; + return; + } + + /* We have a valid option which may or may not have a "short + name" (a single-character alias for the long option). */ + if (opt->optch <= 255) + opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name); + else if (long_alias) + opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias); + else + opts = apr_psprintf(pool, "--%s", opt->name); + + if (opt->has_arg) + opts = apr_pstrcat(pool, opts, _(" ARG"), (char *)NULL); + + if (doc) + opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description)); + + *string = opts; +} + +void +svn_opt_format_option(const char **string, + const apr_getopt_option_t *opt, + svn_boolean_t doc, + apr_pool_t *pool) +{ + format_option(string, opt, NULL, doc, pool); +} + + +svn_boolean_t +svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command, + int option_code, + const int *global_options) +{ + apr_size_t i; + + for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) + if (command->valid_options[i] == option_code) + return TRUE; + + if (global_options) + for (i = 0; global_options[i]; i++) + if (global_options[i] == option_code) + return TRUE; + + return FALSE; +} + +svn_boolean_t +svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command, + int option_code) +{ + return svn_opt_subcommand_takes_option3(command, + option_code, + NULL); +} + + +svn_boolean_t +svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command, + int option_code) +{ + apr_size_t i; + + for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) + if (command->valid_options[i] == option_code) + return TRUE; + + return FALSE; +} + + +/* Print the canonical command name for CMD, and all its aliases, to + STREAM. If HELP is set, print CMD's help string too, in which case + obtain option usage from OPTIONS_TABLE. */ +static svn_error_t * +print_command_info2(const svn_opt_subcommand_desc2_t *cmd, + const apr_getopt_option_t *options_table, + const int *global_options, + svn_boolean_t help, + apr_pool_t *pool, + FILE *stream) +{ + svn_boolean_t first_time; + apr_size_t i; + + /* Print the canonical command name. */ + SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool)); + + /* Print the list of aliases. */ + first_time = TRUE; + for (i = 0; i < SVN_OPT_MAX_ALIASES; i++) + { + if (cmd->aliases[i] == NULL) + break; + + if (first_time) { + SVN_ERR(svn_cmdline_fputs(" (", stream, pool)); + first_time = FALSE; + } + else + SVN_ERR(svn_cmdline_fputs(", ", stream, pool)); + + SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool)); + } + + if (! first_time) + SVN_ERR(svn_cmdline_fputs(")", stream, pool)); + + if (help) + { + const apr_getopt_option_t *option; + const char *long_alias; + svn_boolean_t have_options = FALSE; + + SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help))); + + /* Loop over all valid option codes attached to the subcommand */ + for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) + { + if (cmd->valid_options[i]) + { + if (!have_options) + { + SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"), + stream, pool)); + have_options = TRUE; + } + + /* convert each option code into an option */ + option = get_option_from_code(&long_alias, cmd->valid_options[i], + options_table, cmd, pool); + + /* print the option's docstring */ + if (option && option->description) + { + const char *optstr; + format_option(&optstr, option, long_alias, TRUE, pool); + SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n", + optstr)); + } + } + } + /* And global options too */ + if (global_options && *global_options) + { + SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"), + stream, pool)); + have_options = TRUE; + + for (i = 0; global_options[i]; i++) + { + + /* convert each option code into an option */ + option = get_option_from_code(&long_alias, global_options[i], + options_table, cmd, pool); + + /* print the option's docstring */ + if (option && option->description) + { + const char *optstr; + format_option(&optstr, option, long_alias, TRUE, pool); + SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n", + optstr)); + } + } + } + + if (have_options) + SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n")); + } + + return SVN_NO_ERROR; +} + +void +svn_opt_print_generic_help2(const char *header, + const svn_opt_subcommand_desc2_t *cmd_table, + const apr_getopt_option_t *opt_table, + const char *footer, + apr_pool_t *pool, FILE *stream) +{ + int i = 0; + svn_error_t *err; + + if (header) + if ((err = svn_cmdline_fputs(header, stream, pool))) + goto print_error; + + while (cmd_table[i].name) + { + if ((err = svn_cmdline_fputs(" ", stream, pool)) + || (err = print_command_info2(cmd_table + i, opt_table, + NULL, FALSE, + pool, stream)) + || (err = svn_cmdline_fputs("\n", stream, pool))) + goto print_error; + i++; + } + + if ((err = svn_cmdline_fputs("\n", stream, pool))) + goto print_error; + + if (footer) + if ((err = svn_cmdline_fputs(footer, stream, pool))) + goto print_error; + + return; + + print_error: + /* Issue #3014: + * Don't print anything on broken pipes. The pipe was likely + * closed by the process at the other end. We expect that + * process to perform error reporting as necessary. + * + * ### This assumes that there is only one error in a chain for + * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ + if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) + svn_handle_error2(err, stderr, FALSE, "svn: "); + svn_error_clear(err); +} + + +void +svn_opt_subcommand_help3(const char *subcommand, + const svn_opt_subcommand_desc2_t *table, + const apr_getopt_option_t *options_table, + const int *global_options, + apr_pool_t *pool) +{ + const svn_opt_subcommand_desc2_t *cmd = + svn_opt_get_canonical_subcommand2(table, subcommand); + svn_error_t *err; + + if (cmd) + err = print_command_info2(cmd, options_table, global_options, + TRUE, pool, stdout); + else + err = svn_cmdline_fprintf(stderr, pool, + _("\"%s\": unknown command.\n\n"), subcommand); + + if (err) { + svn_handle_error2(err, stderr, FALSE, "svn: "); + svn_error_clear(err); + } +} + + + +/*** Parsing revision and date options. ***/ + + +/** Parsing "X:Y"-style arguments. **/ + +/* If WORD matches one of the special revision descriptors, + * case-insensitively, set *REVISION accordingly: + * + * - For "head", set REVISION->kind to svn_opt_revision_head. + * + * - For "prev", set REVISION->kind to svn_opt_revision_previous. + * + * - For "base", set REVISION->kind to svn_opt_revision_base. + * + * - For "committed", set REVISION->kind to svn_opt_revision_committed. + * + * If match, return 0, else return -1 and don't touch REVISION. + */ +static int +revision_from_word(svn_opt_revision_t *revision, const char *word) +{ + if (svn_cstring_casecmp(word, "head") == 0) + { + revision->kind = svn_opt_revision_head; + } + else if (svn_cstring_casecmp(word, "prev") == 0) + { + revision->kind = svn_opt_revision_previous; + } + else if (svn_cstring_casecmp(word, "base") == 0) + { + revision->kind = svn_opt_revision_base; + } + else if (svn_cstring_casecmp(word, "committed") == 0) + { + revision->kind = svn_opt_revision_committed; + } + else + return -1; + + return 0; +} + + +/* Parse one revision specification. Return pointer to character + after revision, or NULL if the revision is invalid. Modifies + str, so make sure to pass a copy of anything precious. Uses + POOL for temporary allocation. */ +static char *parse_one_rev(svn_opt_revision_t *revision, char *str, + apr_pool_t *pool) +{ + char *end, save; + + /* Allow any number of 'r's to prefix a revision number, because + that way if a script pastes svn output into another svn command + (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work, + even when compounded. + + As it happens, none of our special revision words begins with + "r". If any ever do, then this code will have to get smarter. + + Incidentally, this allows "r{DATE}". We could avoid that with + some trivial code rearrangement, but it's not clear what would + be gained by doing so. */ + while (*str == 'r') + str++; + + if (*str == '{') + { + svn_boolean_t matched; + apr_time_t tm; + svn_error_t *err; + + /* Brackets denote a date. */ + str++; + end = strchr(str, '}'); + if (!end) + return NULL; + *end = '\0'; + err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + if (!matched) + return NULL; + revision->kind = svn_opt_revision_date; + revision->value.date = tm; + return end + 1; + } + else if (svn_ctype_isdigit(*str)) + { + /* It's a number. */ + end = str + 1; + while (svn_ctype_isdigit(*end)) + end++; + save = *end; + *end = '\0'; + revision->kind = svn_opt_revision_number; + revision->value.number = SVN_STR_TO_REV(str); + *end = save; + return end; + } + else if (svn_ctype_isalpha(*str)) + { + end = str + 1; + while (svn_ctype_isalpha(*end)) + end++; + save = *end; + *end = '\0'; + if (revision_from_word(revision, str) != 0) + return NULL; + *end = save; + return end; + } + else + return NULL; +} + + +int +svn_opt_parse_revision(svn_opt_revision_t *start_revision, + svn_opt_revision_t *end_revision, + const char *arg, + apr_pool_t *pool) +{ + char *left_rev, *right_rev, *end; + + /* Operate on a copy of the argument. */ + left_rev = apr_pstrdup(pool, arg); + + right_rev = parse_one_rev(start_revision, left_rev, pool); + if (right_rev && *right_rev == ':') + { + right_rev++; + end = parse_one_rev(end_revision, right_rev, pool); + if (!end || *end != '\0') + return -1; + } + else if (!right_rev || *right_rev != '\0') + return -1; + + return 0; +} + + +int +svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges, + const char *arg, + apr_pool_t *pool) +{ + svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range)); + + range->start.kind = svn_opt_revision_unspecified; + range->end.kind = svn_opt_revision_unspecified; + + if (svn_opt_parse_revision(&(range->start), &(range->end), + arg, pool) == -1) + return -1; + + APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range; + return 0; +} + +svn_error_t * +svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev, + svn_opt_revision_t *op_rev, + svn_boolean_t is_url, + svn_boolean_t notice_local_mods, + apr_pool_t *pool) +{ + if (peg_rev->kind == svn_opt_revision_unspecified) + { + if (is_url) + { + peg_rev->kind = svn_opt_revision_head; + } + else + { + if (notice_local_mods) + peg_rev->kind = svn_opt_revision_working; + else + peg_rev->kind = svn_opt_revision_base; + } + } + + if (op_rev->kind == svn_opt_revision_unspecified) + *op_rev = *peg_rev; + + return SVN_NO_ERROR; +} + +const char * +svn_opt__revision_to_string(const svn_opt_revision_t *revision, + apr_pool_t *result_pool) +{ + switch (revision->kind) + { + case svn_opt_revision_unspecified: + return "unspecified"; + case svn_opt_revision_number: + return apr_psprintf(result_pool, "%ld", revision->value.number); + case svn_opt_revision_date: + /* ### svn_time_to_human_cstring()? */ + return svn_time_to_cstring(revision->value.date, result_pool); + case svn_opt_revision_committed: + return "committed"; + case svn_opt_revision_previous: + return "previous"; + case svn_opt_revision_base: + return "base"; + case svn_opt_revision_working: + return "working"; + case svn_opt_revision_head: + return "head"; + default: + return NULL; + } +} + +svn_opt_revision_range_t * +svn_opt__revision_range_create(const svn_opt_revision_t *start_revision, + const svn_opt_revision_t *end_revision, + apr_pool_t *result_pool) +{ + svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); + + range->start = *start_revision; + range->end = *end_revision; + return range; +} + +svn_opt_revision_range_t * +svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum, + svn_revnum_t end_revnum, + apr_pool_t *result_pool) +{ + svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); + + range->start.kind = svn_opt_revision_number; + range->start.value.number = start_revnum; + range->end.kind = svn_opt_revision_number; + range->end.value.number = end_revnum; + return range; +} + + + +/*** Parsing arguments. ***/ +#define DEFAULT_ARRAY_SIZE 5 + + +/* Copy STR into POOL and push the copy onto ARRAY. */ +static void +array_push_str(apr_array_header_t *array, + const char *str, + apr_pool_t *pool) +{ + /* ### Not sure if this function is still necessary. It used to + convert str to svn_stringbuf_t * and push it, but now it just + dups str in pool and pushes the copy. So its only effect is + transfer str's lifetime to pool. Is that something callers are + depending on? */ + + APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str); +} + + +void +svn_opt_push_implicit_dot_target(apr_array_header_t *targets, + apr_pool_t *pool) +{ + if (targets->nelts == 0) + APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */ + assert(targets->nelts); +} + + +svn_error_t * +svn_opt_parse_num_args(apr_array_header_t **args_p, + apr_getopt_t *os, + int num_args, + apr_pool_t *pool) +{ + int i; + apr_array_header_t *args + = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + + /* loop for num_args and add each arg to the args array */ + for (i = 0; i < num_args; i++) + { + if (os->ind >= os->argc) + { + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + } + array_push_str(args, os->argv[os->ind++], pool); + } + + *args_p = args; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt_parse_all_args(apr_array_header_t **args_p, + apr_getopt_t *os, + apr_pool_t *pool) +{ + apr_array_header_t *args + = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + + if (os->ind > os->argc) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + } + while (os->ind < os->argc) + { + array_push_str(args, os->argv[os->ind++], pool); + } + + *args_p = args; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_opt_parse_path(svn_opt_revision_t *rev, + const char **truepath, + const char *path /* UTF-8! */, + apr_pool_t *pool) +{ + const char *peg_rev; + + SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool)); + + /* Parse the peg revision, if one was found */ + if (strlen(peg_rev)) + { + int ret; + svn_opt_revision_t start_revision, end_revision; + + end_revision.kind = svn_opt_revision_unspecified; + + if (peg_rev[1] == '\0') /* looking at empty peg revision */ + { + ret = 0; + start_revision.kind = svn_opt_revision_unspecified; + start_revision.value.number = 0; + } + else /* looking at non-empty peg revision */ + { + const char *rev_str = &peg_rev[1]; + + /* URLs get treated differently from wc paths. */ + if (svn_path_is_url(path)) + { + /* URLs are URI-encoded, so we look for dates with + URI-encoded delimeters. */ + size_t rev_len = strlen(rev_str); + if (rev_len > 6 + && rev_str[0] == '%' + && rev_str[1] == '7' + && (rev_str[2] == 'B' + || rev_str[2] == 'b') + && rev_str[rev_len-3] == '%' + && rev_str[rev_len-2] == '7' + && (rev_str[rev_len-1] == 'D' + || rev_str[rev_len-1] == 'd')) + { + rev_str = svn_path_uri_decode(rev_str, pool); + } + } + ret = svn_opt_parse_revision(&start_revision, + &end_revision, + rev_str, pool); + } + + if (ret || end_revision.kind != svn_opt_revision_unspecified) + { + /* If an svn+ssh URL was used and it contains only one @, + * provide an error message that presents a possible solution + * to the parsing error (issue #2349). */ + if (strncmp(path, "svn+ssh://", 10) == 0) + { + const char *at; + + at = strchr(path, '@'); + if (at && strrchr(path, '@') == at) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error parsing peg revision " + "'%s'; did you mean '%s@'?"), + &peg_rev[1], path); + } + + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error parsing peg revision '%s'"), + &peg_rev[1]); + } + rev->kind = start_revision.kind; + rev->value = start_revision.value; + } + else + { + /* Didn't find a peg revision. */ + rev->kind = svn_opt_revision_unspecified; + } + + return SVN_NO_ERROR; +} + + +/* Note: This is substantially copied into svn_client_args_to_target_array() in + * order to move to libsvn_client while maintaining backward compatibility. */ +svn_error_t * +svn_opt__args_to_target_array(apr_array_header_t **targets_p, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + apr_pool_t *pool) +{ + int i; + svn_error_t *err = SVN_NO_ERROR; + apr_array_header_t *input_targets = + apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + apr_array_header_t *output_targets = + apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + + /* Step 1: create a master array of targets that are in UTF-8 + encoding, and come from concatenating the targets left by apr_getopt, + plus any extra targets (e.g., from the --targets switch.) */ + + for (; os->ind < os->argc; os->ind++) + { + /* The apr_getopt targets are still in native encoding. */ + const char *raw_target = os->argv[os->ind]; + SVN_ERR(svn_utf_cstring_to_utf8 + ((const char **) apr_array_push(input_targets), + raw_target, pool)); + } + + if (known_targets) + { + for (i = 0; i < known_targets->nelts; i++) + { + /* The --targets array have already been converted to UTF-8, + because we needed to split up the list with svn_cstring_split. */ + const char *utf8_target = APR_ARRAY_IDX(known_targets, + i, const char *); + APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; + } + } + + /* Step 2: process each target. */ + + for (i = 0; i < input_targets->nelts; i++) + { + const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *); + const char *true_target; + const char *target; /* after all processing is finished */ + const char *peg_rev; + + /* + * This is needed so that the target can be properly canonicalized, + * otherwise the canonicalization does not treat a ".@BASE" as a "." + * with a BASE peg revision, and it is not canonicalized to "@BASE". + * If any peg revision exists, it is appended to the final + * canonicalized path or URL. Do not use svn_opt_parse_path() + * because the resulting peg revision is a structure that would have + * to be converted back into a string. Converting from a string date + * to the apr_time_t field in the svn_opt_revision_value_t and back to + * a string would not necessarily preserve the exact bytes of the + * input date, so its easier just to keep it in string form. + */ + SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, + utf8_target, pool)); + + /* URLs and wc-paths get treated differently. */ + if (svn_path_is_url(true_target)) + { + SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target, + pool)); + } + else /* not a url, so treat as a path */ + { + const char *base_name; + + SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target, + pool)); + + /* If the target has the same name as a Subversion + working copy administrative dir, skip it. */ + base_name = svn_dirent_basename(true_target, pool); + + /* FIXME: + The canonical list of administrative directory names is + maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir(). + That list can't be used here, because that use would + create a circular dependency between libsvn_wc and + libsvn_subr. Make sure changes to the lists are always + synchronized! */ + if (0 == strcmp(base_name, ".svn") + || 0 == strcmp(base_name, "_svn")) + { + err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, + err, _("'%s' ends in a reserved name"), + utf8_target); + continue; + } + } + + target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL); + + APR_ARRAY_PUSH(output_targets, const char *) = target; + } + + + /* kff todo: need to remove redundancies from targets before + passing it to the cmd_func. */ + + *targets_p = output_targets; + + return err; +} + +svn_error_t * +svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec, + apr_pool_t *pool) +{ + const char *sep, *propname; + svn_string_t *propval; + + if (! *revprop_spec) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Revision property pair is empty")); + + if (! *revprop_table_p) + *revprop_table_p = apr_hash_make(pool); + + sep = strchr(revprop_spec, '='); + if (sep) + { + propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec); + SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool)); + propval = svn_string_create(sep + 1, pool); + } + else + { + SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool)); + propval = svn_string_create_empty(pool); + } + + if (!svn_prop_name_is_valid(propname)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid Subversion property name"), + propname); + + svn_hash_sets(*revprop_table_p, propname, propval); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt__split_arg_at_peg_revision(const char **true_target, + const char **peg_revision, + const char *utf8_target, + apr_pool_t *pool) +{ + const char *peg_start = NULL; /* pointer to the peg revision, if any */ + const char *ptr; + + for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target; + --ptr) + { + /* If we hit a path separator, stop looking. This is OK + only because our revision specifiers can't contain '/'. */ + if (*ptr == '/') + break; + + if (*ptr == '@') + { + peg_start = ptr; + break; + } + } + + if (peg_start) + { + /* Error out if target is the empty string. */ + if (ptr == utf8_target) + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("'%s' is just a peg revision. " + "Maybe try '%s@' instead?"), + utf8_target, utf8_target); + + *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target); + if (peg_revision) + *peg_revision = apr_pstrdup(pool, peg_start); + } + else + { + *true_target = utf8_target; + if (peg_revision) + *peg_revision = ""; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in, + apr_pool_t *pool) +{ + const char *target; + + /* Convert to URI. */ + target = svn_path_uri_from_iri(url_in, pool); + /* Auto-escape some ASCII characters. */ + target = svn_path_uri_autoescape(target, pool); + +#if '/' != SVN_PATH_LOCAL_SEPARATOR + /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */ + if (strchr(target, SVN_PATH_LOCAL_SEPARATOR)) + { + char *p = apr_pstrdup(pool, target); + target = p; + + /* Convert all local-style separators to the canonical ones. */ + for (; *p != '\0'; ++p) + if (*p == SVN_PATH_LOCAL_SEPARATOR) + *p = '/'; + } +#endif + + /* Verify that no backpaths are present in the URL. */ + if (svn_path_is_backpath_present(target)) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("URL '%s' contains a '..' element"), + target); + + /* Strip any trailing '/' and collapse other redundant elements. */ + target = svn_uri_canonicalize(target, pool); + + *url_out = target; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in, + apr_pool_t *pool) +{ + const char *apr_target; + char *truenamed_target; /* APR-encoded */ + apr_status_t apr_err; + + /* canonicalize case, and change all separators to '/'. */ + SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); + apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, + APR_FILEPATH_TRUENAME, pool); + + if (!apr_err) + /* We have a canonicalized APR-encoded target now. */ + apr_target = truenamed_target; + else if (APR_STATUS_IS_ENOENT(apr_err)) + /* It's okay for the file to not exist, that just means we + have to accept the case given to the client. We'll use + the original APR-encoded target. */ + ; + else + return svn_error_createf(apr_err, NULL, + _("Error resolving case of '%s'"), + svn_dirent_local_style(path_in, pool)); + + /* convert back to UTF-8. */ + SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); + *path_out = svn_dirent_canonicalize(*path_out, pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_opt__print_version_info(const char *pgm_name, + const char *footer, + const svn_version_extended_t *info, + svn_boolean_t quiet, + svn_boolean_t verbose, + apr_pool_t *pool) +{ + if (quiet) + return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER); + + SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n" + " compiled %s, %s on %s\n\n"), + pgm_name, SVN_VERSION, + svn_version_ext_build_date(info), + svn_version_ext_build_time(info), + svn_version_ext_build_host(info))); + SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info))); + + if (footer) + { + SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer)); + } + + if (verbose) + { + const apr_array_header_t *libs; + + SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool)); + SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), + svn_version_ext_runtime_host(info))); + if (svn_version_ext_runtime_osname(info)) + { + SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"), + svn_version_ext_runtime_osname(info))); + } + + libs = svn_version_ext_linked_libs(info); + if (libs && libs->nelts) + { + const svn_version_ext_linked_lib_t *lib; + int i; + + SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"), + stdout, pool)); + for (i = 0; i < libs->nelts; ++i) + { + lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t); + if (lib->runtime_version) + SVN_ERR(svn_cmdline_printf(pool, + " - %s %s (compiled with %s)\n", + lib->name, + lib->runtime_version, + lib->compiled_version)); + else + SVN_ERR(svn_cmdline_printf(pool, + " - %s %s (static)\n", + lib->name, + lib->compiled_version)); + } + } + + libs = svn_version_ext_loaded_libs(info); + if (libs && libs->nelts) + { + const svn_version_ext_loaded_lib_t *lib; + int i; + + SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"), + stdout, pool)); + for (i = 0; i < libs->nelts; ++i) + { + lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t); + if (lib->version) + SVN_ERR(svn_cmdline_printf(pool, + " - %s (%s)\n", + lib->name, lib->version)); + else + SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name)); + } + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt_print_help4(apr_getopt_t *os, + const char *pgm_name, + svn_boolean_t print_version, + svn_boolean_t quiet, + svn_boolean_t verbose, + const char *version_footer, + const char *header, + const svn_opt_subcommand_desc2_t *cmd_table, + const apr_getopt_option_t *option_table, + const int *global_options, + const char *footer, + apr_pool_t *pool) +{ + apr_array_header_t *targets = NULL; + + if (os) + SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); + + if (os && targets->nelts) /* help on subcommand(s) requested */ + { + int i; + + for (i = 0; i < targets->nelts; i++) + { + svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *), + cmd_table, option_table, + global_options, pool); + } + } + else if (print_version) /* just --version */ + { + SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, + svn_version_extended(verbose, pool), + quiet, verbose, pool)); + } + else if (os && !targets->nelts) /* `-h', `--help', or `help' */ + svn_opt_print_generic_help2(header, + cmd_table, + option_table, + footer, + pool, + stdout); + else /* unknown option or cmd */ + SVN_ERR(svn_cmdline_fprintf(stderr, pool, + _("Type '%s help' for usage.\n"), pgm_name)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/opt.h b/subversion/libsvn_subr/opt.h new file mode 100644 index 000000000000..ddf398477a99 --- /dev/null +++ b/subversion/libsvn_subr/opt.h @@ -0,0 +1,54 @@ +/* + * opt.h: share svn_opt__* functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_OPT_H +#define SVN_LIBSVN_SUBR_OPT_H + +#include "svn_version.h" +#include "svn_opt.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Print version version info for PGM_NAME to the console. If QUIET is + * true, print in brief. Else if QUIET is not true, print the version + * more verbosely, and if FOOTER is non-null, print it following the + * version information. If VERBOSE is true, print running system info. + * + * Use POOL for temporary allocations. + */ +svn_error_t * +svn_opt__print_version_info(const char *pgm_name, + const char *footer, + const svn_version_extended_t *info, + svn_boolean_t quiet, + svn_boolean_t verbose, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_OPT_H */ diff --git a/subversion/libsvn_subr/path.c b/subversion/libsvn_subr/path.c new file mode 100644 index 000000000000..84368f3bb77b --- /dev/null +++ b/subversion/libsvn_subr/path.c @@ -0,0 +1,1315 @@ +/* + * paths.c: a path manipulation library using svn_stringbuf_t + * + * ==================================================================== + * 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 <string.h> +#include <assert.h> + +#include <apr_file_info.h> +#include <apr_lib.h> +#include <apr_uri.h> + +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_private_config.h" /* for SVN_PATH_LOCAL_SEPARATOR */ +#include "svn_utf.h" +#include "svn_io.h" /* for svn_io_stat() */ +#include "svn_ctype.h" + +#include "dirent_uri.h" + + +/* The canonical empty path. Can this be changed? Well, change the empty + test below and the path library will work, not so sure about the fs/wc + libraries. */ +#define SVN_EMPTY_PATH "" + +/* TRUE if s is the canonical empty path, FALSE otherwise */ +#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0') + +/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can + this be changed? Well, the path library will work, not so sure about + the OS! */ +#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.') + + + + +#ifndef NDEBUG +/* This function is an approximation of svn_path_is_canonical. + * It is supposed to be used in functions that do not have access + * to a pool, but still want to assert that a path is canonical. + * + * PATH with length LEN is assumed to be canonical if it isn't + * the platform's empty path (see definition of SVN_PATH_IS_PLATFORM_EMPTY), + * and does not contain "/./", and any one of the following + * conditions is also met: + * + * 1. PATH has zero length + * 2. PATH is the root directory (what exactly a root directory is + * depends on the platform) + * 3. PATH is not a root directory and does not end with '/' + * + * If possible, please use svn_path_is_canonical instead. + */ +static svn_boolean_t +is_canonical(const char *path, + apr_size_t len) +{ + return (! SVN_PATH_IS_PLATFORM_EMPTY(path, len) + && strstr(path, "/./") == NULL + && (len == 0 + || (len == 1 && path[0] == '/') + || (path[len-1] != '/') +#if defined(WIN32) || defined(__CYGWIN__) + || svn_dirent_is_root(path, len) +#endif + )); +} +#endif + + +/* functionality of svn_path_is_canonical but without the deprecation */ +static svn_boolean_t +svn_path_is_canonical_internal(const char *path, apr_pool_t *pool) +{ + return svn_uri_is_canonical(path, pool) || + svn_dirent_is_canonical(path, pool) || + svn_relpath_is_canonical(path); +} + +svn_boolean_t +svn_path_is_canonical(const char *path, apr_pool_t *pool) +{ + return svn_path_is_canonical_internal(path, pool); +} + +/* functionality of svn_path_join but without the deprecation */ +static char * +svn_path_join_internal(const char *base, + const char *component, + apr_pool_t *pool) +{ + apr_size_t blen = strlen(base); + apr_size_t clen = strlen(component); + char *path; + + assert(svn_path_is_canonical_internal(base, pool)); + assert(svn_path_is_canonical_internal(component, pool)); + + /* If the component is absolute, then return it. */ + if (*component == '/') + return apr_pmemdup(pool, component, clen + 1); + + /* If either is empty return the other */ + if (SVN_PATH_IS_EMPTY(base)) + return apr_pmemdup(pool, component, clen + 1); + if (SVN_PATH_IS_EMPTY(component)) + return apr_pmemdup(pool, base, blen + 1); + + if (blen == 1 && base[0] == '/') + blen = 0; /* Ignore base, just return separator + component */ + + /* Construct the new, combined path. */ + path = apr_palloc(pool, blen + 1 + clen + 1); + memcpy(path, base, blen); + path[blen] = '/'; + memcpy(path + blen + 1, component, clen + 1); + + return path; +} + +char *svn_path_join(const char *base, + const char *component, + apr_pool_t *pool) +{ + return svn_path_join_internal(base, component, pool); +} + +char *svn_path_join_many(apr_pool_t *pool, const char *base, ...) +{ +#define MAX_SAVED_LENGTHS 10 + apr_size_t saved_lengths[MAX_SAVED_LENGTHS]; + apr_size_t total_len; + int nargs; + va_list va; + const char *s; + apr_size_t len; + char *path; + char *p; + svn_boolean_t base_is_empty = FALSE, base_is_root = FALSE; + int base_arg = 0; + + total_len = strlen(base); + + assert(svn_path_is_canonical_internal(base, pool)); + + if (total_len == 1 && *base == '/') + base_is_root = TRUE; + else if (SVN_PATH_IS_EMPTY(base)) + { + total_len = sizeof(SVN_EMPTY_PATH) - 1; + base_is_empty = TRUE; + } + + saved_lengths[0] = total_len; + + /* Compute the length of the resulting string. */ + + nargs = 0; + va_start(va, base); + while ((s = va_arg(va, const char *)) != NULL) + { + len = strlen(s); + + assert(svn_path_is_canonical_internal(s, pool)); + + if (SVN_PATH_IS_EMPTY(s)) + continue; + + if (nargs++ < MAX_SAVED_LENGTHS) + saved_lengths[nargs] = len; + + if (*s == '/') + { + /* an absolute path. skip all components to this point and reset + the total length. */ + total_len = len; + base_arg = nargs; + base_is_root = len == 1; + base_is_empty = FALSE; + } + else if (nargs == base_arg + || (nargs == base_arg + 1 && base_is_root) + || base_is_empty) + { + /* if we have skipped everything up to this arg, then the base + and all prior components are empty. just set the length to + this component; do not add a separator. If the base is empty + we can now ignore it. */ + if (base_is_empty) + { + base_is_empty = FALSE; + total_len = 0; + } + total_len += len; + } + else + { + total_len += 1 + len; + } + } + va_end(va); + + /* base == "/" and no further components. just return that. */ + if (base_is_root && total_len == 1) + return apr_pmemdup(pool, "/", 2); + + /* we got the total size. allocate it, with room for a NULL character. */ + path = p = apr_palloc(pool, total_len + 1); + + /* if we aren't supposed to skip forward to an absolute component, and if + this is not an empty base that we are skipping, then copy the base + into the output. */ + if (base_arg == 0 && ! (SVN_PATH_IS_EMPTY(base) && ! base_is_empty)) + { + if (SVN_PATH_IS_EMPTY(base)) + memcpy(p, SVN_EMPTY_PATH, len = saved_lengths[0]); + else + memcpy(p, base, len = saved_lengths[0]); + p += len; + } + + nargs = 0; + va_start(va, base); + while ((s = va_arg(va, const char *)) != NULL) + { + if (SVN_PATH_IS_EMPTY(s)) + continue; + + if (++nargs < base_arg) + continue; + + if (nargs < MAX_SAVED_LENGTHS) + len = saved_lengths[nargs]; + else + len = strlen(s); + + /* insert a separator if we aren't copying in the first component + (which can happen when base_arg is set). also, don't put in a slash + if the prior character is a slash (occurs when prior component + is "/"). */ + if (p != path && p[-1] != '/') + *p++ = '/'; + + /* copy the new component and advance the pointer */ + memcpy(p, s, len); + p += len; + } + va_end(va); + + *p = '\0'; + assert((apr_size_t)(p - path) == total_len); + + return path; +} + + + +apr_size_t +svn_path_component_count(const char *path) +{ + apr_size_t count = 0; + + assert(is_canonical(path, strlen(path))); + + while (*path) + { + const char *start; + + while (*path == '/') + ++path; + + start = path; + + while (*path && *path != '/') + ++path; + + if (path != start) + ++count; + } + + return count; +} + + +/* Return the length of substring necessary to encompass the entire + * previous path segment in PATH, which should be a LEN byte string. + * + * A trailing slash will not be included in the returned length except + * in the case in which PATH is absolute and there are no more + * previous segments. + */ +static apr_size_t +previous_segment(const char *path, + apr_size_t len) +{ + if (len == 0) + return 0; + + while (len > 0 && path[--len] != '/') + ; + + if (len == 0 && path[0] == '/') + return 1; + else + return len; +} + + +void +svn_path_add_component(svn_stringbuf_t *path, + const char *component) +{ + apr_size_t len = strlen(component); + + assert(is_canonical(path->data, path->len)); + assert(is_canonical(component, strlen(component))); + + /* Append a dir separator, but only if this path is neither empty + nor consists of a single dir separator already. */ + if ((! SVN_PATH_IS_EMPTY(path->data)) + && (! ((path->len == 1) && (*(path->data) == '/')))) + { + char dirsep = '/'; + svn_stringbuf_appendbytes(path, &dirsep, sizeof(dirsep)); + } + + svn_stringbuf_appendbytes(path, component, len); +} + + +void +svn_path_remove_component(svn_stringbuf_t *path) +{ + assert(is_canonical(path->data, path->len)); + + path->len = previous_segment(path->data, path->len); + path->data[path->len] = '\0'; +} + + +void +svn_path_remove_components(svn_stringbuf_t *path, apr_size_t n) +{ + while (n > 0) + { + svn_path_remove_component(path); + n--; + } +} + + +char * +svn_path_dirname(const char *path, apr_pool_t *pool) +{ + apr_size_t len = strlen(path); + + assert(svn_path_is_canonical_internal(path, pool)); + + return apr_pstrmemdup(pool, path, previous_segment(path, len)); +} + + +char * +svn_path_basename(const char *path, apr_pool_t *pool) +{ + apr_size_t len = strlen(path); + apr_size_t start; + + assert(svn_path_is_canonical_internal(path, pool)); + + if (len == 1 && path[0] == '/') + start = 0; + else + { + start = len; + while (start > 0 && path[start - 1] != '/') + --start; + } + + return apr_pstrmemdup(pool, path + start, len - start); +} + +int +svn_path_is_empty(const char *path) +{ + assert(is_canonical(path, strlen(path))); + + if (SVN_PATH_IS_EMPTY(path)) + return 1; + + return 0; +} + +int +svn_path_compare_paths(const char *path1, + const char *path2) +{ + apr_size_t path1_len = strlen(path1); + apr_size_t path2_len = strlen(path2); + apr_size_t min_len = ((path1_len < path2_len) ? path1_len : path2_len); + apr_size_t i = 0; + + assert(is_canonical(path1, path1_len)); + assert(is_canonical(path2, path2_len)); + + /* Skip past common prefix. */ + while (i < min_len && path1[i] == path2[i]) + ++i; + + /* Are the paths exactly the same? */ + if ((path1_len == path2_len) && (i >= min_len)) + return 0; + + /* Children of paths are greater than their parents, but less than + greater siblings of their parents. */ + if ((path1[i] == '/') && (path2[i] == 0)) + return 1; + if ((path2[i] == '/') && (path1[i] == 0)) + return -1; + if (path1[i] == '/') + return -1; + if (path2[i] == '/') + return 1; + + /* Common prefix was skipped above, next character is compared to + determine order. We need to use an unsigned comparison, though, + so a "next character" of NULL (0x00) sorts numerically + smallest. */ + return (unsigned char)(path1[i]) < (unsigned char)(path2[i]) ? -1 : 1; +} + +/* Return the string length of the longest common ancestor of PATH1 and PATH2. + * + * This function handles everything except the URL-handling logic + * of svn_path_get_longest_ancestor, and assumes that PATH1 and + * PATH2 are *not* URLs. + * + * If the two paths do not share a common ancestor, return 0. + * + * New strings are allocated in POOL. + */ +static apr_size_t +get_path_ancestor_length(const char *path1, + const char *path2, + apr_pool_t *pool) +{ + apr_size_t path1_len, path2_len; + apr_size_t i = 0; + apr_size_t last_dirsep = 0; + + path1_len = strlen(path1); + path2_len = strlen(path2); + + if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2)) + return 0; + + while (path1[i] == path2[i]) + { + /* Keep track of the last directory separator we hit. */ + if (path1[i] == '/') + last_dirsep = i; + + i++; + + /* If we get to the end of either path, break out. */ + if ((i == path1_len) || (i == path2_len)) + break; + } + + /* two special cases: + 1. '/' is the longest common ancestor of '/' and '/foo' + 2. '/' is the longest common ancestor of '/rif' and '/raf' */ + if (i == 1 && path1[0] == '/' && path2[0] == '/') + return 1; + + /* last_dirsep is now the offset of the last directory separator we + crossed before reaching a non-matching byte. i is the offset of + that non-matching byte. */ + if (((i == path1_len) && (path2[i] == '/')) + || ((i == path2_len) && (path1[i] == '/')) + || ((i == path1_len) && (i == path2_len))) + return i; + else + if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/') + return 1; + return last_dirsep; +} + + +char * +svn_path_get_longest_ancestor(const char *path1, + const char *path2, + apr_pool_t *pool) +{ + svn_boolean_t path1_is_url = svn_path_is_url(path1); + svn_boolean_t path2_is_url = svn_path_is_url(path2); + + /* Are we messing with URLs? If we have a mix of URLs and non-URLs, + there's nothing common between them. */ + if (path1_is_url && path2_is_url) + { + return svn_uri_get_longest_ancestor(path1, path2, pool); + } + else if ((! path1_is_url) && (! path2_is_url)) + { + return apr_pstrndup(pool, path1, + get_path_ancestor_length(path1, path2, pool)); + } + else + { + /* A URL and a non-URL => no common prefix */ + return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH)); + } +} + +const char * +svn_path_is_child(const char *path1, + const char *path2, + apr_pool_t *pool) +{ + apr_size_t i; + + /* assert (is_canonical (path1, strlen (path1))); ### Expensive strlen */ + /* assert (is_canonical (path2, strlen (path2))); ### Expensive strlen */ + + /* Allow "" and "foo" to be parent/child */ + if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */ + { + if (SVN_PATH_IS_EMPTY(path2) /* "" not a child */ + || path2[0] == '/') /* "/foo" not a child */ + return NULL; + else + /* everything else is child */ + return pool ? apr_pstrdup(pool, path2) : path2; + } + + /* Reach the end of at least one of the paths. How should we handle + things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't + appear to arise in the current Subversion code, it's not clear to me + if they should be parent/child or not. */ + for (i = 0; path1[i] && path2[i]; i++) + if (path1[i] != path2[i]) + return NULL; + + /* There are two cases that are parent/child + ... path1[i] == '\0' + .../foo path2[i] == '/' + or + / path1[i] == '\0' + /foo path2[i] != '/' + */ + if (path1[i] == '\0' && path2[i]) + { + if (path2[i] == '/') + return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1; + else if (i == 1 && path1[0] == '/') + return pool ? apr_pstrdup(pool, path2 + 1) : path2 + 1; + } + + /* Otherwise, path2 isn't a child. */ + return NULL; +} + + +svn_boolean_t +svn_path_is_ancestor(const char *path1, const char *path2) +{ + apr_size_t path1_len = strlen(path1); + + /* If path1 is empty and path2 is not absoulte, then path1 is an ancestor. */ + if (SVN_PATH_IS_EMPTY(path1)) + return *path2 != '/'; + + /* If path1 is a prefix of path2, then: + - If path1 ends in a path separator, + - If the paths are of the same length + OR + - path2 starts a new path component after the common prefix, + then path1 is an ancestor. */ + if (strncmp(path1, path2, path1_len) == 0) + return path1[path1_len - 1] == '/' + || (path2[path1_len] == '/' || path2[path1_len] == '\0'); + + return FALSE; +} + + +apr_array_header_t * +svn_path_decompose(const char *path, + apr_pool_t *pool) +{ + apr_size_t i, oldi; + + apr_array_header_t *components = + apr_array_make(pool, 1, sizeof(const char *)); + + assert(svn_path_is_canonical_internal(path, pool)); + + if (SVN_PATH_IS_EMPTY(path)) + return components; /* ### Should we return a "" component? */ + + /* If PATH is absolute, store the '/' as the first component. */ + i = oldi = 0; + if (path[i] == '/') + { + char dirsep = '/'; + + APR_ARRAY_PUSH(components, const char *) + = apr_pstrmemdup(pool, &dirsep, sizeof(dirsep)); + + i++; + oldi++; + if (path[i] == '\0') /* path is a single '/' */ + return components; + } + + do + { + if ((path[i] == '/') || (path[i] == '\0')) + { + if (SVN_PATH_IS_PLATFORM_EMPTY(path + oldi, i - oldi)) + APR_ARRAY_PUSH(components, const char *) = SVN_EMPTY_PATH; + else + APR_ARRAY_PUSH(components, const char *) + = apr_pstrmemdup(pool, path + oldi, i - oldi); + + i++; + oldi = i; /* skipping past the dirsep */ + continue; + } + i++; + } + while (path[i-1]); + + return components; +} + + +const char * +svn_path_compose(const apr_array_header_t *components, + apr_pool_t *pool) +{ + apr_size_t *lengths = apr_palloc(pool, components->nelts*sizeof(*lengths)); + apr_size_t max_length = components->nelts; + char *path; + char *p; + int i; + + /* Get the length of each component so a total length can be + calculated. */ + for (i = 0; i < components->nelts; ++i) + { + apr_size_t l = strlen(APR_ARRAY_IDX(components, i, const char *)); + lengths[i] = l; + max_length += l; + } + + path = apr_palloc(pool, max_length + 1); + p = path; + + for (i = 0; i < components->nelts; ++i) + { + /* Append a '/' to the path. Handle the case with an absolute + path where a '/' appears in the first component. Only append + a '/' if the component is the second component that does not + follow a "/" first component; or it is the third or later + component. */ + if (i > 1 || + (i == 1 && strcmp("/", APR_ARRAY_IDX(components, + 0, + const char *)) != 0)) + { + *p++ = '/'; + } + + memcpy(p, APR_ARRAY_IDX(components, i, const char *), lengths[i]); + p += lengths[i]; + } + + *p = '\0'; + + return path; +} + + +svn_boolean_t +svn_path_is_single_path_component(const char *name) +{ + assert(is_canonical(name, strlen(name))); + + /* Can't be empty or `..' */ + if (SVN_PATH_IS_EMPTY(name) + || (name[0] == '.' && name[1] == '.' && name[2] == '\0')) + return FALSE; + + /* Slashes are bad, m'kay... */ + if (strchr(name, '/') != NULL) + return FALSE; + + /* It is valid. */ + return TRUE; +} + + +svn_boolean_t +svn_path_is_dotpath_present(const char *path) +{ + size_t len; + + /* The empty string does not have a dotpath */ + if (path[0] == '\0') + return FALSE; + + /* Handle "." or a leading "./" */ + if (path[0] == '.' && (path[1] == '\0' || path[1] == '/')) + return TRUE; + + /* Paths of length 1 (at this point) have no dotpath present. */ + if (path[1] == '\0') + return FALSE; + + /* If any segment is "/./", then a dotpath is present. */ + if (strstr(path, "/./") != NULL) + return TRUE; + + /* Does the path end in "/." ? */ + len = strlen(path); + return path[len - 2] == '/' && path[len - 1] == '.'; +} + +svn_boolean_t +svn_path_is_backpath_present(const char *path) +{ + size_t len; + + /* 0 and 1-length paths do not have a backpath */ + if (path[0] == '\0' || path[1] == '\0') + return FALSE; + + /* Handle ".." or a leading "../" */ + if (path[0] == '.' && path[1] == '.' && (path[2] == '\0' || path[2] == '/')) + return TRUE; + + /* Paths of length 2 (at this point) have no backpath present. */ + if (path[2] == '\0') + return FALSE; + + /* If any segment is "..", then a backpath is present. */ + if (strstr(path, "/../") != NULL) + return TRUE; + + /* Does the path end in "/.." ? */ + len = strlen(path); + return path[len - 3] == '/' && path[len - 2] == '.' && path[len - 1] == '.'; +} + + +/*** URI Stuff ***/ + +/* Examine PATH as a potential URI, and return a substring of PATH + that immediately follows the (scheme):// portion of the URI, or + NULL if PATH doesn't appear to be a valid URI. The returned value + is not alloced -- it shares memory with PATH. */ +static const char * +skip_uri_scheme(const char *path) +{ + apr_size_t j; + + /* A scheme is terminated by a : and cannot contain any /'s. */ + for (j = 0; path[j] && path[j] != ':'; ++j) + if (path[j] == '/') + return NULL; + + if (j > 0 && path[j] == ':' && path[j+1] == '/' && path[j+2] == '/') + return path + j + 3; + + return NULL; +} + + +svn_boolean_t +svn_path_is_url(const char *path) +{ + /* ### This function is reaaaaaaaaaaaaaally stupid right now. + We're just going to look for: + + (scheme)://(optional_stuff) + + Where (scheme) has no ':' or '/' characters. + + Someday it might be nice to have an actual URI parser here. + */ + return skip_uri_scheme(path) != NULL; +} + + + +/* Here is the BNF for path components in a URI. "pchar" is a + character in a path component. + + pchar = unreserved | escaped | + ":" | "@" | "&" | "=" | "+" | "$" | "," + unreserved = alphanum | mark + mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + + Note that "escaped" doesn't really apply to what users can put in + their paths, so that really means the set of characters is: + + alphanum | mark | ":" | "@" | "&" | "=" | "+" | "$" | "," +*/ +const char svn_uri__char_validity[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, + + /* 64 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, + + /* 128 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 192 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + +svn_boolean_t +svn_path_is_uri_safe(const char *path) +{ + apr_size_t i; + + /* Skip the URI scheme. */ + path = skip_uri_scheme(path); + + /* No scheme? Get outta here. */ + if (! path) + return FALSE; + + /* Skip to the first slash that's after the URI scheme. */ + path = strchr(path, '/'); + + /* If there's no first slash, then there's only a host portion; + therefore there couldn't be any uri-unsafe characters after the + host... so return true. */ + if (path == NULL) + return TRUE; + + for (i = 0; path[i]; i++) + { + /* Allow '%XX' (where each X is a hex digit) */ + if (path[i] == '%') + { + if (svn_ctype_isxdigit(path[i + 1]) && + svn_ctype_isxdigit(path[i + 2])) + { + i += 2; + continue; + } + return FALSE; + } + else if (! svn_uri__char_validity[((unsigned char)path[i])]) + { + return FALSE; + } + } + + return TRUE; +} + + +/* URI-encode each character c in PATH for which TABLE[c] is 0. + If no encoding was needed, return PATH, else return a new string allocated + in POOL. */ +static const char * +uri_escape(const char *path, const char table[], apr_pool_t *pool) +{ + svn_stringbuf_t *retstr; + apr_size_t i, copied = 0; + int c; + + retstr = svn_stringbuf_create_ensure(strlen(path), pool); + for (i = 0; path[i]; i++) + { + c = (unsigned char)path[i]; + if (table[c]) + continue; + + /* If we got here, we're looking at a character that isn't + supported by the (or at least, our) URI encoding scheme. We + need to escape this character. */ + + /* First things first, copy all the good stuff that we haven't + yet copied into our output buffer. */ + if (i - copied) + svn_stringbuf_appendbytes(retstr, path + copied, + i - copied); + + /* Now, write in our escaped character, consisting of the + '%' and two digits. We cast the C to unsigned char here because + the 'X' format character will be tempted to treat it as an unsigned + int...which causes problem when messing with 0x80-0xFF chars. + We also need space for a null as apr_snprintf will write one. */ + svn_stringbuf_ensure(retstr, retstr->len + 4); + apr_snprintf(retstr->data + retstr->len, 4, "%%%02X", (unsigned char)c); + retstr->len += 3; + + /* Finally, update our copy counter. */ + copied = i + 1; + } + + /* If we didn't encode anything, we don't need to duplicate the string. */ + if (retstr->len == 0) + return path; + + /* Anything left to copy? */ + if (i - copied) + svn_stringbuf_appendbytes(retstr, path + copied, i - copied); + + /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf + functions. */ + + return retstr->data; +} + + +const char * +svn_path_uri_encode(const char *path, apr_pool_t *pool) +{ + const char *ret; + + ret = uri_escape(path, svn_uri__char_validity, pool); + + /* Our interface guarantees a copy. */ + if (ret == path) + return apr_pstrdup(pool, path); + else + return ret; +} + +static const char iri_escape_chars[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + /* 128 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +const char * +svn_path_uri_from_iri(const char *iri, apr_pool_t *pool) +{ + return uri_escape(iri, iri_escape_chars, pool); +} + +static const char uri_autoescape_chars[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + + /* 64 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + + /* 128 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + /* 192 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +}; + +const char * +svn_path_uri_autoescape(const char *uri, apr_pool_t *pool) +{ + return uri_escape(uri, uri_autoescape_chars, pool); +} + +const char * +svn_path_uri_decode(const char *path, apr_pool_t *pool) +{ + svn_stringbuf_t *retstr; + apr_size_t i; + svn_boolean_t query_start = FALSE; + + /* avoid repeated realloc */ + retstr = svn_stringbuf_create_ensure(strlen(path) + 1, pool); + + retstr->len = 0; + for (i = 0; path[i]; i++) + { + char c = path[i]; + + if (c == '?') + { + /* Mark the start of the query string, if it exists. */ + query_start = TRUE; + } + else if (c == '+' && query_start) + { + /* Only do this if we are into the query string. + * RFC 2396, section 3.3 */ + c = ' '; + } + else if (c == '%' && svn_ctype_isxdigit(path[i + 1]) + && svn_ctype_isxdigit(path[i+2])) + { + char digitz[3]; + digitz[0] = path[++i]; + digitz[1] = path[++i]; + digitz[2] = '\0'; + c = (char)(strtol(digitz, NULL, 16)); + } + + retstr->data[retstr->len++] = c; + } + + /* Null-terminate this bad-boy. */ + retstr->data[retstr->len] = 0; + + return retstr->data; +} + + +const char * +svn_path_url_add_component2(const char *url, + const char *component, + apr_pool_t *pool) +{ + /* = svn_path_uri_encode() but without always copying */ + component = uri_escape(component, svn_uri__char_validity, pool); + + return svn_path_join_internal(url, component, pool); +} + +svn_error_t * +svn_path_get_absolute(const char **pabsolute, + const char *relative, + apr_pool_t *pool) +{ + if (svn_path_is_url(relative)) + { + *pabsolute = apr_pstrdup(pool, relative); + return SVN_NO_ERROR; + } + + return svn_dirent_get_absolute(pabsolute, relative, pool); +} + + +#if !defined(WIN32) && !defined(DARWIN) +/** Get APR's internal path encoding. */ +static svn_error_t * +get_path_encoding(svn_boolean_t *path_is_utf8, apr_pool_t *pool) +{ + apr_status_t apr_err; + int encoding_style; + + apr_err = apr_filepath_encoding(&encoding_style, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't determine the native path encoding")); + + /* ### What to do about APR_FILEPATH_ENCODING_UNKNOWN? + Well, for now we'll just punt to the svn_utf_ functions; + those will at least do the ASCII-subset check. */ + *path_is_utf8 = (encoding_style == APR_FILEPATH_ENCODING_UTF8); + return SVN_NO_ERROR; +} +#endif + + +svn_error_t * +svn_path_cstring_from_utf8(const char **path_apr, + const char *path_utf8, + apr_pool_t *pool) +{ +#if !defined(WIN32) && !defined(DARWIN) + svn_boolean_t path_is_utf8; + SVN_ERR(get_path_encoding(&path_is_utf8, pool)); + if (path_is_utf8) +#endif + { + *path_apr = apr_pstrdup(pool, path_utf8); + return SVN_NO_ERROR; + } +#if !defined(WIN32) && !defined(DARWIN) + else + return svn_utf_cstring_from_utf8(path_apr, path_utf8, pool); +#endif +} + + +svn_error_t * +svn_path_cstring_to_utf8(const char **path_utf8, + const char *path_apr, + apr_pool_t *pool) +{ +#if !defined(WIN32) && !defined(DARWIN) + svn_boolean_t path_is_utf8; + SVN_ERR(get_path_encoding(&path_is_utf8, pool)); + if (path_is_utf8) +#endif + { + *path_utf8 = apr_pstrdup(pool, path_apr); + return SVN_NO_ERROR; + } +#if !defined(WIN32) && !defined(DARWIN) + else + return svn_utf_cstring_to_utf8(path_utf8, path_apr, pool); +#endif +} + + +/* Return a copy of PATH, allocated from POOL, for which control + characters have been escaped using the form \NNN (where NNN is the + octal representation of the byte's ordinal value). */ +const char * +svn_path_illegal_path_escape(const char *path, apr_pool_t *pool) +{ + svn_stringbuf_t *retstr; + apr_size_t i, copied = 0; + int c; + + /* At least one control character: + strlen - 1 (control) + \ + N + N + N + null . */ + retstr = svn_stringbuf_create_ensure(strlen(path) + 4, pool); + for (i = 0; path[i]; i++) + { + c = (unsigned char)path[i]; + if (! svn_ctype_iscntrl(c)) + continue; + + /* If we got here, we're looking at a character that isn't + supported by the (or at least, our) URI encoding scheme. We + need to escape this character. */ + + /* First things first, copy all the good stuff that we haven't + yet copied into our output buffer. */ + if (i - copied) + svn_stringbuf_appendbytes(retstr, path + copied, + i - copied); + + /* Make sure buffer is big enough for '\' 'N' 'N' 'N' (and NUL) */ + svn_stringbuf_ensure(retstr, retstr->len + 5); + /*### The backslash separator doesn't work too great with Windows, + but it's what we'll use for consistency with invalid utf8 + formatting (until someone has a better idea) */ + apr_snprintf(retstr->data + retstr->len, 5, "\\%03o", (unsigned char)c); + retstr->len += 4; + + /* Finally, update our copy counter. */ + copied = i + 1; + } + + /* If we didn't encode anything, we don't need to duplicate the string. */ + if (retstr->len == 0) + return path; + + /* Anything left to copy? */ + if (i - copied) + svn_stringbuf_appendbytes(retstr, path + copied, i - copied); + + /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf + functions. */ + + return retstr->data; +} + +svn_error_t * +svn_path_check_valid(const char *path, apr_pool_t *pool) +{ + const char *c; + + for (c = path; *c; c++) + { + if (svn_ctype_iscntrl(*c)) + { + return svn_error_createf + (SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Invalid control character '0x%02x' in path '%s'"), + (unsigned char)*c, + svn_path_illegal_path_escape(svn_dirent_local_style(path, pool), + pool)); + } + } + + return SVN_NO_ERROR; +} + +void +svn_path_splitext(const char **path_root, + const char **path_ext, + const char *path, + apr_pool_t *pool) +{ + const char *last_dot, *last_slash; + + /* Easy out -- why do all the work when there's no way to report it? */ + if (! (path_root || path_ext)) + return; + + /* Do we even have a period in this thing? And if so, is there + anything after it? We look for the "rightmost" period in the + string. */ + last_dot = strrchr(path, '.'); + if (last_dot && (last_dot + 1 != '\0')) + { + /* If we have a period, we need to make sure it occurs in the + final path component -- that there's no path separator + between the last period and the end of the PATH -- otherwise, + it doesn't count. Also, we want to make sure that our period + isn't the first character of the last component. */ + last_slash = strrchr(path, '/'); + if ((last_slash && (last_dot > (last_slash + 1))) + || ((! last_slash) && (last_dot > path))) + { + if (path_root) + *path_root = apr_pstrmemdup(pool, path, + (last_dot - path + 1) * sizeof(*path)); + if (path_ext) + *path_ext = apr_pstrdup(pool, last_dot + 1); + return; + } + } + /* If we get here, we never found a suitable separator character, so + there's no split. */ + if (path_root) + *path_root = apr_pstrdup(pool, path); + if (path_ext) + *path_ext = ""; +} + + +/* Repository relative URLs (^/). */ + +svn_boolean_t +svn_path_is_repos_relative_url(const char *path) +{ + return (0 == strncmp("^/", path, 2)); +} + +svn_error_t * +svn_path_resolve_repos_relative_url(const char **absolute_url, + const char *relative_url, + const char *repos_root_url, + apr_pool_t *pool) +{ + if (! svn_path_is_repos_relative_url(relative_url)) + return svn_error_createf(SVN_ERR_BAD_URL, NULL, + _("Improper relative URL '%s'"), + relative_url); + + /* No assumptions are made about the canonicalization of the inut + * arguments, it is presumed that the output will be canonicalized after + * this function, which will remove any duplicate path separator. + */ + *absolute_url = apr_pstrcat(pool, repos_root_url, relative_url + 1, + (char *)NULL); + + return SVN_NO_ERROR; +} + diff --git a/subversion/libsvn_subr/pool.c b/subversion/libsvn_subr/pool.c new file mode 100644 index 000000000000..179ef796047d --- /dev/null +++ b/subversion/libsvn_subr/pool.c @@ -0,0 +1,142 @@ +/* pool.c: pool wrappers for Subversion + * + * ==================================================================== + * 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 <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#include <apr_general.h> +#include <apr_pools.h> +#include <apr_thread_mutex.h> + +#include "svn_pools.h" + + +#if APR_POOL_DEBUG +/* file_line for the non-debug case. */ +static const char SVN_FILE_LINE_UNDEFINED[] = "svn:<undefined>"; +#endif /* APR_POOL_DEBUG */ + + + +/*-----------------------------------------------------------------*/ + + +/* Pool allocation handler which just aborts, since we aren't generally + prepared to deal with out-of-memory errors. + */ +static int +abort_on_pool_failure(int retcode) +{ + /* Don't translate this string! It requires memory allocation to do so! + And we don't have any of it... */ + printf("Out of memory - terminating application.\n"); + abort(); + return 0; /* not reached */ +} + + +#if APR_POOL_DEBUG +#undef svn_pool_create_ex +#endif /* APR_POOL_DEBUG */ + +#if !APR_POOL_DEBUG + +apr_pool_t * +svn_pool_create_ex(apr_pool_t *parent_pool, apr_allocator_t *allocator) +{ + apr_pool_t *pool; + apr_pool_create_ex(&pool, parent_pool, abort_on_pool_failure, allocator); + return pool; +} + +/* Wrapper that ensures binary compatibility */ +apr_pool_t * +svn_pool_create_ex_debug(apr_pool_t *pool, apr_allocator_t *allocator, + const char *file_line) +{ + return svn_pool_create_ex(pool, allocator); +} + +#else /* APR_POOL_DEBUG */ + +apr_pool_t * +svn_pool_create_ex_debug(apr_pool_t *parent_pool, apr_allocator_t *allocator, + const char *file_line) +{ + apr_pool_t *pool; + apr_pool_create_ex_debug(&pool, parent_pool, abort_on_pool_failure, + allocator, file_line); + return pool; +} + +/* Wrapper that ensures binary compatibility */ +apr_pool_t * +svn_pool_create_ex(apr_pool_t *pool, apr_allocator_t *allocator) +{ + return svn_pool_create_ex_debug(pool, allocator, SVN_FILE_LINE_UNDEFINED); +} + +#endif /* APR_POOL_DEBUG */ + +apr_allocator_t * +svn_pool_create_allocator(svn_boolean_t thread_safe) +{ + apr_allocator_t *allocator; + apr_pool_t *pool; + + /* create the allocator and limit it's internal free list to keep + * memory usage in check */ + + if (apr_allocator_create(&allocator)) + abort_on_pool_failure(EXIT_FAILURE); + + apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE); + + /* create the root pool */ + + pool = svn_pool_create_ex(NULL, allocator); + apr_allocator_owner_set(allocator, pool); + +#if APR_POOL_DEBUG + apr_pool_tag (pool, "svn root pool"); +#endif + + /* By default, allocators are *not* thread-safe. We must provide a mutex + * if we want thread-safety for that mutex. */ + +#if APR_HAS_THREADS + if (thread_safe) + { + apr_thread_mutex_t *mutex; + apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool); + apr_allocator_mutex_set(allocator, mutex); + } +#endif + + /* better safe than sorry */ + SVN_ERR_ASSERT_NO_RETURN(allocator != NULL); + + return allocator; +} diff --git a/subversion/libsvn_subr/prompt.c b/subversion/libsvn_subr/prompt.c new file mode 100644 index 000000000000..92ee6a27888a --- /dev/null +++ b/subversion/libsvn_subr/prompt.c @@ -0,0 +1,954 @@ +/* + * prompt.c -- ask the user for authentication information. + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_lib.h> +#include <apr_poll.h> +#include <apr_portable.h> + +#include "svn_cmdline.h" +#include "svn_ctype.h" +#include "svn_string.h" +#include "svn_auth.h" +#include "svn_error.h" +#include "svn_path.h" + +#include "private/svn_cmdline_private.h" +#include "svn_private_config.h" + +#ifdef WIN32 +#include <conio.h> +#elif defined(HAVE_TERMIOS_H) +#include <signal.h> +#include <termios.h> +#endif + + + +/* Descriptor of an open terminal */ +typedef struct terminal_handle_t terminal_handle_t; +struct terminal_handle_t +{ + apr_file_t *infd; /* input file handle */ + apr_file_t *outfd; /* output file handle */ + svn_boolean_t noecho; /* terminal echo was turned off */ + svn_boolean_t close_handles; /* close handles when closing the terminal */ + apr_pool_t *pool; /* pool associated with the file handles */ + +#ifdef HAVE_TERMIOS_H + svn_boolean_t restore_state; /* terminal state was changed */ + apr_os_file_t osinfd; /* OS-specific handle for infd */ + struct termios attr; /* saved terminal attributes */ +#endif +}; + +/* Initialize safe state of terminal_handle_t. */ +static void +terminal_handle_init(terminal_handle_t *terminal, + apr_file_t *infd, apr_file_t *outfd, + svn_boolean_t noecho, svn_boolean_t close_handles, + apr_pool_t *pool) +{ + memset(terminal, 0, sizeof(*terminal)); + terminal->infd = infd; + terminal->outfd = outfd; + terminal->noecho = noecho; + terminal->close_handles = close_handles; + terminal->pool = pool; +} + +/* + * Common pool cleanup handler for terminal_handle_t. Closes TERMINAL. + * If CLOSE_HANDLES is TRUE, close the terminal file handles. + * If RESTORE_STATE is TRUE, restores the TERMIOS flags of the terminal. + */ +static apr_status_t +terminal_cleanup_handler(terminal_handle_t *terminal, + svn_boolean_t close_handles, + svn_boolean_t restore_state) +{ + apr_status_t status = APR_SUCCESS; + +#ifdef HAVE_TERMIOS_H + /* Restore terminal state flags. */ + if (restore_state && terminal->restore_state) + tcsetattr(terminal->osinfd, TCSANOW, &terminal->attr); +#endif + + /* Close terminal handles. */ + if (close_handles && terminal->close_handles) + { + apr_file_t *const infd = terminal->infd; + apr_file_t *const outfd = terminal->outfd; + + if (infd) + { + terminal->infd = NULL; + status = apr_file_close(infd); + } + + if (!status && outfd && outfd != infd) + { + terminal->outfd = NULL; + status = apr_file_close(terminal->outfd); + } + } + return status; +} + +/* Normal pool cleanup for a terminal. */ +static apr_status_t terminal_plain_cleanup(void *baton) +{ + return terminal_cleanup_handler(baton, FALSE, TRUE); +} + +/* Child pool cleanup for a terminal -- does not restore echo state. */ +static apr_status_t terminal_child_cleanup(void *baton) +{ + return terminal_cleanup_handler(baton, FALSE, FALSE); +} + +/* Explicitly close the terminal, removing its cleanup handlers. */ +static svn_error_t * +terminal_close(terminal_handle_t *terminal) +{ + apr_status_t status; + + /* apr_pool_cleanup_kill() removes both normal and child cleanup */ + apr_pool_cleanup_kill(terminal->pool, terminal, terminal_plain_cleanup); + + status = terminal_cleanup_handler(terminal, TRUE, TRUE); + if (status) + return svn_error_create(status, NULL, _("Can't close terminal")); + return SVN_NO_ERROR; +} + +/* Allocate and open *TERMINAL. If NOECHO is TRUE, try to turn off + terminal echo. Use POOL for all allocations.*/ +static svn_error_t * +terminal_open(terminal_handle_t **terminal, svn_boolean_t noecho, + apr_pool_t *pool) +{ + apr_status_t status; + +#ifdef WIN32 + /* On Windows, we'll use the console API directly if the process has + a console attached; otherwise we'll just use stdin and stderr. */ + const HANDLE conin = CreateFileW(L"CONIN$", GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + *terminal = apr_palloc(pool, sizeof(terminal_handle_t)); + if (conin != INVALID_HANDLE_VALUE) + { + /* The process has a console. */ + CloseHandle(conin); + terminal_handle_init(*terminal, NULL, NULL, noecho, FALSE, NULL); + return SVN_NO_ERROR; + } +#else /* !WIN32 */ + /* Without evidence to the contrary, we'll assume this is *nix and + try to open /dev/tty. If that fails, we'll use stdin for input + and stderr for prompting. */ + apr_file_t *tmpfd; + status = apr_file_open(&tmpfd, "/dev/tty", + APR_FOPEN_READ | APR_FOPEN_WRITE, + APR_OS_DEFAULT, pool); + *terminal = apr_palloc(pool, sizeof(terminal_handle_t)); + if (!status) + { + /* We have a terminal handle that we can use for input and output. */ + terminal_handle_init(*terminal, tmpfd, tmpfd, FALSE, TRUE, pool); + } +#endif /* !WIN32 */ + else + { + /* There is no terminal. Sigh. */ + apr_file_t *infd; + apr_file_t *outfd; + + status = apr_file_open_stdin(&infd, pool); + if (status) + return svn_error_wrap_apr(status, _("Can't open stdin")); + status = apr_file_open_stderr(&outfd, pool); + if (status) + return svn_error_wrap_apr(status, _("Can't open stderr")); + terminal_handle_init(*terminal, infd, outfd, FALSE, FALSE, pool); + } + +#ifdef HAVE_TERMIOS_H + /* Set terminal state */ + if (0 == apr_os_file_get(&(*terminal)->osinfd, (*terminal)->infd)) + { + if (0 == tcgetattr((*terminal)->osinfd, &(*terminal)->attr)) + { + struct termios attr = (*terminal)->attr; + /* Turn off signal handling and canonical input mode */ + attr.c_lflag &= ~(ISIG | ICANON); + attr.c_cc[VMIN] = 1; /* Read one byte at a time */ + attr.c_cc[VTIME] = 0; /* No timeout, wait indefinitely */ + attr.c_lflag &= ~(ECHO); /* Turn off echo */ + if (0 == tcsetattr((*terminal)->osinfd, TCSAFLUSH, &attr)) + { + (*terminal)->noecho = noecho; + (*terminal)->restore_state = TRUE; + } + } + } +#endif /* HAVE_TERMIOS_H */ + + /* Register pool cleanup to close handles and restore echo state. */ + apr_pool_cleanup_register((*terminal)->pool, *terminal, + terminal_plain_cleanup, + terminal_child_cleanup); + return SVN_NO_ERROR; +} + +/* Write a null-terminated STRING to TERMINAL. + Use POOL for allocations related to converting STRING from UTF-8. */ +static svn_error_t * +terminal_puts(const char *string, terminal_handle_t *terminal, + apr_pool_t *pool) +{ + svn_error_t *err; + apr_status_t status; + const char *converted; + + err = svn_cmdline_cstring_from_utf8(&converted, string, pool); + if (err) + { + svn_error_clear(err); + converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool); + } + +#ifdef WIN32 + if (!terminal->outfd) + { + /* See terminal_open; we're using Console I/O. */ + _cputs(converted); + return SVN_NO_ERROR; + } +#endif + + status = apr_file_write_full(terminal->outfd, converted, + strlen(converted), NULL); + if (!status) + status = apr_file_flush(terminal->outfd); + if (status) + return svn_error_wrap_apr(status, _("Can't write to terminal")); + return SVN_NO_ERROR; +} + +/* These codes can be returned from terminal_getc instead of a character. */ +#define TERMINAL_NONE 0x80000 /* no character read, retry */ +#define TERMINAL_DEL (TERMINAL_NONE + 1) /* the input was a deleteion */ +#define TERMINAL_EOL (TERMINAL_NONE + 2) /* end of input/end of line */ +#define TERMINAL_EOF (TERMINAL_NONE + 3) /* end of file during input */ + +/* Helper for terminal_getc: writes CH to OUTFD as a control char. */ +#ifndef WIN32 +static void +echo_control_char(char ch, apr_file_t *outfd) +{ + if (svn_ctype_iscntrl(ch)) + { + const char substitute = (ch < 32? '@' + ch : '?'); + apr_file_putc('^', outfd); + apr_file_putc(substitute, outfd); + } + else if (svn_ctype_isprint(ch)) + { + /* Pass printable characters unchanged. */ + apr_file_putc(ch, outfd); + } + else + { + /* Everything else is strange. */ + apr_file_putc('^', outfd); + apr_file_putc('!', outfd); + } +} +#endif /* WIN32 */ + +/* Read one character or control code from TERMINAL, returning it in CODE. + if CAN_ERASE and the input was a deletion, emit codes to erase the + last character displayed on the terminal. + Use POOL for all allocations. */ +static svn_error_t * +terminal_getc(int *code, terminal_handle_t *terminal, + svn_boolean_t can_erase, apr_pool_t *pool) +{ + const svn_boolean_t echo = !terminal->noecho; + apr_status_t status = APR_SUCCESS; + char ch; + +#ifdef WIN32 + if (!terminal->infd) + { + /* See terminal_open; we're using Console I/O. */ + + /* The following was hoisted from APR's getpass for Windows. */ + int concode = _getch(); + switch (concode) + { + case '\r': /* end-of-line */ + *code = TERMINAL_EOL; + if (echo) + _cputs("\r\n"); + break; + + case EOF: /* end-of-file */ + case 26: /* Ctrl+Z */ + *code = TERMINAL_EOF; + if (echo) + _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n")); + break; + + case 3: /* Ctrl+C, Ctrl+Break */ + /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */ + if (echo) + _cputs("^C\r\n"); + return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL); + + case 0: /* Function code prefix */ + case 0xE0: + concode = (concode << 4) | _getch(); + /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */ + if (concode == 0xE53 || concode == 0xE4B + || concode == 0x053 || concode == 0x04B) + { + *code = TERMINAL_DEL; + if (can_erase) + _cputs("\b \b"); + } + else + { + *code = TERMINAL_NONE; + _putch('\a'); + } + break; + + case '\b': /* BS */ + case 127: /* DEL */ + *code = TERMINAL_DEL; + if (can_erase) + _cputs("\b \b"); + break; + + default: + if (!apr_iscntrl(concode)) + { + *code = (int)(unsigned char)concode; + _putch(echo ? concode : '*'); + } + else + { + *code = TERMINAL_NONE; + _putch('\a'); + } + } + return SVN_NO_ERROR; + } +#elif defined(HAVE_TERMIOS_H) + if (terminal->restore_state) + { + /* We're using a bytewise-immediate termios input */ + const struct termios *const attr = &terminal->attr; + + status = apr_file_getc(&ch, terminal->infd); + if (status) + return svn_error_wrap_apr(status, _("Can't read from terminal")); + + if (ch == attr->c_cc[VINTR] || ch == attr->c_cc[VQUIT]) + { + /* Break */ + echo_control_char(ch, terminal->outfd); + return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL); + } + else if (ch == '\r' || ch == '\n' || ch == attr->c_cc[VEOL]) + { + /* Newline */ + *code = TERMINAL_EOL; + apr_file_putc('\n', terminal->outfd); + } + else if (ch == '\b' || ch == attr->c_cc[VERASE]) + { + /* Delete */ + *code = TERMINAL_DEL; + if (can_erase) + { + apr_file_putc('\b', terminal->outfd); + apr_file_putc(' ', terminal->outfd); + apr_file_putc('\b', terminal->outfd); + } + } + else if (ch == attr->c_cc[VEOF]) + { + /* End of input */ + *code = TERMINAL_EOF; + echo_control_char(ch, terminal->outfd); + } + else if (ch == attr->c_cc[VSUSP]) + { + /* Suspend */ + *code = TERMINAL_NONE; + kill(0, SIGTSTP); + } + else if (!apr_iscntrl(ch)) + { + /* Normal character */ + *code = (int)(unsigned char)ch; + apr_file_putc((echo ? ch : '*'), terminal->outfd); + } + else + { + /* Ignored character */ + *code = TERMINAL_NONE; + apr_file_putc('\a', terminal->outfd); + } + return SVN_NO_ERROR; + } +#endif /* HAVE_TERMIOS_H */ + + /* Fall back to plain stream-based I/O. */ +#ifndef WIN32 + /* Wait for input on termin. This code is based on + apr_wait_for_io_or_timeout(). + Note that this will return an EINTR on a signal. */ + { + apr_pollfd_t pollset; + int n; + + pollset.desc_type = APR_POLL_FILE; + pollset.desc.f = terminal->infd; + pollset.p = pool; + pollset.reqevents = APR_POLLIN; + + status = apr_poll(&pollset, 1, &n, -1); + + if (n == 1 && pollset.rtnevents & APR_POLLIN) + status = APR_SUCCESS; + } +#endif /* !WIN32 */ + + if (!status) + status = apr_file_getc(&ch, terminal->infd); + if (APR_STATUS_IS_EINTR(status)) + { + *code = TERMINAL_NONE; + return SVN_NO_ERROR; + } + else if (APR_STATUS_IS_EOF(status)) + { + *code = TERMINAL_EOF; + return SVN_NO_ERROR; + } + else if (status) + return svn_error_wrap_apr(status, _("Can't read from terminal")); + + *code = (int)(unsigned char)ch; + return SVN_NO_ERROR; +} + + +/* Set @a *result to the result of prompting the user with @a + * prompt_msg. Use @ *pb to get the cancel_func and cancel_baton. + * Do not call the cancel_func if @a *pb is NULL. + * Allocate @a *result in @a pool. + * + * If @a hide is true, then try to avoid displaying the user's input. + */ +static svn_error_t * +prompt(const char **result, + const char *prompt_msg, + svn_boolean_t hide, + svn_cmdline_prompt_baton2_t *pb, + apr_pool_t *pool) +{ + /* XXX: If this functions ever starts using members of *pb + * which were not included in svn_cmdline_prompt_baton_t, + * we need to update svn_cmdline_prompt_user2 and its callers. */ + + svn_boolean_t saw_first_half_of_eol = FALSE; + svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool); + terminal_handle_t *terminal; + int code; + char c; + + SVN_ERR(terminal_open(&terminal, hide, pool)); + SVN_ERR(terminal_puts(prompt_msg, terminal, pool)); + + while (1) + { + SVN_ERR(terminal_getc(&code, terminal, (strbuf->len > 0), pool)); + + /* Check for cancellation after a character has been read, some + input processing modes may eat ^C and we'll only notice a + cancellation signal after characters have been read -- + sometimes even after a newline. */ + if (pb) + SVN_ERR(pb->cancel_func(pb->cancel_baton)); + + switch (code) + { + case TERMINAL_NONE: + /* Nothing useful happened; retry. */ + continue; + + case TERMINAL_DEL: + /* Delete the last input character. terminal_getc takes care + of erasing the feedback from the terminal, if applicable. */ + svn_stringbuf_chop(strbuf, 1); + continue; + + case TERMINAL_EOL: + /* End-of-line means end of input. Trick the EOL-detection code + below to stop reading. */ + saw_first_half_of_eol = TRUE; + c = APR_EOL_STR[1]; /* Could be \0 but still stops reading. */ + break; + + case TERMINAL_EOF: + return svn_error_create( + APR_EOF, + terminal_close(terminal), + _("End of file while reading from terminal")); + + default: + /* Convert the returned code back to the character. */ + c = (char)code; + } + + if (saw_first_half_of_eol) + { + if (c == APR_EOL_STR[1]) + break; + else + saw_first_half_of_eol = FALSE; + } + else if (c == APR_EOL_STR[0]) + { + /* GCC might complain here: "warning: will never be executed" + * That's fine. This is a compile-time check for "\r\n\0" */ + if (sizeof(APR_EOL_STR) == 3) + { + saw_first_half_of_eol = TRUE; + continue; + } + else if (sizeof(APR_EOL_STR) == 2) + break; + else + /* ### APR_EOL_STR holds more than two chars? Who + ever heard of such a thing? */ + SVN_ERR_MALFUNCTION(); + } + + svn_stringbuf_appendbyte(strbuf, c); + } + + if (terminal->noecho) + { + /* If terminal echo was turned off, make sure future output + to the terminal starts on a new line, as expected. */ + SVN_ERR(terminal_puts(APR_EOL_STR, terminal, pool)); + } + SVN_ERR(terminal_close(terminal)); + + return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool); +} + + + +/** Prompt functions for auth providers. **/ + +/* Helper function for auth provider prompters: mention the + * authentication @a realm on stderr, in a manner appropriate for + * preceding a prompt; or if @a realm is null, then do nothing. + */ +static svn_error_t * +maybe_print_realm(const char *realm, apr_pool_t *pool) +{ + if (realm) + { + terminal_handle_t *terminal; + SVN_ERR(terminal_open(&terminal, FALSE, pool)); + SVN_ERR(terminal_puts( + apr_psprintf(pool, + _("Authentication realm: %s\n"), realm), + terminal, pool)); + SVN_ERR(terminal_close(terminal)); + } + + return SVN_NO_ERROR; +} + + +/* This implements 'svn_auth_simple_prompt_func_t'. */ +svn_error_t * +svn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p, + void *baton, + const char *realm, + const char *username, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *ret = apr_pcalloc(pool, sizeof(*ret)); + const char *pass_prompt; + svn_cmdline_prompt_baton2_t *pb = baton; + + SVN_ERR(maybe_print_realm(realm, pool)); + + if (username) + ret->username = apr_pstrdup(pool, username); + else + SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool)); + + pass_prompt = apr_psprintf(pool, _("Password for '%s': "), ret->username); + SVN_ERR(prompt(&(ret->password), pass_prompt, TRUE, pb, pool)); + ret->may_save = may_save; + *cred_p = ret; + return SVN_NO_ERROR; +} + + +/* This implements 'svn_auth_username_prompt_func_t'. */ +svn_error_t * +svn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_username_t *ret = apr_pcalloc(pool, sizeof(*ret)); + svn_cmdline_prompt_baton2_t *pb = baton; + + SVN_ERR(maybe_print_realm(realm, pool)); + + SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool)); + ret->may_save = may_save; + *cred_p = ret; + return SVN_NO_ERROR; +} + + +/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. */ +svn_error_t * +svn_cmdline_auth_ssl_server_trust_prompt + (svn_auth_cred_ssl_server_trust_t **cred_p, + void *baton, + const char *realm, + apr_uint32_t failures, + const svn_auth_ssl_server_cert_info_t *cert_info, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + const char *choice; + svn_stringbuf_t *msg; + svn_cmdline_prompt_baton2_t *pb = baton; + svn_stringbuf_t *buf = svn_stringbuf_createf + (pool, _("Error validating server certificate for '%s':\n"), realm); + + if (failures & SVN_AUTH_SSL_UNKNOWNCA) + { + svn_stringbuf_appendcstr + (buf, + _(" - The certificate is not issued by a trusted authority. Use the\n" + " fingerprint to validate the certificate manually!\n")); + } + + if (failures & SVN_AUTH_SSL_CNMISMATCH) + { + svn_stringbuf_appendcstr + (buf, _(" - The certificate hostname does not match.\n")); + } + + if (failures & SVN_AUTH_SSL_NOTYETVALID) + { + svn_stringbuf_appendcstr + (buf, _(" - The certificate is not yet valid.\n")); + } + + if (failures & SVN_AUTH_SSL_EXPIRED) + { + svn_stringbuf_appendcstr + (buf, _(" - The certificate has expired.\n")); + } + + if (failures & SVN_AUTH_SSL_OTHER) + { + svn_stringbuf_appendcstr + (buf, _(" - The certificate has an unknown error.\n")); + } + + msg = svn_stringbuf_createf + (pool, + _("Certificate information:\n" + " - Hostname: %s\n" + " - Valid: from %s until %s\n" + " - Issuer: %s\n" + " - Fingerprint: %s\n"), + cert_info->hostname, + cert_info->valid_from, + cert_info->valid_until, + cert_info->issuer_dname, + cert_info->fingerprint); + svn_stringbuf_appendstr(buf, msg); + + if (may_save) + { + svn_stringbuf_appendcstr + (buf, _("(R)eject, accept (t)emporarily or accept (p)ermanently? ")); + } + else + { + svn_stringbuf_appendcstr(buf, _("(R)eject or accept (t)emporarily? ")); + } + SVN_ERR(prompt(&choice, buf->data, FALSE, pb, pool)); + + if (choice[0] == 't' || choice[0] == 'T') + { + *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); + (*cred_p)->may_save = FALSE; + (*cred_p)->accepted_failures = failures; + } + else if (may_save && (choice[0] == 'p' || choice[0] == 'P')) + { + *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); + (*cred_p)->may_save = TRUE; + (*cred_p)->accepted_failures = failures; + } + else + { + *cred_p = NULL; + } + + return SVN_NO_ERROR; +} + + +/* This implements 'svn_auth_ssl_client_cert_prompt_func_t'. */ +svn_error_t * +svn_cmdline_auth_ssl_client_cert_prompt + (svn_auth_cred_ssl_client_cert_t **cred_p, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_ssl_client_cert_t *cred = NULL; + const char *cert_file = NULL; + const char *abs_cert_file = NULL; + svn_cmdline_prompt_baton2_t *pb = baton; + + SVN_ERR(maybe_print_realm(realm, pool)); + SVN_ERR(prompt(&cert_file, _("Client certificate filename: "), + FALSE, pb, pool)); + SVN_ERR(svn_dirent_get_absolute(&abs_cert_file, cert_file, pool)); + + cred = apr_palloc(pool, sizeof(*cred)); + cred->cert_file = abs_cert_file; + cred->may_save = may_save; + *cred_p = cred; + + return SVN_NO_ERROR; +} + + +/* This implements 'svn_auth_ssl_client_cert_pw_prompt_func_t'. */ +svn_error_t * +svn_cmdline_auth_ssl_client_cert_pw_prompt + (svn_auth_cred_ssl_client_cert_pw_t **cred_p, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_ssl_client_cert_pw_t *cred = NULL; + const char *result; + const char *text = apr_psprintf(pool, _("Passphrase for '%s': "), realm); + svn_cmdline_prompt_baton2_t *pb = baton; + + SVN_ERR(prompt(&result, text, TRUE, pb, pool)); + + cred = apr_pcalloc(pool, sizeof(*cred)); + cred->password = result; + cred->may_save = may_save; + *cred_p = cred; + + return SVN_NO_ERROR; +} + +/* This is a helper for plaintext prompt functions. */ +static svn_error_t * +plaintext_prompt_helper(svn_boolean_t *may_save_plaintext, + const char *realmstring, + const char *prompt_string, + const char *prompt_text, + void *baton, + apr_pool_t *pool) +{ + const char *answer = NULL; + svn_boolean_t answered = FALSE; + svn_cmdline_prompt_baton2_t *pb = baton; + const char *config_path = NULL; + terminal_handle_t *terminal; + + if (pb) + SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir, + SVN_CONFIG_CATEGORY_SERVERS, pool)); + + SVN_ERR(terminal_open(&terminal, FALSE, pool)); + SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text, + realmstring, config_path), + terminal, pool)); + SVN_ERR(terminal_close(terminal)); + + do + { + svn_error_t *err = prompt(&answer, prompt_string, FALSE, pb, pool); + if (err) + { + if (err->apr_err == SVN_ERR_CANCELLED) + { + svn_error_clear(err); + *may_save_plaintext = FALSE; + return SVN_NO_ERROR; + } + else + return err; + } + if (apr_strnatcasecmp(answer, _("yes")) == 0 || + apr_strnatcasecmp(answer, _("y")) == 0) + { + *may_save_plaintext = TRUE; + answered = TRUE; + } + else if (apr_strnatcasecmp(answer, _("no")) == 0 || + apr_strnatcasecmp(answer, _("n")) == 0) + { + *may_save_plaintext = FALSE; + answered = TRUE; + } + else + prompt_string = _("Please type 'yes' or 'no': "); + } + while (! answered); + + return SVN_NO_ERROR; +} + +/* This implements 'svn_auth_plaintext_prompt_func_t'. */ +svn_error_t * +svn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext, + const char *realmstring, + void *baton, + apr_pool_t *pool) +{ + const char *prompt_string = _("Store password unencrypted (yes/no)? "); + const char *prompt_text = + _("\n-----------------------------------------------------------------------" + "\nATTENTION! Your password for authentication realm:\n" + "\n" + " %s\n" + "\n" + "can only be stored to disk unencrypted! You are advised to configure\n" + "your system so that Subversion can store passwords encrypted, if\n" + "possible. See the documentation for details.\n" + "\n" + "You can avoid future appearances of this warning by setting the value\n" + "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n" + "'%s'.\n" + "-----------------------------------------------------------------------\n" + ); + + return plaintext_prompt_helper(may_save_plaintext, realmstring, + prompt_string, prompt_text, baton, + pool); +} + +/* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */ +svn_error_t * +svn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext, + const char *realmstring, + void *baton, + apr_pool_t *pool) +{ + const char *prompt_string = _("Store passphrase unencrypted (yes/no)? "); + const char *prompt_text = + _("\n-----------------------------------------------------------------------\n" + "ATTENTION! Your passphrase for client certificate:\n" + "\n" + " %s\n" + "\n" + "can only be stored to disk unencrypted! You are advised to configure\n" + "your system so that Subversion can store passphrase encrypted, if\n" + "possible. See the documentation for details.\n" + "\n" + "You can avoid future appearances of this warning by setting the value\n" + "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n" + "'no' in '%s'.\n" + "-----------------------------------------------------------------------\n" + ); + + return plaintext_prompt_helper(may_save_plaintext, realmstring, + prompt_string, prompt_text, baton, + pool); +} + + +/** Generic prompting. **/ + +svn_error_t * +svn_cmdline_prompt_user2(const char **result, + const char *prompt_str, + svn_cmdline_prompt_baton_t *baton, + apr_pool_t *pool) +{ + /* XXX: We know prompt doesn't use the new members + * of svn_cmdline_prompt_baton2_t. */ + return prompt(result, prompt_str, FALSE /* don't hide input */, + (svn_cmdline_prompt_baton2_t *)baton, pool); +} + +/* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */ +svn_error_t * +svn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password, + const char *keyring_name, + void *baton, + apr_pool_t *pool) +{ + const char *password; + const char *pass_prompt; + svn_cmdline_prompt_baton2_t *pb = baton; + + pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "), + keyring_name); + SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool)); + *keyring_password = apr_pstrdup(pool, password); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/properties.c b/subversion/libsvn_subr/properties.c new file mode 100644 index 000000000000..738d00f16682 --- /dev/null +++ b/subversion/libsvn_subr/properties.c @@ -0,0 +1,507 @@ +/* + * properties.c: stuff related to Subversion properties + * + * ==================================================================== + * 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_hash.h> +#include <apr_tables.h> +#include <string.h> /* for strncmp() */ +#include "svn_hash.h" +#include "svn_string.h" +#include "svn_props.h" +#include "svn_error.h" +#include "svn_ctype.h" +#include "private/svn_subr_private.h" + + +/* All Subversion-specific versioned node properties + * known to this client, that are applicable to both a file and a dir. + */ +#define SVN_PROP__NODE_COMMON_PROPS SVN_PROP_MERGEINFO, \ + SVN_PROP_TEXT_TIME, \ + SVN_PROP_OWNER, \ + SVN_PROP_GROUP, \ + SVN_PROP_UNIX_MODE, + +/* All Subversion-specific versioned node properties + * known to this client, that are applicable to a dir only. + */ +#define SVN_PROP__NODE_DIR_ONLY_PROPS SVN_PROP_IGNORE, \ + SVN_PROP_INHERITABLE_IGNORES, \ + SVN_PROP_INHERITABLE_AUTO_PROPS, \ + SVN_PROP_EXTERNALS, + +/* All Subversion-specific versioned node properties + * known to this client, that are applicable to a file only. + */ +#define SVN_PROP__NODE_FILE_ONLY_PROPS SVN_PROP_MIME_TYPE, \ + SVN_PROP_EOL_STYLE, \ + SVN_PROP_KEYWORDS, \ + SVN_PROP_EXECUTABLE, \ + SVN_PROP_NEEDS_LOCK, \ + SVN_PROP_SPECIAL, + +static const char *const known_rev_props[] + = { SVN_PROP_REVISION_ALL_PROPS + NULL }; + +static const char *const known_node_props[] + = { SVN_PROP__NODE_COMMON_PROPS + SVN_PROP__NODE_DIR_ONLY_PROPS + SVN_PROP__NODE_FILE_ONLY_PROPS + NULL }; + +static const char *const known_dir_props[] + = { SVN_PROP__NODE_COMMON_PROPS + SVN_PROP__NODE_DIR_ONLY_PROPS + NULL }; + +static const char *const known_file_props[] + = { SVN_PROP__NODE_COMMON_PROPS + SVN_PROP__NODE_FILE_ONLY_PROPS + NULL }; + +static svn_boolean_t +is_known_prop(const char *prop_name, + const char *const *known_props) +{ + while (*known_props) + { + if (strcmp(prop_name, *known_props++) == 0) + return TRUE; + } + return FALSE; +} + +svn_boolean_t +svn_prop_is_known_svn_rev_prop(const char *prop_name) +{ + return is_known_prop(prop_name, known_rev_props); +} + +svn_boolean_t +svn_prop_is_known_svn_node_prop(const char *prop_name) +{ + return is_known_prop(prop_name, known_node_props); +} + +svn_boolean_t +svn_prop_is_known_svn_file_prop(const char *prop_name) +{ + return is_known_prop(prop_name, known_file_props); +} + +svn_boolean_t +svn_prop_is_known_svn_dir_prop(const char *prop_name) +{ + return is_known_prop(prop_name, known_dir_props); +} + + +svn_boolean_t +svn_prop_is_svn_prop(const char *prop_name) +{ + return strncmp(prop_name, SVN_PROP_PREFIX, (sizeof(SVN_PROP_PREFIX) - 1)) + == 0; +} + + +svn_boolean_t +svn_prop_has_svn_prop(const apr_hash_t *props, apr_pool_t *pool) +{ + apr_hash_index_t *hi; + const void *prop_name; + + if (! props) + return FALSE; + + for (hi = apr_hash_first(pool, (apr_hash_t *)props); hi; + hi = apr_hash_next(hi)) + { + apr_hash_this(hi, &prop_name, NULL, NULL); + if (svn_prop_is_svn_prop((const char *) prop_name)) + return TRUE; + } + + return FALSE; +} + + +#define SIZEOF_WC_PREFIX (sizeof(SVN_PROP_WC_PREFIX) - 1) +#define SIZEOF_ENTRY_PREFIX (sizeof(SVN_PROP_ENTRY_PREFIX) - 1) + +svn_prop_kind_t +svn_property_kind2(const char *prop_name) +{ + + if (strncmp(prop_name, SVN_PROP_WC_PREFIX, SIZEOF_WC_PREFIX) == 0) + return svn_prop_wc_kind; + + if (strncmp(prop_name, SVN_PROP_ENTRY_PREFIX, SIZEOF_ENTRY_PREFIX) == 0) + return svn_prop_entry_kind; + + return svn_prop_regular_kind; +} + + +/* NOTE: this function is deprecated, but we cannot move it to deprecated.c + because we need the SIZEOF_*_PREFIX constant symbols defined above. */ +svn_prop_kind_t +svn_property_kind(int *prefix_len, + const char *prop_name) +{ + svn_prop_kind_t kind = svn_property_kind2(prop_name); + + if (prefix_len) + { + if (kind == svn_prop_wc_kind) + *prefix_len = SIZEOF_WC_PREFIX; + else if (kind == svn_prop_entry_kind) + *prefix_len = SIZEOF_ENTRY_PREFIX; + else + *prefix_len = 0; + } + + return kind; +} + + +svn_error_t * +svn_categorize_props(const apr_array_header_t *proplist, + apr_array_header_t **entry_props, + apr_array_header_t **wc_props, + apr_array_header_t **regular_props, + apr_pool_t *pool) +{ + int i; + if (entry_props) + *entry_props = apr_array_make(pool, 1, sizeof(svn_prop_t)); + if (wc_props) + *wc_props = apr_array_make(pool, 1, sizeof(svn_prop_t)); + if (regular_props) + *regular_props = apr_array_make(pool, 1, sizeof(svn_prop_t)); + + for (i = 0; i < proplist->nelts; i++) + { + svn_prop_t *prop, *newprop; + enum svn_prop_kind kind; + + prop = &APR_ARRAY_IDX(proplist, i, svn_prop_t); + kind = svn_property_kind2(prop->name); + newprop = NULL; + + if (kind == svn_prop_regular_kind) + { + if (regular_props) + newprop = apr_array_push(*regular_props); + } + else if (kind == svn_prop_wc_kind) + { + if (wc_props) + newprop = apr_array_push(*wc_props); + } + else if (kind == svn_prop_entry_kind) + { + if (entry_props) + newprop = apr_array_push(*entry_props); + } + else + /* Technically this can't happen, but might as well have the + code ready in case that ever changes. */ + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + "Bad property kind for property '%s'", + prop->name); + + if (newprop) + { + newprop->name = prop->name; + newprop->value = prop->value; + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_prop_diffs(apr_array_header_t **propdiffs, + const apr_hash_t *target_props, + const apr_hash_t *source_props, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + apr_array_header_t *ary = apr_array_make(pool, 1, sizeof(svn_prop_t)); + + /* Note: we will be storing the pointers to the keys (from the hashes) + into the propdiffs array. It is acceptable for us to + reference the same memory as the base/target_props hash. */ + + /* Loop over SOURCE_PROPS and examine each key. This will allow us to + detect any `deletion' events or `set-modification' events. */ + for (hi = apr_hash_first(pool, (apr_hash_t *)source_props); hi; + hi = apr_hash_next(hi)) + { + const void *key; + apr_ssize_t klen; + void *val; + const svn_string_t *propval1, *propval2; + + /* Get next property */ + apr_hash_this(hi, &key, &klen, &val); + propval1 = val; + + /* Does property name exist in TARGET_PROPS? */ + propval2 = apr_hash_get((apr_hash_t *)target_props, key, klen); + + if (propval2 == NULL) + { + /* Add a delete event to the array */ + svn_prop_t *p = apr_array_push(ary); + p->name = key; + p->value = NULL; + } + else if (! svn_string_compare(propval1, propval2)) + { + /* Add a set (modification) event to the array */ + svn_prop_t *p = apr_array_push(ary); + p->name = key; + p->value = svn_string_dup(propval2, pool); + } + } + + /* Loop over TARGET_PROPS and examine each key. This allows us to + detect `set-creation' events */ + for (hi = apr_hash_first(pool, (apr_hash_t *)target_props); hi; + hi = apr_hash_next(hi)) + { + const void *key; + apr_ssize_t klen; + void *val; + const svn_string_t *propval; + + /* Get next property */ + apr_hash_this(hi, &key, &klen, &val); + propval = val; + + /* Does property name exist in SOURCE_PROPS? */ + if (NULL == apr_hash_get((apr_hash_t *)source_props, key, klen)) + { + /* Add a set (creation) event to the array */ + svn_prop_t *p = apr_array_push(ary); + p->name = key; + p->value = svn_string_dup(propval, pool); + } + } + + /* Done building our array of user events. */ + *propdiffs = ary; + + return SVN_NO_ERROR; +} + +apr_hash_t * +svn_prop__patch(const apr_hash_t *original_props, + const apr_array_header_t *prop_changes, + apr_pool_t *pool) +{ + apr_hash_t *props = apr_hash_copy(pool, original_props); + int i; + + for (i = 0; i < prop_changes->nelts; i++) + { + const svn_prop_t *p = &APR_ARRAY_IDX(prop_changes, i, svn_prop_t); + + svn_hash_sets(props, p->name, p->value); + } + return props; +} + +/** + * Reallocate the members of PROP using POOL. + */ +static void +svn_prop__members_dup(svn_prop_t *prop, apr_pool_t *pool) +{ + if (prop->name) + prop->name = apr_pstrdup(pool, prop->name); + if (prop->value) + prop->value = svn_string_dup(prop->value, pool); +} + +svn_prop_t * +svn_prop_dup(const svn_prop_t *prop, apr_pool_t *pool) +{ + svn_prop_t *new_prop = apr_palloc(pool, sizeof(*new_prop)); + + *new_prop = *prop; + + svn_prop__members_dup(new_prop, pool); + + return new_prop; +} + +apr_array_header_t * +svn_prop_array_dup(const apr_array_header_t *array, apr_pool_t *pool) +{ + int i; + apr_array_header_t *new_array = apr_array_copy(pool, array); + for (i = 0; i < new_array->nelts; ++i) + { + svn_prop_t *elt = &APR_ARRAY_IDX(new_array, i, svn_prop_t); + svn_prop__members_dup(elt, pool); + } + return new_array; +} + +apr_array_header_t * +svn_prop_hash_to_array(const apr_hash_t *hash, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + apr_array_header_t *array = apr_array_make(pool, + apr_hash_count((apr_hash_t *)hash), + sizeof(svn_prop_t)); + + for (hi = apr_hash_first(pool, (apr_hash_t *)hash); hi; + hi = apr_hash_next(hi)) + { + const void *key; + void *val; + svn_prop_t prop; + + apr_hash_this(hi, &key, NULL, &val); + prop.name = key; + prop.value = val; + APR_ARRAY_PUSH(array, svn_prop_t) = prop; + } + + return array; +} + +apr_hash_t * +svn_prop_hash_dup(const apr_hash_t *hash, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + apr_hash_t *new_hash = apr_hash_make(pool); + + for (hi = apr_hash_first(pool, (apr_hash_t *)hash); hi; + hi = apr_hash_next(hi)) + { + const void *key; + apr_ssize_t klen; + void *prop; + + apr_hash_this(hi, &key, &klen, &prop); + apr_hash_set(new_hash, apr_pstrmemdup(pool, key, klen), klen, + svn_string_dup(prop, pool)); + } + return new_hash; +} + +apr_hash_t * +svn_prop_array_to_hash(const apr_array_header_t *properties, + apr_pool_t *pool) +{ + int i; + apr_hash_t *prop_hash = apr_hash_make(pool); + + for (i = 0; i < properties->nelts; i++) + { + const svn_prop_t *prop = &APR_ARRAY_IDX(properties, i, svn_prop_t); + svn_hash_sets(prop_hash, prop->name, prop->value); + } + + return prop_hash; +} + +svn_boolean_t +svn_prop_is_boolean(const char *prop_name) +{ + /* If we end up with more than 3 of these, we should probably put + them in a table and use bsearch. With only three, it doesn't + make any speed difference. */ + if (strcmp(prop_name, SVN_PROP_EXECUTABLE) == 0 + || strcmp(prop_name, SVN_PROP_NEEDS_LOCK) == 0 + || strcmp(prop_name, SVN_PROP_SPECIAL) == 0) + return TRUE; + return FALSE; +} + + +svn_boolean_t +svn_prop_needs_translation(const char *propname) +{ + /* ### Someday, we may want to be picky and choosy about which + properties require UTF8 and EOL conversion. For now, all "svn:" + props need it. */ + + return svn_prop_is_svn_prop(propname); +} + + +svn_boolean_t +svn_prop_name_is_valid(const char *prop_name) +{ + const char *p = prop_name; + + /* The characters we allow use identical representations in UTF8 + and ASCII, so we can just test for the appropriate ASCII codes. + But we can't use standard C character notation ('A', 'B', etc) + because there's no guarantee that this C environment is using + ASCII. */ + + if (!(svn_ctype_isalpha(*p) + || *p == SVN_CTYPE_ASCII_COLON + || *p == SVN_CTYPE_ASCII_UNDERSCORE)) + return FALSE; + p++; + for (; *p; p++) + { + if (!(svn_ctype_isalnum(*p) + || *p == SVN_CTYPE_ASCII_MINUS + || *p == SVN_CTYPE_ASCII_DOT + || *p == SVN_CTYPE_ASCII_COLON + || *p == SVN_CTYPE_ASCII_UNDERSCORE)) + return FALSE; + } + return TRUE; +} + +const char * +svn_prop_get_value(const apr_hash_t *props, + const char *prop_name) +{ + svn_string_t *str; + + if (!props) + return NULL; + + str = svn_hash_gets((apr_hash_t *)props, prop_name); + + if (str) + return str->data; + + return NULL; +} diff --git a/subversion/libsvn_subr/pseudo_md5.c b/subversion/libsvn_subr/pseudo_md5.c new file mode 100644 index 000000000000..8c194f7dd104 --- /dev/null +++ b/subversion/libsvn_subr/pseudo_md5.c @@ -0,0 +1,422 @@ +/* + * This is work is derived from material Copyright RSA Data Security, Inc. + * + * The RSA copyright statement and Licence for that original material is + * included below. This is followed by the Apache copyright statement and + * licence for the modifications made to that material. + */ + +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +/* 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. + */ + +/* + * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0 + * MD5 crypt() function, which is licenced as follows: + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + */ + +/* + * pseudo_md5.c: md5-esque hash sum calculation for short data blocks. + * Code taken and adapted from the APR (see licenses above). + */ +#include "private/svn_pseudo_md5.h" + +/* Constants for MD5 calculation. + */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#if defined(_MSC_VER) && _MSC_VER >= 1310 +#pragma intrinsic(_rotl) +#define ROTATE_LEFT(x, n) (_rotl(x,n)) +#else +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) +#endif + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + * Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* The idea of the functions below is as follows: + * + * - The core MD5 algorithm does not assume that the "important" data + * is at the begin of the encryption block, followed by e.g. 0. + * Instead, all bits are equally relevant. + * + * - If some bytes in the input are known to be 0, we may hard-code them. + * With the previous property, it is safe to move them to the upper end + * of the encryption block to maximize the number of steps that can be + * pre-calculated. + * + * - Variable-length streams will use the upper 8 byte of the last + * encryption block to store the stream length in bits (to make 0, 00, + * 000, ... etc. produce different hash sums). + * + * - We will hash at most 63 bytes, i.e. 504 bits. In the standard stream + * implementation, the upper 6 bytes of the last encryption block would + * be 0. We will put at least one non-NULL value in the last 4 bytes. + * Therefore, our input will always be different to a standard MD5 stream + * implementation in either block count, content or both. + * + * - Our length indicator also varies with the number bytes in the input. + * Hence, different pseudo-MD5 input length produces different output + * (with "cryptographic probability") even if the content is all 0 or + * otherwise identical. + * + * - Collisions between pseudo-MD5 and pseudo-MD5 as well as pseudo-MD5 + * and standard MD5 are as likely as any other MD5 collision. + */ + +void svn__pseudo_md5_15(apr_uint32_t digest[4], + const apr_uint32_t x[4]) +{ + apr_uint32_t a = 0x67452301; + apr_uint32_t b = 0xefcdab89; + apr_uint32_t c = 0x98badcfe; + apr_uint32_t d = 0x10325476; + + /* make sure byte 63 gets the marker independently of BE / LE */ + apr_uint32_t x3n = x[3] ^ 0xffffffff; + + /* Round 1 */ + FF(a, b, c, d, 0, S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, 0, S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, 0, S13, 0x242070db); /* 3 */ + FF(b, c, d, a, 0, S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, 0, S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, 0, S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, 0, S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, 0, S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, 0, S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, 0, S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, 0, S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, 0, S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, x[0], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, x[1], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, x[2], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, x3n, S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG(a, b, c, d, 0, S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, 0, S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, 0, S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, 0, S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, 0, S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, 0, S22, 0x2441453); /* 22 */ + GG(c, d, a, b, x3n, S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, 0, S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, 0, S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, x[2], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, 0, S23, 0xf4d50d87); /* 27 */ + GG(b, c, d, a, 0, S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, x[1], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, 0, S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, 0, S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, x[0], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH(a, b, c, d, 0, S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, 0, S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, 0, S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, x[2], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, 0, S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, 0, S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, 0, S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, 0, S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, x[1], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, 0, S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, 0, S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, 0, S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, 0, S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, x[0], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, x3n, S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, 0, S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II(a, b, c, d, 0, S41, 0xf4292244); /* 49 */ + II(d, a, b, c, 0, S42, 0x432aff97); /* 50 */ + II(c, d, a, b, x[2], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, 0, S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, x[0], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, 0, S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, 0, S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, 0, S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, 0, S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, x3n, S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, 0, S43, 0xa3014314); /* 59 */ + II(b, c, d, a, x[1], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, 0, S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, 0, S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, 0, S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, 0, S44, 0xeb86d391); /* 64 */ + + digest[0] = a; + digest[1] = b; + digest[2] = c; + digest[3] = d; +} + +void svn__pseudo_md5_31(apr_uint32_t digest[4], + const apr_uint32_t x[8]) +{ + apr_uint32_t a = 0x67452301; + apr_uint32_t b = 0xefcdab89; + apr_uint32_t c = 0x98badcfe; + apr_uint32_t d = 0x10325476; + + /* make sure byte 63 gets the marker independently of BE / LE */ + apr_uint32_t x7n = x[7] ^ 0xfefefefe; + + /* Round 1 */ + FF(a, b, c, d, 0, S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, 0, S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, 0, S13, 0x242070db); /* 3 */ + FF(b, c, d, a, 0, S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, 0, S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, 0, S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, 0, S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, 0, S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, x[0], S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, x[1], S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, x[2], S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, x[3], S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, x[4], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, x[5], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, x[6], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, x7n, S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG(a, b, c, d, 0, S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, 0, S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, x[3], S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, 0, S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, 0, S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, x[2], S22, 0x2441453); /* 22 */ + GG(c, d, a, b, x7n, S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, 0, S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, x[1], S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, x[6], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, 0, S23, 0xf4d50d87); /* 27 */ + GG(b, c, d, a, x[0], S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, x[5], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, 0, S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, 0, S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, x[4], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH(a, b, c, d, 0, S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, x[0], S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, x[3], S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, x[6], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, 0, S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, 0, S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, 0, S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, x[2], S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, x[5], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, 0, S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, 0, S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, 0, S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, x[1], S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, x[4], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, x7n, S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, 0, S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II(a, b, c, d, 0, S41, 0xf4292244); /* 49 */ + II(d, a, b, c, 0, S42, 0x432aff97); /* 50 */ + II(c, d, a, b, x[6], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, 0, S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, x[4], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, 0, S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, x[2], S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, 0, S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, x[0], S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, x7n, S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, 0, S43, 0xa3014314); /* 59 */ + II(b, c, d, a, x[5], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, 0, S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, x[3], S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, 0, S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, x[1], S44, 0xeb86d391); /* 64 */ + + digest[0] = a; + digest[1] = b; + digest[2] = c; + digest[3] = d; +} + +void svn__pseudo_md5_63(apr_uint32_t digest[4], + const apr_uint32_t x[16]) +{ + apr_uint32_t a = 0x67452301; + apr_uint32_t b = 0xefcdab89; + apr_uint32_t c = 0x98badcfe; + apr_uint32_t d = 0x10325476; + + /* make sure byte 63 gets the marker independently of BE / LE */ + apr_uint32_t x15n = x[15] ^ 0xfcfcfcfc; + + /* Round 1 */ + FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */ + FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, x15n, S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG(c, d, a, b, x15n, S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ + GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, x15n, S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ + II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ + II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, x15n, S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ + II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ + + digest[0] = a; + digest[1] = b; + digest[2] = c; + digest[3] = d; +} diff --git a/subversion/libsvn_subr/quoprint.c b/subversion/libsvn_subr/quoprint.c new file mode 100644 index 000000000000..bb9869d89c89 --- /dev/null +++ b/subversion/libsvn_subr/quoprint.c @@ -0,0 +1,309 @@ +/* + * quoprint.c: quoted-printable encoding and decoding functions + * + * ==================================================================== + * 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 <string.h> + +#include <apr.h> +#include <apr_pools.h> +#include <apr_general.h> /* for APR_INLINE */ + +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_error.h" +#include "svn_quoprint.h" + + +/* Caveats: + + (1) This code is for the encoding and decoding of binary data + only. Thus, CRLF sequences are encoded as =0D=0A, and we + don't have to worry about tabs and spaces coming before + hard newlines, since there aren't any. + + (2) The decoder does no error reporting, and instead throws + away invalid sequences. It also discards CRLF sequences, + since those can only appear in the encoding of text data. + + (3) The decoder does not strip whitespace at the end of a + line, so it is not actually compliant with RFC 2045. + (Such whitespace should never occur, even in the encoding + of text data, but RFC 2045 requires a decoder to detect + that a transport agent has added trailing whitespace). + + (4) The encoder is tailored to make output embeddable in XML, + which means it quotes <>'"& as well as the characters + required by RFC 2045. */ + +#define QUOPRINT_LINELEN 76 +#define VALID_LITERAL(c) ((c) == '\t' || ((c) >= ' ' && (c) <= '~' \ + && (c) != '=')) +#define ENCODE_AS_LITERAL(c) (VALID_LITERAL(c) && (c) != '\t' && (c) != '<' \ + && (c) != '>' && (c) != '\'' && (c) != '"' \ + && (c) != '&') +static const char hextab[] = "0123456789ABCDEF"; + + + +/* Binary input --> quoted-printable-encoded output */ + +struct encode_baton { + svn_stream_t *output; + int linelen; /* Bytes output so far on this line */ + apr_pool_t *pool; +}; + + +/* Quoted-printable-encode a byte string which may or may not be the + totality of the data being encoded. *LINELEN carries the length of + the current output line; initialize it to 0. Output will be + appended to STR. */ +static void +encode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len, + int *linelen) +{ + char buf[3]; + const char *p; + + /* Keep encoding three-byte groups until we run out. */ + for (p = data; p < data + len; p++) + { + /* Encode this character. */ + if (ENCODE_AS_LITERAL(*p)) + { + svn_stringbuf_appendbyte(str, *p); + (*linelen)++; + } + else + { + buf[0] = '='; + buf[1] = hextab[(*p >> 4) & 0xf]; + buf[2] = hextab[*p & 0xf]; + svn_stringbuf_appendbytes(str, buf, 3); + *linelen += 3; + } + + /* Make sure our output lines don't exceed QUOPRINT_LINELEN. */ + if (*linelen + 3 > QUOPRINT_LINELEN) + { + svn_stringbuf_appendcstr(str, "=\n"); + *linelen = 0; + } + } +} + + +/* Write handler for svn_quoprint_encode. */ +static svn_error_t * +encode_data(void *baton, const char *data, apr_size_t *len) +{ + struct encode_baton *eb = baton; + apr_pool_t *subpool = svn_pool_create(eb->pool); + svn_stringbuf_t *encoded = svn_stringbuf_create_empty(subpool); + apr_size_t enclen; + svn_error_t *err = SVN_NO_ERROR; + + /* Encode this block of data and write it out. */ + encode_bytes(encoded, data, *len, &eb->linelen); + enclen = encoded->len; + if (enclen != 0) + err = svn_stream_write(eb->output, encoded->data, &enclen); + svn_pool_destroy(subpool); + return err; +} + + +/* Close handler for svn_quoprint_encode(). */ +static svn_error_t * +finish_encoding_data(void *baton) +{ + struct encode_baton *eb = baton; + svn_error_t *err = SVN_NO_ERROR; + apr_size_t len; + + /* Terminate the current output line if it's not empty. */ + if (eb->linelen > 0) + { + len = 2; + err = svn_stream_write(eb->output, "=\n", &len); + } + + /* Pass on the close request and clean up the baton. */ + if (err == SVN_NO_ERROR) + err = svn_stream_close(eb->output); + svn_pool_destroy(eb->pool); + return err; +} + + +svn_stream_t * +svn_quoprint_encode(svn_stream_t *output, apr_pool_t *pool) +{ + apr_pool_t *subpool = svn_pool_create(pool); + struct encode_baton *eb = apr_palloc(subpool, sizeof(*eb)); + svn_stream_t *stream; + + eb->output = output; + eb->linelen = 0; + eb->pool = subpool; + stream = svn_stream_create(eb, pool); + svn_stream_set_write(stream, encode_data); + svn_stream_set_close(stream, finish_encoding_data); + return stream; +} + + +svn_stringbuf_t * +svn_quoprint_encode_string(const svn_stringbuf_t *str, apr_pool_t *pool) +{ + svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool); + int linelen = 0; + + encode_bytes(encoded, str->data, str->len, &linelen); + if (linelen > 0) + svn_stringbuf_appendcstr(encoded, "=\n"); + return encoded; +} + + + +/* Quoted-printable-encoded input --> binary output */ + +struct decode_baton { + svn_stream_t *output; + char buf[3]; /* Bytes waiting to be decoded */ + int buflen; /* Number of bytes waiting */ + apr_pool_t *pool; +}; + + +/* Decode a byte string which may or may not be the total amount of + data being decoded. INBUF and *INBUFLEN carry the leftover bytes + from call to call. Have room for four bytes in INBUF and + initialize *INBUFLEN to 0 and *DONE to FALSE. Output will be + appended to STR. */ +static void +decode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len, + char *inbuf, int *inbuflen) +{ + const char *p, *find1, *find2; + char c; + + for (p = data; p <= data + len; p++) + { + /* Append this byte to the buffer and see what we have. */ + inbuf[(*inbuflen)++] = *p; + if (*inbuf != '=') + { + /* Literal character; append it if it's valid as such. */ + if (VALID_LITERAL(*inbuf)) + svn_stringbuf_appendbyte(str, *inbuf); + *inbuflen = 0; + } + else if (*inbuf == '=' && *inbuflen == 2 && inbuf[1] == '\n') + { + /* Soft newline; ignore. */ + *inbuflen = 0; + } + else if (*inbuf == '=' && *inbuflen == 3) + { + /* Encoded character; decode it and append. */ + find1 = strchr(hextab, inbuf[1]); + find2 = strchr(hextab, inbuf[2]); + if (find1 != NULL && find2 != NULL) + { + c = (char)(((find1 - hextab) << 4) | (find2 - hextab)); + svn_stringbuf_appendbyte(str, c); + } + *inbuflen = 0; + } + } +} + + +/* Write handler for svn_quoprint_decode. */ +static svn_error_t * +decode_data(void *baton, const char *data, apr_size_t *len) +{ + struct decode_baton *db = baton; + apr_pool_t *subpool; + svn_stringbuf_t *decoded; + apr_size_t declen; + svn_error_t *err = SVN_NO_ERROR; + + /* Decode this block of data. */ + subpool = svn_pool_create(db->pool); + decoded = svn_stringbuf_create_empty(subpool); + decode_bytes(decoded, data, *len, db->buf, &db->buflen); + + /* Write the output, clean up, go home. */ + declen = decoded->len; + if (declen != 0) + err = svn_stream_write(db->output, decoded->data, &declen); + svn_pool_destroy(subpool); + return err; +} + + +/* Close handler for svn_quoprint_decode(). */ +static svn_error_t * +finish_decoding_data(void *baton) +{ + struct decode_baton *db = baton; + svn_error_t *err; + + /* Pass on the close request and clean up the baton. */ + err = svn_stream_close(db->output); + svn_pool_destroy(db->pool); + return err; +} + + +svn_stream_t * +svn_quoprint_decode(svn_stream_t *output, apr_pool_t *pool) +{ + apr_pool_t *subpool = svn_pool_create(pool); + struct decode_baton *db = apr_palloc(subpool, sizeof(*db)); + svn_stream_t *stream; + + db->output = output; + db->buflen = 0; + db->pool = subpool; + stream = svn_stream_create(db, pool); + svn_stream_set_write(stream, decode_data); + svn_stream_set_close(stream, finish_decoding_data); + return stream; +} + + +svn_stringbuf_t * +svn_quoprint_decode_string(const svn_stringbuf_t *str, apr_pool_t *pool) +{ + svn_stringbuf_t *decoded = svn_stringbuf_create_empty(pool); + char ingroup[4]; + int ingrouplen = 0; + + decode_bytes(decoded, str->data, str->len, ingroup, &ingrouplen); + return decoded; +} diff --git a/subversion/libsvn_subr/sha1.c b/subversion/libsvn_subr/sha1.c new file mode 100644 index 000000000000..45470cb10afb --- /dev/null +++ b/subversion/libsvn_subr/sha1.c @@ -0,0 +1,82 @@ +/* + * sha1.c: SHA1 checksum routines + * + * ==================================================================== + * 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_sha1.h> + +#include "sha1.h" + + + +/* The SHA1 digest for the empty string. */ +static const unsigned char svn_sha1__empty_string_digest_array[] = { + 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, + 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 +}; + +const unsigned char * +svn_sha1__empty_string_digest(void) +{ + return svn_sha1__empty_string_digest_array; +} + + +const char * +svn_sha1__digest_to_cstring_display(const unsigned char digest[], + apr_pool_t *pool) +{ + static const char *hex = "0123456789abcdef"; + char *str = apr_palloc(pool, (APR_SHA1_DIGESTSIZE * 2) + 1); + int i; + + for (i = 0; i < APR_SHA1_DIGESTSIZE; i++) + { + str[i*2] = hex[digest[i] >> 4]; + str[i*2+1] = hex[digest[i] & 0x0f]; + } + str[i*2] = '\0'; + + return str; +} + + +const char * +svn_sha1__digest_to_cstring(const unsigned char digest[], apr_pool_t *pool) +{ + static const unsigned char zeros_digest[APR_SHA1_DIGESTSIZE] = { 0 }; + + if (memcmp(digest, zeros_digest, APR_SHA1_DIGESTSIZE) != 0) + return svn_sha1__digest_to_cstring_display(digest, pool); + else + return NULL; +} + + +svn_boolean_t +svn_sha1__digests_match(const unsigned char d1[], const unsigned char d2[]) +{ + static const unsigned char zeros[APR_SHA1_DIGESTSIZE] = { 0 }; + + return ((memcmp(d1, zeros, APR_SHA1_DIGESTSIZE) == 0) + || (memcmp(d2, zeros, APR_SHA1_DIGESTSIZE) == 0) + || (memcmp(d1, d2, APR_SHA1_DIGESTSIZE) == 0)); +} diff --git a/subversion/libsvn_subr/sha1.h b/subversion/libsvn_subr/sha1.h new file mode 100644 index 000000000000..976810b4d13e --- /dev/null +++ b/subversion/libsvn_subr/sha1.h @@ -0,0 +1,70 @@ +/* + * sha1.h: Converting and comparing SHA1 checksums + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_SHA1_H +#define SVN_LIBSVN_SUBR_SHA1_H + +#include <apr_pools.h> +#include "svn_types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/* The SHA1 digest for the empty string. */ +const unsigned char * +svn_sha1__empty_string_digest(void); + + +/* Return the hex representation of DIGEST, which must be + * APR_SHA1_DIGESTSIZE bytes long, allocating the string in POOL. + */ +const char * +svn_sha1__digest_to_cstring_display(const unsigned char digest[], + apr_pool_t *pool); + + +/* Return the hex representation of DIGEST, which must be + * APR_SHA1_DIGESTSIZE bytes long, allocating the string in POOL. + * If DIGEST is all zeros, then return NULL. + */ +const char * +svn_sha1__digest_to_cstring(const unsigned char digest[], + apr_pool_t *pool); + + +/** Compare digests D1 and D2, each APR_SHA1_DIGESTSIZE bytes long. + * If neither is all zeros, and they do not match, then return FALSE; + * else return TRUE. + */ +svn_boolean_t +svn_sha1__digests_match(const unsigned char d1[], + const unsigned char d2[]); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_SHA1_H */ diff --git a/subversion/libsvn_subr/simple_providers.c b/subversion/libsvn_subr/simple_providers.c new file mode 100644 index 000000000000..e70770a2c31c --- /dev/null +++ b/subversion/libsvn_subr/simple_providers.c @@ -0,0 +1,734 @@ +/* + * simple_providers.c: providers for SVN_AUTH_CRED_SIMPLE + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_pools.h> +#include "svn_auth.h" +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_config.h" +#include "svn_user.h" + +#include "private/svn_auth_private.h" + +#include "svn_private_config.h" + +#include "auth.h" + +/*-----------------------------------------------------------------------*/ +/* File provider */ +/*-----------------------------------------------------------------------*/ + +/* The keys that will be stored on disk. These serve the same role as + similar constants in other providers. */ +#define AUTHN_USERNAME_KEY "username" +#define AUTHN_PASSWORD_KEY "password" +#define AUTHN_PASSTYPE_KEY "passtype" + +/* Baton type for the simple provider. */ +typedef struct simple_provider_baton_t +{ + svn_auth_plaintext_prompt_func_t plaintext_prompt_func; + void *prompt_baton; + /* We cache the user's answer to the plaintext prompt, keyed + * by realm, in case we'll be called multiple times for the + * same realm. */ + apr_hash_t *plaintext_answers; +} simple_provider_baton_t; + + +/* Implementation of svn_auth__password_get_t that retrieves + the plaintext password from CREDS. */ +svn_error_t * +svn_auth__simple_password_get(svn_boolean_t *done, + const char **password, + apr_hash_t *creds, + const char *realmstring, + const char *username, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + svn_string_t *str; + + *done = FALSE; + + str = svn_hash_gets(creds, AUTHN_USERNAME_KEY); + if (str && username && strcmp(str->data, username) == 0) + { + str = svn_hash_gets(creds, AUTHN_PASSWORD_KEY); + if (str && str->data) + { + *password = str->data; + *done = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* Implementation of svn_auth__password_set_t that stores + the plaintext password in CREDS. */ +svn_error_t * +svn_auth__simple_password_set(svn_boolean_t *done, + apr_hash_t *creds, + const char *realmstring, + const char *username, + const char *password, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + svn_hash_sets(creds, AUTHN_PASSWORD_KEY, svn_string_create(password, pool)); + *done = TRUE; + + return SVN_NO_ERROR; +} + +/* Set **USERNAME to the username retrieved from CREDS; ignore + other parameters. *USERNAME will have the same lifetime as CREDS. */ +static svn_boolean_t +simple_username_get(const char **username, + apr_hash_t *creds, + const char *realmstring, + svn_boolean_t non_interactive) +{ + svn_string_t *str; + str = svn_hash_gets(creds, AUTHN_USERNAME_KEY); + if (str && str->data) + { + *username = str->data; + return TRUE; + } + return FALSE; +} + + +svn_error_t * +svn_auth__simple_creds_cache_get(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + svn_auth__password_get_t password_get, + const char *passtype, + apr_pool_t *pool) +{ + const char *config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + svn_config_t *cfg = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS); + const char *server_group = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SERVER_GROUP); + const char *username = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_USERNAME); + const char *password = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_PASSWORD); + svn_boolean_t non_interactive = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NON_INTERACTIVE) + != NULL; + const char *default_username = NULL; /* Default username from cache. */ + const char *default_password = NULL; /* Default password from cache. */ + + /* This checks if we should save the CREDS, iff saving the credentials is + allowed by the run-time configuration. */ + svn_boolean_t need_to_save = FALSE; + apr_hash_t *creds_hash = NULL; + svn_error_t *err; + svn_string_t *str; + + /* Try to load credentials from a file on disk, based on the + realmstring. Don't throw an error, though: if something went + wrong reading the file, no big deal. What really matters is that + we failed to get the creds, so allow the auth system to try the + next provider. */ + err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SIMPLE, + realmstring, config_dir, pool); + if (err) + { + svn_error_clear(err); + err = NULL; + } + else if (creds_hash) + { + /* We have something in the auth cache for this realm. */ + svn_boolean_t have_passtype = FALSE; + + /* The password type in the auth data must match the + mangler's type, otherwise the password must be + interpreted by another provider. */ + str = svn_hash_gets(creds_hash, AUTHN_PASSTYPE_KEY); + if (str && str->data) + if (passtype && (0 == strcmp(str->data, passtype))) + have_passtype = TRUE; + + /* See if we need to save this username if it is not present in + auth cache. */ + if (username) + { + if (!simple_username_get(&default_username, creds_hash, realmstring, + non_interactive)) + { + need_to_save = TRUE; + } + else + { + if (strcmp(default_username, username) != 0) + need_to_save = TRUE; + } + } + + /* See if we need to save this password if it is not present in + auth cache. */ + if (password) + { + if (have_passtype) + { + svn_boolean_t done; + + SVN_ERR(password_get(&done, &default_password, creds_hash, + realmstring, username, parameters, + non_interactive, pool)); + if (!done) + { + need_to_save = TRUE; + } + else + { + if (strcmp(default_password, password) != 0) + need_to_save = TRUE; + } + } + } + + /* If we don't have a username and a password yet, we try the + auth cache */ + if (! (username && password)) + { + if (! username) + if (!simple_username_get(&username, creds_hash, realmstring, + non_interactive)) + username = NULL; + + if (username && ! password) + { + if (! have_passtype) + password = NULL; + else + { + svn_boolean_t done; + + SVN_ERR(password_get(&done, &password, creds_hash, + realmstring, username, parameters, + non_interactive, pool)); + if (!done) + password = NULL; + + /* If the auth data didn't contain a password type, + force a write to upgrade the format of the auth + data file. */ + if (password && ! have_passtype) + need_to_save = TRUE; + } + } + } + } + else + { + /* Nothing was present in the auth cache, so indicate that these + credentials should be saved. */ + need_to_save = TRUE; + } + + /* If we don't have a username yet, check the 'servers' file */ + if (! username) + { + username = svn_config_get_server_setting(cfg, server_group, + SVN_CONFIG_OPTION_USERNAME, + NULL); + } + + /* Ask the OS for the username if we have a password but no + username. */ + if (password && ! username) + username = svn_user_get_name(pool); + + if (username && password) + { + svn_auth_cred_simple_t *creds = apr_pcalloc(pool, sizeof(*creds)); + creds->username = username; + creds->password = password; + creds->may_save = need_to_save; + *credentials = creds; + } + else + *credentials = NULL; + + *iter_baton = NULL; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth__simple_creds_cache_set(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + svn_auth__password_set_t password_set, + const char *passtype, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *creds = credentials; + apr_hash_t *creds_hash = NULL; + const char *config_dir; + svn_error_t *err; + svn_boolean_t dont_store_passwords = + svn_hash_gets(parameters, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS) != NULL; + svn_boolean_t non_interactive = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NON_INTERACTIVE) + != NULL; + svn_boolean_t no_auth_cache = + (! creds->may_save) || (svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE) + != NULL); + + /* Make sure we've been passed a passtype. */ + SVN_ERR_ASSERT(passtype != NULL); + + *saved = FALSE; + + if (no_auth_cache) + return SVN_NO_ERROR; + + config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + + /* Put the username into the credentials hash. */ + creds_hash = apr_hash_make(pool); + svn_hash_sets(creds_hash, AUTHN_USERNAME_KEY, + svn_string_create(creds->username, pool)); + + /* Don't store passwords in any form if the user has told + * us not to do so. */ + if (! dont_store_passwords) + { + svn_boolean_t may_save_password = FALSE; + + /* If the password is going to be stored encrypted, go right + * ahead and store it to disk. Else determine whether saving + * in plaintext is OK. */ + if (passtype && + (strcmp(passtype, SVN_AUTH__WINCRYPT_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__KEYCHAIN_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__KWALLET_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__GPG_AGENT_PASSWORD_TYPE) == 0)) + { + may_save_password = TRUE; + } + else + { +#ifdef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + may_save_password = FALSE; +#else + const char *store_plaintext_passwords = + svn_hash_gets(parameters, SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS); + simple_provider_baton_t *b = + (simple_provider_baton_t *)provider_baton; + + if (store_plaintext_passwords + && svn_cstring_casecmp(store_plaintext_passwords, + SVN_CONFIG_ASK) == 0) + { + if (non_interactive) + /* In non-interactive mode, the default behaviour is + * to not store the password, because it is usually + * passed on the command line. */ + may_save_password = FALSE; + else if (b->plaintext_prompt_func) + { + /* We're interactive, and the client provided a + * prompt callback. So we can ask the user. + * + * Check for a cached answer before prompting. */ + svn_boolean_t *cached_answer; + cached_answer = svn_hash_gets(b->plaintext_answers, + realmstring); + if (cached_answer != NULL) + may_save_password = *cached_answer; + else + { + apr_pool_t *cached_answer_pool; + + /* Nothing cached for this realm, prompt the user. */ + SVN_ERR((*b->plaintext_prompt_func)(&may_save_password, + realmstring, + b->prompt_baton, + pool)); + + /* Cache the user's answer in case we're called again + * for the same realm. + * + * We allocate the answer cache in the hash table's pool + * to make sure that is has the same life time as the + * hash table itself. This means that the answer will + * survive across RA sessions -- which is important, + * because otherwise we'd prompt users once per RA session. + */ + cached_answer_pool = apr_hash_pool_get(b->plaintext_answers); + cached_answer = apr_palloc(cached_answer_pool, + sizeof(svn_boolean_t)); + *cached_answer = may_save_password; + svn_hash_sets(b->plaintext_answers, realmstring, + cached_answer); + } + } + else + { + /* TODO: We might want to default to not storing if the + * prompt callback is NULL, i.e. have may_save_password + * default to FALSE here, in order to force clients to + * implement the callback. + * + * This would change the semantics of old API though. + * + * So for now, clients that don't implement the callback + * and provide no explicit value for + * SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS + * cause unencrypted passwords to be stored by default. + * Needless to say, our own client is sane, but who knows + * what other clients are doing. + */ + may_save_password = TRUE; + } + } + else if (store_plaintext_passwords + && svn_cstring_casecmp(store_plaintext_passwords, + SVN_CONFIG_FALSE) == 0) + { + may_save_password = FALSE; + } + else if (!store_plaintext_passwords + || svn_cstring_casecmp(store_plaintext_passwords, + SVN_CONFIG_TRUE) == 0) + { + may_save_password = TRUE; + } + else + { + return svn_error_createf + (SVN_ERR_BAD_CONFIG_VALUE, NULL, + _("Config error: invalid value '%s' for option '%s'"), + store_plaintext_passwords, + SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS); + } +#endif + } + + if (may_save_password) + { + SVN_ERR(password_set(saved, creds_hash, realmstring, + creds->username, creds->password, + parameters, non_interactive, pool)); + if (*saved && passtype) + /* Store the password type with the auth data, so that we + know which provider owns the password. */ + svn_hash_sets(creds_hash, AUTHN_PASSTYPE_KEY, + svn_string_create(passtype, pool)); + } + } + + /* Save credentials to disk. */ + err = svn_config_write_auth_data(creds_hash, SVN_AUTH_CRED_SIMPLE, + realmstring, config_dir, pool); + if (err) + *saved = FALSE; + + /* ### return error? */ + svn_error_clear(err); + + return SVN_NO_ERROR; +} + +/* Get cached (unencrypted) credentials from the simple provider's cache. */ +static svn_error_t * +simple_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_get(credentials, iter_baton, + provider_baton, parameters, + realmstring, + svn_auth__simple_password_get, + SVN_AUTH__SIMPLE_PASSWORD_TYPE, + pool); +} + +/* Save (unencrypted) credentials to the simple provider's cache. */ +static svn_error_t * +simple_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_set(saved, credentials, provider_baton, + parameters, realmstring, + svn_auth__simple_password_set, + SVN_AUTH__SIMPLE_PASSWORD_TYPE, + pool); +} + +static const svn_auth_provider_t simple_provider = { + SVN_AUTH_CRED_SIMPLE, + simple_first_creds, + NULL, + simple_save_creds +}; + + +/* Public API */ +void +svn_auth_get_simple_provider2 + (svn_auth_provider_object_t **provider, + svn_auth_plaintext_prompt_func_t plaintext_prompt_func, + void* prompt_baton, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + simple_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb)); + + pb->plaintext_prompt_func = plaintext_prompt_func; + pb->prompt_baton = prompt_baton; + pb->plaintext_answers = apr_hash_make(pool); + + po->vtable = &simple_provider; + po->provider_baton = pb; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Prompt provider */ +/*-----------------------------------------------------------------------*/ + +/* Baton type for username/password prompting. */ +typedef struct simple_prompt_provider_baton_t +{ + svn_auth_simple_prompt_func_t prompt_func; + void *prompt_baton; + + /* how many times to re-prompt after the first one fails */ + int retry_limit; +} simple_prompt_provider_baton_t; + + +/* Iteration baton type for username/password prompting. */ +typedef struct simple_prompt_iter_baton_t +{ + /* how many times we've reprompted */ + int retries; +} simple_prompt_iter_baton_t; + + + +/*** Helper Functions ***/ +static svn_error_t * +prompt_for_simple_creds(svn_auth_cred_simple_t **cred_p, + simple_prompt_provider_baton_t *pb, + apr_hash_t *parameters, + const char *realmstring, + svn_boolean_t first_time, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + const char *default_username = NULL; + const char *default_password = NULL; + + *cred_p = NULL; + + /* If we're allowed to check for default usernames and passwords, do + so. */ + if (first_time) + { + default_username = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_USERNAME); + + /* No default username? Try the auth cache. */ + if (! default_username) + { + const char *config_dir = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_DIR); + apr_hash_t *creds_hash = NULL; + svn_string_t *str; + svn_error_t *err; + + err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SIMPLE, + realmstring, config_dir, pool); + svn_error_clear(err); + if (! err && creds_hash) + { + str = svn_hash_gets(creds_hash, AUTHN_USERNAME_KEY); + if (str && str->data) + default_username = str->data; + } + } + + /* Still no default username? Try the 'servers' file. */ + if (! default_username) + { + svn_config_t *cfg = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS); + const char *server_group = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SERVER_GROUP); + default_username = + svn_config_get_server_setting(cfg, server_group, + SVN_CONFIG_OPTION_USERNAME, + NULL); + } + + /* Still no default username? Try the UID. */ + if (! default_username) + default_username = svn_user_get_name(pool); + + default_password = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_PASSWORD); + } + + /* If we have defaults, just build the cred here and return it. + * + * ### I do wonder why this is here instead of in a separate + * ### 'defaults' provider that would run before the prompt + * ### provider... Hmmm. + */ + if (default_username && default_password) + { + *cred_p = apr_palloc(pool, sizeof(**cred_p)); + (*cred_p)->username = apr_pstrdup(pool, default_username); + (*cred_p)->password = apr_pstrdup(pool, default_password); + (*cred_p)->may_save = TRUE; + } + else + { + SVN_ERR(pb->prompt_func(cred_p, pb->prompt_baton, realmstring, + default_username, may_save, pool)); + } + + return SVN_NO_ERROR; +} + + +/* Our first attempt will use any default username/password passed + in, and prompt for the remaining stuff. */ +static svn_error_t * +simple_prompt_first_creds(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + simple_prompt_provider_baton_t *pb = provider_baton; + simple_prompt_iter_baton_t *ibaton = apr_pcalloc(pool, sizeof(*ibaton)); + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t **) credentials_p, + pb, parameters, realmstring, TRUE, + ! no_auth_cache, pool)); + + ibaton->retries = 0; + *iter_baton = ibaton; + + return SVN_NO_ERROR; +} + + +/* Subsequent attempts to fetch will ignore the default values, and + simply re-prompt for both, up to a maximum of ib->pb->retry_limit. */ +static svn_error_t * +simple_prompt_next_creds(void **credentials_p, + void *iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + simple_prompt_iter_baton_t *ib = iter_baton; + simple_prompt_provider_baton_t *pb = provider_baton; + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + if ((pb->retry_limit >= 0) && (ib->retries >= pb->retry_limit)) + { + /* give up, go on to next provider. */ + *credentials_p = NULL; + return SVN_NO_ERROR; + } + ib->retries++; + + return prompt_for_simple_creds((svn_auth_cred_simple_t **) credentials_p, + pb, parameters, realmstring, FALSE, + ! no_auth_cache, pool); +} + +static const svn_auth_provider_t simple_prompt_provider = { + SVN_AUTH_CRED_SIMPLE, + simple_prompt_first_creds, + simple_prompt_next_creds, + NULL, +}; + + +/* Public API */ +void +svn_auth_get_simple_prompt_provider + (svn_auth_provider_object_t **provider, + svn_auth_simple_prompt_func_t prompt_func, + void *prompt_baton, + int retry_limit, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + simple_prompt_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb)); + + pb->prompt_func = prompt_func; + pb->prompt_baton = prompt_baton; + pb->retry_limit = retry_limit; + + po->vtable = &simple_prompt_provider; + po->provider_baton = pb; + *provider = po; +} diff --git a/subversion/libsvn_subr/skel.c b/subversion/libsvn_subr/skel.c new file mode 100644 index 000000000000..ed12db094ad0 --- /dev/null +++ b/subversion/libsvn_subr/skel.c @@ -0,0 +1,881 @@ +/* skel.c --- parsing and unparsing skeletons + * + * ==================================================================== + * 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 <string.h> +#include "svn_string.h" +#include "svn_error.h" +#include "svn_props.h" +#include "svn_pools.h" +#include "private/svn_skel.h" +#include "private/svn_string_private.h" + + +/* Parsing skeletons. */ + +enum char_type { + type_nothing = 0, + type_space = 1, + type_digit = 2, + type_paren = 3, + type_name = 4 +}; + + +/* We can't use the <ctype.h> macros here, because they are locale- + dependent. The syntax of a skel is specified directly in terms of + byte values, and is independent of locale. */ + +static const enum char_type skel_char_type[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, + + /* 64 */ + 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 0, 3, 0, 0, + 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, + + /* 128 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* 192 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + + +/* ### WTF? since when is number conversion LOCALE DEPENDENT? */ +/* stsp: In C99, various numerical string properties such as decimal point, + * thousands separator, and the plus/minus sign are locale dependent. */ + +/* Converting text to numbers. */ + +/* Return the value of the string of digits at DATA as an ASCII + decimal number. The string is at most LEN bytes long. The value + of the number is at most MAX. Set *END to the address of the first + byte after the number, or zero if an error occurred while + converting the number (overflow, for example). + + We would like to use strtoul, but that family of functions is + locale-dependent, whereas we're trying to parse data in a + locale-independent format. */ +static apr_size_t +getsize(const char *data, apr_size_t len, + const char **endptr, apr_size_t max) +{ + /* We can't detect overflow by simply comparing value against max, + since multiplying value by ten can overflow in strange ways if + max is close to the limits of apr_size_t. For example, suppose + that max is 54, and apr_size_t is six bits long; its range is + 0..63. If we're parsing the number "502", then value will be 50 + after parsing the first two digits. 50 * 10 = 500. But 500 + doesn't fit in an apr_size_t, so it'll be truncated to 500 mod 64 + = 52, which is less than max, so we'd fail to recognize the + overflow. Furthermore, it *is* greater than 50, so you can't + detect overflow by checking whether value actually increased + after each multiplication --- sometimes it does increase, but + it's still wrong. + + So we do the check for overflow before we multiply value and add + in the new digit. */ + apr_size_t max_prefix = max / 10; + apr_size_t max_digit = max % 10; + apr_size_t i; + apr_size_t value = 0; + + for (i = 0; i < len && '0' <= data[i] && data[i] <= '9'; i++) + { + apr_size_t digit = data[i] - '0'; + + /* Check for overflow. */ + if (value > max_prefix + || (value == max_prefix && digit > max_digit)) + { + *endptr = 0; + return 0; + } + + value = (value * 10) + digit; + } + + /* There must be at least one digit there. */ + if (i == 0) + { + *endptr = 0; + return 0; + } + else + { + *endptr = data + i; + return value; + } +} + + +/* Checking validity of skels. */ +static svn_error_t * +skel_err(const char *skel_type) +{ + return svn_error_createf(SVN_ERR_FS_MALFORMED_SKEL, NULL, + "Malformed%s%s skeleton", + skel_type ? " " : "", + skel_type ? skel_type : ""); +} + + +static svn_boolean_t +is_valid_proplist_skel(const svn_skel_t *skel) +{ + int len = svn_skel__list_length(skel); + + if ((len >= 0) && (len & 1) == 0) + { + svn_skel_t *elt; + + for (elt = skel->children; elt; elt = elt->next) + if (! elt->is_atom) + return FALSE; + + return TRUE; + } + + return FALSE; +} + +static svn_boolean_t +is_valid_iproplist_skel(const svn_skel_t *skel) +{ + int len = svn_skel__list_length(skel); + + if ((len >= 0) && (len & 1) == 0) + { + svn_skel_t *elt; + + for (elt = skel->children; elt; elt = elt->next) + { + if (!elt->is_atom) + return FALSE; + + if (elt->next == NULL) + return FALSE; + + elt = elt->next; + + if (! is_valid_proplist_skel(elt)) + return FALSE; + } + + return TRUE; + } + + return FALSE; +} + + +static svn_skel_t *parse(const char *data, apr_size_t len, + apr_pool_t *pool); +static svn_skel_t *list(const char *data, apr_size_t len, + apr_pool_t *pool); +static svn_skel_t *implicit_atom(const char *data, apr_size_t len, + apr_pool_t *pool); +static svn_skel_t *explicit_atom(const char *data, apr_size_t len, + apr_pool_t *pool); + + +svn_skel_t * +svn_skel__parse(const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + return parse(data, len, pool); +} + + +/* Parse any kind of skel object --- atom, or list. */ +static svn_skel_t * +parse(const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + char c; + + /* The empty string isn't a valid skel. */ + if (len <= 0) + return NULL; + + c = *data; + + /* Is it a list, or an atom? */ + if (c == '(') + return list(data, len, pool); + + /* Is it a string with an implicit length? */ + if (skel_char_type[(unsigned char) c] == type_name) + return implicit_atom(data, len, pool); + + /* Otherwise, we assume it's a string with an explicit length; + svn_skel__getsize will catch the error. */ + else + return explicit_atom(data, len, pool); +} + + +static svn_skel_t * +list(const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + const char *end = data + len; + const char *list_start; + + /* Verify that the list starts with an opening paren. At the + moment, all callers have checked this already, but it's more + robust this way. */ + if (data >= end || *data != '(') + return NULL; + + /* Mark where the list starts. */ + list_start = data; + + /* Skip the opening paren. */ + data++; + + /* Parse the children. */ + { + svn_skel_t *children = NULL; + svn_skel_t **tail = &children; + + for (;;) + { + svn_skel_t *element; + + /* Skip any whitespace. */ + while (data < end + && skel_char_type[(unsigned char) *data] == type_space) + data++; + + /* End of data, but no closing paren? */ + if (data >= end) + return NULL; + + /* End of list? */ + if (*data == ')') + { + data++; + break; + } + + /* Parse the next element in the list. */ + element = parse(data, end - data, pool); + if (! element) + return NULL; + + /* Link that element into our list. */ + element->next = NULL; + *tail = element; + tail = &element->next; + + /* Advance past that element. */ + data = element->data + element->len; + } + + /* Construct the return value. */ + { + svn_skel_t *s = apr_pcalloc(pool, sizeof(*s)); + + s->is_atom = FALSE; + s->data = list_start; + s->len = data - list_start; + s->children = children; + + return s; + } + } +} + + +/* Parse an atom with implicit length --- one that starts with a name + character, terminated by whitespace, '(', ')', or end-of-data. */ +static svn_skel_t * +implicit_atom(const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + const char *start = data; + const char *end = data + len; + svn_skel_t *s; + + /* Verify that the atom starts with a name character. At the + moment, all callers have checked this already, but it's more + robust this way. */ + if (data >= end || skel_char_type[(unsigned char) *data] != type_name) + return NULL; + + /* Find the end of the string. */ + while (++data < end + && skel_char_type[(unsigned char) *data] != type_space + && skel_char_type[(unsigned char) *data] != type_paren) + ; + + /* Allocate the skel representing this string. */ + s = apr_pcalloc(pool, sizeof(*s)); + s->is_atom = TRUE; + s->data = start; + s->len = data - start; + + return s; +} + + +/* Parse an atom with explicit length --- one that starts with a byte + length, as a decimal ASCII number. */ +static svn_skel_t * +explicit_atom(const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + const char *end = data + len; + const char *next; + apr_size_t size; + svn_skel_t *s; + + /* Parse the length. */ + size = getsize(data, end - data, &next, end - data); + data = next; + + /* Exit if we overflowed, or there wasn't a valid number there. */ + if (! data) + return NULL; + + /* Skip the whitespace character after the length. */ + if (data >= end || skel_char_type[(unsigned char) *data] != type_space) + return NULL; + data++; + + /* Check the length. */ + if (data + size > end) + return NULL; + + /* Allocate the skel representing this string. */ + s = apr_pcalloc(pool, sizeof(*s)); + s->is_atom = TRUE; + s->data = data; + s->len = size; + + return s; +} + + + +/* Unparsing skeletons. */ + +static apr_size_t estimate_unparsed_size(const svn_skel_t *skel); +static svn_stringbuf_t *unparse(const svn_skel_t *skel, + svn_stringbuf_t *str); + + +svn_stringbuf_t * +svn_skel__unparse(const svn_skel_t *skel, apr_pool_t *pool) +{ + svn_stringbuf_t *str + = svn_stringbuf_create_ensure(estimate_unparsed_size(skel) + 200, pool); + + return unparse(skel, str); +} + + +/* Return an estimate of the number of bytes that the external + representation of SKEL will occupy. Since reallocing is expensive + in pools, it's worth trying to get the buffer size right the first + time. */ +static apr_size_t +estimate_unparsed_size(const svn_skel_t *skel) +{ + if (skel->is_atom) + { + if (skel->len < 100) + /* If we have to use the explicit-length form, that'll be + two bytes for the length, one byte for the space, and + the contents. */ + return skel->len + 3; + else + return skel->len + 30; + } + else + { + apr_size_t total_len; + svn_skel_t *child; + + /* Allow space for opening and closing parens, and a space + between each pair of elements. */ + total_len = 2; + for (child = skel->children; child; child = child->next) + total_len += estimate_unparsed_size(child) + 1; + + return total_len; + } +} + + +/* Return non-zero iff we should use the implicit-length form for SKEL. + Assume that SKEL is an atom. */ +static svn_boolean_t +use_implicit(const svn_skel_t *skel) +{ + /* If it's null, or long, we should use explicit-length form. */ + if (skel->len == 0 + || skel->len >= 100) + return FALSE; + + /* If it doesn't start with a name character, we must use + explicit-length form. */ + if (skel_char_type[(unsigned char) skel->data[0]] != type_name) + return FALSE; + + /* If it contains any whitespace or parens, then we must use + explicit-length form. */ + { + apr_size_t i; + + for (i = 1; i < skel->len; i++) + if (skel_char_type[(unsigned char) skel->data[i]] == type_space + || skel_char_type[(unsigned char) skel->data[i]] == type_paren) + return FALSE; + } + + /* If we can't reject it for any of the above reasons, then we can + use implicit-length form. */ + return TRUE; +} + + +/* Append the concrete representation of SKEL to the string STR. */ +static svn_stringbuf_t * +unparse(const svn_skel_t *skel, svn_stringbuf_t *str) +{ + if (skel->is_atom) + { + /* Append an atom to STR. */ + if (use_implicit(skel)) + svn_stringbuf_appendbytes(str, skel->data, skel->len); + else + { + /* Append the length to STR. Ensure enough space for at least + * one 64 bit int. */ + char buf[200 + SVN_INT64_BUFFER_SIZE]; + apr_size_t length_len; + + length_len = svn__ui64toa(buf, skel->len); + + SVN_ERR_ASSERT_NO_RETURN(length_len > 0); + + /* Make sure we have room for the length, the space, and the + atom's contents. */ + svn_stringbuf_ensure(str, str->len + length_len + 1 + skel->len); + svn_stringbuf_appendbytes(str, buf, length_len); + svn_stringbuf_appendbyte(str, ' '); + svn_stringbuf_appendbytes(str, skel->data, skel->len); + } + } + else + { + /* Append a list to STR: an opening parenthesis, the list elements + * separated by a space, and a closing parenthesis. */ + svn_skel_t *child; + + svn_stringbuf_appendbyte(str, '('); + + for (child = skel->children; child; child = child->next) + { + unparse(child, str); + if (child->next) + svn_stringbuf_appendbyte(str, ' '); + } + + svn_stringbuf_appendbyte(str, ')'); + } + + return str; +} + + + +/* Building skels. */ + + +svn_skel_t * +svn_skel__str_atom(const char *str, apr_pool_t *pool) +{ + svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel)); + skel->is_atom = TRUE; + skel->data = str; + skel->len = strlen(str); + + return skel; +} + + +svn_skel_t * +svn_skel__mem_atom(const void *addr, + apr_size_t len, + apr_pool_t *pool) +{ + svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel)); + skel->is_atom = TRUE; + skel->data = addr; + skel->len = len; + + return skel; +} + + +svn_skel_t * +svn_skel__make_empty_list(apr_pool_t *pool) +{ + svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel)); + return skel; +} + +svn_skel_t *svn_skel__dup(const svn_skel_t *src_skel, svn_boolean_t dup_data, + apr_pool_t *result_pool) +{ + svn_skel_t *skel = apr_pmemdup(result_pool, src_skel, sizeof(svn_skel_t)); + + if (dup_data && skel->data) + { + if (skel->is_atom) + skel->data = apr_pmemdup(result_pool, skel->data, skel->len); + else + { + /* When creating a skel this would be NULL, 0 for a list. + When parsing a string to a skel this might point to real data + delimiting the sublist. We don't copy that from here. */ + skel->data = NULL; + skel->len = 0; + } + } + + if (skel->children) + skel->children = svn_skel__dup(skel->children, dup_data, result_pool); + + if (skel->next) + skel->next = svn_skel__dup(skel->next, dup_data, result_pool); + + return skel; +} + +void +svn_skel__prepend(svn_skel_t *skel, svn_skel_t *list_skel) +{ + /* If list_skel isn't even a list, somebody's not using this + function properly. */ + SVN_ERR_ASSERT_NO_RETURN(! list_skel->is_atom); + + skel->next = list_skel->children; + list_skel->children = skel; +} + + +void svn_skel__prepend_int(apr_int64_t value, + svn_skel_t *skel, + apr_pool_t *result_pool) +{ + char *val_string = apr_palloc(result_pool, SVN_INT64_BUFFER_SIZE); + svn__i64toa(val_string, value); + + svn_skel__prepend_str(val_string, skel, result_pool); +} + + +void svn_skel__prepend_str(const char *value, + svn_skel_t *skel, + apr_pool_t *result_pool) +{ + svn_skel_t *atom = svn_skel__str_atom(value, result_pool); + + svn_skel__prepend(atom, skel); +} + + +void svn_skel__append(svn_skel_t *list_skel, svn_skel_t *skel) +{ + SVN_ERR_ASSERT_NO_RETURN(list_skel != NULL && !list_skel->is_atom); + + if (list_skel->children == NULL) + { + list_skel->children = skel; + } + else + { + list_skel = list_skel->children; + while (list_skel->next != NULL) + list_skel = list_skel->next; + list_skel->next = skel; + } +} + + +/* Examining skels. */ + + +svn_boolean_t +svn_skel__matches_atom(const svn_skel_t *skel, const char *str) +{ + if (skel && skel->is_atom) + { + apr_size_t len = strlen(str); + + return (skel->len == len + && ! memcmp(skel->data, str, len)); + } + return FALSE; +} + + +int +svn_skel__list_length(const svn_skel_t *skel) +{ + int len = 0; + const svn_skel_t *child; + + if ((! skel) || skel->is_atom) + return -1; + + for (child = skel->children; child; child = child->next) + len++; + + return len; +} + + + +/* Parsing and unparsing into high-level types. */ + +svn_error_t * +svn_skel__parse_int(apr_int64_t *n, const svn_skel_t *skel, + apr_pool_t *scratch_pool) +{ + const char *str; + + /* We need to duplicate the SKEL contents in order to get a NUL-terminated + version of it. The SKEL may not have valid memory at DATA[LEN]. */ + str = apr_pstrmemdup(scratch_pool, skel->data, skel->len); + return svn_error_trace(svn_cstring_atoi64(n, str)); +} + + +svn_error_t * +svn_skel__parse_proplist(apr_hash_t **proplist_p, + const svn_skel_t *skel, + apr_pool_t *pool /* result_pool */) +{ + apr_hash_t *proplist = NULL; + svn_skel_t *elt; + + /* Validate the skel. */ + if (! is_valid_proplist_skel(skel)) + return skel_err("proplist"); + + /* Create the returned structure */ + proplist = apr_hash_make(pool); + for (elt = skel->children; elt; elt = elt->next->next) + { + svn_string_t *value = svn_string_ncreate(elt->next->data, + elt->next->len, pool); + apr_hash_set(proplist, + apr_pstrmemdup(pool, elt->data, elt->len), + elt->len, + value); + } + + /* Return the structure. */ + *proplist_p = proplist; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_skel__parse_iprops(apr_array_header_t **iprops, + const svn_skel_t *skel, + apr_pool_t *result_pool) +{ + svn_skel_t *elt; + + /* Validate the skel. */ + if (! is_valid_iproplist_skel(skel)) + return skel_err("iprops"); + + /* Create the returned structure */ + *iprops = apr_array_make(result_pool, 1, + sizeof(svn_prop_inherited_item_t *)); + + for (elt = skel->children; elt; elt = elt->next->next) + { + svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool, + sizeof(*new_iprop)); + svn_string_t *repos_parent = svn_string_ncreate(elt->data, elt->len, + result_pool); + SVN_ERR(svn_skel__parse_proplist(&(new_iprop->prop_hash), elt->next, + result_pool)); + new_iprop->path_or_url = repos_parent->data; + APR_ARRAY_PUSH(*iprops, svn_prop_inherited_item_t *) = new_iprop; + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_skel__parse_prop(svn_string_t **propval, + const svn_skel_t *skel, + const char *propname, + apr_pool_t *pool /* result_pool */) +{ + svn_skel_t *elt; + + *propval = NULL; + + /* Validate the skel. */ + if (! is_valid_proplist_skel(skel)) + return skel_err("proplist"); + + /* Look for PROPNAME in SKEL. */ + for (elt = skel->children; elt; elt = elt->next->next) + { + if (elt->len == strlen(propname) + && strncmp(propname, elt->data, elt->len) == 0) + { + *propval = svn_string_ncreate(elt->next->data, elt->next->len, + pool); + break; + } + else + { + continue; + } + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_skel__unparse_proplist(svn_skel_t **skel_p, + const apr_hash_t *proplist, + apr_pool_t *pool) +{ + svn_skel_t *skel = svn_skel__make_empty_list(pool); + apr_hash_index_t *hi; + + /* Create the skel. */ + if (proplist) + { + /* Loop over hash entries */ + for (hi = apr_hash_first(pool, (apr_hash_t *)proplist); hi; + hi = apr_hash_next(hi)) + { + const void *key; + void *val; + apr_ssize_t klen; + svn_string_t *value; + + apr_hash_this(hi, &key, &klen, &val); + value = val; + + /* VALUE */ + svn_skel__prepend(svn_skel__mem_atom(value->data, value->len, pool), + skel); + + /* NAME */ + svn_skel__prepend(svn_skel__mem_atom(key, klen, pool), skel); + } + } + + /* Validate and return the skel. */ + if (! is_valid_proplist_skel(skel)) + return skel_err("proplist"); + *skel_p = skel; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_skel__unparse_iproplist(svn_skel_t **skel_p, + const apr_array_header_t *inherited_props, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *skel = svn_skel__make_empty_list(result_pool); + + /* Create the skel. */ + if (inherited_props) + { + int i; + apr_hash_index_t *hi; + + for (i = 0; i < inherited_props->nelts; i++) + { + svn_prop_inherited_item_t *iprop = + APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); + + svn_skel_t *skel_list = svn_skel__make_empty_list(result_pool); + svn_skel_t *skel_atom; + + /* Loop over hash entries */ + for (hi = apr_hash_first(scratch_pool, iprop->prop_hash); + hi; + hi = apr_hash_next(hi)) + { + const void *key; + void *val; + apr_ssize_t klen; + svn_string_t *value; + + apr_hash_this(hi, &key, &klen, &val); + value = val; + + /* VALUE */ + svn_skel__prepend(svn_skel__mem_atom(value->data, value->len, + result_pool), skel_list); + + /* NAME */ + svn_skel__prepend(svn_skel__mem_atom(key, klen, result_pool), + skel_list); + } + + skel_atom = svn_skel__str_atom( + apr_pstrdup(result_pool, iprop->path_or_url), result_pool); + svn_skel__append(skel, skel_atom); + svn_skel__append(skel, skel_list); + } + } + + /* Validate and return the skel. */ + if (! is_valid_iproplist_skel(skel)) + return skel_err("iproplist"); + + *skel_p = skel; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/sorts.c b/subversion/libsvn_subr/sorts.c new file mode 100644 index 000000000000..bdec8e47d2a0 --- /dev/null +++ b/subversion/libsvn_subr/sorts.c @@ -0,0 +1,309 @@ +/* + * sorts.c: all sorts of sorts + * + * ==================================================================== + * 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_hash.h> +#include <apr_tables.h> +#include <stdlib.h> /* for qsort() */ +#include <assert.h> +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_sorts.h" +#include "svn_error.h" + + + +/*** svn_sort__hash() ***/ + +/* (Should this be a permanent part of APR?) + + OK, folks, here's what's going on. APR hash tables hash on + key/klen objects, and store associated generic values. They work + great, but they have no ordering. + + The point of this exercise is to somehow arrange a hash's keys into + an "ordered list" of some kind -- in this case, a nicely sorted + one. + + We're using APR arrays, therefore, because that's what they are: + ordered lists. However, what "keys" should we put in the array? + Clearly, (const char *) objects aren't general enough. Or rather, + they're not as general as APR's hash implementation, which stores + (void *)/length as keys. We don't want to lose this information. + + Therefore, it makes sense to store pointers to {void *, size_t} + structures in our array. No such apr object exists... BUT... if we + can use a new type svn_sort__item_t which contains {char *, size_t, void + *}. If store these objects in our array, we get the hash value + *for free*. When looping over the final array, we don't need to + call apr_hash_get(). Major bonus! + */ + + +int +svn_sort_compare_items_as_paths(const svn_sort__item_t *a, + const svn_sort__item_t *b) +{ + const char *astr, *bstr; + + astr = a->key; + bstr = b->key; + assert(astr[a->klen] == '\0'); + assert(bstr[b->klen] == '\0'); + return svn_path_compare_paths(astr, bstr); +} + + +int +svn_sort_compare_items_lexically(const svn_sort__item_t *a, + const svn_sort__item_t *b) +{ + int val; + apr_size_t len; + + /* Compare bytes of a's key and b's key up to the common length. */ + len = (a->klen < b->klen) ? a->klen : b->klen; + val = memcmp(a->key, b->key, len); + if (val != 0) + return val; + + /* They match up until one of them ends; whichever is longer is greater. */ + return (a->klen < b->klen) ? -1 : (a->klen > b->klen) ? 1 : 0; +} + + +int +svn_sort_compare_revisions(const void *a, const void *b) +{ + svn_revnum_t a_rev = *(const svn_revnum_t *)a; + svn_revnum_t b_rev = *(const svn_revnum_t *)b; + + if (a_rev == b_rev) + return 0; + + return a_rev < b_rev ? 1 : -1; +} + + +int +svn_sort_compare_paths(const void *a, const void *b) +{ + const char *item1 = *((const char * const *) a); + const char *item2 = *((const char * const *) b); + + return svn_path_compare_paths(item1, item2); +} + + +int +svn_sort_compare_ranges(const void *a, const void *b) +{ + const svn_merge_range_t *item1 = *((const svn_merge_range_t * const *) a); + const svn_merge_range_t *item2 = *((const svn_merge_range_t * const *) b); + + if (item1->start == item2->start + && item1->end == item2->end) + return 0; + + if (item1->start == item2->start) + return item1->end < item2->end ? -1 : 1; + + return item1->start < item2->start ? -1 : 1; +} + +apr_array_header_t * +svn_sort__hash(apr_hash_t *ht, + int (*comparison_func)(const svn_sort__item_t *, + const svn_sort__item_t *), + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + apr_array_header_t *ary; + svn_boolean_t sorted; + svn_sort__item_t *prev_item; + + /* allocate an array with enough elements to hold all the keys. */ + ary = apr_array_make(pool, apr_hash_count(ht), sizeof(svn_sort__item_t)); + + /* loop over hash table and push all keys into the array */ + sorted = TRUE; + prev_item = NULL; + for (hi = apr_hash_first(pool, ht); hi; hi = apr_hash_next(hi)) + { + svn_sort__item_t *item = apr_array_push(ary); + + apr_hash_this(hi, &item->key, &item->klen, &item->value); + + if (prev_item == NULL) + { + prev_item = item; + continue; + } + + if (sorted) + { + sorted = (comparison_func(prev_item, item) < 0); + prev_item = item; + } + } + + /* quicksort the array if it isn't already sorted. */ + if (!sorted) + qsort(ary->elts, ary->nelts, ary->elt_size, + (int (*)(const void *, const void *))comparison_func); + + return ary; +} + +/* Return the lowest index at which the element *KEY should be inserted into + the array at BASE which has NELTS elements of size ELT_SIZE bytes each, + according to the ordering defined by COMPARE_FUNC. + 0 <= NELTS <= INT_MAX, 1 <= ELT_SIZE <= INT_MAX. + The array must already be sorted in the ordering defined by COMPARE_FUNC. + COMPARE_FUNC is defined as for the C stdlib function bsearch(). + Note: This function is modeled on bsearch() and on lower_bound() in the + C++ STL. + */ +static int +bsearch_lower_bound(const void *key, + const void *base, + int nelts, + int elt_size, + int (*compare_func)(const void *, const void *)) +{ + int lower = 0; + int upper = nelts - 1; + + /* Binary search for the lowest position at which to insert KEY. */ + while (lower <= upper) + { + int try = lower + (upper - lower) / 2; /* careful to avoid overflow */ + int cmp = compare_func((const char *)base + try * elt_size, key); + + if (cmp < 0) + lower = try + 1; + else + upper = try - 1; + } + assert(lower == upper + 1); + + return lower; +} + +int +svn_sort__bsearch_lower_bound(const void *key, + const apr_array_header_t *array, + int (*compare_func)(const void *, const void *)) +{ + return bsearch_lower_bound(key, + array->elts, array->nelts, array->elt_size, + compare_func); +} + +void +svn_sort__array_insert(const void *new_element, + apr_array_header_t *array, + int insert_index) +{ + int elements_to_move; + char *new_position; + + assert(0 <= insert_index && insert_index <= array->nelts); + elements_to_move = array->nelts - insert_index; /* before bumping nelts */ + + /* Grow the array, allocating a new space at the end. Note: this can + reallocate the array's "elts" at a different address. */ + apr_array_push(array); + + /* Move the elements after INSERT_INDEX along. (When elements_to_move == 0, + this is a no-op.) */ + new_position = (char *)array->elts + insert_index * array->elt_size; + memmove(new_position + array->elt_size, new_position, + array->elt_size * elements_to_move); + + /* Copy in the new element */ + memcpy(new_position, new_element, array->elt_size); +} + +void +svn_sort__array_delete(apr_array_header_t *arr, + int delete_index, + int elements_to_delete) +{ + /* Do we have a valid index and are there enough elements? */ + if (delete_index >= 0 + && delete_index < arr->nelts + && elements_to_delete > 0 + && (elements_to_delete + delete_index) <= arr->nelts) + { + /* If we are not deleting a block of elements that extends to the end + of the array, then we need to move the remaining elements to keep + the array contiguous. */ + if ((elements_to_delete + delete_index) < arr->nelts) + memmove( + arr->elts + arr->elt_size * delete_index, + arr->elts + (arr->elt_size * (delete_index + elements_to_delete)), + arr->elt_size * (arr->nelts - elements_to_delete - delete_index)); + + /* Delete the last ELEMENTS_TO_DELETE elements. */ + arr->nelts -= elements_to_delete; + } +} + +void +svn_sort__array_reverse(apr_array_header_t *array, + apr_pool_t *scratch_pool) +{ + int i; + + if (array->elt_size == sizeof(void *)) + { + for (i = 0; i < array->nelts / 2; i++) + { + int swap_index = array->nelts - i - 1; + void *tmp = APR_ARRAY_IDX(array, i, void *); + + APR_ARRAY_IDX(array, i, void *) = + APR_ARRAY_IDX(array, swap_index, void *); + APR_ARRAY_IDX(array, swap_index, void *) = tmp; + } + } + else + { + apr_size_t sz = array->elt_size; + char *tmp = apr_palloc(scratch_pool, sz); + + for (i = 0; i < array->nelts / 2; i++) + { + int swap_index = array->nelts - i - 1; + char *x = array->elts + (sz * i); + char *y = array->elts + (sz * swap_index); + + memcpy(tmp, x, sz); + memcpy(x, y, sz); + memcpy(y, tmp, sz); + } + } +} diff --git a/subversion/libsvn_subr/spillbuf.c b/subversion/libsvn_subr/spillbuf.c new file mode 100644 index 000000000000..e028741621b0 --- /dev/null +++ b/subversion/libsvn_subr/spillbuf.c @@ -0,0 +1,615 @@ +/* + * spillbuf.c : an in-memory buffer that can spill to disk + * + * ==================================================================== + * 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_file_io.h> + +#include "svn_io.h" +#include "svn_pools.h" + +#include "private/svn_subr_private.h" + + +struct memblock_t { + apr_size_t size; + char *data; + + struct memblock_t *next; +}; + + +struct svn_spillbuf_t { + /* Pool for allocating blocks and the spill file. */ + apr_pool_t *pool; + + /* Size of in-memory blocks. */ + apr_size_t blocksize; + + /* Maximum in-memory size; start spilling when we reach this size. */ + apr_size_t maxsize; + + /* The amount of content in memory. */ + apr_size_t memory_size; + + /* HEAD points to the first block of the linked list of buffers. + TAIL points to the last block, for quickly appending more blocks + to the overall list. */ + struct memblock_t *head; + struct memblock_t *tail; + + /* Available blocks for storing pending data. These were allocated + previously, then the data consumed and returned to this list. */ + struct memblock_t *avail; + + /* When a block is borrowed for reading, it is listed here. */ + struct memblock_t *out_for_reading; + + /* Once MEMORY_SIZE exceeds SPILL_SIZE, then arriving content will be + appended to the (temporary) file indicated by SPILL. */ + apr_file_t *spill; + + /* As we consume content from SPILL, this value indicates where we + will begin reading. */ + apr_off_t spill_start; + + /* How much content remains in SPILL. */ + svn_filesize_t spill_size; +}; + + +struct svn_spillbuf_reader_t { + /* Embed the spill-buffer within the reader. */ + struct svn_spillbuf_t buf; + + /* When we read content from the underlying spillbuf, these fields store + the ptr/len pair. The ptr will be incremented as we "read" out of this + buffer since we don't have to retain the original pointer (it is + managed inside of the spillbuf). */ + const char *sb_ptr; + apr_size_t sb_len; + + /* If a write comes in, then we may need to save content from our + borrowed buffer (since that buffer may be destroyed by our call into + the spillbuf code). Note that we retain the original pointer since + this buffer is allocated by the reader code and re-used. The SAVE_POS + field indicates the current position within this save buffer. The + SAVE_LEN field describes how much content is present. */ + char *save_ptr; + apr_size_t save_len; + apr_size_t save_pos; +}; + + +svn_spillbuf_t * +svn_spillbuf__create(apr_size_t blocksize, + apr_size_t maxsize, + apr_pool_t *result_pool) +{ + svn_spillbuf_t *buf = apr_pcalloc(result_pool, sizeof(*buf)); + + buf->pool = result_pool; + buf->blocksize = blocksize; + buf->maxsize = maxsize; + /* Note: changes here should also go into svn_spillbuf__reader_create() */ + + return buf; +} + + +svn_filesize_t +svn_spillbuf__get_size(const svn_spillbuf_t *buf) +{ + return buf->memory_size + buf->spill_size; +} + + +/* Get a memblock from the spill-buffer. It will be the block that we + passed out for reading, come from the free list, or allocated. */ +static struct memblock_t * +get_buffer(svn_spillbuf_t *buf) +{ + struct memblock_t *mem = buf->out_for_reading; + + if (mem != NULL) + { + buf->out_for_reading = NULL; + return mem; + } + + if (buf->avail == NULL) + { + mem = apr_palloc(buf->pool, sizeof(*mem)); + mem->data = apr_palloc(buf->pool, buf->blocksize); + return mem; + } + + mem = buf->avail; + buf->avail = mem->next; + return mem; +} + + +/* Return MEM to the list of available buffers in BUF. */ +static void +return_buffer(svn_spillbuf_t *buf, + struct memblock_t *mem) +{ + mem->next = buf->avail; + buf->avail = mem; +} + + +svn_error_t * +svn_spillbuf__write(svn_spillbuf_t *buf, + const char *data, + apr_size_t len, + apr_pool_t *scratch_pool) +{ + struct memblock_t *mem; + + /* We do not (yet) have a spill file, but the amount stored in memory + will grow too large. Create the file and place the pending data into + the temporary file. */ + if (buf->spill == NULL + && (buf->memory_size + len) > buf->maxsize) + { + SVN_ERR(svn_io_open_unique_file3(&buf->spill, + NULL /* temp_path */, + NULL /* dirpath */, + svn_io_file_del_on_close, + buf->pool, scratch_pool)); + } + + /* Once a spill file has been constructed, then we need to put all + arriving data into the file. We will no longer attempt to hold it + in memory. */ + if (buf->spill != NULL) + { + apr_off_t output_unused = 0; /* ### stupid API */ + + /* Seek to the end of the spill file. We don't know if a read has + occurred since our last write, and moved the file position. */ + SVN_ERR(svn_io_file_seek(buf->spill, + APR_END, &output_unused, + scratch_pool)); + + SVN_ERR(svn_io_file_write_full(buf->spill, data, len, + NULL, scratch_pool)); + buf->spill_size += len; + + return SVN_NO_ERROR; + } + + while (len > 0) + { + apr_size_t amt; + + if (buf->tail == NULL || buf->tail->size == buf->blocksize) + { + /* There is no existing memblock (that may have space), or the + tail memblock has no space, so we need a new memblock. */ + mem = get_buffer(buf); + mem->size = 0; + mem->next = NULL; + } + else + { + mem = buf->tail; + } + + /* Compute how much to write into the memblock. */ + amt = buf->blocksize - mem->size; + if (amt > len) + amt = len; + + /* Copy some data into this memblock. */ + memcpy(&mem->data[mem->size], data, amt); + mem->size += amt; + data += amt; + len -= amt; + + /* We need to record how much is buffered in memory. Once we reach + buf->maxsize (or thereabouts, it doesn't have to be precise), then + we'll switch to putting the content into a file. */ + buf->memory_size += amt; + + /* Start a list of buffers, or (if we're not writing into the tail) + append to the end of the linked list of buffers. */ + if (buf->tail == NULL) + { + buf->head = mem; + buf->tail = mem; + } + else if (mem != buf->tail) + { + buf->tail->next = mem; + buf->tail = mem; + } + } + + return SVN_NO_ERROR; +} + + +/* Return a memblock of content, if any is available. *mem will be NULL if + no further content is available. The memblock should eventually be + passed to return_buffer() (or stored into buf->out_for_reading which + will grab that block at the next get_buffer() call). */ +static svn_error_t * +read_data(struct memblock_t **mem, + svn_spillbuf_t *buf, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + /* If we have some in-memory blocks, then return one. */ + if (buf->head != NULL) + { + *mem = buf->head; + if (buf->tail == *mem) + buf->head = buf->tail = NULL; + else + buf->head = (*mem)->next; + + /* We're using less memory now. If we haven't hit the spill file, + then we may be able to keep using memory. */ + buf->memory_size -= (*mem)->size; + + return SVN_NO_ERROR; + } + + /* No file? Done. */ + if (buf->spill == NULL) + { + *mem = NULL; + return SVN_NO_ERROR; + } + + /* Assume that the caller has seeked the spill file to the correct pos. */ + + /* Get a buffer that we can read content into. */ + *mem = get_buffer(buf); + /* NOTE: mem's size/next are uninitialized. */ + + if ((apr_uint64_t)buf->spill_size < (apr_uint64_t)buf->blocksize) + (*mem)->size = (apr_size_t)buf->spill_size; + else + (*mem)->size = buf->blocksize; /* The size of (*mem)->data */ + (*mem)->next = NULL; + + /* Read some data from the spill file into the memblock. */ + err = svn_io_file_read(buf->spill, (*mem)->data, &(*mem)->size, + scratch_pool); + if (err) + { + return_buffer(buf, *mem); + return svn_error_trace(err); + } + + /* Mark the data that we consumed from the spill file. */ + buf->spill_start += (*mem)->size; + + /* Did we consume all the data from the spill file? */ + if ((buf->spill_size -= (*mem)->size) == 0) + { + /* Close and reset our spill file information. */ + SVN_ERR(svn_io_file_close(buf->spill, scratch_pool)); + buf->spill = NULL; + buf->spill_start = 0; + } + + /* *mem has been initialized. Done. */ + return SVN_NO_ERROR; +} + + +/* If the next read would consume data from the file, then seek to the + correct position. */ +static svn_error_t * +maybe_seek(svn_boolean_t *seeked, + const svn_spillbuf_t *buf, + apr_pool_t *scratch_pool) +{ + if (buf->head == NULL && buf->spill != NULL) + { + apr_off_t output_unused; + + /* Seek to where we left off reading. */ + output_unused = buf->spill_start; /* ### stupid API */ + SVN_ERR(svn_io_file_seek(buf->spill, + APR_SET, &output_unused, + scratch_pool)); + if (seeked != NULL) + *seeked = TRUE; + } + else if (seeked != NULL) + { + *seeked = FALSE; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_spillbuf__read(const char **data, + apr_size_t *len, + svn_spillbuf_t *buf, + apr_pool_t *scratch_pool) +{ + struct memblock_t *mem; + + /* Possibly seek... */ + SVN_ERR(maybe_seek(NULL, buf, scratch_pool)); + + SVN_ERR(read_data(&mem, buf, scratch_pool)); + if (mem == NULL) + { + *data = NULL; + *len = 0; + } + else + { + *data = mem->data; + *len = mem->size; + + /* If a block was out for reading, then return it. */ + if (buf->out_for_reading != NULL) + return_buffer(buf, buf->out_for_reading); + + /* Remember that we've passed this block out for reading. */ + buf->out_for_reading = mem; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_spillbuf__process(svn_boolean_t *exhausted, + svn_spillbuf_t *buf, + svn_spillbuf_read_t read_func, + void *read_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t has_seeked = FALSE; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + *exhausted = FALSE; + + while (TRUE) + { + struct memblock_t *mem; + svn_error_t *err; + svn_boolean_t stop; + + svn_pool_clear(iterpool); + + /* If this call to read_data() will read from the spill file, and we + have not seek'd the file... then do it now. */ + if (!has_seeked) + SVN_ERR(maybe_seek(&has_seeked, buf, iterpool)); + + /* Get some content to pass to the read callback. */ + SVN_ERR(read_data(&mem, buf, iterpool)); + if (mem == NULL) + { + *exhausted = TRUE; + break; + } + + err = read_func(&stop, read_baton, mem->data, mem->size, iterpool); + + return_buffer(buf, mem); + + if (err) + return svn_error_trace(err); + + /* If the callbacks told us to stop, then we're done for now. */ + if (stop) + break; + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +svn_spillbuf_reader_t * +svn_spillbuf__reader_create(apr_size_t blocksize, + apr_size_t maxsize, + apr_pool_t *result_pool) +{ + svn_spillbuf_reader_t *sbr = apr_pcalloc(result_pool, sizeof(*sbr)); + + /* See svn_spillbuf__create() */ + sbr->buf.pool = result_pool; + sbr->buf.blocksize = blocksize; + sbr->buf.maxsize = maxsize; + + return sbr; +} + + +svn_error_t * +svn_spillbuf__reader_read(apr_size_t *amt, + svn_spillbuf_reader_t *reader, + char *data, + apr_size_t len, + apr_pool_t *scratch_pool) +{ + if (len == 0) + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, NULL); + + *amt = 0; + + while (len > 0) + { + apr_size_t copy_amt; + + if (reader->save_len > 0) + { + /* We have some saved content, so use this first. */ + + if (len < reader->save_len) + copy_amt = len; + else + copy_amt = reader->save_len; + + memcpy(data, reader->save_ptr + reader->save_pos, copy_amt); + reader->save_pos += copy_amt; + reader->save_len -= copy_amt; + } + else + { + /* No saved content. We should now copy from spillbuf-provided + buffers of content. */ + + /* We may need more content from the spillbuf. */ + if (reader->sb_len == 0) + { + SVN_ERR(svn_spillbuf__read(&reader->sb_ptr, &reader->sb_len, + &reader->buf, + scratch_pool)); + + /* We've run out of content, so return with whatever has + been copied into DATA and stored into AMT. */ + if (reader->sb_ptr == NULL) + { + /* For safety, read() may not have set SB_LEN. We use it + as an indicator, so it needs to be cleared. */ + reader->sb_len = 0; + return SVN_NO_ERROR; + } + } + + if (len < reader->sb_len) + copy_amt = len; + else + copy_amt = reader->sb_len; + + memcpy(data, reader->sb_ptr, copy_amt); + reader->sb_ptr += copy_amt; + reader->sb_len -= copy_amt; + } + + data += copy_amt; + len -= copy_amt; + (*amt) += copy_amt; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_spillbuf__reader_getc(char *c, + svn_spillbuf_reader_t *reader, + apr_pool_t *scratch_pool) +{ + apr_size_t amt; + + SVN_ERR(svn_spillbuf__reader_read(&amt, reader, c, 1, scratch_pool)); + if (amt == 0) + return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, NULL); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_spillbuf__reader_write(svn_spillbuf_reader_t *reader, + const char *data, + apr_size_t len, + apr_pool_t *scratch_pool) +{ + /* If we have a buffer of content from the spillbuf, then we need to + move that content to a safe place. */ + if (reader->sb_len > 0) + { + if (reader->save_ptr == NULL) + reader->save_ptr = apr_palloc(reader->buf.pool, reader->buf.blocksize); + + memcpy(reader->save_ptr, reader->sb_ptr, reader->sb_len); + reader->save_len = reader->sb_len; + reader->save_pos = 0; + + /* No more content in the spillbuf-borrowed buffer. */ + reader->sb_len = 0; + } + + return svn_error_trace(svn_spillbuf__write(&reader->buf, data, len, + scratch_pool)); +} + + +struct spillbuf_baton +{ + svn_spillbuf_reader_t *reader; + apr_pool_t *scratch_pool; +}; + + +static svn_error_t * +read_handler_spillbuf(void *baton, char *buffer, apr_size_t *len) +{ + struct spillbuf_baton *sb = baton; + + SVN_ERR(svn_spillbuf__reader_read(len, sb->reader, buffer, *len, + sb->scratch_pool)); + + svn_pool_clear(sb->scratch_pool); + return SVN_NO_ERROR; +} + + +static svn_error_t * +write_handler_spillbuf(void *baton, const char *data, apr_size_t *len) +{ + struct spillbuf_baton *sb = baton; + + SVN_ERR(svn_spillbuf__reader_write(sb->reader, data, *len, + sb->scratch_pool)); + + svn_pool_clear(sb->scratch_pool); + return SVN_NO_ERROR; +} + + +svn_stream_t * +svn_stream__from_spillbuf(apr_size_t blocksize, + apr_size_t maxsize, + apr_pool_t *result_pool) +{ + svn_stream_t *stream; + struct spillbuf_baton *sb = apr_palloc(result_pool, sizeof(*sb)); + + sb->reader = svn_spillbuf__reader_create(blocksize, maxsize, result_pool); + sb->scratch_pool = svn_pool_create(result_pool); + + stream = svn_stream_create(sb, result_pool); + + svn_stream_set_read(stream, read_handler_spillbuf); + svn_stream_set_write(stream, write_handler_spillbuf); + + return stream; +} diff --git a/subversion/libsvn_subr/sqlite.c b/subversion/libsvn_subr/sqlite.c new file mode 100644 index 000000000000..0afceffe6617 --- /dev/null +++ b/subversion/libsvn_subr/sqlite.c @@ -0,0 +1,1294 @@ +/* sqlite.c + * + * ==================================================================== + * 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 "svn_types.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_dirent_uri.h" +#include "svn_checksum.h" + +#include "internal_statements.h" + +#include "private/svn_sqlite.h" +#include "svn_private_config.h" +#include "private/svn_dep_compat.h" +#include "private/svn_atomic.h" +#include "private/svn_skel.h" +#include "private/svn_token.h" + +#ifdef SQLITE3_DEBUG +#include "private/svn_debug.h" +#endif + +#ifdef SVN_SQLITE_INLINE +/* Import the sqlite3 API vtable from sqlite3wrapper.c */ +# define SQLITE_OMIT_DEPRECATED +# include <sqlite3ext.h> +extern const sqlite3_api_routines *const svn_sqlite3__api_funcs; +extern int (*const svn_sqlite3__api_initialize)(void); +extern int (*const svn_sqlite3__api_config)(int, ...); +# define sqlite3_api svn_sqlite3__api_funcs +# define sqlite3_initialize svn_sqlite3__api_initialize +# define sqlite3_config svn_sqlite3__api_config +#else +# include <sqlite3.h> +#endif + +#if !SQLITE_VERSION_AT_LEAST(3,7,12) +#error SQLite is too old -- version 3.7.12 is the minimum required version +#endif + +const char * +svn_sqlite__compiled_version(void) +{ + static const char sqlite_version[] = SQLITE_VERSION; + return sqlite_version; +} + +const char * +svn_sqlite__runtime_version(void) +{ + return sqlite3_libversion(); +} + + +INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENTS(internal_statements); + + +#ifdef SQLITE3_DEBUG +/* An sqlite query execution callback. */ +static void +sqlite_tracer(void *data, const char *sql) +{ + /* sqlite3 *db3 = data; */ + SVN_DBG(("sql=\"%s\"\n", sql)); +} +#endif + +#ifdef SQLITE3_PROFILE +/* An sqlite execution timing callback. */ +static void +sqlite_profiler(void *data, const char *sql, sqlite3_uint64 duration) +{ + /* sqlite3 *db3 = data; */ + SVN_DBG(("[%.3f] sql=\"%s\"\n", 1e-9 * duration, sql)); +} +#endif + +struct svn_sqlite__db_t +{ + sqlite3 *db3; + const char * const *statement_strings; + int nbr_statements; + svn_sqlite__stmt_t **prepared_stmts; + apr_pool_t *state_pool; +}; + +struct svn_sqlite__stmt_t +{ + sqlite3_stmt *s3stmt; + svn_sqlite__db_t *db; + svn_boolean_t needs_reset; +}; + +struct svn_sqlite__context_t +{ + sqlite3_context *context; +}; + +struct svn_sqlite__value_t +{ + sqlite3_value *value; +}; + + +/* Convert SQLite error codes to SVN. Evaluates X multiple times */ +#define SQLITE_ERROR_CODE(x) ((x) == SQLITE_READONLY \ + ? SVN_ERR_SQLITE_READONLY \ + : ((x) == SQLITE_BUSY \ + ? SVN_ERR_SQLITE_BUSY \ + : ((x) == SQLITE_CONSTRAINT \ + ? SVN_ERR_SQLITE_CONSTRAINT \ + : SVN_ERR_SQLITE_ERROR))) + + +/* SQLITE->SVN quick error wrap, much like SVN_ERR. */ +#define SQLITE_ERR(x, db) do \ +{ \ + int sqlite_err__temp = (x); \ + if (sqlite_err__temp != SQLITE_OK) \ + return svn_error_createf(SQLITE_ERROR_CODE(sqlite_err__temp), \ + NULL, "sqlite: %s (S%d)", \ + sqlite3_errmsg((db)->db3), \ + sqlite_err__temp); \ +} while (0) + +#define SQLITE_ERR_MSG(x, msg) do \ +{ \ + int sqlite_err__temp = (x); \ + if (sqlite_err__temp != SQLITE_OK) \ + return svn_error_createf(SQLITE_ERROR_CODE(sqlite_err__temp), \ + NULL, "sqlite: %s (S%d)", (msg), \ + sqlite_err__temp); \ +} while (0) + + +/* Time (in milliseconds) to wait for sqlite locks before giving up. */ +#define BUSY_TIMEOUT 10000 + + +/* Convenience wrapper around exec_sql2(). */ +#define exec_sql(db, sql) exec_sql2((db), (sql), SQLITE_OK) + +/* Run the statement SQL on DB, ignoring SQLITE_OK and IGNORED_ERR. + (Note: the IGNORED_ERR parameter itself is not ignored.) */ +static svn_error_t * +exec_sql2(svn_sqlite__db_t *db, const char *sql, int ignored_err) +{ + char *err_msg; + int sqlite_err = sqlite3_exec(db->db3, sql, NULL, NULL, &err_msg); + + if (sqlite_err != SQLITE_OK && sqlite_err != ignored_err) + { + svn_error_t *err = svn_error_createf(SQLITE_ERROR_CODE(sqlite_err), NULL, + _("sqlite: %s (S%d)," + " executing statement '%s'"), + err_msg, sqlite_err, sql); + sqlite3_free(err_msg); + return err; + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +prepare_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db, + const char *text, apr_pool_t *result_pool) +{ + *stmt = apr_palloc(result_pool, sizeof(**stmt)); + (*stmt)->db = db; + (*stmt)->needs_reset = FALSE; + + SQLITE_ERR(sqlite3_prepare_v2(db->db3, text, -1, &(*stmt)->s3stmt, NULL), db); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_sqlite__exec_statements(svn_sqlite__db_t *db, int stmt_idx) +{ + SVN_ERR_ASSERT(stmt_idx < db->nbr_statements); + + return svn_error_trace(exec_sql(db, db->statement_strings[stmt_idx])); +} + + +svn_error_t * +svn_sqlite__get_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db, + int stmt_idx) +{ + SVN_ERR_ASSERT(stmt_idx < db->nbr_statements); + + if (db->prepared_stmts[stmt_idx] == NULL) + SVN_ERR(prepare_statement(&db->prepared_stmts[stmt_idx], db, + db->statement_strings[stmt_idx], + db->state_pool)); + + *stmt = db->prepared_stmts[stmt_idx]; + + if ((*stmt)->needs_reset) + return svn_error_trace(svn_sqlite__reset(*stmt)); + + return SVN_NO_ERROR; +} + +/* Like svn_sqlite__get_statement but gets an internal statement. + + All internal statements that use this api are executed with step_done(), + so we don't need the fallback reset handling here or in the pool cleanup */ +static svn_error_t * +get_internal_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db, + int stmt_idx) +{ + /* The internal statements are stored after the registered statements */ + int prep_idx = db->nbr_statements + stmt_idx; + SVN_ERR_ASSERT(stmt_idx < STMT_INTERNAL_LAST); + + if (db->prepared_stmts[prep_idx] == NULL) + SVN_ERR(prepare_statement(&db->prepared_stmts[prep_idx], db, + internal_statements[stmt_idx], + db->state_pool)); + + *stmt = db->prepared_stmts[prep_idx]; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +step_with_expectation(svn_sqlite__stmt_t* stmt, + svn_boolean_t expecting_row) +{ + svn_boolean_t got_row; + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + if ((got_row && !expecting_row) + || + (!got_row && expecting_row)) + return svn_error_create(SVN_ERR_SQLITE_ERROR, + svn_sqlite__reset(stmt), + expecting_row + ? _("sqlite: Expected database row missing") + : _("sqlite: Extra database row found")); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__step_done(svn_sqlite__stmt_t *stmt) +{ + SVN_ERR(step_with_expectation(stmt, FALSE)); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_sqlite__step_row(svn_sqlite__stmt_t *stmt) +{ + return svn_error_trace(step_with_expectation(stmt, TRUE)); +} + + +svn_error_t * +svn_sqlite__step(svn_boolean_t *got_row, svn_sqlite__stmt_t *stmt) +{ + int sqlite_result = sqlite3_step(stmt->s3stmt); + + if (sqlite_result != SQLITE_DONE && sqlite_result != SQLITE_ROW) + { + svn_error_t *err1, *err2; + + err1 = svn_error_createf(SQLITE_ERROR_CODE(sqlite_result), NULL, + "sqlite: %s (S%d)", + sqlite3_errmsg(stmt->db->db3), sqlite_result); + err2 = svn_sqlite__reset(stmt); + return svn_error_compose_create(err1, err2); + } + + *got_row = (sqlite_result == SQLITE_ROW); + stmt->needs_reset = TRUE; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__insert(apr_int64_t *row_id, svn_sqlite__stmt_t *stmt) +{ + svn_boolean_t got_row; + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + if (row_id) + *row_id = sqlite3_last_insert_rowid(stmt->db->db3); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_sqlite__update(int *affected_rows, svn_sqlite__stmt_t *stmt) +{ + SVN_ERR(step_with_expectation(stmt, FALSE)); + + if (affected_rows) + *affected_rows = sqlite3_changes(stmt->db->db3); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +static svn_error_t * +vbindf(svn_sqlite__stmt_t *stmt, const char *fmt, va_list ap) +{ + int count; + + for (count = 1; *fmt; fmt++, count++) + { + const void *blob; + apr_size_t blob_size; + const svn_token_map_t *map; + + switch (*fmt) + { + case 's': + SVN_ERR(svn_sqlite__bind_text(stmt, count, + va_arg(ap, const char *))); + break; + + case 'd': + SVN_ERR(svn_sqlite__bind_int(stmt, count, + va_arg(ap, int))); + break; + + case 'i': + case 'L': + SVN_ERR(svn_sqlite__bind_int64(stmt, count, + va_arg(ap, apr_int64_t))); + break; + + case 'b': + blob = va_arg(ap, const void *); + blob_size = va_arg(ap, apr_size_t); + SVN_ERR(svn_sqlite__bind_blob(stmt, count, blob, blob_size)); + break; + + case 'r': + SVN_ERR(svn_sqlite__bind_revnum(stmt, count, + va_arg(ap, svn_revnum_t))); + break; + + case 't': + map = va_arg(ap, const svn_token_map_t *); + SVN_ERR(svn_sqlite__bind_token(stmt, count, map, va_arg(ap, int))); + break; + + case 'n': + /* Skip this column: no binding */ + break; + + default: + SVN_ERR_MALFUNCTION(); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bindf(svn_sqlite__stmt_t *stmt, const char *fmt, ...) +{ + svn_error_t *err; + va_list ap; + + va_start(ap, fmt); + err = vbindf(stmt, fmt, ap); + va_end(ap); + return svn_error_trace(err); +} + +svn_error_t * +svn_sqlite__bind_int(svn_sqlite__stmt_t *stmt, + int slot, + int val) +{ + SQLITE_ERR(sqlite3_bind_int(stmt->s3stmt, slot, val), stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_int64(svn_sqlite__stmt_t *stmt, + int slot, + apr_int64_t val) +{ + SQLITE_ERR(sqlite3_bind_int64(stmt->s3stmt, slot, val), stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_text(svn_sqlite__stmt_t *stmt, + int slot, + const char *val) +{ + SQLITE_ERR(sqlite3_bind_text(stmt->s3stmt, slot, val, -1, SQLITE_TRANSIENT), + stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_blob(svn_sqlite__stmt_t *stmt, + int slot, + const void *val, + apr_size_t len) +{ + SQLITE_ERR(sqlite3_bind_blob(stmt->s3stmt, slot, val, (int) len, + SQLITE_TRANSIENT), + stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_token(svn_sqlite__stmt_t *stmt, + int slot, + const svn_token_map_t *map, + int value) +{ + const char *word = svn_token__to_word(map, value); + + SQLITE_ERR(sqlite3_bind_text(stmt->s3stmt, slot, word, -1, SQLITE_STATIC), + stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_revnum(svn_sqlite__stmt_t *stmt, + int slot, + svn_revnum_t value) +{ + if (SVN_IS_VALID_REVNUM(value)) + SQLITE_ERR(sqlite3_bind_int64(stmt->s3stmt, slot, + (sqlite_int64)value), stmt->db); + else + SQLITE_ERR(sqlite3_bind_null(stmt->s3stmt, slot), stmt->db); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__bind_properties(svn_sqlite__stmt_t *stmt, + int slot, + const apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + svn_skel_t *skel; + svn_stringbuf_t *properties; + + if (props == NULL) + return svn_error_trace(svn_sqlite__bind_blob(stmt, slot, NULL, 0)); + + SVN_ERR(svn_skel__unparse_proplist(&skel, props, scratch_pool)); + properties = svn_skel__unparse(skel, scratch_pool); + return svn_error_trace(svn_sqlite__bind_blob(stmt, + slot, + properties->data, + properties->len)); +} + +svn_error_t * +svn_sqlite__bind_iprops(svn_sqlite__stmt_t *stmt, + int slot, + const apr_array_header_t *inherited_props, + apr_pool_t *scratch_pool) +{ + svn_skel_t *skel; + svn_stringbuf_t *properties; + + if (inherited_props == NULL) + return svn_error_trace(svn_sqlite__bind_blob(stmt, slot, NULL, 0)); + + SVN_ERR(svn_skel__unparse_iproplist(&skel, inherited_props, + scratch_pool, scratch_pool)); + properties = svn_skel__unparse(skel, scratch_pool); + return svn_error_trace(svn_sqlite__bind_blob(stmt, + slot, + properties->data, + properties->len)); +} + +svn_error_t * +svn_sqlite__bind_checksum(svn_sqlite__stmt_t *stmt, + int slot, + const svn_checksum_t *checksum, + apr_pool_t *scratch_pool) +{ + const char *csum_str; + + if (checksum == NULL) + csum_str = NULL; + else + csum_str = svn_checksum_serialize(checksum, scratch_pool, scratch_pool); + + return svn_error_trace(svn_sqlite__bind_text(stmt, slot, csum_str)); +} + + +const void * +svn_sqlite__column_blob(svn_sqlite__stmt_t *stmt, int column, + apr_size_t *len, apr_pool_t *result_pool) +{ + const void *val = sqlite3_column_blob(stmt->s3stmt, column); + *len = sqlite3_column_bytes(stmt->s3stmt, column); + + if (result_pool && val != NULL) + val = apr_pmemdup(result_pool, val, *len); + + return val; +} + +const char * +svn_sqlite__column_text(svn_sqlite__stmt_t *stmt, int column, + apr_pool_t *result_pool) +{ + /* cast from 'unsigned char' to regular 'char' */ + const char *result = (const char *)sqlite3_column_text(stmt->s3stmt, column); + + if (result_pool && result != NULL) + result = apr_pstrdup(result_pool, result); + + return result; +} + +svn_revnum_t +svn_sqlite__column_revnum(svn_sqlite__stmt_t *stmt, int column) +{ + if (svn_sqlite__column_is_null(stmt, column)) + return SVN_INVALID_REVNUM; + return (svn_revnum_t) sqlite3_column_int64(stmt->s3stmt, column); +} + +svn_boolean_t +svn_sqlite__column_boolean(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_int64(stmt->s3stmt, column) != 0; +} + +int +svn_sqlite__column_int(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_int(stmt->s3stmt, column); +} + +apr_int64_t +svn_sqlite__column_int64(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_int64(stmt->s3stmt, column); +} + +int +svn_sqlite__column_token(svn_sqlite__stmt_t *stmt, + int column, + const svn_token_map_t *map) +{ + /* cast from 'unsigned char' to regular 'char' */ + const char *word = (const char *)sqlite3_column_text(stmt->s3stmt, column); + + return svn_token__from_word_strict(map, word); +} + +int +svn_sqlite__column_token_null(svn_sqlite__stmt_t *stmt, + int column, + const svn_token_map_t *map, + int null_val) +{ + /* cast from 'unsigned char' to regular 'char' */ + const char *word = (const char *)sqlite3_column_text(stmt->s3stmt, column); + + if (!word) + return null_val; + + return svn_token__from_word_strict(map, word); +} + +svn_error_t * +svn_sqlite__column_properties(apr_hash_t **props, + svn_sqlite__stmt_t *stmt, + int column, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_size_t len; + const void *val; + + /* svn_skel__parse_proplist copies everything needed to result_pool */ + val = svn_sqlite__column_blob(stmt, column, &len, NULL); + if (val == NULL) + { + *props = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_skel__parse_proplist(props, + svn_skel__parse(val, len, scratch_pool), + result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__column_iprops(apr_array_header_t **iprops, + svn_sqlite__stmt_t *stmt, + int column, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_size_t len; + const void *val; + + /* svn_skel__parse_iprops copies everything needed to result_pool */ + val = svn_sqlite__column_blob(stmt, column, &len, NULL); + if (val == NULL) + { + *iprops = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_skel__parse_iprops(iprops, + svn_skel__parse(val, len, scratch_pool), + result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__column_checksum(const svn_checksum_t **checksum, + svn_sqlite__stmt_t *stmt, int column, + apr_pool_t *result_pool) +{ + const char *digest = svn_sqlite__column_text(stmt, column, NULL); + + if (digest == NULL) + *checksum = NULL; + else + SVN_ERR(svn_checksum_deserialize(checksum, digest, + result_pool, result_pool)); + + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_sqlite__column_is_null(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_type(stmt->s3stmt, column) == SQLITE_NULL; +} + +int +svn_sqlite__column_bytes(svn_sqlite__stmt_t *stmt, int column) +{ + return sqlite3_column_bytes(stmt->s3stmt, column); +} + +svn_error_t * +svn_sqlite__finalize(svn_sqlite__stmt_t *stmt) +{ + SQLITE_ERR(sqlite3_finalize(stmt->s3stmt), stmt->db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__reset(svn_sqlite__stmt_t *stmt) +{ + SQLITE_ERR(sqlite3_reset(stmt->s3stmt), stmt->db); + SQLITE_ERR(sqlite3_clear_bindings(stmt->s3stmt), stmt->db); + stmt->needs_reset = FALSE; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_sqlite__read_schema_version(int *version, + svn_sqlite__db_t *db, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(prepare_statement(&stmt, db, "PRAGMA user_version;", scratch_pool)); + SVN_ERR(svn_sqlite__step_row(stmt)); + + *version = svn_sqlite__column_int(stmt, 0); + + return svn_error_trace(svn_sqlite__finalize(stmt)); +} + + +static volatile svn_atomic_t sqlite_init_state = 0; + +/* If possible, verify that SQLite was compiled in a thread-safe + manner. */ +/* Don't call this function directly! Use svn_atomic__init_once(). */ +static svn_error_t * +init_sqlite(void *baton, apr_pool_t *pool) +{ + if (sqlite3_libversion_number() < SVN_SQLITE_MIN_VERSION_NUMBER) + { + return svn_error_createf( + SVN_ERR_SQLITE_ERROR, NULL, + _("SQLite compiled for %s, but running with %s"), + SVN_SQLITE_MIN_VERSION, sqlite3_libversion()); + } + +#if APR_HAS_THREADS + + /* SQLite 3.5 allows verification of its thread-safety at runtime. + Older versions are simply expected to have been configured with + --enable-threadsafe, which compiles with -DSQLITE_THREADSAFE=1 + (or -DTHREADSAFE, for older versions). */ + if (! sqlite3_threadsafe()) + return svn_error_create(SVN_ERR_SQLITE_ERROR, NULL, + _("SQLite is required to be compiled and run in " + "thread-safe mode")); + + /* If SQLite has been already initialized, sqlite3_config() returns + SQLITE_MISUSE. */ + { + int err = sqlite3_config(SQLITE_CONFIG_MULTITHREAD); + if (err != SQLITE_OK && err != SQLITE_MISUSE) + return svn_error_createf(SQLITE_ERROR_CODE(err), NULL, + _("Could not configure SQLite (S%d)"), err); + } + SQLITE_ERR_MSG(sqlite3_initialize(), _("Could not initialize SQLite")); + +#endif /* APR_HAS_THRADS */ + + return SVN_NO_ERROR; +} + +static svn_error_t * +internal_open(sqlite3 **db3, const char *path, svn_sqlite__mode_t mode, + apr_pool_t *scratch_pool) +{ + { + int flags; + + if (mode == svn_sqlite__mode_readonly) + flags = SQLITE_OPEN_READONLY; + else if (mode == svn_sqlite__mode_readwrite) + flags = SQLITE_OPEN_READWRITE; + else if (mode == svn_sqlite__mode_rwcreate) + flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + else + SVN_ERR_MALFUNCTION(); + + /* Turn off SQLite's mutexes. All svn objects are single-threaded, + so we can already guarantee that our use of the SQLite handle + will be serialized properly. + + Note: in 3.6.x, we've already config'd SQLite into MULTITHREAD mode, + so this is probably redundant, but if we are running in a process where + somebody initialized SQLite before us it is needed anyway. */ + flags |= SQLITE_OPEN_NOMUTEX; + + /* Open the database. Note that a handle is returned, even when an error + occurs (except for out-of-memory); thus, we can safely use it to + extract an error message and construct an svn_error_t. */ + { + /* We'd like to use SQLITE_ERR here, but we can't since it would + just return an error and leave the database open. So, we need to + do this manually. */ + /* ### SQLITE_CANTOPEN */ + int err_code = sqlite3_open_v2(path, db3, flags, NULL); + if (err_code != SQLITE_OK) + { + /* Save the error message before closing the SQLite handle. */ + char *msg = apr_pstrdup(scratch_pool, sqlite3_errmsg(*db3)); + + /* We don't catch the error here, since we care more about the open + error than the close error at this point. */ + sqlite3_close(*db3); + + SQLITE_ERR_MSG(err_code, msg); + } + } + } + + /* Retry until timeout when database is busy. */ + SQLITE_ERR_MSG(sqlite3_busy_timeout(*db3, BUSY_TIMEOUT), + sqlite3_errmsg(*db3)); + + return SVN_NO_ERROR; +} + + +/* APR cleanup function used to close the database when its pool is destroyed. + DATA should be the svn_sqlite__db_t handle for the database. */ +static apr_status_t +close_apr(void *data) +{ + svn_sqlite__db_t *db = data; + svn_error_t *err = SVN_NO_ERROR; + apr_status_t result; + int i; + + /* Check to see if we've already closed this database. */ + if (db->db3 == NULL) + return APR_SUCCESS; + + /* Finalize any existing prepared statements. */ + for (i = 0; i < db->nbr_statements; i++) + { + if (db->prepared_stmts[i]) + { + if (db->prepared_stmts[i]->needs_reset) + { +#ifdef SVN_DEBUG + const char *stmt_text = db->statement_strings[i]; + stmt_text = stmt_text; /* Provide value for debugger */ + + SVN_ERR_MALFUNCTION_NO_RETURN(); +#else + err = svn_error_compose_create( + err, + svn_sqlite__reset(db->prepared_stmts[i])); +#endif + } + err = svn_error_compose_create( + svn_sqlite__finalize(db->prepared_stmts[i]), err); + } + } + /* And finalize any used internal statements */ + for (; i < db->nbr_statements + STMT_INTERNAL_LAST; i++) + { + if (db->prepared_stmts[i]) + { + err = svn_error_compose_create( + svn_sqlite__finalize(db->prepared_stmts[i]), err); + } + } + + result = sqlite3_close(db->db3); + + /* If there's a pre-existing error, return it. */ + if (err) + { + result = err->apr_err; + svn_error_clear(err); + return result; + } + + if (result != SQLITE_OK) + return SQLITE_ERROR_CODE(result); /* ### lossy */ + + db->db3 = NULL; + + return APR_SUCCESS; +} + + +svn_error_t * +svn_sqlite__open(svn_sqlite__db_t **db, const char *path, + svn_sqlite__mode_t mode, const char * const statements[], + int unused1, const char * const *unused2, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_atomic__init_once(&sqlite_init_state, + init_sqlite, NULL, scratch_pool)); + + *db = apr_pcalloc(result_pool, sizeof(**db)); + + SVN_ERR(internal_open(&(*db)->db3, path, mode, scratch_pool)); + +#ifdef SQLITE3_DEBUG + sqlite3_trace((*db)->db3, sqlite_tracer, (*db)->db3); +#endif +#ifdef SQLITE3_PROFILE + sqlite3_profile((*db)->db3, sqlite_profiler, (*db)->db3); +#endif + + /* ### simplify this. remnants of some old SQLite compat code. */ + { + int ignored_err = SQLITE_OK; + + SVN_ERR(exec_sql2(*db, "PRAGMA case_sensitive_like=1;", ignored_err)); + } + + SVN_ERR(exec_sql(*db, + /* Disable synchronization to disable the explicit disk flushes + that make Sqlite up to 50 times slower; especially on small + transactions. + + This removes some stability guarantees on specific hardware + and power failures, but still guarantees atomic commits on + application crashes. With our dependency on external data + like pristine files (Wc) and revision files (repository), + we can't keep up these additional guarantees anyway. + + ### Maybe switch to NORMAL(1) when we use larger transaction + scopes */ + "PRAGMA synchronous=OFF;" + /* Enable recursive triggers so that a user trigger will fire + in the deletion phase of an INSERT OR REPLACE statement. + Requires SQLite >= 3.6.18 */ + "PRAGMA recursive_triggers=ON;")); + +#if defined(SVN_DEBUG) + /* When running in debug mode, enable the checking of foreign key + constraints. This has possible performance implications, so we don't + bother to do it for production...for now. */ + SVN_ERR(exec_sql(*db, "PRAGMA foreign_keys=ON;")); +#endif + + /* Store temporary tables in RAM instead of in temporary files, but don't + fail on this if this option is disabled in the sqlite compilation by + setting SQLITE_TEMP_STORE to 0 (always to disk) */ + svn_error_clear(exec_sql(*db, "PRAGMA temp_store = MEMORY;")); + + /* Store the provided statements. */ + if (statements) + { + (*db)->statement_strings = statements; + (*db)->nbr_statements = 0; + while (*statements != NULL) + { + statements++; + (*db)->nbr_statements++; + } + + (*db)->prepared_stmts = apr_pcalloc( + result_pool, + ((*db)->nbr_statements + STMT_INTERNAL_LAST) + * sizeof(svn_sqlite__stmt_t *)); + } + else + { + (*db)->nbr_statements = 0; + (*db)->prepared_stmts = apr_pcalloc(result_pool, + (0 + STMT_INTERNAL_LAST) + * sizeof(svn_sqlite__stmt_t *)); + } + + (*db)->state_pool = result_pool; + apr_pool_cleanup_register(result_pool, *db, close_apr, apr_pool_cleanup_null); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__close(svn_sqlite__db_t *db) +{ + apr_status_t result = apr_pool_cleanup_run(db->state_pool, db, close_apr); + + if (result == APR_SUCCESS) + return SVN_NO_ERROR; + + return svn_error_wrap_apr(result, NULL); +} + +static svn_error_t * +reset_all_statements(svn_sqlite__db_t *db, + svn_error_t *error_to_wrap) +{ + int i; + svn_error_t *err; + + /* ### Should we reorder the errors in this specific case + ### to avoid returning the normal error as top level error? */ + + err = svn_error_compose_create(error_to_wrap, + svn_error_create(SVN_ERR_SQLITE_RESETTING_FOR_ROLLBACK, + NULL, NULL)); + + for (i = 0; i < db->nbr_statements; i++) + if (db->prepared_stmts[i] && db->prepared_stmts[i]->needs_reset) + err = svn_error_compose_create(err, + svn_sqlite__reset(db->prepared_stmts[i])); + + return err; +} + +svn_error_t * +svn_sqlite__begin_transaction(svn_sqlite__db_t *db) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_BEGIN_TRANSACTION)); + SVN_ERR(svn_sqlite__step_done(stmt)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__begin_immediate_transaction(svn_sqlite__db_t *db) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION)); + SVN_ERR(svn_sqlite__step_done(stmt)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__begin_savepoint(svn_sqlite__db_t *db) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_SAVEPOINT_SVN)); + SVN_ERR(svn_sqlite__step_done(stmt)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__finish_transaction(svn_sqlite__db_t *db, + svn_error_t *err) +{ + svn_sqlite__stmt_t *stmt; + + /* Commit or rollback the sqlite transaction. */ + if (err) + { + svn_error_t *err2; + + err2 = get_internal_statement(&stmt, db, + STMT_INTERNAL_ROLLBACK_TRANSACTION); + if (!err2) + err2 = svn_sqlite__step_done(stmt); + + if (err2 && err2->apr_err == SVN_ERR_SQLITE_BUSY) + { + /* ### Houston, we have a problem! + + We are trying to rollback but we can't because some + statements are still busy. This leaves the database + unusable for future transactions as the current transaction + is still open. + + As we are returning the actual error as the most relevant + error in the chain, our caller might assume that it can + retry/compensate on this error (e.g. SVN_WC_LOCKED), while + in fact the SQLite database is unusable until the statements + started within this transaction are reset and the transaction + aborted. + + We try to compensate by resetting all prepared but unreset + statements; but we leave the busy error in the chain anyway to + help diagnosing the original error and help in finding where + a reset statement is missing. */ + + err2 = reset_all_statements(db, err2); + err2 = svn_error_compose_create( + svn_sqlite__step_done(stmt), + err2); + } + + return svn_error_compose_create(err, + err2); + } + + SVN_ERR(get_internal_statement(&stmt, db, STMT_INTERNAL_COMMIT_TRANSACTION)); + return svn_error_trace(svn_sqlite__step_done(stmt)); +} + +svn_error_t * +svn_sqlite__finish_savepoint(svn_sqlite__db_t *db, + svn_error_t *err) +{ + svn_sqlite__stmt_t *stmt; + + if (err) + { + svn_error_t *err2; + + err2 = get_internal_statement(&stmt, db, + STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN); + + if (!err2) + err2 = svn_sqlite__step_done(stmt); + + if (err2 && err2->apr_err == SVN_ERR_SQLITE_BUSY) + { + /* Ok, we have a major problem. Some statement is still open, which + makes it impossible to release this savepoint. + + ### See huge comment in svn_sqlite__finish_transaction for + further details */ + + err2 = reset_all_statements(db, err2); + err2 = svn_error_compose_create(svn_sqlite__step_done(stmt), err2); + } + + err = svn_error_compose_create(err, err2); + err2 = get_internal_statement(&stmt, db, + STMT_INTERNAL_RELEASE_SAVEPOINT_SVN); + + if (!err2) + err2 = svn_sqlite__step_done(stmt); + + return svn_error_trace(svn_error_compose_create(err, err2)); + } + + SVN_ERR(get_internal_statement(&stmt, db, + STMT_INTERNAL_RELEASE_SAVEPOINT_SVN)); + + return svn_error_trace(svn_sqlite__step_done(stmt)); +} + +svn_error_t * +svn_sqlite__with_transaction(svn_sqlite__db_t *db, + svn_sqlite__transaction_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool /* NULL allowed */) +{ + SVN_SQLITE__WITH_TXN(cb_func(cb_baton, db, scratch_pool), db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__with_immediate_transaction( + svn_sqlite__db_t *db, + svn_sqlite__transaction_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool /* NULL allowed */) +{ + SVN_SQLITE__WITH_IMMEDIATE_TXN(cb_func(cb_baton, db, scratch_pool), db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__with_lock(svn_sqlite__db_t *db, + svn_sqlite__transaction_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool /* NULL allowed */) +{ + SVN_SQLITE__WITH_LOCK(cb_func(cb_baton, db, scratch_pool), db); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_sqlite__hotcopy(const char *src_path, + const char *dst_path, + apr_pool_t *scratch_pool) +{ + svn_sqlite__db_t *src_db; + + SVN_ERR(svn_sqlite__open(&src_db, src_path, svn_sqlite__mode_readonly, + NULL, 0, NULL, + scratch_pool, scratch_pool)); + + { + svn_sqlite__db_t *dst_db; + sqlite3_backup *backup; + int rc1, rc2; + + SVN_ERR(svn_sqlite__open(&dst_db, dst_path, svn_sqlite__mode_rwcreate, + NULL, 0, NULL, scratch_pool, scratch_pool)); + backup = sqlite3_backup_init(dst_db->db3, "main", src_db->db3, "main"); + if (!backup) + return svn_error_createf(SVN_ERR_SQLITE_ERROR, NULL, + _("SQLite hotcopy failed for %s"), src_path); + do + { + /* Pages are usually 1024 byte (SQLite docs). On my laptop + copying gets faster as the number of pages is increased up + to about 64, beyond that speed levels off. Lets put the + number of pages an order of magnitude higher, this is still + likely to be a fraction of large databases. */ + rc1 = sqlite3_backup_step(backup, 1024); + + /* Should we sleep on SQLITE_OK? That would make copying a + large database take much longer. When we do sleep how, + long should we sleep? Should the sleep get longer if we + keep getting BUSY/LOCKED? I have no real reason for + choosing 25. */ + if (rc1 == SQLITE_BUSY || rc1 == SQLITE_LOCKED) + sqlite3_sleep(25); + } + while (rc1 == SQLITE_OK || rc1 == SQLITE_BUSY || rc1 == SQLITE_LOCKED); + rc2 = sqlite3_backup_finish(backup); + if (rc1 != SQLITE_DONE) + SQLITE_ERR(rc1, dst_db); + SQLITE_ERR(rc2, dst_db); + SVN_ERR(svn_sqlite__close(dst_db)); + } + + SVN_ERR(svn_sqlite__close(src_db)); + + return SVN_NO_ERROR; +} + +struct function_wrapper_baton_t +{ + svn_sqlite__func_t func; + void *baton; + + apr_pool_t *scratch_pool; +}; + +static void +wrapped_func(sqlite3_context *context, + int argc, + sqlite3_value *values[]) +{ + struct function_wrapper_baton_t *fwb = sqlite3_user_data(context); + svn_sqlite__context_t sctx; + svn_sqlite__value_t **local_vals = + apr_palloc(fwb->scratch_pool, + sizeof(svn_sqlite__value_t *) * argc); + svn_error_t *err; + int i; + + sctx.context = context; + + for (i = 0; i < argc; i++) + { + local_vals[i] = apr_palloc(fwb->scratch_pool, sizeof(*local_vals[i])); + local_vals[i]->value = values[i]; + } + + err = fwb->func(&sctx, argc, local_vals, fwb->scratch_pool); + svn_pool_clear(fwb->scratch_pool); + + if (err) + { + char buf[256]; + sqlite3_result_error(context, + svn_err_best_message(err, buf, sizeof(buf)), + -1); + svn_error_clear(err); + } +} + +svn_error_t * +svn_sqlite__create_scalar_function(svn_sqlite__db_t *db, + const char *func_name, + int argc, + svn_sqlite__func_t func, + void *baton) +{ + struct function_wrapper_baton_t *fwb = apr_pcalloc(db->state_pool, + sizeof(*fwb)); + + fwb->scratch_pool = svn_pool_create(db->state_pool); + fwb->func = func; + fwb->baton = baton; + + SQLITE_ERR(sqlite3_create_function(db->db3, func_name, argc, SQLITE_ANY, + fwb, wrapped_func, NULL, NULL), + db); + + return SVN_NO_ERROR; +} + +int +svn_sqlite__value_type(svn_sqlite__value_t *val) +{ + return sqlite3_value_type(val->value); +} + +const char * +svn_sqlite__value_text(svn_sqlite__value_t *val) +{ + return (const char *) sqlite3_value_text(val->value); +} + +void +svn_sqlite__result_null(svn_sqlite__context_t *sctx) +{ + sqlite3_result_null(sctx->context); +} + +void +svn_sqlite__result_int64(svn_sqlite__context_t *sctx, apr_int64_t val) +{ + sqlite3_result_int64(sctx->context, val); +} diff --git a/subversion/libsvn_subr/sqlite3wrapper.c b/subversion/libsvn_subr/sqlite3wrapper.c new file mode 100644 index 000000000000..d1941aa88185 --- /dev/null +++ b/subversion/libsvn_subr/sqlite3wrapper.c @@ -0,0 +1,62 @@ +/* sqlite3wrapper.c + * + * ==================================================================== + * 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_private_config.h" + +/* Include sqlite3 inline, making all symbols private. */ +#ifdef SVN_SQLITE_INLINE +# define SQLITE_OMIT_DEPRECATED +# define SQLITE_API static +# if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 6 || __APPLE_CC__)) +# if !__APPLE_CC__ || __GNUC_MINOR__ >= 6 +# pragma GCC diagnostic push +# endif +# pragma GCC diagnostic ignored "-Wunreachable-code" +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wcast-qual" +# pragma GCC diagnostic ignored "-Wunused" +# pragma GCC diagnostic ignored "-Wshadow" +# if __APPLE_CC__ +# pragma GCC diagnostic ignored "-Wshorten-64-to-32" +# endif +# endif +# ifdef __APPLE__ +# include <Availability.h> +# if __MAC_OS_X_VERSION_MIN_REQUIRED < 1060 + /* <libkern/OSAtomic.h> is included on OS X by sqlite3.c, and + on old systems (Leopard or older), it cannot be compiled + with -std=c89 because it uses inline. This is a work-around. */ +# define inline __inline__ +# include <libkern/OSAtomic.h> +# undef inline +# endif +# endif +# include <sqlite3.c> +# if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +# endif + +/* Expose the sqlite API vtable and the two missing functions */ +const sqlite3_api_routines *const svn_sqlite3__api_funcs = &sqlite3Apis; +int (*const svn_sqlite3__api_initialize)(void) = sqlite3_initialize; +int (*const svn_sqlite3__api_config)(int, ...) = sqlite3_config; +#endif diff --git a/subversion/libsvn_subr/ssl_client_cert_providers.c b/subversion/libsvn_subr/ssl_client_cert_providers.c new file mode 100644 index 000000000000..cf86fa178226 --- /dev/null +++ b/subversion/libsvn_subr/ssl_client_cert_providers.c @@ -0,0 +1,209 @@ +/* + * ssl_client_cert_providers.c: providers for + * SVN_AUTH_CRED_SSL_CLIENT_CERT + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_pools.h> +#include "svn_hash.h" +#include "svn_auth.h" +#include "svn_error.h" +#include "svn_config.h" + + +/*-----------------------------------------------------------------------*/ +/* File provider */ +/*-----------------------------------------------------------------------*/ + +/* retrieve and load the ssl client certificate file from servers + config */ +static svn_error_t * +ssl_client_cert_file_first_credentials(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + svn_config_t *cfg = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS); + const char *server_group = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SERVER_GROUP); + const char *cert_file; + + cert_file = + svn_config_get_server_setting(cfg, server_group, + SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE, + NULL); + + if (cert_file != NULL) + { + svn_auth_cred_ssl_client_cert_t *cred = + apr_palloc(pool, sizeof(*cred)); + + cred->cert_file = cert_file; + cred->may_save = FALSE; + *credentials_p = cred; + } + else + { + *credentials_p = NULL; + } + + *iter_baton = NULL; + return SVN_NO_ERROR; +} + + +static const svn_auth_provider_t ssl_client_cert_file_provider = + { + SVN_AUTH_CRED_SSL_CLIENT_CERT, + ssl_client_cert_file_first_credentials, + NULL, + NULL + }; + + +/*** Public API to SSL file providers. ***/ +void svn_auth_get_ssl_client_cert_file_provider + (svn_auth_provider_object_t **provider, apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + po->vtable = &ssl_client_cert_file_provider; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Prompt provider */ +/*-----------------------------------------------------------------------*/ + +/* Baton type for prompting to send client ssl creds. + There is no iteration baton type. */ +typedef struct ssl_client_cert_prompt_provider_baton_t +{ + svn_auth_ssl_client_cert_prompt_func_t prompt_func; + void *prompt_baton; + + /* how many times to re-prompt after the first one fails */ + int retry_limit; +} ssl_client_cert_prompt_provider_baton_t; + +/* Iteration baton. */ +typedef struct ssl_client_cert_prompt_iter_baton_t +{ + /* The original provider baton */ + ssl_client_cert_prompt_provider_baton_t *pb; + + /* The original realmstring */ + const char *realmstring; + + /* how many times we've reprompted */ + int retries; +} ssl_client_cert_prompt_iter_baton_t; + + +static svn_error_t * +ssl_client_cert_prompt_first_cred(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + ssl_client_cert_prompt_provider_baton_t *pb = provider_baton; + ssl_client_cert_prompt_iter_baton_t *ib = + apr_pcalloc(pool, sizeof(*ib)); + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + SVN_ERR(pb->prompt_func((svn_auth_cred_ssl_client_cert_t **) credentials_p, + pb->prompt_baton, realmstring, ! no_auth_cache, + pool)); + + ib->pb = pb; + ib->realmstring = apr_pstrdup(pool, realmstring); + ib->retries = 0; + *iter_baton = ib; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +ssl_client_cert_prompt_next_cred(void **credentials_p, + void *iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + ssl_client_cert_prompt_iter_baton_t *ib = iter_baton; + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + if ((ib->pb->retry_limit >= 0) && (ib->retries >= ib->pb->retry_limit)) + { + /* give up, go on to next provider. */ + *credentials_p = NULL; + return SVN_NO_ERROR; + } + ib->retries++; + + return ib->pb->prompt_func((svn_auth_cred_ssl_client_cert_t **) + credentials_p, ib->pb->prompt_baton, + ib->realmstring, ! no_auth_cache, pool); +} + + +static const svn_auth_provider_t ssl_client_cert_prompt_provider = { + SVN_AUTH_CRED_SSL_CLIENT_CERT, + ssl_client_cert_prompt_first_cred, + ssl_client_cert_prompt_next_cred, + NULL +}; + + +/*** Public API to SSL prompting providers. ***/ +void svn_auth_get_ssl_client_cert_prompt_provider + (svn_auth_provider_object_t **provider, + svn_auth_ssl_client_cert_prompt_func_t prompt_func, + void *prompt_baton, + int retry_limit, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + ssl_client_cert_prompt_provider_baton_t *pb = apr_palloc(pool, sizeof(*pb)); + + pb->prompt_func = prompt_func; + pb->prompt_baton = prompt_baton; + pb->retry_limit = retry_limit; + + po->vtable = &ssl_client_cert_prompt_provider; + po->provider_baton = pb; + *provider = po; +} diff --git a/subversion/libsvn_subr/ssl_client_cert_pw_providers.c b/subversion/libsvn_subr/ssl_client_cert_pw_providers.c new file mode 100644 index 000000000000..6c1bcf17c29c --- /dev/null +++ b/subversion/libsvn_subr/ssl_client_cert_pw_providers.c @@ -0,0 +1,506 @@ +/* + * ssl_client_cert_pw_providers.c: providers for + * SVN_AUTH_CRED_SSL_CLIENT_CERT_PW + * + * ==================================================================== + * 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 "svn_hash.h" +#include "svn_auth.h" +#include "svn_error.h" +#include "svn_config.h" +#include "svn_string.h" + +#include "private/svn_auth_private.h" + +#include "svn_private_config.h" + +/*-----------------------------------------------------------------------*/ +/* File provider */ +/*-----------------------------------------------------------------------*/ + +/* The keys that will be stored on disk. These serve the same role as + * similar constants in other providers. + * + * AUTHN_PASSTYPE_KEY just records the passphrase type next to the + * passphrase, so that anyone who is manually editing their authn + * files can know which provider owns the password. + */ +#define AUTHN_PASSPHRASE_KEY "passphrase" +#define AUTHN_PASSTYPE_KEY "passtype" + +/* Baton type for the ssl client cert passphrase provider. */ +typedef struct ssl_client_cert_pw_file_provider_baton_t +{ + svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func; + void *prompt_baton; + /* We cache the user's answer to the plaintext prompt, keyed + by realm, in case we'll be called multiple times for the + same realm. So: keys are 'const char *' realm strings, and + values are 'svn_boolean_t *'. */ + apr_hash_t *plaintext_answers; +} ssl_client_cert_pw_file_provider_baton_t; + +/* This implements the svn_auth__password_get_t interface. + Set **PASSPHRASE to the plaintext passphrase retrieved from CREDS; + ignore other parameters. */ +svn_error_t * +svn_auth__ssl_client_cert_pw_get(svn_boolean_t *done, + const char **passphrase, + apr_hash_t *creds, + const char *realmstring, + const char *username, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + svn_string_t *str; + str = svn_hash_gets(creds, AUTHN_PASSPHRASE_KEY); + if (str && str->data) + { + *passphrase = str->data; + *done = TRUE; + return SVN_NO_ERROR; + } + *done = FALSE; + return SVN_NO_ERROR; +} + +/* This implements the svn_auth__password_set_t interface. + Store PASSPHRASE in CREDS; ignore other parameters. */ +svn_error_t * +svn_auth__ssl_client_cert_pw_set(svn_boolean_t *done, + apr_hash_t *creds, + const char *realmstring, + const char *username, + const char *passphrase, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + svn_hash_sets(creds, AUTHN_PASSPHRASE_KEY, + svn_string_create(passphrase, pool)); + *done = TRUE; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_auth__ssl_client_cert_pw_cache_get(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + svn_auth__password_get_t passphrase_get, + const char *passtype, + apr_pool_t *pool) +{ + svn_config_t *cfg = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS); + const char *server_group = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SERVER_GROUP); + svn_boolean_t non_interactive = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NON_INTERACTIVE) + != NULL; + const char *password = + svn_config_get_server_setting(cfg, server_group, + SVN_CONFIG_OPTION_SSL_CLIENT_CERT_PASSWORD, + NULL); + if (! password) + { + svn_error_t *err; + apr_hash_t *creds_hash = NULL; + const char *config_dir = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_DIR); + + /* Try to load passphrase from the auth/ cache. */ + err = svn_config_read_auth_data(&creds_hash, + SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, + realmstring, config_dir, pool); + svn_error_clear(err); + if (! err && creds_hash) + { + svn_boolean_t done; + + SVN_ERR(passphrase_get(&done, &password, creds_hash, realmstring, + NULL, parameters, non_interactive, pool)); + if (!done) + password = NULL; + } + } + + if (password) + { + svn_auth_cred_ssl_client_cert_pw_t *cred + = apr_palloc(pool, sizeof(*cred)); + cred->password = password; + cred->may_save = FALSE; + *credentials_p = cred; + } + else *credentials_p = NULL; + *iter_baton = NULL; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_auth__ssl_client_cert_pw_cache_set(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + svn_auth__password_set_t passphrase_set, + const char *passtype, + apr_pool_t *pool) +{ + svn_auth_cred_ssl_client_cert_pw_t *creds = credentials; + apr_hash_t *creds_hash = NULL; + const char *config_dir; + svn_error_t *err; + svn_boolean_t dont_store_passphrase = + svn_hash_gets(parameters, SVN_AUTH_PARAM_DONT_STORE_SSL_CLIENT_CERT_PP) + != NULL; + svn_boolean_t non_interactive = + svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE) != NULL; + svn_boolean_t no_auth_cache = + (! creds->may_save) + || (svn_hash_gets(parameters, SVN_AUTH_PARAM_NO_AUTH_CACHE) != NULL); + + *saved = FALSE; + + if (no_auth_cache) + return SVN_NO_ERROR; + + config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + creds_hash = apr_hash_make(pool); + + /* Don't store passphrase in any form if the user has told + us not to do so. */ + if (! dont_store_passphrase) + { + svn_boolean_t may_save_passphrase = FALSE; + + /* If the passphrase is going to be stored encrypted, go right + ahead and store it to disk. Else determine whether saving + in plaintext is OK. */ + if (strcmp(passtype, SVN_AUTH__WINCRYPT_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__KWALLET_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE) == 0 + || strcmp(passtype, SVN_AUTH__KEYCHAIN_PASSWORD_TYPE) == 0) + { + may_save_passphrase = TRUE; + } + else + { +#ifdef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE + may_save_passphrase = FALSE; +#else + const char *store_ssl_client_cert_pp_plaintext = + svn_hash_gets(parameters, + SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT); + ssl_client_cert_pw_file_provider_baton_t *b = + (ssl_client_cert_pw_file_provider_baton_t *)provider_baton; + + if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext, + SVN_CONFIG_ASK) == 0) + { + if (non_interactive) + { + /* In non-interactive mode, the default behaviour is + to not store the passphrase */ + may_save_passphrase = FALSE; + } + else if (b->plaintext_passphrase_prompt_func) + { + /* We're interactive, and the client provided a + prompt callback. So we can ask the user. + Check for a cached answer before prompting. + + This is a pointer-to-boolean, rather than just a + boolean, because we must distinguish between + "cached answer is no" and "no answer has been + cached yet". */ + svn_boolean_t *cached_answer = + svn_hash_gets(b->plaintext_answers, realmstring); + + if (cached_answer != NULL) + { + may_save_passphrase = *cached_answer; + } + else + { + apr_pool_t *cached_answer_pool; + + /* Nothing cached for this realm, prompt the user. */ + SVN_ERR((*b->plaintext_passphrase_prompt_func)( + &may_save_passphrase, + realmstring, + b->prompt_baton, + pool)); + + /* Cache the user's answer in case we're called again + * for the same realm. + * + * We allocate the answer cache in the hash table's pool + * to make sure that is has the same life time as the + * hash table itself. This means that the answer will + * survive across RA sessions -- which is important, + * because otherwise we'd prompt users once per RA session. + */ + cached_answer_pool = apr_hash_pool_get(b->plaintext_answers); + cached_answer = apr_palloc(cached_answer_pool, + sizeof(*cached_answer)); + *cached_answer = may_save_passphrase; + svn_hash_sets(b->plaintext_answers, realmstring, + cached_answer); + } + } + else + { + may_save_passphrase = FALSE; + } + } + else if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext, + SVN_CONFIG_FALSE) == 0) + { + may_save_passphrase = FALSE; + } + else if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext, + SVN_CONFIG_TRUE) == 0) + { + may_save_passphrase = TRUE; + } + else + { + return svn_error_createf + (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL, + _("Config error: invalid value '%s' for option '%s'"), + store_ssl_client_cert_pp_plaintext, + SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT); + } +#endif + } + + if (may_save_passphrase) + { + SVN_ERR(passphrase_set(saved, creds_hash, realmstring, + NULL, creds->password, parameters, + non_interactive, pool)); + + if (*saved && passtype) + { + svn_hash_sets(creds_hash, AUTHN_PASSTYPE_KEY, + svn_string_create(passtype, pool)); + } + + /* Save credentials to disk. */ + err = svn_config_write_auth_data(creds_hash, + SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, + realmstring, config_dir, pool); + svn_error_clear(err); + *saved = ! err; + } + } + + return SVN_NO_ERROR; +} + + +/* This implements the svn_auth_provider_t.first_credentials API. + It gets cached (unencrypted) credentials from the ssl client cert + password provider's cache. */ +static svn_error_t * +ssl_client_cert_pw_file_first_credentials(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__ssl_client_cert_pw_cache_get(credentials_p, iter_baton, + provider_baton, parameters, + realmstring, + svn_auth__ssl_client_cert_pw_get, + SVN_AUTH__SIMPLE_PASSWORD_TYPE, + pool); +} + + +/* This implements the svn_auth_provider_t.save_credentials API. + It saves the credentials unencrypted. */ +static svn_error_t * +ssl_client_cert_pw_file_save_credentials(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__ssl_client_cert_pw_cache_set(saved, credentials, + provider_baton, + parameters, + realmstring, + svn_auth__ssl_client_cert_pw_set, + SVN_AUTH__SIMPLE_PASSWORD_TYPE, + pool); +} + + +static const svn_auth_provider_t ssl_client_cert_pw_file_provider = { + SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, + ssl_client_cert_pw_file_first_credentials, + NULL, + ssl_client_cert_pw_file_save_credentials +}; + + +/*** Public API to SSL file providers. ***/ +void +svn_auth_get_ssl_client_cert_pw_file_provider2 + (svn_auth_provider_object_t **provider, + svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func, + void *prompt_baton, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + ssl_client_cert_pw_file_provider_baton_t *pb = apr_pcalloc(pool, + sizeof(*pb)); + + pb->plaintext_passphrase_prompt_func = plaintext_passphrase_prompt_func; + pb->prompt_baton = prompt_baton; + pb->plaintext_answers = apr_hash_make(pool); + + po->vtable = &ssl_client_cert_pw_file_provider; + po->provider_baton = pb; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Prompt provider */ +/*-----------------------------------------------------------------------*/ + +/* Baton type for client passphrase prompting. + There is no iteration baton type. */ +typedef struct ssl_client_cert_pw_prompt_provider_baton_t +{ + svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func; + void *prompt_baton; + + /* how many times to re-prompt after the first one fails */ + int retry_limit; +} ssl_client_cert_pw_prompt_provider_baton_t; + +/* Iteration baton. */ +typedef struct ssl_client_cert_pw_prompt_iter_baton_t +{ + /* The original provider baton */ + ssl_client_cert_pw_prompt_provider_baton_t *pb; + + /* The original realmstring */ + const char *realmstring; + + /* how many times we've reprompted */ + int retries; +} ssl_client_cert_pw_prompt_iter_baton_t; + + +static svn_error_t * +ssl_client_cert_pw_prompt_first_cred(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + ssl_client_cert_pw_prompt_provider_baton_t *pb = provider_baton; + ssl_client_cert_pw_prompt_iter_baton_t *ib = + apr_pcalloc(pool, sizeof(*ib)); + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + SVN_ERR(pb->prompt_func((svn_auth_cred_ssl_client_cert_pw_t **) + credentials_p, pb->prompt_baton, realmstring, + ! no_auth_cache, pool)); + + ib->pb = pb; + ib->realmstring = apr_pstrdup(pool, realmstring); + ib->retries = 0; + *iter_baton = ib; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +ssl_client_cert_pw_prompt_next_cred(void **credentials_p, + void *iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + ssl_client_cert_pw_prompt_iter_baton_t *ib = iter_baton; + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + if ((ib->pb->retry_limit >= 0) && (ib->retries >= ib->pb->retry_limit)) + { + /* give up, go on to next provider. */ + *credentials_p = NULL; + return SVN_NO_ERROR; + } + ib->retries++; + + return ib->pb->prompt_func((svn_auth_cred_ssl_client_cert_pw_t **) + credentials_p, ib->pb->prompt_baton, + ib->realmstring, ! no_auth_cache, pool); +} + + +static const svn_auth_provider_t client_cert_pw_prompt_provider = { + SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, + ssl_client_cert_pw_prompt_first_cred, + ssl_client_cert_pw_prompt_next_cred, + NULL +}; + + +void svn_auth_get_ssl_client_cert_pw_prompt_provider + (svn_auth_provider_object_t **provider, + svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func, + void *prompt_baton, + int retry_limit, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + ssl_client_cert_pw_prompt_provider_baton_t *pb = + apr_palloc(pool, sizeof(*pb)); + + pb->prompt_func = prompt_func; + pb->prompt_baton = prompt_baton; + pb->retry_limit = retry_limit; + + po->vtable = &client_cert_pw_prompt_provider; + po->provider_baton = pb; + *provider = po; +} diff --git a/subversion/libsvn_subr/ssl_server_trust_providers.c b/subversion/libsvn_subr/ssl_server_trust_providers.c new file mode 100644 index 000000000000..c69be772e0e7 --- /dev/null +++ b/subversion/libsvn_subr/ssl_server_trust_providers.c @@ -0,0 +1,234 @@ +/* + * ssl_server_trust_providers.c: providers for + * SVN_AUTH_CRED_SSL_SERVER_TRUST + * + * ==================================================================== + * 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 "svn_hash.h" +#include "svn_auth.h" +#include "svn_error.h" +#include "svn_config.h" +#include "svn_string.h" + + +/*-----------------------------------------------------------------------*/ +/* File provider */ +/*-----------------------------------------------------------------------*/ + +/* The keys that will be stored on disk. These serve the same role as + similar constants in other providers. */ +#define AUTHN_ASCII_CERT_KEY "ascii_cert" +#define AUTHN_FAILURES_KEY "failures" + + +/* retrieve ssl server CA failure overrides (if any) from servers + config */ +static svn_error_t * +ssl_server_trust_file_first_credentials(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + apr_uint32_t *failures = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SSL_SERVER_FAILURES); + const svn_auth_ssl_server_cert_info_t *cert_info = + svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO); + apr_hash_t *creds_hash = NULL; + const char *config_dir; + svn_error_t *error = SVN_NO_ERROR; + + *credentials = NULL; + *iter_baton = NULL; + + /* Check if this is a permanently accepted certificate */ + config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + error = + svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SSL_SERVER_TRUST, + realmstring, config_dir, pool); + svn_error_clear(error); + if (! error && creds_hash) + { + svn_string_t *trusted_cert, *this_cert, *failstr; + apr_uint32_t last_failures = 0; + + trusted_cert = svn_hash_gets(creds_hash, AUTHN_ASCII_CERT_KEY); + this_cert = svn_string_create(cert_info->ascii_cert, pool); + failstr = svn_hash_gets(creds_hash, AUTHN_FAILURES_KEY); + + if (failstr) + { + char *endptr; + unsigned long tmp_ulong = strtoul(failstr->data, &endptr, 10); + + if (*endptr == '\0') + last_failures = (apr_uint32_t) tmp_ulong; + } + + /* If the cert is trusted and there are no new failures, we + * accept it by clearing all failures. */ + if (trusted_cert && + svn_string_compare(this_cert, trusted_cert) && + (*failures & ~last_failures) == 0) + { + *failures = 0; + } + } + + /* If all failures are cleared now, we return the creds */ + if (! *failures) + { + svn_auth_cred_ssl_server_trust_t *creds = + apr_pcalloc(pool, sizeof(*creds)); + creds->may_save = FALSE; /* No need to save it again... */ + *credentials = creds; + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +ssl_server_trust_file_save_credentials(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + svn_auth_cred_ssl_server_trust_t *creds = credentials; + const svn_auth_ssl_server_cert_info_t *cert_info; + apr_hash_t *creds_hash = NULL; + const char *config_dir; + + if (! creds->may_save) + return SVN_NO_ERROR; + + config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + + cert_info = svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO); + + creds_hash = apr_hash_make(pool); + svn_hash_sets(creds_hash, AUTHN_ASCII_CERT_KEY, + svn_string_create(cert_info->ascii_cert, pool)); + svn_hash_sets(creds_hash, + AUTHN_FAILURES_KEY, + svn_string_createf(pool, "%lu", + (unsigned long)creds->accepted_failures)); + + SVN_ERR(svn_config_write_auth_data(creds_hash, + SVN_AUTH_CRED_SSL_SERVER_TRUST, + realmstring, + config_dir, + pool)); + *saved = TRUE; + return SVN_NO_ERROR; +} + + +static const svn_auth_provider_t ssl_server_trust_file_provider = { + SVN_AUTH_CRED_SSL_SERVER_TRUST, + &ssl_server_trust_file_first_credentials, + NULL, + &ssl_server_trust_file_save_credentials, +}; + + +/*** Public API to SSL file providers. ***/ +void +svn_auth_get_ssl_server_trust_file_provider + (svn_auth_provider_object_t **provider, apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &ssl_server_trust_file_provider; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Prompt provider */ +/*-----------------------------------------------------------------------*/ + +/* Baton type for prompting to verify server ssl creds. + There is no iteration baton type. */ +typedef struct ssl_server_trust_prompt_provider_baton_t +{ + svn_auth_ssl_server_trust_prompt_func_t prompt_func; + void *prompt_baton; +} ssl_server_trust_prompt_provider_baton_t; + + +static svn_error_t * +ssl_server_trust_prompt_first_cred(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + ssl_server_trust_prompt_provider_baton_t *pb = provider_baton; + apr_uint32_t *failures = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SSL_SERVER_FAILURES); + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + const svn_auth_ssl_server_cert_info_t *cert_info = + svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO); + svn_boolean_t may_save = (!no_auth_cache + && !(*failures & SVN_AUTH_SSL_OTHER)); + + SVN_ERR(pb->prompt_func((svn_auth_cred_ssl_server_trust_t **)credentials_p, + pb->prompt_baton, realmstring, *failures, cert_info, + may_save, pool)); + + *iter_baton = NULL; + return SVN_NO_ERROR; +} + + +static const svn_auth_provider_t ssl_server_trust_prompt_provider = { + SVN_AUTH_CRED_SSL_SERVER_TRUST, + ssl_server_trust_prompt_first_cred, + NULL, + NULL +}; + + +/*** Public API to SSL prompting providers. ***/ +void +svn_auth_get_ssl_server_trust_prompt_provider + (svn_auth_provider_object_t **provider, + svn_auth_ssl_server_trust_prompt_func_t prompt_func, + void *prompt_baton, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + ssl_server_trust_prompt_provider_baton_t *pb = + apr_palloc(pool, sizeof(*pb)); + pb->prompt_func = prompt_func; + pb->prompt_baton = prompt_baton; + po->vtable = &ssl_server_trust_prompt_provider; + po->provider_baton = pb; + *provider = po; +} diff --git a/subversion/libsvn_subr/stream.c b/subversion/libsvn_subr/stream.c new file mode 100644 index 000000000000..e2529c746765 --- /dev/null +++ b/subversion/libsvn_subr/stream.c @@ -0,0 +1,1826 @@ +/* + * stream.c: svn_stream operations + * + * ==================================================================== + * 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 <assert.h> +#include <stdio.h> + +#include <apr.h> +#include <apr_pools.h> +#include <apr_strings.h> +#include <apr_file_io.h> +#include <apr_errno.h> +#include <apr_md5.h> + +#include <zlib.h> + +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_error.h" +#include "svn_string.h" +#include "svn_utf.h" +#include "svn_checksum.h" +#include "svn_path.h" +#include "svn_private_config.h" +#include "private/svn_error_private.h" +#include "private/svn_eol_private.h" +#include "private/svn_io_private.h" +#include "private/svn_subr_private.h" + + +struct svn_stream_t { + void *baton; + svn_read_fn_t read_fn; + svn_stream_skip_fn_t skip_fn; + svn_write_fn_t write_fn; + svn_close_fn_t close_fn; + svn_stream_mark_fn_t mark_fn; + svn_stream_seek_fn_t seek_fn; + svn_stream__is_buffered_fn_t is_buffered_fn; +}; + + +/*** Forward declarations. ***/ + +static svn_error_t * +skip_default_handler(void *baton, apr_size_t len, svn_read_fn_t read_fn); + + +/*** Generic streams. ***/ + +svn_stream_t * +svn_stream_create(void *baton, apr_pool_t *pool) +{ + svn_stream_t *stream; + + stream = apr_palloc(pool, sizeof(*stream)); + stream->baton = baton; + stream->read_fn = NULL; + stream->skip_fn = NULL; + stream->write_fn = NULL; + stream->close_fn = NULL; + stream->mark_fn = NULL; + stream->seek_fn = NULL; + stream->is_buffered_fn = NULL; + return stream; +} + + +void +svn_stream_set_baton(svn_stream_t *stream, void *baton) +{ + stream->baton = baton; +} + + +void +svn_stream_set_read(svn_stream_t *stream, svn_read_fn_t read_fn) +{ + stream->read_fn = read_fn; +} + +void +svn_stream_set_skip(svn_stream_t *stream, svn_stream_skip_fn_t skip_fn) +{ + stream->skip_fn = skip_fn; +} + +void +svn_stream_set_write(svn_stream_t *stream, svn_write_fn_t write_fn) +{ + stream->write_fn = write_fn; +} + +void +svn_stream_set_close(svn_stream_t *stream, svn_close_fn_t close_fn) +{ + stream->close_fn = close_fn; +} + +void +svn_stream_set_mark(svn_stream_t *stream, svn_stream_mark_fn_t mark_fn) +{ + stream->mark_fn = mark_fn; +} + +void +svn_stream_set_seek(svn_stream_t *stream, svn_stream_seek_fn_t seek_fn) +{ + stream->seek_fn = seek_fn; +} + +void +svn_stream__set_is_buffered(svn_stream_t *stream, + svn_stream__is_buffered_fn_t is_buffered_fn) +{ + stream->is_buffered_fn = is_buffered_fn; +} + +svn_error_t * +svn_stream_read(svn_stream_t *stream, char *buffer, apr_size_t *len) +{ + SVN_ERR_ASSERT(stream->read_fn != NULL); + return svn_error_trace(stream->read_fn(stream->baton, buffer, len)); +} + + +svn_error_t * +svn_stream_skip(svn_stream_t *stream, apr_size_t len) +{ + if (stream->skip_fn == NULL) + return svn_error_trace( + skip_default_handler(stream->baton, len, stream->read_fn)); + + return svn_error_trace(stream->skip_fn(stream->baton, len)); +} + + +svn_error_t * +svn_stream_write(svn_stream_t *stream, const char *data, apr_size_t *len) +{ + SVN_ERR_ASSERT(stream->write_fn != NULL); + return svn_error_trace(stream->write_fn(stream->baton, data, len)); +} + + +svn_error_t * +svn_stream_reset(svn_stream_t *stream) +{ + return svn_error_trace( + svn_stream_seek(stream, NULL)); +} + +svn_boolean_t +svn_stream_supports_mark(svn_stream_t *stream) +{ + return stream->mark_fn != NULL; +} + +svn_error_t * +svn_stream_mark(svn_stream_t *stream, svn_stream_mark_t **mark, + apr_pool_t *pool) +{ + if (stream->mark_fn == NULL) + return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL); + + return svn_error_trace(stream->mark_fn(stream->baton, mark, pool)); +} + +svn_error_t * +svn_stream_seek(svn_stream_t *stream, const svn_stream_mark_t *mark) +{ + if (stream->seek_fn == NULL) + return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL); + + return svn_error_trace(stream->seek_fn(stream->baton, mark)); +} + +svn_boolean_t +svn_stream__is_buffered(svn_stream_t *stream) +{ + if (stream->is_buffered_fn == NULL) + return FALSE; + + return stream->is_buffered_fn(stream->baton); +} + +svn_error_t * +svn_stream_close(svn_stream_t *stream) +{ + if (stream->close_fn == NULL) + return SVN_NO_ERROR; + return svn_error_trace(stream->close_fn(stream->baton)); +} + +svn_error_t * +svn_stream_puts(svn_stream_t *stream, + const char *str) +{ + apr_size_t len; + len = strlen(str); + return svn_error_trace(svn_stream_write(stream, str, &len)); +} + +svn_error_t * +svn_stream_printf(svn_stream_t *stream, + apr_pool_t *pool, + const char *fmt, + ...) +{ + const char *message; + va_list ap; + + va_start(ap, fmt); + message = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + + return svn_error_trace(svn_stream_puts(stream, message)); +} + + +svn_error_t * +svn_stream_printf_from_utf8(svn_stream_t *stream, + const char *encoding, + apr_pool_t *pool, + const char *fmt, + ...) +{ + const char *message, *translated; + va_list ap; + + va_start(ap, fmt); + message = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + + SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated, message, encoding, + pool)); + + return svn_error_trace(svn_stream_puts(stream, translated)); +} + +/* Size that 90% of the lines we encounter will be not longer than. + used by stream_readline_bytewise() and stream_readline_chunky(). + */ +#define LINE_CHUNK_SIZE 80 + +/* Guts of svn_stream_readline(). + * Returns the line read from STREAM in *STRINGBUF, and indicates + * end-of-file in *EOF. If DETECT_EOL is TRUE, the end-of-line indicator + * is detected automatically and returned in *EOL. + * If DETECT_EOL is FALSE, *EOL must point to the desired end-of-line + * indicator. STRINGBUF is allocated in POOL. */ +static svn_error_t * +stream_readline_bytewise(svn_stringbuf_t **stringbuf, + svn_boolean_t *eof, + const char *eol, + svn_stream_t *stream, + apr_pool_t *pool) +{ + svn_stringbuf_t *str; + apr_size_t numbytes; + const char *match; + char c; + + /* Since we're reading one character at a time, let's at least + optimize for the 90% case. 90% of the time, we can avoid the + stringbuf ever having to realloc() itself if we start it out at + 80 chars. */ + str = svn_stringbuf_create_ensure(LINE_CHUNK_SIZE, pool); + + /* Read into STR up to and including the next EOL sequence. */ + match = eol; + while (*match) + { + numbytes = 1; + SVN_ERR(svn_stream_read(stream, &c, &numbytes)); + if (numbytes != 1) + { + /* a 'short' read means the stream has run out. */ + *eof = TRUE; + *stringbuf = str; + return SVN_NO_ERROR; + } + + if (c == *match) + match++; + else + match = eol; + + svn_stringbuf_appendbyte(str, c); + } + + *eof = FALSE; + svn_stringbuf_chop(str, match - eol); + *stringbuf = str; + + return SVN_NO_ERROR; +} + +static svn_error_t * +stream_readline_chunky(svn_stringbuf_t **stringbuf, + svn_boolean_t *eof, + const char *eol, + svn_stream_t *stream, + apr_pool_t *pool) +{ + /* Read larger chunks of data at once into this buffer and scan + * that for EOL. A good chunk size should be about 80 chars since + * most text lines will be shorter. However, don't use a much + * larger value because filling the buffer from the stream takes + * time as well. + */ + char buffer[LINE_CHUNK_SIZE+1]; + + /* variables */ + svn_stream_mark_t *mark; + apr_size_t numbytes; + const char *eol_pos; + apr_size_t total_parsed = 0; + + /* invariant for this call */ + const size_t eol_len = strlen(eol); + + /* Remember the line start so this plus the line length will be + * the position to move to at the end of this function. + */ + SVN_ERR(svn_stream_mark(stream, &mark, pool)); + + /* Read the first chunk. */ + numbytes = LINE_CHUNK_SIZE; + SVN_ERR(svn_stream_read(stream, buffer, &numbytes)); + buffer[numbytes] = '\0'; + + /* Look for the EOL in this first chunk. If we find it, we are done here. + */ + eol_pos = strstr(buffer, eol); + if (eol_pos != NULL) + { + *stringbuf = svn_stringbuf_ncreate(buffer, eol_pos - buffer, pool); + total_parsed = eol_pos - buffer + eol_len; + } + else if (numbytes < LINE_CHUNK_SIZE) + { + /* We hit EOF but not EOL. + */ + *stringbuf = svn_stringbuf_ncreate(buffer, numbytes, pool); + *eof = TRUE; + return SVN_NO_ERROR; + } + else + { + /* A larger buffer for the string is needed. */ + svn_stringbuf_t *str; + str = svn_stringbuf_create_ensure(2*LINE_CHUNK_SIZE, pool); + svn_stringbuf_appendbytes(str, buffer, numbytes); + *stringbuf = str; + + /* Loop reading chunks until an EOL was found. If we hit EOF, fall + * back to the standard implementation. */ + do + { + /* Append the next chunk to the string read so far. + */ + svn_stringbuf_ensure(str, str->len + LINE_CHUNK_SIZE); + numbytes = LINE_CHUNK_SIZE; + SVN_ERR(svn_stream_read(stream, str->data + str->len, &numbytes)); + str->len += numbytes; + str->data[str->len] = '\0'; + + /* Look for the EOL in the new data plus the last part of the + * previous chunk because the EOL may span over the boundary + * between both chunks. + */ + eol_pos = strstr(str->data + str->len - numbytes - (eol_len-1), eol); + + if ((numbytes < LINE_CHUNK_SIZE) && (eol_pos == NULL)) + { + /* We hit EOF instead of EOL. */ + *eof = TRUE; + return SVN_NO_ERROR; + } + } + while (eol_pos == NULL); + + /* Number of bytes we actually consumed (i.e. line + EOF). + * We need to "return" the rest to the stream by moving its + * read pointer. + */ + total_parsed = eol_pos - str->data + eol_len; + + /* Terminate the string at the EOL postion and return it. */ + str->len = eol_pos - str->data; + str->data[str->len] = 0; + } + + /* Move the stream read pointer to the first position behind the EOL. + */ + SVN_ERR(svn_stream_seek(stream, mark)); + return svn_error_trace(svn_stream_skip(stream, total_parsed)); +} + +/* Guts of svn_stream_readline(). + * Returns the line read from STREAM in *STRINGBUF, and indicates + * end-of-file in *EOF. EOL must point to the desired end-of-line + * indicator. STRINGBUF is allocated in POOL. */ +static svn_error_t * +stream_readline(svn_stringbuf_t **stringbuf, + svn_boolean_t *eof, + const char *eol, + svn_stream_t *stream, + apr_pool_t *pool) +{ + *eof = FALSE; + + /* Often, we operate on APR file or string-based streams and know what + * EOL we are looking for. Optimize that common case. + */ + if (svn_stream_supports_mark(stream) && + svn_stream__is_buffered(stream)) + { + /* We can efficiently read chunks speculatively and reposition the + * stream pointer to the end of the line once we found that. + */ + SVN_ERR(stream_readline_chunky(stringbuf, + eof, + eol, + stream, + pool)); + } + else + { + /* Use the standard byte-byte implementation. + */ + SVN_ERR(stream_readline_bytewise(stringbuf, + eof, + eol, + stream, + pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_stream_readline(svn_stream_t *stream, + svn_stringbuf_t **stringbuf, + const char *eol, + svn_boolean_t *eof, + apr_pool_t *pool) +{ + return svn_error_trace(stream_readline(stringbuf, eof, eol, stream, + pool)); +} + +svn_error_t *svn_stream_copy3(svn_stream_t *from, svn_stream_t *to, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + char *buf = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); + svn_error_t *err; + svn_error_t *err2; + + /* Read and write chunks until we get a short read, indicating the + end of the stream. (We can't get a short write without an + associated error.) */ + while (1) + { + apr_size_t len = SVN__STREAM_CHUNK_SIZE; + + if (cancel_func) + { + err = cancel_func(cancel_baton); + if (err) + break; + } + + err = svn_stream_read(from, buf, &len); + if (err) + break; + + if (len > 0) + err = svn_stream_write(to, buf, &len); + + if (err || (len != SVN__STREAM_CHUNK_SIZE)) + break; + } + + err2 = svn_error_compose_create(svn_stream_close(from), + svn_stream_close(to)); + + return svn_error_compose_create(err, err2); +} + +svn_error_t * +svn_stream_contents_same2(svn_boolean_t *same, + svn_stream_t *stream1, + svn_stream_t *stream2, + apr_pool_t *pool) +{ + char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); + char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); + apr_size_t bytes_read1 = SVN__STREAM_CHUNK_SIZE; + apr_size_t bytes_read2 = SVN__STREAM_CHUNK_SIZE; + svn_error_t *err = NULL; + + *same = TRUE; /* assume TRUE, until disproved below */ + while (bytes_read1 == SVN__STREAM_CHUNK_SIZE + && bytes_read2 == SVN__STREAM_CHUNK_SIZE) + { + err = svn_stream_read(stream1, buf1, &bytes_read1); + if (err) + break; + err = svn_stream_read(stream2, buf2, &bytes_read2); + if (err) + break; + + if ((bytes_read1 != bytes_read2) + || (memcmp(buf1, buf2, bytes_read1))) + { + *same = FALSE; + break; + } + } + + return svn_error_compose_create(err, + svn_error_compose_create( + svn_stream_close(stream1), + svn_stream_close(stream2))); +} + + +/*** Stream implementation utilities ***/ + +/* Skip data from any stream by reading and simply discarding it. */ +static svn_error_t * +skip_default_handler(void *baton, apr_size_t len, svn_read_fn_t read_fn) +{ + apr_size_t bytes_read = 1; + char buffer[4096]; + apr_size_t to_read = len; + + while ((to_read > 0) && (bytes_read > 0)) + { + bytes_read = sizeof(buffer) < to_read ? sizeof(buffer) : to_read; + SVN_ERR(read_fn(baton, buffer, &bytes_read)); + to_read -= bytes_read; + } + + return SVN_NO_ERROR; +} + + + +/*** Generic readable empty stream ***/ + +static svn_error_t * +read_handler_empty(void *baton, char *buffer, apr_size_t *len) +{ + *len = 0; + return SVN_NO_ERROR; +} + +static svn_error_t * +write_handler_empty(void *baton, const char *data, apr_size_t *len) +{ + return SVN_NO_ERROR; +} + +static svn_error_t * +mark_handler_empty(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) +{ + *mark = NULL; /* Seek to start of stream marker */ + return SVN_NO_ERROR; +} + +static svn_error_t * +seek_handler_empty(void *baton, const svn_stream_mark_t *mark) +{ + return SVN_NO_ERROR; +} + +static svn_boolean_t +is_buffered_handler_empty(void *baton) +{ + return FALSE; +} + + +svn_stream_t * +svn_stream_empty(apr_pool_t *pool) +{ + svn_stream_t *stream; + + stream = svn_stream_create(NULL, pool); + svn_stream_set_read(stream, read_handler_empty); + svn_stream_set_write(stream, write_handler_empty); + svn_stream_set_mark(stream, mark_handler_empty); + svn_stream_set_seek(stream, seek_handler_empty); + svn_stream__set_is_buffered(stream, is_buffered_handler_empty); + return stream; +} + + + +/*** Stream duplication support ***/ +struct baton_tee { + svn_stream_t *out1; + svn_stream_t *out2; +}; + + +static svn_error_t * +write_handler_tee(void *baton, const char *data, apr_size_t *len) +{ + struct baton_tee *bt = baton; + + SVN_ERR(svn_stream_write(bt->out1, data, len)); + SVN_ERR(svn_stream_write(bt->out2, data, len)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +close_handler_tee(void *baton) +{ + struct baton_tee *bt = baton; + + SVN_ERR(svn_stream_close(bt->out1)); + SVN_ERR(svn_stream_close(bt->out2)); + + return SVN_NO_ERROR; +} + + +svn_stream_t * +svn_stream_tee(svn_stream_t *out1, + svn_stream_t *out2, + apr_pool_t *pool) +{ + struct baton_tee *baton; + svn_stream_t *stream; + + if (out1 == NULL) + return out2; + + if (out2 == NULL) + return out1; + + baton = apr_palloc(pool, sizeof(*baton)); + baton->out1 = out1; + baton->out2 = out2; + stream = svn_stream_create(baton, pool); + svn_stream_set_write(stream, write_handler_tee); + svn_stream_set_close(stream, close_handler_tee); + + return stream; +} + + + +/*** Ownership detaching stream ***/ + +static svn_error_t * +read_handler_disown(void *baton, char *buffer, apr_size_t *len) +{ + return svn_error_trace(svn_stream_read(baton, buffer, len)); +} + +static svn_error_t * +skip_handler_disown(void *baton, apr_size_t len) +{ + return svn_error_trace(svn_stream_skip(baton, len)); +} + +static svn_error_t * +write_handler_disown(void *baton, const char *buffer, apr_size_t *len) +{ + return svn_error_trace(svn_stream_write(baton, buffer, len)); +} + +static svn_error_t * +mark_handler_disown(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) +{ + return svn_error_trace(svn_stream_mark(baton, mark, pool)); +} + +static svn_error_t * +seek_handler_disown(void *baton, const svn_stream_mark_t *mark) +{ + return svn_error_trace(svn_stream_seek(baton, mark)); +} + +static svn_boolean_t +is_buffered_handler_disown(void *baton) +{ + return svn_stream__is_buffered(baton); +} + +svn_stream_t * +svn_stream_disown(svn_stream_t *stream, apr_pool_t *pool) +{ + svn_stream_t *s = svn_stream_create(stream, pool); + + svn_stream_set_read(s, read_handler_disown); + svn_stream_set_skip(s, skip_handler_disown); + svn_stream_set_write(s, write_handler_disown); + svn_stream_set_mark(s, mark_handler_disown); + svn_stream_set_seek(s, seek_handler_disown); + svn_stream__set_is_buffered(s, is_buffered_handler_disown); + + return s; +} + + + +/*** Generic stream for APR files ***/ +struct baton_apr { + apr_file_t *file; + apr_pool_t *pool; +}; + +/* svn_stream_mark_t for streams backed by APR files. */ +struct mark_apr { + apr_off_t off; +}; + +static svn_error_t * +read_handler_apr(void *baton, char *buffer, apr_size_t *len) +{ + struct baton_apr *btn = baton; + svn_error_t *err; + svn_boolean_t eof; + + if (*len == 1) + { + err = svn_io_file_getc(buffer, btn->file, btn->pool); + if (err) + { + *len = 0; + if (APR_STATUS_IS_EOF(err->apr_err)) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + } + } + } + else + err = svn_io_file_read_full2(btn->file, buffer, *len, len, + &eof, btn->pool); + + return svn_error_trace(err); +} + +static svn_error_t * +skip_handler_apr(void *baton, apr_size_t len) +{ + struct baton_apr *btn = baton; + apr_off_t offset = len; + + return svn_error_trace( + svn_io_file_seek(btn->file, APR_CUR, &offset, btn->pool)); +} + +static svn_error_t * +write_handler_apr(void *baton, const char *data, apr_size_t *len) +{ + struct baton_apr *btn = baton; + svn_error_t *err; + + if (*len == 1) + { + err = svn_io_file_putc(*data, btn->file, btn->pool); + if (err) + *len = 0; + } + else + err = svn_io_file_write_full(btn->file, data, *len, len, btn->pool); + + return svn_error_trace(err); +} + +static svn_error_t * +close_handler_apr(void *baton) +{ + struct baton_apr *btn = baton; + + return svn_error_trace(svn_io_file_close(btn->file, btn->pool)); +} + +static svn_error_t * +mark_handler_apr(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) +{ + struct baton_apr *btn = baton; + struct mark_apr *mark_apr; + + mark_apr = apr_palloc(pool, sizeof(*mark_apr)); + mark_apr->off = 0; + SVN_ERR(svn_io_file_seek(btn->file, APR_CUR, &mark_apr->off, btn->pool)); + *mark = (svn_stream_mark_t *)mark_apr; + return SVN_NO_ERROR; +} + +static svn_error_t * +seek_handler_apr(void *baton, const svn_stream_mark_t *mark) +{ + struct baton_apr *btn = baton; + apr_off_t offset = (mark != NULL) ? ((const struct mark_apr *)mark)->off : 0; + + SVN_ERR(svn_io_file_seek(btn->file, APR_SET, &offset, btn->pool)); + + return SVN_NO_ERROR; +} + +static svn_boolean_t +is_buffered_handler_apr(void *baton) +{ + struct baton_apr *btn = baton; + return (apr_file_flags_get(btn->file) & APR_BUFFERED) != 0; +} + +svn_error_t * +svn_stream_open_readonly(svn_stream_t **stream, + const char *path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *file; + + SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, result_pool)); + *stream = svn_stream_from_aprfile2(file, FALSE, result_pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_stream_open_writable(svn_stream_t **stream, + const char *path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *file; + + SVN_ERR(svn_io_file_open(&file, path, + APR_WRITE + | APR_BUFFERED + | APR_CREATE + | APR_EXCL, + APR_OS_DEFAULT, result_pool)); + *stream = svn_stream_from_aprfile2(file, FALSE, result_pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_stream_open_unique(svn_stream_t **stream, + const char **temp_path, + const char *dirpath, + svn_io_file_del_t delete_when, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_file_t *file; + + SVN_ERR(svn_io_open_unique_file3(&file, temp_path, dirpath, + delete_when, result_pool, scratch_pool)); + *stream = svn_stream_from_aprfile2(file, FALSE, result_pool); + + return SVN_NO_ERROR; +} + + +svn_stream_t * +svn_stream_from_aprfile2(apr_file_t *file, + svn_boolean_t disown, + apr_pool_t *pool) +{ + struct baton_apr *baton; + svn_stream_t *stream; + + if (file == NULL) + return svn_stream_empty(pool); + + baton = apr_palloc(pool, sizeof(*baton)); + baton->file = file; + baton->pool = pool; + stream = svn_stream_create(baton, pool); + svn_stream_set_read(stream, read_handler_apr); + svn_stream_set_write(stream, write_handler_apr); + svn_stream_set_skip(stream, skip_handler_apr); + svn_stream_set_mark(stream, mark_handler_apr); + svn_stream_set_seek(stream, seek_handler_apr); + svn_stream__set_is_buffered(stream, is_buffered_handler_apr); + + if (! disown) + svn_stream_set_close(stream, close_handler_apr); + + return stream; +} + + +/* Compressed stream support */ + +#define ZBUFFER_SIZE 4096 /* The size of the buffer the + compressed stream uses to read from + the substream. Basically an + arbitrary value, picked to be about + page-sized. */ + +struct zbaton { + z_stream *in; /* compressed stream for reading */ + z_stream *out; /* compressed stream for writing */ + svn_read_fn_t read; /* substream's read function */ + svn_write_fn_t write; /* substream's write function */ + svn_close_fn_t close; /* substream's close function */ + void *read_buffer; /* buffer used for reading from + substream */ + int read_flush; /* what flush mode to use while + reading */ + apr_pool_t *pool; /* The pool this baton is allocated + on */ + void *subbaton; /* The substream's baton */ +}; + +/* zlib alloc function. opaque is the pool we need. */ +static voidpf +zalloc(voidpf opaque, uInt items, uInt size) +{ + apr_pool_t *pool = opaque; + + return apr_palloc(pool, items * size); +} + +/* zlib free function */ +static void +zfree(voidpf opaque, voidpf address) +{ + /* Empty, since we allocate on the pool */ +} + +/* Helper function to figure out the sync mode */ +static svn_error_t * +read_helper_gz(svn_read_fn_t read_fn, + void *baton, + char *buffer, + uInt *len, int *zflush) +{ + uInt orig_len = *len; + + /* There's no reason this value should grow bigger than the range of + uInt, but Subversion's API requires apr_size_t. */ + apr_size_t apr_len = (apr_size_t) *len; + + SVN_ERR((*read_fn)(baton, buffer, &apr_len)); + + /* Type cast back to uInt type that zlib uses. On LP64 platforms + apr_size_t will be bigger than uInt. */ + *len = (uInt) apr_len; + + /* I wanted to use Z_FINISH here, but we need to know our buffer is + big enough */ + *zflush = (*len) < orig_len ? Z_SYNC_FLUSH : Z_SYNC_FLUSH; + + return SVN_NO_ERROR; +} + +/* Handle reading from a compressed stream */ +static svn_error_t * +read_handler_gz(void *baton, char *buffer, apr_size_t *len) +{ + struct zbaton *btn = baton; + int zerr; + + if (btn->in == NULL) + { + btn->in = apr_palloc(btn->pool, sizeof(z_stream)); + btn->in->zalloc = zalloc; + btn->in->zfree = zfree; + btn->in->opaque = btn->pool; + btn->read_buffer = apr_palloc(btn->pool, ZBUFFER_SIZE); + btn->in->next_in = btn->read_buffer; + btn->in->avail_in = ZBUFFER_SIZE; + + SVN_ERR(read_helper_gz(btn->read, btn->subbaton, btn->read_buffer, + &btn->in->avail_in, &btn->read_flush)); + + zerr = inflateInit(btn->in); + SVN_ERR(svn_error__wrap_zlib(zerr, "inflateInit", btn->in->msg)); + } + + btn->in->next_out = (Bytef *) buffer; + btn->in->avail_out = (uInt) *len; + + while (btn->in->avail_out > 0) + { + if (btn->in->avail_in <= 0) + { + btn->in->avail_in = ZBUFFER_SIZE; + btn->in->next_in = btn->read_buffer; + SVN_ERR(read_helper_gz(btn->read, btn->subbaton, btn->read_buffer, + &btn->in->avail_in, &btn->read_flush)); + } + + /* Short read means underlying stream has run out. */ + if (btn->in->avail_in == 0) + { + *len = 0; + return SVN_NO_ERROR; + } + + zerr = inflate(btn->in, btn->read_flush); + if (zerr == Z_STREAM_END) + break; + else if (zerr != Z_OK) + return svn_error_trace(svn_error__wrap_zlib(zerr, "inflate", + btn->in->msg)); + } + + *len -= btn->in->avail_out; + return SVN_NO_ERROR; +} + +/* Compress data and write it to the substream */ +static svn_error_t * +write_handler_gz(void *baton, const char *buffer, apr_size_t *len) +{ + struct zbaton *btn = baton; + apr_pool_t *subpool; + void *write_buf; + apr_size_t buf_size, write_len; + int zerr; + + if (btn->out == NULL) + { + btn->out = apr_palloc(btn->pool, sizeof(z_stream)); + btn->out->zalloc = zalloc; + btn->out->zfree = zfree; + btn->out->opaque = btn->pool; + + zerr = deflateInit(btn->out, Z_DEFAULT_COMPRESSION); + SVN_ERR(svn_error__wrap_zlib(zerr, "deflateInit", btn->out->msg)); + } + + /* The largest buffer we should need is 0.1% larger than the + compressed data, + 12 bytes. This info comes from zlib.h. */ + buf_size = *len + (*len / 1000) + 13; + subpool = svn_pool_create(btn->pool); + write_buf = apr_palloc(subpool, buf_size); + + btn->out->next_in = (Bytef *) buffer; /* Casting away const! */ + btn->out->avail_in = (uInt) *len; + + while (btn->out->avail_in > 0) + { + btn->out->next_out = write_buf; + btn->out->avail_out = (uInt) buf_size; + + zerr = deflate(btn->out, Z_NO_FLUSH); + SVN_ERR(svn_error__wrap_zlib(zerr, "deflate", btn->out->msg)); + write_len = buf_size - btn->out->avail_out; + if (write_len > 0) + SVN_ERR(btn->write(btn->subbaton, write_buf, &write_len)); + } + + svn_pool_destroy(subpool); + + return SVN_NO_ERROR; +} + +/* Handle flushing and closing the stream */ +static svn_error_t * +close_handler_gz(void *baton) +{ + struct zbaton *btn = baton; + int zerr; + + if (btn->in != NULL) + { + zerr = inflateEnd(btn->in); + SVN_ERR(svn_error__wrap_zlib(zerr, "inflateEnd", btn->in->msg)); + } + + if (btn->out != NULL) + { + void *buf; + apr_size_t write_len; + + buf = apr_palloc(btn->pool, ZBUFFER_SIZE); + + while (TRUE) + { + btn->out->next_out = buf; + btn->out->avail_out = ZBUFFER_SIZE; + + zerr = deflate(btn->out, Z_FINISH); + if (zerr != Z_STREAM_END && zerr != Z_OK) + return svn_error_trace(svn_error__wrap_zlib(zerr, "deflate", + btn->out->msg)); + write_len = ZBUFFER_SIZE - btn->out->avail_out; + if (write_len > 0) + SVN_ERR(btn->write(btn->subbaton, buf, &write_len)); + if (zerr == Z_STREAM_END) + break; + } + + zerr = deflateEnd(btn->out); + SVN_ERR(svn_error__wrap_zlib(zerr, "deflateEnd", btn->out->msg)); + } + + if (btn->close != NULL) + return svn_error_trace(btn->close(btn->subbaton)); + else + return SVN_NO_ERROR; +} + + +svn_stream_t * +svn_stream_compressed(svn_stream_t *stream, apr_pool_t *pool) +{ + struct svn_stream_t *zstream; + struct zbaton *baton; + + assert(stream != NULL); + + baton = apr_palloc(pool, sizeof(*baton)); + baton->in = baton->out = NULL; + baton->read = stream->read_fn; + baton->write = stream->write_fn; + baton->close = stream->close_fn; + baton->subbaton = stream->baton; + baton->pool = pool; + baton->read_buffer = NULL; + baton->read_flush = Z_SYNC_FLUSH; + + zstream = svn_stream_create(baton, pool); + svn_stream_set_read(zstream, read_handler_gz); + svn_stream_set_write(zstream, write_handler_gz); + svn_stream_set_close(zstream, close_handler_gz); + + return zstream; +} + + +/* Checksummed stream support */ + +struct checksum_stream_baton +{ + svn_checksum_ctx_t *read_ctx, *write_ctx; + svn_checksum_t **read_checksum; /* Output value. */ + svn_checksum_t **write_checksum; /* Output value. */ + svn_stream_t *proxy; + + /* True if more data should be read when closing the stream. */ + svn_boolean_t read_more; + + /* Pool to allocate read buffer and output values from. */ + apr_pool_t *pool; +}; + +static svn_error_t * +read_handler_checksum(void *baton, char *buffer, apr_size_t *len) +{ + struct checksum_stream_baton *btn = baton; + apr_size_t saved_len = *len; + + SVN_ERR(svn_stream_read(btn->proxy, buffer, len)); + + if (btn->read_checksum) + SVN_ERR(svn_checksum_update(btn->read_ctx, buffer, *len)); + + if (saved_len != *len) + btn->read_more = FALSE; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +write_handler_checksum(void *baton, const char *buffer, apr_size_t *len) +{ + struct checksum_stream_baton *btn = baton; + + if (btn->write_checksum && *len > 0) + SVN_ERR(svn_checksum_update(btn->write_ctx, buffer, *len)); + + return svn_error_trace(svn_stream_write(btn->proxy, buffer, len)); +} + + +static svn_error_t * +close_handler_checksum(void *baton) +{ + struct checksum_stream_baton *btn = baton; + + /* If we're supposed to drain the stream, do so before finalizing the + checksum. */ + if (btn->read_more) + { + char *buf = apr_palloc(btn->pool, SVN__STREAM_CHUNK_SIZE); + apr_size_t len = SVN__STREAM_CHUNK_SIZE; + + do + { + SVN_ERR(read_handler_checksum(baton, buf, &len)); + } + while (btn->read_more); + } + + if (btn->read_ctx) + SVN_ERR(svn_checksum_final(btn->read_checksum, btn->read_ctx, btn->pool)); + + if (btn->write_ctx) + SVN_ERR(svn_checksum_final(btn->write_checksum, btn->write_ctx, btn->pool)); + + return svn_error_trace(svn_stream_close(btn->proxy)); +} + + +svn_stream_t * +svn_stream_checksummed2(svn_stream_t *stream, + svn_checksum_t **read_checksum, + svn_checksum_t **write_checksum, + svn_checksum_kind_t checksum_kind, + svn_boolean_t read_all, + apr_pool_t *pool) +{ + svn_stream_t *s; + struct checksum_stream_baton *baton; + + if (read_checksum == NULL && write_checksum == NULL) + return stream; + + baton = apr_palloc(pool, sizeof(*baton)); + if (read_checksum) + baton->read_ctx = svn_checksum_ctx_create(checksum_kind, pool); + else + baton->read_ctx = NULL; + + if (write_checksum) + baton->write_ctx = svn_checksum_ctx_create(checksum_kind, pool); + else + baton->write_ctx = NULL; + + baton->read_checksum = read_checksum; + baton->write_checksum = write_checksum; + baton->proxy = stream; + baton->read_more = read_all; + baton->pool = pool; + + s = svn_stream_create(baton, pool); + svn_stream_set_read(s, read_handler_checksum); + svn_stream_set_write(s, write_handler_checksum); + svn_stream_set_close(s, close_handler_checksum); + return s; +} + +struct md5_stream_baton +{ + const unsigned char **read_digest; + const unsigned char **write_digest; + svn_checksum_t *read_checksum; + svn_checksum_t *write_checksum; + svn_stream_t *proxy; + apr_pool_t *pool; +}; + +static svn_error_t * +read_handler_md5(void *baton, char *buffer, apr_size_t *len) +{ + struct md5_stream_baton *btn = baton; + return svn_error_trace(svn_stream_read(btn->proxy, buffer, len)); +} + +static svn_error_t * +skip_handler_md5(void *baton, apr_size_t len) +{ + struct md5_stream_baton *btn = baton; + return svn_error_trace(svn_stream_skip(btn->proxy, len)); +} + +static svn_error_t * +write_handler_md5(void *baton, const char *buffer, apr_size_t *len) +{ + struct md5_stream_baton *btn = baton; + return svn_error_trace(svn_stream_write(btn->proxy, buffer, len)); +} + +static svn_error_t * +close_handler_md5(void *baton) +{ + struct md5_stream_baton *btn = baton; + + SVN_ERR(svn_stream_close(btn->proxy)); + + if (btn->read_digest) + *btn->read_digest + = apr_pmemdup(btn->pool, btn->read_checksum->digest, + APR_MD5_DIGESTSIZE); + + if (btn->write_digest) + *btn->write_digest + = apr_pmemdup(btn->pool, btn->write_checksum->digest, + APR_MD5_DIGESTSIZE); + + return SVN_NO_ERROR; +} + + +svn_stream_t * +svn_stream_checksummed(svn_stream_t *stream, + const unsigned char **read_digest, + const unsigned char **write_digest, + svn_boolean_t read_all, + apr_pool_t *pool) +{ + svn_stream_t *s; + struct md5_stream_baton *baton; + + if (! read_digest && ! write_digest) + return stream; + + baton = apr_palloc(pool, sizeof(*baton)); + baton->read_digest = read_digest; + baton->write_digest = write_digest; + baton->pool = pool; + + /* Set BATON->proxy to a stream that will fill in BATON->read_checksum + * and BATON->write_checksum (if we want them) when it is closed. */ + baton->proxy + = svn_stream_checksummed2(stream, + read_digest ? &baton->read_checksum : NULL, + write_digest ? &baton->write_checksum : NULL, + svn_checksum_md5, + read_all, pool); + + /* Create a stream that will forward its read/write/close operations to + * BATON->proxy and will fill in *READ_DIGEST and *WRITE_DIGEST (if we + * want them) after it closes BATON->proxy. */ + s = svn_stream_create(baton, pool); + svn_stream_set_read(s, read_handler_md5); + svn_stream_set_skip(s, skip_handler_md5); + svn_stream_set_write(s, write_handler_md5); + svn_stream_set_close(s, close_handler_md5); + return s; +} + + + + +/* Miscellaneous stream functions. */ +struct stringbuf_stream_baton +{ + svn_stringbuf_t *str; + apr_size_t amt_read; +}; + +/* svn_stream_mark_t for streams backed by stringbufs. */ +struct stringbuf_stream_mark { + apr_size_t pos; +}; + +static svn_error_t * +read_handler_stringbuf(void *baton, char *buffer, apr_size_t *len) +{ + struct stringbuf_stream_baton *btn = baton; + apr_size_t left_to_read = btn->str->len - btn->amt_read; + + *len = (*len > left_to_read) ? left_to_read : *len; + memcpy(buffer, btn->str->data + btn->amt_read, *len); + btn->amt_read += *len; + return SVN_NO_ERROR; +} + +static svn_error_t * +skip_handler_stringbuf(void *baton, apr_size_t len) +{ + struct stringbuf_stream_baton *btn = baton; + apr_size_t left_to_read = btn->str->len - btn->amt_read; + + len = (len > left_to_read) ? left_to_read : len; + btn->amt_read += len; + return SVN_NO_ERROR; +} + +static svn_error_t * +write_handler_stringbuf(void *baton, const char *data, apr_size_t *len) +{ + struct stringbuf_stream_baton *btn = baton; + + svn_stringbuf_appendbytes(btn->str, data, *len); + return SVN_NO_ERROR; +} + +static svn_error_t * +mark_handler_stringbuf(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) +{ + struct stringbuf_stream_baton *btn; + struct stringbuf_stream_mark *stringbuf_stream_mark; + + btn = baton; + + stringbuf_stream_mark = apr_palloc(pool, sizeof(*stringbuf_stream_mark)); + stringbuf_stream_mark->pos = btn->amt_read; + *mark = (svn_stream_mark_t *)stringbuf_stream_mark; + return SVN_NO_ERROR; +} + +static svn_error_t * +seek_handler_stringbuf(void *baton, const svn_stream_mark_t *mark) +{ + struct stringbuf_stream_baton *btn = baton; + + if (mark != NULL) + { + const struct stringbuf_stream_mark *stringbuf_stream_mark; + + stringbuf_stream_mark = (const struct stringbuf_stream_mark *)mark; + btn->amt_read = stringbuf_stream_mark->pos; + } + else + btn->amt_read = 0; + + return SVN_NO_ERROR; +} + +static svn_boolean_t +is_buffered_handler_stringbuf(void *baton) +{ + return TRUE; +} + +svn_stream_t * +svn_stream_from_stringbuf(svn_stringbuf_t *str, + apr_pool_t *pool) +{ + svn_stream_t *stream; + struct stringbuf_stream_baton *baton; + + if (! str) + return svn_stream_empty(pool); + + baton = apr_palloc(pool, sizeof(*baton)); + baton->str = str; + baton->amt_read = 0; + stream = svn_stream_create(baton, pool); + svn_stream_set_read(stream, read_handler_stringbuf); + svn_stream_set_skip(stream, skip_handler_stringbuf); + svn_stream_set_write(stream, write_handler_stringbuf); + svn_stream_set_mark(stream, mark_handler_stringbuf); + svn_stream_set_seek(stream, seek_handler_stringbuf); + svn_stream__set_is_buffered(stream, is_buffered_handler_stringbuf); + return stream; +} + +struct string_stream_baton +{ + const svn_string_t *str; + apr_size_t amt_read; +}; + +/* svn_stream_mark_t for streams backed by stringbufs. */ +struct string_stream_mark { + apr_size_t pos; +}; + +static svn_error_t * +read_handler_string(void *baton, char *buffer, apr_size_t *len) +{ + struct string_stream_baton *btn = baton; + apr_size_t left_to_read = btn->str->len - btn->amt_read; + + *len = (*len > left_to_read) ? left_to_read : *len; + memcpy(buffer, btn->str->data + btn->amt_read, *len); + btn->amt_read += *len; + return SVN_NO_ERROR; +} + +static svn_error_t * +mark_handler_string(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) +{ + struct string_stream_baton *btn; + struct string_stream_mark *marker; + + btn = baton; + + marker = apr_palloc(pool, sizeof(*marker)); + marker->pos = btn->amt_read; + *mark = (svn_stream_mark_t *)marker; + return SVN_NO_ERROR; +} + +static svn_error_t * +seek_handler_string(void *baton, const svn_stream_mark_t *mark) +{ + struct string_stream_baton *btn = baton; + + if (mark != NULL) + { + const struct string_stream_mark *marker; + + marker = (const struct string_stream_mark *)mark; + btn->amt_read = marker->pos; + } + else + btn->amt_read = 0; + + return SVN_NO_ERROR; +} + +static svn_error_t * +skip_handler_string(void *baton, apr_size_t len) +{ + struct string_stream_baton *btn = baton; + apr_size_t left_to_read = btn->str->len - btn->amt_read; + + len = (len > left_to_read) ? left_to_read : len; + btn->amt_read += len; + return SVN_NO_ERROR; +} + +static svn_boolean_t +is_buffered_handler_string(void *baton) +{ + return TRUE; +} + +svn_stream_t * +svn_stream_from_string(const svn_string_t *str, + apr_pool_t *pool) +{ + svn_stream_t *stream; + struct string_stream_baton *baton; + + if (! str) + return svn_stream_empty(pool); + + baton = apr_palloc(pool, sizeof(*baton)); + baton->str = str; + baton->amt_read = 0; + stream = svn_stream_create(baton, pool); + svn_stream_set_read(stream, read_handler_string); + svn_stream_set_mark(stream, mark_handler_string); + svn_stream_set_seek(stream, seek_handler_string); + svn_stream_set_skip(stream, skip_handler_string); + svn_stream__set_is_buffered(stream, is_buffered_handler_string); + return stream; +} + + +svn_error_t * +svn_stream_for_stdin(svn_stream_t **in, apr_pool_t *pool) +{ + apr_file_t *stdin_file; + apr_status_t apr_err; + + apr_err = apr_file_open_stdin(&stdin_file, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, "Can't open stdin"); + + *in = svn_stream_from_aprfile2(stdin_file, TRUE, pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_stream_for_stdout(svn_stream_t **out, apr_pool_t *pool) +{ + apr_file_t *stdout_file; + apr_status_t apr_err; + + apr_err = apr_file_open_stdout(&stdout_file, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, "Can't open stdout"); + + *out = svn_stream_from_aprfile2(stdout_file, TRUE, pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_stream_for_stderr(svn_stream_t **err, apr_pool_t *pool) +{ + apr_file_t *stderr_file; + apr_status_t apr_err; + + apr_err = apr_file_open_stderr(&stderr_file, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, "Can't open stderr"); + + *err = svn_stream_from_aprfile2(stderr_file, TRUE, pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_string_from_stream(svn_string_t **result, + svn_stream_t *stream, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *work = svn_stringbuf_create_ensure(SVN__STREAM_CHUNK_SIZE, + result_pool); + char *buffer = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE); + + while (1) + { + apr_size_t len = SVN__STREAM_CHUNK_SIZE; + + SVN_ERR(svn_stream_read(stream, buffer, &len)); + svn_stringbuf_appendbytes(work, buffer, len); + + if (len < SVN__STREAM_CHUNK_SIZE) + break; + } + + SVN_ERR(svn_stream_close(stream)); + + *result = apr_palloc(result_pool, sizeof(**result)); + (*result)->data = work->data; + (*result)->len = work->len; + + return SVN_NO_ERROR; +} + + +/* These are somewhat arbirary, if we ever get good empirical data as to + actually valid values, feel free to update them. */ +#define BUFFER_BLOCK_SIZE 1024 +#define BUFFER_MAX_SIZE 100000 + +svn_stream_t * +svn_stream_buffered(apr_pool_t *result_pool) +{ + return svn_stream__from_spillbuf(BUFFER_BLOCK_SIZE, BUFFER_MAX_SIZE, + result_pool); +} + + + +/*** Lazyopen Streams ***/ + +/* Custom baton for lazyopen-style wrapper streams. */ +typedef struct lazyopen_baton_t { + + /* Callback function and baton for opening the wrapped stream. */ + svn_stream_lazyopen_func_t open_func; + void *open_baton; + + /* The wrapped stream, or NULL if the stream hasn't yet been + opened. */ + svn_stream_t *real_stream; + apr_pool_t *pool; + + /* Whether to open the wrapped stream on a close call. */ + svn_boolean_t open_on_close; + +} lazyopen_baton_t; + + +/* Use B->open_func/baton to create and set B->real_stream iff it + isn't already set. */ +static svn_error_t * +lazyopen_if_unopened(lazyopen_baton_t *b) +{ + if (b->real_stream == NULL) + { + svn_stream_t *stream; + apr_pool_t *scratch_pool = svn_pool_create(b->pool); + + SVN_ERR(b->open_func(&stream, b->open_baton, + b->pool, scratch_pool)); + + svn_pool_destroy(scratch_pool); + + b->real_stream = stream; + } + + return SVN_NO_ERROR; +} + +/* Implements svn_read_fn_t */ +static svn_error_t * +read_handler_lazyopen(void *baton, + char *buffer, + apr_size_t *len) +{ + lazyopen_baton_t *b = baton; + + SVN_ERR(lazyopen_if_unopened(b)); + SVN_ERR(svn_stream_read(b->real_stream, buffer, len)); + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_skip_fn_t */ +static svn_error_t * +skip_handler_lazyopen(void *baton, + apr_size_t len) +{ + lazyopen_baton_t *b = baton; + + SVN_ERR(lazyopen_if_unopened(b)); + SVN_ERR(svn_stream_skip(b->real_stream, len)); + + return SVN_NO_ERROR; +} + +/* Implements svn_write_fn_t */ +static svn_error_t * +write_handler_lazyopen(void *baton, + const char *data, + apr_size_t *len) +{ + lazyopen_baton_t *b = baton; + + SVN_ERR(lazyopen_if_unopened(b)); + SVN_ERR(svn_stream_write(b->real_stream, data, len)); + + return SVN_NO_ERROR; +} + +/* Implements svn_close_fn_t */ +static svn_error_t * +close_handler_lazyopen(void *baton) +{ + lazyopen_baton_t *b = baton; + + if (b->open_on_close) + SVN_ERR(lazyopen_if_unopened(b)); + if (b->real_stream) + SVN_ERR(svn_stream_close(b->real_stream)); + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_mark_fn_t */ +static svn_error_t * +mark_handler_lazyopen(void *baton, + svn_stream_mark_t **mark, + apr_pool_t *pool) +{ + lazyopen_baton_t *b = baton; + + SVN_ERR(lazyopen_if_unopened(b)); + SVN_ERR(svn_stream_mark(b->real_stream, mark, pool)); + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_seek_fn_t */ +static svn_error_t * +seek_handler_lazyopen(void *baton, + const svn_stream_mark_t *mark) +{ + lazyopen_baton_t *b = baton; + + SVN_ERR(lazyopen_if_unopened(b)); + SVN_ERR(svn_stream_seek(b->real_stream, mark)); + + return SVN_NO_ERROR; +} + +/* Implements svn_stream__is_buffered_fn_t */ +static svn_boolean_t +is_buffered_lazyopen(void *baton) +{ + lazyopen_baton_t *b = baton; + + /* No lazy open as we cannot handle an open error. */ + if (!b->real_stream) + return FALSE; + + return svn_stream__is_buffered(b->real_stream); +} + +svn_stream_t * +svn_stream_lazyopen_create(svn_stream_lazyopen_func_t open_func, + void *open_baton, + svn_boolean_t open_on_close, + apr_pool_t *result_pool) +{ + lazyopen_baton_t *lob = apr_pcalloc(result_pool, sizeof(*lob)); + svn_stream_t *stream; + + lob->open_func = open_func; + lob->open_baton = open_baton; + lob->real_stream = NULL; + lob->pool = result_pool; + lob->open_on_close = open_on_close; + + stream = svn_stream_create(lob, result_pool); + svn_stream_set_read(stream, read_handler_lazyopen); + svn_stream_set_skip(stream, skip_handler_lazyopen); + svn_stream_set_write(stream, write_handler_lazyopen); + svn_stream_set_close(stream, close_handler_lazyopen); + svn_stream_set_mark(stream, mark_handler_lazyopen); + svn_stream_set_seek(stream, seek_handler_lazyopen); + svn_stream__set_is_buffered(stream, is_buffered_lazyopen); + + return stream; +} diff --git a/subversion/libsvn_subr/string.c b/subversion/libsvn_subr/string.c new file mode 100644 index 000000000000..20b0f2452380 --- /dev/null +++ b/subversion/libsvn_subr/string.c @@ -0,0 +1,1273 @@ +/* + * string.c: routines to manipulate counted-length strings + * (svn_stringbuf_t and svn_string_t) and C strings. + * + * + * ==================================================================== + * 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.h> + +#include <string.h> /* for memcpy(), memcmp(), strlen() */ +#include <apr_fnmatch.h> +#include "svn_string.h" /* loads "svn_types.h" and <apr_pools.h> */ +#include "svn_ctype.h" +#include "private/svn_dep_compat.h" +#include "private/svn_string_private.h" + +#include "svn_private_config.h" + + + +/* Allocate the space for a memory buffer from POOL. + * Return a pointer to the new buffer in *DATA and its size in *SIZE. + * The buffer size will be at least MINIMUM_SIZE. + * + * N.B.: The stringbuf creation functions use this, but since stringbufs + * always consume at least 1 byte for the NUL terminator, the + * resulting data pointers will never be NULL. + */ +static APR_INLINE void +membuf_create(void **data, apr_size_t *size, + apr_size_t minimum_size, apr_pool_t *pool) +{ + /* apr_palloc will allocate multiples of 8. + * Thus, we would waste some of that memory if we stuck to the + * smaller size. Note that this is safe even if apr_palloc would + * use some other aligment or none at all. */ + minimum_size = APR_ALIGN_DEFAULT(minimum_size); + *data = (!minimum_size ? NULL : apr_palloc(pool, minimum_size)); + *size = minimum_size; +} + +/* Ensure that the size of a given memory buffer is at least MINIMUM_SIZE + * bytes. If *SIZE is already greater than or equal to MINIMUM_SIZE, + * this function does nothing. + * + * If *SIZE is 0, the allocated buffer size will be MINIMUM_SIZE + * rounded up to the nearest APR alignment boundary. Otherwse, *SIZE + * will be multiplied by a power of two such that the result is + * greater or equal to MINIMUM_SIZE. The pointer to the new buffer + * will be returned in *DATA, and its size in *SIZE. + */ +static APR_INLINE void +membuf_ensure(void **data, apr_size_t *size, + apr_size_t minimum_size, apr_pool_t *pool) +{ + if (minimum_size > *size) + { + apr_size_t new_size = *size; + + if (new_size == 0) + /* APR will increase odd allocation sizes to the next + * multiple for 8, for instance. Take advantage of that + * knowledge and allow for the extra size to be used. */ + new_size = minimum_size; + else + while (new_size < minimum_size) + { + /* new_size is aligned; doubling it should keep it aligned */ + const apr_size_t prev_size = new_size; + new_size *= 2; + + /* check for apr_size_t overflow */ + if (prev_size > new_size) + { + new_size = minimum_size; + break; + } + } + + membuf_create(data, size, new_size, pool); + } +} + +void +svn_membuf__create(svn_membuf_t *membuf, apr_size_t size, apr_pool_t *pool) +{ + membuf_create(&membuf->data, &membuf->size, size, pool); + membuf->pool = pool; +} + +void +svn_membuf__ensure(svn_membuf_t *membuf, apr_size_t size) +{ + membuf_ensure(&membuf->data, &membuf->size, size, membuf->pool); +} + +void +svn_membuf__resize(svn_membuf_t *membuf, apr_size_t size) +{ + const void *const old_data = membuf->data; + const apr_size_t old_size = membuf->size; + + membuf_ensure(&membuf->data, &membuf->size, size, membuf->pool); + if (membuf->data && old_data && old_data != membuf->data) + memcpy(membuf->data, old_data, old_size); +} + +/* Always provide an out-of-line implementation of svn_membuf__zero */ +#undef svn_membuf__zero +void +svn_membuf__zero(svn_membuf_t *membuf) +{ + SVN_MEMBUF__ZERO(membuf); +} + +/* Always provide an out-of-line implementation of svn_membuf__nzero */ +#undef svn_membuf__nzero +void +svn_membuf__nzero(svn_membuf_t *membuf, apr_size_t size) +{ + SVN_MEMBUF__NZERO(membuf, size); +} + +static APR_INLINE svn_boolean_t +string_compare(const char *str1, + const char *str2, + apr_size_t len1, + apr_size_t len2) +{ + /* easy way out :) */ + if (len1 != len2) + return FALSE; + + /* now the strings must have identical lenghths */ + + if ((memcmp(str1, str2, len1)) == 0) + return TRUE; + else + return FALSE; +} + +static APR_INLINE apr_size_t +string_first_non_whitespace(const char *str, apr_size_t len) +{ + apr_size_t i; + + for (i = 0; i < len; i++) + { + if (! svn_ctype_isspace(str[i])) + return i; + } + + /* if we get here, then the string must be entirely whitespace */ + return len; +} + +static APR_INLINE apr_size_t +find_char_backward(const char *str, apr_size_t len, char ch) +{ + apr_size_t i = len; + + while (i != 0) + { + if (str[--i] == ch) + return i; + } + + /* char was not found, return len */ + return len; +} + + +/* svn_string functions */ + +/* Return a new svn_string_t object, allocated in POOL, initialized with + * DATA and SIZE. Do not copy the contents of DATA, just store the pointer. + * SIZE is the length in bytes of DATA, excluding the required NUL + * terminator. */ +static svn_string_t * +create_string(const char *data, apr_size_t size, + apr_pool_t *pool) +{ + svn_string_t *new_string; + + new_string = apr_palloc(pool, sizeof(*new_string)); + + new_string->data = data; + new_string->len = size; + + return new_string; +} + +/* A data buffer for a zero-length string (just a null terminator). Many + * svn_string_t instances may share this same buffer. */ +static const char empty_buffer[1] = {0}; + +svn_string_t * +svn_string_create_empty(apr_pool_t *pool) +{ + svn_string_t *new_string = apr_palloc(pool, sizeof(*new_string)); + new_string->data = empty_buffer; + new_string->len = 0; + + return new_string; +} + + +svn_string_t * +svn_string_ncreate(const char *bytes, apr_size_t size, apr_pool_t *pool) +{ + void *mem; + char *data; + svn_string_t *new_string; + + /* Allocate memory for svn_string_t and data in one chunk. */ + mem = apr_palloc(pool, sizeof(*new_string) + size + 1); + data = (char*)mem + sizeof(*new_string); + + new_string = mem; + new_string->data = data; + new_string->len = size; + + memcpy(data, bytes, size); + + /* Null termination is the convention -- even if we suspect the data + to be binary, it's not up to us to decide, it's the caller's + call. Heck, that's why they call it the caller! */ + data[size] = '\0'; + + return new_string; +} + + +svn_string_t * +svn_string_create(const char *cstring, apr_pool_t *pool) +{ + return svn_string_ncreate(cstring, strlen(cstring), pool); +} + + +svn_string_t * +svn_string_create_from_buf(const svn_stringbuf_t *strbuf, apr_pool_t *pool) +{ + return svn_string_ncreate(strbuf->data, strbuf->len, pool); +} + + +svn_string_t * +svn_string_createv(apr_pool_t *pool, const char *fmt, va_list ap) +{ + char *data = apr_pvsprintf(pool, fmt, ap); + + /* wrap an svn_string_t around the new data */ + return create_string(data, strlen(data), pool); +} + + +svn_string_t * +svn_string_createf(apr_pool_t *pool, const char *fmt, ...) +{ + svn_string_t *str; + + va_list ap; + va_start(ap, fmt); + str = svn_string_createv(pool, fmt, ap); + va_end(ap); + + return str; +} + + +svn_boolean_t +svn_string_isempty(const svn_string_t *str) +{ + return (str->len == 0); +} + + +svn_string_t * +svn_string_dup(const svn_string_t *original_string, apr_pool_t *pool) +{ + return (svn_string_ncreate(original_string->data, + original_string->len, pool)); +} + + + +svn_boolean_t +svn_string_compare(const svn_string_t *str1, const svn_string_t *str2) +{ + return + string_compare(str1->data, str2->data, str1->len, str2->len); +} + + + +apr_size_t +svn_string_first_non_whitespace(const svn_string_t *str) +{ + return + string_first_non_whitespace(str->data, str->len); +} + + +apr_size_t +svn_string_find_char_backward(const svn_string_t *str, char ch) +{ + return find_char_backward(str->data, str->len, ch); +} + +svn_string_t * +svn_stringbuf__morph_into_string(svn_stringbuf_t *strbuf) +{ + /* In debug mode, detect attempts to modify the original STRBUF object. + */ +#ifdef SVN_DEBUG + strbuf->pool = NULL; + strbuf->blocksize = strbuf->len + 1; +#endif + + /* Both, svn_string_t and svn_stringbuf_t are public API structures + * since the svn epoch. Thus, we can rely on their precise layout not + * to change. + * + * It just so happens that svn_string_t is structurally equivalent + * to the (data, len) sub-set of svn_stringbuf_t. There is also no + * difference in alignment and padding. So, we can just re-interpret + * that part of STRBUF as a svn_string_t. + * + * However, since svn_string_t does not know about the blocksize + * member in svn_stringbuf_t, any attempt to re-size the returned + * svn_string_t might invalidate the STRBUF struct. Hence, we consider + * the source STRBUF "consumed". + * + * Modifying the string character content is fine, though. + */ + return (svn_string_t *)&strbuf->data; +} + + + +/* svn_stringbuf functions */ + +svn_stringbuf_t * +svn_stringbuf_create_empty(apr_pool_t *pool) +{ + return svn_stringbuf_create_ensure(0, pool); +} + +svn_stringbuf_t * +svn_stringbuf_create_ensure(apr_size_t blocksize, apr_pool_t *pool) +{ + void *mem; + svn_stringbuf_t *new_string; + + ++blocksize; /* + space for '\0' */ + + /* Allocate memory for svn_string_t and data in one chunk. */ + membuf_create(&mem, &blocksize, blocksize + sizeof(*new_string), pool); + + /* Initialize header and string */ + new_string = mem; + new_string->data = (char*)mem + sizeof(*new_string); + new_string->data[0] = '\0'; + new_string->len = 0; + new_string->blocksize = blocksize - sizeof(*new_string); + new_string->pool = pool; + + return new_string; +} + +svn_stringbuf_t * +svn_stringbuf_ncreate(const char *bytes, apr_size_t size, apr_pool_t *pool) +{ + svn_stringbuf_t *strbuf = svn_stringbuf_create_ensure(size, pool); + memcpy(strbuf->data, bytes, size); + + /* Null termination is the convention -- even if we suspect the data + to be binary, it's not up to us to decide, it's the caller's + call. Heck, that's why they call it the caller! */ + strbuf->data[size] = '\0'; + strbuf->len = size; + + return strbuf; +} + + +svn_stringbuf_t * +svn_stringbuf_create(const char *cstring, apr_pool_t *pool) +{ + return svn_stringbuf_ncreate(cstring, strlen(cstring), pool); +} + + +svn_stringbuf_t * +svn_stringbuf_create_from_string(const svn_string_t *str, apr_pool_t *pool) +{ + return svn_stringbuf_ncreate(str->data, str->len, pool); +} + + +svn_stringbuf_t * +svn_stringbuf_createv(apr_pool_t *pool, const char *fmt, va_list ap) +{ + char *data = apr_pvsprintf(pool, fmt, ap); + apr_size_t size = strlen(data); + svn_stringbuf_t *new_string; + + new_string = apr_palloc(pool, sizeof(*new_string)); + new_string->data = data; + new_string->len = size; + new_string->blocksize = size + 1; + new_string->pool = pool; + + return new_string; +} + + +svn_stringbuf_t * +svn_stringbuf_createf(apr_pool_t *pool, const char *fmt, ...) +{ + svn_stringbuf_t *str; + + va_list ap; + va_start(ap, fmt); + str = svn_stringbuf_createv(pool, fmt, ap); + va_end(ap); + + return str; +} + + +void +svn_stringbuf_fillchar(svn_stringbuf_t *str, unsigned char c) +{ + memset(str->data, c, str->len); +} + + +void +svn_stringbuf_set(svn_stringbuf_t *str, const char *value) +{ + apr_size_t amt = strlen(value); + + svn_stringbuf_ensure(str, amt); + memcpy(str->data, value, amt + 1); + str->len = amt; +} + +void +svn_stringbuf_setempty(svn_stringbuf_t *str) +{ + if (str->len > 0) + str->data[0] = '\0'; + + str->len = 0; +} + + +void +svn_stringbuf_chop(svn_stringbuf_t *str, apr_size_t nbytes) +{ + if (nbytes > str->len) + str->len = 0; + else + str->len -= nbytes; + + str->data[str->len] = '\0'; +} + + +svn_boolean_t +svn_stringbuf_isempty(const svn_stringbuf_t *str) +{ + return (str->len == 0); +} + + +void +svn_stringbuf_ensure(svn_stringbuf_t *str, apr_size_t minimum_size) +{ + void *mem = NULL; + ++minimum_size; /* + space for '\0' */ + + membuf_ensure(&mem, &str->blocksize, minimum_size, str->pool); + if (mem && mem != str->data) + { + if (str->data) + memcpy(mem, str->data, str->len + 1); + str->data = mem; + } +} + + +/* WARNING - Optimized code ahead! + * This function has been hand-tuned for performance. Please read + * the comments below before modifying the code. + */ +void +svn_stringbuf_appendbyte(svn_stringbuf_t *str, char byte) +{ + char *dest; + apr_size_t old_len = str->len; + + /* In most cases, there will be pre-allocated memory left + * to just write the new byte at the end of the used section + * and terminate the string properly. + */ + if (str->blocksize > old_len + 1) + { + /* The following read does not depend this write, so we + * can issue the write first to minimize register pressure: + * The value of old_len+1 is no longer needed; on most processors, + * dest[old_len+1] will be calculated implicitly as part of + * the addressing scheme. + */ + str->len = old_len+1; + + /* Since the compiler cannot be sure that *src->data and *src + * don't overlap, we read src->data *once* before writing + * to *src->data. Replacing dest with str->data would force + * the compiler to read it again after the first byte. + */ + dest = str->data; + + /* If not already available in a register as per ABI, load + * "byte" into the register (e.g. the one freed from old_len+1), + * then write it to the string buffer and terminate it properly. + * + * Including the "byte" fetch, all operations so far could be + * issued at once and be scheduled at the CPU's descression. + * Most likely, no-one will soon depend on the data that will be + * written in this function. So, no stalls there, either. + */ + dest[old_len] = byte; + dest[old_len+1] = '\0'; + } + else + { + /* we need to re-allocate the string buffer + * -> let the more generic implementation take care of that part + */ + + /* Depending on the ABI, "byte" is a register value. If we were + * to take its address directly, the compiler might decide to + * put in on the stack *unconditionally*, even if that would + * only be necessary for this block. + */ + char b = byte; + svn_stringbuf_appendbytes(str, &b, 1); + } +} + + +void +svn_stringbuf_appendbytes(svn_stringbuf_t *str, const char *bytes, + apr_size_t count) +{ + apr_size_t total_len; + void *start_address; + + total_len = str->len + count; /* total size needed */ + + /* svn_stringbuf_ensure adds 1 for null terminator. */ + svn_stringbuf_ensure(str, total_len); + + /* get address 1 byte beyond end of original bytestring */ + start_address = (str->data + str->len); + + memcpy(start_address, bytes, count); + str->len = total_len; + + str->data[str->len] = '\0'; /* We don't know if this is binary + data or not, but convention is + to null-terminate. */ +} + + +void +svn_stringbuf_appendstr(svn_stringbuf_t *targetstr, + const svn_stringbuf_t *appendstr) +{ + svn_stringbuf_appendbytes(targetstr, appendstr->data, appendstr->len); +} + + +void +svn_stringbuf_appendcstr(svn_stringbuf_t *targetstr, const char *cstr) +{ + svn_stringbuf_appendbytes(targetstr, cstr, strlen(cstr)); +} + +void +svn_stringbuf_insert(svn_stringbuf_t *str, + apr_size_t pos, + const char *bytes, + apr_size_t count) +{ + if (bytes + count > str->data && bytes < str->data + str->blocksize) + { + /* special case: BYTES overlaps with this string -> copy the source */ + const char *temp = apr_pstrndup(str->pool, bytes, count); + svn_stringbuf_insert(str, pos, temp, count); + } + else + { + if (pos > str->len) + pos = str->len; + + svn_stringbuf_ensure(str, str->len + count); + memmove(str->data + pos + count, str->data + pos, str->len - pos + 1); + memcpy(str->data + pos, bytes, count); + + str->len += count; + } +} + +void +svn_stringbuf_remove(svn_stringbuf_t *str, + apr_size_t pos, + apr_size_t count) +{ + if (pos > str->len) + pos = str->len; + if (pos + count > str->len) + count = str->len - pos; + + memmove(str->data + pos, str->data + pos + count, str->len - pos - count + 1); + str->len -= count; +} + +void +svn_stringbuf_replace(svn_stringbuf_t *str, + apr_size_t pos, + apr_size_t old_count, + const char *bytes, + apr_size_t new_count) +{ + if (bytes + new_count > str->data && bytes < str->data + str->blocksize) + { + /* special case: BYTES overlaps with this string -> copy the source */ + const char *temp = apr_pstrndup(str->pool, bytes, new_count); + svn_stringbuf_replace(str, pos, old_count, temp, new_count); + } + else + { + if (pos > str->len) + pos = str->len; + if (pos + old_count > str->len) + old_count = str->len - pos; + + if (old_count < new_count) + { + apr_size_t delta = new_count - old_count; + svn_stringbuf_ensure(str, str->len + delta); + } + + if (old_count != new_count) + memmove(str->data + pos + new_count, str->data + pos + old_count, + str->len - pos - old_count + 1); + + memcpy(str->data + pos, bytes, new_count); + str->len += new_count - old_count; + } +} + + +svn_stringbuf_t * +svn_stringbuf_dup(const svn_stringbuf_t *original_string, apr_pool_t *pool) +{ + return (svn_stringbuf_ncreate(original_string->data, + original_string->len, pool)); +} + + + +svn_boolean_t +svn_stringbuf_compare(const svn_stringbuf_t *str1, + const svn_stringbuf_t *str2) +{ + return string_compare(str1->data, str2->data, str1->len, str2->len); +} + + + +apr_size_t +svn_stringbuf_first_non_whitespace(const svn_stringbuf_t *str) +{ + return string_first_non_whitespace(str->data, str->len); +} + + +void +svn_stringbuf_strip_whitespace(svn_stringbuf_t *str) +{ + /* Find first non-whitespace character */ + apr_size_t offset = svn_stringbuf_first_non_whitespace(str); + + /* Go ahead! Waste some RAM, we've got pools! :) */ + str->data += offset; + str->len -= offset; + str->blocksize -= offset; + + /* Now that we've trimmed the front, trim the end, wasting more RAM. */ + while ((str->len > 0) && svn_ctype_isspace(str->data[str->len - 1])) + str->len--; + str->data[str->len] = '\0'; +} + + +apr_size_t +svn_stringbuf_find_char_backward(const svn_stringbuf_t *str, char ch) +{ + return find_char_backward(str->data, str->len, ch); +} + + +svn_boolean_t +svn_string_compare_stringbuf(const svn_string_t *str1, + const svn_stringbuf_t *str2) +{ + return string_compare(str1->data, str2->data, str1->len, str2->len); +} + + + +/*** C string stuff. ***/ + +void +svn_cstring_split_append(apr_array_header_t *array, + const char *input, + const char *sep_chars, + svn_boolean_t chop_whitespace, + apr_pool_t *pool) +{ + char *pats; + char *p; + + pats = apr_pstrdup(pool, input); /* strtok wants non-const data */ + p = svn_cstring_tokenize(sep_chars, &pats); + + while (p) + { + if (chop_whitespace) + { + while (svn_ctype_isspace(*p)) + p++; + + { + char *e = p + (strlen(p) - 1); + while ((e >= p) && (svn_ctype_isspace(*e))) + e--; + *(++e) = '\0'; + } + } + + if (p[0] != '\0') + APR_ARRAY_PUSH(array, const char *) = p; + + p = svn_cstring_tokenize(sep_chars, &pats); + } + + return; +} + + +apr_array_header_t * +svn_cstring_split(const char *input, + const char *sep_chars, + svn_boolean_t chop_whitespace, + apr_pool_t *pool) +{ + apr_array_header_t *a = apr_array_make(pool, 5, sizeof(input)); + svn_cstring_split_append(a, input, sep_chars, chop_whitespace, pool); + return a; +} + + +svn_boolean_t svn_cstring_match_glob_list(const char *str, + const apr_array_header_t *list) +{ + int i; + + for (i = 0; i < list->nelts; i++) + { + const char *this_pattern = APR_ARRAY_IDX(list, i, char *); + + if (apr_fnmatch(this_pattern, str, 0) == APR_SUCCESS) + return TRUE; + } + + return FALSE; +} + +svn_boolean_t +svn_cstring_match_list(const char *str, const apr_array_header_t *list) +{ + int i; + + for (i = 0; i < list->nelts; i++) + { + const char *this_str = APR_ARRAY_IDX(list, i, char *); + + if (strcmp(this_str, str) == 0) + return TRUE; + } + + return FALSE; +} + +char * +svn_cstring_tokenize(const char *sep, char **str) +{ + char *token; + const char * next; + char csep; + + /* check parameters */ + if ((sep == NULL) || (str == NULL) || (*str == NULL)) + return NULL; + + /* let APR handle edge cases and multiple separators */ + csep = *sep; + if (csep == '\0' || sep[1] != '\0') + return apr_strtok(NULL, sep, str); + + /* skip characters in sep (will terminate at '\0') */ + token = *str; + while (*token == csep) + ++token; + + if (!*token) /* no more tokens */ + return NULL; + + /* skip valid token characters to terminate token and + * prepare for the next call (will terminate at '\0) + */ + next = strchr(token, csep); + if (next == NULL) + { + *str = token + strlen(token); + } + else + { + *(char *)next = '\0'; + *str = (char *)next + 1; + } + + return token; +} + +int svn_cstring_count_newlines(const char *msg) +{ + int count = 0; + const char *p; + + for (p = msg; *p; p++) + { + if (*p == '\n') + { + count++; + if (*(p + 1) == '\r') + p++; + } + else if (*p == '\r') + { + count++; + if (*(p + 1) == '\n') + p++; + } + } + + return count; +} + +char * +svn_cstring_join(const apr_array_header_t *strings, + const char *separator, + apr_pool_t *pool) +{ + svn_stringbuf_t *new_str = svn_stringbuf_create_empty(pool); + size_t sep_len = strlen(separator); + int i; + + for (i = 0; i < strings->nelts; i++) + { + const char *string = APR_ARRAY_IDX(strings, i, const char *); + svn_stringbuf_appendbytes(new_str, string, strlen(string)); + svn_stringbuf_appendbytes(new_str, separator, sep_len); + } + return new_str->data; +} + +int +svn_cstring_casecmp(const char *str1, const char *str2) +{ + for (;;) + { + const int a = *str1++; + const int b = *str2++; + const int cmp = svn_ctype_casecmp(a, b); + if (cmp || !a || !b) + return cmp; + } +} + +svn_error_t * +svn_cstring_strtoui64(apr_uint64_t *n, const char *str, + apr_uint64_t minval, apr_uint64_t maxval, + int base) +{ + apr_int64_t val; + char *endptr; + + /* We assume errno is thread-safe. */ + errno = 0; /* APR-0.9 doesn't always set errno */ + + /* ### We're throwing away half the number range here. + * ### APR needs a apr_strtoui64() function. */ + val = apr_strtoi64(str, &endptr, base); + if (errno == EINVAL || endptr == str || str[0] == '\0' || *endptr != '\0') + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Could not convert '%s' into a number"), + str); + if ((errno == ERANGE && (val == APR_INT64_MIN || val == APR_INT64_MAX)) || + val < 0 || (apr_uint64_t)val < minval || (apr_uint64_t)val > maxval) + /* ### Mark this for translation when gettext doesn't choke on macros. */ + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + "Number '%s' is out of range " + "'[%" APR_UINT64_T_FMT ", %" APR_UINT64_T_FMT "]'", + str, minval, maxval); + *n = val; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cstring_atoui64(apr_uint64_t *n, const char *str) +{ + return svn_error_trace(svn_cstring_strtoui64(n, str, 0, + APR_UINT64_MAX, 10)); +} + +svn_error_t * +svn_cstring_atoui(unsigned int *n, const char *str) +{ + apr_uint64_t val; + + SVN_ERR(svn_cstring_strtoui64(&val, str, 0, APR_UINT32_MAX, 10)); + *n = (unsigned int)val; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cstring_strtoi64(apr_int64_t *n, const char *str, + apr_int64_t minval, apr_int64_t maxval, + int base) +{ + apr_int64_t val; + char *endptr; + + /* We assume errno is thread-safe. */ + errno = 0; /* APR-0.9 doesn't always set errno */ + + val = apr_strtoi64(str, &endptr, base); + if (errno == EINVAL || endptr == str || str[0] == '\0' || *endptr != '\0') + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Could not convert '%s' into a number"), + str); + if ((errno == ERANGE && (val == APR_INT64_MIN || val == APR_INT64_MAX)) || + val < minval || val > maxval) + /* ### Mark this for translation when gettext doesn't choke on macros. */ + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + "Number '%s' is out of range " + "'[%" APR_INT64_T_FMT ", %" APR_INT64_T_FMT "]'", + str, minval, maxval); + *n = val; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cstring_atoi64(apr_int64_t *n, const char *str) +{ + return svn_error_trace(svn_cstring_strtoi64(n, str, APR_INT64_MIN, + APR_INT64_MAX, 10)); +} + +svn_error_t * +svn_cstring_atoi(int *n, const char *str) +{ + apr_int64_t val; + + SVN_ERR(svn_cstring_strtoi64(&val, str, APR_INT32_MIN, APR_INT32_MAX, 10)); + *n = (int)val; + return SVN_NO_ERROR; +} + + +apr_status_t +svn__strtoff(apr_off_t *offset, const char *buf, char **end, int base) +{ +#if !APR_VERSION_AT_LEAST(1,0,0) + errno = 0; + *offset = strtol(buf, end, base); + return APR_FROM_OS_ERROR(errno); +#else + return apr_strtoff(offset, buf, end, base); +#endif +} + +/* "Precalculated" itoa values for 2 places (including leading zeros). + * For maximum performance, make sure all table entries are word-aligned. + */ +static const char decimal_table[100][4] + = { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09" + , "10", "11", "12", "13", "14", "15", "16", "17", "18", "19" + , "20", "21", "22", "23", "24", "25", "26", "27", "28", "29" + , "30", "31", "32", "33", "34", "35", "36", "37", "38", "39" + , "40", "41", "42", "43", "44", "45", "46", "47", "48", "49" + , "50", "51", "52", "53", "54", "55", "56", "57", "58", "59" + , "60", "61", "62", "63", "64", "65", "66", "67", "68", "69" + , "70", "71", "72", "73", "74", "75", "76", "77", "78", "79" + , "80", "81", "82", "83", "84", "85", "86", "87", "88", "89" + , "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"}; + +/* Copy the two bytes at SOURCE[0] and SOURCE[1] to DEST[0] and DEST[1] */ +#define COPY_TWO_BYTES(dest,source)\ + memcpy((dest), (source), 2) + +apr_size_t +svn__ui64toa(char * dest, apr_uint64_t number) +{ + char buffer[SVN_INT64_BUFFER_SIZE]; + apr_uint32_t reduced; /* used for 32 bit DIV */ + char* target; + + /* Small numbers are by far the most common case. + * Therefore, we use special code. + */ + if (number < 100) + { + if (number < 10) + { + dest[0] = (char)('0' + number); + dest[1] = 0; + return 1; + } + else + { + COPY_TWO_BYTES(dest, decimal_table[(apr_size_t)number]); + dest[2] = 0; + return 2; + } + } + + /* Standard code. Write string in pairs of chars back-to-front */ + buffer[SVN_INT64_BUFFER_SIZE - 1] = 0; + target = &buffer[SVN_INT64_BUFFER_SIZE - 3]; + + /* Loop may be executed 0 .. 2 times. */ + while (number >= 100000000) + { + /* Number is larger than 100^4, i.e. we can write 4x2 chars. + * Also, use 32 bit DIVs as these are about twice as fast. + */ + reduced = (apr_uint32_t)(number % 100000000); + number /= 100000000; + + COPY_TWO_BYTES(target - 0, decimal_table[reduced % 100]); + reduced /= 100; + COPY_TWO_BYTES(target - 2, decimal_table[reduced % 100]); + reduced /= 100; + COPY_TWO_BYTES(target - 4, decimal_table[reduced % 100]); + reduced /= 100; + COPY_TWO_BYTES(target - 6, decimal_table[reduced % 100]); + target -= 8; + } + + /* Now, the number fits into 32 bits, but may still be larger than 99 */ + reduced = (apr_uint32_t)(number); + while (reduced >= 100) + { + COPY_TWO_BYTES(target, decimal_table[reduced % 100]); + reduced /= 100; + target -= 2; + } + + /* The number is now smaller than 100 but larger than 1 */ + COPY_TWO_BYTES(target, decimal_table[reduced]); + + /* Correction for uneven count of places. */ + if (reduced < 10) + ++target; + + /* Copy to target */ + memcpy(dest, target, &buffer[SVN_INT64_BUFFER_SIZE] - target); + return &buffer[SVN_INT64_BUFFER_SIZE] - target - 1; +} + +apr_size_t +svn__i64toa(char * dest, apr_int64_t number) +{ + if (number >= 0) + return svn__ui64toa(dest, (apr_uint64_t)number); + + *dest = '-'; + return svn__ui64toa(dest + 1, (apr_uint64_t)(0-number)) + 1; +} + +static void +ui64toa_sep(apr_uint64_t number, char seperator, char *buffer) +{ + apr_size_t length = svn__ui64toa(buffer, number); + apr_size_t i; + + for (i = length; i > 3; i -= 3) + { + memmove(&buffer[i - 2], &buffer[i - 3], length - i + 3); + buffer[i-3] = seperator; + length++; + } + + buffer[length] = 0; +} + +char * +svn__ui64toa_sep(apr_uint64_t number, char seperator, apr_pool_t *pool) +{ + char buffer[2 * SVN_INT64_BUFFER_SIZE]; + ui64toa_sep(number, seperator, buffer); + + return apr_pstrdup(pool, buffer); +} + +char * +svn__i64toa_sep(apr_int64_t number, char seperator, apr_pool_t *pool) +{ + char buffer[2 * SVN_INT64_BUFFER_SIZE]; + if (number < 0) + { + buffer[0] = '-'; + ui64toa_sep((apr_uint64_t)(-number), seperator, &buffer[1]); + } + else + ui64toa_sep((apr_uint64_t)(number), seperator, buffer); + + return apr_pstrdup(pool, buffer); +} + +unsigned int +svn_cstring__similarity(const char *stra, const char *strb, + svn_membuf_t *buffer, apr_size_t *rlcs) +{ + svn_string_t stringa, stringb; + stringa.data = stra; + stringa.len = strlen(stra); + stringb.data = strb; + stringb.len = strlen(strb); + return svn_string__similarity(&stringa, &stringb, buffer, rlcs); +} + +unsigned int +svn_string__similarity(const svn_string_t *stringa, + const svn_string_t *stringb, + svn_membuf_t *buffer, apr_size_t *rlcs) +{ + const char *stra = stringa->data; + const char *strb = stringb->data; + const apr_size_t lena = stringa->len; + const apr_size_t lenb = stringb->len; + const apr_size_t total = lena + lenb; + const char *enda = stra + lena; + const char *endb = strb + lenb; + apr_size_t lcs = 0; + + /* Skip the common prefix ... */ + while (stra < enda && strb < endb && *stra == *strb) + { + ++stra; ++strb; + ++lcs; + } + + /* ... and the common suffix */ + while (stra < enda && strb < endb) + { + --enda; --endb; + if (*enda != *endb) + { + ++enda; ++endb; + break; + } + + ++lcs; + } + + if (stra < enda && strb < endb) + { + const apr_size_t resta = enda - stra; + const apr_size_t restb = endb - strb; + const apr_size_t slots = (resta > restb ? restb : resta); + apr_size_t *curr, *prev; + const char *pstr; + + /* The outer loop must iterate on the longer string. */ + if (resta < restb) + { + pstr = stra; + stra = strb; + strb = pstr; + + pstr = enda; + enda = endb; + endb = pstr; + } + + /* Allocate two columns in the LCS matrix + ### Optimize this to (slots + 2) instesd of 2 * (slots + 1) */ + svn_membuf__ensure(buffer, 2 * (slots + 1) * sizeof(apr_size_t)); + svn_membuf__nzero(buffer, (slots + 2) * sizeof(apr_size_t)); + prev = buffer->data; + curr = prev + slots + 1; + + /* Calculate LCS length of the remainder */ + for (pstr = stra; pstr < enda; ++pstr) + { + int i; + for (i = 1; i <= slots; ++i) + { + if (*pstr == strb[i-1]) + curr[i] = prev[i-1] + 1; + else + curr[i] = (curr[i-1] > prev[i] ? curr[i-1] : prev[i]); + } + + /* Swap the buffers, making the previous one current */ + { + apr_size_t *const temp = prev; + prev = curr; + curr = temp; + } + } + + lcs += prev[slots]; + } + + if (rlcs) + *rlcs = lcs; + + /* Return similarity ratio rounded to 4 significant digits */ + if (total) + return(unsigned int)((2000 * lcs + total/2) / total); + else + return 1000; +} diff --git a/subversion/libsvn_subr/subst.c b/subversion/libsvn_subr/subst.c new file mode 100644 index 000000000000..f69dcf8f2bae --- /dev/null +++ b/subversion/libsvn_subr/subst.c @@ -0,0 +1,2025 @@ +/* + * subst.c : generic eol/keyword substitution routines + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include <stdlib.h> +#include <assert.h> +#include <apr_pools.h> +#include <apr_tables.h> +#include <apr_file_io.h> +#include <apr_strings.h> + +#include "svn_hash.h" +#include "svn_cmdline.h" +#include "svn_types.h" +#include "svn_string.h" +#include "svn_time.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_io.h" +#include "svn_subst.h" +#include "svn_pools.h" +#include "private/svn_io_private.h" + +#include "svn_private_config.h" + +#include "private/svn_string_private.h" + +/** + * The textual elements of a detranslated special file. One of these + * strings must appear as the first element of any special file as it + * exists in the repository or the text base. + */ +#define SVN_SUBST__SPECIAL_LINK_STR "link" + +void +svn_subst_eol_style_from_value(svn_subst_eol_style_t *style, + const char **eol, + const char *value) +{ + if (value == NULL) + { + /* property doesn't exist. */ + *eol = NULL; + if (style) + *style = svn_subst_eol_style_none; + } + else if (! strcmp("native", value)) + { + *eol = APR_EOL_STR; /* whee, a portability library! */ + if (style) + *style = svn_subst_eol_style_native; + } + else if (! strcmp("LF", value)) + { + *eol = "\n"; + if (style) + *style = svn_subst_eol_style_fixed; + } + else if (! strcmp("CR", value)) + { + *eol = "\r"; + if (style) + *style = svn_subst_eol_style_fixed; + } + else if (! strcmp("CRLF", value)) + { + *eol = "\r\n"; + if (style) + *style = svn_subst_eol_style_fixed; + } + else + { + *eol = NULL; + if (style) + *style = svn_subst_eol_style_unknown; + } +} + + +svn_boolean_t +svn_subst_translation_required(svn_subst_eol_style_t style, + const char *eol, + apr_hash_t *keywords, + svn_boolean_t special, + svn_boolean_t force_eol_check) +{ + return (special || keywords + || (style != svn_subst_eol_style_none && force_eol_check) + || (style == svn_subst_eol_style_native && + strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0) + || (style == svn_subst_eol_style_fixed && + strcmp(APR_EOL_STR, eol) != 0)); +} + + + +/* Helper function for svn_subst_build_keywords */ + +/* Given a printf-like format string, return a string with proper + * information filled in. + * + * Important API note: This function is the core of the implementation of + * svn_subst_build_keywords (all versions), and as such must implement the + * tolerance of NULL and zero inputs that that function's documention + * stipulates. + * + * The format codes: + * + * %a author of this revision + * %b basename of the URL of this file + * %d short format of date of this revision + * %D long format of date of this revision + * %P path relative to root of repos + * %r number of this revision + * %R root url of repository + * %u URL of this file + * %_ a space + * %% a literal % + * + * The following special format codes are also recognized: + * %H is equivalent to %P%_%r%_%d%_%a + * %I is equivalent to %b%_%r%_%d%_%a + * + * All memory is allocated out of @a pool. + */ +static svn_string_t * +keyword_printf(const char *fmt, + const char *rev, + const char *url, + const char *repos_root_url, + apr_time_t date, + const char *author, + apr_pool_t *pool) +{ + svn_stringbuf_t *value = svn_stringbuf_ncreate("", 0, pool); + const char *cur; + size_t n; + + for (;;) + { + cur = fmt; + + while (*cur != '\0' && *cur != '%') + cur++; + + if ((n = cur - fmt) > 0) /* Do we have an as-is string? */ + svn_stringbuf_appendbytes(value, fmt, n); + + if (*cur == '\0') + break; + + switch (cur[1]) + { + case 'a': /* author of this revision */ + if (author) + svn_stringbuf_appendcstr(value, author); + break; + case 'b': /* basename of this file */ + if (url && *url) + { + const char *base_name = svn_uri_basename(url, pool); + svn_stringbuf_appendcstr(value, base_name); + } + break; + case 'd': /* short format of date of this revision */ + if (date) + { + apr_time_exp_t exploded_time; + const char *human; + + apr_time_exp_gmt(&exploded_time, date); + + human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ", + exploded_time.tm_year + 1900, + exploded_time.tm_mon + 1, + exploded_time.tm_mday, + exploded_time.tm_hour, + exploded_time.tm_min, + exploded_time.tm_sec); + + svn_stringbuf_appendcstr(value, human); + } + break; + case 'D': /* long format of date of this revision */ + if (date) + svn_stringbuf_appendcstr(value, + svn_time_to_human_cstring(date, pool)); + break; + case 'P': /* relative path of this file */ + if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0') + { + const char *repos_relpath; + + repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool); + if (repos_relpath) + svn_stringbuf_appendcstr(value, repos_relpath); + } + break; + case 'R': /* root of repos */ + if (repos_root_url && *repos_root_url != '\0') + svn_stringbuf_appendcstr(value, repos_root_url); + break; + case 'r': /* number of this revision */ + if (rev) + svn_stringbuf_appendcstr(value, rev); + break; + case 'u': /* URL of this file */ + if (url) + svn_stringbuf_appendcstr(value, url); + break; + case '_': /* '%_' => a space */ + svn_stringbuf_appendbyte(value, ' '); + break; + case '%': /* '%%' => a literal % */ + svn_stringbuf_appendbyte(value, *cur); + break; + case '\0': /* '%' as the last character of the string. */ + svn_stringbuf_appendbyte(value, *cur); + /* Now go back one character, since this was just a one character + * sequence, whereas all others are two characters, and we do not + * want to skip the null terminator entirely and carry on + * formatting random memory contents. */ + cur--; + break; + case 'H': + { + svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url, + repos_root_url, date, author, + pool); + svn_stringbuf_appendcstr(value, s->data); + } + break; + case 'I': + { + svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url, + repos_root_url, date, author, + pool); + svn_stringbuf_appendcstr(value, s->data); + } + break; + default: /* Unrecognized code, just print it literally. */ + svn_stringbuf_appendbytes(value, cur, 2); + break; + } + + /* Format code is processed - skip it, and get ready for next chunk. */ + fmt = cur + 2; + } + + return svn_stringbuf__morph_into_string(value); +} + +static svn_error_t * +build_keywords(apr_hash_t **kw, + svn_boolean_t expand_custom_keywords, + const char *keywords_val, + const char *rev, + const char *url, + const char *repos_root_url, + apr_time_t date, + const char *author, + apr_pool_t *pool) +{ + apr_array_header_t *keyword_tokens; + int i; + *kw = apr_hash_make(pool); + + keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f", + TRUE /* chop */, pool); + + for (i = 0; i < keyword_tokens->nelts; ++i) + { + const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *); + const char *custom_fmt = NULL; + + if (expand_custom_keywords) + { + char *sep; + + /* Check if there is a custom keyword definition, started by '='. */ + sep = strchr(keyword, '='); + if (sep) + { + *sep = '\0'; /* Split keyword's name from custom format. */ + custom_fmt = sep + 1; + } + } + + if (custom_fmt) + { + svn_string_t *custom_val; + + /* Custom keywords must be allowed to match the name of an + * existing fixed keyword. This is for compatibility purposes, + * in case new fixed keywords are added to Subversion which + * happen to match a custom keyword defined somewhere. + * There is only one global namespace for keyword names. */ + custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url, + date, author, pool); + svn_hash_sets(*kw, keyword, custom_val); + } + else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG)) + || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM)) + || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT))) + { + svn_string_t *revision_val; + + revision_val = keyword_printf("%r", rev, url, repos_root_url, + date, author, pool); + svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val); + svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val); + svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val); + } + else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG)) + || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT))) + { + svn_string_t *date_val; + + date_val = keyword_printf("%D", rev, url, repos_root_url, date, + author, pool); + svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val); + svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val); + } + else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG)) + || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT))) + { + svn_string_t *author_val; + + author_val = keyword_printf("%a", rev, url, repos_root_url, date, + author, pool); + svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val); + svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val); + } + else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG)) + || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT))) + { + svn_string_t *url_val; + + url_val = keyword_printf("%u", rev, url, repos_root_url, date, + author, pool); + svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val); + svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val); + } + else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID))) + { + svn_string_t *id_val; + + id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url, + date, author, pool); + svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val); + } + else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER))) + { + svn_string_t *header_val; + + header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url, + date, author, pool); + svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_subst_build_keywords2(apr_hash_t **kw, + const char *keywords_val, + const char *rev, + const char *url, + apr_time_t date, + const char *author, + apr_pool_t *pool) +{ + return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url, + NULL, date, author, pool)); +} + + +svn_error_t * +svn_subst_build_keywords3(apr_hash_t **kw, + const char *keywords_val, + const char *rev, + const char *url, + const char *repos_root_url, + apr_time_t date, + const char *author, + apr_pool_t *pool) +{ + return svn_error_trace(build_keywords(kw, TRUE, keywords_val, + rev, url, repos_root_url, + date, author, pool)); +} + + +/*** Helpers for svn_subst_translate_stream2 ***/ + + +/* Write out LEN bytes of BUF into STREAM. */ +/* ### TODO: 'stream_write()' would be a better name for this. */ +static svn_error_t * +translate_write(svn_stream_t *stream, + const void *buf, + apr_size_t len) +{ + SVN_ERR(svn_stream_write(stream, buf, &len)); + /* (No need to check LEN, as a short write always produces an error.) */ + return SVN_NO_ERROR; +} + + +/* Perform the substitution of VALUE into keyword string BUF (with len + *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating + *LEN to the new size of the substituted result. Return TRUE if all + goes well, FALSE otherwise. If VALUE is NULL, keyword will be + contracted, else it will be expanded. */ +static svn_boolean_t +translate_keyword_subst(char *buf, + apr_size_t *len, + const char *keyword, + apr_size_t keyword_len, + const svn_string_t *value) +{ + char *buf_ptr; + + /* Make sure we gotz good stuffs. */ + assert(*len <= SVN_KEYWORD_MAX_LEN); + assert((buf[0] == '$') && (buf[*len - 1] == '$')); + + /* Need at least a keyword and two $'s. */ + if (*len < keyword_len + 2) + return FALSE; + + /* Need at least space for two $'s, two spaces and a colon, and that + leaves zero space for the value itself. */ + if (keyword_len > SVN_KEYWORD_MAX_LEN - 5) + return FALSE; + + /* The keyword needs to match what we're looking for. */ + if (strncmp(buf + 1, keyword, keyword_len)) + return FALSE; + + buf_ptr = buf + 1 + keyword_len; + + /* Check for fixed-length expansion. + * The format of fixed length keyword and its data is + * Unexpanded keyword: "$keyword:: $" + * Expanded keyword: "$keyword:: value $" + * Expanded kw with filling: "$keyword:: value $" + * Truncated keyword: "$keyword:: longval#$" + */ + if ((buf_ptr[0] == ':') /* first char after keyword is ':' */ + && (buf_ptr[1] == ':') /* second char after keyword is ':' */ + && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */ + && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */ + || (buf[*len - 2] == '#')) /* .. or has '#' for next to last + character */ + && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */ + { + /* This is fixed length keyword, so *len remains unchanged */ + apr_size_t max_value_len = *len - (6 + keyword_len); + + if (! value) + { + /* no value, so unexpand */ + buf_ptr += 2; + while (*buf_ptr != '$') + *(buf_ptr++) = ' '; + } + else + { + if (value->len <= max_value_len) + { /* replacement not as long as template, pad with spaces */ + strncpy(buf_ptr + 3, value->data, value->len); + buf_ptr += 3 + value->len; + while (*buf_ptr != '$') + *(buf_ptr++) = ' '; + } + else + { + /* replacement needs truncating */ + strncpy(buf_ptr + 3, value->data, max_value_len); + buf[*len - 2] = '#'; + buf[*len - 1] = '$'; + } + } + return TRUE; + } + + /* Check for unexpanded keyword. */ + else if (buf_ptr[0] == '$') /* "$keyword$" */ + { + /* unexpanded... */ + if (value) + { + /* ...so expand. */ + buf_ptr[0] = ':'; + buf_ptr[1] = ' '; + if (value->len) + { + apr_size_t vallen = value->len; + + /* "$keyword: value $" */ + if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len)) + vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len; + strncpy(buf_ptr + 2, value->data, vallen); + buf_ptr[2 + vallen] = ' '; + buf_ptr[2 + vallen + 1] = '$'; + *len = 5 + keyword_len + vallen; + } + else + { + /* "$keyword: $" */ + buf_ptr[2] = '$'; + *len = 4 + keyword_len; + } + } + else + { + /* ...but do nothing. */ + } + return TRUE; + } + + /* Check for expanded keyword. */ + else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */ + && (buf_ptr[0] == ':') /* first char after keyword is ':' */ + && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */ + && (buf[*len - 2] == ' ')) + || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */ + && (buf_ptr[0] == ':') /* first char after keyword is ':' */ + && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */ + { + /* expanded... */ + if (! value) + { + /* ...so unexpand. */ + buf_ptr[0] = '$'; + *len = 2 + keyword_len; + } + else + { + /* ...so re-expand. */ + buf_ptr[0] = ':'; + buf_ptr[1] = ' '; + if (value->len) + { + apr_size_t vallen = value->len; + + /* "$keyword: value $" */ + if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len)) + vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len; + strncpy(buf_ptr + 2, value->data, vallen); + buf_ptr[2 + vallen] = ' '; + buf_ptr[2 + vallen + 1] = '$'; + *len = 5 + keyword_len + vallen; + } + else + { + /* "$keyword: $" */ + buf_ptr[2] = '$'; + *len = 4 + keyword_len; + } + } + return TRUE; + } + + return FALSE; +} + +/* Parse BUF (whose length is LEN, and which starts and ends with '$'), + trying to match one of the keyword names in KEYWORDS. If such a + keyword is found, update *KEYWORD_NAME with the keyword name and + return TRUE. */ +static svn_boolean_t +match_keyword(char *buf, + apr_size_t len, + char *keyword_name, + apr_hash_t *keywords) +{ + apr_size_t i; + + /* Early return for ignored keywords */ + if (! keywords) + return FALSE; + + /* Extract the name of the keyword */ + for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++) + keyword_name[i] = buf[i + 1]; + keyword_name[i] = '\0'; + + return svn_hash_gets(keywords, keyword_name) != NULL; +} + +/* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN): + optionally perform the substitution in place, update *LEN with + the new length of the translated keyword string, and return TRUE. + If this buffer doesn't contain a known keyword pattern, leave BUF + and *LEN untouched and return FALSE. + + See the docstring for svn_subst_copy_and_translate for how the + EXPAND and KEYWORDS parameters work. + + NOTE: It is assumed that BUF has been allocated to be at least + SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less + than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions + which would result in a keyword string which is greater than + SVN_KEYWORD_MAX_LEN will have their values truncated in such a way + that the resultant keyword string is still valid (begins with + "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */ +static svn_boolean_t +translate_keyword(char *buf, + apr_size_t *len, + const char *keyword_name, + svn_boolean_t expand, + apr_hash_t *keywords) +{ + const svn_string_t *value; + + /* Make sure we gotz good stuffs. */ + assert(*len <= SVN_KEYWORD_MAX_LEN); + assert((buf[0] == '$') && (buf[*len - 1] == '$')); + + /* Early return for ignored keywords */ + if (! keywords) + return FALSE; + + value = svn_hash_gets(keywords, keyword_name); + + if (value) + { + return translate_keyword_subst(buf, len, + keyword_name, strlen(keyword_name), + expand ? value : NULL); + } + + return FALSE; +} + +/* A boolean expression that evaluates to true if the first STR_LEN characters + of the string STR are one of the end-of-line strings LF, CR, or CRLF; + to false otherwise. */ +#define STRING_IS_EOL(str, str_len) \ + (((str_len) == 2 && (str)[0] == '\r' && (str)[1] == '\n') || \ + ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r'))) + +/* A boolean expression that evaluates to true if the end-of-line string EOL1, + having length EOL1_LEN, and the end-of-line string EOL2, having length + EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the + set {"\n", "\r", "\r\n"}; to false otherwise. + + Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if + EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course + different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both + "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1. + We need only check the one character for equality to determine whether + EOL1 and EOL2 are different in that case. */ +#define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \ + (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2))) + + +/* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to + the newline string EOL_STR (of length EOL_STR_LEN), writing the + result (which is always EOL_STR) to the stream DST. + + This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n". + + Also check for consistency of the source newline strings across + multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache + of the first newline found. If the current newline is not the same + as SRC_FORMAT, look to the REPAIR parameter. If REPAIR is TRUE, + ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL + error. If *SRC_FORMAT_LEN is 0, assume we are examining the first + newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to + use for later consistency checks. + + If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the + newline string that was written (EOL_STR) is not the same as the newline + string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL + untouched. + + Note: all parameters are required even if REPAIR is TRUE. + ### We could require that REPAIR must not change across a sequence of + calls, and could then optimize by not using SRC_FORMAT at all if + REPAIR is TRUE. +*/ +static svn_error_t * +translate_newline(const char *eol_str, + apr_size_t eol_str_len, + char *src_format, + apr_size_t *src_format_len, + const char *newline_buf, + apr_size_t newline_len, + svn_stream_t *dst, + svn_boolean_t *translated_eol, + svn_boolean_t repair) +{ + SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len)); + + /* If we've seen a newline before, compare it with our cache to + check for consistency, else cache it for future comparisons. */ + if (*src_format_len) + { + /* Comparing with cache. If we are inconsistent and + we are NOT repairing the file, generate an error! */ + if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len, + newline_buf, newline_len)) + return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL); + } + else + { + /* This is our first line ending, so cache it before + handling it. */ + strncpy(src_format, newline_buf, newline_len); + *src_format_len = newline_len; + } + + /* Write the desired newline */ + SVN_ERR(translate_write(dst, eol_str, eol_str_len)); + + /* Report whether we translated it. Note: Not using DIFFERENT_EOL_STRINGS() + * because EOL_STR may not be a valid EOL sequence. */ + if (translated_eol != NULL && + (eol_str_len != newline_len || + memcmp(eol_str, newline_buf, eol_str_len) != 0)) + *translated_eol = TRUE; + + return SVN_NO_ERROR; +} + + + +/*** Public interfaces. ***/ + +svn_boolean_t +svn_subst_keywords_differ(const svn_subst_keywords_t *a, + const svn_subst_keywords_t *b, + svn_boolean_t compare_values) +{ + if (((a == NULL) && (b == NULL)) /* no A or B */ + /* no A, and B has no contents */ + || ((a == NULL) + && (b->revision == NULL) + && (b->date == NULL) + && (b->author == NULL) + && (b->url == NULL)) + /* no B, and A has no contents */ + || ((b == NULL) && (a->revision == NULL) + && (a->date == NULL) + && (a->author == NULL) + && (a->url == NULL)) + /* neither A nor B has any contents */ + || ((a != NULL) && (b != NULL) + && (b->revision == NULL) + && (b->date == NULL) + && (b->author == NULL) + && (b->url == NULL) + && (a->revision == NULL) + && (a->date == NULL) + && (a->author == NULL) + && (a->url == NULL))) + { + return FALSE; + } + else if ((a == NULL) || (b == NULL)) + return TRUE; + + /* Else both A and B have some keywords. */ + + if ((! a->revision) != (! b->revision)) + return TRUE; + else if ((compare_values && (a->revision != NULL)) + && (strcmp(a->revision->data, b->revision->data) != 0)) + return TRUE; + + if ((! a->date) != (! b->date)) + return TRUE; + else if ((compare_values && (a->date != NULL)) + && (strcmp(a->date->data, b->date->data) != 0)) + return TRUE; + + if ((! a->author) != (! b->author)) + return TRUE; + else if ((compare_values && (a->author != NULL)) + && (strcmp(a->author->data, b->author->data) != 0)) + return TRUE; + + if ((! a->url) != (! b->url)) + return TRUE; + else if ((compare_values && (a->url != NULL)) + && (strcmp(a->url->data, b->url->data) != 0)) + return TRUE; + + /* Else we never found a difference, so they must be the same. */ + + return FALSE; +} + +svn_boolean_t +svn_subst_keywords_differ2(apr_hash_t *a, + apr_hash_t *b, + svn_boolean_t compare_values, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + unsigned int a_count, b_count; + + /* An empty hash is logically equal to a NULL, + * as far as this API is concerned. */ + a_count = (a == NULL) ? 0 : apr_hash_count(a); + b_count = (b == NULL) ? 0 : apr_hash_count(b); + + if (a_count != b_count) + return TRUE; + + if (a_count == 0) + return FALSE; + + /* The hashes are both non-NULL, and have the same number of items. + * We must check that every item of A is present in B. */ + for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi)) + { + const void *key; + apr_ssize_t klen; + void *void_a_val; + svn_string_t *a_val, *b_val; + + apr_hash_this(hi, &key, &klen, &void_a_val); + a_val = void_a_val; + b_val = apr_hash_get(b, key, klen); + + if (!b_val || (compare_values && !svn_string_compare(a_val, b_val))) + return TRUE; + } + + return FALSE; +} + + +/* Baton for translate_chunk() to store its state in. */ +struct translation_baton +{ + const char *eol_str; + svn_boolean_t *translated_eol; + svn_boolean_t repair; + apr_hash_t *keywords; + svn_boolean_t expand; + + /* 'short boolean' array that encodes what character values + may trigger a translation action, hence are 'interesting' */ + char interesting[256]; + + /* Length of the string EOL_STR points to. */ + apr_size_t eol_str_len; + + /* Buffer to cache any newline state between translation chunks */ + char newline_buf[2]; + + /* Offset (within newline_buf) of the first *unused* character */ + apr_size_t newline_off; + + /* Buffer to cache keyword-parsing state between translation chunks */ + char keyword_buf[SVN_KEYWORD_MAX_LEN]; + + /* Offset (within keyword-buf) to the first *unused* character */ + apr_size_t keyword_off; + + /* EOL style used in the chunk-source */ + char src_format[2]; + + /* Length of the EOL style string found in the chunk-source, + or zero if none encountered yet */ + apr_size_t src_format_len; + + /* If this is svn_tristate_false, translate_newline() will be called + for every newline in the file */ + svn_tristate_t nl_translation_skippable; +}; + + +/* Allocate a baton for use with translate_chunk() in POOL and + * initialize it for the first iteration. + * + * The caller must assure that EOL_STR and KEYWORDS at least + * have the same life time as that of POOL. + */ +static struct translation_baton * +create_translation_baton(const char *eol_str, + svn_boolean_t *translated_eol, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + apr_pool_t *pool) +{ + struct translation_baton *b = apr_palloc(pool, sizeof(*b)); + + /* For efficiency, convert an empty set of keywords to NULL. */ + if (keywords && (apr_hash_count(keywords) == 0)) + keywords = NULL; + + b->eol_str = eol_str; + b->eol_str_len = eol_str ? strlen(eol_str) : 0; + b->translated_eol = translated_eol; + b->repair = repair; + b->keywords = keywords; + b->expand = expand; + b->newline_off = 0; + b->keyword_off = 0; + b->src_format_len = 0; + b->nl_translation_skippable = svn_tristate_unknown; + + /* Most characters don't start translation actions. + * Mark those that do depending on the parameters we got. */ + memset(b->interesting, FALSE, sizeof(b->interesting)); + if (keywords) + b->interesting['$'] = TRUE; + if (eol_str) + { + b->interesting['\r'] = TRUE; + b->interesting['\n'] = TRUE; + } + + return b; +} + +/* Return TRUE if the EOL starting at BUF matches the eol_str member of B. + * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like + * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is + * more efficient to handle that special case implicitly in the calling code + * by exiting the quick scan loop. + * The caller must ensure that buf[0] and buf[1] refer to valid memory + * locations. + */ +static APR_INLINE svn_boolean_t +eol_unchanged(struct translation_baton *b, + const char *buf) +{ + /* If the first byte doesn't match, the whole EOL won't. + * This does also handle the (certainly invalid) case that + * eol_str would be an empty string. + */ + if (buf[0] != b->eol_str[0]) + return FALSE; + + /* two-char EOLs must be a full match */ + if (b->eol_str_len == 2) + return buf[1] == b->eol_str[1]; + + /* The first char matches the required 1-byte EOL. + * But maybe, buf[] contains a 2-byte EOL? + * In that case, the second byte will be interesting + * and not be another EOL of its own. + */ + return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1]; +} + + +/* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN + * according to the settings and state stored in baton B. + * + * Write output to stream DST. + * + * To finish a series of chunk translations, flush all buffers by calling + * this routine with a NULL value for BUF. + * + * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if + * an end-of-line sequence was changed, otherwise leave it untouched. + * + * Use POOL for temporary allocations. + */ +static svn_error_t * +translate_chunk(svn_stream_t *dst, + struct translation_baton *b, + const char *buf, + apr_size_t buflen, + apr_pool_t *pool) +{ + const char *p; + apr_size_t len; + + if (buf) + { + /* precalculate some oft-used values */ + const char *end = buf + buflen; + const char *interesting = b->interesting; + apr_size_t next_sign_off = 0; + + /* At the beginning of this loop, assume that we might be in an + * interesting state, i.e. with data in the newline or keyword + * buffer. First try to get to the boring state so we can copy + * a run of boring characters; then try to get back to the + * interesting state by processing an interesting character, + * and repeat. */ + for (p = buf; p < end;) + { + /* Try to get to the boring state, if necessary. */ + if (b->newline_off) + { + if (*p == '\n') + b->newline_buf[b->newline_off++] = *p++; + + SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, + b->src_format, + &b->src_format_len, b->newline_buf, + b->newline_off, dst, b->translated_eol, + b->repair)); + + b->newline_off = 0; + } + else if (b->keyword_off && *p == '$') + { + svn_boolean_t keyword_matches; + char keyword_name[SVN_KEYWORD_MAX_LEN + 1]; + + /* If keyword is matched, but not correctly translated, try to + * look for the next ending '$'. */ + b->keyword_buf[b->keyword_off++] = *p++; + keyword_matches = match_keyword(b->keyword_buf, b->keyword_off, + keyword_name, b->keywords); + if (!keyword_matches) + { + /* reuse the ending '$' */ + p--; + b->keyword_off--; + } + + if (!keyword_matches || + translate_keyword(b->keyword_buf, &b->keyword_off, + keyword_name, b->expand, b->keywords) || + b->keyword_off >= SVN_KEYWORD_MAX_LEN) + { + /* write out non-matching text or translated keyword */ + SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); + + next_sign_off = 0; + b->keyword_off = 0; + } + else + { + if (next_sign_off == 0) + next_sign_off = b->keyword_off - 1; + + continue; + } + } + else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1 + || (b->keyword_off && (*p == '\r' || *p == '\n'))) + { + if (next_sign_off > 0) + { + /* rolling back, continue with next '$' in keyword_buf */ + p -= (b->keyword_off - next_sign_off); + b->keyword_off = next_sign_off; + next_sign_off = 0; + } + /* No closing '$' found; flush the keyword buffer. */ + SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); + + b->keyword_off = 0; + } + else if (b->keyword_off) + { + b->keyword_buf[b->keyword_off++] = *p++; + continue; + } + + /* translate_newline will modify the baton for src_format_len==0 + or may return an error if b->repair is FALSE. In all other + cases, we can skip the newline translation as long as source + EOL format and actual EOL format match. If there is a + mismatch, translate_newline will be called regardless of + nl_translation_skippable. + */ + if (b->nl_translation_skippable == svn_tristate_unknown && + b->src_format_len > 0) + { + /* test whether translate_newline may return an error */ + if (b->eol_str_len == b->src_format_len && + strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0) + b->nl_translation_skippable = svn_tristate_true; + else if (b->repair) + b->nl_translation_skippable = svn_tristate_true; + else + b->nl_translation_skippable = svn_tristate_false; + } + + /* We're in the boring state; look for interesting characters. + Offset len such that it will become 0 in the first iteration. + */ + len = 0 - b->eol_str_len; + + /* Look for the next EOL (or $) that actually needs translation. + Stop there or at EOF, whichever is encountered first. + */ + do + { + /* skip current EOL */ + len += b->eol_str_len; + + /* Check 4 bytes at once to allow for efficient pipelining + and to reduce loop condition overhead. */ + while ((p + len + 4) <= end) + { + if (interesting[(unsigned char)p[len]] + || interesting[(unsigned char)p[len+1]] + || interesting[(unsigned char)p[len+2]] + || interesting[(unsigned char)p[len+3]]) + break; + + len += 4; + } + + /* Found an interesting char or EOF in the next 4 bytes. + Find its exact position. */ + while ((p + len) < end && !interesting[(unsigned char)p[len]]) + ++len; + } + while (b->nl_translation_skippable == + svn_tristate_true && /* can potentially skip EOLs */ + p + len + 2 < end && /* not too close to EOF */ + eol_unchanged (b, p + len)); /* EOL format already ok */ + + while ((p + len) < end && !interesting[(unsigned char)p[len]]) + len++; + + if (len) + { + SVN_ERR(translate_write(dst, p, len)); + p += len; + } + + /* Set up state according to the interesting character, if any. */ + if (p < end) + { + switch (*p) + { + case '$': + b->keyword_buf[b->keyword_off++] = *p++; + break; + case '\r': + b->newline_buf[b->newline_off++] = *p++; + break; + case '\n': + b->newline_buf[b->newline_off++] = *p++; + + SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, + b->src_format, + &b->src_format_len, + b->newline_buf, + b->newline_off, dst, + b->translated_eol, b->repair)); + + b->newline_off = 0; + break; + + } + } + } + } + else + { + if (b->newline_off) + { + SVN_ERR(translate_newline(b->eol_str, b->eol_str_len, + b->src_format, &b->src_format_len, + b->newline_buf, b->newline_off, + dst, b->translated_eol, b->repair)); + b->newline_off = 0; + } + + if (b->keyword_off) + { + SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off)); + b->keyword_off = 0; + } + } + + return SVN_NO_ERROR; +} + +/* Baton for use with translated stream callbacks. */ +struct translated_stream_baton +{ + /* Stream to take input from (before translation) on read + /write output to (after translation) on write. */ + svn_stream_t *stream; + + /* Input/Output translation batons to make them separate chunk streams. */ + struct translation_baton *in_baton, *out_baton; + + /* Remembers whether any write operations have taken place; + if so, we need to flush the output chunk stream. */ + svn_boolean_t written; + + /* Buffer to hold translated read data. */ + svn_stringbuf_t *readbuf; + + /* Offset of the first non-read character in readbuf. */ + apr_size_t readbuf_off; + + /* Buffer to hold read data + between svn_stream_read() and translate_chunk(). */ + char *buf; +#define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1) + + /* Pool for callback iterations */ + apr_pool_t *iterpool; +}; + + +/* Implements svn_read_fn_t. */ +static svn_error_t * +translated_stream_read(void *baton, + char *buffer, + apr_size_t *len) +{ + struct translated_stream_baton *b = baton; + apr_size_t readlen = SVN__STREAM_CHUNK_SIZE; + apr_size_t unsatisfied = *len; + apr_size_t off = 0; + + /* Optimization for a frequent special case. The configuration parser (and + a few others) reads the stream one byte at a time. All the memcpy, pool + clearing etc. imposes a huge overhead in that case. In most cases, we + can just take that single byte directly from the read buffer. + + Since *len > 1 requires lots of code to be run anyways, we can afford + the extra overhead of checking for *len == 1. + + See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>. + */ + if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len) + { + /* Just take it from the read buffer */ + *buffer = b->readbuf->data[b->readbuf_off++]; + + return SVN_NO_ERROR; + } + + /* Standard code path. */ + while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0) + { + apr_size_t to_copy; + apr_size_t buffer_remainder; + + svn_pool_clear(b->iterpool); + /* fill read buffer, if necessary */ + if (! (b->readbuf_off < b->readbuf->len)) + { + svn_stream_t *buf_stream; + + svn_stringbuf_setempty(b->readbuf); + b->readbuf_off = 0; + SVN_ERR(svn_stream_read(b->stream, b->buf, &readlen)); + buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool); + + SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf, + readlen, b->iterpool)); + + if (readlen != SVN__STREAM_CHUNK_SIZE) + SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0, + b->iterpool)); + + SVN_ERR(svn_stream_close(buf_stream)); + } + + /* Satisfy from the read buffer */ + buffer_remainder = b->readbuf->len - b->readbuf_off; + to_copy = (buffer_remainder > unsatisfied) + ? unsatisfied : buffer_remainder; + memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy); + off += to_copy; + b->readbuf_off += to_copy; + unsatisfied -= to_copy; + } + + *len -= unsatisfied; + + return SVN_NO_ERROR; +} + +/* Implements svn_write_fn_t. */ +static svn_error_t * +translated_stream_write(void *baton, + const char *buffer, + apr_size_t *len) +{ + struct translated_stream_baton *b = baton; + svn_pool_clear(b->iterpool); + + b->written = TRUE; + return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool); +} + +/* Implements svn_close_fn_t. */ +static svn_error_t * +translated_stream_close(void *baton) +{ + struct translated_stream_baton *b = baton; + svn_error_t *err = NULL; + + if (b->written) + err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool); + + err = svn_error_compose_create(err, svn_stream_close(b->stream)); + + svn_pool_destroy(b->iterpool); + + return svn_error_trace(err); +} + + +/* svn_stream_mark_t for translation streams. */ +typedef struct mark_translated_t +{ + /* Saved translation state. */ + struct translated_stream_baton saved_baton; + + /* Mark set on the underlying stream. */ + svn_stream_mark_t *mark; +} mark_translated_t; + +/* Implements svn_stream_mark_fn_t. */ +static svn_error_t * +translated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) +{ + mark_translated_t *mt; + struct translated_stream_baton *b = baton; + + mt = apr_palloc(pool, sizeof(*mt)); + SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool)); + + /* Save translation state. */ + mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton, + sizeof(*mt->saved_baton.in_baton)); + mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton, + sizeof(*mt->saved_baton.out_baton)); + mt->saved_baton.written = b->written; + mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool); + mt->saved_baton.readbuf_off = b->readbuf_off; + mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE); + + *mark = (svn_stream_mark_t *)mt; + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_seek_fn_t. */ +static svn_error_t * +translated_stream_seek(void *baton, const svn_stream_mark_t *mark) +{ + struct translated_stream_baton *b = baton; + + if (mark != NULL) + { + const mark_translated_t *mt = (const mark_translated_t *)mark; + + /* Flush output buffer if necessary. */ + if (b->written) + SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0, + b->iterpool)); + + SVN_ERR(svn_stream_seek(b->stream, mt->mark)); + + /* Restore translation state, avoiding new allocations. */ + *b->in_baton = *mt->saved_baton.in_baton; + *b->out_baton = *mt->saved_baton.out_baton; + b->written = mt->saved_baton.written; + svn_stringbuf_setempty(b->readbuf); + svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data, + mt->saved_baton.readbuf->len); + b->readbuf_off = mt->saved_baton.readbuf_off; + memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE); + } + else + { + SVN_ERR(svn_stream_reset(b->stream)); + + b->in_baton->newline_off = 0; + b->in_baton->keyword_off = 0; + b->in_baton->src_format_len = 0; + b->out_baton->newline_off = 0; + b->out_baton->keyword_off = 0; + b->out_baton->src_format_len = 0; + + b->written = FALSE; + svn_stringbuf_setempty(b->readbuf); + b->readbuf_off = 0; + } + + return SVN_NO_ERROR; +} + +/* Implements svn_stream__is_buffered_fn_t. */ +static svn_boolean_t +translated_stream_is_buffered(void *baton) +{ + struct translated_stream_baton *b = baton; + return svn_stream__is_buffered(b->stream); +} + +svn_error_t * +svn_subst_read_specialfile(svn_stream_t **stream, + const char *path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_finfo_t finfo; + svn_string_t *buf; + + /* First determine what type of special file we are + detranslating. */ + SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, + scratch_pool)); + + switch (finfo.filetype) { + case APR_REG: + /* Nothing special to do here, just create stream from the original + file's contents. */ + SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool)); + break; + + case APR_LNK: + /* Determine the destination of the link. */ + SVN_ERR(svn_io_read_link(&buf, path, scratch_pool)); + *stream = svn_stream_from_string(svn_string_createf(result_pool, + "link %s", + buf->data), + result_pool); + break; + + default: + SVN_ERR_MALFUNCTION(); + } + + return SVN_NO_ERROR; +} + +/* Same as svn_subst_stream_translated(), except for the following. + * + * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream + * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed, + * otherwise leave it untouched. + */ +static svn_stream_t * +stream_translated(svn_stream_t *stream, + const char *eol_str, + svn_boolean_t *translated_eol, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + apr_pool_t *result_pool) +{ + struct translated_stream_baton *baton + = apr_palloc(result_pool, sizeof(*baton)); + svn_stream_t *s = svn_stream_create(baton, result_pool); + + /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL + so they have the same lifetime as the stream. */ + if (eol_str) + eol_str = apr_pstrdup(result_pool, eol_str); + if (keywords) + { + if (apr_hash_count(keywords) == 0) + keywords = NULL; + else + { + /* deep copy the hash to make sure it's allocated in RESULT_POOL */ + apr_hash_t *copy = apr_hash_make(result_pool); + apr_hash_index_t *hi; + apr_pool_t *subpool; + + subpool = svn_pool_create(result_pool); + for (hi = apr_hash_first(subpool, keywords); + hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + + apr_hash_this(hi, &key, NULL, &val); + svn_hash_sets(copy, apr_pstrdup(result_pool, key), + svn_string_dup(val, result_pool)); + } + svn_pool_destroy(subpool); + + keywords = copy; + } + } + + /* Setup the baton fields */ + baton->stream = stream; + baton->in_baton + = create_translation_baton(eol_str, translated_eol, repair, keywords, + expand, result_pool); + baton->out_baton + = create_translation_baton(eol_str, translated_eol, repair, keywords, + expand, result_pool); + baton->written = FALSE; + baton->readbuf = svn_stringbuf_create_empty(result_pool); + baton->readbuf_off = 0; + baton->iterpool = svn_pool_create(result_pool); + baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE); + + /* Setup the stream methods */ + svn_stream_set_read(s, translated_stream_read); + svn_stream_set_write(s, translated_stream_write); + svn_stream_set_close(s, translated_stream_close); + svn_stream_set_mark(s, translated_stream_mark); + svn_stream_set_seek(s, translated_stream_seek); + svn_stream__set_is_buffered(s, translated_stream_is_buffered); + + return s; +} + +svn_stream_t * +svn_subst_stream_translated(svn_stream_t *stream, + const char *eol_str, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + apr_pool_t *result_pool) +{ + return stream_translated(stream, eol_str, NULL, repair, keywords, expand, + result_pool); +} + +/* Same as svn_subst_translate_cstring2(), except for the following. + * + * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an + * end-of-line sequence was changed, or to FALSE otherwise. + */ +static svn_error_t * +translate_cstring(const char **dst, + svn_boolean_t *translated_eol, + const char *src, + const char *eol_str, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + apr_pool_t *pool) +{ + svn_stringbuf_t *dst_stringbuf; + svn_stream_t *dst_stream; + apr_size_t len = strlen(src); + + /* The easy way out: no translation needed, just copy. */ + if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0)))) + { + *dst = apr_pstrmemdup(pool, src, len); + return SVN_NO_ERROR; + } + + /* Create a stringbuf and wrapper stream to hold the output. */ + dst_stringbuf = svn_stringbuf_create_empty(pool); + dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool); + + if (translated_eol) + *translated_eol = FALSE; + + /* Another wrapper to translate the content. */ + dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair, + keywords, expand, pool); + + /* Jam the text into the destination stream (to translate it). */ + SVN_ERR(svn_stream_write(dst_stream, src, &len)); + + /* Close the destination stream to flush unwritten data. */ + SVN_ERR(svn_stream_close(dst_stream)); + + *dst = dst_stringbuf->data; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_subst_translate_cstring2(const char *src, + const char **dst, + const char *eol_str, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + apr_pool_t *pool) +{ + return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand, + pool); +} + +/* Given a special file at SRC, generate a textual representation of + it in a normal file at DST. Perform all allocations in POOL. */ +/* ### this should be folded into svn_subst_copy_and_translate3 */ +static svn_error_t * +detranslate_special_file(const char *src, const char *dst, + svn_cancel_func_t cancel_func, void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *dst_tmp; + svn_stream_t *src_stream; + svn_stream_t *dst_stream; + + /* Open a temporary destination that we will eventually atomically + rename into place. */ + SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, + svn_dirent_dirname(dst, scratch_pool), + svn_io_file_del_none, + scratch_pool, scratch_pool)); + SVN_ERR(svn_subst_read_specialfile(&src_stream, src, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_copy3(src_stream, dst_stream, + cancel_func, cancel_baton, scratch_pool)); + + /* Do the atomic rename from our temporary location. */ + return svn_error_trace(svn_io_file_rename(dst_tmp, dst, scratch_pool)); +} + +/* Creates a special file DST from the "normal form" located in SOURCE. + * + * All temporary allocations will be done in POOL. + */ +static svn_error_t * +create_special_file_from_stream(svn_stream_t *source, const char *dst, + apr_pool_t *pool) +{ + svn_stringbuf_t *contents; + svn_boolean_t eof; + const char *identifier; + const char *remainder; + const char *dst_tmp; + svn_boolean_t create_using_internal_representation = FALSE; + + SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool)); + + /* Separate off the identifier. The first space character delimits + the identifier, after which any remaining characters are specific + to the actual special file type being created. */ + identifier = contents->data; + for (remainder = identifier; *remainder; remainder++) + { + if (*remainder == ' ') + { + remainder++; + break; + } + } + + if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ", + sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1)) + { + /* For symlinks, the type specific data is just a filesystem + path that the symlink should reference. */ + svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder, + ".tmp", pool); + + /* If we had an error, check to see if it was because symlinks are + not supported on the platform. If so, fall back + to using the internal representation. */ + if (err) + { + if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE) + { + svn_error_clear(err); + create_using_internal_representation = TRUE; + } + else + return err; + } + } + else + { + /* Just create a normal file using the internal special file + representation. We don't want a commit of an unknown special + file type to DoS all the clients. */ + create_using_internal_representation = TRUE; + } + + /* If nothing else worked, write out the internal representation to + a file that can be edited by the user. + + ### this only writes the first line! + */ + if (create_using_internal_representation) + SVN_ERR(svn_io_write_unique(&dst_tmp, svn_dirent_dirname(dst, pool), + contents->data, contents->len, + svn_io_file_del_none, pool)); + + /* Do the atomic rename from our temporary location. */ + return svn_io_file_rename(dst_tmp, dst, pool); +} + + +svn_error_t * +svn_subst_copy_and_translate4(const char *src, + const char *dst, + const char *eol_str, + svn_boolean_t repair, + apr_hash_t *keywords, + svn_boolean_t expand, + svn_boolean_t special, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_stream_t *src_stream; + svn_stream_t *dst_stream; + const char *dst_tmp; + svn_error_t *err; + svn_node_kind_t kind; + svn_boolean_t path_special; + + SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool)); + + /* If this is a 'special' file, we may need to create it or + detranslate it. */ + if (special || path_special) + { + if (expand) + { + if (path_special) + { + /* We are being asked to create a special file from a special + file. Do a temporary detranslation and work from there. */ + + /* ### woah. this section just undoes all the work we already did + ### to read the contents of the special file. shoot... the + ### svn_subst_read_specialfile even checks the file type + ### for us! */ + + SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool)); + } + else + { + SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool)); + } + + return svn_error_trace(create_special_file_from_stream(src_stream, + dst, pool)); + } + /* else !expand */ + + return svn_error_trace(detranslate_special_file(src, dst, + cancel_func, + cancel_baton, + pool)); + } + + /* The easy way out: no translation needed, just copy. */ + if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0)))) + return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool)); + + /* Open source file. */ + SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool)); + + /* For atomicity, we translate to a tmp file and then rename the tmp file + over the real destination. */ + SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, + svn_dirent_dirname(dst, pool), + svn_io_file_del_none, pool, pool)); + + dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair, + keywords, expand, pool); + + /* ###: use cancel func/baton in place of NULL/NULL below. */ + err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton, + pool); + if (err) + { + /* On errors, we have a pathname available. */ + if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL) + err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err, + _("File '%s' has inconsistent newlines"), + svn_dirent_local_style(src, pool)); + return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, + FALSE, pool)); + } + + /* Now that dst_tmp contains the translated data, do the atomic rename. */ + SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool)); + + /* Preserve the source file's permission bits. */ + SVN_ERR(svn_io_copy_perms(src, dst, pool)); + + return SVN_NO_ERROR; +} + + +/*** 'Special file' stream support */ + +struct special_stream_baton +{ + svn_stream_t *read_stream; + svn_stringbuf_t *write_content; + svn_stream_t *write_stream; + const char *path; + apr_pool_t *pool; +}; + + +static svn_error_t * +read_handler_special(void *baton, char *buffer, apr_size_t *len) +{ + struct special_stream_baton *btn = baton; + + if (btn->read_stream) + /* We actually found a file to read from */ + return svn_stream_read(btn->read_stream, buffer, len); + else + return svn_error_createf(APR_ENOENT, NULL, + "Can't read special file: File '%s' not found", + svn_dirent_local_style(btn->path, btn->pool)); +} + +static svn_error_t * +write_handler_special(void *baton, const char *buffer, apr_size_t *len) +{ + struct special_stream_baton *btn = baton; + + return svn_stream_write(btn->write_stream, buffer, len); +} + + +static svn_error_t * +close_handler_special(void *baton) +{ + struct special_stream_baton *btn = baton; + + if (btn->write_content->len) + { + /* yeay! we received data and need to create a special file! */ + + svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content, + btn->pool); + SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_subst_create_specialfile(svn_stream_t **stream, + const char *path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton)); + + baton->path = apr_pstrdup(result_pool, path); + + /* SCRATCH_POOL may not exist after the function returns. */ + baton->pool = result_pool; + + baton->write_content = svn_stringbuf_create_empty(result_pool); + baton->write_stream = svn_stream_from_stringbuf(baton->write_content, + result_pool); + + *stream = svn_stream_create(baton, result_pool); + svn_stream_set_write(*stream, write_handler_special); + svn_stream_set_close(*stream, close_handler_special); + + return SVN_NO_ERROR; +} + + +/* NOTE: this function is deprecated, but we cannot move it over to + deprecated.c because it uses stuff private to this file, and it is + not easily rebuilt in terms of "new" functions. */ +svn_error_t * +svn_subst_stream_from_specialfile(svn_stream_t **stream, + const char *path, + apr_pool_t *pool) +{ + struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton)); + svn_error_t *err; + + baton->pool = pool; + baton->path = apr_pstrdup(pool, path); + + err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool); + + /* File might not exist because we intend to create it upon close. */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + + /* Note: the special file is missing. the caller won't find out + until the first read. Oh well. This function is deprecated anyways, + so they can just deal with the weird behavior. */ + baton->read_stream = NULL; + } + + baton->write_content = svn_stringbuf_create_empty(pool); + baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool); + + *stream = svn_stream_create(baton, pool); + svn_stream_set_read(*stream, read_handler_special); + svn_stream_set_write(*stream, write_handler_special); + svn_stream_set_close(*stream, close_handler_special); + + return SVN_NO_ERROR; +} + + + +/*** String translation */ +svn_error_t * +svn_subst_translate_string2(svn_string_t **new_value, + svn_boolean_t *translated_to_utf8, + svn_boolean_t *translated_line_endings, + const svn_string_t *value, + const char *encoding, + svn_boolean_t repair, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *val_utf8; + const char *val_utf8_lf; + + if (value == NULL) + { + *new_value = NULL; + return SVN_NO_ERROR; + } + + if (encoding) + { + SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data, + encoding, scratch_pool)); + } + else + { + SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool)); + } + + if (translated_to_utf8) + *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0); + + SVN_ERR(translate_cstring(&val_utf8_lf, + translated_line_endings, + val_utf8, + "\n", /* translate to LF */ + repair, + NULL, /* no keywords */ + FALSE, /* no expansion */ + scratch_pool)); + + *new_value = svn_string_create(val_utf8_lf, result_pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_subst_detranslate_string(svn_string_t **new_value, + const svn_string_t *value, + svn_boolean_t for_output, + apr_pool_t *pool) +{ + svn_error_t *err; + const char *val_neol; + const char *val_nlocale_neol; + + if (value == NULL) + { + *new_value = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_subst_translate_cstring2(value->data, + &val_neol, + APR_EOL_STR, /* 'native' eol */ + FALSE, /* no repair */ + NULL, /* no keywords */ + FALSE, /* no expansion */ + pool)); + + if (for_output) + { + err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool); + if (err && (APR_STATUS_IS_EINVAL(err->apr_err))) + { + val_nlocale_neol = + svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool); + svn_error_clear(err); + } + else if (err) + return err; + } + else + { + err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool); + if (err && (APR_STATUS_IS_EINVAL(err->apr_err))) + { + val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool); + svn_error_clear(err); + } + else if (err) + return err; + } + + *new_value = svn_string_create(val_nlocale_neol, pool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/sysinfo.c b/subversion/libsvn_subr/sysinfo.c new file mode 100644 index 000000000000..455dca44cb6d --- /dev/null +++ b/subversion/libsvn_subr/sysinfo.c @@ -0,0 +1,1132 @@ +/* + * sysinfo.c : information about the running system + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#define PSAPI_VERSION 1 +#include <windows.h> +#include <psapi.h> +#include <Ws2tcpip.h> +#endif + +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include <apr_lib.h> +#include <apr_pools.h> +#include <apr_file_info.h> +#include <apr_signal.h> +#include <apr_strings.h> +#include <apr_thread_proc.h> +#include <apr_version.h> +#include <apu_version.h> + +#include "svn_pools.h" +#include "svn_ctype.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_string.h" +#include "svn_utf.h" +#include "svn_version.h" + +#include "private/svn_sqlite.h" + +#include "sysinfo.h" +#include "svn_private_config.h" + +#if HAVE_SYS_UTSNAME_H +#include <sys/utsname.h> +#endif + +#ifdef SVN_HAVE_MACOS_PLIST +#include <CoreFoundation/CoreFoundation.h> +#endif + +#ifdef SVN_HAVE_MACHO_ITERATE +#include <mach-o/dyld.h> +#include <mach-o/loader.h> +#endif + +#if HAVE_UNAME +static const char *canonical_host_from_uname(apr_pool_t *pool); +# ifndef SVN_HAVE_MACOS_PLIST +static const char *release_name_from_uname(apr_pool_t *pool); +# endif +#endif + +#ifdef WIN32 +static const char *win32_canonical_host(apr_pool_t *pool); +static const char *win32_release_name(apr_pool_t *pool); +static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool); +#endif /* WIN32 */ + +#ifdef SVN_HAVE_MACOS_PLIST +static const char *macos_release_name(apr_pool_t *pool); +#endif + +#ifdef SVN_HAVE_MACHO_ITERATE +static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool); +#endif + + +#if __linux__ +static const char *linux_release_name(apr_pool_t *pool); +#endif + +const char * +svn_sysinfo__canonical_host(apr_pool_t *pool) +{ +#ifdef WIN32 + return win32_canonical_host(pool); +#elif HAVE_UNAME + return canonical_host_from_uname(pool); +#else + return "unknown-unknown-unknown"; +#endif +} + + +const char * +svn_sysinfo__release_name(apr_pool_t *pool) +{ +#ifdef WIN32 + return win32_release_name(pool); +#elif defined(SVN_HAVE_MACOS_PLIST) + return macos_release_name(pool); +#elif __linux__ + return linux_release_name(pool); +#elif HAVE_UNAME + return release_name_from_uname(pool); +#else + return NULL; +#endif +} + +const apr_array_header_t * +svn_sysinfo__linked_libs(apr_pool_t *pool) +{ + svn_version_ext_linked_lib_t *lib; + apr_array_header_t *array = apr_array_make(pool, 3, sizeof(*lib)); + + lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t); + lib->name = "APR"; + lib->compiled_version = APR_VERSION_STRING; + lib->runtime_version = apr_pstrdup(pool, apr_version_string()); + +/* Don't list APR-Util if it isn't linked in, which it may not be if + * we're using APR 2.x+ which combined APR-Util into APR. */ +#ifdef APU_VERSION_STRING + lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t); + lib->name = "APR-Util"; + lib->compiled_version = APU_VERSION_STRING; + lib->runtime_version = apr_pstrdup(pool, apu_version_string()); +#endif + + lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t); + lib->name = "SQLite"; + lib->compiled_version = apr_pstrdup(pool, svn_sqlite__compiled_version()); +#ifdef SVN_SQLITE_INLINE + lib->runtime_version = NULL; +#else + lib->runtime_version = apr_pstrdup(pool, svn_sqlite__runtime_version()); +#endif + + return array; +} + +const apr_array_header_t * +svn_sysinfo__loaded_libs(apr_pool_t *pool) +{ +#ifdef WIN32 + return win32_shared_libs(pool); +#elif defined(SVN_HAVE_MACHO_ITERATE) + return macos_shared_libs(pool); +#else + return NULL; +#endif +} + + +#if HAVE_UNAME +static const char* +canonical_host_from_uname(apr_pool_t *pool) +{ + const char *machine = "unknown"; + const char *vendor = "unknown"; + const char *sysname = "unknown"; + const char *sysver = ""; + struct utsname info; + + if (0 <= uname(&info)) + { + svn_error_t *err; + const char *tmp; + + err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool); + if (err) + svn_error_clear(err); + else + machine = tmp; + + err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool); + if (err) + svn_error_clear(err); + else + { + char *lwr = apr_pstrdup(pool, tmp); + char *it = lwr; + while (*it) + { + if (svn_ctype_isupper(*it)) + *it = apr_tolower(*it); + ++it; + } + sysname = lwr; + } + + if (0 == strcmp(sysname, "darwin")) + vendor = "apple"; + if (0 == strcmp(sysname, "linux")) + sysver = "-gnu"; + else + { + err = svn_utf_cstring_to_utf8(&tmp, info.release, pool); + if (err) + svn_error_clear(err); + else + { + apr_size_t n = strspn(tmp, ".0123456789"); + if (n > 0) + { + char *ver = apr_pstrdup(pool, tmp); + ver[n] = 0; + sysver = ver; + } + else + sysver = tmp; + } + } + } + + return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver); +} + +# ifndef SVN_HAVE_MACOS_PLIST +/* Generate a release name from the uname(3) info, effectively + returning "`uname -s` `uname -r`". */ +static const char * +release_name_from_uname(apr_pool_t *pool) +{ + struct utsname info; + if (0 <= uname(&info)) + { + svn_error_t *err; + const char *sysname; + const char *sysver; + + err = svn_utf_cstring_to_utf8(&sysname, info.sysname, pool); + if (err) + { + sysname = NULL; + svn_error_clear(err); + } + + + err = svn_utf_cstring_to_utf8(&sysver, info.release, pool); + if (err) + { + sysver = NULL; + svn_error_clear(err); + } + + if (sysname || sysver) + { + return apr_psprintf(pool, "%s%s%s", + (sysname ? sysname : ""), + (sysver ? (sysname ? " " : "") : ""), + (sysver ? sysver : "")); + } + } + return NULL; +} +# endif /* !SVN_HAVE_MACOS_PLIST */ +#endif /* HAVE_UNAME */ + + +#if __linux__ +/* Split a stringbuf into a key/value pair. + Return the key, leaving the striped value in the stringbuf. */ +static const char * +stringbuf_split_key(svn_stringbuf_t *buffer, char delim) +{ + char *key; + char *end; + + end = strchr(buffer->data, delim); + if (!end) + return NULL; + + svn_stringbuf_strip_whitespace(buffer); + key = buffer->data; + end = strchr(key, delim); + *end = '\0'; + buffer->len = 1 + end - key; + buffer->data = end + 1; + svn_stringbuf_strip_whitespace(buffer); + + return key; +} + +/* Parse `/usr/bin/lsb_rlease --all` */ +static const char * +lsb_release(apr_pool_t *pool) +{ + static const char *const args[3] = + { + "/usr/bin/lsb_release", + "--all", + NULL + }; + + const char *distributor = NULL; + const char *description = NULL; + const char *release = NULL; + const char *codename = NULL; + + apr_proc_t lsbproc; + svn_stream_t *lsbinfo; + svn_error_t *err; + + /* Run /usr/bin/lsb_release --all < /dev/null 2>/dev/null */ + { + apr_file_t *stdin_handle; + apr_file_t *stdout_handle; + + err = svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME, + APR_READ, APR_OS_DEFAULT, pool); + if (!err) + err = svn_io_file_open(&stdout_handle, SVN_NULL_DEVICE_NAME, + APR_WRITE, APR_OS_DEFAULT, pool); + if (!err) + err = svn_io_start_cmd3(&lsbproc, NULL, args[0], args, NULL, FALSE, + FALSE, stdin_handle, + TRUE, NULL, + FALSE, stdout_handle, + pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + } + + /* Parse the output and try to populate the */ + lsbinfo = svn_stream_from_aprfile2(lsbproc.out, TRUE, pool); + if (lsbinfo) + { + for (;;) + { + svn_boolean_t eof = FALSE; + svn_stringbuf_t *line; + const char *key; + + err = svn_stream_readline(lsbinfo, &line, "\n", &eof, pool); + if (err || eof) + break; + + key = stringbuf_split_key(line, ':'); + if (!key) + continue; + + if (0 == svn_cstring_casecmp(key, "Distributor ID")) + distributor = line->data; + else if (0 == svn_cstring_casecmp(key, "Description")) + description = line->data; + else if (0 == svn_cstring_casecmp(key, "Release")) + release = line->data; + else if (0 == svn_cstring_casecmp(key, "Codename")) + codename = line->data; + } + err = svn_error_compose_create(err, + svn_stream_close(lsbinfo)); + if (err) + { + svn_error_clear(err); + apr_proc_kill(&lsbproc, SIGKILL); + return NULL; + } + } + + /* Reap the child process */ + err = svn_io_wait_for_cmd(&lsbproc, "", NULL, NULL, pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + + if (description) + return apr_psprintf(pool, "%s%s%s%s", description, + (codename ? " (" : ""), + (codename ? codename : ""), + (codename ? ")" : "")); + if (distributor) + return apr_psprintf(pool, "%s%s%s%s%s%s", distributor, + (release ? " " : ""), + (release ? release : ""), + (codename ? " (" : ""), + (codename ? codename : ""), + (codename ? ")" : "")); + + return NULL; +} + +/* Read the whole contents of a file. */ +static svn_stringbuf_t * +read_file_contents(const char *filename, apr_pool_t *pool) +{ + svn_error_t *err; + svn_stringbuf_t *buffer; + + err = svn_stringbuf_from_file2(&buffer, filename, pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + + return buffer; +} + +/* Strip everything but the first line from a stringbuf. */ +static void +stringbuf_first_line_only(svn_stringbuf_t *buffer) +{ + char *eol = strchr(buffer->data, '\n'); + if (eol) + { + *eol = '\0'; + buffer->len = 1 + eol - buffer->data; + } + svn_stringbuf_strip_whitespace(buffer); +} + +/* Look at /etc/redhat_release to detect RHEL/Fedora/CentOS. */ +static const char * +redhat_release(apr_pool_t *pool) +{ + svn_stringbuf_t *buffer = read_file_contents("/etc/redhat-release", pool); + if (buffer) + { + stringbuf_first_line_only(buffer); + return buffer->data; + } + return NULL; +} + +/* Look at /etc/SuSE-release to detect non-LSB SuSE. */ +static const char * +suse_release(apr_pool_t *pool) +{ + const char *release = NULL; + const char *codename = NULL; + + svn_stringbuf_t *buffer = read_file_contents("/etc/SuSE-release", pool); + svn_stringbuf_t *line; + svn_stream_t *stream; + svn_boolean_t eof; + svn_error_t *err; + if (!buffer) + return NULL; + + stream = svn_stream_from_stringbuf(buffer, pool); + err = svn_stream_readline(stream, &line, "\n", &eof, pool); + if (err || eof) + { + svn_error_clear(err); + return NULL; + } + + svn_stringbuf_strip_whitespace(line); + release = line->data; + + for (;;) + { + const char *key; + + err = svn_stream_readline(stream, &line, "\n", &eof, pool); + if (err || eof) + { + svn_error_clear(err); + break; + } + + key = stringbuf_split_key(line, '='); + if (!key) + continue; + + if (0 == strncmp(key, "CODENAME", 8)) + codename = line->data; + } + + return apr_psprintf(pool, "%s%s%s%s", + release, + (codename ? " (" : ""), + (codename ? codename : ""), + (codename ? ")" : "")); +} + +/* Look at /etc/debian_version to detect non-LSB Debian. */ +static const char * +debian_release(apr_pool_t *pool) +{ + svn_stringbuf_t *buffer = read_file_contents("/etc/debian_version", pool); + if (!buffer) + return NULL; + + stringbuf_first_line_only(buffer); + return apr_pstrcat(pool, "Debian ", buffer->data, NULL); +} + +/* Try to find the Linux distribution name, or return info from uname. */ +static const char * +linux_release_name(apr_pool_t *pool) +{ + const char *uname_release = release_name_from_uname(pool); + + /* Try anything that has /usr/bin/lsb_release. + Covers, for example, Debian, Ubuntu and SuSE. */ + const char *release_name = lsb_release(pool); + + /* Try RHEL/Fedora/CentOS */ + if (!release_name) + release_name = redhat_release(pool); + + /* Try Non-LSB SuSE */ + if (!release_name) + release_name = suse_release(pool); + + /* Try non-LSB Debian */ + if (!release_name) + release_name = debian_release(pool); + + if (!release_name) + return uname_release; + + if (!uname_release) + return release_name; + + return apr_psprintf(pool, "%s [%s]", release_name, uname_release); +} +#endif /* __linux__ */ + + +#ifdef WIN32 +typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO); +typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE, DWORD, LPDWORD); + +/* Get system and version info, and try to tell the difference + between the native system type and the runtime environment of the + current process. Populate results in SYSINFO, LOCAL_SYSINFO + (optional) and OSINFO. */ +static BOOL +system_info(SYSTEM_INFO *sysinfo, + SYSTEM_INFO *local_sysinfo, + OSVERSIONINFOEXW *osinfo) +{ + FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO) + GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetNativeSystemInfo"); + + ZeroMemory(sysinfo, sizeof *sysinfo); + if (local_sysinfo) + { + ZeroMemory(local_sysinfo, sizeof *local_sysinfo); + GetSystemInfo(local_sysinfo); + if (GetNativeSystemInfo_) + GetNativeSystemInfo_(sysinfo); + else + memcpy(sysinfo, local_sysinfo, sizeof *sysinfo); + } + else + GetSystemInfo(sysinfo); + + ZeroMemory(osinfo, sizeof *osinfo); + osinfo->dwOSVersionInfoSize = sizeof *osinfo; + if (!GetVersionExW((LPVOID)osinfo)) + return FALSE; + + return TRUE; +} + +/* Map the proccessor type from SYSINFO to a string. */ +static const char * +processor_name(SYSTEM_INFO *sysinfo) +{ + switch (sysinfo->wProcessorArchitecture) + { + case PROCESSOR_ARCHITECTURE_AMD64: return "x86_64"; + case PROCESSOR_ARCHITECTURE_IA64: return "ia64"; + case PROCESSOR_ARCHITECTURE_INTEL: return "x86"; + case PROCESSOR_ARCHITECTURE_MIPS: return "mips"; + case PROCESSOR_ARCHITECTURE_ALPHA: return "alpha32"; + case PROCESSOR_ARCHITECTURE_PPC: return "powerpc"; + case PROCESSOR_ARCHITECTURE_SHX: return "shx"; + case PROCESSOR_ARCHITECTURE_ARM: return "arm"; + case PROCESSOR_ARCHITECTURE_ALPHA64: return "alpha"; + case PROCESSOR_ARCHITECTURE_MSIL: return "msil"; + case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64"; + default: return "unknown"; + } +} + +/* Return the Windows-specific canonical host name. */ +static const char * +win32_canonical_host(apr_pool_t *pool) +{ + SYSTEM_INFO sysinfo; + SYSTEM_INFO local_sysinfo; + OSVERSIONINFOEXW osinfo; + + if (system_info(&sysinfo, &local_sysinfo, &osinfo)) + { + const char *arch = processor_name(&local_sysinfo); + const char *machine = processor_name(&sysinfo); + const char *vendor = "microsoft"; + const char *sysname = "windows"; + const char *sysver = apr_psprintf(pool, "%u.%u.%u", + (unsigned int)osinfo.dwMajorVersion, + (unsigned int)osinfo.dwMinorVersion, + (unsigned int)osinfo.dwBuildNumber); + + if (sysinfo.wProcessorArchitecture + == local_sysinfo.wProcessorArchitecture) + return apr_psprintf(pool, "%s-%s-%s%s", + machine, vendor, sysname, sysver); + return apr_psprintf(pool, "%s/%s-%s-%s%s", + arch, machine, vendor, sysname, sysver); + } + + return "unknown-microsoft-windows"; +} + +/* Convert a Unicode string to UTF-8. */ +static char * +wcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool) +{ + const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1, + NULL, 0, NULL, NULL); + if (bufsize > 0) + { + char *const utf8 = apr_palloc(pool, bufsize + 1); + WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL); + return utf8; + } + return NULL; +} + +/* Query the value called NAME of the registry key HKEY. */ +static char * +registry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool) +{ + DWORD size; + wchar_t *value; + + if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size)) + return NULL; + + value = apr_palloc(pool, size + sizeof *value); + if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size)) + return NULL; + value[size / sizeof *value] = 0; + return wcs_to_utf8(value, pool); +} + +/* Try to glean the Windows release name and associated info from the + registry. Failing that, construct a release name from the version + info. */ +static const char * +win32_release_name(apr_pool_t *pool) +{ + SYSTEM_INFO sysinfo; + OSVERSIONINFOEXW osinfo; + HKEY hkcv; + + if (!system_info(&sysinfo, NULL, &osinfo)) + return NULL; + + if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + 0, KEY_QUERY_VALUE, &hkcv)) + { + const char *release = registry_value(hkcv, L"ProductName", pool); + const char *spack = registry_value(hkcv, L"CSDVersion", pool); + const char *curver = registry_value(hkcv, L"CurrentVersion", pool); + const char *curtype = registry_value(hkcv, L"CurrentType", pool); + const char *install = registry_value(hkcv, L"InstallationType", pool); + const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool); + + if (!spack && *osinfo.szCSDVersion) + spack = wcs_to_utf8(osinfo.szCSDVersion, pool); + + if (!curbuild) + curbuild = registry_value(hkcv, L"CurrentBuild", pool); + + if (release || spack || curver || curtype || curbuild) + { + const char *bootinfo = ""; + if (curver || install || curtype) + { + bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]", + (curver ? curver : ""), + (install ? (curver ? " " : "") : ""), + (install ? install : ""), + (curtype + ? (curver||install ? " " : "") + : ""), + (curtype ? curtype : "")); + } + + return apr_psprintf(pool, "%s%s%s%s%s%s%s", + (release ? release : ""), + (spack ? (release ? ", " : "") : ""), + (spack ? spack : ""), + (curbuild + ? (release||spack ? ", build " : "build ") + : ""), + (curbuild ? curbuild : ""), + (bootinfo + ? (release||spack||curbuild ? " " : "") + : ""), + (bootinfo ? bootinfo : "")); + } + } + + if (*osinfo.szCSDVersion) + { + const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool); + + if (servicepack) + return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u", + (unsigned int)osinfo.dwMajorVersion, + (unsigned int)osinfo.dwMinorVersion, + servicepack, + (unsigned int)osinfo.dwBuildNumber); + + /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */ + if (osinfo.wServicePackMinor) + return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u", + (unsigned int)osinfo.dwMajorVersion, + (unsigned int)osinfo.dwMinorVersion, + (unsigned int)osinfo.wServicePackMajor, + (unsigned int)osinfo.wServicePackMinor, + (unsigned int)osinfo.dwBuildNumber); + + return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u", + (unsigned int)osinfo.dwMajorVersion, + (unsigned int)osinfo.dwMinorVersion, + (unsigned int)osinfo.wServicePackMajor, + (unsigned int)osinfo.dwBuildNumber); + } + + return apr_psprintf(pool, "Windows NT %u.%u, build %u", + (unsigned int)osinfo.dwMajorVersion, + (unsigned int)osinfo.dwMinorVersion, + (unsigned int)osinfo.dwBuildNumber); +} + + +/* Get a list of handles of shared libs loaded by the current + process. Returns a NULL-terminated array alocated from POOL. */ +static HMODULE * +enum_loaded_modules(apr_pool_t *pool) +{ + HANDLE current = GetCurrentProcess(); + HMODULE dummy[1]; + HMODULE *handles; + DWORD size; + + if (!EnumProcessModules(current, dummy, sizeof(dummy), &size)) + return NULL; + + handles = apr_palloc(pool, size + sizeof *handles); + if (!EnumProcessModules(current, handles, size, &size)) + return NULL; + handles[size / sizeof *handles] = NULL; + return handles; +} + +/* Find the version number, if any, embedded in FILENAME. */ +static const char * +file_version_number(const wchar_t *filename, apr_pool_t *pool) +{ + VS_FIXEDFILEINFO info; + unsigned int major, minor, micro, nano; + void *data; + DWORD data_size = GetFileVersionInfoSizeW(filename, NULL); + void *vinfo; + UINT vinfo_size; + + if (!data_size) + return NULL; + + data = apr_palloc(pool, data_size); + if (!GetFileVersionInfoW(filename, 0, data_size, data)) + return NULL; + + if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size)) + return NULL; + + if (vinfo_size != sizeof info) + return NULL; + + memcpy(&info, vinfo, sizeof info); + major = (info.dwFileVersionMS >> 16) & 0xFFFF; + minor = info.dwFileVersionMS & 0xFFFF; + micro = (info.dwFileVersionLS >> 16) & 0xFFFF; + nano = info.dwFileVersionLS & 0xFFFF; + + if (!nano) + { + if (!micro) + return apr_psprintf(pool, "%u.%u", major, minor); + else + return apr_psprintf(pool, "%u.%u.%u", major, minor, micro); + } + return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano); +} + +/* List the shared libraries loaded by the current process. */ +static const apr_array_header_t * +win32_shared_libs(apr_pool_t *pool) +{ + apr_array_header_t *array = NULL; + wchar_t buffer[MAX_PATH + 1]; + HMODULE *handles = enum_loaded_modules(pool); + HMODULE *module; + + for (module = handles; module && *module; ++module) + { + const char *filename; + const char *version; + if (GetModuleFileNameW(*module, buffer, MAX_PATH)) + { + buffer[MAX_PATH] = 0; + + version = file_version_number(buffer, pool); + filename = wcs_to_utf8(buffer, pool); + if (filename) + { + svn_version_ext_loaded_lib_t *lib; + + if (!array) + { + array = apr_array_make(pool, 32, sizeof(*lib)); + } + lib = &APR_ARRAY_PUSH(array, svn_version_ext_loaded_lib_t); + lib->name = svn_dirent_local_style(filename, pool); + lib->version = version; + } + } + } + + return array; +} +#endif /* WIN32 */ + + +#ifdef SVN_HAVE_MACOS_PLIST +/* Load the SystemVersion.plist or ServerVersion.plist file into a + property list. Set SERVER to TRUE if the file read was + ServerVersion.plist. */ +static CFDictionaryRef +system_version_plist(svn_boolean_t *server, apr_pool_t *pool) +{ + static const UInt8 server_version[] = + "/System/Library/CoreServices/ServerVersion.plist"; + static const UInt8 system_version[] = + "/System/Library/CoreServices/SystemVersion.plist"; + + CFPropertyListRef plist = NULL; + CFDataRef resource = NULL; + CFStringRef errstr = NULL; + CFURLRef url = NULL; + SInt32 errcode; + + url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + server_version, + sizeof(server_version) - 1, + FALSE); + if (!url) + return NULL; + + if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, + url, &resource, + NULL, NULL, &errcode)) + { + CFRelease(url); + url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + system_version, + sizeof(system_version) - 1, + FALSE); + if (!url) + return NULL; + + if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, + url, &resource, + NULL, NULL, &errcode)) + { + CFRelease(url); + return NULL; + } + else + { + CFRelease(url); + *server = FALSE; + } + } + else + { + CFRelease(url); + *server = TRUE; + } + + /* ### CFPropertyListCreateFromXMLData is obsolete, but its + replacement CFPropertyListCreateWithData is only available + from Mac OS 1.6 onward. */ + plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource, + kCFPropertyListImmutable, + &errstr); + if (resource) + CFRelease(resource); + if (errstr) + CFRelease(errstr); + + if (CFDictionaryGetTypeID() != CFGetTypeID(plist)) + { + /* Oops ... this really should be a dict. */ + CFRelease(plist); + return NULL; + } + + return plist; +} + +/* Return the value for KEY from PLIST, or NULL if not available. */ +static const char * +value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool) +{ + CFStringRef valref; + CFIndex bufsize; + const void *valptr; + const char *value; + + if (!CFDictionaryGetValueIfPresent(plist, key, &valptr)) + return NULL; + + valref = valptr; + if (CFStringGetTypeID() != CFGetTypeID(valref)) + return NULL; + + value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8); + if (value) + return apr_pstrdup(pool, value); + + bufsize = 5 * CFStringGetLength(valref) + 1; + value = apr_palloc(pool, bufsize); + if (!CFStringGetCString(valref, (char*)value, bufsize, + kCFStringEncodingUTF8)) + value = NULL; + + return value; +} + +/* Return the commercial name of the OS, given the version number in + a format that matches the regular expression /^10\.\d+(\..*)?$/ */ +static const char * +release_name_from_version(const char *osver) +{ + char *end = NULL; + unsigned long num = strtoul(osver, &end, 10); + + if (!end || *end != '.' || num != 10) + return NULL; + + osver = end + 1; + end = NULL; + num = strtoul(osver, &end, 10); + if (!end || (*end && *end != '.')) + return NULL; + + /* See http://en.wikipedia.org/wiki/History_of_OS_X#Release_timeline */ + switch(num) + { + case 0: return "Cheetah"; + case 1: return "Puma"; + case 2: return "Jaguar"; + case 3: return "Panther"; + case 4: return "Tiger"; + case 5: return "Leopard"; + case 6: return "Snow Leopard"; + case 7: return "Lion"; + case 8: return "Mountain Lion"; + } + + return NULL; +} + +/* Construct the release name from information stored in the Mac OS X + "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os + Server. */ +static const char * +macos_release_name(apr_pool_t *pool) +{ + svn_boolean_t server; + CFDictionaryRef plist = system_version_plist(&server, pool); + + if (plist) + { + const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool); + const char *osver = value_from_dict(plist, + CFSTR("ProductUserVisibleVersion"), + pool); + const char *build = value_from_dict(plist, + CFSTR("ProductBuildVersion"), + pool); + const char *release; + + if (!osver) + osver = value_from_dict(plist, CFSTR("ProductVersion"), pool); + release = release_name_from_version(osver); + + CFRelease(plist); + return apr_psprintf(pool, "%s%s%s%s%s%s%s%s", + (osname ? osname : ""), + (osver ? (osname ? " " : "") : ""), + (osver ? osver : ""), + (release ? (osname||osver ? " " : "") : ""), + (release ? release : ""), + (build + ? (osname||osver||release ? ", " : "") + : ""), + (build + ? (server ? "server build " : "build ") + : ""), + (build ? build : "")); + } + + return NULL; +} +#endif /* SVN_HAVE_MACOS_PLIST */ + +#ifdef SVN_HAVE_MACHO_ITERATE +/* List the shared libraries loaded by the current process. + Ignore frameworks and system libraries, they're just clutter. */ +static const apr_array_header_t * +macos_shared_libs(apr_pool_t *pool) +{ + static const char slb_prefix[] = "/usr/lib/system/"; + static const char fwk_prefix[] = "/System/Library/Frameworks/"; + static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/"; + + const size_t slb_prefix_len = strlen(slb_prefix); + const size_t fwk_prefix_len = strlen(fwk_prefix); + const size_t pfk_prefix_len = strlen(pfk_prefix); + + apr_array_header_t *result = NULL; + apr_array_header_t *dylibs = NULL; + + uint32_t i; + for (i = 0;; ++i) + { + const struct mach_header *header = _dyld_get_image_header(i); + const char *filename = _dyld_get_image_name(i); + const char *version; + char *truename; + svn_version_ext_loaded_lib_t *lib; + + if (!(header && filename)) + break; + + switch (header->cputype) + { + case CPU_TYPE_I386: version = _("Intel"); break; + case CPU_TYPE_X86_64: version = _("Intel 64-bit"); break; + case CPU_TYPE_POWERPC: version = _("PowerPC"); break; + case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break; + default: + version = NULL; + } + + if (0 == apr_filepath_merge(&truename, "", filename, + APR_FILEPATH_NATIVE + | APR_FILEPATH_TRUENAME, + pool)) + filename = truename; + else + filename = apr_pstrdup(pool, filename); + + if (0 == strncmp(filename, slb_prefix, slb_prefix_len) + || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len) + || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len)) + { + /* Ignore frameworks and system libraries. */ + continue; + } + + if (header->filetype == MH_EXECUTE) + { + /* Make sure the program filename is first in the list */ + if (!result) + { + result = apr_array_make(pool, 32, sizeof(*lib)); + } + lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t); + } + else + { + if (!dylibs) + { + dylibs = apr_array_make(pool, 32, sizeof(*lib)); + } + lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t); + } + + lib->name = filename; + lib->version = version; + } + + /* Gather results into one array. */ + if (dylibs) + { + if (result) + apr_array_cat(result, dylibs); + else + result = dylibs; + } + + return result; +} +#endif /* SVN_HAVE_MACHO_ITERATE */ diff --git a/subversion/libsvn_subr/sysinfo.h b/subversion/libsvn_subr/sysinfo.h new file mode 100644 index 000000000000..6a6e74d8798b --- /dev/null +++ b/subversion/libsvn_subr/sysinfo.h @@ -0,0 +1,69 @@ +/* + * sysinfo.h: share svn_sysinfo__* functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_SYSINFO_H +#define SVN_LIBSVN_SUBR_SYSINFO_H + +#include <apr_pools.h> +#include <apr_tables.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Return a canonical name similar to the output of config.guess, + * identifying the running system. + * + * All allocations are done in POOL. + */ +const char *svn_sysinfo__canonical_host(apr_pool_t *pool); + +/* Return the release name (i.e., marketing name) of the running + * system, or NULL if it's not available. + * + * All allocations are done in POOL. + */ +const char *svn_sysinfo__release_name(apr_pool_t *pool); + +/* Return an array of svn_version_linked_lib_t of descriptions of the + * link-time and run-time versions of dependent libraries, or NULL of + * the info is not available. + * + * All allocations are done in POOL. + */ +const apr_array_header_t *svn_sysinfo__linked_libs(apr_pool_t *pool); + +/* Return an array of svn_version_loaded_lib_t of descriptions of + * shared libraries loaded by the running process, including their + * versions where applicable, or NULL if the information is not + * available. + * + * All allocations are done in POOL. + */ +const apr_array_header_t *svn_sysinfo__loaded_libs(apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_SUBR_SYSINFO_H */ diff --git a/subversion/libsvn_subr/target.c b/subversion/libsvn_subr/target.c new file mode 100644 index 000000000000..3525167c66ae --- /dev/null +++ b/subversion/libsvn_subr/target.c @@ -0,0 +1,335 @@ +/* + * target.c: functions which operate on a list of targets supplied to + * a subversion subcommand. + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + + +/*** Code. ***/ + +svn_error_t * +svn_path_condense_targets(const char **pcommon, + apr_array_header_t **pcondensed_targets, + const apr_array_header_t *targets, + svn_boolean_t remove_redundancies, + apr_pool_t *pool) +{ + int i, j, num_condensed = targets->nelts; + svn_boolean_t *removed; + apr_array_header_t *abs_targets; + size_t basedir_len; + const char *first_target; + svn_boolean_t first_target_is_url; + + /* Early exit when there's no data to work on. */ + if (targets->nelts <= 0) + { + *pcommon = NULL; + if (pcondensed_targets) + *pcondensed_targets = NULL; + return SVN_NO_ERROR; + } + + /* Get the absolute path of the first target. */ + first_target = APR_ARRAY_IDX(targets, 0, const char *); + first_target_is_url = svn_path_is_url(first_target); + if (first_target_is_url) + { + first_target = apr_pstrdup(pool, first_target); + *pcommon = first_target; + } + else + SVN_ERR(svn_dirent_get_absolute(pcommon, first_target, pool)); + + /* Early exit when there's only one path to work on. */ + if (targets->nelts == 1) + { + if (pcondensed_targets) + *pcondensed_targets = apr_array_make(pool, 0, sizeof(const char *)); + return SVN_NO_ERROR; + } + + /* Copy the targets array, but with absolute paths instead of + relative. Also, find the pcommon argument by finding what is + common in all of the absolute paths. NOTE: This is not as + efficient as it could be. The calculation of the basedir could + be done in the loop below, which would save some calls to + svn_path_get_longest_ancestor. I decided to do it this way + because I thought it would be simpler, since this way, we don't + even do the loop if we don't need to condense the targets. */ + + removed = apr_pcalloc(pool, (targets->nelts * sizeof(svn_boolean_t))); + abs_targets = apr_array_make(pool, targets->nelts, sizeof(const char *)); + + APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon; + + for (i = 1; i < targets->nelts; ++i) + { + const char *rel = APR_ARRAY_IDX(targets, i, const char *); + const char *absolute; + svn_boolean_t is_url = svn_path_is_url(rel); + + if (is_url) + absolute = apr_pstrdup(pool, rel); /* ### TODO: avoid pool dup? */ + else + SVN_ERR(svn_dirent_get_absolute(&absolute, rel, pool)); + + APR_ARRAY_PUSH(abs_targets, const char *) = absolute; + + /* If we've not already determined that there's no common + parent, then continue trying to do so. */ + if (*pcommon && **pcommon) + { + /* If the is-url-ness of this target doesn't match that of + the first target, our search for a common ancestor can + end right here. Otherwise, use the appropriate + get-longest-ancestor function per the path type. */ + if (is_url != first_target_is_url) + *pcommon = ""; + else if (first_target_is_url) + *pcommon = svn_uri_get_longest_ancestor(*pcommon, absolute, pool); + else + *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute, + pool); + } + } + + if (pcondensed_targets != NULL) + { + if (remove_redundancies) + { + /* Find the common part of each pair of targets. If + common part is equal to one of the paths, the other + is a child of it, and can be removed. If a target is + equal to *pcommon, it can also be removed. */ + + /* First pass: when one non-removed target is a child of + another non-removed target, remove the child. */ + for (i = 0; i < abs_targets->nelts; ++i) + { + if (removed[i]) + continue; + + for (j = i + 1; j < abs_targets->nelts; ++j) + { + const char *abs_targets_i; + const char *abs_targets_j; + svn_boolean_t i_is_url, j_is_url; + const char *ancestor; + + if (removed[j]) + continue; + + abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *); + abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *); + i_is_url = svn_path_is_url(abs_targets_i); + j_is_url = svn_path_is_url(abs_targets_j); + + if (i_is_url != j_is_url) + continue; + + if (i_is_url) + ancestor = svn_uri_get_longest_ancestor(abs_targets_i, + abs_targets_j, + pool); + else + ancestor = svn_dirent_get_longest_ancestor(abs_targets_i, + abs_targets_j, + pool); + + if (*ancestor == '\0') + continue; + + if (strcmp(ancestor, abs_targets_i) == 0) + { + removed[j] = TRUE; + num_condensed--; + } + else if (strcmp(ancestor, abs_targets_j) == 0) + { + removed[i] = TRUE; + num_condensed--; + } + } + } + + /* Second pass: when a target is the same as *pcommon, + remove the target. */ + for (i = 0; i < abs_targets->nelts; ++i) + { + const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i, + const char *); + + if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i])) + { + removed[i] = TRUE; + num_condensed--; + } + } + } + + /* Now create the return array, and copy the non-removed items */ + basedir_len = strlen(*pcommon); + *pcondensed_targets = apr_array_make(pool, num_condensed, + sizeof(const char *)); + + for (i = 0; i < abs_targets->nelts; ++i) + { + const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *); + + /* Skip this if it's been removed. */ + if (removed[i]) + continue; + + /* If a common prefix was found, condensed_targets are given + relative to that prefix. */ + if (basedir_len > 0) + { + /* Only advance our pointer past a path separator if + REL_ITEM isn't the same as *PCOMMON. + + If *PCOMMON is a root path, basedir_len will already + include the closing '/', so never advance the pointer + here. + */ + rel_item += basedir_len; + if (rel_item[0] && + ! svn_dirent_is_root(*pcommon, basedir_len)) + rel_item++; + } + + APR_ARRAY_PUSH(*pcondensed_targets, const char *) + = apr_pstrdup(pool, rel_item); + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_path_remove_redundancies(apr_array_header_t **pcondensed_targets, + const apr_array_header_t *targets, + apr_pool_t *pool) +{ + apr_pool_t *temp_pool; + apr_array_header_t *abs_targets; + apr_array_header_t *rel_targets; + int i; + + if ((targets->nelts <= 0) || (! pcondensed_targets)) + { + /* No targets or no place to store our work means this function + really has nothing to do. */ + if (pcondensed_targets) + *pcondensed_targets = NULL; + return SVN_NO_ERROR; + } + + /* Initialize our temporary pool. */ + temp_pool = svn_pool_create(pool); + + /* Create our list of absolute paths for our "keepers" */ + abs_targets = apr_array_make(temp_pool, targets->nelts, + sizeof(const char *)); + + /* Create our list of untainted paths for our "keepers" */ + rel_targets = apr_array_make(pool, targets->nelts, + sizeof(const char *)); + + /* For each target in our list we do the following: + + 1. Calculate its absolute path (ABS_PATH). + 2. See if any of the keepers in ABS_TARGETS is a parent of, or + is the same path as, ABS_PATH. If so, we ignore this + target. If not, however, add this target's absolute path to + ABS_TARGETS and its original path to REL_TARGETS. + */ + for (i = 0; i < targets->nelts; i++) + { + const char *rel_path = APR_ARRAY_IDX(targets, i, const char *); + const char *abs_path; + int j; + svn_boolean_t is_url, keep_me; + + /* Get the absolute path for this target. */ + is_url = svn_path_is_url(rel_path); + if (is_url) + abs_path = rel_path; + else + SVN_ERR(svn_dirent_get_absolute(&abs_path, rel_path, temp_pool)); + + /* For each keeper in ABS_TARGETS, see if this target is the + same as or a child of that keeper. */ + keep_me = TRUE; + for (j = 0; j < abs_targets->nelts; j++) + { + const char *keeper = APR_ARRAY_IDX(abs_targets, j, const char *); + svn_boolean_t keeper_is_url = svn_path_is_url(keeper); + const char *child_relpath; + + /* If KEEPER hasn't the same is-url-ness as ABS_PATH, we + know they aren't equal and that one isn't the child of + the other. */ + if (is_url != keeper_is_url) + continue; + + /* Quit here if this path is the same as or a child of one of the + keepers. */ + if (is_url) + child_relpath = svn_uri_skip_ancestor(keeper, abs_path, temp_pool); + else + child_relpath = svn_dirent_skip_ancestor(keeper, abs_path); + if (child_relpath) + { + keep_me = FALSE; + break; + } + } + + /* If this is a new keeper, add its absolute path to ABS_TARGETS + and its original path to REL_TARGETS. */ + if (keep_me) + { + APR_ARRAY_PUSH(abs_targets, const char *) = abs_path; + APR_ARRAY_PUSH(rel_targets, const char *) = rel_path; + } + } + + /* Destroy our temporary pool. */ + svn_pool_destroy(temp_pool); + + /* Make sure we return the list of untainted keeper paths. */ + *pcondensed_targets = rel_targets; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/temp_serializer.c b/subversion/libsvn_subr/temp_serializer.c new file mode 100644 index 000000000000..261267a37d37 --- /dev/null +++ b/subversion/libsvn_subr/temp_serializer.c @@ -0,0 +1,382 @@ +/* + * svn_temp_serializer.c: implement the tempoary structure serialization API + * + * ==================================================================== + * 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 <assert.h> +#include "private/svn_temp_serializer.h" +#include "svn_string.h" + +/* This is a very efficient serialization and especially efficient + * deserialization framework. The idea is just to concatenate all sub- + * structures and strings into a single buffer while preserving proper + * member alignment. Pointers will be replaced by the respective data + * offsets in the buffer when that target that it pointed to gets + * serialized, i.e. appended to the data buffer written so far. + * + * Hence, deserialization can be simply done by copying the buffer and + * adjusting the pointers. No fine-grained allocation and copying is + * necessary. + */ + +/* An element in the structure stack. It contains a pointer to the source + * structure so that the relative offset of sub-structure or string + * references can be determined properly. It also contains the corresponding + * position within the serialized data. Thus, pointers can be serialized + * as offsets within the target buffer. + */ +typedef struct source_stack_t +{ + /* the source structure passed in to *_init or *_push */ + const void *source_struct; + + /* offset within the target buffer to where the structure got copied */ + apr_size_t target_offset; + + /* parent stack entry. Will be NULL for the root entry. + * Items in the svn_temp_serializer__context_t recycler will use this + * to link to the next unused item. */ + struct source_stack_t *upper; +} source_stack_t; + +/* Serialization context info. It basically consists of the buffer holding + * the serialized result and the stack of source structure information. + */ +struct svn_temp_serializer__context_t +{ + /* allocations are made from this pool */ + apr_pool_t *pool; + + /* the buffer holding all serialized data */ + svn_stringbuf_t *buffer; + + /* the stack of structures being serialized. If NULL, the serialization + * process has been finished. However, it is not necessarily NULL when + * the application end serialization. */ + source_stack_t *source; + + /* unused stack elements will be put here for later reuse. */ + source_stack_t *recycler; +}; + +/* Make sure the serialized data len is a multiple of the default alignment, + * i.e. structures may be appended without violating member alignment + * guarantees. + */ +static void +align_buffer_end(svn_temp_serializer__context_t *context) +{ + apr_size_t current_len = context->buffer->len; + apr_size_t aligned_len = APR_ALIGN_DEFAULT(current_len); + + if (aligned_len + 1 > context->buffer->blocksize) + svn_stringbuf_ensure(context->buffer, aligned_len); + + context->buffer->len = aligned_len; +} + +/* Begin the serialization process for the SOURCE_STRUCT and all objects + * referenced from it. STRUCT_SIZE must match the result of sizeof() of + * the actual structure. You may suggest a larger initial buffer size + * in SUGGESTED_BUFFER_SIZE to minimize the number of internal buffer + * re-allocations during the serialization process. All allocations will + * be made from POOL. + */ +svn_temp_serializer__context_t * +svn_temp_serializer__init(const void *source_struct, + apr_size_t struct_size, + apr_size_t suggested_buffer_size, + apr_pool_t *pool) +{ + /* select a meaningful initial memory buffer capacity */ + apr_size_t init_size = suggested_buffer_size < struct_size + ? struct_size + : suggested_buffer_size; + + /* create the serialization context and initialize it */ + svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context)); + context->pool = pool; + context->buffer = svn_stringbuf_create_ensure(init_size, pool); + context->recycler = NULL; + + /* If a source struct has been given, make it the root struct. */ + if (source_struct) + { + context->source = apr_palloc(pool, sizeof(*context->source)); + context->source->source_struct = source_struct; + context->source->target_offset = 0; + context->source->upper = NULL; + + /* serialize, i.e. append, the content of the first structure */ + svn_stringbuf_appendbytes(context->buffer, source_struct, struct_size); + } + else + { + /* The root struct will be set with the first push() op, or not at all + * (in case of a plain string). */ + context->source = NULL; + } + + /* done */ + return context; +} + +/* Continue the serialization process of the SOURCE_STRUCT that has already + * been serialized to BUFFER but contains references to new objects yet to + * serialize. The current size of the serialized data is given in + * CURRENTLY_USED. If the allocated data buffer is actually larger, you may + * specifiy that in CURRENTLY_ALLOCATED to prevent unnecessary allocations. + * Otherwise, set it to 0. All allocations will be made from POOl. + */ +svn_temp_serializer__context_t * +svn_temp_serializer__init_append(void *buffer, + void *source_struct, + apr_size_t currently_used, + apr_size_t currently_allocated, + apr_pool_t *pool) +{ + /* determine the current memory buffer capacity */ + apr_size_t init_size = currently_allocated < currently_used + ? currently_used + : currently_allocated; + + /* create the serialization context and initialize it */ + svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context)); + context->pool = pool; + + /* use BUFFER as serialization target */ + context->buffer = svn_stringbuf_create_ensure(0, pool); + context->buffer->data = buffer; + context->buffer->len = currently_used; + context->buffer->blocksize = init_size; + + /* SOURCE_STRUCT is our serialization root */ + context->source = apr_palloc(pool, sizeof(*context->source)); + context->source->source_struct = source_struct; + context->source->target_offset = (char *)source_struct - (char *)buffer; + context->source->upper = NULL; + + /* initialize the RECYCLER */ + context->recycler = NULL; + + /* done */ + return context; +} + +/* Utility function replacing the serialized pointer corresponding to + * *SOURCE_POINTER with the offset that it will be put when being append + * right after this function call. + */ +static void +store_current_end_pointer(svn_temp_serializer__context_t *context, + const void * const * source_pointer) +{ + apr_size_t ptr_offset; + apr_size_t *target_ptr; + + /* if *source_pointer is the root struct, there will be no parent structure + * to relate it to */ + if (context->source == NULL) + return; + + /* position of the serialized pointer relative to the begin of the buffer */ + ptr_offset = (const char *)source_pointer + - (const char *)context->source->source_struct + + context->source->target_offset; + + /* the offset must be within the serialized data. Otherwise, you forgot + * to serialize the respective sub-struct. */ + assert(context->buffer->len > ptr_offset); + + /* use the serialized pointer as a storage for the offset */ + target_ptr = (apr_size_t*)(context->buffer->data + ptr_offset); + + /* store the current buffer length because that's where we will append + * the serialized data of the sub-struct or string */ + *target_ptr = *source_pointer == NULL + ? 0 + : context->buffer->len - context->source->target_offset; +} + +/* Begin serialization of a referenced sub-structure within the + * serialization CONTEXT. SOURCE_STRUCT must be a reference to the pointer + * in the original parent structure so that the correspondence in the + * serialized structure can be established. STRUCT_SIZE must match the + * result of sizeof() of the actual structure. + */ +void +svn_temp_serializer__push(svn_temp_serializer__context_t *context, + const void * const * source_struct, + apr_size_t struct_size) +{ + const void *source = *source_struct; + source_stack_t *new; + + /* recycle an old entry or create a new one for the structure stack */ + if (context->recycler) + { + new = context->recycler; + context->recycler = new->upper; + } + else + new = apr_palloc(context->pool, sizeof(*new)); + + /* the serialized structure must be properly aligned */ + if (source) + align_buffer_end(context); + + /* Store the offset at which the struct data that will the appended. + * Write 0 for NULL pointers. */ + store_current_end_pointer(context, source_struct); + + /* store source and target information */ + new->source_struct = source; + new->target_offset = context->buffer->len; + + /* put the new entry onto the stack*/ + new->upper = context->source; + context->source = new; + + /* finally, actually append the new struct + * (so we can now manipulate pointers within it) */ + if (*source_struct) + svn_stringbuf_appendbytes(context->buffer, source, struct_size); +} + +/* Remove the lastest structure from the stack. + */ +void +svn_temp_serializer__pop(svn_temp_serializer__context_t *context) +{ + source_stack_t *old = context->source; + + /* we may pop the original struct but not further */ + assert(context->source); + + /* one level up the structure stack */ + context->source = context->source->upper; + + /* put the old stack element into the recycler for later reuse */ + old->upper = context->recycler; + context->recycler = old; +} + +/* Serialize a string referenced from the current structure within the + * serialization CONTEXT. S must be a reference to the char* pointer in + * the original structure so that the correspondence in the serialized + * structure can be established. + */ +void +svn_temp_serializer__add_string(svn_temp_serializer__context_t *context, + const char * const * s) +{ + const char *string = *s; + + /* Store the offset at which the string data that will the appended. + * Write 0 for NULL pointers. Strings don't need special alignment. */ + store_current_end_pointer(context, (const void **)s); + + /* append the string data */ + if (string) + svn_stringbuf_appendbytes(context->buffer, string, strlen(string) + 1); +} + +/* Set the serialized representation of the pointer PTR inside the current + * structure within the serialization CONTEXT to NULL. This is particularly + * useful if the pointer is not NULL in the source structure. + */ +void +svn_temp_serializer__set_null(svn_temp_serializer__context_t *context, + const void * const * ptr) +{ + apr_size_t offset; + + /* there must be a parent structure */ + assert(context->source); + + /* position of the serialized pointer relative to the begin of the buffer */ + offset = (const char *)ptr + - (const char *)context->source->source_struct + + context->source->target_offset; + + /* the offset must be within the serialized data. Otherwise, you forgot + * to serialize the respective sub-struct. */ + assert(context->buffer->len > offset); + + /* use the serialized pointer as a storage for the offset */ + *(apr_size_t*)(context->buffer->data + offset) = 0; +} + +/* Return the number of bytes currently used in the serialization buffer + * of the given serialization CONTEXT.*/ +apr_size_t +svn_temp_serializer__get_length(svn_temp_serializer__context_t *context) +{ + return context->buffer->len; +} + +/* Return the data buffer that receives the serialized data from + * the given serialization CONTEXT. + */ +svn_stringbuf_t * +svn_temp_serializer__get(svn_temp_serializer__context_t *context) +{ + return context->buffer; +} + +/* Replace the deserialized pointer value at PTR inside BUFFER with a + * proper pointer value. + */ +void +svn_temp_deserializer__resolve(void *buffer, void **ptr) +{ + /* All pointers are stored as offsets to the buffer start + * (of the respective serialized sub-struct). */ + apr_size_t ptr_offset = *(apr_size_t *)ptr; + if (ptr_offset) + { + /* Reconstruct the original pointer value */ + const char *target = (const char *)buffer + ptr_offset; + + /* All sub-structs are written _after_ their respective parent. + * Thus, all offsets are > 0. If the following assertion is not met, + * the data is either corrupt or you tried to resolve the pointer + * more than once. */ + assert(target > (const char *)buffer); + + /* replace the PTR_OFFSET in *ptr with the pointer to TARGET */ + (*(const char **)ptr) = target; + } + else + { + /* NULL pointers are stored as 0 which might have a different + * binary representation. */ + *ptr = NULL; + } +} + +const void * +svn_temp_deserializer__ptr(const void *buffer, const void *const *ptr) +{ + return (apr_size_t)*ptr == 0 + ? NULL + : (const char*)buffer + (apr_size_t)*ptr; +} diff --git a/subversion/libsvn_subr/time.c b/subversion/libsvn_subr/time.c new file mode 100644 index 000000000000..ccd62691d810 --- /dev/null +++ b/subversion/libsvn_subr/time.c @@ -0,0 +1,277 @@ +/* + * time.c: time/date utilities + * + * ==================================================================== + * 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 <string.h> +#include <stdlib.h> +#include <apr_pools.h> +#include <apr_time.h> +#include <apr_strings.h> +#include "svn_io.h" +#include "svn_time.h" +#include "svn_utf.h" +#include "svn_error.h" +#include "svn_private_config.h" + + + +/*** Code. ***/ + +/* Our timestamp strings look like this: + * + * "2002-05-07Thh:mm:ss.uuuuuuZ" + * + * The format is conformant with ISO-8601 and the date format required + * by RFC2518 for creationdate. It is a direct conversion between + * apr_time_t and a string, so converting to string and back retains + * the exact value. + */ +#define TIMESTAMP_FORMAT "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" + +/* Our old timestamp strings looked like this: + * + * "Tue 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)" + * + * The idea is that they are conventionally human-readable for the + * first part, and then in parentheses comes everything else required + * to completely fill in an apr_time_exp_t: tm_yday, tm_isdst, + * and tm_gmtoff. + * + * This format is still recognized on input, for backward + * compatibility, but no longer generated. + */ +#define OLD_TIMESTAMP_FORMAT \ + "%3s %d %3s %d %02d:%02d:%02d.%06d (day %03d, dst %d, gmt_off %06d)" + +/* Our human representation of dates looks like this: + * + * "2002-06-23 11:13:02 +0300 (Sun, 23 Jun 2002)" + * + * This format is used whenever time is shown to the user. It consists + * of a machine parseable, almost ISO-8601, part in the beginning - + * and a human explanatory part at the end. The machine parseable part + * is generated strictly by APR and our code, with a apr_snprintf. The + * human explanatory part is generated by apr_strftime, which means + * that its generation can be affected by locale, it can fail and it + * doesn't need to be constant in size. In other words, perfect to be + * converted to a configuration option later on. + */ +/* Maximum length for the date string. */ +#define SVN_TIME__MAX_LENGTH 80 +/* Machine parseable part, generated by apr_snprintf. */ +#define HUMAN_TIMESTAMP_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %+.2d%.2d" +/* Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000" */ +#define human_timestamp_format_suffix _(" (%a, %d %b %Y)") + +const char * +svn_time_to_cstring(apr_time_t when, apr_pool_t *pool) +{ + apr_time_exp_t exploded_time; + + /* We toss apr_status_t return value here -- for one thing, caller + should pass in good information. But also, where APR's own code + calls these functions it tosses the return values, and + furthermore their current implementations can only return success + anyway. */ + + /* We get the date in GMT now -- and expect the tm_gmtoff and + tm_isdst to be not set. We also ignore the weekday and yearday, + since those are not needed. */ + + apr_time_exp_gmt(&exploded_time, when); + + /* It would be nice to use apr_strftime(), but APR doesn't give a + way to convert back, so we wouldn't be able to share the format + string between the writer and reader. */ + return apr_psprintf(pool, + TIMESTAMP_FORMAT, + exploded_time.tm_year + 1900, + exploded_time.tm_mon + 1, + exploded_time.tm_mday, + exploded_time.tm_hour, + exploded_time.tm_min, + exploded_time.tm_sec, + exploded_time.tm_usec); +} + + +static apr_int32_t +find_matching_string(char *str, apr_size_t size, const char strings[][4]) +{ + apr_size_t i; + + for (i = 0; i < size; i++) + if (strings[i] && (strcmp(str, strings[i]) == 0)) + return (apr_int32_t) i; + + return -1; +} + + +svn_error_t * +svn_time_from_cstring(apr_time_t *when, const char *data, apr_pool_t *pool) +{ + apr_time_exp_t exploded_time; + apr_status_t apr_err; + char wday[4], month[4]; + char *c; + + /* Open-code parsing of the new timestamp format, as this + is a hot path for reading the entries file. This format looks + like: "2001-08-31T04:24:14.966996Z" */ + exploded_time.tm_year = (apr_int32_t) strtol(data, &c, 10); + if (*c++ != '-') goto fail; + exploded_time.tm_mon = (apr_int32_t) strtol(c, &c, 10); + if (*c++ != '-') goto fail; + exploded_time.tm_mday = (apr_int32_t) strtol(c, &c, 10); + if (*c++ != 'T') goto fail; + exploded_time.tm_hour = (apr_int32_t) strtol(c, &c, 10); + if (*c++ != ':') goto fail; + exploded_time.tm_min = (apr_int32_t) strtol(c, &c, 10); + if (*c++ != ':') goto fail; + exploded_time.tm_sec = (apr_int32_t) strtol(c, &c, 10); + if (*c++ != '.') goto fail; + exploded_time.tm_usec = (apr_int32_t) strtol(c, &c, 10); + if (*c++ != 'Z') goto fail; + + exploded_time.tm_year -= 1900; + exploded_time.tm_mon -= 1; + exploded_time.tm_wday = 0; + exploded_time.tm_yday = 0; + exploded_time.tm_isdst = 0; + exploded_time.tm_gmtoff = 0; + + apr_err = apr_time_exp_gmt_get(when, &exploded_time); + if (apr_err == APR_SUCCESS) + return SVN_NO_ERROR; + + return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); + + fail: + /* Try the compatibility option. This does not need to be fast, + as this format is no longer generated and the client will convert + an old-format entries file the first time it reads it. */ + if (sscanf(data, + OLD_TIMESTAMP_FORMAT, + wday, + &exploded_time.tm_mday, + month, + &exploded_time.tm_year, + &exploded_time.tm_hour, + &exploded_time.tm_min, + &exploded_time.tm_sec, + &exploded_time.tm_usec, + &exploded_time.tm_yday, + &exploded_time.tm_isdst, + &exploded_time.tm_gmtoff) == 11) + { + exploded_time.tm_year -= 1900; + exploded_time.tm_yday -= 1; + /* Using hard coded limits for the arrays - they are going away + soon in any case. */ + exploded_time.tm_wday = find_matching_string(wday, 7, apr_day_snames); + exploded_time.tm_mon = find_matching_string(month, 12, apr_month_snames); + + apr_err = apr_time_exp_gmt_get(when, &exploded_time); + if (apr_err != APR_SUCCESS) + return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); + + return SVN_NO_ERROR; + } + /* Timestamp is something we do not recognize. */ + else + return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); +} + + +const char * +svn_time_to_human_cstring(apr_time_t when, apr_pool_t *pool) +{ + apr_time_exp_t exploded_time; + apr_size_t len, retlen; + apr_status_t ret; + char *datestr, *curptr, human_datestr[SVN_TIME__MAX_LENGTH]; + + /* Get the time into parts */ + ret = apr_time_exp_lt(&exploded_time, when); + if (ret) + return NULL; + + /* Make room for datestring */ + datestr = apr_palloc(pool, SVN_TIME__MAX_LENGTH); + + /* Put in machine parseable part */ + len = apr_snprintf(datestr, + SVN_TIME__MAX_LENGTH, + HUMAN_TIMESTAMP_FORMAT, + exploded_time.tm_year + 1900, + exploded_time.tm_mon + 1, + exploded_time.tm_mday, + exploded_time.tm_hour, + exploded_time.tm_min, + exploded_time.tm_sec, + exploded_time.tm_gmtoff / (60 * 60), + (abs(exploded_time.tm_gmtoff) / 60) % 60); + + /* If we overfilled the buffer, just return what we got. */ + if (len >= SVN_TIME__MAX_LENGTH) + return datestr; + + /* Calculate offset to the end of the machine parseable part. */ + curptr = datestr + len; + + /* Put in human explanatory part */ + ret = apr_strftime(human_datestr, + &retlen, + SVN_TIME__MAX_LENGTH - len, + human_timestamp_format_suffix, + &exploded_time); + + /* If there was an error, ensure that the string is zero-terminated. */ + if (ret || retlen == 0) + *curptr = '\0'; + else + { + const char *utf8_string; + svn_error_t *err; + + err = svn_utf_cstring_to_utf8(&utf8_string, human_datestr, pool); + if (err) + { + *curptr = '\0'; + svn_error_clear(err); + } + else + apr_cpystrn(curptr, utf8_string, SVN_TIME__MAX_LENGTH - len); + } + + return datestr; +} + + +void +svn_sleep_for_timestamps(void) +{ + svn_io_sleep_for_timestamps(NULL, NULL); +} diff --git a/subversion/libsvn_subr/token.c b/subversion/libsvn_subr/token.c new file mode 100644 index 000000000000..d4fc725ebf4a --- /dev/null +++ b/subversion/libsvn_subr/token.c @@ -0,0 +1,98 @@ +/* + * token.c : value/string-token functions + * + * ==================================================================== + * 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_types.h" +#include "svn_error.h" + +#include "private/svn_token.h" +#include "svn_private_config.h" + + +const char * +svn_token__to_word(const svn_token_map_t *map, + int value) +{ + for (; map->str != NULL; ++map) + if (map->val == value) + return map->str; + + /* Internal, numeric values should always be found. */ + SVN_ERR_MALFUNCTION_NO_RETURN(); +} + + +int +svn_token__from_word_strict(const svn_token_map_t *map, + const char *word) +{ + int value = svn_token__from_word(map, word); + + if (value == SVN_TOKEN_UNKNOWN) + SVN_ERR_MALFUNCTION_NO_RETURN(); + + return value; +} + + +svn_error_t * +svn_token__from_word_err(int *value, + const svn_token_map_t *map, + const char *word) +{ + *value = svn_token__from_word(map, word); + + if (*value == SVN_TOKEN_UNKNOWN) + return svn_error_createf(SVN_ERR_BAD_TOKEN, NULL, + _("Token '%s' is unrecognized"), + word); + + return SVN_NO_ERROR; +} + + +int +svn_token__from_word(const svn_token_map_t *map, + const char *word) +{ + if (word == NULL) + return SVN_TOKEN_UNKNOWN; + + for (; map->str != NULL; ++map) + if (strcmp(map->str, word) == 0) + return map->val; + + return SVN_TOKEN_UNKNOWN; +} + + +int +svn_token__from_mem(const svn_token_map_t *map, + const char *word, + apr_size_t len) +{ + for (; map->str != NULL; ++map) + if (strncmp(map->str, word, len) == 0 && map->str[len] == '\0') + return map->val; + + return SVN_TOKEN_UNKNOWN; +} diff --git a/subversion/libsvn_subr/types.c b/subversion/libsvn_subr/types.c new file mode 100644 index 000000000000..30ebb65852f0 --- /dev/null +++ b/subversion/libsvn_subr/types.c @@ -0,0 +1,340 @@ +/* + * svn_types.c : Implementation for Subversion's data types. + * + * ==================================================================== + * 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_uuid.h> + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_error.h" +#include "svn_string.h" +#include "svn_props.h" +#include "svn_private_config.h" + +svn_error_t * +svn_revnum_parse(svn_revnum_t *rev, + const char *str, + const char **endptr) +{ + char *end; + + svn_revnum_t result = strtol(str, &end, 10); + + if (endptr) + *endptr = end; + + if (str == end) + return svn_error_createf(SVN_ERR_REVNUM_PARSE_FAILURE, NULL, + _("Invalid revision number found parsing '%s'"), + str); + + if (result < 0) + { + /* The end pointer from strtol() is valid, but a negative revision + number is invalid, so move the end pointer back to the + beginning of the string. */ + if (endptr) + *endptr = str; + + return svn_error_createf(SVN_ERR_REVNUM_PARSE_FAILURE, NULL, + _("Negative revision number found parsing '%s'"), + str); + } + + *rev = result; + + return SVN_NO_ERROR; +} + +const char * +svn_uuid_generate(apr_pool_t *pool) +{ + apr_uuid_t uuid; + char *uuid_str = apr_pcalloc(pool, APR_UUID_FORMATTED_LENGTH + 1); + apr_uuid_get(&uuid); + apr_uuid_format(uuid_str, &uuid); + return uuid_str; +} + +const char * +svn_depth_to_word(svn_depth_t depth) +{ + switch (depth) + { + case svn_depth_exclude: + return "exclude"; + case svn_depth_unknown: + return "unknown"; + case svn_depth_empty: + return "empty"; + case svn_depth_files: + return "files"; + case svn_depth_immediates: + return "immediates"; + case svn_depth_infinity: + return "infinity"; + default: + return "INVALID-DEPTH"; + } +} + + +svn_depth_t +svn_depth_from_word(const char *word) +{ + if (strcmp(word, "exclude") == 0) + return svn_depth_exclude; + if (strcmp(word, "unknown") == 0) + return svn_depth_unknown; + if (strcmp(word, "empty") == 0) + return svn_depth_empty; + if (strcmp(word, "files") == 0) + return svn_depth_files; + if (strcmp(word, "immediates") == 0) + return svn_depth_immediates; + if (strcmp(word, "infinity") == 0) + return svn_depth_infinity; + /* There's no special value for invalid depth, and no convincing + reason to make one yet, so just fall back to unknown depth. + If you ever change that convention, check callers to make sure + they're not depending on it (e.g., option parsing in main() ). + */ + return svn_depth_unknown; +} + +const char * +svn_node_kind_to_word(svn_node_kind_t kind) +{ + switch (kind) + { + case svn_node_none: + return "none"; + case svn_node_file: + return "file"; + case svn_node_dir: + return "dir"; + case svn_node_symlink: + return "symlink"; + case svn_node_unknown: + default: + return "unknown"; + } +} + + +svn_node_kind_t +svn_node_kind_from_word(const char *word) +{ + if (word == NULL) + return svn_node_unknown; + + if (strcmp(word, "none") == 0) + return svn_node_none; + else if (strcmp(word, "file") == 0) + return svn_node_file; + else if (strcmp(word, "dir") == 0) + return svn_node_dir; + else if (strcmp(word, "symlink") == 0) + return svn_node_symlink; + else + /* This also handles word == "unknown" */ + return svn_node_unknown; +} + +const char * +svn_tristate__to_word(svn_tristate_t tristate) +{ + switch (tristate) + { + case svn_tristate_false: + return "false"; + case svn_tristate_true: + return "true"; + case svn_tristate_unknown: + default: + return NULL; + } +} + +svn_tristate_t +svn_tristate__from_word(const char *word) +{ + if (word == NULL) + return svn_tristate_unknown; + else if (0 == svn_cstring_casecmp(word, "true") + || 0 == svn_cstring_casecmp(word, "yes") + || 0 == svn_cstring_casecmp(word, "on") + || 0 == strcmp(word, "1")) + return svn_tristate_true; + else if (0 == svn_cstring_casecmp(word, "false") + || 0 == svn_cstring_casecmp(word, "no") + || 0 == svn_cstring_casecmp(word, "off") + || 0 == strcmp(word, "0")) + return svn_tristate_false; + + return svn_tristate_unknown; +} + +svn_commit_info_t * +svn_create_commit_info(apr_pool_t *pool) +{ + svn_commit_info_t *commit_info + = apr_pcalloc(pool, sizeof(*commit_info)); + + commit_info->revision = SVN_INVALID_REVNUM; + /* All other fields were initialized to NULL above. */ + + return commit_info; +} + +svn_commit_info_t * +svn_commit_info_dup(const svn_commit_info_t *src_commit_info, + apr_pool_t *pool) +{ + svn_commit_info_t *dst_commit_info + = apr_palloc(pool, sizeof(*dst_commit_info)); + + dst_commit_info->date = src_commit_info->date + ? apr_pstrdup(pool, src_commit_info->date) : NULL; + dst_commit_info->author = src_commit_info->author + ? apr_pstrdup(pool, src_commit_info->author) : NULL; + dst_commit_info->revision = src_commit_info->revision; + dst_commit_info->post_commit_err = src_commit_info->post_commit_err + ? apr_pstrdup(pool, src_commit_info->post_commit_err) : NULL; + dst_commit_info->repos_root = src_commit_info->repos_root + ? apr_pstrdup(pool, src_commit_info->repos_root) : NULL; + + return dst_commit_info; +} + +svn_log_changed_path2_t * +svn_log_changed_path2_create(apr_pool_t *pool) +{ + svn_log_changed_path2_t *new_changed_path + = apr_pcalloc(pool, sizeof(*new_changed_path)); + + new_changed_path->text_modified = svn_tristate_unknown; + new_changed_path->props_modified = svn_tristate_unknown; + + return new_changed_path; +} + +svn_log_changed_path2_t * +svn_log_changed_path2_dup(const svn_log_changed_path2_t *changed_path, + apr_pool_t *pool) +{ + svn_log_changed_path2_t *new_changed_path + = apr_palloc(pool, sizeof(*new_changed_path)); + + *new_changed_path = *changed_path; + + if (new_changed_path->copyfrom_path) + new_changed_path->copyfrom_path = + apr_pstrdup(pool, new_changed_path->copyfrom_path); + + return new_changed_path; +} + +svn_dirent_t * +svn_dirent_create(apr_pool_t *result_pool) +{ + svn_dirent_t *new_dirent = apr_pcalloc(result_pool, sizeof(*new_dirent)); + + new_dirent->kind = svn_node_unknown; + new_dirent->size = SVN_INVALID_FILESIZE; + new_dirent->created_rev = SVN_INVALID_REVNUM; + new_dirent->time = 0; + new_dirent->last_author = NULL; + + return new_dirent; +} + +svn_dirent_t * +svn_dirent_dup(const svn_dirent_t *dirent, + apr_pool_t *pool) +{ + svn_dirent_t *new_dirent = apr_palloc(pool, sizeof(*new_dirent)); + + *new_dirent = *dirent; + + new_dirent->last_author = apr_pstrdup(pool, dirent->last_author); + + return new_dirent; +} + +svn_log_entry_t * +svn_log_entry_create(apr_pool_t *pool) +{ + svn_log_entry_t *log_entry = apr_pcalloc(pool, sizeof(*log_entry)); + + return log_entry; +} + +svn_log_entry_t * +svn_log_entry_dup(const svn_log_entry_t *log_entry, apr_pool_t *pool) +{ + apr_hash_index_t *hi; + svn_log_entry_t *new_entry = apr_palloc(pool, sizeof(*new_entry)); + + *new_entry = *log_entry; + + if (log_entry->revprops) + new_entry->revprops = svn_prop_hash_dup(log_entry->revprops, pool); + + if (log_entry->changed_paths2) + { + new_entry->changed_paths2 = apr_hash_make(pool); + + for (hi = apr_hash_first(pool, log_entry->changed_paths2); + hi; hi = apr_hash_next(hi)) + { + const void *key; + void *change; + + apr_hash_this(hi, &key, NULL, &change); + + svn_hash_sets(new_entry->changed_paths2, apr_pstrdup(pool, key), + svn_log_changed_path2_dup(change, pool)); + } + } + + /* We can't copy changed_paths by itself without using deprecated code, + but we don't have to, as this function was new after the introduction + of the changed_paths2 field. */ + new_entry->changed_paths = new_entry->changed_paths2; + + return new_entry; +} + +svn_location_segment_t * +svn_location_segment_dup(const svn_location_segment_t *segment, + apr_pool_t *pool) +{ + svn_location_segment_t *new_segment = + apr_palloc(pool, sizeof(*new_segment)); + + *new_segment = *segment; + if (segment->path) + new_segment->path = apr_pstrdup(pool, segment->path); + return new_segment; +} diff --git a/subversion/libsvn_subr/user.c b/subversion/libsvn_subr/user.c new file mode 100644 index 000000000000..7d6d0bf0e115 --- /dev/null +++ b/subversion/libsvn_subr/user.c @@ -0,0 +1,86 @@ +/* + * user.c: APR wrapper functions for Subversion + * + * ==================================================================== + * 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_user.h> +#include <apr_env.h> + +#include "svn_user.h" +#include "svn_utf.h" + +/* Get the current user's name from the OS */ +static const char * +get_os_username(apr_pool_t *pool) +{ +#if APR_HAS_USER + char *username; + apr_uid_t uid; + apr_gid_t gid; + + if (apr_uid_current(&uid, &gid, pool) == APR_SUCCESS && + apr_uid_name_get(&username, uid, pool) == APR_SUCCESS) + return username; +#endif + + return NULL; +} + +/* Return a UTF8 version of STR, or NULL on error. + Use POOL for any necessary allocation. */ +static const char * +utf8_or_nothing(const char *str, apr_pool_t *pool) { + if (str) + { + const char *utf8_str; + svn_error_t *err = svn_utf_cstring_to_utf8(&utf8_str, str, pool); + if (! err) + return utf8_str; + svn_error_clear(err); + } + return NULL; +} + +const char * +svn_user_get_name(apr_pool_t *pool) +{ + const char *username = get_os_username(pool); + return utf8_or_nothing(username, pool); +} + +const char * +svn_user_get_homedir(apr_pool_t *pool) +{ + const char *username; + char *homedir; + + if (apr_env_get(&homedir, "HOME", pool) == APR_SUCCESS) + return utf8_or_nothing(homedir, pool); + + username = get_os_username(pool); + if (username != NULL && + apr_uid_homepath_get(&homedir, username, pool) == APR_SUCCESS) + return utf8_or_nothing(homedir, pool); + + return NULL; +} diff --git a/subversion/libsvn_subr/username_providers.c b/subversion/libsvn_subr/username_providers.c new file mode 100644 index 000000000000..a6ef5b3ea20d --- /dev/null +++ b/subversion/libsvn_subr/username_providers.c @@ -0,0 +1,306 @@ +/* + * username_providers.c: providers for SVN_AUTH_CRED_USERNAME + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <apr_pools.h> +#include "svn_hash.h" +#include "svn_auth.h" +#include "svn_error.h" +#include "svn_utf.h" +#include "svn_config.h" +#include "svn_user.h" + + +/*-----------------------------------------------------------------------*/ +/* File provider */ +/*-----------------------------------------------------------------------*/ + +/* The key that will be stored on disk. Serves the same role as similar + constants in other providers. */ +#define AUTHN_USERNAME_KEY "username" + + + +/*** Username-only Provider ***/ +static svn_error_t * +username_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + const char *config_dir = svn_hash_gets(parameters, + SVN_AUTH_PARAM_CONFIG_DIR); + const char *username = svn_hash_gets(parameters, + SVN_AUTH_PARAM_DEFAULT_USERNAME); + svn_boolean_t may_save = !! username; + svn_error_t *err; + + /* If we don't have a usename yet, try the auth cache */ + if (! username) + { + apr_hash_t *creds_hash = NULL; + + /* Try to load credentials from a file on disk, based on the + realmstring. Don't throw an error, though: if something went + wrong reading the file, no big deal. What really matters is that + we failed to get the creds, so allow the auth system to try the + next provider. */ + err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_USERNAME, + realmstring, config_dir, pool); + svn_error_clear(err); + if (! err && creds_hash) + { + svn_string_t *str = svn_hash_gets(creds_hash, AUTHN_USERNAME_KEY); + if (str && str->data) + username = str->data; + } + } + + /* If that failed, ask the OS for the username */ + if (! username) + username = svn_user_get_name(pool); + + if (username) + { + svn_auth_cred_simple_t *creds = apr_pcalloc(pool, sizeof(*creds)); + creds->username = username; + creds->may_save = may_save; + *credentials = creds; + } + else + *credentials = NULL; + + *iter_baton = NULL; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +username_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *creds = credentials; + apr_hash_t *creds_hash = NULL; + const char *config_dir; + svn_error_t *err; + + *saved = FALSE; + + if (! creds->may_save) + return SVN_NO_ERROR; + + config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); + + /* Put the credentials in a hash and save it to disk */ + creds_hash = apr_hash_make(pool); + svn_hash_sets(creds_hash, AUTHN_USERNAME_KEY, + svn_string_create(creds->username, pool)); + err = svn_config_write_auth_data(creds_hash, SVN_AUTH_CRED_USERNAME, + realmstring, config_dir, pool); + svn_error_clear(err); + *saved = ! err; + + return SVN_NO_ERROR; +} + + +static const svn_auth_provider_t username_provider = { + SVN_AUTH_CRED_USERNAME, + username_first_creds, + NULL, + username_save_creds +}; + + +/* Public API */ +void +svn_auth_get_username_provider(svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &username_provider; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Prompt provider */ +/*-----------------------------------------------------------------------*/ + +/* Baton type for username-only prompting. */ +typedef struct username_prompt_provider_baton_t +{ + svn_auth_username_prompt_func_t prompt_func; + void *prompt_baton; + + /* how many times to re-prompt after the first one fails */ + int retry_limit; +} username_prompt_provider_baton_t; + + +/* Iteration baton type for username-only prompting. */ +typedef struct username_prompt_iter_baton_t +{ + /* how many times we've reprompted */ + int retries; + +} username_prompt_iter_baton_t; + + +/*** Helper Functions ***/ +static svn_error_t * +prompt_for_username_creds(svn_auth_cred_username_t **cred_p, + username_prompt_provider_baton_t *pb, + apr_hash_t *parameters, + const char *realmstring, + svn_boolean_t first_time, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + const char *def_username = NULL; + + *cred_p = NULL; + + /* If we're allowed to check for default usernames, do so. */ + if (first_time) + def_username = svn_hash_gets(parameters, SVN_AUTH_PARAM_DEFAULT_USERNAME); + + /* If we have defaults, just build the cred here and return it. + * + * ### I do wonder why this is here instead of in a separate + * ### 'defaults' provider that would run before the prompt + * ### provider... Hmmm. + */ + if (def_username) + { + *cred_p = apr_palloc(pool, sizeof(**cred_p)); + (*cred_p)->username = apr_pstrdup(pool, def_username); + (*cred_p)->may_save = TRUE; + } + else + { + SVN_ERR(pb->prompt_func(cred_p, pb->prompt_baton, realmstring, + may_save, pool)); + } + + return SVN_NO_ERROR; +} + + +/* Our first attempt will use any default username passed + in, and prompt for the remaining stuff. */ +static svn_error_t * +username_prompt_first_creds(void **credentials_p, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + username_prompt_provider_baton_t *pb = provider_baton; + username_prompt_iter_baton_t *ibaton = apr_pcalloc(pool, sizeof(*ibaton)); + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + SVN_ERR(prompt_for_username_creds + ((svn_auth_cred_username_t **) credentials_p, pb, + parameters, realmstring, TRUE, ! no_auth_cache, pool)); + + ibaton->retries = 0; + *iter_baton = ibaton; + + return SVN_NO_ERROR; +} + + +/* Subsequent attempts to fetch will ignore the default username + value, and simply re-prompt for the username, up to a maximum of + ib->pb->retry_limit. */ +static svn_error_t * +username_prompt_next_creds(void **credentials_p, + void *iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + username_prompt_iter_baton_t *ib = iter_baton; + username_prompt_provider_baton_t *pb = provider_baton; + const char *no_auth_cache = svn_hash_gets(parameters, + SVN_AUTH_PARAM_NO_AUTH_CACHE); + + if ((pb->retry_limit >= 0) && (ib->retries >= pb->retry_limit)) + { + /* give up, go on to next provider. */ + *credentials_p = NULL; + return SVN_NO_ERROR; + } + ib->retries++; + + return prompt_for_username_creds + ((svn_auth_cred_username_t **) credentials_p, pb, + parameters, realmstring, FALSE, ! no_auth_cache, pool); +} + + +static const svn_auth_provider_t username_prompt_provider = { + SVN_AUTH_CRED_USERNAME, + username_prompt_first_creds, + username_prompt_next_creds, + NULL, +}; + + +/* Public API */ +void +svn_auth_get_username_prompt_provider + (svn_auth_provider_object_t **provider, + svn_auth_username_prompt_func_t prompt_func, + void *prompt_baton, + int retry_limit, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + username_prompt_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb)); + + pb->prompt_func = prompt_func; + pb->prompt_baton = prompt_baton; + pb->retry_limit = retry_limit; + + po->vtable = &username_prompt_provider; + po->provider_baton = pb; + *provider = po; +} diff --git a/subversion/libsvn_subr/utf.c b/subversion/libsvn_subr/utf.c new file mode 100644 index 000000000000..355e068f5e55 --- /dev/null +++ b/subversion/libsvn_subr/utf.c @@ -0,0 +1,1075 @@ +/* + * utf.c: UTF-8 conversion routines + * + * ==================================================================== + * 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 <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <apr_strings.h> +#include <apr_lib.h> +#include <apr_xlate.h> +#include <apr_atomic.h> + +#include "svn_hash.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_ctype.h" +#include "svn_utf.h" +#include "svn_private_config.h" +#include "win32_xlate.h" + +#include "private/svn_utf_private.h" +#include "private/svn_dep_compat.h" +#include "private/svn_string_private.h" +#include "private/svn_mutex.h" + + + +/* Use these static strings to maximize performance on standard conversions. + * Any strings on other locations are still valid, however. + */ +static const char *SVN_UTF_NTOU_XLATE_HANDLE = "svn-utf-ntou-xlate-handle"; +static const char *SVN_UTF_UTON_XLATE_HANDLE = "svn-utf-uton-xlate-handle"; + +static const char *SVN_APR_UTF8_CHARSET = "UTF-8"; + +static svn_mutex__t *xlate_handle_mutex = NULL; +static svn_boolean_t assume_native_charset_is_utf8 = FALSE; + +/* The xlate handle cache is a global hash table with linked lists of xlate + * handles. In multi-threaded environments, a thread "borrows" an xlate + * handle from the cache during a translation and puts it back afterwards. + * This avoids holding a global lock for all translations. + * If there is no handle for a particular key when needed, a new is + * handle is created and put in the cache after use. + * This means that there will be at most N handles open for a key, where N + * is the number of simultanous handles in use for that key. */ + +typedef struct xlate_handle_node_t { + apr_xlate_t *handle; + /* FALSE if the handle is not valid, since its pool is being + destroyed. */ + svn_boolean_t valid; + /* The name of a char encoding or APR_LOCALE_CHARSET. */ + const char *frompage, *topage; + struct xlate_handle_node_t *next; +} xlate_handle_node_t; + +/* This maps const char * userdata_key strings to xlate_handle_node_t ** + handles to the first entry in the linked list of xlate handles. We don't + store the pointer to the list head directly in the hash table, since we + remove/insert entries at the head in the list in the code below, and + we can't use apr_hash_set() in each character translation because that + function allocates memory in each call where the value is non-NULL. + Since these allocations take place in a global pool, this would be a + memory leak. */ +static apr_hash_t *xlate_handle_hash = NULL; + +/* "1st level cache" to standard conversion maps. We may access these + * using atomic xchange ops, i.e. without further thread synchronization. + * If the respective item is NULL, fallback to hash lookup. + */ +static void * volatile xlat_ntou_static_handle = NULL; +static void * volatile xlat_uton_static_handle = NULL; + +/* Clean up the xlate handle cache. */ +static apr_status_t +xlate_cleanup(void *arg) +{ + /* We set the cache variables to NULL so that translation works in other + cleanup functions, even if it isn't cached then. */ + xlate_handle_hash = NULL; + + /* ensure no stale objects get accessed */ + xlat_ntou_static_handle = NULL; + xlat_uton_static_handle = NULL; + + return APR_SUCCESS; +} + +/* Set the handle of ARG to NULL. */ +static apr_status_t +xlate_handle_node_cleanup(void *arg) +{ + xlate_handle_node_t *node = arg; + + node->valid = FALSE; + return APR_SUCCESS; +} + +void +svn_utf_initialize2(svn_boolean_t assume_native_utf8, + apr_pool_t *pool) +{ + if (!xlate_handle_hash) + { + /* We create our own subpool, which we protect with the mutex. + We can't use the pool passed to us by the caller, since we will + use it for xlate handle allocations, possibly in multiple threads, + and pool allocation is not thread-safe. */ + apr_pool_t *subpool = svn_pool_create(pool); + svn_mutex__t *mutex; + svn_error_t *err = svn_mutex__init(&mutex, TRUE, subpool); + if (err) + { + svn_error_clear(err); + return; + } + + xlate_handle_mutex = mutex; + xlate_handle_hash = apr_hash_make(subpool); + + apr_pool_cleanup_register(subpool, NULL, xlate_cleanup, + apr_pool_cleanup_null); + } + + if (!assume_native_charset_is_utf8) + assume_native_charset_is_utf8 = assume_native_utf8; +} + +/* Return a unique string key based on TOPAGE and FROMPAGE. TOPAGE and + * FROMPAGE can be any valid arguments of the same name to + * apr_xlate_open(). Allocate the returned string in POOL. */ +static const char* +get_xlate_key(const char *topage, + const char *frompage, + apr_pool_t *pool) +{ + /* In the cases of SVN_APR_LOCALE_CHARSET and SVN_APR_DEFAULT_CHARSET + * topage/frompage is really an int, not a valid string. So generate a + * unique key accordingly. */ + if (frompage == SVN_APR_LOCALE_CHARSET) + frompage = "APR_LOCALE_CHARSET"; + else if (frompage == SVN_APR_DEFAULT_CHARSET) + frompage = "APR_DEFAULT_CHARSET"; + + if (topage == SVN_APR_LOCALE_CHARSET) + topage = "APR_LOCALE_CHARSET"; + else if (topage == SVN_APR_DEFAULT_CHARSET) + topage = "APR_DEFAULT_CHARSET"; + + return apr_pstrcat(pool, "svn-utf-", frompage, "to", topage, + "-xlate-handle", (char *)NULL); +} + +/* Atomically replace the content in *MEM with NEW_VALUE and return + * the previous content of *MEM. If atomicy cannot be guaranteed, + * *MEM will not be modified and NEW_VALUE is simply returned to + * the caller. + */ +static APR_INLINE void* +atomic_swap(void * volatile * mem, void *new_value) +{ +#if APR_HAS_THREADS +#if APR_VERSION_AT_LEAST(1,3,0) + /* Cast is necessary because of APR bug: + https://issues.apache.org/bugzilla/show_bug.cgi?id=50731 */ + return apr_atomic_xchgptr((volatile void **)mem, new_value); +#else + /* old APRs don't support atomic swaps. Simply return the + * input to the caller for further proccessing. */ + return new_value; +#endif +#else + /* no threads - no sync. necessary */ + void *old_value = (void*)*mem; + *mem = new_value; + return old_value; +#endif +} + +/* Set *RET to a newly created handle node for converting from FROMPAGE + to TOPAGE, If apr_xlate_open() returns APR_EINVAL or APR_ENOTIMPL, set + (*RET)->handle to NULL. If fail for any other reason, return the error. + Allocate *RET and its xlate handle in POOL. */ +static svn_error_t * +xlate_alloc_handle(xlate_handle_node_t **ret, + const char *topage, const char *frompage, + apr_pool_t *pool) +{ + apr_status_t apr_err; + apr_xlate_t *handle; + + /* The error handling doesn't support the following cases, since we don't + use them currently. Catch this here. */ + SVN_ERR_ASSERT(frompage != SVN_APR_DEFAULT_CHARSET + && topage != SVN_APR_DEFAULT_CHARSET + && (frompage != SVN_APR_LOCALE_CHARSET + || topage != SVN_APR_LOCALE_CHARSET)); + + /* Try to create a handle. */ +#if defined(WIN32) + apr_err = svn_subr__win32_xlate_open((win32_xlate_t **)&handle, topage, + frompage, pool); +#else + apr_err = apr_xlate_open(&handle, topage, frompage, pool); +#endif + + if (APR_STATUS_IS_EINVAL(apr_err) || APR_STATUS_IS_ENOTIMPL(apr_err)) + handle = NULL; + else if (apr_err != APR_SUCCESS) + { + const char *errstr; + /* Can't use svn_error_wrap_apr here because it calls functions in + this file, leading to infinite recursion. */ + if (frompage == SVN_APR_LOCALE_CHARSET) + errstr = apr_psprintf(pool, + _("Can't create a character converter from " + "native encoding to '%s'"), topage); + else if (topage == SVN_APR_LOCALE_CHARSET) + errstr = apr_psprintf(pool, + _("Can't create a character converter from " + "'%s' to native encoding"), frompage); + else + errstr = apr_psprintf(pool, + _("Can't create a character converter from " + "'%s' to '%s'"), frompage, topage); + + return svn_error_create(apr_err, NULL, errstr); + } + + /* Allocate and initialize the node. */ + *ret = apr_palloc(pool, sizeof(xlate_handle_node_t)); + (*ret)->handle = handle; + (*ret)->valid = TRUE; + (*ret)->frompage = ((frompage != SVN_APR_LOCALE_CHARSET) + ? apr_pstrdup(pool, frompage) : frompage); + (*ret)->topage = ((topage != SVN_APR_LOCALE_CHARSET) + ? apr_pstrdup(pool, topage) : topage); + (*ret)->next = NULL; + + /* If we are called from inside a pool cleanup handler, the just created + xlate handle will be closed when that handler returns by a newly + registered cleanup handler, however, the handle is still cached by us. + To prevent this, we register a cleanup handler that will reset the valid + flag of our node, so we don't use an invalid handle. */ + if (handle) + apr_pool_cleanup_register(pool, *ret, xlate_handle_node_cleanup, + apr_pool_cleanup_null); + + return SVN_NO_ERROR; +} + +/* Extend xlate_alloc_handle by using USERDATA_KEY as a key in our + global hash map, if available. + + Allocate *RET and its xlate handle in POOL if svn_utf_initialize() + hasn't been called or USERDATA_KEY is NULL. Else, allocate them + in the pool of xlate_handle_hash. + + Note: this function is not thread-safe. Call get_xlate_handle_node + instead. */ +static svn_error_t * +get_xlate_handle_node_internal(xlate_handle_node_t **ret, + const char *topage, const char *frompage, + const char *userdata_key, apr_pool_t *pool) +{ + /* If we already have a handle, just return it. */ + if (userdata_key && xlate_handle_hash) + { + xlate_handle_node_t *old_node = NULL; + + /* 2nd level: hash lookup */ + xlate_handle_node_t **old_node_p = svn_hash_gets(xlate_handle_hash, + userdata_key); + if (old_node_p) + old_node = *old_node_p; + if (old_node) + { + /* Ensure that the handle is still valid. */ + if (old_node->valid) + { + /* Remove from the list. */ + *old_node_p = old_node->next; + old_node->next = NULL; + *ret = old_node; + return SVN_NO_ERROR; + } + } + } + + /* Note that we still have the mutex locked (if it is initialized), so we + can use the global pool for creating the new xlate handle. */ + + /* Use the correct pool for creating the handle. */ + pool = apr_hash_pool_get(xlate_handle_hash); + + return xlate_alloc_handle(ret, topage, frompage, pool); +} + +/* Set *RET to a handle node for converting from FROMPAGE to TOPAGE, + creating the handle node if it doesn't exist in USERDATA_KEY. + If a node is not cached and apr_xlate_open() returns APR_EINVAL or + APR_ENOTIMPL, set (*RET)->handle to NULL. If fail for any other + reason, return the error. + + Allocate *RET and its xlate handle in POOL if svn_utf_initialize() + hasn't been called or USERDATA_KEY is NULL. Else, allocate them + in the pool of xlate_handle_hash. */ +static svn_error_t * +get_xlate_handle_node(xlate_handle_node_t **ret, + const char *topage, const char *frompage, + const char *userdata_key, apr_pool_t *pool) +{ + xlate_handle_node_t *old_node = NULL; + + /* If we already have a handle, just return it. */ + if (userdata_key) + { + if (xlate_handle_hash) + { + /* 1st level: global, static items */ + if (userdata_key == SVN_UTF_NTOU_XLATE_HANDLE) + old_node = atomic_swap(&xlat_ntou_static_handle, NULL); + else if (userdata_key == SVN_UTF_UTON_XLATE_HANDLE) + old_node = atomic_swap(&xlat_uton_static_handle, NULL); + + if (old_node && old_node->valid) + { + *ret = old_node; + return SVN_NO_ERROR; + } + } + else + { + void *p; + /* We fall back on a per-pool cache instead. */ + apr_pool_userdata_get(&p, userdata_key, pool); + old_node = p; + /* Ensure that the handle is still valid. */ + if (old_node && old_node->valid) + { + *ret = old_node; + return SVN_NO_ERROR; + } + + return xlate_alloc_handle(ret, topage, frompage, pool); + } + } + + SVN_MUTEX__WITH_LOCK(xlate_handle_mutex, + get_xlate_handle_node_internal(ret, + topage, + frompage, + userdata_key, + pool)); + + return SVN_NO_ERROR; +} + +/* Put back NODE into the xlate handle cache for use by other calls. + + Note: this function is not thread-safe. Call put_xlate_handle_node + instead. */ +static svn_error_t * +put_xlate_handle_node_internal(xlate_handle_node_t *node, + const char *userdata_key) +{ + xlate_handle_node_t **node_p = svn_hash_gets(xlate_handle_hash, userdata_key); + if (node_p == NULL) + { + userdata_key = apr_pstrdup(apr_hash_pool_get(xlate_handle_hash), + userdata_key); + node_p = apr_palloc(apr_hash_pool_get(xlate_handle_hash), + sizeof(*node_p)); + *node_p = NULL; + svn_hash_sets(xlate_handle_hash, userdata_key, node_p); + } + node->next = *node_p; + *node_p = node; + + return SVN_NO_ERROR; +} + +/* Put back NODE into the xlate handle cache for use by other calls. + If there is no global cache, store the handle in POOL. + Ignore errors related to locking/unlocking the mutex. */ +static svn_error_t * +put_xlate_handle_node(xlate_handle_node_t *node, + const char *userdata_key, + apr_pool_t *pool) +{ + assert(node->next == NULL); + if (!userdata_key) + return SVN_NO_ERROR; + + /* push previous global node to the hash */ + if (xlate_handle_hash) + { + /* 1st level: global, static items */ + if (userdata_key == SVN_UTF_NTOU_XLATE_HANDLE) + node = atomic_swap(&xlat_ntou_static_handle, node); + else if (userdata_key == SVN_UTF_UTON_XLATE_HANDLE) + node = atomic_swap(&xlat_uton_static_handle, node); + if (node == NULL) + return SVN_NO_ERROR; + + SVN_MUTEX__WITH_LOCK(xlate_handle_mutex, + put_xlate_handle_node_internal(node, + userdata_key)); + } + else + { + /* Store it in the per-pool cache. */ + apr_pool_userdata_set(node, userdata_key, apr_pool_cleanup_null, pool); + } + + return SVN_NO_ERROR; +} + +/* Return the apr_xlate handle for converting native characters to UTF-8. */ +static svn_error_t * +get_ntou_xlate_handle_node(xlate_handle_node_t **ret, apr_pool_t *pool) +{ + return get_xlate_handle_node(ret, SVN_APR_UTF8_CHARSET, + assume_native_charset_is_utf8 + ? SVN_APR_UTF8_CHARSET + : SVN_APR_LOCALE_CHARSET, + SVN_UTF_NTOU_XLATE_HANDLE, pool); +} + + +/* Return the apr_xlate handle for converting UTF-8 to native characters. + Create one if it doesn't exist. If unable to find a handle, or + unable to create one because apr_xlate_open returned APR_EINVAL, then + set *RET to null and return SVN_NO_ERROR; if fail for some other + reason, return error. */ +static svn_error_t * +get_uton_xlate_handle_node(xlate_handle_node_t **ret, apr_pool_t *pool) +{ + return get_xlate_handle_node(ret, + assume_native_charset_is_utf8 + ? SVN_APR_UTF8_CHARSET + : SVN_APR_LOCALE_CHARSET, + SVN_APR_UTF8_CHARSET, + SVN_UTF_UTON_XLATE_HANDLE, pool); +} + + +/* Copy LEN bytes of SRC, converting non-ASCII and zero bytes to ?\nnn + sequences, allocating the result in POOL. */ +static const char * +fuzzy_escape(const char *src, apr_size_t len, apr_pool_t *pool) +{ + const char *src_orig = src, *src_end = src + len; + apr_size_t new_len = 0; + char *new; + const char *new_orig; + + /* First count how big a dest string we'll need. */ + while (src < src_end) + { + if (! svn_ctype_isascii(*src) || *src == '\0') + new_len += 5; /* 5 slots, for "?\XXX" */ + else + new_len += 1; /* one slot for the 7-bit char */ + + src++; + } + + /* Allocate that amount, plus one slot for '\0' character. */ + new = apr_palloc(pool, new_len + 1); + + new_orig = new; + + /* And fill it up. */ + while (src_orig < src_end) + { + if (! svn_ctype_isascii(*src_orig) || src_orig == '\0') + { + /* This is the same format as svn_xml_fuzzy_escape uses, but that + function escapes different characters. Please keep in sync! + ### If we add another fuzzy escape somewhere, we should abstract + ### this out to a common function. */ + apr_snprintf(new, 6, "?\\%03u", (unsigned char) *src_orig); + new += 5; + } + else + { + *new = *src_orig; + new += 1; + } + + src_orig++; + } + + *new = '\0'; + + return new_orig; +} + +/* Convert SRC_LENGTH bytes of SRC_DATA in NODE->handle, store the result + in *DEST, which is allocated in POOL. */ +static svn_error_t * +convert_to_stringbuf(xlate_handle_node_t *node, + const char *src_data, + apr_size_t src_length, + svn_stringbuf_t **dest, + apr_pool_t *pool) +{ +#ifdef WIN32 + apr_status_t apr_err; + + apr_err = svn_subr__win32_xlate_to_stringbuf((win32_xlate_t *) node->handle, + src_data, src_length, + dest, pool); +#else + apr_size_t buflen = src_length * 2; + apr_status_t apr_err; + apr_size_t srclen = src_length; + apr_size_t destlen = buflen; + + /* Initialize *DEST to an empty stringbuf. + A 1:2 ratio of input bytes to output bytes (as assigned above) + should be enough for most translations, and if it turns out not + to be enough, we'll grow the buffer again, sizing it based on a + 1:3 ratio of the remainder of the string. */ + *dest = svn_stringbuf_create_ensure(buflen + 1, pool); + + /* Not only does it not make sense to convert an empty string, but + apr-iconv is quite unreasonable about not allowing that. */ + if (src_length == 0) + return SVN_NO_ERROR; + + do + { + /* Set up state variables for xlate. */ + destlen = buflen - (*dest)->len; + + /* Attempt the conversion. */ + apr_err = apr_xlate_conv_buffer(node->handle, + src_data + (src_length - srclen), + &srclen, + (*dest)->data + (*dest)->len, + &destlen); + + /* Now, update the *DEST->len to track the amount of output data + churned out so far from this loop. */ + (*dest)->len += ((buflen - (*dest)->len) - destlen); + buflen += srclen * 3; /* 3 is middle ground, 2 wasn't enough + for all characters in the buffer, 4 is + maximum character size (currently) */ + + + } while (apr_err == APR_SUCCESS && srclen != 0); +#endif + + /* If we exited the loop with an error, return the error. */ + if (apr_err) + { + const char *errstr; + svn_error_t *err; + + /* Can't use svn_error_wrap_apr here because it calls functions in + this file, leading to infinite recursion. */ + if (node->frompage == SVN_APR_LOCALE_CHARSET) + errstr = apr_psprintf + (pool, _("Can't convert string from native encoding to '%s':"), + node->topage); + else if (node->topage == SVN_APR_LOCALE_CHARSET) + errstr = apr_psprintf + (pool, _("Can't convert string from '%s' to native encoding:"), + node->frompage); + else + errstr = apr_psprintf + (pool, _("Can't convert string from '%s' to '%s':"), + node->frompage, node->topage); + + err = svn_error_create(apr_err, NULL, fuzzy_escape(src_data, + src_length, pool)); + return svn_error_create(apr_err, err, errstr); + } + /* Else, exited due to success. Trim the result buffer down to the + right length. */ + (*dest)->data[(*dest)->len] = '\0'; + + return SVN_NO_ERROR; +} + + +/* Return APR_EINVAL if the first LEN bytes of DATA contain anything + other than seven-bit, non-control (except for whitespace) ASCII + characters, finding the error pool from POOL. Otherwise, return + SVN_NO_ERROR. */ +static svn_error_t * +check_non_ascii(const char *data, apr_size_t len, apr_pool_t *pool) +{ + const char *data_start = data; + + for (; len > 0; --len, data++) + { + if ((! svn_ctype_isascii(*data)) + || ((! svn_ctype_isspace(*data)) + && svn_ctype_iscntrl(*data))) + { + /* Show the printable part of the data, followed by the + decimal code of the questionable character. Because if a + user ever gets this error, she's going to have to spend + time tracking down the non-ASCII data, so we want to help + as much as possible. And yes, we just call the unsafe + data "non-ASCII", even though the actual constraint is + somewhat more complex than that. */ + + if (data - data_start) + { + const char *error_data + = apr_pstrndup(pool, data_start, (data - data_start)); + + return svn_error_createf + (APR_EINVAL, NULL, + _("Safe data '%s' was followed by non-ASCII byte %d: " + "unable to convert to/from UTF-8"), + error_data, *((const unsigned char *) data)); + } + else + { + return svn_error_createf + (APR_EINVAL, NULL, + _("Non-ASCII character (code %d) detected, " + "and unable to convert to/from UTF-8"), + *((const unsigned char *) data)); + } + } + } + + return SVN_NO_ERROR; +} + +/* Construct an error with code APR_EINVAL and with a suitable message + * to describe the invalid UTF-8 sequence DATA of length LEN (which + * may have embedded NULLs). We can't simply print the data, almost + * by definition we don't really know how it is encoded. + */ +static svn_error_t * +invalid_utf8(const char *data, apr_size_t len, apr_pool_t *pool) +{ + const char *last = svn_utf__last_valid(data, len); + const char *valid_txt = "", *invalid_txt = ""; + apr_size_t i; + size_t valid, invalid; + + /* We will display at most 24 valid octets (this may split a leading + multi-byte character) as that should fit on one 80 character line. */ + valid = last - data; + if (valid > 24) + valid = 24; + for (i = 0; i < valid; ++i) + valid_txt = apr_pstrcat(pool, valid_txt, + apr_psprintf(pool, " %02x", + (unsigned char)last[i-valid]), + (char *)NULL); + + /* 4 invalid octets will guarantee that the faulty octet is displayed */ + invalid = data + len - last; + if (invalid > 4) + invalid = 4; + for (i = 0; i < invalid; ++i) + invalid_txt = apr_pstrcat(pool, invalid_txt, + apr_psprintf(pool, " %02x", + (unsigned char)last[i]), + (char *)NULL); + + return svn_error_createf(APR_EINVAL, NULL, + _("Valid UTF-8 data\n(hex:%s)\n" + "followed by invalid UTF-8 sequence\n(hex:%s)"), + valid_txt, invalid_txt); +} + +/* Verify that the sequence DATA of length LEN is valid UTF-8. + If it is not, return an error with code APR_EINVAL. */ +static svn_error_t * +check_utf8(const char *data, apr_size_t len, apr_pool_t *pool) +{ + if (! svn_utf__is_valid(data, len)) + return invalid_utf8(data, len, pool); + return SVN_NO_ERROR; +} + +/* Verify that the NULL terminated sequence DATA is valid UTF-8. + If it is not, return an error with code APR_EINVAL. */ +static svn_error_t * +check_cstring_utf8(const char *data, apr_pool_t *pool) +{ + + if (! svn_utf__cstring_is_valid(data)) + return invalid_utf8(data, strlen(data), pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_utf_stringbuf_to_utf8(svn_stringbuf_t **dest, + const svn_stringbuf_t *src, + apr_pool_t *pool) +{ + xlate_handle_node_t *node; + svn_error_t *err; + + SVN_ERR(get_ntou_xlate_handle_node(&node, pool)); + + if (node->handle) + { + err = convert_to_stringbuf(node, src->data, src->len, dest, pool); + if (! err) + err = check_utf8((*dest)->data, (*dest)->len, pool); + } + else + { + err = check_non_ascii(src->data, src->len, pool); + if (! err) + *dest = svn_stringbuf_dup(src, pool); + } + + return svn_error_compose_create(err, + put_xlate_handle_node + (node, + SVN_UTF_NTOU_XLATE_HANDLE, + pool)); +} + + +svn_error_t * +svn_utf_string_to_utf8(const svn_string_t **dest, + const svn_string_t *src, + apr_pool_t *pool) +{ + svn_stringbuf_t *destbuf; + xlate_handle_node_t *node; + svn_error_t *err; + + SVN_ERR(get_ntou_xlate_handle_node(&node, pool)); + + if (node->handle) + { + err = convert_to_stringbuf(node, src->data, src->len, &destbuf, pool); + if (! err) + err = check_utf8(destbuf->data, destbuf->len, pool); + if (! err) + *dest = svn_stringbuf__morph_into_string(destbuf); + } + else + { + err = check_non_ascii(src->data, src->len, pool); + if (! err) + *dest = svn_string_dup(src, pool); + } + + return svn_error_compose_create(err, + put_xlate_handle_node + (node, + SVN_UTF_NTOU_XLATE_HANDLE, + pool)); +} + + +/* Common implementation for svn_utf_cstring_to_utf8, + svn_utf_cstring_to_utf8_ex, svn_utf_cstring_from_utf8 and + svn_utf_cstring_from_utf8_ex. Convert SRC to DEST using NODE->handle as + the translator and allocating from POOL. */ +static svn_error_t * +convert_cstring(const char **dest, + const char *src, + xlate_handle_node_t *node, + apr_pool_t *pool) +{ + if (node->handle) + { + svn_stringbuf_t *destbuf; + SVN_ERR(convert_to_stringbuf(node, src, strlen(src), + &destbuf, pool)); + *dest = destbuf->data; + } + else + { + apr_size_t len = strlen(src); + SVN_ERR(check_non_ascii(src, len, pool)); + *dest = apr_pstrmemdup(pool, src, len); + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_utf_cstring_to_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + xlate_handle_node_t *node; + svn_error_t *err; + + SVN_ERR(get_ntou_xlate_handle_node(&node, pool)); + err = convert_cstring(dest, src, node, pool); + SVN_ERR(svn_error_compose_create(err, + put_xlate_handle_node + (node, + SVN_UTF_NTOU_XLATE_HANDLE, + pool))); + return check_cstring_utf8(*dest, pool); +} + + +svn_error_t * +svn_utf_cstring_to_utf8_ex2(const char **dest, + const char *src, + const char *frompage, + apr_pool_t *pool) +{ + xlate_handle_node_t *node; + svn_error_t *err; + const char *convset_key = get_xlate_key(SVN_APR_UTF8_CHARSET, frompage, + pool); + + SVN_ERR(get_xlate_handle_node(&node, SVN_APR_UTF8_CHARSET, frompage, + convset_key, pool)); + err = convert_cstring(dest, src, node, pool); + SVN_ERR(svn_error_compose_create(err, + put_xlate_handle_node + (node, + SVN_UTF_NTOU_XLATE_HANDLE, + pool))); + + return check_cstring_utf8(*dest, pool); +} + + +svn_error_t * +svn_utf_cstring_to_utf8_ex(const char **dest, + const char *src, + const char *frompage, + const char *convset_key, + apr_pool_t *pool) +{ + return svn_utf_cstring_to_utf8_ex2(dest, src, frompage, pool); +} + + +svn_error_t * +svn_utf_stringbuf_from_utf8(svn_stringbuf_t **dest, + const svn_stringbuf_t *src, + apr_pool_t *pool) +{ + xlate_handle_node_t *node; + svn_error_t *err; + + SVN_ERR(get_uton_xlate_handle_node(&node, pool)); + + if (node->handle) + { + err = check_utf8(src->data, src->len, pool); + if (! err) + err = convert_to_stringbuf(node, src->data, src->len, dest, pool); + } + else + { + err = check_non_ascii(src->data, src->len, pool); + if (! err) + *dest = svn_stringbuf_dup(src, pool); + } + + err = svn_error_compose_create( + err, + put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); + + return err; +} + + +svn_error_t * +svn_utf_string_from_utf8(const svn_string_t **dest, + const svn_string_t *src, + apr_pool_t *pool) +{ + svn_stringbuf_t *dbuf; + xlate_handle_node_t *node; + svn_error_t *err; + + SVN_ERR(get_uton_xlate_handle_node(&node, pool)); + + if (node->handle) + { + err = check_utf8(src->data, src->len, pool); + if (! err) + err = convert_to_stringbuf(node, src->data, src->len, + &dbuf, pool); + if (! err) + *dest = svn_stringbuf__morph_into_string(dbuf); + } + else + { + err = check_non_ascii(src->data, src->len, pool); + if (! err) + *dest = svn_string_dup(src, pool); + } + + err = svn_error_compose_create( + err, + put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); + + return err; +} + + +svn_error_t * +svn_utf_cstring_from_utf8(const char **dest, + const char *src, + apr_pool_t *pool) +{ + xlate_handle_node_t *node; + svn_error_t *err; + + SVN_ERR(check_cstring_utf8(src, pool)); + + SVN_ERR(get_uton_xlate_handle_node(&node, pool)); + err = convert_cstring(dest, src, node, pool); + err = svn_error_compose_create( + err, + put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); + + return err; +} + + +svn_error_t * +svn_utf_cstring_from_utf8_ex2(const char **dest, + const char *src, + const char *topage, + apr_pool_t *pool) +{ + xlate_handle_node_t *node; + svn_error_t *err; + const char *convset_key = get_xlate_key(topage, SVN_APR_UTF8_CHARSET, + pool); + + SVN_ERR(check_cstring_utf8(src, pool)); + + SVN_ERR(get_xlate_handle_node(&node, topage, SVN_APR_UTF8_CHARSET, + convset_key, pool)); + err = convert_cstring(dest, src, node, pool); + err = svn_error_compose_create( + err, + put_xlate_handle_node(node, convset_key, pool)); + + return err; +} + + +svn_error_t * +svn_utf_cstring_from_utf8_ex(const char **dest, + const char *src, + const char *topage, + const char *convset_key, + apr_pool_t *pool) +{ + return svn_utf_cstring_from_utf8_ex2(dest, src, topage, pool); +} + + +const char * +svn_utf__cstring_from_utf8_fuzzy(const char *src, + apr_pool_t *pool, + svn_error_t *(*convert_from_utf8) + (const char **, const char *, apr_pool_t *)) +{ + const char *escaped, *converted; + svn_error_t *err; + + escaped = fuzzy_escape(src, strlen(src), pool); + + /* Okay, now we have a *new* UTF-8 string, one that's guaranteed to + contain only 7-bit bytes :-). Recode to native... */ + err = convert_from_utf8(((const char **) &converted), escaped, pool); + + if (err) + { + svn_error_clear(err); + return escaped; + } + else + return converted; + + /* ### Check the client locale, maybe we can avoid that second + * conversion! See Ulrich Drepper's patch at + * http://subversion.tigris.org/issues/show_bug.cgi?id=807. + */ +} + + +const char * +svn_utf_cstring_from_utf8_fuzzy(const char *src, + apr_pool_t *pool) +{ + return svn_utf__cstring_from_utf8_fuzzy(src, pool, + svn_utf_cstring_from_utf8); +} + + +svn_error_t * +svn_utf_cstring_from_utf8_stringbuf(const char **dest, + const svn_stringbuf_t *src, + apr_pool_t *pool) +{ + svn_stringbuf_t *destbuf; + + SVN_ERR(svn_utf_stringbuf_from_utf8(&destbuf, src, pool)); + *dest = destbuf->data; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_utf_cstring_from_utf8_string(const char **dest, + const svn_string_t *src, + apr_pool_t *pool) +{ + svn_stringbuf_t *dbuf; + xlate_handle_node_t *node; + svn_error_t *err; + + SVN_ERR(get_uton_xlate_handle_node(&node, pool)); + + if (node->handle) + { + err = check_utf8(src->data, src->len, pool); + if (! err) + err = convert_to_stringbuf(node, src->data, src->len, + &dbuf, pool); + if (! err) + *dest = dbuf->data; + } + else + { + err = check_non_ascii(src->data, src->len, pool); + if (! err) + *dest = apr_pstrmemdup(pool, src->data, src->len); + } + + err = svn_error_compose_create( + err, + put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool)); + + return err; +} diff --git a/subversion/libsvn_subr/utf_validate.c b/subversion/libsvn_subr/utf_validate.c new file mode 100644 index 000000000000..8311fd71afd8 --- /dev/null +++ b/subversion/libsvn_subr/utf_validate.c @@ -0,0 +1,485 @@ +/* + * utf_validate.c: Validate a UTF-8 string + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* Validate a UTF-8 string according to the rules in + * + * Table 3-6. Well-Formed UTF-8 Bytes Sequences + * + * in + * + * The Unicode Standard, Version 4.0 + * + * which is available at + * + * http://www.unicode.org/ + * + * UTF-8 was originally defined in RFC-2279, Unicode's "well-formed UTF-8" + * is a subset of that enconding. The Unicode enconding prohibits things + * like non-shortest encodings (some characters can be represented by more + * than one multi-byte encoding) and the encodings for the surrogate code + * points. RFC-3629 superceeds RFC-2279 and adopts the same well-formed + * rules as Unicode. This is the ABNF in RFC-3629 that describes + * well-formed UTF-8 rules: + * + * UTF8-octets = *( UTF8-char ) + * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 + * UTF8-1 = %x00-7F + * UTF8-2 = %xC2-DF UTF8-tail + * UTF8-3 = %xE0 %xA0-BF UTF8-tail / + * %xE1-EC 2( UTF8-tail ) / + * %xED %x80-9F UTF8-tail / + * %xEE-EF 2( UTF8-tail ) + * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / + * %xF1-F3 3( UTF8-tail ) / + * %xF4 %x80-8F 2( UTF8-tail ) + * UTF8-tail = %x80-BF + * + */ + +#include "private/svn_utf_private.h" +#include "private/svn_eol_private.h" +#include "private/svn_dep_compat.h" + +/* Lookup table to categorise each octet in the string. */ +static const char octet_category[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00-0x7f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80-0x8f */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x90-0x9f */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xa0-0xbf */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, /* 0xc0-0xc1 */ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* 0xc2-0xdf */ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, /* 0xe0 */ + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* 0xe1-0xec */ + 8, /* 0xed */ + 9, 9, /* 0xee-0xef */ + 10, /* 0xf0 */ + 11, 11, 11, /* 0xf1-0xf3 */ + 12, /* 0xf4 */ + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 /* 0xf5-0xff */ +}; + +/* Machine states */ +#define FSM_START 0 +#define FSM_80BF 1 +#define FSM_A0BF 2 +#define FSM_80BF80BF 3 +#define FSM_809F 4 +#define FSM_90BF 5 +#define FSM_80BF80BF80BF 6 +#define FSM_808F 7 +#define FSM_ERROR 8 + +/* In the FSM it appears that categories 0xc0-0xc1 and 0xf5-0xff make the + same transitions, as do categories 0xe1-0xec and 0xee-0xef. I wonder if + there is any great benefit in combining categories? It would reduce the + memory footprint of the transition table by 16 bytes, but might it be + harder to understand? */ + +/* Machine transition table */ +static const char machine [9][14] = { + /* FSM_START */ + {FSM_START, /* 0x00-0x7f */ + FSM_ERROR, /* 0x80-0x8f */ + FSM_ERROR, /* 0x90-0x9f */ + FSM_ERROR, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_80BF, /* 0xc2-0xdf */ + FSM_A0BF, /* 0xe0 */ + FSM_80BF80BF, /* 0xe1-0xec */ + FSM_809F, /* 0xed */ + FSM_80BF80BF, /* 0xee-0xef */ + FSM_90BF, /* 0xf0 */ + FSM_80BF80BF80BF, /* 0xf1-0xf3 */ + FSM_808F, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_80BF */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_START, /* 0x80-0x8f */ + FSM_START, /* 0x90-0x9f */ + FSM_START, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_A0BF */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_ERROR, /* 0x80-0x8f */ + FSM_ERROR, /* 0x90-0x9f */ + FSM_80BF, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_80BF80BF */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_80BF, /* 0x80-0x8f */ + FSM_80BF, /* 0x90-0x9f */ + FSM_80BF, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_809F */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_80BF, /* 0x80-0x8f */ + FSM_80BF, /* 0x90-0x9f */ + FSM_ERROR, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_90BF */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_ERROR, /* 0x80-0x8f */ + FSM_80BF80BF, /* 0x90-0x9f */ + FSM_80BF80BF, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_80BF80BF80BF */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_80BF80BF, /* 0x80-0x8f */ + FSM_80BF80BF, /* 0x90-0x9f */ + FSM_80BF80BF, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_808F */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_80BF80BF, /* 0x80-0x8f */ + FSM_ERROR, /* 0x90-0x9f */ + FSM_ERROR, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ + + /* FSM_ERROR */ + {FSM_ERROR, /* 0x00-0x7f */ + FSM_ERROR, /* 0x80-0x8f */ + FSM_ERROR, /* 0x90-0x9f */ + FSM_ERROR, /* 0xa0-0xbf */ + FSM_ERROR, /* 0xc0-0xc1 */ + FSM_ERROR, /* 0xc2-0xdf */ + FSM_ERROR, /* 0xe0 */ + FSM_ERROR, /* 0xe1-0xec */ + FSM_ERROR, /* 0xed */ + FSM_ERROR, /* 0xee-0xef */ + FSM_ERROR, /* 0xf0 */ + FSM_ERROR, /* 0xf1-0xf3 */ + FSM_ERROR, /* 0xf4 */ + FSM_ERROR}, /* 0xf5-0xff */ +}; + +/* Scan MAX_LEN bytes in *DATA for chars that are not in the octet + * category 0 (FSM_START). Return the position of the first such char + * or DATA + MAX_LEN if all were cat 0. + */ +static const char * +first_non_fsm_start_char(const char *data, apr_size_t max_len) +{ +#if !SVN_UNALIGNED_ACCESS_IS_OK + + /* On some systems, we need to make sure that buf is properly aligned + * for chunky data access. + */ + if ((apr_uintptr_t)data & (sizeof(apr_uintptr_t)-1)) + { + apr_size_t len = (~(apr_uintptr_t)data) & (sizeof(apr_uintptr_t)-1); + if (len > max_len) + len = max_len; + max_len -= len; + + for (; len > 0; ++data, --len) + if (*data < 0 || *data >= 0x80) + return data; + } + +#endif + + /* Scan the input one machine word at a time. */ + for (; max_len > sizeof(apr_uintptr_t) + ; data += sizeof(apr_uintptr_t), max_len -= sizeof(apr_uintptr_t)) + if (*(const apr_uintptr_t *)data & SVN__BIT_7_SET) + break; + + /* The remaining odd bytes will be examined the naive way: */ + for (; max_len > 0; ++data, --max_len) + if (*data < 0 || *data >= 0x80) + break; + + return data; +} + +/* Scan the C string in *DATA for chars that are not in the octet + * category 0 (FSM_START). Return the position of either the such + * char or of the terminating NUL. + */ +static const char * +first_non_fsm_start_char_cstring(const char *data) +{ + /* We need to make sure that BUF is properly aligned for chunky data + * access because we don't know the string's length. Unaligned chunk + * read access beyond the NUL terminator could therefore result in a + * segfault. + */ + for (; (apr_uintptr_t)data & (sizeof(apr_uintptr_t)-1); ++data) + if (*data <= 0 || *data >= 0x80) + return data; + + /* Scan the input one machine word at a time. */ +#ifndef SVN_UTF_NO_UNINITIALISED_ACCESS + /* This may read allocated but initialised bytes beyond the + terminating null. Any such bytes are always readable and this + code operates correctly whatever the uninitialised values happen + to be. However memory checking tools such as valgrind and GCC + 4.8's address santitizer will object so this bit of code can be + disabled at compile time. */ + for (; ; data += sizeof(apr_uintptr_t)) + { + /* Check for non-ASCII chars: */ + apr_uintptr_t chunk = *(const apr_uintptr_t *)data; + if (chunk & SVN__BIT_7_SET) + break; + + /* This is the well-known strlen test: */ + chunk |= (chunk & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET; + if ((chunk & SVN__BIT_7_SET) != SVN__BIT_7_SET) + break; + } +#endif + + /* The remaining odd bytes will be examined the naive way: */ + for (; ; ++data) + if (*data <= 0 || *data >= 0x80) + break; + + return data; +} + +const char * +svn_utf__last_valid(const char *data, apr_size_t len) +{ + const char *start = first_non_fsm_start_char(data, len); + const char *end = data + len; + int state = FSM_START; + + data = start; + while (data < end) + { + unsigned char octet = *data++; + int category = octet_category[octet]; + state = machine[state][category]; + if (state == FSM_START) + start = data; + } + return start; +} + +svn_boolean_t +svn_utf__cstring_is_valid(const char *data) +{ + int state = FSM_START; + + if (!data) + return FALSE; + + data = first_non_fsm_start_char_cstring(data); + + while (*data) + { + unsigned char octet = *data++; + int category = octet_category[octet]; + state = machine[state][category]; + } + return state == FSM_START; +} + +svn_boolean_t +svn_utf__is_valid(const char *data, apr_size_t len) +{ + const char *end = data + len; + int state = FSM_START; + + if (!data) + return FALSE; + + data = first_non_fsm_start_char(data, len); + + while (data < end) + { + unsigned char octet = *data++; + int category = octet_category[octet]; + state = machine[state][category]; + } + return state == FSM_START; +} + +const char * +svn_utf__last_valid2(const char *data, apr_size_t len) +{ + const char *start = first_non_fsm_start_char(data, len); + const char *end = data + len; + int state = FSM_START; + + data = start; + while (data < end) + { + unsigned char octet = *data++; + switch (state) + { + case FSM_START: + if (octet <= 0x7F) + break; + else if (octet <= 0xC1) + state = FSM_ERROR; + else if (octet <= 0xDF) + state = FSM_80BF; + else if (octet == 0xE0) + state = FSM_A0BF; + else if (octet <= 0xEC) + state = FSM_80BF80BF; + else if (octet == 0xED) + state = FSM_809F; + else if (octet <= 0xEF) + state = FSM_80BF80BF; + else if (octet == 0xF0) + state = FSM_90BF; + else if (octet <= 0xF3) + state = FSM_80BF80BF80BF; + else if (octet <= 0xF4) + state = FSM_808F; + else + state = FSM_ERROR; + break; + case FSM_80BF: + if (octet >= 0x80 && octet <= 0xBF) + state = FSM_START; + else + state = FSM_ERROR; + break; + case FSM_A0BF: + if (octet >= 0xA0 && octet <= 0xBF) + state = FSM_80BF; + else + state = FSM_ERROR; + break; + case FSM_80BF80BF: + if (octet >= 0x80 && octet <= 0xBF) + state = FSM_80BF; + else + state = FSM_ERROR; + break; + case FSM_809F: + if (octet >= 0x80 && octet <= 0x9F) + state = FSM_80BF; + else + state = FSM_ERROR; + break; + case FSM_90BF: + if (octet >= 0x90 && octet <= 0xBF) + state = FSM_80BF80BF; + else + state = FSM_ERROR; + break; + case FSM_80BF80BF80BF: + if (octet >= 0x80 && octet <= 0xBF) + state = FSM_80BF80BF; + else + state = FSM_ERROR; + break; + case FSM_808F: + if (octet >= 0x80 && octet <= 0x8F) + state = FSM_80BF80BF; + else + state = FSM_ERROR; + break; + default: + case FSM_ERROR: + return start; + } + if (state == FSM_START) + start = data; + } + return start; +} diff --git a/subversion/libsvn_subr/utf_width.c b/subversion/libsvn_subr/utf_width.c new file mode 100644 index 000000000000..3adff4cce331 --- /dev/null +++ b/subversion/libsvn_subr/utf_width.c @@ -0,0 +1,283 @@ +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include <apr_lib.h> + +#include "svn_utf.h" +#include "private/svn_utf_private.h" + +#include "svn_private_config.h" + +struct interval { + apr_uint32_t first; + apr_uint32_t last; +}; + +/* auxiliary function for binary search in interval table */ +static int +bisearch(apr_uint32_t ucs, const struct interval *table, apr_uint32_t max) +{ + apr_uint32_t min = 0; + apr_uint32_t mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; /* this is safe because ucs >= table[0].first */ + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +static int +mk_wcwidth(apr_uint32_t ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + +int +svn_utf_cstring_utf8_width(const char *cstr) +{ + int width = 0; + + if (*cstr == '\0') + return 0; + + /* Ensure the conversion below doesn't fail because of encoding errors. */ + if (!svn_utf__cstring_is_valid(cstr)) + return -1; + + /* Convert the UTF-8 string to UTF-32 (UCS4) which is the format + * mk_wcwidth() expects, and get the width of each character. + * We don't need much error checking since the input is valid UTF-8. */ + while (*cstr) + { + apr_uint32_t ucs; + int nbytes; + int lead_mask; + int w; + int i; + + if ((*cstr & 0x80) == 0) + { + nbytes = 1; + lead_mask = 0x7f; + } + else if ((*cstr & 0xe0) == 0xc0) + { + nbytes = 2; + lead_mask = 0x1f; + } + else if ((*cstr & 0xf0) == 0xe0) + { + nbytes = 3; + lead_mask = 0x0f; + } + else if ((*cstr & 0xf8) == 0xf0) + { + nbytes = 4; + lead_mask = 0x07; + } + else + { + /* RFC 3629 restricts UTF-8 to max 4 bytes per character. */ + return -1; + } + + /* Parse character data from leading byte. */ + ucs = (apr_uint32_t)(*cstr & lead_mask); + + /* Parse character data from continuation bytes. */ + for (i = 1; i < nbytes; i++) + { + ucs <<= 6; + ucs |= (cstr[i] & 0x3f); + } + + cstr += nbytes; + + /* Determine the width of this character and add it to the total. */ + w = mk_wcwidth(ucs); + if (w == -1) + return -1; + width += w; + } + + return width; +} diff --git a/subversion/libsvn_subr/validate.c b/subversion/libsvn_subr/validate.c new file mode 100644 index 000000000000..205233989d36 --- /dev/null +++ b/subversion/libsvn_subr/validate.c @@ -0,0 +1,102 @@ +/* + * validate.c: validation routines + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include "svn_error.h" +#include "svn_ctype.h" +#include "svn_private_config.h" + + + +/*** Code. ***/ + +svn_error_t * +svn_mime_type_validate(const char *mime_type, apr_pool_t *pool) +{ + /* Since svn:mime-type can actually contain a full content type + specification, e.g., "text/html; charset=UTF-8", make sure we're + only looking at the media type here. */ + const apr_size_t len = strcspn(mime_type, "; "); + const apr_size_t len2 = strlen(mime_type); + const char *const slash_pos = strchr(mime_type, '/'); + apr_size_t i; + const char *tspecials = "()<>@,;:\\\"/[]?="; + + if (len == 0) + return svn_error_createf + (SVN_ERR_BAD_MIME_TYPE, NULL, + _("MIME type '%s' has empty media type"), mime_type); + + if (slash_pos == NULL || slash_pos >= &mime_type[len]) + return svn_error_createf + (SVN_ERR_BAD_MIME_TYPE, NULL, + _("MIME type '%s' does not contain '/'"), mime_type); + + /* Check the mime type for illegal characters. See RFC 1521. */ + for (i = 0; i < len; i++) + { + if (&mime_type[i] != slash_pos + && (! svn_ctype_isascii(mime_type[i]) + || svn_ctype_iscntrl(mime_type[i]) + || svn_ctype_isspace(mime_type[i]) + || (strchr(tspecials, mime_type[i]) != NULL))) + return svn_error_createf + (SVN_ERR_BAD_MIME_TYPE, NULL, + _("MIME type '%s' contains invalid character '%c' " + "in media type"), + mime_type, mime_type[i]); + } + + /* Check the whole string for unsafe characters. (issue #2872) */ + for (i = 0; i < len2; i++) + { + if (svn_ctype_iscntrl(mime_type[i]) && mime_type[i] != '\t') + return svn_error_createf( + SVN_ERR_BAD_MIME_TYPE, NULL, + _("MIME type '%s' contains invalid character '0x%02x' " + "in postfix"), + mime_type, mime_type[i]); + } + + return SVN_NO_ERROR; +} + + +svn_boolean_t +svn_mime_type_is_binary(const char *mime_type) +{ + /* See comment in svn_mime_type_validate() above. */ + const apr_size_t len = strcspn(mime_type, "; "); + return ((strncmp(mime_type, "text/", 5) != 0) + && (len != 15 || strncmp(mime_type, "image/x-xbitmap", len) != 0) + && (len != 15 || strncmp(mime_type, "image/x-xpixmap", len) != 0) + ); +} diff --git a/subversion/libsvn_subr/version.c b/subversion/libsvn_subr/version.c new file mode 100644 index 000000000000..ad2a270b76e9 --- /dev/null +++ b/subversion/libsvn_subr/version.c @@ -0,0 +1,291 @@ +/* + * version.c: library version number and utilities + * + * ==================================================================== + * 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_error.h" +#include "svn_version.h" + +#include "sysinfo.h" +#include "svn_private_config.h" +#include "private/svn_subr_private.h" + +const svn_version_t * +svn_subr_version(void) +{ + SVN_VERSION_BODY; +} + + +svn_boolean_t svn_ver_compatible(const svn_version_t *my_version, + const svn_version_t *lib_version) +{ + /* With normal development builds the matching rules are strict, to + avoid inadvertantly using the wrong libraries. For backward + compatibility testing use --disable-full-version-match to + configure 1.7 and then the libraries that get built can be used + to replace those in 1.6 or earlier builds. */ + +#ifndef SVN_DISABLE_FULL_VERSION_MATCH + if (lib_version->tag[0] != '\0') + /* Development library; require exact match. */ + return svn_ver_equal(my_version, lib_version); + else if (my_version->tag[0] != '\0') + /* Development client; must be newer than the library + and have the same major and minor version. */ + return (my_version->major == lib_version->major + && my_version->minor == lib_version->minor + && my_version->patch > lib_version->patch); +#endif + + /* General compatibility rules for released versions. */ + return (my_version->major == lib_version->major + && my_version->minor <= lib_version->minor); +} + + +svn_boolean_t svn_ver_equal(const svn_version_t *my_version, + const svn_version_t *lib_version) +{ + return (my_version->major == lib_version->major + && my_version->minor == lib_version->minor + && my_version->patch == lib_version->patch + && 0 == strcmp(my_version->tag, lib_version->tag)); +} + + +svn_error_t * +svn_ver_check_list(const svn_version_t *my_version, + const svn_version_checklist_t *checklist) +{ + svn_error_t *err = SVN_NO_ERROR; + int i; + + for (i = 0; checklist[i].label != NULL; ++i) + { + const svn_version_t *lib_version = checklist[i].version_query(); + if (!svn_ver_compatible(my_version, lib_version)) + err = svn_error_createf(SVN_ERR_VERSION_MISMATCH, err, + _("Version mismatch in '%s':" + " found %d.%d.%d%s," + " expected %d.%d.%d%s"), + checklist[i].label, + lib_version->major, lib_version->minor, + lib_version->patch, lib_version->tag, + my_version->major, my_version->minor, + my_version->patch, my_version->tag); + } + + return err; +} + + +struct svn_version_extended_t +{ + const char *build_date; /* Compilation date */ + const char *build_time; /* Compilation time */ + const char *build_host; /* Build canonical host name */ + const char *copyright; /* Copyright notice (localized) */ + const char *runtime_host; /* Runtime canonical host name */ + const char *runtime_osname; /* Running OS release name */ + + /* Array of svn_version_ext_linked_lib_t describing dependent + libraries. */ + const apr_array_header_t *linked_libs; + + /* Array of svn_version_ext_loaded_lib_t describing loaded shared + libraries. */ + const apr_array_header_t *loaded_libs; +}; + + +const svn_version_extended_t * +svn_version_extended(svn_boolean_t verbose, + apr_pool_t *pool) +{ + svn_version_extended_t *info = apr_pcalloc(pool, sizeof(*info)); + + info->build_date = __DATE__; + info->build_time = __TIME__; + info->build_host = SVN_BUILD_HOST; + info->copyright = apr_pstrdup + (pool, _("Copyright (C) 2013 The Apache Software Foundation.\n" + "This software consists of contributions made by many people;\n" + "see the NOTICE file for more information.\n" + "Subversion is open source software, see " + "http://subversion.apache.org/\n")); + + if (verbose) + { + info->runtime_host = svn_sysinfo__canonical_host(pool); + info->runtime_osname = svn_sysinfo__release_name(pool); + info->linked_libs = svn_sysinfo__linked_libs(pool); + info->loaded_libs = svn_sysinfo__loaded_libs(pool); + } + + return info; +} + + +const char * +svn_version_ext_build_date(const svn_version_extended_t *ext_info) +{ + return ext_info->build_date; +} + +const char * +svn_version_ext_build_time(const svn_version_extended_t *ext_info) +{ + return ext_info->build_time; +} + +const char * +svn_version_ext_build_host(const svn_version_extended_t *ext_info) +{ + return ext_info->build_host; +} + +const char * +svn_version_ext_copyright(const svn_version_extended_t *ext_info) +{ + return ext_info->copyright; +} + +const char * +svn_version_ext_runtime_host(const svn_version_extended_t *ext_info) +{ + return ext_info->runtime_host; +} + +const char * +svn_version_ext_runtime_osname(const svn_version_extended_t *ext_info) +{ + return ext_info->runtime_osname; +} + +const apr_array_header_t * +svn_version_ext_linked_libs(const svn_version_extended_t *ext_info) +{ + return ext_info->linked_libs; +} + +const apr_array_header_t * +svn_version_ext_loaded_libs(const svn_version_extended_t *ext_info) +{ + return ext_info->loaded_libs; +} + +svn_error_t * +svn_version__parse_version_string(svn_version_t **version_p, + const char *version_string, + apr_pool_t *result_pool) +{ + svn_error_t *err; + svn_version_t *version; + apr_array_header_t *pieces = + svn_cstring_split(version_string, ".", FALSE, result_pool); + + if ((pieces->nelts < 2) || (pieces->nelts > 3)) + return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, NULL, + _("Failed to parse version number string '%s'"), + version_string); + + version = apr_pcalloc(result_pool, sizeof(*version)); + version->tag = ""; + + /* Parse the major and minor integers strictly. */ + err = svn_cstring_atoi(&(version->major), + APR_ARRAY_IDX(pieces, 0, const char *)); + if (err) + return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err, + _("Failed to parse version number string '%s'"), + version_string); + err = svn_cstring_atoi(&(version->minor), + APR_ARRAY_IDX(pieces, 1, const char *)); + if (err) + return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err, + _("Failed to parse version number string '%s'"), + version_string); + + /* If there's a third component, we'll parse it, too. But we don't + require that it be present. */ + if (pieces->nelts == 3) + { + const char *piece = APR_ARRAY_IDX(pieces, 2, const char *); + char *hyphen = strchr(piece, '-'); + if (hyphen) + { + version->tag = apr_pstrdup(result_pool, hyphen + 1); + *hyphen = '\0'; + } + err = svn_cstring_atoi(&(version->patch), piece); + if (err) + return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err, + _("Failed to parse version number string '%s'" + ), + version_string); + } + + if (version->major < 0 || version->minor < 0 || version->patch < 0) + return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err, + _("Failed to parse version number string '%s'"), + version_string); + + *version_p = version; + return SVN_NO_ERROR; +} + + +svn_boolean_t +svn_version__at_least(svn_version_t *version, + int major, + int minor, + int patch) +{ + /* Compare major versions. */ + if (version->major < major) + return FALSE; + if (version->major > major) + return TRUE; + + /* Major versions are the same. Compare minor versions. */ + if (version->minor < minor) + return FALSE; + if (version->minor > minor) + return TRUE; + + /* Major and minor versions are the same. Compare patch + versions. */ + if (version->patch < patch) + return FALSE; + if (version->patch > patch) + return TRUE; + + /* Major, minor, and patch versions are identical matches. But tags + in our schema are always used for versions not yet quite at the + given patch level. */ + if (version->tag && version->tag[0]) + return FALSE; + + return TRUE; +} diff --git a/subversion/libsvn_subr/win32_crashrpt.c b/subversion/libsvn_subr/win32_crashrpt.c new file mode 100644 index 000000000000..6becc96d74d3 --- /dev/null +++ b/subversion/libsvn_subr/win32_crashrpt.c @@ -0,0 +1,805 @@ +/* + * win32_crashrpt.c : provides information after a crash + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* prevent "empty compilation unit" warning on e.g. UNIX */ +typedef int win32_crashrpt__dummy; + +#ifdef WIN32 +#ifdef SVN_USE_WIN32_CRASHHANDLER + +/*** Includes. ***/ +#include <apr.h> +#include <dbghelp.h> +#include <direct.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include "svn_version.h" + +#include "win32_crashrpt.h" +#include "win32_crashrpt_dll.h" + +/*** Global variables ***/ +HANDLE dbghelp_dll = INVALID_HANDLE_VALUE; + +/* Email address where the crash reports should be sent too. */ +#define CRASHREPORT_EMAIL "users@subversion.apache.org" + +#define DBGHELP_DLL "dbghelp.dll" + +#define LOGFILE_PREFIX "svn-crash-log" + +#if defined(_M_IX86) +#define FORMAT_PTR "0x%08x" +#elif defined(_M_X64) +#define FORMAT_PTR "0x%016I64x" +#endif + +/*** Code. ***/ + +/* Convert a wide-character string to utf-8. This function will create a buffer + * large enough to hold the result string, the caller should free this buffer. + * If the string can't be converted, NULL is returned. + */ +static char * +convert_wbcs_to_utf8(const wchar_t *str) +{ + size_t len = wcslen(str); + char *utf8_str = malloc(sizeof(wchar_t) * len + 1); + len = wcstombs(utf8_str, str, len); + + if (len == -1) + return NULL; + + utf8_str[len] = '\0'; + + return utf8_str; +} + +/* Convert the exception code to a string */ +static const char * +exception_string(int exception) +{ +#define EXCEPTION(x) case EXCEPTION_##x: return (#x); + + switch (exception) + { + EXCEPTION(ACCESS_VIOLATION) + EXCEPTION(DATATYPE_MISALIGNMENT) + EXCEPTION(BREAKPOINT) + EXCEPTION(SINGLE_STEP) + EXCEPTION(ARRAY_BOUNDS_EXCEEDED) + EXCEPTION(FLT_DENORMAL_OPERAND) + EXCEPTION(FLT_DIVIDE_BY_ZERO) + EXCEPTION(FLT_INEXACT_RESULT) + EXCEPTION(FLT_INVALID_OPERATION) + EXCEPTION(FLT_OVERFLOW) + EXCEPTION(FLT_STACK_CHECK) + EXCEPTION(FLT_UNDERFLOW) + EXCEPTION(INT_DIVIDE_BY_ZERO) + EXCEPTION(INT_OVERFLOW) + EXCEPTION(PRIV_INSTRUCTION) + EXCEPTION(IN_PAGE_ERROR) + EXCEPTION(ILLEGAL_INSTRUCTION) + EXCEPTION(NONCONTINUABLE_EXCEPTION) + EXCEPTION(STACK_OVERFLOW) + EXCEPTION(INVALID_DISPOSITION) + EXCEPTION(GUARD_PAGE) + EXCEPTION(INVALID_HANDLE) + + default: + return "UNKNOWN_ERROR"; + } +#undef EXCEPTION +} + +/* Write the minidump to file. The callback function will at the same time + write the list of modules to the log file. */ +static BOOL +write_minidump_file(const char *file, PEXCEPTION_POINTERS ptrs, + MINIDUMP_CALLBACK_ROUTINE module_callback, + void *data) +{ + /* open minidump file */ + HANDLE minidump_file = CreateFile(file, GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (minidump_file != INVALID_HANDLE_VALUE) + { + MINIDUMP_EXCEPTION_INFORMATION expt_info; + MINIDUMP_CALLBACK_INFORMATION dump_cb_info; + + expt_info.ThreadId = GetCurrentThreadId(); + expt_info.ExceptionPointers = ptrs; + expt_info.ClientPointers = FALSE; + + dump_cb_info.CallbackRoutine = module_callback; + dump_cb_info.CallbackParam = data; + + MiniDumpWriteDump_(GetCurrentProcess(), + GetCurrentProcessId(), + minidump_file, + MiniDumpNormal, + ptrs ? &expt_info : NULL, + NULL, + &dump_cb_info); + + CloseHandle(minidump_file); + return TRUE; + } + + return FALSE; +} + +/* Write module information to the log file */ +static BOOL CALLBACK +write_module_info_callback(void *data, + CONST PMINIDUMP_CALLBACK_INPUT callback_input, + PMINIDUMP_CALLBACK_OUTPUT callback_output) +{ + if (data != NULL && + callback_input != NULL && + callback_input->CallbackType == ModuleCallback) + { + FILE *log_file = (FILE *)data; + MINIDUMP_MODULE_CALLBACK module = callback_input->Module; + + char *buf = convert_wbcs_to_utf8(module.FullPath); + fprintf(log_file, FORMAT_PTR, module.BaseOfImage); + fprintf(log_file, " %s", buf); + free(buf); + + fprintf(log_file, " (%d.%d.%d.%d, %d bytes)\n", + HIWORD(module.VersionInfo.dwFileVersionMS), + LOWORD(module.VersionInfo.dwFileVersionMS), + HIWORD(module.VersionInfo.dwFileVersionLS), + LOWORD(module.VersionInfo.dwFileVersionLS), + module.SizeOfImage); + } + + return TRUE; +} + +/* Write details about the current process, platform and the exception */ +static void +write_process_info(EXCEPTION_RECORD *exception, CONTEXT *context, + FILE *log_file) +{ + OSVERSIONINFO oi; + const char *cmd_line; + char workingdir[8192]; + + /* write the command line */ + cmd_line = GetCommandLine(); + fprintf(log_file, + "Cmd line: %s\n", cmd_line); + + _getcwd(workingdir, sizeof(workingdir)); + fprintf(log_file, + "Working Dir: %s\n", workingdir); + + /* write the svn version number info. */ + fprintf(log_file, + "Version: %s, compiled %s, %s\n", + SVN_VERSION, __DATE__, __TIME__); + + /* write information about the OS */ + oi.dwOSVersionInfoSize = sizeof(oi); + GetVersionEx(&oi); + + fprintf(log_file, + "Platform: Windows OS version %d.%d build %d %s\n\n", + oi.dwMajorVersion, oi.dwMinorVersion, oi.dwBuildNumber, + oi.szCSDVersion); + + /* write the exception code */ + fprintf(log_file, + "Exception: %s\n\n", + exception_string(exception->ExceptionCode)); + + /* write the register info. */ + fprintf(log_file, + "Registers:\n"); +#if defined(_M_IX86) + fprintf(log_file, + "eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n", + context->Eax, context->Ebx, context->Ecx, + context->Edx, context->Esi, context->Edi); + fprintf(log_file, + "eip=%08x esp=%08x ebp=%08x efl=%08x\n", + context->Eip, context->Esp, + context->Ebp, context->EFlags); + fprintf(log_file, + "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x\n", + context->SegCs, context->SegSs, context->SegDs, + context->SegEs, context->SegFs, context->SegGs); +#elif defined(_M_X64) + fprintf(log_file, + "Rax=%016I64x Rcx=%016I64x Rdx=%016I64x Rbx=%016I64x\n", + context->Rax, context->Rcx, context->Rdx, context->Rbx); + fprintf(log_file, + "Rsp=%016I64x Rbp=%016I64x Rsi=%016I64x Rdi=%016I64x\n", + context->Rsp, context->Rbp, context->Rsi, context->Rdi); + fprintf(log_file, + "R8= %016I64x R9= %016I64x R10= %016I64x R11=%016I64x\n", + context->R8, context->R9, context->R10, context->R11); + fprintf(log_file, + "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n", + context->R12, context->R13, context->R14, context->R15); + + fprintf(log_file, + "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x ss=%04x\n", + context->SegCs, context->SegDs, context->SegEs, + context->SegFs, context->SegGs, context->SegSs); +#else +#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER +#endif +} + +/* Formats the value at address based on the specified basic type + * (char, int, long ...). */ +static void +format_basic_type(char *buf, DWORD basic_type, DWORD64 length, void *address) +{ + switch(length) + { + case 1: + sprintf(buf, "0x%02x", (int)*(unsigned char *)address); + break; + case 2: + sprintf(buf, "0x%04x", (int)*(unsigned short *)address); + break; + case 4: + switch(basic_type) + { + case 2: /* btChar */ + { + if (!IsBadStringPtr(*(PSTR*)address, 32)) + sprintf(buf, "\"%.31s\"", *(const char **)address); + else + sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address); + } + case 6: /* btInt */ + sprintf(buf, "%d", *(int *)address); + break; + case 8: /* btFloat */ + sprintf(buf, "%f", *(float *)address); + break; + default: + sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address); + break; + } + break; + case 8: + if (basic_type == 8) /* btFloat */ + sprintf(buf, "%lf", *(double *)address); + else + sprintf(buf, "0x%016I64X", *(unsigned __int64 *)address); + break; + default: + sprintf(buf, "[unhandled type 0x%08x of length " FORMAT_PTR "]", + basic_type, length); + break; + } +} + +/* Formats the value at address based on the type (pointer, user defined, + * basic type). */ +static void +format_value(char *value_str, DWORD64 mod_base, DWORD type, void *value_addr) +{ + DWORD tag = 0; + int ptr = 0; + HANDLE proc = GetCurrentProcess(); + + while (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMTAG, &tag)) + { + /* SymTagPointerType */ + if (tag == 14) + { + ptr++; + SymGetTypeInfo_(proc, mod_base, type, TI_GET_TYPE, &type); + continue; + } + break; + } + + switch(tag) + { + case 11: /* SymTagUDT */ + { + WCHAR *type_name_wbcs; + if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMNAME, + &type_name_wbcs)) + { + char *type_name = convert_wbcs_to_utf8(type_name_wbcs); + LocalFree(type_name_wbcs); + + if (ptr == 0) + sprintf(value_str, "(%s) " FORMAT_PTR, + type_name, (DWORD_PTR *)value_addr); + else if (ptr == 1) + sprintf(value_str, "(%s *) " FORMAT_PTR, + type_name, *(DWORD_PTR *)value_addr); + else + sprintf(value_str, "(%s **) " FORMAT_PTR, + type_name, *(DWORD_PTR *)value_addr); + + free(type_name); + } + else + sprintf(value_str, "[no symbol tag]"); + } + break; + case 16: /* SymTagBaseType */ + { + DWORD bt; + ULONG64 length; + SymGetTypeInfo_(proc, mod_base, type, TI_GET_LENGTH, &length); + + /* print a char * as a string */ + if (ptr == 1 && length == 1) + { + sprintf(value_str, FORMAT_PTR " \"%s\"", + *(DWORD_PTR *)value_addr, *(const char **)value_addr); + } + else if (ptr >= 1) + { + sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr); + } + else if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_BASETYPE, &bt)) + { + format_basic_type(value_str, bt, length, value_addr); + } + } + break; + case 12: /* SymTagEnum */ + sprintf(value_str, "%d", *(DWORD_PTR *)value_addr); + break; + case 13: /* SymTagFunctionType */ + sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr); + break; + default: + sprintf(value_str, "[unhandled tag: %d]", tag); + break; + } +} + +/* Internal structure used to pass some data to the enumerate symbols + * callback */ +typedef struct symbols_baton_t { + STACKFRAME64 *stack_frame; + FILE *log_file; + int nr_of_frame; + BOOL log_params; +} symbols_baton_t; + +/* Write the details of one parameter or local variable to the log file */ +static BOOL WINAPI +write_var_values(PSYMBOL_INFO sym_info, ULONG sym_size, void *baton) +{ + static int last_nr_of_frame = 0; + DWORD_PTR var_data = 0; /* Will point to the variable's data in memory */ + STACKFRAME64 *stack_frame = ((symbols_baton_t*)baton)->stack_frame; + FILE *log_file = ((symbols_baton_t*)baton)->log_file; + int nr_of_frame = ((symbols_baton_t*)baton)->nr_of_frame; + BOOL log_params = ((symbols_baton_t*)baton)->log_params; + char value_str[256] = ""; + + /* get the variable's data */ + if (sym_info->Flags & SYMFLAG_REGREL) + { + var_data = (DWORD_PTR)stack_frame->AddrFrame.Offset; + var_data += (DWORD_PTR)sym_info->Address; + } + else + return FALSE; + + if (log_params && sym_info->Flags & SYMFLAG_PARAMETER) + { + if (last_nr_of_frame == nr_of_frame) + fprintf(log_file, ", ", 2); + else + last_nr_of_frame = nr_of_frame; + + format_value(value_str, sym_info->ModBase, sym_info->TypeIndex, + (void *)var_data); + fprintf(log_file, "%s=%s", sym_info->Name, value_str); + } + if (!log_params && sym_info->Flags & SYMFLAG_LOCAL) + { + format_value(value_str, sym_info->ModBase, sym_info->TypeIndex, + (void *)var_data); + fprintf(log_file, " %s = %s\n", sym_info->Name, value_str); + } + + return TRUE; +} + +/* Write the details of one function to the log file */ +static void +write_function_detail(STACKFRAME64 stack_frame, int nr_of_frame, FILE *log_file) +{ + ULONG64 symbolBuffer[(sizeof(SYMBOL_INFO) + + MAX_SYM_NAME + + sizeof(ULONG64) - 1) / + sizeof(ULONG64)]; + PSYMBOL_INFO pIHS = (PSYMBOL_INFO)symbolBuffer; + DWORD64 func_disp=0; + + IMAGEHLP_STACK_FRAME ih_stack_frame; + IMAGEHLP_LINE64 ih_line; + DWORD line_disp=0; + + HANDLE proc = GetCurrentProcess(); + + symbols_baton_t ensym; + + nr_of_frame++; /* We need a 1 based index here */ + + /* log the function name */ + pIHS->SizeOfStruct = sizeof(SYMBOL_INFO); + pIHS->MaxNameLen = MAX_SYM_NAME; + if (SymFromAddr_(proc, stack_frame.AddrPC.Offset, &func_disp, pIHS)) + { + fprintf(log_file, + "#%d 0x%08I64x in %.200s(", + nr_of_frame, stack_frame.AddrPC.Offset, pIHS->Name); + + /* restrict symbol enumeration to this frame only */ + ih_stack_frame.InstructionOffset = stack_frame.AddrPC.Offset; + SymSetContext_(proc, &ih_stack_frame, 0); + + ensym.log_file = log_file; + ensym.stack_frame = &stack_frame; + ensym.nr_of_frame = nr_of_frame; + + /* log all function parameters */ + ensym.log_params = TRUE; + SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym); + + fprintf(log_file, ")"); + } + else + { + fprintf(log_file, + "#%d 0x%08I64x in (unknown function)", + nr_of_frame, stack_frame.AddrPC.Offset); + } + + /* find the source line for this function. */ + ih_line.SizeOfStruct = sizeof(IMAGEHLP_LINE); + if (SymGetLineFromAddr64_(proc, stack_frame.AddrPC.Offset, + &line_disp, &ih_line) != 0) + { + fprintf(log_file, + " at %s:%d\n", ih_line.FileName, ih_line.LineNumber); + } + else + { + fprintf(log_file, "\n"); + } + + /* log all function local variables */ + ensym.log_params = FALSE; + SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym); +} + +/* Walk over the stack and log all relevant information to the log file */ +static void +write_stacktrace(CONTEXT *context, FILE *log_file) +{ +#if defined (_M_IX86) || defined(_M_X64) || defined(_M_IA64) + HANDLE proc = GetCurrentProcess(); + STACKFRAME64 stack_frame; + DWORD machine; + CONTEXT ctx; + int skip = 0, i = 0; + + /* The thread information - if not supplied. */ + if (context == NULL) + { + /* If no context is supplied, skip 1 frame */ + skip = 1; + + ctx.ContextFlags = CONTEXT_FULL; + if (!GetThreadContext(GetCurrentThread(), &ctx)) + return; + } + else + { + ctx = *context; + } + + if (context == NULL) + return; + + /* Write the stack trace */ + ZeroMemory(&stack_frame, sizeof(STACKFRAME64)); + stack_frame.AddrPC.Mode = AddrModeFlat; + stack_frame.AddrStack.Mode = AddrModeFlat; + stack_frame.AddrFrame.Mode = AddrModeFlat; + +#if defined(_M_IX86) + machine = IMAGE_FILE_MACHINE_I386; + stack_frame.AddrPC.Offset = context->Eip; + stack_frame.AddrStack.Offset = context->Esp; + stack_frame.AddrFrame.Offset = context->Ebp; +#elif defined(_M_X64) + machine = IMAGE_FILE_MACHINE_AMD64; + stack_frame.AddrPC.Offset = context->Rip; + stack_frame.AddrStack.Offset = context->Rsp; + stack_frame.AddrFrame.Offset = context->Rbp; +#elif defined(_M_IA64) + machine = IMAGE_FILE_MACHINE_IA64; + stack_frame.AddrPC.Offset = context->StIIP; + stack_frame.AddrStack.Offset = context->SP; + stack_frame.AddrBStore.Mode = AddrModeFlat; + stack_frame.AddrBStore.Offset = context->RsBSP; +#else +#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER +#endif + + while (1) + { + if (! StackWalk64_(machine, proc, GetCurrentThread(), + &stack_frame, &ctx, NULL, + SymFunctionTableAccess64_, SymGetModuleBase64_, NULL)) + { + break; + } + + if (i >= skip) + { + /* Try to include symbolic information. + Also check that the address is not zero. Sometimes StackWalk + returns TRUE with a frame of zero. */ + if (stack_frame.AddrPC.Offset != 0) + { + write_function_detail(stack_frame, i, log_file); + } + } + i++; + } +#else +#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER +#endif +} + +/* Check if a debugger is attached to this process */ +static BOOL +is_debugger_present() +{ + HANDLE kernel32_dll = LoadLibrary("kernel32.dll"); + BOOL result; + + ISDEBUGGERPRESENT IsDebuggerPresent_ = + (ISDEBUGGERPRESENT)GetProcAddress(kernel32_dll, "IsDebuggerPresent"); + + if (IsDebuggerPresent_ && IsDebuggerPresent_()) + result = TRUE; + else + result = FALSE; + + FreeLibrary(kernel32_dll); + + return result; +} + +/* Load the dbghelp.dll file, try to find a version that matches our + requirements. */ +static BOOL +load_dbghelp_dll() +{ + dbghelp_dll = LoadLibrary(DBGHELP_DLL); + if (dbghelp_dll != INVALID_HANDLE_VALUE) + { + DWORD opts; + + /* load the functions */ + MiniDumpWriteDump_ = + (MINIDUMPWRITEDUMP)GetProcAddress(dbghelp_dll, "MiniDumpWriteDump"); + SymInitialize_ = + (SYMINITIALIZE)GetProcAddress(dbghelp_dll, "SymInitialize"); + SymSetOptions_ = + (SYMSETOPTIONS)GetProcAddress(dbghelp_dll, "SymSetOptions"); + SymGetOptions_ = + (SYMGETOPTIONS)GetProcAddress(dbghelp_dll, "SymGetOptions"); + SymCleanup_ = + (SYMCLEANUP)GetProcAddress(dbghelp_dll, "SymCleanup"); + SymGetTypeInfo_ = + (SYMGETTYPEINFO)GetProcAddress(dbghelp_dll, "SymGetTypeInfo"); + SymGetLineFromAddr64_ = + (SYMGETLINEFROMADDR64)GetProcAddress(dbghelp_dll, + "SymGetLineFromAddr64"); + SymEnumSymbols_ = + (SYMENUMSYMBOLS)GetProcAddress(dbghelp_dll, "SymEnumSymbols"); + SymSetContext_ = + (SYMSETCONTEXT)GetProcAddress(dbghelp_dll, "SymSetContext"); + SymFromAddr_ = (SYMFROMADDR)GetProcAddress(dbghelp_dll, "SymFromAddr"); + StackWalk64_ = (STACKWALK64)GetProcAddress(dbghelp_dll, "StackWalk64"); + SymFunctionTableAccess64_ = + (SYMFUNCTIONTABLEACCESS64)GetProcAddress(dbghelp_dll, + "SymFunctionTableAccess64"); + SymGetModuleBase64_ = + (SYMGETMODULEBASE64)GetProcAddress(dbghelp_dll, "SymGetModuleBase64"); + + if (! (MiniDumpWriteDump_ && + SymInitialize_ && SymSetOptions_ && SymGetOptions_ && + SymCleanup_ && SymGetTypeInfo_ && SymGetLineFromAddr64_ && + SymEnumSymbols_ && SymSetContext_ && SymFromAddr_ && + SymGetModuleBase64_ && StackWalk64_ && + SymFunctionTableAccess64_)) + goto cleanup; + + /* initialize the symbol loading code */ + opts = SymGetOptions_(); + + /* Set the 'load lines' option to retrieve line number information; + set the Deferred Loads option to map the debug info in memory only + when needed. */ + SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS); + + /* Initialize the debughlp DLL with the default path and automatic + module enumeration (and loading of symbol tables) for this process. + */ + SymInitialize_(GetCurrentProcess(), NULL, TRUE); + + return TRUE; + } + +cleanup: + if (dbghelp_dll) + FreeLibrary(dbghelp_dll); + + return FALSE; +} + +/* Cleanup the dbghelp.dll library */ +static void +cleanup_debughlp() +{ + SymCleanup_(GetCurrentProcess()); + + FreeLibrary(dbghelp_dll); +} + +/* Create a filename based on a prefix, the timestamp and an extension. + check if the filename was already taken, retry 3 times. */ +BOOL +get_temp_filename(char *filename, const char *prefix, const char *ext) +{ + char temp_dir[MAX_PATH - 64]; + int i; + + if (! GetTempPath(MAX_PATH - 64, temp_dir)) + return FALSE; + + for (i = 0;i < 3;i++) + { + HANDLE file; + time_t now; + char time_str[64]; + + time(&now); + strftime(time_str, 64, "%Y%m%d%H%M%S", localtime(&now)); + sprintf(filename, "%s%s%s.%s", temp_dir, prefix, time_str, ext); + + file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, NULL); + if (file != INVALID_HANDLE_VALUE) + { + CloseHandle(file); + return TRUE; + } + } + + filename[0] = '\0'; + return FALSE; +} + +/* Unhandled exception callback set with SetUnhandledExceptionFilter() */ +LONG WINAPI +svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs) +{ + char dmp_filename[MAX_PATH]; + char log_filename[MAX_PATH]; + FILE *log_file; + + /* Check if the crash handler was already loaded (crash while handling the + crash) */ + if (dbghelp_dll != INVALID_HANDLE_VALUE) + return EXCEPTION_CONTINUE_SEARCH; + + /* don't log anything if we're running inside a debugger ... */ + if (is_debugger_present()) + return EXCEPTION_CONTINUE_SEARCH; + + /* ... or if we can't create the log files ... */ + if (!get_temp_filename(dmp_filename, LOGFILE_PREFIX, "dmp") || + !get_temp_filename(log_filename, LOGFILE_PREFIX, "log")) + return EXCEPTION_CONTINUE_SEARCH; + + /* If we can't load a recent version of the dbghelp.dll, pass on this + exception */ + if (!load_dbghelp_dll()) + return EXCEPTION_CONTINUE_SEARCH; + + /* open log file */ + log_file = fopen(log_filename, "w+"); + + /* write information about the process */ + fprintf(log_file, "\nProcess info:\n"); + write_process_info(ptrs ? ptrs->ExceptionRecord : NULL, + ptrs ? ptrs->ContextRecord : NULL, + log_file); + + /* write the stacktrace, if available */ + fprintf(log_file, "\nStacktrace:\n"); + write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, log_file); + + /* write the minidump file and use the callback to write the list of modules + to the log file */ + fprintf(log_file, "\n\nLoaded modules:\n"); + write_minidump_file(dmp_filename, ptrs, + write_module_info_callback, (void *)log_file); + + fclose(log_file); + + /* inform the user */ + fprintf(stderr, "This application has halted due to an unexpected error.\n" + "A crash report and minidump file were saved to disk, you" + " can find them here:\n" + "%s\n%s\n" + "Please send the log file to %s to help us analyze\nand " + "solve this problem.\n\n" + "NOTE: The crash report and minidump files can contain some" + " sensitive information\n(filenames, partial file content, " + "usernames and passwords etc.)\n", + log_filename, + dmp_filename, + CRASHREPORT_EMAIL); + + if (getenv("SVN_DBG_STACKTRACES_TO_STDERR") != NULL) + { + fprintf(stderr, "\nProcess info:\n"); + write_process_info(ptrs ? ptrs->ExceptionRecord : NULL, + ptrs ? ptrs->ContextRecord : NULL, + stderr); + fprintf(stderr, "\nStacktrace:\n"); + write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, stderr); + } + + fflush(stderr); + fflush(stdout); + + cleanup_debughlp(); + + /* terminate the application */ + return EXCEPTION_EXECUTE_HANDLER; +} +#endif /* SVN_USE_WIN32_CRASHHANDLER */ +#endif /* WIN32 */ diff --git a/subversion/libsvn_subr/win32_crashrpt.h b/subversion/libsvn_subr/win32_crashrpt.h new file mode 100644 index 000000000000..77c25c1b5ae6 --- /dev/null +++ b/subversion/libsvn_subr/win32_crashrpt.h @@ -0,0 +1,35 @@ +/* + * win32_crashrpt.h : shares the win32 crashhandler functions in libsvn_subr. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_WIN32_CRASHRPT_H +#define SVN_LIBSVN_SUBR_WIN32_CRASHRPT_H + +#ifdef WIN32 +#ifdef SVN_USE_WIN32_CRASHHANDLER + +LONG WINAPI svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs); + +#endif /* SVN_USE_WIN32_CRASHHANDLER */ +#endif /* WIN32 */ + +#endif /* SVN_LIBSVN_SUBR_WIN32_CRASHRPT_H */ diff --git a/subversion/libsvn_subr/win32_crashrpt_dll.h b/subversion/libsvn_subr/win32_crashrpt_dll.h new file mode 100644 index 000000000000..18a4fc978823 --- /dev/null +++ b/subversion/libsvn_subr/win32_crashrpt_dll.h @@ -0,0 +1,93 @@ +/* + * win32_crashrpt_dll.h : private header file. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_WIN32_CRASHRPT_DLL_H +#define SVN_LIBSVN_SUBR_WIN32_CRASHRPT_DLL_H + +#ifdef WIN32 +#ifdef SVN_USE_WIN32_CRASHHANDLER + +/* public functions in dbghelp.dll */ +typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD ProcessId, + HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); +typedef BOOL (WINAPI * SYMINITIALIZE)(HANDLE hProcess, PSTR UserSearchPath, + BOOL fInvadeProcess); +typedef DWORD (WINAPI * SYMSETOPTIONS)(DWORD SymOptions); + +typedef DWORD (WINAPI * SYMGETOPTIONS)(VOID); + +typedef BOOL (WINAPI * SYMCLEANUP)(HANDLE hProcess); + +typedef BOOL (WINAPI * SYMGETTYPEINFO)(HANDLE hProcess, DWORD64 ModBase, + ULONG TypeId, IMAGEHLP_SYMBOL_TYPE_INFO GetType, + PVOID pInfo); + +typedef BOOL (WINAPI * SYMGETLINEFROMADDR64)(HANDLE hProcess, DWORD64 dwAddr, + PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line); + +typedef BOOL (WINAPI * SYMENUMSYMBOLS)(HANDLE hProcess, ULONG64 BaseOfDll, PCSTR Mask, + PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + PVOID UserContext); + +typedef BOOL (WINAPI * SYMSETCONTEXT)(HANDLE hProcess, PIMAGEHLP_STACK_FRAME StackFrame, + PIMAGEHLP_CONTEXT Context); + +typedef BOOL (WINAPI * SYMFROMADDR)(HANDLE hProcess, DWORD64 Address, + PDWORD64 Displacement, PSYMBOL_INFO Symbol); + +typedef BOOL (WINAPI * STACKWALK64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); + +typedef PVOID (WINAPI * SYMFUNCTIONTABLEACCESS64)(HANDLE hProcess, DWORD64 AddrBase); + +typedef DWORD64 (WINAPI * SYMGETMODULEBASE64)(HANDLE hProcess, DWORD64 dwAddr); + +/* public functions in kernel32.dll */ +typedef BOOL (WINAPI * ISDEBUGGERPRESENT)(VOID); + +/* function pointers */ +MINIDUMPWRITEDUMP MiniDumpWriteDump_; +SYMINITIALIZE SymInitialize_; +SYMSETOPTIONS SymSetOptions_; +SYMGETOPTIONS SymGetOptions_; +SYMCLEANUP SymCleanup_; +SYMGETTYPEINFO SymGetTypeInfo_; +SYMGETLINEFROMADDR64 SymGetLineFromAddr64_; +SYMENUMSYMBOLS SymEnumSymbols_; +SYMSETCONTEXT SymSetContext_; +SYMFROMADDR SymFromAddr_; +STACKWALK64 StackWalk64_; +SYMFUNCTIONTABLEACCESS64 SymFunctionTableAccess64_; +SYMGETMODULEBASE64 SymGetModuleBase64_; + +#endif /* SVN_USE_WIN32_CRASHHANDLER */ +#endif /* WIN32 */ + +#endif /* SVN_LIBSVN_SUBR_WIN32_CRASHRPT_DLL_H */
\ No newline at end of file diff --git a/subversion/libsvn_subr/win32_crypto.c b/subversion/libsvn_subr/win32_crypto.c new file mode 100644 index 000000000000..a7e3828c90e2 --- /dev/null +++ b/subversion/libsvn_subr/win32_crypto.c @@ -0,0 +1,492 @@ +/* + * win32_crypto.c: win32 providers for SVN_AUTH_* + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* prevent "empty compilation unit" warning on e.g. UNIX */ +typedef int win32_crypto__dummy; + +/* ==================================================================== */ + +#if defined(WIN32) && !defined(__MINGW32__) + +/*** Includes. ***/ + +#include <apr_pools.h> +#include <apr_base64.h> +#include "svn_auth.h" +#include "svn_error.h" +#include "svn_hash.h" +#include "svn_utf.h" +#include "svn_config.h" +#include "svn_user.h" +#include "svn_base64.h" + +#include "private/svn_auth_private.h" + +#include "svn_private_config.h" + +#include <wincrypt.h> + + +/* The description string that's combined with unencrypted data by the + Windows CryptoAPI. Used during decryption to verify that the + encrypted data were valid. */ +static const WCHAR description[] = L"auth_svn.simple.wincrypt"; + + +/* Return a copy of ORIG, encrypted using the Windows CryptoAPI and + allocated from POOL. */ +const svn_string_t * +encrypt_data(const svn_string_t *orig, + apr_pool_t *pool) +{ + DATA_BLOB blobin; + DATA_BLOB blobout; + const svn_string_t *crypted = NULL; + + blobin.cbData = orig->len; + blobin.pbData = (BYTE *)orig->data; + if (CryptProtectData(&blobin, description, NULL, NULL, NULL, + CRYPTPROTECT_UI_FORBIDDEN, &blobout)) + { + crypted = svn_string_ncreate((const char *)blobout.pbData, + blobout.cbData, pool); + LocalFree(blobout.pbData); + } + return crypted; +} + +/* Return a copy of CRYPTED, decrypted using the Windows CryptoAPI and + allocated from POOL. */ +const svn_string_t * +decrypt_data(const svn_string_t *crypted, + apr_pool_t *pool) +{ + DATA_BLOB blobin; + DATA_BLOB blobout; + LPWSTR descr; + const svn_string_t *orig = NULL; + + blobin.cbData = crypted->len; + blobin.pbData = (BYTE *)crypted->data; + if (CryptUnprotectData(&blobin, &descr, NULL, NULL, NULL, + CRYPTPROTECT_UI_FORBIDDEN, &blobout)) + { + if (0 == lstrcmpW(descr, description)) + orig = svn_string_ncreate((const char *)blobout.pbData, + blobout.cbData, pool); + LocalFree(blobout.pbData); + LocalFree(descr); + } + return orig; +} + + +/*-----------------------------------------------------------------------*/ +/* Windows simple provider, encrypts the password on Win2k and later. */ +/*-----------------------------------------------------------------------*/ + +/* Implementation of svn_auth__password_set_t that encrypts + the incoming password using the Windows CryptoAPI. */ +static svn_error_t * +windows_password_encrypter(svn_boolean_t *done, + apr_hash_t *creds, + const char *realmstring, + const char *username, + const char *in, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + const svn_string_t *coded; + + coded = encrypt_data(svn_string_create(in, pool), pool); + if (coded) + { + coded = svn_base64_encode_string2(coded, FALSE, pool); + SVN_ERR(svn_auth__simple_password_set(done, creds, realmstring, username, + coded->data, parameters, + non_interactive, pool)); + } + + return SVN_NO_ERROR; +} + +/* Implementation of svn_auth__password_get_t that decrypts + the incoming password using the Windows CryptoAPI and verifies its + validity. */ +static svn_error_t * +windows_password_decrypter(svn_boolean_t *done, + const char **out, + apr_hash_t *creds, + const char *realmstring, + const char *username, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + const svn_string_t *orig; + const char *in; + + SVN_ERR(svn_auth__simple_password_get(done, &in, creds, realmstring, username, + parameters, non_interactive, pool)); + if (!*done) + return SVN_NO_ERROR; + + orig = svn_base64_decode_string(svn_string_create(in, pool), pool); + orig = decrypt_data(orig, pool); + if (orig) + { + *out = orig->data; + *done = TRUE; + } + else + { + *done = FALSE; + } + return SVN_NO_ERROR; +} + +/* Get cached encrypted credentials from the simple provider's cache. */ +static svn_error_t * +windows_simple_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_get(credentials, + iter_baton, + provider_baton, + parameters, + realmstring, + windows_password_decrypter, + SVN_AUTH__WINCRYPT_PASSWORD_TYPE, + pool); +} + +/* Save encrypted credentials to the simple provider's cache. */ +static svn_error_t * +windows_simple_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__simple_creds_cache_set(saved, credentials, + provider_baton, + parameters, + realmstring, + windows_password_encrypter, + SVN_AUTH__WINCRYPT_PASSWORD_TYPE, + pool); +} + +static const svn_auth_provider_t windows_simple_provider = { + SVN_AUTH_CRED_SIMPLE, + windows_simple_first_creds, + NULL, + windows_simple_save_creds +}; + + +/* Public API */ +void +svn_auth_get_windows_simple_provider(svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &windows_simple_provider; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Windows SSL server trust provider, validates ssl certificate using */ +/* CryptoApi. */ +/*-----------------------------------------------------------------------*/ + +/* Implementation of svn_auth__password_set_t that encrypts + the incoming password using the Windows CryptoAPI. */ +static svn_error_t * +windows_ssl_client_cert_pw_encrypter(svn_boolean_t *done, + apr_hash_t *creds, + const char *realmstring, + const char *username, + const char *in, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + const svn_string_t *coded; + + coded = encrypt_data(svn_string_create(in, pool), pool); + if (coded) + { + coded = svn_base64_encode_string2(coded, FALSE, pool); + SVN_ERR(svn_auth__ssl_client_cert_pw_set(done, creds, realmstring, + username, coded->data, + parameters, non_interactive, + pool)); + } + + return SVN_NO_ERROR; +} + +/* Implementation of svn_auth__password_get_t that decrypts + the incoming password using the Windows CryptoAPI and verifies its + validity. */ +static svn_error_t * +windows_ssl_client_cert_pw_decrypter(svn_boolean_t *done, + const char **out, + apr_hash_t *creds, + const char *realmstring, + const char *username, + apr_hash_t *parameters, + svn_boolean_t non_interactive, + apr_pool_t *pool) +{ + const svn_string_t *orig; + const char *in; + + SVN_ERR(svn_auth__ssl_client_cert_pw_get(done, &in, creds, realmstring, + username, parameters, + non_interactive, pool)); + if (!*done) + return SVN_NO_ERROR; + + orig = svn_base64_decode_string(svn_string_create(in, pool), pool); + orig = decrypt_data(orig, pool); + if (orig) + { + *out = orig->data; + *done = TRUE; + } + else + { + *done = FALSE; + } + return SVN_NO_ERROR; +} + +/* Get cached encrypted credentials from the simple provider's cache. */ +static svn_error_t * +windows_ssl_client_cert_pw_first_creds(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__ssl_client_cert_pw_cache_get( + credentials, iter_baton, provider_baton, parameters, realmstring, + windows_ssl_client_cert_pw_decrypter, + SVN_AUTH__WINCRYPT_PASSWORD_TYPE, pool); +} + +/* Save encrypted credentials to the simple provider's cache. */ +static svn_error_t * +windows_ssl_client_cert_pw_save_creds(svn_boolean_t *saved, + void *credentials, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + return svn_auth__ssl_client_cert_pw_cache_set( + saved, credentials, provider_baton, parameters, realmstring, + windows_ssl_client_cert_pw_encrypter, + SVN_AUTH__WINCRYPT_PASSWORD_TYPE, pool); +} + +static const svn_auth_provider_t windows_ssl_client_cert_pw_provider = { + SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, + windows_ssl_client_cert_pw_first_creds, + NULL, + windows_ssl_client_cert_pw_save_creds +}; + + +/* Public API */ +void +svn_auth_get_windows_ssl_client_cert_pw_provider + (svn_auth_provider_object_t **provider, + apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &windows_ssl_client_cert_pw_provider; + *provider = po; +} + + +/*-----------------------------------------------------------------------*/ +/* Windows SSL server trust provider, validates ssl certificate using */ +/* CryptoApi. */ +/*-----------------------------------------------------------------------*/ + +/* Helper to create CryptoAPI CERT_CONTEXT from base64 encoded BASE64_CERT. + * Returns NULL on error. + */ +static PCCERT_CONTEXT +certcontext_from_base64(const char *base64_cert, apr_pool_t *pool) +{ + PCCERT_CONTEXT cert_context = NULL; + int cert_len; + BYTE *binary_cert; + + /* Use apr-util as CryptStringToBinaryA is available only on XP+. */ + binary_cert = apr_palloc(pool, + apr_base64_decode_len(base64_cert)); + cert_len = apr_base64_decode((char*)binary_cert, base64_cert); + + /* Parse the certificate into a context. */ + cert_context = CertCreateCertificateContext + (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, binary_cert, cert_len); + + return cert_context; +} + +/* Helper for windows_ssl_server_trust_first_credentials for validating + * certificate using CryptoApi. Sets *OK_P to TRUE if base64 encoded ASCII_CERT + * certificate considered as valid. + */ +static svn_error_t * +windows_validate_certificate(svn_boolean_t *ok_p, + const char *ascii_cert, + apr_pool_t *pool) +{ + PCCERT_CONTEXT cert_context = NULL; + CERT_CHAIN_PARA chain_para; + PCCERT_CHAIN_CONTEXT chain_context = NULL; + + *ok_p = FALSE; + + /* Parse the certificate into a context. */ + cert_context = certcontext_from_base64(ascii_cert, pool); + + if (cert_context) + { + /* Retrieve the certificate chain of the certificate + (a certificate without a valid root does not have a chain). */ + memset(&chain_para, 0, sizeof(chain_para)); + chain_para.cbSize = sizeof(chain_para); + + if (CertGetCertificateChain(NULL, cert_context, NULL, NULL, &chain_para, + CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, + NULL, &chain_context)) + { + CERT_CHAIN_POLICY_PARA policy_para; + CERT_CHAIN_POLICY_STATUS policy_status; + + policy_para.cbSize = sizeof(policy_para); + policy_para.dwFlags = 0; + policy_para.pvExtraPolicyPara = NULL; + + policy_status.cbSize = sizeof(policy_status); + + if (CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, + chain_context, &policy_para, + &policy_status)) + { + if (policy_status.dwError == S_OK) + { + /* Windows thinks the certificate is valid. */ + *ok_p = TRUE; + } + } + + CertFreeCertificateChain(chain_context); + } + CertFreeCertificateContext(cert_context); + } + + return SVN_NO_ERROR; +} + +/* Retrieve ssl server CA failure overrides (if any) from CryptoApi. */ +static svn_error_t * +windows_ssl_server_trust_first_credentials(void **credentials, + void **iter_baton, + void *provider_baton, + apr_hash_t *parameters, + const char *realmstring, + apr_pool_t *pool) +{ + apr_uint32_t *failures = svn_hash_gets(parameters, + SVN_AUTH_PARAM_SSL_SERVER_FAILURES); + const svn_auth_ssl_server_cert_info_t *cert_info = + svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO); + + *credentials = NULL; + *iter_baton = NULL; + + /* We can accept only unknown certificate authority. */ + if (*failures & SVN_AUTH_SSL_UNKNOWNCA) + { + svn_boolean_t ok; + + SVN_ERR(windows_validate_certificate(&ok, cert_info->ascii_cert, pool)); + + /* Windows thinks that certificate is ok. */ + if (ok) + { + /* Clear failure flag. */ + *failures &= ~SVN_AUTH_SSL_UNKNOWNCA; + } + } + + /* If all failures are cleared now, we return the creds */ + if (! *failures) + { + svn_auth_cred_ssl_server_trust_t *creds = + apr_pcalloc(pool, sizeof(*creds)); + creds->may_save = FALSE; /* No need to save it. */ + *credentials = creds; + } + + return SVN_NO_ERROR; +} + +static const svn_auth_provider_t windows_server_trust_provider = { + SVN_AUTH_CRED_SSL_SERVER_TRUST, + windows_ssl_server_trust_first_credentials, + NULL, + NULL, +}; + +/* Public API */ +void +svn_auth_get_windows_ssl_server_trust_provider + (svn_auth_provider_object_t **provider, apr_pool_t *pool) +{ + svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); + + po->vtable = &windows_server_trust_provider; + *provider = po; +} + +#endif /* WIN32 */ diff --git a/subversion/libsvn_subr/win32_xlate.c b/subversion/libsvn_subr/win32_xlate.c new file mode 100644 index 000000000000..efe9c056d74e --- /dev/null +++ b/subversion/libsvn_subr/win32_xlate.c @@ -0,0 +1,238 @@ +/* + * win32_xlate.c : Windows xlate stuff. + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* prevent "empty compilation unit" warning on e.g. UNIX */ +typedef int win32_xlate__dummy; + +#ifdef WIN32 + +/* Define _WIN32_DCOM for CoInitializeEx(). */ +#define _WIN32_DCOM + +/* We must include windows.h ourselves or apr.h includes it for us with + many ignore options set. Including Winsock is required to resolve IPv6 + compilation errors. APR_HAVE_IPV6 is only defined after including + apr.h, so we can't detect this case here. */ + +/* winsock2.h includes windows.h */ +#include <winsock2.h> +#include <Ws2tcpip.h> +#include <mlang.h> + +#include <apr.h> +#include <apr_errno.h> +#include <apr_portable.h> + +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_utf.h" +#include "private/svn_atomic.h" + +#include "win32_xlate.h" + +static svn_atomic_t com_initialized = 0; + +/* Initializes COM and keeps COM available until process exit. + Implements svn_atomic__init_once init_func */ +static svn_error_t * +initialize_com(void *baton, apr_pool_t* pool) +{ + /* Try to initialize for apartment-threaded object concurrency. */ + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + if (hr == RPC_E_CHANGED_MODE) + { + /* COM already initalized for multi-threaded object concurrency. We are + neutral to object concurrency so try to initalize it in the same way + for us, to keep an handle open. */ + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + } + + if (FAILED(hr)) + return svn_error_create(APR_EGENERAL, NULL, NULL); + + return SVN_NO_ERROR; +} + +typedef struct win32_xlate_t +{ + UINT from_page_id; + UINT to_page_id; +} win32_xlate_t; + +static apr_status_t +get_page_id_from_name(UINT *page_id_p, const char *page_name, apr_pool_t *pool) +{ + IMultiLanguage * mlang = NULL; + HRESULT hr; + MIMECSETINFO page_info; + WCHAR ucs2_page_name[128]; + svn_error_t *err; + + if (page_name == SVN_APR_DEFAULT_CHARSET) + { + *page_id_p = CP_ACP; + return APR_SUCCESS; + } + else if (page_name == SVN_APR_LOCALE_CHARSET) + { + *page_id_p = CP_THREAD_ACP; /* Valid on Windows 2000+ */ + return APR_SUCCESS; + } + else if (!strcmp(page_name, "UTF-8")) + { + *page_id_p = CP_UTF8; + return APR_SUCCESS; + } + + /* Use codepage identifier nnn if the codepage name is in the form + of "CPnnn". + We need this code since apr_os_locale_encoding() and svn_cmdline_init() + generates such codepage names even if they are not valid IANA charset + name. */ + if ((page_name[0] == 'c' || page_name[0] == 'C') + && (page_name[1] == 'p' || page_name[1] == 'P')) + { + *page_id_p = atoi(page_name + 2); + return APR_SUCCESS; + } + + err = svn_atomic__init_once(&com_initialized, initialize_com, NULL, pool); + + if (err) + { + svn_error_clear(err); + return APR_EGENERAL; + } + + hr = CoCreateInstance(&CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, + &IID_IMultiLanguage, (void **) &mlang); + + if (FAILED(hr)) + return APR_EGENERAL; + + /* Convert page name to wide string. */ + MultiByteToWideChar(CP_UTF8, 0, page_name, -1, ucs2_page_name, + sizeof(ucs2_page_name) / sizeof(ucs2_page_name[0])); + memset(&page_info, 0, sizeof(page_info)); + hr = mlang->lpVtbl->GetCharsetInfo(mlang, ucs2_page_name, &page_info); + if (FAILED(hr)) + { + mlang->lpVtbl->Release(mlang); + return APR_EINVAL; + } + + if (page_info.uiInternetEncoding) + *page_id_p = page_info.uiInternetEncoding; + else + *page_id_p = page_info.uiCodePage; + + mlang->lpVtbl->Release(mlang); + + return APR_SUCCESS; +} + +apr_status_t +svn_subr__win32_xlate_open(win32_xlate_t **xlate_p, const char *topage, + const char *frompage, apr_pool_t *pool) +{ + UINT from_page_id, to_page_id; + apr_status_t apr_err = APR_SUCCESS; + win32_xlate_t *xlate; + + apr_err = get_page_id_from_name(&to_page_id, topage, pool); + if (apr_err == APR_SUCCESS) + apr_err = get_page_id_from_name(&from_page_id, frompage, pool); + + if (apr_err == APR_SUCCESS) + { + xlate = apr_palloc(pool, sizeof(*xlate)); + xlate->from_page_id = from_page_id; + xlate->to_page_id = to_page_id; + + *xlate_p = xlate; + } + + return apr_err; +} + +apr_status_t +svn_subr__win32_xlate_to_stringbuf(win32_xlate_t *handle, + const char *src_data, + apr_size_t src_length, + svn_stringbuf_t **dest, + apr_pool_t *pool) +{ + WCHAR * wide_str; + int retval, wide_size; + + if (src_length == 0) + { + *dest = svn_stringbuf_create_empty(pool); + return APR_SUCCESS; + } + + retval = MultiByteToWideChar(handle->from_page_id, 0, src_data, src_length, + NULL, 0); + if (retval == 0) + return apr_get_os_error(); + + wide_size = retval; + + /* Allocate temporary buffer for small strings on stack instead of heap. */ + if (wide_size <= MAX_PATH) + { + wide_str = alloca(wide_size * sizeof(WCHAR)); + } + else + { + wide_str = apr_palloc(pool, wide_size * sizeof(WCHAR)); + } + + retval = MultiByteToWideChar(handle->from_page_id, 0, src_data, src_length, + wide_str, wide_size); + + if (retval == 0) + return apr_get_os_error(); + + retval = WideCharToMultiByte(handle->to_page_id, 0, wide_str, wide_size, + NULL, 0, NULL, NULL); + + if (retval == 0) + return apr_get_os_error(); + + /* Ensure that buffer is enough to hold result string and termination + character. */ + *dest = svn_stringbuf_create_ensure(retval + 1, pool); + (*dest)->len = retval; + + retval = WideCharToMultiByte(handle->to_page_id, 0, wide_str, wide_size, + (*dest)->data, (*dest)->len, NULL, NULL); + if (retval == 0) + return apr_get_os_error(); + + (*dest)->len = retval; + return APR_SUCCESS; +} + +#endif /* WIN32 */ diff --git a/subversion/libsvn_subr/win32_xlate.h b/subversion/libsvn_subr/win32_xlate.h new file mode 100644 index 000000000000..82fc832235e8 --- /dev/null +++ b/subversion/libsvn_subr/win32_xlate.h @@ -0,0 +1,52 @@ +/* + * win32_xlate.h : Windows xlate stuff. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_SUBR_WIN32_XLATE_H +#define SVN_LIBSVN_SUBR_WIN32_XLATE_H + +#ifdef WIN32 + +/* Opaque translation buffer. */ +typedef struct win32_xlate_t win32_xlate_t; + +/* Set *XLATE_P to a handle node for converting from FROMPAGE to TOPAGE. + Returns APR_EINVAL or APR_ENOTIMPL, if a conversion isn't supported. + If fail for any other reason, return the error. + + Allocate *RET in POOL. */ +apr_status_t svn_subr__win32_xlate_open(win32_xlate_t **xlate_p, + const char *topage, + const char *frompage, + apr_pool_t *pool); + +/* Convert SRC_LENGTH bytes of SRC_DATA in NODE->handle, store the result + in *DEST, which is allocated in POOL. */ +apr_status_t svn_subr__win32_xlate_to_stringbuf(win32_xlate_t *handle, + const char *src_data, + apr_size_t src_length, + svn_stringbuf_t **dest, + apr_pool_t *pool); + +#endif /* WIN32 */ + +#endif /* SVN_LIBSVN_SUBR_WIN32_XLATE_H */ diff --git a/subversion/libsvn_subr/xml.c b/subversion/libsvn_subr/xml.c new file mode 100644 index 000000000000..a9d834a82c54 --- /dev/null +++ b/subversion/libsvn_subr/xml.c @@ -0,0 +1,655 @@ +/* + * xml.c: xml helper code shared among the Subversion libraries. + * + * ==================================================================== + * 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 <string.h> +#include <assert.h> + +#include "svn_private_config.h" /* for SVN_HAVE_OLD_EXPAT */ +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_xml.h" +#include "svn_error.h" +#include "svn_ctype.h" + +#include "private/svn_utf_private.h" + +#ifdef SVN_HAVE_OLD_EXPAT +#include <xmlparse.h> +#else +#include <expat.h> +#endif + +#ifdef XML_UNICODE +#error Expat is unusable -- it has been compiled for wide characters +#endif + +/* The private internals for a parser object. */ +struct svn_xml_parser_t +{ + /** the expat parser */ + XML_Parser parser; + + /** the SVN callbacks to call from the Expat callbacks */ + svn_xml_start_elem start_handler; + svn_xml_end_elem end_handler; + svn_xml_char_data data_handler; + + /** the user's baton for private data */ + void *baton; + + /** if non-@c NULL, an error happened while parsing */ + svn_error_t *error; + + /** where this object is allocated, so we can free it easily */ + apr_pool_t *pool; + +}; + + +/*** XML character validation ***/ + +svn_boolean_t +svn_xml_is_xml_safe(const char *data, apr_size_t len) +{ + const char *end = data + len; + const char *p; + + if (! svn_utf__is_valid(data, len)) + return FALSE; + + for (p = data; p < end; p++) + { + unsigned char c = *p; + + if (svn_ctype_iscntrl(c)) + { + if ((c != SVN_CTYPE_ASCII_TAB) + && (c != SVN_CTYPE_ASCII_LINEFEED) + && (c != SVN_CTYPE_ASCII_CARRIAGERETURN) + && (c != SVN_CTYPE_ASCII_DELETE)) + return FALSE; + } + } + return TRUE; +} + + + + + +/*** XML escaping. ***/ + +/* ### ...? + * + * If *OUTSTR is @c NULL, set *OUTSTR to a new stringbuf allocated + * in POOL, else append to the existing stringbuf there. + */ +static void +xml_escape_cdata(svn_stringbuf_t **outstr, + const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + const char *end = data + len; + const char *p = data, *q; + + if (*outstr == NULL) + *outstr = svn_stringbuf_create_empty(pool); + + while (1) + { + /* Find a character which needs to be quoted and append bytes up + to that point. Strictly speaking, '>' only needs to be + quoted if it follows "]]", but it's easier to quote it all + the time. + + So, why are we escaping '\r' here? Well, according to the + XML spec, '\r\n' gets converted to '\n' during XML parsing. + Also, any '\r' not followed by '\n' is converted to '\n'. By + golly, if we say we want to escape a '\r', we want to make + sure it remains a '\r'! */ + q = p; + while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r') + q++; + svn_stringbuf_appendbytes(*outstr, p, q - p); + + /* We may already be a winner. */ + if (q == end) + break; + + /* Append the entity reference for the character. */ + if (*q == '&') + svn_stringbuf_appendcstr(*outstr, "&"); + else if (*q == '<') + svn_stringbuf_appendcstr(*outstr, "<"); + else if (*q == '>') + svn_stringbuf_appendcstr(*outstr, ">"); + else if (*q == '\r') + svn_stringbuf_appendcstr(*outstr, " "); + + p = q + 1; + } +} + +/* Essentially the same as xml_escape_cdata, with the addition of + whitespace and quote characters. */ +static void +xml_escape_attr(svn_stringbuf_t **outstr, + const char *data, + apr_size_t len, + apr_pool_t *pool) +{ + const char *end = data + len; + const char *p = data, *q; + + if (*outstr == NULL) + *outstr = svn_stringbuf_create_ensure(len, pool); + + while (1) + { + /* Find a character which needs to be quoted and append bytes up + to that point. */ + q = p; + while (q < end && *q != '&' && *q != '<' && *q != '>' + && *q != '"' && *q != '\'' && *q != '\r' + && *q != '\n' && *q != '\t') + q++; + svn_stringbuf_appendbytes(*outstr, p, q - p); + + /* We may already be a winner. */ + if (q == end) + break; + + /* Append the entity reference for the character. */ + if (*q == '&') + svn_stringbuf_appendcstr(*outstr, "&"); + else if (*q == '<') + svn_stringbuf_appendcstr(*outstr, "<"); + else if (*q == '>') + svn_stringbuf_appendcstr(*outstr, ">"); + else if (*q == '"') + svn_stringbuf_appendcstr(*outstr, """); + else if (*q == '\'') + svn_stringbuf_appendcstr(*outstr, "'"); + else if (*q == '\r') + svn_stringbuf_appendcstr(*outstr, " "); + else if (*q == '\n') + svn_stringbuf_appendcstr(*outstr, " "); + else if (*q == '\t') + svn_stringbuf_appendcstr(*outstr, "	"); + + p = q + 1; + } +} + + +void +svn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr, + const svn_stringbuf_t *string, + apr_pool_t *pool) +{ + xml_escape_cdata(outstr, string->data, string->len, pool); +} + + +void +svn_xml_escape_cdata_string(svn_stringbuf_t **outstr, + const svn_string_t *string, + apr_pool_t *pool) +{ + xml_escape_cdata(outstr, string->data, string->len, pool); +} + + +void +svn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr, + const char *string, + apr_pool_t *pool) +{ + xml_escape_cdata(outstr, string, (apr_size_t) strlen(string), pool); +} + + +void +svn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr, + const svn_stringbuf_t *string, + apr_pool_t *pool) +{ + xml_escape_attr(outstr, string->data, string->len, pool); +} + + +void +svn_xml_escape_attr_string(svn_stringbuf_t **outstr, + const svn_string_t *string, + apr_pool_t *pool) +{ + xml_escape_attr(outstr, string->data, string->len, pool); +} + + +void +svn_xml_escape_attr_cstring(svn_stringbuf_t **outstr, + const char *string, + apr_pool_t *pool) +{ + xml_escape_attr(outstr, string, (apr_size_t) strlen(string), pool); +} + + +const char * +svn_xml_fuzzy_escape(const char *string, apr_pool_t *pool) +{ + const char *end = string + strlen(string); + const char *p = string, *q; + svn_stringbuf_t *outstr; + char escaped_char[6]; /* ? \ u u u \0 */ + + for (q = p; q < end; q++) + { + if (svn_ctype_iscntrl(*q) + && ! ((*q == '\n') || (*q == '\r') || (*q == '\t'))) + break; + } + + /* Return original string if no unsafe characters found. */ + if (q == end) + return string; + + outstr = svn_stringbuf_create_empty(pool); + while (1) + { + q = p; + + /* Traverse till either unsafe character or eos. */ + while ((q < end) + && ((! svn_ctype_iscntrl(*q)) + || (*q == '\n') || (*q == '\r') || (*q == '\t'))) + q++; + + /* copy chunk before marker */ + svn_stringbuf_appendbytes(outstr, p, q - p); + + if (q == end) + break; + + /* Append an escaped version of the unsafe character. + + ### This format was chosen for consistency with + ### svn_utf__cstring_from_utf8_fuzzy(). The two functions + ### should probably share code, even though they escape + ### different characters. + */ + apr_snprintf(escaped_char, sizeof(escaped_char), "?\\%03u", + (unsigned char) *q); + svn_stringbuf_appendcstr(outstr, escaped_char); + + p = q + 1; + } + + return outstr->data; +} + + +/*** Map from the Expat callback types to the SVN XML types. ***/ + +static void expat_start_handler(void *userData, + const XML_Char *name, + const XML_Char **atts) +{ + svn_xml_parser_t *svn_parser = userData; + + (*svn_parser->start_handler)(svn_parser->baton, name, atts); +} + +static void expat_end_handler(void *userData, const XML_Char *name) +{ + svn_xml_parser_t *svn_parser = userData; + + (*svn_parser->end_handler)(svn_parser->baton, name); +} + +static void expat_data_handler(void *userData, const XML_Char *s, int len) +{ + svn_xml_parser_t *svn_parser = userData; + + (*svn_parser->data_handler)(svn_parser->baton, s, (apr_size_t)len); +} + + +/*** Making a parser. ***/ + +svn_xml_parser_t * +svn_xml_make_parser(void *baton, + svn_xml_start_elem start_handler, + svn_xml_end_elem end_handler, + svn_xml_char_data data_handler, + apr_pool_t *pool) +{ + svn_xml_parser_t *svn_parser; + apr_pool_t *subpool; + + XML_Parser parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, + start_handler ? expat_start_handler : NULL, + end_handler ? expat_end_handler : NULL); + XML_SetCharacterDataHandler(parser, + data_handler ? expat_data_handler : NULL); + + /* ### we probably don't want this pool; or at least we should pass it + ### to the callbacks and clear it periodically. */ + subpool = svn_pool_create(pool); + + svn_parser = apr_pcalloc(subpool, sizeof(*svn_parser)); + + svn_parser->parser = parser; + svn_parser->start_handler = start_handler; + svn_parser->end_handler = end_handler; + svn_parser->data_handler = data_handler; + svn_parser->baton = baton; + svn_parser->pool = subpool; + + /* store our parser info as the UserData in the Expat parser */ + XML_SetUserData(parser, svn_parser); + + return svn_parser; +} + + +/* Free a parser */ +void +svn_xml_free_parser(svn_xml_parser_t *svn_parser) +{ + /* Free the expat parser */ + XML_ParserFree(svn_parser->parser); + + /* Free the subversion parser */ + svn_pool_destroy(svn_parser->pool); +} + + + + +svn_error_t * +svn_xml_parse(svn_xml_parser_t *svn_parser, + const char *buf, + apr_size_t len, + svn_boolean_t is_final) +{ + svn_error_t *err; + int success; + + /* Parse some xml data */ + success = XML_Parse(svn_parser->parser, buf, (int) len, is_final); + + /* If expat choked internally, return its error. */ + if (! success) + { + /* Line num is "int" in Expat v1, "long" in v2; hide the difference. */ + long line = XML_GetCurrentLineNumber(svn_parser->parser); + + err = svn_error_createf + (SVN_ERR_XML_MALFORMED, NULL, + _("Malformed XML: %s at line %ld"), + XML_ErrorString(XML_GetErrorCode(svn_parser->parser)), line); + + /* Kill all parsers and return the expat error */ + svn_xml_free_parser(svn_parser); + return err; + } + + /* Did an error occur somewhere *inside* the expat callbacks? */ + if (svn_parser->error) + { + err = svn_parser->error; + svn_xml_free_parser(svn_parser); + return err; + } + + return SVN_NO_ERROR; +} + + + +void svn_xml_signal_bailout(svn_error_t *error, + svn_xml_parser_t *svn_parser) +{ + /* This will cause the current XML_Parse() call to finish quickly! */ + XML_SetElementHandler(svn_parser->parser, NULL, NULL); + XML_SetCharacterDataHandler(svn_parser->parser, NULL); + + /* Once outside of XML_Parse(), the existence of this field will + cause svn_delta_parse()'s main read-loop to return error. */ + svn_parser->error = error; +} + + + + + + + + +/*** Attribute walking. ***/ + +const char * +svn_xml_get_attr_value(const char *name, const char *const *atts) +{ + while (atts && (*atts)) + { + if (strcmp(atts[0], name) == 0) + return atts[1]; + else + atts += 2; /* continue looping */ + } + + /* Else no such attribute name seen. */ + return NULL; +} + + + +/*** Printing XML ***/ + +void +svn_xml_make_header2(svn_stringbuf_t **str, const char *encoding, + apr_pool_t *pool) +{ + + if (*str == NULL) + *str = svn_stringbuf_create_empty(pool); + svn_stringbuf_appendcstr(*str, "<?xml version=\"1.0\""); + if (encoding) + { + encoding = apr_psprintf(pool, " encoding=\"%s\"", encoding); + svn_stringbuf_appendcstr(*str, encoding); + } + svn_stringbuf_appendcstr(*str, "?>\n"); +} + + + +/*** Creating attribute hashes. ***/ + +/* Combine an existing attribute list ATTS with a HASH that itself + represents an attribute list. Iff PRESERVE is true, then no value + already in HASH will be changed, else values from ATTS will + override previous values in HASH. */ +static void +amalgamate(const char **atts, + apr_hash_t *ht, + svn_boolean_t preserve, + apr_pool_t *pool) +{ + const char *key; + + if (atts) + for (key = *atts; key; key = *(++atts)) + { + const char *val = *(++atts); + size_t keylen; + assert(key != NULL); + /* kff todo: should we also insist that val be non-null here? + Probably. */ + + keylen = strlen(key); + if (preserve && ((apr_hash_get(ht, key, keylen)) != NULL)) + continue; + else + apr_hash_set(ht, apr_pstrndup(pool, key, keylen), keylen, + val ? apr_pstrdup(pool, val) : NULL); + } +} + + +apr_hash_t * +svn_xml_ap_to_hash(va_list ap, apr_pool_t *pool) +{ + apr_hash_t *ht = apr_hash_make(pool); + const char *key; + + while ((key = va_arg(ap, char *)) != NULL) + { + const char *val = va_arg(ap, const char *); + svn_hash_sets(ht, key, val); + } + + return ht; +} + + +apr_hash_t * +svn_xml_make_att_hash(const char **atts, apr_pool_t *pool) +{ + apr_hash_t *ht = apr_hash_make(pool); + amalgamate(atts, ht, 0, pool); /* third arg irrelevant in this case */ + return ht; +} + + +void +svn_xml_hash_atts_overlaying(const char **atts, + apr_hash_t *ht, + apr_pool_t *pool) +{ + amalgamate(atts, ht, 0, pool); +} + + +void +svn_xml_hash_atts_preserving(const char **atts, + apr_hash_t *ht, + apr_pool_t *pool) +{ + amalgamate(atts, ht, 1, pool); +} + + + +/*** Making XML tags. ***/ + + +void +svn_xml_make_open_tag_hash(svn_stringbuf_t **str, + apr_pool_t *pool, + enum svn_xml_open_tag_style style, + const char *tagname, + apr_hash_t *attributes) +{ + apr_hash_index_t *hi; + apr_size_t est_size = strlen(tagname) + 4 + apr_hash_count(attributes) * 30; + + if (*str == NULL) + *str = svn_stringbuf_create_ensure(est_size, pool); + + svn_stringbuf_appendcstr(*str, "<"); + svn_stringbuf_appendcstr(*str, tagname); + + for (hi = apr_hash_first(pool, attributes); hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + + apr_hash_this(hi, &key, NULL, &val); + assert(val != NULL); + + svn_stringbuf_appendcstr(*str, "\n "); + svn_stringbuf_appendcstr(*str, key); + svn_stringbuf_appendcstr(*str, "=\""); + svn_xml_escape_attr_cstring(str, val, pool); + svn_stringbuf_appendcstr(*str, "\""); + } + + if (style == svn_xml_self_closing) + svn_stringbuf_appendcstr(*str, "/"); + svn_stringbuf_appendcstr(*str, ">"); + if (style != svn_xml_protect_pcdata) + svn_stringbuf_appendcstr(*str, "\n"); +} + + +void +svn_xml_make_open_tag_v(svn_stringbuf_t **str, + apr_pool_t *pool, + enum svn_xml_open_tag_style style, + const char *tagname, + va_list ap) +{ + apr_pool_t *subpool = svn_pool_create(pool); + apr_hash_t *ht = svn_xml_ap_to_hash(ap, subpool); + + svn_xml_make_open_tag_hash(str, pool, style, tagname, ht); + svn_pool_destroy(subpool); +} + + + +void +svn_xml_make_open_tag(svn_stringbuf_t **str, + apr_pool_t *pool, + enum svn_xml_open_tag_style style, + const char *tagname, + ...) +{ + va_list ap; + + va_start(ap, tagname); + svn_xml_make_open_tag_v(str, pool, style, tagname, ap); + va_end(ap); +} + + +void svn_xml_make_close_tag(svn_stringbuf_t **str, + apr_pool_t *pool, + const char *tagname) +{ + if (*str == NULL) + *str = svn_stringbuf_create_empty(pool); + + svn_stringbuf_appendcstr(*str, "</"); + svn_stringbuf_appendcstr(*str, tagname); + svn_stringbuf_appendcstr(*str, ">\n"); +} |