serf.c revision 262253
1/* 2 * serf.c : entry point for ra_serf 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#define APR_WANT_STRFUNC 27#include <apr_want.h> 28 29#include <apr_uri.h> 30#include <serf.h> 31 32#include "svn_pools.h" 33#include "svn_ra.h" 34#include "svn_dav.h" 35#include "svn_xml.h" 36#include "../libsvn_ra/ra_loader.h" 37#include "svn_config.h" 38#include "svn_delta.h" 39#include "svn_dirent_uri.h" 40#include "svn_hash.h" 41#include "svn_path.h" 42#include "svn_time.h" 43#include "svn_version.h" 44 45#include "private/svn_dav_protocol.h" 46#include "private/svn_dep_compat.h" 47#include "private/svn_fspath.h" 48#include "private/svn_subr_private.h" 49#include "svn_private_config.h" 50 51#include "ra_serf.h" 52 53 54/* Implements svn_ra__vtable_t.get_version(). */ 55static const svn_version_t * 56ra_serf_version(void) 57{ 58 SVN_VERSION_BODY; 59} 60 61#define RA_SERF_DESCRIPTION \ 62 N_("Module for accessing a repository via WebDAV protocol using serf.") 63 64#define RA_SERF_DESCRIPTION_VER \ 65 N_("Module for accessing a repository via WebDAV protocol using serf.\n" \ 66 " - using serf %d.%d.%d") 67 68/* Implements svn_ra__vtable_t.get_description(). */ 69static const char * 70ra_serf_get_description(apr_pool_t *pool) 71{ 72 int major, minor, patch; 73 74 serf_lib_version(&major, &minor, &patch); 75 return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch); 76} 77 78/* Implements svn_ra__vtable_t.get_schemes(). */ 79static const char * const * 80ra_serf_get_schemes(apr_pool_t *pool) 81{ 82 static const char *serf_ssl[] = { "http", "https", NULL }; 83#if 0 84 /* ### Temporary: to shut up a warning. */ 85 static const char *serf_no_ssl[] = { "http", NULL }; 86#endif 87 88 /* TODO: Runtime detection. */ 89 return serf_ssl; 90} 91 92/* Load the setting http-auth-types from the global or server specific 93 section, parse its value and set the types of authentication we should 94 accept from the server. */ 95static svn_error_t * 96load_http_auth_types(apr_pool_t *pool, svn_config_t *config, 97 const char *server_group, 98 int *authn_types) 99{ 100 const char *http_auth_types = NULL; 101 *authn_types = SERF_AUTHN_NONE; 102 103 svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL, 104 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL); 105 106 if (server_group) 107 { 108 svn_config_get(config, &http_auth_types, server_group, 109 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types); 110 } 111 112 if (http_auth_types) 113 { 114 char *token; 115 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1); 116 apr_collapse_spaces(auth_types_list, http_auth_types); 117 while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL) 118 { 119 if (svn_cstring_casecmp("basic", token) == 0) 120 *authn_types |= SERF_AUTHN_BASIC; 121 else if (svn_cstring_casecmp("digest", token) == 0) 122 *authn_types |= SERF_AUTHN_DIGEST; 123 else if (svn_cstring_casecmp("ntlm", token) == 0) 124 *authn_types |= SERF_AUTHN_NTLM; 125 else if (svn_cstring_casecmp("negotiate", token) == 0) 126 *authn_types |= SERF_AUTHN_NEGOTIATE; 127 else 128 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, 129 _("Invalid config: unknown %s " 130 "'%s'"), 131 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token); 132 } 133 } 134 else 135 { 136 /* Nothing specified by the user, so accept all types. */ 137 *authn_types = SERF_AUTHN_ALL; 138 } 139 140 return SVN_NO_ERROR; 141} 142 143/* Default HTTP timeout (in seconds); overridden by the 'http-timeout' 144 runtime configuration variable. */ 145#define DEFAULT_HTTP_TIMEOUT 600 146 147/* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */ 148#define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests" 149 150 151static svn_error_t * 152load_config(svn_ra_serf__session_t *session, 153 apr_hash_t *config_hash, 154 apr_pool_t *pool) 155{ 156 svn_config_t *config, *config_client; 157 const char *server_group; 158 const char *proxy_host = NULL; 159 const char *port_str = NULL; 160 const char *timeout_str = NULL; 161 const char *exceptions; 162 apr_port_t proxy_port; 163 svn_tristate_t chunked_requests; 164 165 if (config_hash) 166 { 167 config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS); 168 config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG); 169 } 170 else 171 { 172 config = NULL; 173 config_client = NULL; 174 } 175 176 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 177 SVN_CONFIG_SECTION_GLOBAL, 178 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE)); 179 svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL, 180 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL); 181 182 if (session->wc_callbacks->auth_baton) 183 { 184 if (config_client) 185 { 186 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 187 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, 188 config_client); 189 } 190 if (config) 191 { 192 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 193 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, 194 config); 195 } 196 } 197 198 /* Use the default proxy-specific settings if and only if 199 "http-proxy-exceptions" is not set to exclude this host. */ 200 svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL, 201 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, ""); 202 if (! svn_cstring_match_glob_list(session->session_url.hostname, 203 svn_cstring_split(exceptions, ",", 204 TRUE, pool))) 205 { 206 svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL, 207 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL); 208 svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL, 209 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL); 210 svn_config_get(config, &session->proxy_username, 211 SVN_CONFIG_SECTION_GLOBAL, 212 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL); 213 svn_config_get(config, &session->proxy_password, 214 SVN_CONFIG_SECTION_GLOBAL, 215 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL); 216 } 217 218 /* Load the global ssl settings, if set. */ 219 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 220 SVN_CONFIG_SECTION_GLOBAL, 221 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 222 TRUE)); 223 svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL, 224 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL); 225 226 /* If set, read the flag that tells us to do bulk updates or not. Defaults 227 to skelta updates. */ 228 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 229 SVN_CONFIG_SECTION_GLOBAL, 230 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 231 "auto", 232 svn_tristate_unknown)); 233 234 /* Load the maximum number of parallel session connections. */ 235 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 236 SVN_CONFIG_SECTION_GLOBAL, 237 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 238 SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS)); 239 240 /* Should we use chunked transfer encoding. */ 241 SVN_ERR(svn_config_get_tristate(config, &chunked_requests, 242 SVN_CONFIG_SECTION_GLOBAL, 243 OPTION_HTTP_CHUNKED_REQUESTS, 244 "auto", svn_tristate_unknown)); 245 246 if (config) 247 server_group = svn_config_find_group(config, 248 session->session_url.hostname, 249 SVN_CONFIG_SECTION_GROUPS, pool); 250 else 251 server_group = NULL; 252 253 if (server_group) 254 { 255 SVN_ERR(svn_config_get_bool(config, &session->using_compression, 256 server_group, 257 SVN_CONFIG_OPTION_HTTP_COMPRESSION, 258 session->using_compression)); 259 svn_config_get(config, &timeout_str, server_group, 260 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str); 261 262 svn_auth_set_parameter(session->wc_callbacks->auth_baton, 263 SVN_AUTH_PARAM_SERVER_GROUP, server_group); 264 265 /* Load the group proxy server settings, overriding global 266 settings. We intentionally ignore 'http-proxy-exceptions' 267 here because, well, if this site was an exception, why is 268 there a per-server proxy configuration for it? */ 269 svn_config_get(config, &proxy_host, server_group, 270 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host); 271 svn_config_get(config, &port_str, server_group, 272 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str); 273 svn_config_get(config, &session->proxy_username, server_group, 274 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, 275 session->proxy_username); 276 svn_config_get(config, &session->proxy_password, server_group, 277 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, 278 session->proxy_password); 279 280 /* Load the group ssl settings. */ 281 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca, 282 server_group, 283 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA, 284 session->trust_default_ca)); 285 svn_config_get(config, &session->ssl_authorities, server_group, 286 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, 287 session->ssl_authorities); 288 289 /* Load the group bulk updates flag. */ 290 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates, 291 server_group, 292 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, 293 "auto", 294 session->bulk_updates)); 295 296 /* Load the maximum number of parallel session connections, 297 overriding global values. */ 298 SVN_ERR(svn_config_get_int64(config, &session->max_connections, 299 server_group, 300 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 301 session->max_connections)); 302 303 /* Should we use chunked transfer encoding. */ 304 SVN_ERR(svn_config_get_tristate(config, &chunked_requests, 305 server_group, 306 OPTION_HTTP_CHUNKED_REQUESTS, 307 "auto", chunked_requests)); 308 } 309 310 /* Don't allow the http-max-connections value to be larger than our 311 compiled-in limit, or to be too small to operate. Broken 312 functionality and angry administrators are equally undesirable. */ 313 if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT) 314 session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT; 315 if (session->max_connections < 2) 316 session->max_connections = 2; 317 318 /* Parse the connection timeout value, if any. */ 319 session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT); 320 if (timeout_str) 321 { 322 char *endstr; 323 const long int timeout = strtol(timeout_str, &endstr, 10); 324 325 if (*endstr) 326 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 327 _("Invalid config: illegal character in " 328 "timeout value")); 329 if (timeout < 0) 330 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL, 331 _("Invalid config: negative timeout value")); 332 session->timeout = apr_time_from_sec(timeout); 333 } 334 SVN_ERR_ASSERT(session->timeout >= 0); 335 336 /* Convert the proxy port value, if any. */ 337 if (port_str) 338 { 339 char *endstr; 340 const long int port = strtol(port_str, &endstr, 10); 341 342 if (*endstr) 343 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 344 _("Invalid URL: illegal character in proxy " 345 "port number")); 346 if (port < 0) 347 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 348 _("Invalid URL: negative proxy port number")); 349 if (port > 65535) 350 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL, 351 _("Invalid URL: proxy port number greater " 352 "than maximum TCP port number 65535")); 353 proxy_port = (apr_port_t) port; 354 } 355 else 356 { 357 proxy_port = 80; 358 } 359 360 if (proxy_host) 361 { 362 apr_sockaddr_t *proxy_addr; 363 apr_status_t status; 364 365 status = apr_sockaddr_info_get(&proxy_addr, proxy_host, 366 APR_UNSPEC, proxy_port, 0, 367 session->pool); 368 if (status) 369 { 370 return svn_ra_serf__wrap_err( 371 status, _("Could not resolve proxy server '%s'"), 372 proxy_host); 373 } 374 session->using_proxy = TRUE; 375 serf_config_proxy(session->context, proxy_addr); 376 } 377 else 378 { 379 session->using_proxy = FALSE; 380 } 381 382 /* Setup detect_chunking and using_chunked_requests based on 383 * the chunked_requests tristate */ 384 if (chunked_requests == svn_tristate_unknown) 385 { 386 session->detect_chunking = TRUE; 387 session->using_chunked_requests = TRUE; 388 } 389 else if (chunked_requests == svn_tristate_true) 390 { 391 session->detect_chunking = FALSE; 392 session->using_chunked_requests = TRUE; 393 } 394 else /* chunked_requests == svn_tristate_false */ 395 { 396 session->detect_chunking = FALSE; 397 session->using_chunked_requests = FALSE; 398 } 399 400 /* Setup authentication. */ 401 SVN_ERR(load_http_auth_types(pool, config, server_group, 402 &session->authn_types)); 403 serf_config_authn_types(session->context, session->authn_types); 404 serf_config_credentials_callback(session->context, 405 svn_ra_serf__credentials_callback); 406 407 return SVN_NO_ERROR; 408} 409#undef DEFAULT_HTTP_TIMEOUT 410 411static void 412svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written) 413{ 414 const svn_ra_serf__session_t *serf_sess = progress_baton; 415 if (serf_sess->progress_func) 416 { 417 serf_sess->progress_func(read + written, -1, 418 serf_sess->progress_baton, 419 serf_sess->pool); 420 } 421} 422 423/** Our User-Agent string. */ 424static const char * 425get_user_agent_string(apr_pool_t *pool) 426{ 427 int major, minor, patch; 428 serf_lib_version(&major, &minor, &patch); 429 430 return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d", 431 SVN_VER_NUMBER, SVN_BUILD_TARGET, 432 major, minor, patch); 433} 434 435/* Implements svn_ra__vtable_t.open_session(). */ 436static svn_error_t * 437svn_ra_serf__open(svn_ra_session_t *session, 438 const char **corrected_url, 439 const char *session_URL, 440 const svn_ra_callbacks2_t *callbacks, 441 void *callback_baton, 442 apr_hash_t *config, 443 apr_pool_t *pool) 444{ 445 apr_status_t status; 446 svn_ra_serf__session_t *serf_sess; 447 apr_uri_t url; 448 const char *client_string = NULL; 449 svn_error_t *err; 450 451 if (corrected_url) 452 *corrected_url = NULL; 453 454 serf_sess = apr_pcalloc(pool, sizeof(*serf_sess)); 455 serf_sess->pool = svn_pool_create(pool); 456 serf_sess->wc_callbacks = callbacks; 457 serf_sess->wc_callback_baton = callback_baton; 458 serf_sess->progress_func = callbacks->progress_func; 459 serf_sess->progress_baton = callbacks->progress_baton; 460 serf_sess->cancel_func = callbacks->cancel_func; 461 serf_sess->cancel_baton = callback_baton; 462 463 /* todo: reuse serf context across sessions */ 464 serf_sess->context = serf_context_create(serf_sess->pool); 465 466 SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache, 467 serf_sess->pool)); 468 469 470 status = apr_uri_parse(serf_sess->pool, session_URL, &url); 471 if (status) 472 { 473 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 474 _("Illegal URL '%s'"), 475 session_URL); 476 } 477 /* Depending the version of apr-util in use, for root paths url.path 478 will be NULL or "", where serf requires "/". */ 479 if (url.path == NULL || url.path[0] == '\0') 480 { 481 url.path = apr_pstrdup(serf_sess->pool, "/"); 482 } 483 if (!url.port) 484 { 485 url.port = apr_uri_port_of_scheme(url.scheme); 486 } 487 serf_sess->session_url = url; 488 serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL); 489 serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0); 490 491 serf_sess->supports_deadprop_count = svn_tristate_unknown; 492 493 serf_sess->capabilities = apr_hash_make(serf_sess->pool); 494 495 /* We have to assume that the server only supports HTTP/1.0. Once it's clear 496 HTTP/1.1 is supported, we can upgrade. */ 497 serf_sess->http10 = TRUE; 498 499 /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable 500 this, if we find an intervening proxy does not support chunked requests. */ 501 serf_sess->using_chunked_requests = TRUE; 502 503 SVN_ERR(load_config(serf_sess, config, serf_sess->pool)); 504 505 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool, 506 sizeof(*serf_sess->conns[0])); 507 serf_sess->conns[0]->bkt_alloc = 508 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL); 509 serf_sess->conns[0]->session = serf_sess; 510 serf_sess->conns[0]->last_status_code = -1; 511 512 /* create the user agent string */ 513 if (callbacks->get_client_string) 514 SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool)); 515 516 if (client_string) 517 serf_sess->useragent = apr_pstrcat(pool, get_user_agent_string(pool), " ", 518 client_string, (char *)NULL); 519 else 520 serf_sess->useragent = get_user_agent_string(pool); 521 522 /* go ahead and tell serf about the connection. */ 523 status = 524 serf_connection_create2(&serf_sess->conns[0]->conn, 525 serf_sess->context, 526 url, 527 svn_ra_serf__conn_setup, serf_sess->conns[0], 528 svn_ra_serf__conn_closed, serf_sess->conns[0], 529 serf_sess->pool); 530 if (status) 531 return svn_ra_serf__wrap_err(status, NULL); 532 533 /* Set the progress callback. */ 534 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress, 535 serf_sess); 536 537 serf_sess->num_conns = 1; 538 539 session->priv = serf_sess; 540 541 err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool); 542 543 /* serf should produce a usable error code instead of APR_EGENERAL */ 544 if (err && err->apr_err == APR_EGENERAL) 545 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err, 546 _("Connection to '%s' failed"), session_URL); 547 SVN_ERR(err); 548 549 /* We have set up a useful connection (that doesn't indication a redirect). 550 If we've been told there is possibly a worrisome proxy in our path to the 551 server AND we switched to HTTP/1.1 (chunked requests), then probe for 552 problems in any proxy. */ 553 if ((corrected_url == NULL || *corrected_url == NULL) 554 && serf_sess->detect_chunking && !serf_sess->http10) 555 SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool)); 556 557 return SVN_NO_ERROR; 558} 559 560/* Implements svn_ra__vtable_t.reparent(). */ 561static svn_error_t * 562svn_ra_serf__reparent(svn_ra_session_t *ra_session, 563 const char *url, 564 apr_pool_t *pool) 565{ 566 svn_ra_serf__session_t *session = ra_session->priv; 567 apr_uri_t new_url; 568 apr_status_t status; 569 570 /* If it's the URL we already have, wave our hands and do nothing. */ 571 if (strcmp(session->session_url_str, url) == 0) 572 { 573 return SVN_NO_ERROR; 574 } 575 576 if (!session->repos_root_str) 577 { 578 const char *vcc_url; 579 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 580 } 581 582 if (!svn_uri__is_ancestor(session->repos_root_str, url)) 583 { 584 return svn_error_createf( 585 SVN_ERR_RA_ILLEGAL_URL, NULL, 586 _("URL '%s' is not a child of the session's repository root " 587 "URL '%s'"), url, session->repos_root_str); 588 } 589 590 status = apr_uri_parse(pool, url, &new_url); 591 if (status) 592 { 593 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 594 _("Illegal repository URL '%s'"), url); 595 } 596 597 /* Depending the version of apr-util in use, for root paths url.path 598 will be NULL or "", where serf requires "/". */ 599 /* ### Maybe we should use a string buffer for these strings so we 600 ### don't allocate memory in the session on every reparent? */ 601 if (new_url.path == NULL || new_url.path[0] == '\0') 602 { 603 session->session_url.path = apr_pstrdup(session->pool, "/"); 604 } 605 else 606 { 607 session->session_url.path = apr_pstrdup(session->pool, new_url.path); 608 } 609 session->session_url_str = apr_pstrdup(session->pool, url); 610 611 return SVN_NO_ERROR; 612} 613 614/* Implements svn_ra__vtable_t.get_session_url(). */ 615static svn_error_t * 616svn_ra_serf__get_session_url(svn_ra_session_t *ra_session, 617 const char **url, 618 apr_pool_t *pool) 619{ 620 svn_ra_serf__session_t *session = ra_session->priv; 621 *url = apr_pstrdup(pool, session->session_url_str); 622 return SVN_NO_ERROR; 623} 624 625/* Implements svn_ra__vtable_t.get_latest_revnum(). */ 626static svn_error_t * 627svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session, 628 svn_revnum_t *latest_revnum, 629 apr_pool_t *pool) 630{ 631 svn_ra_serf__session_t *session = ra_session->priv; 632 633 return svn_error_trace(svn_ra_serf__get_youngest_revnum( 634 latest_revnum, session, pool)); 635} 636 637/* Implements svn_ra__vtable_t.rev_proplist(). */ 638static svn_error_t * 639svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session, 640 svn_revnum_t rev, 641 apr_hash_t **ret_props, 642 apr_pool_t *pool) 643{ 644 svn_ra_serf__session_t *session = ra_session->priv; 645 apr_hash_t *props; 646 const char *propfind_path; 647 648 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 649 { 650 propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev); 651 652 /* svn_ra_serf__retrieve_props() wants to added the revision as 653 a Label to the PROPFIND, which isn't really necessary when 654 querying a rev-stub URI. *Shrug* Probably okay to leave the 655 Label, but whatever. */ 656 rev = SVN_INVALID_REVNUM; 657 } 658 else 659 { 660 /* Use the VCC as the propfind target path. */ 661 SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool)); 662 } 663 664 /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */ 665 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], 666 propfind_path, rev, "0", all_props, 667 pool, pool)); 668 669 SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props, 670 pool, pool)); 671 672 return SVN_NO_ERROR; 673} 674 675/* Implements svn_ra__vtable_t.rev_prop(). */ 676static svn_error_t * 677svn_ra_serf__rev_prop(svn_ra_session_t *session, 678 svn_revnum_t rev, 679 const char *name, 680 svn_string_t **value, 681 apr_pool_t *pool) 682{ 683 apr_hash_t *props; 684 685 SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool)); 686 687 *value = svn_hash_gets(props, name); 688 689 return SVN_NO_ERROR; 690} 691 692static svn_error_t * 693fetch_path_props(apr_hash_t **props, 694 svn_ra_serf__session_t *session, 695 const char *session_relpath, 696 svn_revnum_t revision, 697 const svn_ra_serf__dav_props_t *desired_props, 698 apr_pool_t *result_pool, 699 apr_pool_t *scratch_pool) 700{ 701 const char *url; 702 703 url = session->session_url.path; 704 705 /* If we have a relative path, append it. */ 706 if (session_relpath) 707 url = svn_path_url_add_component2(url, session_relpath, scratch_pool); 708 709 /* If we were given a specific revision, get a URL that refers to that 710 specific revision (rather than floating with HEAD). */ 711 if (SVN_IS_VALID_REVNUM(revision)) 712 { 713 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, 714 session, NULL /* conn */, 715 url, revision, 716 scratch_pool, scratch_pool)); 717 } 718 719 /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant. 720 Or we started with SVN_INVALID_REVNUM and URL may be floating. */ 721 SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0], 722 url, SVN_INVALID_REVNUM, 723 desired_props, 724 result_pool, scratch_pool)); 725 726 return SVN_NO_ERROR; 727} 728 729/* Implements svn_ra__vtable_t.check_path(). */ 730static svn_error_t * 731svn_ra_serf__check_path(svn_ra_session_t *ra_session, 732 const char *rel_path, 733 svn_revnum_t revision, 734 svn_node_kind_t *kind, 735 apr_pool_t *pool) 736{ 737 svn_ra_serf__session_t *session = ra_session->priv; 738 apr_hash_t *props; 739 740 svn_error_t *err = fetch_path_props(&props, session, rel_path, 741 revision, check_path_props, 742 pool, pool); 743 744 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 745 { 746 svn_error_clear(err); 747 *kind = svn_node_none; 748 } 749 else 750 { 751 /* Any other error, raise to caller. */ 752 if (err) 753 return svn_error_trace(err); 754 755 SVN_ERR(svn_ra_serf__get_resource_type(kind, props)); 756 } 757 758 return SVN_NO_ERROR; 759} 760 761 762struct dirent_walker_baton_t { 763 /* Update the fields in this entry. */ 764 svn_dirent_t *entry; 765 766 svn_tristate_t *supports_deadprop_count; 767 768 /* If allocations are necessary, then use this pool. */ 769 apr_pool_t *result_pool; 770}; 771 772static svn_error_t * 773dirent_walker(void *baton, 774 const char *ns, 775 const char *name, 776 const svn_string_t *val, 777 apr_pool_t *scratch_pool) 778{ 779 struct dirent_walker_baton_t *dwb = baton; 780 781 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 782 { 783 dwb->entry->has_props = TRUE; 784 } 785 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 786 { 787 dwb->entry->has_props = TRUE; 788 } 789 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 790 { 791 if(strcmp(name, "deadprop-count") == 0) 792 { 793 if (*val->data) 794 { 795 apr_int64_t deadprop_count; 796 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data)); 797 dwb->entry->has_props = deadprop_count > 0; 798 if (dwb->supports_deadprop_count) 799 *dwb->supports_deadprop_count = svn_tristate_true; 800 } 801 else if (dwb->supports_deadprop_count) 802 *dwb->supports_deadprop_count = svn_tristate_false; 803 } 804 } 805 else if (strcmp(ns, "DAV:") == 0) 806 { 807 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 808 { 809 dwb->entry->created_rev = SVN_STR_TO_REV(val->data); 810 } 811 else if (strcmp(name, "creator-displayname") == 0) 812 { 813 dwb->entry->last_author = val->data; 814 } 815 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 816 { 817 SVN_ERR(svn_time_from_cstring(&dwb->entry->time, 818 val->data, 819 dwb->result_pool)); 820 } 821 else if (strcmp(name, "getcontentlength") == 0) 822 { 823 /* 'getcontentlength' property is empty for directories. */ 824 if (val->len) 825 { 826 SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data)); 827 } 828 } 829 else if (strcmp(name, "resourcetype") == 0) 830 { 831 if (strcmp(val->data, "collection") == 0) 832 { 833 dwb->entry->kind = svn_node_dir; 834 } 835 else 836 { 837 dwb->entry->kind = svn_node_file; 838 } 839 } 840 } 841 842 return SVN_NO_ERROR; 843} 844 845struct path_dirent_visitor_t { 846 apr_hash_t *full_paths; 847 apr_hash_t *base_paths; 848 const char *orig_path; 849 svn_tristate_t supports_deadprop_count; 850 apr_pool_t *result_pool; 851}; 852 853static svn_error_t * 854path_dirent_walker(void *baton, 855 const char *path, apr_ssize_t path_len, 856 const char *ns, apr_ssize_t ns_len, 857 const char *name, apr_ssize_t name_len, 858 const svn_string_t *val, 859 apr_pool_t *pool) 860{ 861 struct path_dirent_visitor_t *dirents = baton; 862 struct dirent_walker_baton_t dwb; 863 svn_dirent_t *entry; 864 865 /* Skip our original path. */ 866 if (strcmp(path, dirents->orig_path) == 0) 867 { 868 return SVN_NO_ERROR; 869 } 870 871 entry = apr_hash_get(dirents->full_paths, path, path_len); 872 873 if (!entry) 874 { 875 const char *base_name; 876 877 entry = svn_dirent_create(pool); 878 879 apr_hash_set(dirents->full_paths, path, path_len, entry); 880 881 base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool), 882 pool); 883 884 svn_hash_sets(dirents->base_paths, base_name, entry); 885 } 886 887 dwb.entry = entry; 888 dwb.supports_deadprop_count = &dirents->supports_deadprop_count; 889 dwb.result_pool = dirents->result_pool; 890 return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool)); 891} 892 893static const svn_ra_serf__dav_props_t * 894get_dirent_props(apr_uint32_t dirent_fields, 895 svn_ra_serf__session_t *session, 896 apr_pool_t *pool) 897{ 898 svn_ra_serf__dav_props_t *prop; 899 apr_array_header_t *props = apr_array_make 900 (pool, 7, sizeof(svn_ra_serf__dav_props_t)); 901 902 if (session->supports_deadprop_count != svn_tristate_false 903 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS)) 904 { 905 if (dirent_fields & SVN_DIRENT_KIND) 906 { 907 prop = apr_array_push(props); 908 prop->namespace = "DAV:"; 909 prop->name = "resourcetype"; 910 } 911 912 if (dirent_fields & SVN_DIRENT_SIZE) 913 { 914 prop = apr_array_push(props); 915 prop->namespace = "DAV:"; 916 prop->name = "getcontentlength"; 917 } 918 919 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 920 { 921 prop = apr_array_push(props); 922 prop->namespace = SVN_DAV_PROP_NS_DAV; 923 prop->name = "deadprop-count"; 924 } 925 926 if (dirent_fields & SVN_DIRENT_CREATED_REV) 927 { 928 svn_ra_serf__dav_props_t *p = apr_array_push(props); 929 p->namespace = "DAV:"; 930 p->name = SVN_DAV__VERSION_NAME; 931 } 932 933 if (dirent_fields & SVN_DIRENT_TIME) 934 { 935 prop = apr_array_push(props); 936 prop->namespace = "DAV:"; 937 prop->name = SVN_DAV__CREATIONDATE; 938 } 939 940 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 941 { 942 prop = apr_array_push(props); 943 prop->namespace = "DAV:"; 944 prop->name = "creator-displayname"; 945 } 946 } 947 else 948 { 949 /* We found an old subversion server that can't handle 950 the deadprop-count property in the way we expect. 951 952 The neon behavior is to retrieve all properties in this case */ 953 prop = apr_array_push(props); 954 prop->namespace = "DAV:"; 955 prop->name = "allprop"; 956 } 957 958 prop = apr_array_push(props); 959 prop->namespace = NULL; 960 prop->name = NULL; 961 962 return (svn_ra_serf__dav_props_t *) props->elts; 963} 964 965/* Implements svn_ra__vtable_t.stat(). */ 966static svn_error_t * 967svn_ra_serf__stat(svn_ra_session_t *ra_session, 968 const char *rel_path, 969 svn_revnum_t revision, 970 svn_dirent_t **dirent, 971 apr_pool_t *pool) 972{ 973 svn_ra_serf__session_t *session = ra_session->priv; 974 apr_hash_t *props; 975 svn_error_t *err; 976 struct dirent_walker_baton_t dwb; 977 svn_tristate_t deadprop_count = svn_tristate_unknown; 978 979 err = fetch_path_props(&props, 980 session, rel_path, revision, 981 get_dirent_props(SVN_DIRENT_ALL, session, pool), 982 pool, pool); 983 if (err) 984 { 985 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 986 { 987 svn_error_clear(err); 988 *dirent = NULL; 989 return SVN_NO_ERROR; 990 } 991 else 992 return svn_error_trace(err); 993 } 994 995 dwb.entry = svn_dirent_create(pool); 996 dwb.supports_deadprop_count = &deadprop_count; 997 dwb.result_pool = pool; 998 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); 999 1000 if (deadprop_count == svn_tristate_false 1001 && session->supports_deadprop_count == svn_tristate_unknown 1002 && !dwb.entry->has_props) 1003 { 1004 /* We have to requery as the server didn't give us the right 1005 information */ 1006 session->supports_deadprop_count = svn_tristate_false; 1007 1008 SVN_ERR(fetch_path_props(&props, 1009 session, rel_path, SVN_INVALID_REVNUM, 1010 get_dirent_props(SVN_DIRENT_ALL, session, pool), 1011 pool, pool)); 1012 1013 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); 1014 } 1015 1016 if (deadprop_count != svn_tristate_unknown) 1017 session->supports_deadprop_count = deadprop_count; 1018 1019 *dirent = dwb.entry; 1020 1021 return SVN_NO_ERROR; 1022} 1023 1024/* Reads the 'resourcetype' property from the list PROPS and checks if the 1025 * resource at PATH@REVISION really is a directory. Returns 1026 * SVN_ERR_FS_NOT_DIRECTORY if not. 1027 */ 1028static svn_error_t * 1029resource_is_directory(apr_hash_t *props) 1030{ 1031 svn_node_kind_t kind; 1032 1033 SVN_ERR(svn_ra_serf__get_resource_type(&kind, props)); 1034 1035 if (kind != svn_node_dir) 1036 { 1037 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1038 _("Can't get entries of non-directory")); 1039 } 1040 1041 return SVN_NO_ERROR; 1042} 1043 1044/* Implements svn_ra__vtable_t.get_dir(). */ 1045static svn_error_t * 1046svn_ra_serf__get_dir(svn_ra_session_t *ra_session, 1047 apr_hash_t **dirents, 1048 svn_revnum_t *fetched_rev, 1049 apr_hash_t **ret_props, 1050 const char *rel_path, 1051 svn_revnum_t revision, 1052 apr_uint32_t dirent_fields, 1053 apr_pool_t *pool) 1054{ 1055 svn_ra_serf__session_t *session = ra_session->priv; 1056 const char *path; 1057 1058 path = session->session_url.path; 1059 1060 /* If we have a relative path, URI encode and append it. */ 1061 if (rel_path) 1062 { 1063 path = svn_path_url_add_component2(path, rel_path, pool); 1064 } 1065 1066 /* If the user specified a peg revision other than HEAD, we have to fetch 1067 the baseline collection url for that revision. If not, we can use the 1068 public url. */ 1069 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) 1070 { 1071 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev, 1072 session, NULL /* conn */, 1073 path, revision, 1074 pool, pool)); 1075 revision = SVN_INVALID_REVNUM; 1076 } 1077 /* REVISION is always SVN_INVALID_REVNUM */ 1078 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); 1079 1080 /* If we're asked for children, fetch them now. */ 1081 if (dirents) 1082 { 1083 struct path_dirent_visitor_t dirent_walk; 1084 apr_hash_t *props; 1085 const char *rtype; 1086 1087 /* Always request node kind to check that path is really a 1088 * directory. 1089 */ 1090 dirent_fields |= SVN_DIRENT_KIND; 1091 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], 1092 path, SVN_INVALID_REVNUM, "1", 1093 get_dirent_props(dirent_fields, 1094 session, pool), 1095 pool, pool)); 1096 1097 /* Check if the path is really a directory. */ 1098 rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype"); 1099 if (rtype == NULL || strcmp(rtype, "collection") != 0) 1100 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 1101 _("Can't get entries of non-directory")); 1102 1103 /* We're going to create two hashes to help the walker along. 1104 * We're going to return the 2nd one back to the caller as it 1105 * will have the basenames it expects. 1106 */ 1107 dirent_walk.full_paths = apr_hash_make(pool); 1108 dirent_walk.base_paths = apr_hash_make(pool); 1109 dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool); 1110 dirent_walk.supports_deadprop_count = svn_tristate_unknown; 1111 dirent_walk.result_pool = pool; 1112 1113 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, 1114 path_dirent_walker, &dirent_walk, 1115 pool)); 1116 1117 if (dirent_walk.supports_deadprop_count == svn_tristate_false 1118 && session->supports_deadprop_count == svn_tristate_unknown 1119 && dirent_fields & SVN_DIRENT_HAS_PROPS) 1120 { 1121 /* We have to requery as the server didn't give us the right 1122 information */ 1123 session->supports_deadprop_count = svn_tristate_false; 1124 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, 1125 session->conns[0], 1126 path, SVN_INVALID_REVNUM, "1", 1127 get_dirent_props(dirent_fields, 1128 session, pool), 1129 pool, pool)); 1130 1131 apr_hash_clear(dirent_walk.full_paths); 1132 apr_hash_clear(dirent_walk.base_paths); 1133 1134 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, 1135 path_dirent_walker, 1136 &dirent_walk, pool)); 1137 } 1138 1139 *dirents = dirent_walk.base_paths; 1140 1141 if (dirent_walk.supports_deadprop_count != svn_tristate_unknown) 1142 session->supports_deadprop_count = dirent_walk.supports_deadprop_count; 1143 } 1144 1145 /* If we're asked for the directory properties, fetch them too. */ 1146 if (ret_props) 1147 { 1148 apr_hash_t *props; 1149 1150 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0], 1151 path, SVN_INVALID_REVNUM, 1152 all_props, 1153 pool, pool)); 1154 1155 /* Check if the path is really a directory. */ 1156 SVN_ERR(resource_is_directory(props)); 1157 1158 /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props() 1159 ### put them into POOL, so we're okay. */ 1160 SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool)); 1161 } 1162 1163 return SVN_NO_ERROR; 1164} 1165 1166svn_error_t * 1167svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session, 1168 const char **url, 1169 apr_pool_t *pool) 1170{ 1171 svn_ra_serf__session_t *session = ra_session->priv; 1172 1173 if (!session->repos_root_str) 1174 { 1175 const char *vcc_url; 1176 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 1177 } 1178 1179 *url = session->repos_root_str; 1180 return SVN_NO_ERROR; 1181} 1182 1183/* TODO: to fetch the uuid from the repository, we need: 1184 1. a path that exists in HEAD 1185 2. a path that's readable 1186 1187 get_uuid handles the case where a path doesn't exist in HEAD and also the 1188 case where the root of the repository is not readable. 1189 However, it does not handle the case where we're fetching path not existing 1190 in HEAD of a repository with unreadable root directory. 1191 1192 Implements svn_ra__vtable_t.get_uuid(). 1193 */ 1194static svn_error_t * 1195svn_ra_serf__get_uuid(svn_ra_session_t *ra_session, 1196 const char **uuid, 1197 apr_pool_t *pool) 1198{ 1199 svn_ra_serf__session_t *session = ra_session->priv; 1200 1201 if (!session->uuid) 1202 { 1203 const char *vcc_url; 1204 1205 /* We should never get here if we have HTTP v2 support, because 1206 any server with that support should be transmitting the 1207 UUID in the initial OPTIONS response. */ 1208 SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); 1209 1210 /* We're not interested in vcc_url and relative_url, but this call also 1211 stores the repository's uuid in the session. */ 1212 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); 1213 if (!session->uuid) 1214 { 1215 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, 1216 _("The UUID property was not found on the " 1217 "resource or any of its parents")); 1218 } 1219 } 1220 1221 *uuid = session->uuid; 1222 1223 return SVN_NO_ERROR; 1224} 1225 1226 1227static const svn_ra__vtable_t serf_vtable = { 1228 ra_serf_version, 1229 ra_serf_get_description, 1230 ra_serf_get_schemes, 1231 svn_ra_serf__open, 1232 svn_ra_serf__reparent, 1233 svn_ra_serf__get_session_url, 1234 svn_ra_serf__get_latest_revnum, 1235 svn_ra_serf__get_dated_revision, 1236 svn_ra_serf__change_rev_prop, 1237 svn_ra_serf__rev_proplist, 1238 svn_ra_serf__rev_prop, 1239 svn_ra_serf__get_commit_editor, 1240 svn_ra_serf__get_file, 1241 svn_ra_serf__get_dir, 1242 svn_ra_serf__get_mergeinfo, 1243 svn_ra_serf__do_update, 1244 svn_ra_serf__do_switch, 1245 svn_ra_serf__do_status, 1246 svn_ra_serf__do_diff, 1247 svn_ra_serf__get_log, 1248 svn_ra_serf__check_path, 1249 svn_ra_serf__stat, 1250 svn_ra_serf__get_uuid, 1251 svn_ra_serf__get_repos_root, 1252 svn_ra_serf__get_locations, 1253 svn_ra_serf__get_location_segments, 1254 svn_ra_serf__get_file_revs, 1255 svn_ra_serf__lock, 1256 svn_ra_serf__unlock, 1257 svn_ra_serf__get_lock, 1258 svn_ra_serf__get_locks, 1259 svn_ra_serf__replay, 1260 svn_ra_serf__has_capability, 1261 svn_ra_serf__replay_range, 1262 svn_ra_serf__get_deleted_rev, 1263 svn_ra_serf__register_editor_shim_callbacks, 1264 svn_ra_serf__get_inherited_props 1265}; 1266 1267svn_error_t * 1268svn_ra_serf__init(const svn_version_t *loader_version, 1269 const svn_ra__vtable_t **vtable, 1270 apr_pool_t *pool) 1271{ 1272 static const svn_version_checklist_t checklist[] = 1273 { 1274 { "svn_subr", svn_subr_version }, 1275 { "svn_delta", svn_delta_version }, 1276 { NULL, NULL } 1277 }; 1278 int serf_major; 1279 int serf_minor; 1280 int serf_patch; 1281 1282 SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal)); 1283 1284 /* Simplified version check to make sure we can safely use the 1285 VTABLE parameter. The RA loader does a more exhaustive check. */ 1286 if (loader_version->major != SVN_VER_MAJOR) 1287 { 1288 return svn_error_createf( 1289 SVN_ERR_VERSION_MISMATCH, NULL, 1290 _("Unsupported RA loader version (%d) for ra_serf"), 1291 loader_version->major); 1292 } 1293 1294 /* Make sure that we have loaded a compatible library: the MAJOR must 1295 match, and the minor must be at *least* what we compiled against. 1296 The patch level is simply ignored. */ 1297 serf_lib_version(&serf_major, &serf_minor, &serf_patch); 1298 if (serf_major != SERF_MAJOR_VERSION 1299 || serf_minor < SERF_MINOR_VERSION) 1300 { 1301 return svn_error_createf( 1302 /* ### should return a unique error */ 1303 SVN_ERR_VERSION_MISMATCH, NULL, 1304 _("ra_serf was compiled for serf %d.%d.%d but loaded " 1305 "an incompatible %d.%d.%d library"), 1306 SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION, 1307 serf_major, serf_minor, serf_patch); 1308 } 1309 1310 *vtable = &serf_vtable; 1311 1312 return SVN_NO_ERROR; 1313} 1314 1315/* Compatibility wrapper for pre-1.2 subversions. Needed? */ 1316#define NAME "ra_serf" 1317#define DESCRIPTION RA_SERF_DESCRIPTION 1318#define VTBL serf_vtable 1319#define INITFUNC svn_ra_serf__init 1320#define COMPAT_INITFUNC svn_ra_serf_init 1321#include "../libsvn_ra/wrapper_template.h" 1322