util.c revision 251881
1/*
2 * util.c:  Repository access utility routines.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26/*** Includes. ***/
27#include <apr_pools.h>
28#include <apr_network_io.h>
29
30#include "svn_types.h"
31#include "svn_pools.h"
32#include "svn_error.h"
33#include "svn_error_codes.h"
34#include "svn_dirent_uri.h"
35#include "svn_path.h"
36#include "svn_ra.h"
37
38#include "svn_private_config.h"
39#include "private/svn_ra_private.h"
40
41svn_error_t *
42svn_ra__assert_mergeinfo_capable_server(svn_ra_session_t *ra_session,
43                                        const char *path_or_url,
44                                        apr_pool_t *pool)
45{
46  svn_boolean_t mergeinfo_capable;
47  SVN_ERR(svn_ra_has_capability(ra_session, &mergeinfo_capable,
48                                SVN_RA_CAPABILITY_MERGEINFO, pool));
49  if (! mergeinfo_capable)
50    {
51      if (path_or_url == NULL)
52        {
53          svn_error_t *err = svn_ra_get_session_url(ra_session, &path_or_url,
54                                                    pool);
55          if (err)
56            {
57              /* The SVN_ERR_UNSUPPORTED_FEATURE error is more important,
58                 so dummy up the session's URL and chuck this error. */
59              svn_error_clear(err);
60              path_or_url = "<repository>";
61            }
62        }
63      return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
64                               _("Retrieval of mergeinfo unsupported by '%s'"),
65                               svn_path_is_url(path_or_url)
66                                  ? path_or_url
67                                  : svn_dirent_local_style(path_or_url, pool));
68    }
69  return SVN_NO_ERROR;
70}
71
72/* Does ERR mean "the current value of the revprop isn't equal to
73   the *OLD_VALUE_P you gave me"?
74 */
75static svn_boolean_t is_atomicity_error(svn_error_t *err)
76{
77  return svn_error_find_cause(err, SVN_ERR_FS_PROP_BASEVALUE_MISMATCH) != NULL;
78}
79
80svn_error_t *
81svn_ra__release_operational_lock(svn_ra_session_t *session,
82                                 const char *lock_revprop_name,
83                                 const svn_string_t *mylocktoken,
84                                 apr_pool_t *scratch_pool)
85{
86  svn_string_t *reposlocktoken;
87  svn_boolean_t be_atomic;
88
89  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
90                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
91                                scratch_pool));
92  SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
93                          &reposlocktoken, scratch_pool));
94  if (reposlocktoken && svn_string_compare(reposlocktoken, mylocktoken))
95    {
96      svn_error_t *err;
97
98      err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
99                                    be_atomic ? &mylocktoken : NULL, NULL,
100                                    scratch_pool);
101      if (is_atomicity_error(err))
102        {
103          return svn_error_createf(err->apr_err, err,
104                                   _("Lock was stolen by '%s'; unable to "
105                                     "remove it"), reposlocktoken->data);
106        }
107      else
108        SVN_ERR(err);
109    }
110
111  return SVN_NO_ERROR;
112}
113
114svn_error_t *
115svn_ra__get_operational_lock(const svn_string_t **lock_string_p,
116                             const svn_string_t **stolen_lock_p,
117                             svn_ra_session_t *session,
118                             const char *lock_revprop_name,
119                             svn_boolean_t steal_lock,
120                             int num_retries,
121                             svn_ra__lock_retry_func_t retry_func,
122                             void *retry_baton,
123                             svn_cancel_func_t cancel_func,
124                             void *cancel_baton,
125                             apr_pool_t *pool)
126{
127  char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
128  svn_string_t *mylocktoken, *reposlocktoken;
129  apr_status_t apr_err;
130  svn_boolean_t be_atomic;
131  apr_pool_t *subpool;
132  int i;
133
134  *lock_string_p = NULL;
135  if (stolen_lock_p)
136    *stolen_lock_p = NULL;
137
138  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
139                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool));
140
141  /* We build a lock token from the local hostname and a UUID.  */
142  apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
143  if (apr_err)
144    return svn_error_wrap_apr(apr_err,
145                              _("Unable to determine local hostname"));
146  mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
147                                   svn_uuid_generate(pool));
148
149  /* Ye Olde Retry Loope */
150  subpool = svn_pool_create(pool);
151
152  for (i = 0; i < num_retries; ++i)
153    {
154      svn_error_t *err;
155      const svn_string_t *unset = NULL;
156
157      svn_pool_clear(subpool);
158
159      /* Check for cancellation.  If we're cancelled, don't leave a
160         stray lock behind!  */
161      if (cancel_func)
162        {
163          err = cancel_func(cancel_baton);
164          if (err && err->apr_err == SVN_ERR_CANCELLED)
165            return svn_error_compose_create(
166                       svn_ra__release_operational_lock(session,
167                                                        lock_revprop_name,
168                                                        mylocktoken,
169                                                        subpool),
170                       err);
171          SVN_ERR(err);
172        }
173
174      /* Ask the repository for the value of the LOCK_REVPROP_NAME. */
175      SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
176                              &reposlocktoken, subpool));
177
178      /* Did we get a value from the repository?  We'll check to see
179         if it matches our token.  If so, we call it success.  If not
180         and we're told to steal locks, we remember the existing lock
181         token and fall through to the locking code; othewise, we
182         sleep and retry. */
183      if (reposlocktoken)
184        {
185          if (svn_string_compare(reposlocktoken, mylocktoken))
186            {
187              *lock_string_p = mylocktoken;
188              return SVN_NO_ERROR;
189            }
190          else if (! steal_lock)
191            {
192              if (retry_func)
193                SVN_ERR(retry_func(retry_baton, reposlocktoken, subpool));
194              apr_sleep(apr_time_from_sec(1));
195              continue;
196            }
197          else
198            {
199              if (stolen_lock_p)
200                *stolen_lock_p = svn_string_dup(reposlocktoken, pool);
201              unset = reposlocktoken;
202            }
203        }
204
205      /* No lock value in the repository, or we plan to steal it?
206         Well, if we've got a spare iteration, we'll try to set the
207         lock.  (We use the spare iteration to verify that we still
208         have the lock after setting it.) */
209      if (i < num_retries - 1)
210        {
211          /* Except in the very last iteration, try to set the lock. */
212          err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
213                                        be_atomic ? &unset : NULL,
214                                        mylocktoken, subpool);
215
216          if (be_atomic && err && is_atomicity_error(err))
217            {
218              /* Someone else has the lock.  No problem, we'll loop again. */
219              svn_error_clear(err);
220            }
221          else if (be_atomic && err == SVN_NO_ERROR)
222            {
223              /* Yay!  We have the lock!  However, for compatibility
224                 with concurrent processes that don't support
225                 atomicity, loop anyway to double-check that they
226                 haven't overwritten our lock.
227              */
228              continue;
229            }
230          else
231            {
232              /* We have a genuine error, or aren't atomic and need
233                 to loop.  */
234              SVN_ERR(err);
235            }
236        }
237    }
238
239  return svn_error_createf(APR_EINVAL, NULL,
240                           _("Couldn't get lock on destination repos "
241                             "after %d attempts"), i);
242}
243