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
41static const char *
42get_path(const char *path_or_url,
43         svn_ra_session_t *ra_session,
44         apr_pool_t *pool)
45{
46  if (path_or_url == NULL)
47    {
48      svn_error_t *err = svn_ra_get_session_url(ra_session, &path_or_url,
49                                                pool);
50      if (err)
51        {
52          /* The SVN_ERR_UNSUPPORTED_FEATURE error in the caller is more
53             important, so dummy up the session's URL and chuck this error. */
54          svn_error_clear(err);
55          return _("<repository>");
56        }
57    }
58  return path_or_url;
59}
60
61svn_error_t *
62svn_ra__assert_mergeinfo_capable_server(svn_ra_session_t *ra_session,
63                                        const char *path_or_url,
64                                        apr_pool_t *pool)
65{
66  svn_boolean_t mergeinfo_capable;
67  SVN_ERR(svn_ra_has_capability(ra_session, &mergeinfo_capable,
68                                SVN_RA_CAPABILITY_MERGEINFO, pool));
69  if (! mergeinfo_capable)
70    {
71      path_or_url = get_path(path_or_url, ra_session, pool);
72      return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
73                               _("Retrieval of mergeinfo unsupported by '%s'"),
74                               svn_path_is_url(path_or_url)
75                                  ? path_or_url
76                                  : svn_dirent_local_style(path_or_url, pool));
77    }
78  return SVN_NO_ERROR;
79}
80
81svn_error_t *
82svn_ra__assert_capable_server(svn_ra_session_t *ra_session,
83                              const char *capability,
84                              const char *path_or_url,
85                              apr_pool_t *pool)
86{
87  if (!strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO))
88    return svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url,
89                                                   pool);
90
91  else
92    {
93      svn_boolean_t has;
94      SVN_ERR(svn_ra_has_capability(ra_session, &has, capability, pool));
95      if (! has)
96        {
97          path_or_url = get_path(path_or_url, ra_session, pool);
98          return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
99                                 _("The '%s' feature is not supported by '%s'"),
100                                 capability,
101                                 svn_path_is_url(path_or_url)
102                                    ? path_or_url
103                                    : svn_dirent_local_style(path_or_url,
104                                                             pool));
105        }
106    }
107  return SVN_NO_ERROR;
108}
109
110/* Does ERR mean "the current value of the revprop isn't equal to
111   the *OLD_VALUE_P you gave me"?
112 */
113static svn_boolean_t is_atomicity_error(svn_error_t *err)
114{
115  return svn_error_find_cause(err, SVN_ERR_FS_PROP_BASEVALUE_MISMATCH) != NULL;
116}
117
118svn_error_t *
119svn_ra__release_operational_lock(svn_ra_session_t *session,
120                                 const char *lock_revprop_name,
121                                 const svn_string_t *mylocktoken,
122                                 apr_pool_t *scratch_pool)
123{
124  svn_string_t *reposlocktoken;
125  svn_boolean_t be_atomic;
126
127  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
128                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
129                                scratch_pool));
130  SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
131                          &reposlocktoken, scratch_pool));
132  if (reposlocktoken && svn_string_compare(reposlocktoken, mylocktoken))
133    {
134      svn_error_t *err;
135
136      err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
137                                    be_atomic ? &mylocktoken : NULL, NULL,
138                                    scratch_pool);
139      if (is_atomicity_error(err))
140        {
141          return svn_error_createf(err->apr_err, err,
142                                   _("Lock was stolen by '%s'; unable to "
143                                     "remove it"), reposlocktoken->data);
144        }
145      else
146        SVN_ERR(err);
147    }
148
149  return SVN_NO_ERROR;
150}
151
152svn_error_t *
153svn_ra__get_operational_lock(const svn_string_t **lock_string_p,
154                             const svn_string_t **stolen_lock_p,
155                             svn_ra_session_t *session,
156                             const char *lock_revprop_name,
157                             svn_boolean_t steal_lock,
158                             int num_retries,
159                             svn_ra__lock_retry_func_t retry_func,
160                             void *retry_baton,
161                             svn_cancel_func_t cancel_func,
162                             void *cancel_baton,
163                             apr_pool_t *pool)
164{
165  char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
166  svn_string_t *mylocktoken, *reposlocktoken;
167  apr_status_t apr_err;
168  svn_boolean_t be_atomic;
169  apr_pool_t *subpool;
170  int i;
171
172  *lock_string_p = NULL;
173  if (stolen_lock_p)
174    *stolen_lock_p = NULL;
175
176  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
177                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool));
178
179  /* We build a lock token from the local hostname and a UUID.  */
180  apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
181  if (apr_err)
182    return svn_error_wrap_apr(apr_err,
183                              _("Unable to determine local hostname"));
184  mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
185                                   svn_uuid_generate(pool));
186
187  /* Ye Olde Retry Loope */
188  subpool = svn_pool_create(pool);
189
190  for (i = 0; i < num_retries; ++i)
191    {
192      svn_error_t *err;
193      const svn_string_t *unset = NULL;
194
195      svn_pool_clear(subpool);
196
197      /* Check for cancellation.  If we're cancelled, don't leave a
198         stray lock behind!  */
199      if (cancel_func)
200        {
201          err = cancel_func(cancel_baton);
202          if (err && err->apr_err == SVN_ERR_CANCELLED)
203            return svn_error_compose_create(
204                       svn_ra__release_operational_lock(session,
205                                                        lock_revprop_name,
206                                                        mylocktoken,
207                                                        subpool),
208                       err);
209          SVN_ERR(err);
210        }
211
212      /* Ask the repository for the value of the LOCK_REVPROP_NAME. */
213      SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
214                              &reposlocktoken, subpool));
215
216      /* Did we get a value from the repository?  We'll check to see
217         if it matches our token.  If so, we call it success.  If not
218         and we're told to steal locks, we remember the existing lock
219         token and fall through to the locking code; othewise, we
220         sleep and retry. */
221      if (reposlocktoken)
222        {
223          if (svn_string_compare(reposlocktoken, mylocktoken))
224            {
225              *lock_string_p = mylocktoken;
226              return SVN_NO_ERROR;
227            }
228          else if (! steal_lock)
229            {
230              if (retry_func)
231                SVN_ERR(retry_func(retry_baton, reposlocktoken, subpool));
232              apr_sleep(apr_time_from_sec(1));
233              continue;
234            }
235          else
236            {
237              if (stolen_lock_p)
238                *stolen_lock_p = svn_string_dup(reposlocktoken, pool);
239              unset = reposlocktoken;
240            }
241        }
242
243      /* No lock value in the repository, or we plan to steal it?
244         Well, if we've got a spare iteration, we'll try to set the
245         lock.  (We use the spare iteration to verify that we still
246         have the lock after setting it.) */
247      if (i < num_retries - 1)
248        {
249          /* Except in the very last iteration, try to set the lock. */
250          err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
251                                        be_atomic ? &unset : NULL,
252                                        mylocktoken, subpool);
253
254          if (be_atomic && err && is_atomicity_error(err))
255            {
256              /* Someone else has the lock.  No problem, we'll loop again. */
257              svn_error_clear(err);
258            }
259          else if (be_atomic && err == SVN_NO_ERROR)
260            {
261              /* Yay!  We have the lock!  However, for compatibility
262                 with concurrent processes that don't support
263                 atomicity, loop anyway to double-check that they
264                 haven't overwritten our lock.
265              */
266              continue;
267            }
268          else
269            {
270              /* We have a genuine error, or aren't atomic and need
271                 to loop.  */
272              SVN_ERR(err);
273            }
274        }
275    }
276
277  return svn_error_createf(APR_EINVAL, NULL,
278                           _("Couldn't get lock on destination repos "
279                             "after %d attempts"), i);
280}
281