summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_subr
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_subr
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_subr')
-rw-r--r--subversion/libsvn_subr/adler32.c101
-rw-r--r--subversion/libsvn_subr/atomic.c85
-rw-r--r--subversion/libsvn_subr/auth.c652
-rw-r--r--subversion/libsvn_subr/auth.h49
-rw-r--r--subversion/libsvn_subr/base64.c567
-rw-r--r--subversion/libsvn_subr/cache-inprocess.c648
-rw-r--r--subversion/libsvn_subr/cache-membuffer.c2369
-rw-r--r--subversion/libsvn_subr/cache-memcache.c583
-rw-r--r--subversion/libsvn_subr/cache.c265
-rw-r--r--subversion/libsvn_subr/cache.h109
-rw-r--r--subversion/libsvn_subr/cache_config.c169
-rw-r--r--subversion/libsvn_subr/checksum.c500
-rw-r--r--subversion/libsvn_subr/cmdline.c1312
-rw-r--r--subversion/libsvn_subr/compat.c159
-rw-r--r--subversion/libsvn_subr/config.c1208
-rw-r--r--subversion/libsvn_subr/config_auth.c277
-rw-r--r--subversion/libsvn_subr/config_file.c1260
-rw-r--r--subversion/libsvn_subr/config_impl.h161
-rw-r--r--subversion/libsvn_subr/config_win.c259
-rw-r--r--subversion/libsvn_subr/crypto.c705
-rw-r--r--subversion/libsvn_subr/crypto.h141
-rw-r--r--subversion/libsvn_subr/ctype.c319
-rw-r--r--subversion/libsvn_subr/date.c393
-rw-r--r--subversion/libsvn_subr/debug.c155
-rw-r--r--subversion/libsvn_subr/deprecated.c1304
-rw-r--r--subversion/libsvn_subr/dirent_uri.c2597
-rw-r--r--subversion/libsvn_subr/dirent_uri.h40
-rw-r--r--subversion/libsvn_subr/dso.c117
-rw-r--r--subversion/libsvn_subr/eol.c108
-rw-r--r--subversion/libsvn_subr/error.c800
-rwxr-xr-xsubversion/libsvn_subr/genctype.py114
-rw-r--r--subversion/libsvn_subr/gpg_agent.c463
-rw-r--r--subversion/libsvn_subr/hash.c642
-rw-r--r--subversion/libsvn_subr/internal_statements.h76
-rw-r--r--subversion/libsvn_subr/internal_statements.sql47
-rw-r--r--subversion/libsvn_subr/io.c4768
-rw-r--r--subversion/libsvn_subr/iter.c216
-rw-r--r--subversion/libsvn_subr/lock.c60
-rw-r--r--subversion/libsvn_subr/log.c396
-rw-r--r--subversion/libsvn_subr/macos_keychain.c263
-rw-r--r--subversion/libsvn_subr/magic.c161
-rw-r--r--subversion/libsvn_subr/md5.c110
-rw-r--r--subversion/libsvn_subr/md5.h71
-rw-r--r--subversion/libsvn_subr/mergeinfo.c2631
-rw-r--r--subversion/libsvn_subr/mutex.c83
-rw-r--r--subversion/libsvn_subr/named_atomic.c655
-rw-r--r--subversion/libsvn_subr/nls.c132
-rw-r--r--subversion/libsvn_subr/opt.c1240
-rw-r--r--subversion/libsvn_subr/opt.h54
-rw-r--r--subversion/libsvn_subr/path.c1315
-rw-r--r--subversion/libsvn_subr/pool.c142
-rw-r--r--subversion/libsvn_subr/prompt.c954
-rw-r--r--subversion/libsvn_subr/properties.c507
-rw-r--r--subversion/libsvn_subr/pseudo_md5.c422
-rw-r--r--subversion/libsvn_subr/quoprint.c309
-rw-r--r--subversion/libsvn_subr/sha1.c82
-rw-r--r--subversion/libsvn_subr/sha1.h70
-rw-r--r--subversion/libsvn_subr/simple_providers.c734
-rw-r--r--subversion/libsvn_subr/skel.c881
-rw-r--r--subversion/libsvn_subr/sorts.c309
-rw-r--r--subversion/libsvn_subr/spillbuf.c615
-rw-r--r--subversion/libsvn_subr/sqlite.c1294
-rw-r--r--subversion/libsvn_subr/sqlite3wrapper.c62
-rw-r--r--subversion/libsvn_subr/ssl_client_cert_providers.c209
-rw-r--r--subversion/libsvn_subr/ssl_client_cert_pw_providers.c506
-rw-r--r--subversion/libsvn_subr/ssl_server_trust_providers.c234
-rw-r--r--subversion/libsvn_subr/stream.c1826
-rw-r--r--subversion/libsvn_subr/string.c1273
-rw-r--r--subversion/libsvn_subr/subst.c2025
-rw-r--r--subversion/libsvn_subr/sysinfo.c1132
-rw-r--r--subversion/libsvn_subr/sysinfo.h69
-rw-r--r--subversion/libsvn_subr/target.c335
-rw-r--r--subversion/libsvn_subr/temp_serializer.c382
-rw-r--r--subversion/libsvn_subr/time.c277
-rw-r--r--subversion/libsvn_subr/token.c98
-rw-r--r--subversion/libsvn_subr/types.c340
-rw-r--r--subversion/libsvn_subr/user.c86
-rw-r--r--subversion/libsvn_subr/username_providers.c306
-rw-r--r--subversion/libsvn_subr/utf.c1075
-rw-r--r--subversion/libsvn_subr/utf_validate.c485
-rw-r--r--subversion/libsvn_subr/utf_width.c283
-rw-r--r--subversion/libsvn_subr/validate.c102
-rw-r--r--subversion/libsvn_subr/version.c291
-rw-r--r--subversion/libsvn_subr/win32_crashrpt.c805
-rw-r--r--subversion/libsvn_subr/win32_crashrpt.h35
-rw-r--r--subversion/libsvn_subr/win32_crashrpt_dll.h93
-rw-r--r--subversion/libsvn_subr/win32_crypto.c492
-rw-r--r--subversion/libsvn_subr/win32_xlate.c238
-rw-r--r--subversion/libsvn_subr/win32_xlate.h52
-rw-r--r--subversion/libsvn_subr/xml.c655
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 0000000..e290e68
--- /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 0000000..b760da4
--- /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 0000000..5406358
--- /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 0000000..0885f6d
--- /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 0000000..97ee3d2
--- /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 0000000..6401f9f
--- /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 0000000..5f447b3
--- /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 0000000..5332d04
--- /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 0000000..70e189f
--- /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 0000000..5029cef
--- /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 0000000..86c12dc
--- /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 0000000..e5d6a62
--- /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 0000000..8484c96
--- /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 0000000..2089828
--- /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 0000000..94aecd3
--- /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, &sectkey, &sectkeyLength, &sectval);
+ 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 0000000..d53403c
--- /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 0000000..9d15f6b
--- /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 0000000..a3ab8fa
--- /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 0000000..0a15129
--- /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, &section_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, &section_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 0000000..f3611a1e
--- /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 0000000..5e7be86
--- /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 0000000..0dd5d5b
--- /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 0000000..6035645
--- /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 0000000..be331ed
--- /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 0000000..378b3f8
--- /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 0000000..2b51e7a
--- /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 0000000..660fb34
--- /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 0000000..3fa2517
--- /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 *) &not_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 0000000..88a6a37
--- /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 0000000..2f04320
--- /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 0000000..21638ba
--- /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 0000000..f0395c0
--- /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 0000000..7868cac
--- /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 0000000..fc429b3
--- /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 0000000..c78e855
--- /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 0000000..58bc540
--- /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 0000000..45ec489
--- /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 0000000..71dd8c7
--- /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 0000000..9e0b22a
--- /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 0000000..f15324e
--- /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 0000000..812a263
--- /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 0000000..a707a71
--- /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 0000000..0d83539
--- /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 0000000..63496ae
--- /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 0000000..04988eb
--- /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 0000000..d07e742
--- /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 0000000..b026e39
--- /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 0000000..28ffed1
--- /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 0000000..ddf3984
--- /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 0000000..84368f3
--- /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 0000000..179ef79
--- /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 0000000..92ee6a2
--- /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 0000000..738d00f
--- /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 0000000..8c194f7
--- /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 0000000..bb9869d
--- /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 0000000..45470cb
--- /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 0000000..976810b
--- /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 0000000..e70770a
--- /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 0000000..ed12db0
--- /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 0000000..bdec8e4
--- /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 0000000..e028741
--- /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 0000000..0afceff
--- /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 0000000..d1941aa
--- /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 0000000..cf86fa1
--- /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 0000000..6c1bcf1
--- /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 0000000..c69be77
--- /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 0000000..e2529c7
--- /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 0000000..20b0f24
--- /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 0000000..f69dcf8
--- /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 0000000..455dca4
--- /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 0000000..6a6e74d
--- /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 0000000..3525167
--- /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 0000000..261267a
--- /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 0000000..ccd6269
--- /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 0000000..d4fc725
--- /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 0000000..30ebb65
--- /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 0000000..7d6d0bf
--- /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 0000000..a6ef5b3
--- /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 0000000..355e068
--- /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 0000000..8311fd7
--- /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 0000000..3adff4c
--- /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 0000000..2052339
--- /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 0000000..ad2a270
--- /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 0000000..6becc96
--- /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 0000000..77c25c1
--- /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 0000000..18a4fc9
--- /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 0000000..a7e3828
--- /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 0000000..efe9c05
--- /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 0000000..82fc832
--- /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 0000000..a9d834a
--- /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, "&amp;");
+ else if (*q == '<')
+ svn_stringbuf_appendcstr(*outstr, "&lt;");
+ else if (*q == '>')
+ svn_stringbuf_appendcstr(*outstr, "&gt;");
+ else if (*q == '\r')
+ svn_stringbuf_appendcstr(*outstr, "&#13;");
+
+ 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, "&amp;");
+ else if (*q == '<')
+ svn_stringbuf_appendcstr(*outstr, "&lt;");
+ else if (*q == '>')
+ svn_stringbuf_appendcstr(*outstr, "&gt;");
+ else if (*q == '"')
+ svn_stringbuf_appendcstr(*outstr, "&quot;");
+ else if (*q == '\'')
+ svn_stringbuf_appendcstr(*outstr, "&apos;");
+ else if (*q == '\r')
+ svn_stringbuf_appendcstr(*outstr, "&#13;");
+ else if (*q == '\n')
+ svn_stringbuf_appendcstr(*outstr, "&#10;");
+ else if (*q == '\t')
+ svn_stringbuf_appendcstr(*outstr, "&#9;");
+
+ 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");
+}
OpenPOWER on IntegriCloud