Index: subversion/include/svn_error_codes.h =================================================================== --- subversion/include/svn_error_codes.h (revision 7630) +++ 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 7630) +++ subversion/libsvn_repos/repos.c (working copy) @@ -62,6 +62,17 @@ 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_lockfile (svn_repos_t * repos, apr_pool_t *pool) +{ + return svn_path_join (repos->lock_path, SVN_REPOS__LOCK_SVN_LOCK, pool); +} const char * svn_repos_lock_dir (svn_repos_t *repos, apr_pool_t *pool) @@ -748,6 +759,38 @@ } static svn_error_t * +create_lock_conf (svn_repos_t *repos, apr_pool_t *pool) +{ + { + static const char * const lock_conf_contents = + "### This file controls the locking behavior of subversion" + APR_EOL_STR + "### Use it to control whether all repository access is exclusive" + APR_EOL_STR + "### And whether the repository allows 1-at-a-time access via" + APR_EOL_STR + "### An externally enforced mechanism" + APR_EOL_STR + "[lock]" + APR_EOL_STR + APR_EOL_STR + "### Repository access uses the equivalent of an external mutex when true" + "external = false" + APR_EOL_STR + APR_EOL_STR + "### All repository access is exclusive if this is true" + APR_EOL_STR + "exclusive = false" + APR_EOL_STR; + + SVN_ERR_W (svn_io_file_create (svn_repos_lock_conf (repos, pool), + lock_conf_contents, pool), + "creating lock.conf file"); + } + return SVN_NO_ERROR; +} + +static svn_error_t * create_conf (svn_repos_t *repos, apr_pool_t *pool) { /* Create the hook directory. */ @@ -848,6 +891,9 @@ /* Create the conf directory. */ SVN_ERR (create_conf (repos, pool)); + /* Create the lock conf. */ + SVN_ERR (lock_conf (repos, pool)); + /* Write the top-level README file. */ { const char *readme_file_name @@ -1011,11 +1057,18 @@ /* Initialize the filesystem object. */ repos->fs = svn_fs_new (NULL, pool); + /* Read the lock configuration */ + read_lock_conf (repos,pool); + + /* This also reads the lock conf */ + SVN_ERR (create_external_lock_if_requested (repos, pool)); /* Locking. */ { const char *lockfile_path; - svn_boolean_t exclusive = FALSE; + + /* If the exclusive flag is set, use it */ + svn_boolean_t exclusive = repos->exclusive; /* Get a filehandle for the repository's db lockfile. */ lockfile_path = svn_repos_db_lockfile (repos, pool); @@ -1035,7 +1088,6 @@ } - const char * svn_repos_find_root_path (const char *path, apr_pool_t *pool) @@ -1055,11 +1107,202 @@ } +/* 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_error_t * +read_lock_conf (svn_repos_t *repos, + apr_pool_t *pool) +{ + static svn_config_t *cfgp = NULL; + const char * config_lock_file_name = NULL; + const char *valuep; + + /* read the lock configuration once per config */ + if (repos->lock_conf_read) + return SVN_NO_ERROR; + + /* Get the lock configuration file */ + config_lock_file_name = svn_repos_lock_conf (repos, pool); + + SVN_ERR (svn_config_read (&cfgp, config_lock_file_name, FALSE, pool)); + + if (cfgp) + { + svn_config_get (cfgp, &valuep, "lock", "external", SVN_CONFIG_FALSE); + repos->lock = strcmp (valuep, SVN_CONFIG_TRUE) == 0 ? TRUE : FALSE ; + + svn_config_get (cfgp, &valuep, "lock", "exclusive", SVN_CONFIG_FALSE); + repos->exclusive = strcmp (valuep, SVN_CONFIG_TRUE) == 0 ? TRUE : FALSE ; + + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +create_external_lock_if_requested (svn_repos_t *repos, + apr_pool_t *pool) +{ + /* Sub-sequent attempts through here always result */ + /* in a true value since failure the first time is */ + /* fatal */ + const static apr_time_t zero_time = 0; + + /* Since aprcalloc is used to create the repos, repos->lock_time will be 0 */ + if (zero_time != repos->lock_time) + return SVN_NO_ERROR; + + /* Save the time so we only do this once per run */ + repos->lock_time = apr_time_now (); + + /* Only perform if requested */ + if (repos->lock) + { + + 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_lockfile (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, repos->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 7630) +++ 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. */ @@ -91,6 +94,18 @@ /* The path to the Berkeley DB filesystem environment. */ char *db_path; + + /* Exclusive flag */ + svn_boolean_t exclusive; + + /* External lock flag */ + svn_boolean_t lock; + + /* External read configuration once per config */ + svn_boolean_t lock_conf_read; + + /* External read configuration once per config */ + apr_time_t lock_time; };