Index: subversion/include/svn_error_codes.h =================================================================== --- subversion/include/svn_error_codes.h (revision 7628) +++ subversion/include/svn_error_codes.h (working copy) @@ -507,6 +507,10 @@ SVN_ERR_REPOS_CATEGORY_START + 7, "Error running post-commit hook") + SVN_ERRDEF (SVN_ERR_REPOS_LOCK_FAILED, + SVN_ERR_REPOS_CATEGORY_START + 8, + "Error trying to lock repository") + /* generic ra errors */ SVN_ERRDEF (SVN_ERR_RA_ILLEGAL_URL, Index: subversion/libsvn_repos/repos.c =================================================================== --- subversion/libsvn_repos/repos.c (revision 7628) +++ subversion/libsvn_repos/repos.c (working copy) @@ -62,7 +62,18 @@ return svn_path_join (repos->conf_path, SVN_REPOS__CONF_SVNSERVE_CONF, pool); } +static const char * +svn_repos_lock_conf (svn_repos_t * repos, apr_pool_t *pool) +{ + return svn_path_join (repos->conf_path, SVN_REPOS__CONF_LOCK_CONF, pool); +} +static const char * +svn_repos_lock_svn_lock (svn_repos_t * repos, apr_pool_t *pool) +{ + return svn_path_join (repos->conf_path, SVN_REPOS__LOCK_SVN_LOCK, pool); +} + const char * svn_repos_lock_dir (svn_repos_t *repos, apr_pool_t *pool) { @@ -1011,12 +1022,15 @@ /* Initialize the filesystem object. */ repos->fs = svn_fs_new (NULL, pool); + SVN_ERR (create_external_lock_if_requested (repos, pool)); /* Locking. */ { const char *lockfile_path; - svn_boolean_t exclusive = FALSE; + /* If the external lock was requested, default is true */ + svn_boolean_t exclusive = external_lock_requested (repos,pool); + /* Get a filehandle for the repository's db lockfile. */ lockfile_path = svn_repos_db_lockfile (repos, pool); if (locktype == APR_FLOCK_EXCLUSIVE) @@ -1035,7 +1049,6 @@ } - const char * svn_repos_find_root_path (const char *path, apr_pool_t *pool) @@ -1055,11 +1068,200 @@ } +/* True on success, False on failure */ +static svn_boolean_t +lock_file_cleanup (void * arg) +{ + return apr_file_remove ((char *)arg, NULL) ? TRUE : FALSE; +} + +/* True on success, False on failure */ +static svn_boolean_t +check_if_lock_file_exists (const char *lock_file_name, + apr_pool_t *pool) +{ + apr_file_t *lock_file = NULL; + + apr_file_open (&lock_file, + lock_file_name, + APR_READ, + APR_OS_DEFAULT, + pool); + + if (NULL != lock_file) + { + apr_file_close (lock_file); + return TRUE; + } + + return FALSE; +} + +/* True on success, False on failure */ +static svn_boolean_t +check_if_we_own_lock_file (const char *lock_file_name, + const char *lock_file_contents, + apr_pool_t *pool) +{ + 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); + + if (strcmp (current_lockfile_contents, lock_file_contents) == 0) + return TRUE; + } + + return FALSE; +} + + +/* True on success, False on failure */ +static svn_boolean_t +create_lock_file (const char *lock_file_name, + const char *lock_file_contents, + apr_pool_t *pool) +{ + 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); + return TRUE; + } + + return FALSE; +} + +static svn_boolean_t +external_lock_requested (svn_repos_t *repos, + apr_pool_t *pool) +{ + static svn_boolean_t lock = FALSE; + static svn_boolean_t first = TRUE ; + + if(first) + { + svn_config_t *cfgp = NULL; + const char * config_lock_file_name = svn_repos_lock_conf (repos, pool); + const char *valuep; + + SVN_ERR (svn_config_read (&cfgp, config_lock_file_name, FALSE, pool)); + + if (cfgp) + { + svn_config_get (cfgp, &valuep, "lock", "exclusive", SVN_CONFIG_FALSE); + + lock = strcmp (valuep, SVN_CONFIG_TRUE) == 0 ? TRUE : FALSE ; + + first = FALSE; + } + } + + return lock; +} + +static svn_error_t * +create_external_lock_if_requested (svn_repos_t *repos, + apr_pool_t *pool) +{ + const static apr_time_t zero_time = 0; + static apr_time_t lock_time = 0; + + /* Sub-sequent attempts through here always result */ + /* in a true value since failure the first time is */ + /* fatal */ + if (zero_time != lock_time) + return SVN_NO_ERROR; + + /* Save the time so we only do this once per run */ + lock_time = apr_time_now (); + + /* Only perform if requested */ + if (external_lock_requested (repos, pool)) + { + + svn_error_t * err = SVN_NO_ERROR; + + char *lock_file_name; + char *lock_file_contents; + apr_uid_t userid; + apr_gid_t groupid; + char *username = NULL; + + lock_file_name = svn_repos_lock_svn_lock (repos,pool); + + apr_uid_current (&userid, &groupid, pool); + apr_uid_name_get (&username, userid, pool); + + /* Generate what should be in the lock file */ + lock_file_contents = apr_psprintf (pool, "%s/%d", username, lock_time); + + if (check_if_lock_file_exists (lock_file_name, pool)) + { + if (!check_if_we_own_lock_file (lock_file_name, lock_file_contents, pool)) + return svn_error_createf + (SVN_ERR_REPOS_LOCK_FAILED, err, + "Repository at '%s' is currently locked by another user", path); + } + else + { + if (!create_lock_file (lock_file_name, lock_file_contents, pool)) + return svn_error_createf + (SVN_ERR_REPOS_LOCK_FAILED, err, + "Unable to create lock file for repository '%s'", path); + + /* Sleep for two seconds, and then see if the lock file has been corrupted */ + apr_sleep (2000000); + + if (check_if_we_own_lock_file (lock_file_name, lock_file_contents, pool)) + apr_pool_cleanup_register (pool, lock_file_name, lock_file_cleanup, NULL); + else + return svn_error_createf + (SVN_ERR_REPOS_LOCK_FAILED, err, + "Race condition at repository '%s'! Aborting", path); + } + } + + return SVN_NO_ERROR; +} + + svn_error_t * svn_repos_open (svn_repos_t **repos_p, const char *path, apr_pool_t *pool) { + /* Fetch a repository object initialized with a shared read/write lock on the database. */ Index: subversion/libsvn_repos/repos.h =================================================================== --- subversion/libsvn_repos/repos.h (revision 7628) +++ subversion/libsvn_repos/repos.h (working copy) @@ -66,6 +66,9 @@ /* In the repository conf directory, look for these files. */ #define SVN_REPOS__CONF_SVNSERVE_CONF "svnserve.conf" +/* Lock files for external locking as needed */ +#define SVN_REPOS__CONF_LOCK_CONF "lock.conf" +#define SVN_REPOS__LOCK_SVN_LOCK "svn.lck" /* The Repository object, created by svn_repos_open() and svn_repos_create(), allocated in POOL. */