1251881Speter/*
2251881Speter * util.c:  Repository access utility routines.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter/*** Includes. ***/
27251881Speter#include <apr_pools.h>
28251881Speter#include <apr_network_io.h>
29251881Speter
30251881Speter#include "svn_types.h"
31251881Speter#include "svn_pools.h"
32251881Speter#include "svn_error.h"
33251881Speter#include "svn_error_codes.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_path.h"
36251881Speter#include "svn_ra.h"
37251881Speter
38251881Speter#include "svn_private_config.h"
39251881Speter#include "private/svn_ra_private.h"
40251881Speter
41253734Speterstatic const char *
42253734Speterget_path(const char *path_or_url,
43253734Speter         svn_ra_session_t *ra_session,
44253734Speter         apr_pool_t *pool)
45253734Speter{
46253734Speter  if (path_or_url == NULL)
47253734Speter    {
48253734Speter      svn_error_t *err = svn_ra_get_session_url(ra_session, &path_or_url,
49253734Speter                                                pool);
50253734Speter      if (err)
51253734Speter        {
52253734Speter          /* The SVN_ERR_UNSUPPORTED_FEATURE error in the caller is more
53253734Speter             important, so dummy up the session's URL and chuck this error. */
54253734Speter          svn_error_clear(err);
55253734Speter          return _("<repository>");
56253734Speter        }
57253734Speter    }
58253734Speter  return path_or_url;
59253734Speter}
60253734Speter
61251881Spetersvn_error_t *
62251881Spetersvn_ra__assert_mergeinfo_capable_server(svn_ra_session_t *ra_session,
63251881Speter                                        const char *path_or_url,
64251881Speter                                        apr_pool_t *pool)
65251881Speter{
66251881Speter  svn_boolean_t mergeinfo_capable;
67251881Speter  SVN_ERR(svn_ra_has_capability(ra_session, &mergeinfo_capable,
68251881Speter                                SVN_RA_CAPABILITY_MERGEINFO, pool));
69251881Speter  if (! mergeinfo_capable)
70251881Speter    {
71253734Speter      path_or_url = get_path(path_or_url, ra_session, pool);
72251881Speter      return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
73251881Speter                               _("Retrieval of mergeinfo unsupported by '%s'"),
74251881Speter                               svn_path_is_url(path_or_url)
75251881Speter                                  ? path_or_url
76251881Speter                                  : svn_dirent_local_style(path_or_url, pool));
77251881Speter    }
78251881Speter  return SVN_NO_ERROR;
79251881Speter}
80251881Speter
81253734Spetersvn_error_t *
82253734Spetersvn_ra__assert_capable_server(svn_ra_session_t *ra_session,
83253734Speter                              const char *capability,
84253734Speter                              const char *path_or_url,
85253734Speter                              apr_pool_t *pool)
86253734Speter{
87253734Speter  if (!strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO))
88253734Speter    return svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url,
89253734Speter                                                   pool);
90253734Speter
91253734Speter  else
92253734Speter    {
93253734Speter      svn_boolean_t has;
94253734Speter      SVN_ERR(svn_ra_has_capability(ra_session, &has, capability, pool));
95253734Speter      if (! has)
96253734Speter        {
97253734Speter          path_or_url = get_path(path_or_url, ra_session, pool);
98253734Speter          return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
99253734Speter                                 _("The '%s' feature is not supported by '%s'"),
100253734Speter                                 capability,
101253734Speter                                 svn_path_is_url(path_or_url)
102253734Speter                                    ? path_or_url
103253734Speter                                    : svn_dirent_local_style(path_or_url,
104253734Speter                                                             pool));
105253734Speter        }
106253734Speter    }
107253734Speter  return SVN_NO_ERROR;
108253734Speter}
109253734Speter
110251881Speter/* Does ERR mean "the current value of the revprop isn't equal to
111251881Speter   the *OLD_VALUE_P you gave me"?
112251881Speter */
113251881Speterstatic svn_boolean_t is_atomicity_error(svn_error_t *err)
114251881Speter{
115251881Speter  return svn_error_find_cause(err, SVN_ERR_FS_PROP_BASEVALUE_MISMATCH) != NULL;
116251881Speter}
117251881Speter
118251881Spetersvn_error_t *
119251881Spetersvn_ra__release_operational_lock(svn_ra_session_t *session,
120251881Speter                                 const char *lock_revprop_name,
121251881Speter                                 const svn_string_t *mylocktoken,
122251881Speter                                 apr_pool_t *scratch_pool)
123251881Speter{
124251881Speter  svn_string_t *reposlocktoken;
125251881Speter  svn_boolean_t be_atomic;
126251881Speter
127251881Speter  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
128251881Speter                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
129251881Speter                                scratch_pool));
130251881Speter  SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
131251881Speter                          &reposlocktoken, scratch_pool));
132251881Speter  if (reposlocktoken && svn_string_compare(reposlocktoken, mylocktoken))
133251881Speter    {
134251881Speter      svn_error_t *err;
135251881Speter
136251881Speter      err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
137251881Speter                                    be_atomic ? &mylocktoken : NULL, NULL,
138251881Speter                                    scratch_pool);
139251881Speter      if (is_atomicity_error(err))
140251881Speter        {
141251881Speter          return svn_error_createf(err->apr_err, err,
142251881Speter                                   _("Lock was stolen by '%s'; unable to "
143251881Speter                                     "remove it"), reposlocktoken->data);
144251881Speter        }
145251881Speter      else
146251881Speter        SVN_ERR(err);
147251881Speter    }
148251881Speter
149251881Speter  return SVN_NO_ERROR;
150251881Speter}
151251881Speter
152251881Spetersvn_error_t *
153251881Spetersvn_ra__get_operational_lock(const svn_string_t **lock_string_p,
154251881Speter                             const svn_string_t **stolen_lock_p,
155251881Speter                             svn_ra_session_t *session,
156251881Speter                             const char *lock_revprop_name,
157251881Speter                             svn_boolean_t steal_lock,
158251881Speter                             int num_retries,
159251881Speter                             svn_ra__lock_retry_func_t retry_func,
160251881Speter                             void *retry_baton,
161251881Speter                             svn_cancel_func_t cancel_func,
162251881Speter                             void *cancel_baton,
163251881Speter                             apr_pool_t *pool)
164251881Speter{
165251881Speter  char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
166251881Speter  svn_string_t *mylocktoken, *reposlocktoken;
167251881Speter  apr_status_t apr_err;
168251881Speter  svn_boolean_t be_atomic;
169251881Speter  apr_pool_t *subpool;
170251881Speter  int i;
171251881Speter
172251881Speter  *lock_string_p = NULL;
173251881Speter  if (stolen_lock_p)
174251881Speter    *stolen_lock_p = NULL;
175251881Speter
176251881Speter  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
177251881Speter                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool));
178251881Speter
179251881Speter  /* We build a lock token from the local hostname and a UUID.  */
180251881Speter  apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
181251881Speter  if (apr_err)
182251881Speter    return svn_error_wrap_apr(apr_err,
183251881Speter                              _("Unable to determine local hostname"));
184251881Speter  mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
185251881Speter                                   svn_uuid_generate(pool));
186251881Speter
187251881Speter  /* Ye Olde Retry Loope */
188251881Speter  subpool = svn_pool_create(pool);
189251881Speter
190251881Speter  for (i = 0; i < num_retries; ++i)
191251881Speter    {
192251881Speter      svn_error_t *err;
193251881Speter      const svn_string_t *unset = NULL;
194251881Speter
195251881Speter      svn_pool_clear(subpool);
196251881Speter
197251881Speter      /* Check for cancellation.  If we're cancelled, don't leave a
198251881Speter         stray lock behind!  */
199251881Speter      if (cancel_func)
200251881Speter        {
201251881Speter          err = cancel_func(cancel_baton);
202251881Speter          if (err && err->apr_err == SVN_ERR_CANCELLED)
203251881Speter            return svn_error_compose_create(
204251881Speter                       svn_ra__release_operational_lock(session,
205251881Speter                                                        lock_revprop_name,
206251881Speter                                                        mylocktoken,
207251881Speter                                                        subpool),
208251881Speter                       err);
209251881Speter          SVN_ERR(err);
210251881Speter        }
211251881Speter
212251881Speter      /* Ask the repository for the value of the LOCK_REVPROP_NAME. */
213251881Speter      SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
214251881Speter                              &reposlocktoken, subpool));
215251881Speter
216251881Speter      /* Did we get a value from the repository?  We'll check to see
217251881Speter         if it matches our token.  If so, we call it success.  If not
218251881Speter         and we're told to steal locks, we remember the existing lock
219251881Speter         token and fall through to the locking code; othewise, we
220251881Speter         sleep and retry. */
221251881Speter      if (reposlocktoken)
222251881Speter        {
223251881Speter          if (svn_string_compare(reposlocktoken, mylocktoken))
224251881Speter            {
225251881Speter              *lock_string_p = mylocktoken;
226251881Speter              return SVN_NO_ERROR;
227251881Speter            }
228251881Speter          else if (! steal_lock)
229251881Speter            {
230251881Speter              if (retry_func)
231251881Speter                SVN_ERR(retry_func(retry_baton, reposlocktoken, subpool));
232251881Speter              apr_sleep(apr_time_from_sec(1));
233251881Speter              continue;
234251881Speter            }
235251881Speter          else
236251881Speter            {
237251881Speter              if (stolen_lock_p)
238251881Speter                *stolen_lock_p = svn_string_dup(reposlocktoken, pool);
239251881Speter              unset = reposlocktoken;
240251881Speter            }
241251881Speter        }
242251881Speter
243251881Speter      /* No lock value in the repository, or we plan to steal it?
244251881Speter         Well, if we've got a spare iteration, we'll try to set the
245251881Speter         lock.  (We use the spare iteration to verify that we still
246251881Speter         have the lock after setting it.) */
247251881Speter      if (i < num_retries - 1)
248251881Speter        {
249251881Speter          /* Except in the very last iteration, try to set the lock. */
250251881Speter          err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
251251881Speter                                        be_atomic ? &unset : NULL,
252251881Speter                                        mylocktoken, subpool);
253251881Speter
254251881Speter          if (be_atomic && err && is_atomicity_error(err))
255251881Speter            {
256251881Speter              /* Someone else has the lock.  No problem, we'll loop again. */
257251881Speter              svn_error_clear(err);
258251881Speter            }
259251881Speter          else if (be_atomic && err == SVN_NO_ERROR)
260251881Speter            {
261251881Speter              /* Yay!  We have the lock!  However, for compatibility
262251881Speter                 with concurrent processes that don't support
263251881Speter                 atomicity, loop anyway to double-check that they
264251881Speter                 haven't overwritten our lock.
265251881Speter              */
266251881Speter              continue;
267251881Speter            }
268251881Speter          else
269251881Speter            {
270251881Speter              /* We have a genuine error, or aren't atomic and need
271251881Speter                 to loop.  */
272251881Speter              SVN_ERR(err);
273251881Speter            }
274251881Speter        }
275251881Speter    }
276251881Speter
277251881Speter  return svn_error_createf(APR_EINVAL, NULL,
278251881Speter                           _("Couldn't get lock on destination repos "
279251881Speter                             "after %d attempts"), i);
280251881Speter}
281