util.c revision 302408
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