Index: build.conf =================================================================== --- build.conf (revision 7611) +++ build.conf (working copy) @@ -175,6 +175,14 @@ libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr aprutil apriconv apr msvc-static = yes +# Accessing repositories via locked lan libsvn_fs +[libsvn_ra_lock] +type = ra-module +path = subversion/libsvn_ra_lock +install = fs-lib +libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr aprutil apriconv apr +msvc-static = yes + # Routines built on top of libsvn_fs [libsvn_repos] type = lib @@ -670,7 +678,7 @@ [ra-libs] type = lib external-lib = $(SVN_RA_LIB_LINK) -libs = libsvn_ra_dav libsvn_ra_local libsvn_ra_svn +libs = libsvn_ra_dav libsvn_ra_local libsvn_ra_lock libsvn_ra_svn [__ALL__] type = project Index: subversion/libsvn_ra/ra_loader.c =================================================================== --- subversion/libsvn_ra/ra_loader.c (revision 7611) +++ subversion/libsvn_ra/ra_loader.c (working copy) @@ -77,6 +77,13 @@ /* ADD NEW RA IMPLEMENTATIONS HERE (as they're written) */ + { + "lock", +#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_LOCK + svn_ra_lock_init +#endif + }, + /* sentinel */ { NULL } }; Index: subversion/include/svn_error_codes.h =================================================================== --- subversion/include/svn_error_codes.h (revision 7611) +++ subversion/include/svn_error_codes.h (working copy) @@ -136,6 +136,8 @@ + (18 * SVN_ERR_CATEGORY_SIZE)) #define SVN_ERR_AUTHN_CATEGORY_START (APR_OS_START_USERERR \ + (19 * SVN_ERR_CATEGORY_SIZE)) +#define SVN_ERR_RA_LOCK_CATEGORY_START (APR_OS_START_USERERR \ + + (20 * SVN_ERR_CATEGORY_SIZE)) #endif /* DOXYGEN_SHOULD_SKIP_THIS */ @@ -581,6 +583,18 @@ SVN_ERRDEF (SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, SVN_ERR_RA_LOCAL_CATEGORY_START + 1, "Couldn't open a repository") + + /* ra_lock errors */ + + SVN_ERRDEF (SVN_ERR_RA_LOCK_REPOS_NOT_FOUND, + SVN_ERR_RA_LOCK_CATEGORY_START + 0, + "Couldn't find a repository") + + SVN_ERRDEF (SVN_ERR_RA_LOCK_REPOS_OPEN_FAILED, + SVN_ERR_RA_LOCK_CATEGORY_START + 1, + "Couldn't open a repository") + + /* ra_svn errors */ SVN_ERRDEF (SVN_ERR_RA_SVN_CMD_ERR, Index: subversion/libsvn_ra_lock/ra_lock.h =================================================================== --- subversion/libsvn_ra_lock/ra_lock.h (revision 0) +++ subversion/libsvn_ra_lock/ra_lock.h (revision 0) @@ -0,0 +1,47 @@ +/* + * ra_lock.h : shared internal declarations for ra_lock module + * + * ==================================================================== + * Copyright (c) 2000-2003 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_RA_LOCK_H +#define SVN_LIBSVN_RA_LOCK_H + +#include "../libsvn_ra_local/ra_local.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Given a `lock://' URL, decode it, figure out which portion + specifies a repository on local disk, and return in REPOS_URL; + return the remainder (the path *within* the repository's + filesystem) in FS_PATH. Allocate the return values in POOL. + Currently, we are not expecting to handle `file://hostname/'-type + URLs; hostname, in this case, is expected to be the empty string. */ +svn_error_t * +svn_ra_lock__split_URL (svn_repos_t **repos, + const char **repos_url, + const char **fs_path, + const char *URL, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_RA_LOCK_H */ Index: subversion/libsvn_ra_lock/ra_plugin.c =================================================================== --- subversion/libsvn_ra_lock/ra_plugin.c (revision 0) +++ subversion/libsvn_ra_lock/ra_plugin.c (revision 0) @@ -0,0 +1,146 @@ +/* + * ra_plugin.c : the main RA module for local repository access + * + * ==================================================================== + * Copyright (c) 2000-2003 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include "ra_lock.h" +#include "svn_ra.h" +#include "svn_fs.h" +#include "svn_delta.h" +#include "svn_repos.h" +#include "svn_pools.h" +#include "svn_time.h" + +#define APR_WANT_STRFUNC +#include + + +/** The RA plugin routines **/ + + +static svn_error_t * +svn_ra_lock__open (void **session_baton, + const char *repos_URL, + const svn_ra_callbacks_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool) +{ + svn_ra_local__session_baton_t *session; + svn_auth_iterstate_t *iterstate; + + /* Allocate and stash the session_baton args we have already. */ + session = apr_pcalloc (pool, sizeof(*session)); + session->pool = pool; + session->repository_URL = repos_URL; + + /* Look through the URL, figure out which part points to the + repository, and which part is the path *within* the + repository. */ + SVN_ERR_W (svn_ra_lock__split_URL (&(session->repos), + &(session->repos_url), + &(session->fs_path), + session->repository_URL, + session->pool), + "Unable to open an ra_lock session to URL"); + + /* Cache the filesystem object from the repos here for + convenience. */ + session->fs = svn_repos_fs (session->repos); + + /* Cache the repository UUID as well */ + SVN_ERR (svn_fs_get_uuid (session->fs, &session->uuid, session->pool)); + + /* Stuff the callbacks/baton here. */ + session->callbacks = callbacks; + session->callback_baton = callback_baton; + + /* Get a username somehow, so we have some svn:author property to + attach to a commit. */ + if (! callbacks->auth_baton) + { + session->username = ""; + } + else + { + void *creds; + svn_auth_cred_username_t *username_creds; + SVN_ERR (svn_auth_first_credentials (&creds, &iterstate, + SVN_AUTH_CRED_USERNAME, + session->uuid, /* realmstring */ + callbacks->auth_baton, + pool)); + + /* No point in calling next_creds(), since that assumes that the + first_creds() somehow failed to authenticate. But there's no + challenge going on, so we use whatever creds we get back on + the first try. */ + username_creds = creds; + if (username_creds == NULL + || (username_creds->username == NULL)) + session->username = ""; + else + session->username = apr_pstrdup (pool, username_creds->username); + } + + *session_baton = session; + return SVN_NO_ERROR; +} + + +/*----------------------------------------------------------------*/ + +/** The ra_plugin **/ + +static const svn_ra_plugin_t ra_lock_plugin = +{ + "ra_lock", + "Module for accessing a repository on a LAN with external file locking. You should not use this.", + svn_ra_lock__open, + svn_ra_local__get_latest_revnum, + svn_ra_local__get_dated_revision, + svn_ra_local__change_rev_prop, + svn_ra_local__rev_proplist, + svn_ra_local__rev_prop, + svn_ra_local__get_commit_editor, + svn_ra_local__get_file, + svn_ra_local__get_dir, + svn_ra_local__do_update, + svn_ra_local__do_switch, + svn_ra_local__do_status, + svn_ra_local__do_diff, + svn_ra_local__get_log, + svn_ra_local__do_check_path, + svn_ra_local__get_uuid, + svn_ra_local__get_repos_root +}; + + +/*----------------------------------------------------------------*/ + +/** The One Public Routine, called by libsvn_client **/ + +svn_error_t * +svn_ra_lock_init (int abi_version, + apr_pool_t *pool, + apr_hash_t *hash) +{ + apr_hash_set (hash, "lock", APR_HASH_KEY_STRING, &ra_lock_plugin); + + /* ben sez: todo: check that abi_version >=1. */ + + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra_lock/split_url.c =================================================================== --- subversion/libsvn_ra_lock/split_url.c (revision 0) +++ subversion/libsvn_ra_lock/split_url.c (revision 0) @@ -0,0 +1,267 @@ +/* + * checkout.c : read a repository and drive a checkout editor. + * + * ==================================================================== + * Copyright (c) 2000-2003 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#include "ra_lock.h" +#include +#include +#include "svn_pools.h" +#include "svn_private_config.h" + +const static apr_time_t zero_time = 0; +static apr_time_t lock_time = 0; + +apr_status_t lock_file_cleanup(void * arg) +{ + return apr_file_remove((char *)arg, NULL); +} + +apr_int32_t +check_if_lock_file_exists(const char *lock_file_name, + apr_pool_t *pool) +{ + apr_int32_t exists = 0; + apr_file_t *lock_file = NULL; + + apr_file_open(&lock_file, + lock_file_name, + APR_READ, + APR_OS_DEFAULT, + pool); + + if (NULL != lock_file) + { + exists = 1; + apr_file_close(lock_file); + } + + return exists; +} + +apr_int32_t +check_if_we_own_lock_file(const char *lock_file_name, + const char *lock_file_contents, + apr_pool_t *pool) +{ + apr_int32_t own = 0; + apr_size_t lock_file_contents_length = strlen(lock_file_contents); + + /* Try to create our lock file */ + apr_file_t *lock_file = NULL; + + apr_file_open(&lock_file, + lock_file_name, + APR_READ, + APR_OS_DEFAULT, + pool); + + if (NULL != lock_file) + { + /* See if we are the person who created the lock file */ + apr_finfo_t lock_file_info; + char *current_lockfile_contents; + apr_size_t lock_file_size; + + apr_file_info_get(&lock_file_info, APR_FINFO_SIZE, lock_file); + lock_file_size = lock_file_info.size; + + current_lockfile_contents = (char *)apr_pcalloc(pool, lock_file_size + 1); + apr_file_read(lock_file, current_lockfile_contents, &lock_file_size); + + apr_file_close(lock_file); + + own = !strcmp(current_lockfile_contents, lock_file_contents); + } + + return own; +} + +apr_int32_t +create_lock_file(const char *lock_file_name, + const char *lock_file_contents, + apr_pool_t *pool) +{ + apr_int32_t created = 0; + apr_size_t lock_file_contents_length = strlen(lock_file_contents); + apr_file_t *lock_file = NULL; + + apr_file_open(&lock_file, + lock_file_name, + APR_READ | APR_WRITE | APR_CREATE | APR_EXCL, + APR_OS_DEFAULT, + pool); + + if (NULL != lock_file) + { + apr_file_write(lock_file, lock_file_contents, &lock_file_contents_length); + apr_file_close(lock_file); + created = 1; + } + + return created; +} + +svn_error_t * +svn_ra_lock__split_URL (svn_repos_t **repos, + const char **repos_url, + const char **fs_path, + const char *URL, + apr_pool_t *pool) +{ + svn_error_t *err = SVN_NO_ERROR; + const char *repos_root; + const char *hostname, *path; + char *lock_file_name; + char *lock_file_contents; + apr_uid_t userid; + apr_gid_t groupid; + char *username = NULL; + apr_int32_t exists; + + if (zero_time == lock_time) + lock_time = apr_time_now(); + + /* Decode the URL, as we only use its parts as filesystem paths + anyway. */ + URL = svn_path_uri_decode (URL, pool); + + /* Verify that the URL is well-formed (loosely) */ + + /* First, check for the "lock://" prefix. */ + if (strncmp (URL, "lock://", 7) != 0) + return svn_error_createf + (SVN_ERR_RA_ILLEGAL_URL, NULL, + "svn_ra_lock__split_URL: URL does not contain 'lock://' prefix\n" + " (%s)", URL); + + /* Then, skip what's between the "lock://" prefix and the next + occurance of '/' -- this is the hostname, and we are considering + everything from that '/' until the end of the URL to be the + absolute path portion of the URL. */ + hostname = URL + 7; + path = strchr (hostname, '/'); + if (! path) + return svn_error_createf + (SVN_ERR_RA_ILLEGAL_URL, NULL, + "svn_ra_lock__split_URL: URL contains only a hostname, no path\n" + " (%s)", URL); + + /* Currently, the only hostnames we are allowing are the empty + string and 'localhost' */ + if ((hostname != path) && (strncmp (hostname, "localhost/", 10) != 0)) + return svn_error_createf + (SVN_ERR_RA_ILLEGAL_URL, NULL, + "svn_ra_lock__split_URL: URL contains unsupported hostname\n" + " (%s)", URL); + + + /* Duplicate the URL, starting at the top of the path */ +#ifndef SVN_WIN32 + repos_root = apr_pstrdup (pool, path); +#else /* SVN_WIN32 */ + /* 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 schema: + + lock:///X:/path and + lock:///X|/path + + Note that, at least on WinNT and above, lock:////./X:/path will + also work, so we must make sure the transformation doesn't break + that, and lock:///path (that looks within the current drive + only) should also keep working. */ + { + static const char valid_drive_letters[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + if (path[1] && strchr(valid_drive_letters, path[1]) + && (path[2] == ':' || path[2] == '|') + && path[3] == '/') + { + char *const dup_path = apr_pstrdup (pool, ++path); + if (dup_path[1] == '|') + dup_path[1] = ':'; + repos_root = dup_path; + } + else + repos_root = apr_pstrdup (pool, path); + } +#endif /* SVN_WIN32 */ + + /* Search for a repository in the full path. */ + repos_root = svn_repos_find_root_path(repos_root, pool); + if (!repos_root) + return svn_error_createf + (SVN_ERR_RA_LOCK_REPOS_OPEN_FAILED, NULL, + "Unable to open repository '%s'", URL); + + lock_file_name = (char *)apr_palloc(pool, strlen(repos_root) + 9); + sprintf(lock_file_name, "%s/svn.lck", repos_root); + + apr_uid_current(&userid, &groupid, pool); + apr_uid_name_get(&username, userid, pool); + + /* Generate what should be in the lock file */ + lock_file_contents = (char *)apr_palloc(pool, strlen(username) + 64); + sprintf(lock_file_contents, "%s/%d", username, lock_time); + + exists = check_if_lock_file_exists(lock_file_name, pool); + + if (exists) + { + apr_int32_t own = check_if_we_own_lock_file(lock_file_name, lock_file_contents, pool); + + if (!own) + return svn_error_createf + (SVN_ERR_RA_LOCK_REPOS_OPEN_FAILED, err, + "Repository at '%s' is currently locked by another user", URL); + } + else + { + apr_int32_t own; + apr_int32_t created = create_lock_file(lock_file_name, lock_file_contents, pool); + + if (!created) + return svn_error_createf + (SVN_ERR_RA_LOCK_REPOS_OPEN_FAILED, err, + "Unable to create lock file for repository '%s'", URL); + + /* Sleep for two seconds, and then see if the lock file has been corrupted */ + apr_sleep(2000000); + + own = check_if_we_own_lock_file(lock_file_name, lock_file_contents, pool); + if (own) + apr_pool_cleanup_register(pool, lock_file_name, lock_file_cleanup, NULL); + else + return svn_error_createf + (SVN_ERR_RA_LOCK_REPOS_OPEN_FAILED, err, + "Race condition at repository '%s'! Aborting", URL); + } + + /* Attempt to open a repository at URL. */ + err = svn_repos_open (repos, repos_root, pool); + if (err) + return svn_error_createf + (SVN_ERR_RA_LOCK_REPOS_OPEN_FAILED, err, + "Unable to open repository '%s'", URL); + + /* What remains of URL after being hacked at in the previous step is + REPOS_URL. FS_PATH is what we've hacked off in the process. */ + *fs_path = apr_pstrdup (pool, path + strlen (repos_root)); + *repos_url = apr_pstrmemdup (pool, URL, strlen(URL) - strlen(*fs_path)); + + return SVN_NO_ERROR; +} Index: subversion/libsvn_ra_local/ra_plugin.c =================================================================== --- subversion/libsvn_ra_local/ra_plugin.c (revision 7611) +++ subversion/libsvn_ra_local/ra_plugin.c (working copy) @@ -203,7 +203,7 @@ -static svn_error_t * +svn_error_t * svn_ra_local__get_latest_revnum (void *session_baton, svn_revnum_t *latest_revnum, apr_pool_t *pool) @@ -218,7 +218,7 @@ -static svn_error_t * +svn_error_t * svn_ra_local__get_dated_revision (void *session_baton, svn_revnum_t *revision, apr_time_t tm, @@ -233,7 +233,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__change_rev_prop (void *session_baton, svn_revnum_t rev, const char *name, @@ -250,7 +250,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__get_uuid (void *session_baton, const char **uuid, apr_pool_t *pool) @@ -263,7 +263,7 @@ return SVN_NO_ERROR; } -static svn_error_t * +svn_error_t * svn_ra_local__get_repos_root (void *session_baton, const char **url, apr_pool_t *pool) @@ -276,7 +276,7 @@ return SVN_NO_ERROR; } -static svn_error_t * +svn_error_t * svn_ra_local__rev_proplist (void *session_baton, svn_revnum_t rev, apr_hash_t **props, @@ -291,7 +291,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__rev_prop (void *session_baton, svn_revnum_t rev, const char *name, @@ -307,7 +307,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__get_commit_editor (void *session_baton, const svn_delta_editor_t **editor, void **edit_baton, @@ -397,7 +397,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__do_update (void *session_baton, const svn_ra_reporter_t **reporter, void **report_baton, @@ -423,7 +423,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__do_switch (void *session_baton, const svn_ra_reporter_t **reporter, void **report_baton, @@ -450,7 +450,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__do_status (void *session_baton, const svn_ra_reporter_t **reporter, void **report_baton, @@ -476,7 +476,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__do_diff (void *session_baton, const svn_ra_reporter_t **reporter, void **report_baton, @@ -504,7 +504,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__get_log (void *session_baton, const apr_array_header_t *paths, svn_revnum_t start, @@ -543,7 +543,7 @@ } -static svn_error_t * +svn_error_t * svn_ra_local__do_check_path (void *session_baton, const char *path, svn_revnum_t revision, @@ -619,7 +619,7 @@ /* Getting just one file. */ -static svn_error_t * +svn_error_t * svn_ra_local__get_file (void *session_baton, const char *path, svn_revnum_t revision, @@ -713,7 +713,7 @@ /* Getting a directory's entries */ -static svn_error_t * +svn_error_t * svn_ra_local__get_dir (void *session_baton, const char *path, svn_revnum_t revision, Index: subversion/libsvn_ra_local/ra_local.h =================================================================== --- subversion/libsvn_ra_local/ra_local.h (revision 7611) +++ subversion/libsvn_ra_local/ra_local.h (working copy) @@ -71,8 +71,144 @@ } svn_ra_local__session_baton_t; +/* Routines also used by libsvn_ra_lock */ - +svn_error_t * +svn_ra_local__get_latest_revnum (void *session_baton, + svn_revnum_t *latest_revnum, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__get_dated_revision (void *session_baton, + svn_revnum_t *revision, + apr_time_t tm, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__change_rev_prop (void *session_baton, + svn_revnum_t rev, + const char *name, + const svn_string_t *value, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__get_uuid (void *session_baton, + const char **uuid, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__get_repos_root (void *session_baton, + const char **url, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__rev_proplist (void *session_baton, + svn_revnum_t rev, + apr_hash_t **props, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__rev_prop (void *session_baton, + svn_revnum_t rev, + const char *name, + svn_string_t **value, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__get_commit_editor (void *session_baton, + const svn_delta_editor_t **editor, + void **edit_baton, + const char *log_msg, + svn_commit_callback_t callback, + void *callback_baton, + apr_pool_t *pool); + + +svn_error_t * +svn_ra_local__do_update (void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + svn_revnum_t update_revision, + const char *update_target, + svn_boolean_t recurse, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__do_switch (void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + svn_revnum_t update_revision, + const char *update_target, + svn_boolean_t recurse, + const char *switch_url, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__do_status (void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + const char *status_target, + svn_revnum_t revision, + svn_boolean_t recurse, + const svn_delta_editor_t *status_editor, + void *status_baton, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__do_diff (void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + svn_revnum_t update_revision, + const char *update_target, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + const char *switch_url, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__get_log (void *session_baton, + const apr_array_header_t *paths, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_log_message_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool); + +svn_error_t * +svn_ra_local__do_check_path (void *session_baton, + const char *path, + svn_revnum_t revision, + svn_node_kind_t *kind, + apr_pool_t *pool); + +/* Getting just one file. */ +svn_error_t * +svn_ra_local__get_file (void *session_baton, + const char *path, + svn_revnum_t revision, + svn_stream_t *stream, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool); + +/* Getting a directory's entries */ +svn_error_t * +svn_ra_local__get_dir (void *session_baton, + const char *path, + svn_revnum_t revision, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool); + /** Private routines **/