1/*
2 * client.c :  Functions for repository access via the Subversion protocol
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#include "svn_private_config.h"
27
28#define APR_WANT_STRFUNC
29#include <apr_want.h>
30#include <apr_general.h>
31#include <apr_strings.h>
32#include <apr_network_io.h>
33#include <apr_uri.h>
34
35#include "svn_hash.h"
36#include "svn_types.h"
37#include "svn_string.h"
38#include "svn_dirent_uri.h"
39#include "svn_error.h"
40#include "svn_time.h"
41#include "svn_path.h"
42#include "svn_pools.h"
43#include "svn_config.h"
44#include "svn_ra.h"
45#include "svn_ra_svn.h"
46#include "svn_props.h"
47#include "svn_mergeinfo.h"
48#include "svn_version.h"
49
50#include "svn_private_config.h"
51
52#include "private/svn_fspath.h"
53#include "private/svn_subr_private.h"
54
55#include "../libsvn_ra/ra_loader.h"
56
57#include "ra_svn.h"
58
59#ifdef SVN_HAVE_SASL
60#define DO_AUTH svn_ra_svn__do_cyrus_auth
61#else
62#define DO_AUTH svn_ra_svn__do_internal_auth
63#endif
64
65/* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
66   whatever reason) deems svn_depth_immediates as non-recursive, which
67   is ... kinda true, but not true enough for our purposes.  We need
68   our requested recursion level to be *at least* as recursive as the
69   real depth we're looking for.
70 */
71#define DEPTH_TO_RECURSE(d)    \
72        ((d) == svn_depth_unknown || (d) > svn_depth_files)
73
74typedef struct ra_svn_commit_callback_baton_t {
75  svn_ra_svn__session_baton_t *sess_baton;
76  apr_pool_t *pool;
77  svn_revnum_t *new_rev;
78  svn_commit_callback2_t callback;
79  void *callback_baton;
80} ra_svn_commit_callback_baton_t;
81
82typedef struct ra_svn_reporter_baton_t {
83  svn_ra_svn__session_baton_t *sess_baton;
84  svn_ra_svn_conn_t *conn;
85  apr_pool_t *pool;
86  const svn_delta_editor_t *editor;
87  void *edit_baton;
88} ra_svn_reporter_baton_t;
89
90/* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
91   portion. */
92static void parse_tunnel(const char *url, const char **tunnel,
93                         apr_pool_t *pool)
94{
95  *tunnel = NULL;
96
97  if (strncasecmp(url, "svn", 3) != 0)
98    return;
99  url += 3;
100
101  /* Get the tunnel specification, if any. */
102  if (*url == '+')
103    {
104      const char *p;
105
106      url++;
107      p = strchr(url, ':');
108      if (!p)
109        return;
110      *tunnel = apr_pstrmemdup(pool, url, p - url);
111    }
112}
113
114static svn_error_t *make_connection(const char *hostname, unsigned short port,
115                                    apr_socket_t **sock, apr_pool_t *pool)
116{
117  apr_sockaddr_t *sa;
118  apr_status_t status;
119  int family = APR_INET;
120
121  /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
122     APR_UNSPEC, because it may give us back an IPV6 address even if we can't
123     create IPV6 sockets.  */
124
125#if APR_HAVE_IPV6
126#ifdef MAX_SECS_TO_LINGER
127  status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
128#else
129  status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
130                             APR_PROTO_TCP, pool);
131#endif
132  if (status == 0)
133    {
134      apr_socket_close(*sock);
135      family = APR_UNSPEC;
136    }
137#endif
138
139  /* Resolve the hostname. */
140  status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
141  if (status)
142    return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
143                             hostname);
144  /* Iterate through the returned list of addresses attempting to
145   * connect to each in turn. */
146  do
147    {
148      /* Create the socket. */
149#ifdef MAX_SECS_TO_LINGER
150      /* ### old APR interface */
151      status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
152#else
153      status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
154                                 pool);
155#endif
156      if (status == APR_SUCCESS)
157        {
158          status = apr_socket_connect(*sock, sa);
159          if (status != APR_SUCCESS)
160            apr_socket_close(*sock);
161        }
162      sa = sa->next;
163    }
164  while (status != APR_SUCCESS && sa);
165
166  if (status)
167    return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
168                              hostname);
169
170  /* Enable TCP keep-alives on the socket so we time out when
171   * the connection breaks due to network-layer problems.
172   * If the peer has dropped the connection due to a network partition
173   * or a crash, or if the peer no longer considers the connection
174   * valid because we are behind a NAT and our public IP has changed,
175   * it will respond to the keep-alive probe with a RST instead of an
176   * acknowledgment segment, which will cause svn to abort the session
177   * even while it is currently blocked waiting for data from the peer.
178   * See issue #3347. */
179  status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
180  if (status)
181    {
182      /* It's not a fatal error if we cannot enable keep-alives. */
183    }
184
185  return SVN_NO_ERROR;
186}
187
188/* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
189   property diffs in LIST, received from the server. */
190static svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
191                                     apr_pool_t *pool,
192                                     apr_array_header_t **diffs)
193{
194  int i;
195
196  *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
197
198  for (i = 0; i < list->nelts; i++)
199    {
200      svn_prop_t *prop;
201      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
202
203      if (elt->kind != SVN_RA_SVN_LIST)
204        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
205                                _("Prop diffs element not a list"));
206      prop = apr_array_push(*diffs);
207      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
208                                      &prop->value));
209    }
210  return SVN_NO_ERROR;
211}
212
213/* Parse a lockdesc, provided in LIST as specified by the protocol into
214   LOCK, allocated in POOL. */
215static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
216                               svn_lock_t **lock)
217{
218  const char *cdate, *edate;
219  *lock = svn_lock_create(pool);
220  SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
221                                  &(*lock)->token, &(*lock)->owner,
222                                  &(*lock)->comment, &cdate, &edate));
223  (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
224  SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
225  if (edate)
226    SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
227  return SVN_NO_ERROR;
228}
229
230/* --- AUTHENTICATION ROUTINES --- */
231
232svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
233                                       apr_pool_t *pool,
234                                       const char *mech, const char *mech_arg)
235{
236  return svn_error_trace(svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg));
237}
238
239static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
240                                        apr_pool_t *pool)
241{
242  svn_ra_svn_conn_t *conn = sess->conn;
243  apr_array_header_t *mechlist;
244  const char *realm;
245
246  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
247  if (mechlist->nelts == 0)
248    return SVN_NO_ERROR;
249  return DO_AUTH(sess, mechlist, realm, pool);
250}
251
252/* --- REPORTER IMPLEMENTATION --- */
253
254static svn_error_t *ra_svn_set_path(void *baton, const char *path,
255                                    svn_revnum_t rev,
256                                    svn_depth_t depth,
257                                    svn_boolean_t start_empty,
258                                    const char *lock_token,
259                                    apr_pool_t *pool)
260{
261  ra_svn_reporter_baton_t *b = baton;
262
263  SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
264                                         start_empty, lock_token, depth));
265  return SVN_NO_ERROR;
266}
267
268static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
269                                       apr_pool_t *pool)
270{
271  ra_svn_reporter_baton_t *b = baton;
272
273  SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
274  return SVN_NO_ERROR;
275}
276
277static svn_error_t *ra_svn_link_path(void *baton, const char *path,
278                                     const char *url,
279                                     svn_revnum_t rev,
280                                     svn_depth_t depth,
281                                     svn_boolean_t start_empty,
282                                     const char *lock_token,
283                                     apr_pool_t *pool)
284{
285  ra_svn_reporter_baton_t *b = baton;
286
287  SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
288                                          start_empty, lock_token, depth));
289  return SVN_NO_ERROR;
290}
291
292static svn_error_t *ra_svn_finish_report(void *baton,
293                                         apr_pool_t *pool)
294{
295  ra_svn_reporter_baton_t *b = baton;
296
297  SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
298  SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
299  SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
300                                   NULL, FALSE));
301  SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
302  return SVN_NO_ERROR;
303}
304
305static svn_error_t *ra_svn_abort_report(void *baton,
306                                        apr_pool_t *pool)
307{
308  ra_svn_reporter_baton_t *b = baton;
309
310  SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
311  return SVN_NO_ERROR;
312}
313
314static svn_ra_reporter3_t ra_svn_reporter = {
315  ra_svn_set_path,
316  ra_svn_delete_path,
317  ra_svn_link_path,
318  ra_svn_finish_report,
319  ra_svn_abort_report
320};
321
322/* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
323 * EDITOR/EDIT_BATON when it gets the finish_report() call.
324 *
325 * Allocate the new reporter in POOL.
326 */
327static svn_error_t *
328ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
329                    apr_pool_t *pool,
330                    const svn_delta_editor_t *editor,
331                    void *edit_baton,
332                    const char *target,
333                    svn_depth_t depth,
334                    const svn_ra_reporter3_t **reporter,
335                    void **report_baton)
336{
337  ra_svn_reporter_baton_t *b;
338  const svn_delta_editor_t *filter_editor;
339  void *filter_baton;
340
341  /* We can skip the depth filtering when the user requested
342     depth_files or depth_infinity because the server will
343     transmit the right stuff anyway. */
344  if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
345      && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
346    {
347      SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
348                                            &filter_baton,
349                                            editor, edit_baton, depth,
350                                            *target != '\0',
351                                            pool));
352      editor = filter_editor;
353      edit_baton = filter_baton;
354    }
355
356  b = apr_palloc(pool, sizeof(*b));
357  b->sess_baton = sess_baton;
358  b->conn = sess_baton->conn;
359  b->pool = pool;
360  b->editor = editor;
361  b->edit_baton = edit_baton;
362
363  *reporter = &ra_svn_reporter;
364  *report_baton = b;
365
366  return SVN_NO_ERROR;
367}
368
369/* --- RA LAYER IMPLEMENTATION --- */
370
371/* (Note: *ARGV_P is an output parameter.) */
372static svn_error_t *find_tunnel_agent(const char *tunnel,
373                                      const char *hostinfo,
374                                      const char ***argv_p,
375                                      apr_hash_t *config, apr_pool_t *pool)
376{
377  svn_config_t *cfg;
378  const char *val, *var, *cmd;
379  char **cmd_argv;
380  const char **argv;
381  apr_size_t len;
382  apr_status_t status;
383  int n;
384
385  /* Look up the tunnel specification in config. */
386  cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
387  svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
388
389  /* We have one predefined tunnel scheme, if it isn't overridden by config. */
390  if (!val && strcmp(tunnel, "ssh") == 0)
391    {
392      /* Killing the tunnel agent with SIGTERM leads to unsightly
393       * stderr output from ssh, unless we pass -q.
394       * The "-q" option to ssh is widely supported: all versions of
395       * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
396       * versions have it too. If the user is using some other ssh
397       * implementation that doesn't accept it, they can override it
398       * in the [tunnels] section of the config. */
399      val = "$SVN_SSH ssh -q";
400    }
401
402  if (!val || !*val)
403    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
404                             _("Undefined tunnel scheme '%s'"), tunnel);
405
406  /* If the scheme definition begins with "$varname", it means there
407   * is an environment variable which can override the command. */
408  if (*val == '$')
409    {
410      val++;
411      len = strcspn(val, " ");
412      var = apr_pstrmemdup(pool, val, len);
413      cmd = getenv(var);
414      if (!cmd)
415        {
416          cmd = val + len;
417          while (*cmd == ' ')
418            cmd++;
419          if (!*cmd)
420            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
421                                     _("Tunnel scheme %s requires environment "
422                                       "variable %s to be defined"), tunnel,
423                                     var);
424        }
425    }
426  else
427    cmd = val;
428
429  /* Tokenize the command into a list of arguments. */
430  status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
431  if (status != APR_SUCCESS)
432    return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
433
434  /* Calc number of the fixed arguments. */
435  for (n = 0; cmd_argv[n] != NULL; n++)
436    ;
437
438  argv = apr_palloc(pool, (n + 4) * sizeof(char *));
439
440  /* Append the fixed arguments to the result. */
441  for (n = 0; cmd_argv[n] != NULL; n++)
442    argv[n] = cmd_argv[n];
443
444  argv[n++] = svn_path_uri_decode(hostinfo, pool);
445  argv[n++] = "svnserve";
446  argv[n++] = "-t";
447  argv[n] = NULL;
448
449  *argv_p = argv;
450  return SVN_NO_ERROR;
451}
452
453/* This function handles any errors which occur in the child process
454 * created for a tunnel agent.  We write the error out as a command
455 * failure; the code in ra_svn_open() to read the server's greeting
456 * will see the error and return it to the caller. */
457static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
458                                       const char *desc)
459{
460  svn_ra_svn_conn_t *conn;
461  apr_file_t *in_file, *out_file;
462  svn_stream_t *in_stream, *out_stream;
463  svn_error_t *err;
464
465  if (apr_file_open_stdin(&in_file, pool)
466      || apr_file_open_stdout(&out_file, pool))
467    return;
468
469  in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool);
470  out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool);
471
472  conn = svn_ra_svn_create_conn4(NULL, in_stream, out_stream,
473                                 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
474                                 0, pool);
475  err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
476  svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
477  svn_error_clear(err);
478  svn_error_clear(svn_ra_svn__flush(conn, pool));
479}
480
481/* (Note: *CONN is an output parameter.) */
482static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
483                                apr_pool_t *pool)
484{
485  apr_status_t status;
486  apr_proc_t *proc;
487  apr_procattr_t *attr;
488  svn_error_t *err;
489
490  status = apr_procattr_create(&attr, pool);
491  if (status == APR_SUCCESS)
492    status = apr_procattr_io_set(attr, 1, 1, 0);
493  if (status == APR_SUCCESS)
494    status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
495  if (status == APR_SUCCESS)
496    status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
497  proc = apr_palloc(pool, sizeof(*proc));
498  if (status == APR_SUCCESS)
499    status = apr_proc_create(proc, *args, args, NULL, attr, pool);
500  if (status != APR_SUCCESS)
501    return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
502                            svn_error_wrap_apr(status,
503                                               _("Can't create tunnel")), NULL);
504
505  /* Arrange for the tunnel agent to get a SIGTERM on pool
506   * cleanup.  This is a little extreme, but the alternatives
507   * weren't working out.
508   *
509   * Closing the pipes and waiting for the process to die
510   * was prone to mysterious hangs which are difficult to
511   * diagnose (e.g. svnserve dumps core due to unrelated bug;
512   * sshd goes into zombie state; ssh connection is never
513   * closed; ssh never terminates).
514   * See also the long dicussion in issue #2580 if you really
515   * want to know various reasons for these problems and
516   * the different opinions on this issue.
517   *
518   * On Win32, APR does not support KILL_ONLY_ONCE. It only has
519   * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
520   * KILL_ALWAYS, which immediately calls TerminateProcess().
521   * This instantly kills the tunnel, leaving sshd and svnserve
522   * on a remote machine running indefinitely. These processes
523   * accumulate. The problem is most often seen with a fast client
524   * machine and a modest internet connection, as the tunnel
525   * is killed before being able to gracefully complete the
526   * session. In that case, svn is unusable 100% of the time on
527   * the windows machine. Thus, on Win32, we use KILL_NEVER and
528   * take the lesser of two evils.
529   */
530#ifdef WIN32
531  apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
532#else
533  apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
534#endif
535
536  /* APR pipe objects inherit by default.  But we don't want the
537   * tunnel agent's pipes held open by future child processes
538   * (such as other ra_svn sessions), so turn that off. */
539  apr_file_inherit_unset(proc->in);
540  apr_file_inherit_unset(proc->out);
541
542  /* Guard against dotfile output to stdout on the server. */
543  *conn = svn_ra_svn_create_conn4(NULL,
544                                  svn_stream_from_aprfile2(proc->out, FALSE,
545                                                           pool),
546                                  svn_stream_from_aprfile2(proc->in, FALSE,
547                                                           pool),
548                                  SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
549                                  0, 0, pool);
550  err = svn_ra_svn__skip_leading_garbage(*conn, pool);
551  if (err)
552    return svn_error_quick_wrap(
553             err,
554             _("To better debug SSH connection problems, remove the -q "
555               "option from 'ssh' in the [tunnels] section of your "
556               "Subversion configuration file."));
557
558  return SVN_NO_ERROR;
559}
560
561/* Parse URL inot URI, validating it and setting the default port if none
562   was given.  Allocate the URI fileds out of POOL. */
563static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
564                              apr_pool_t *pool)
565{
566  apr_status_t apr_err;
567
568  apr_err = apr_uri_parse(pool, url, uri);
569
570  if (apr_err != 0)
571    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
572                             _("Illegal svn repository URL '%s'"), url);
573
574  return SVN_NO_ERROR;
575}
576
577/* This structure is used as a baton for the pool cleanup function to
578   store tunnel parameters used by the close-tunnel callback. */
579struct tunnel_data_t {
580  void *tunnel_context;
581  void *tunnel_baton;
582  svn_ra_close_tunnel_func_t close_tunnel;
583  svn_stream_t *request;
584  svn_stream_t *response;
585};
586
587/* Pool cleanup function that invokes the close-tunnel callback. */
588static apr_status_t close_tunnel_cleanup(void *baton)
589{
590  const struct tunnel_data_t *const td = baton;
591
592  if (td->close_tunnel)
593    td->close_tunnel(td->tunnel_context, td->tunnel_baton);
594
595  svn_error_clear(svn_stream_close(td->request));
596
597  /* We might have one stream to use for both request and response! */
598  if (td->request != td->response)
599    svn_error_clear(svn_stream_close(td->response));
600
601  return APR_SUCCESS; /* ignored */
602}
603
604/* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
605   URI is a parsed version of URL.  CALLBACKS and CALLBACKS_BATON
606   are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL,
607   it is the name of the tunnel type parsed from the URL scheme.
608   If TUNNEL_ARGV is not NULL, it points to a program argument list to use
609   when invoking the tunnel agent.
610*/
611static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
612                                 const char *url,
613                                 const apr_uri_t *uri,
614                                 const char *tunnel_name,
615                                 const char **tunnel_argv,
616                                 apr_hash_t *config,
617                                 const svn_ra_callbacks2_t *callbacks,
618                                 void *callbacks_baton,
619                                 svn_auth_baton_t *auth_baton,
620                                 apr_pool_t *result_pool,
621                                 apr_pool_t *scratch_pool)
622{
623  svn_ra_svn__session_baton_t *sess;
624  svn_ra_svn_conn_t *conn;
625  apr_socket_t *sock;
626  apr_uint64_t minver, maxver;
627  apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
628  const char *client_string = NULL;
629  apr_pool_t *pool = result_pool;
630
631  sess = apr_palloc(pool, sizeof(*sess));
632  sess->pool = pool;
633  sess->is_tunneled = (tunnel_name != NULL);
634  sess->url = apr_pstrdup(pool, url);
635  sess->user = uri->user;
636  sess->hostname = uri->hostname;
637  sess->tunnel_name = tunnel_name;
638  sess->tunnel_argv = tunnel_argv;
639  sess->callbacks = callbacks;
640  sess->callbacks_baton = callbacks_baton;
641  sess->bytes_read = sess->bytes_written = 0;
642  sess->auth_baton = auth_baton;
643
644  if (config)
645    SVN_ERR(svn_config_copy_config(&sess->config, config, pool));
646  else
647    sess->config = NULL;
648
649  if (tunnel_name)
650    {
651      sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>",
652                                        tunnel_name,
653                                        uri->hostname, uri->port);
654
655      if (tunnel_argv)
656        SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
657      else
658        {
659          struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td));
660
661          td->tunnel_baton = callbacks->tunnel_baton;
662          td->close_tunnel = NULL;
663
664          SVN_ERR(callbacks->open_tunnel_func(
665                      &td->request, &td->response,
666                      &td->close_tunnel, &td->tunnel_context,
667                      callbacks->tunnel_baton, tunnel_name,
668                      uri->user, uri->hostname, uri->port,
669                      callbacks->cancel_func, callbacks_baton,
670                      pool));
671
672          apr_pool_cleanup_register(pool, td, close_tunnel_cleanup,
673                                    apr_pool_cleanup_null);
674
675          conn = svn_ra_svn_create_conn4(NULL, td->response, td->request,
676                                         SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
677                                         0, 0, pool);
678          SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool));
679        }
680    }
681  else
682    {
683      sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
684                                        uri->port ? uri->port : SVN_RA_SVN_PORT);
685
686      SVN_ERR(make_connection(uri->hostname,
687                              uri->port ? uri->port : SVN_RA_SVN_PORT,
688                              &sock, pool));
689      conn = svn_ra_svn_create_conn4(sock, NULL, NULL,
690                                     SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
691                                     0, 0, pool);
692    }
693
694  /* Build the useragent string, querying the client for any
695     customizations it wishes to note.  For historical reasons, we
696     still deliver the hard-coded client version info
697     (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
698     separately in the protocol/capabilities handshake below.  But the
699     commit logic wants the combined form for use with the
700     SVN_PROP_TXN_USER_AGENT ephemeral property because that's
701     consistent with our DAV approach.  */
702  if (sess->callbacks->get_client_string != NULL)
703    SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
704                                               &client_string, pool));
705  if (client_string)
706    sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
707                                  client_string, SVN_VA_NULL);
708  else
709    sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
710
711  /* Make sure we set conn->session before reading from it,
712   * because the reader and writer functions expect a non-NULL value. */
713  sess->conn = conn;
714  conn->session = sess;
715
716  /* Read server's greeting. */
717  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
718                                        &mechlist, &server_caplist));
719
720  /* We support protocol version 2. */
721  if (minver > 2)
722    return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
723                             _("Server requires minimum version %d"),
724                             (int) minver);
725  if (maxver < 2)
726    return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
727                             _("Server only supports versions up to %d"),
728                             (int) maxver);
729  SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
730
731  /* All released versions of Subversion support edit-pipeline,
732   * so we do not support servers that do not. */
733  if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
734    return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
735                            _("Server does not support edit pipelining"));
736
737  /* In protocol version 2, we send back our protocol version, our
738   * capability list, and the URL, and subsequently there is an auth
739   * request. */
740  /* Client-side capabilities list: */
741  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
742                                  (apr_uint64_t) 2,
743                                  SVN_RA_SVN_CAP_EDIT_PIPELINE,
744                                  SVN_RA_SVN_CAP_SVNDIFF1,
745                                  SVN_RA_SVN_CAP_ABSENT_ENTRIES,
746                                  SVN_RA_SVN_CAP_DEPTH,
747                                  SVN_RA_SVN_CAP_MERGEINFO,
748                                  SVN_RA_SVN_CAP_LOG_REVPROPS,
749                                  url,
750                                  SVN_RA_SVN__DEFAULT_USERAGENT,
751                                  client_string));
752  SVN_ERR(handle_auth_request(sess, pool));
753
754  /* This is where the security layer would go into effect if we
755   * supported security layers, which is a ways off. */
756
757  /* Read the repository's uuid and root URL, and perhaps learn more
758     capabilities that weren't available before now. */
759  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
760                                        &conn->repos_root, &repos_caplist));
761  if (repos_caplist)
762    SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
763
764  if (conn->repos_root)
765    {
766      conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
767      /* We should check that the returned string is a prefix of url, since
768         that's the API guarantee, but this isn't true for 1.0 servers.
769         Checking the length prevents client crashes. */
770      if (strlen(conn->repos_root) > strlen(url))
771        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
772                                _("Impossibly long repository root from "
773                                  "server"));
774    }
775
776  *sess_p = sess;
777
778  return SVN_NO_ERROR;
779}
780
781
782#ifdef SVN_HAVE_SASL
783#define RA_SVN_DESCRIPTION \
784  N_("Module for accessing a repository using the svn network protocol.\n" \
785     "  - with Cyrus SASL authentication")
786#else
787#define RA_SVN_DESCRIPTION \
788  N_("Module for accessing a repository using the svn network protocol.")
789#endif
790
791static const char *ra_svn_get_description(apr_pool_t *pool)
792{
793  return _(RA_SVN_DESCRIPTION);
794}
795
796static const char * const *
797ra_svn_get_schemes(apr_pool_t *pool)
798{
799  static const char *schemes[] = { "svn", NULL };
800
801  return schemes;
802}
803
804
805
806static svn_error_t *ra_svn_open(svn_ra_session_t *session,
807                                const char **corrected_url,
808                                const char *url,
809                                const svn_ra_callbacks2_t *callbacks,
810                                void *callback_baton,
811                                svn_auth_baton_t *auth_baton,
812                                apr_hash_t *config,
813                                apr_pool_t *result_pool,
814                                apr_pool_t *scratch_pool)
815{
816  apr_pool_t *sess_pool = svn_pool_create(result_pool);
817  svn_ra_svn__session_baton_t *sess;
818  const char *tunnel, **tunnel_argv;
819  apr_uri_t uri;
820  svn_config_t *cfg, *cfg_client;
821
822  /* We don't support server-prescribed redirections in ra-svn. */
823  if (corrected_url)
824    *corrected_url = NULL;
825
826  SVN_ERR(parse_url(url, &uri, sess_pool));
827
828  parse_tunnel(url, &tunnel, result_pool);
829
830  /* Use the default tunnel implementation if we got a tunnel name,
831     but either do not have tunnel handler callbacks installed, or
832     the handlers don't like the tunnel name. */
833  if (tunnel
834      && (!callbacks->open_tunnel_func
835          || (callbacks->check_tunnel_func && callbacks->open_tunnel_func
836              && !callbacks->check_tunnel_func(callbacks->tunnel_baton,
837                                               tunnel))))
838    SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
839                              result_pool));
840  else
841    tunnel_argv = NULL;
842
843  cfg_client = config
844               ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
845               : NULL;
846  cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
847  svn_auth_set_parameter(auth_baton,
848                         SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
849  svn_auth_set_parameter(auth_baton,
850                         SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
851
852  /* We open the session in a subpool so we can get rid of it if we
853     reparent with a server that doesn't support reparenting. */
854  SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config,
855                       callbacks, callback_baton,
856                       auth_baton, sess_pool, scratch_pool));
857  session->priv = sess;
858
859  return SVN_NO_ERROR;
860}
861
862static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session,
863                                       svn_ra_session_t *old_session,
864                                       const char *new_session_url,
865                                       apr_pool_t *result_pool,
866                                       apr_pool_t *scratch_pool)
867{
868  svn_ra_svn__session_baton_t *old_sess = old_session->priv;
869
870  SVN_ERR(ra_svn_open(new_session, NULL, new_session_url,
871                      old_sess->callbacks, old_sess->callbacks_baton,
872                      old_sess->auth_baton, old_sess->config,
873                      result_pool, scratch_pool));
874
875  return SVN_NO_ERROR;
876}
877
878static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
879                                    const char *url,
880                                    apr_pool_t *pool)
881{
882  svn_ra_svn__session_baton_t *sess = ra_session->priv;
883  svn_ra_svn_conn_t *conn = sess->conn;
884  svn_error_t *err;
885  apr_pool_t *sess_pool;
886  svn_ra_svn__session_baton_t *new_sess;
887  apr_uri_t uri;
888
889  SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
890  err = handle_auth_request(sess, pool);
891  if (! err)
892    {
893      SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
894      sess->url = apr_pstrdup(sess->pool, url);
895      return SVN_NO_ERROR;
896    }
897  else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
898    return err;
899
900  /* Servers before 1.4 doesn't support this command; try to reconnect
901     instead. */
902  svn_error_clear(err);
903  /* Create a new subpool of the RA session pool. */
904  sess_pool = svn_pool_create(ra_session->pool);
905  err = parse_url(url, &uri, sess_pool);
906  if (! err)
907    err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv,
908                       sess->config, sess->callbacks, sess->callbacks_baton,
909                       sess->auth_baton, sess_pool, sess_pool);
910  /* We destroy the new session pool on error, since it is allocated in
911     the main session pool. */
912  if (err)
913    {
914      svn_pool_destroy(sess_pool);
915      return err;
916    }
917
918  /* We have a new connection, assign it and destroy the old. */
919  ra_session->priv = new_sess;
920  svn_pool_destroy(sess->pool);
921
922  return SVN_NO_ERROR;
923}
924
925static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
926                                           const char **url, apr_pool_t *pool)
927{
928  svn_ra_svn__session_baton_t *sess = session->priv;
929  *url = apr_pstrdup(pool, sess->url);
930  return SVN_NO_ERROR;
931}
932
933static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
934                                          svn_revnum_t *rev, apr_pool_t *pool)
935{
936  svn_ra_svn__session_baton_t *sess_baton = session->priv;
937  svn_ra_svn_conn_t *conn = sess_baton->conn;
938
939  SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
940  SVN_ERR(handle_auth_request(sess_baton, pool));
941  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
942  return SVN_NO_ERROR;
943}
944
945static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
946                                         svn_revnum_t *rev, apr_time_t tm,
947                                         apr_pool_t *pool)
948{
949  svn_ra_svn__session_baton_t *sess_baton = session->priv;
950  svn_ra_svn_conn_t *conn = sess_baton->conn;
951
952  SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
953  SVN_ERR(handle_auth_request(sess_baton, pool));
954  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
955  return SVN_NO_ERROR;
956}
957
958/* Forward declaration. */
959static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
960                                          svn_boolean_t *has,
961                                          const char *capability,
962                                          apr_pool_t *pool);
963
964static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
965                                           const char *name,
966                                           const svn_string_t *const *old_value_p,
967                                           const svn_string_t *value,
968                                           apr_pool_t *pool)
969{
970  svn_ra_svn__session_baton_t *sess_baton = session->priv;
971  svn_ra_svn_conn_t *conn = sess_baton->conn;
972  svn_boolean_t dont_care;
973  const svn_string_t *old_value;
974  svn_boolean_t has_atomic_revprops;
975
976  SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
977                                SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
978                                pool));
979
980  if (old_value_p)
981    {
982      /* How did you get past the same check in svn_ra_change_rev_prop2()? */
983      SVN_ERR_ASSERT(has_atomic_revprops);
984
985      dont_care = FALSE;
986      old_value = *old_value_p;
987    }
988  else
989    {
990      dont_care = TRUE;
991      old_value = NULL;
992    }
993
994  if (has_atomic_revprops)
995    SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
996                                                   value, dont_care,
997                                                   old_value));
998  else
999    SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
1000                                                  value));
1001
1002  SVN_ERR(handle_auth_request(sess_baton, pool));
1003  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1004  return SVN_NO_ERROR;
1005}
1006
1007static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
1008                                    apr_pool_t *pool)
1009{
1010  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1011  svn_ra_svn_conn_t *conn = sess_baton->conn;
1012
1013  *uuid = conn->uuid;
1014  return SVN_NO_ERROR;
1015}
1016
1017static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
1018                                          apr_pool_t *pool)
1019{
1020  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1021  svn_ra_svn_conn_t *conn = sess_baton->conn;
1022
1023  if (!conn->repos_root)
1024    return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
1025                            _("Server did not send repository root"));
1026  *url = conn->repos_root;
1027  return SVN_NO_ERROR;
1028}
1029
1030static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
1031                                        apr_hash_t **props, apr_pool_t *pool)
1032{
1033  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1034  svn_ra_svn_conn_t *conn = sess_baton->conn;
1035  apr_array_header_t *proplist;
1036
1037  SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
1038  SVN_ERR(handle_auth_request(sess_baton, pool));
1039  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
1040  SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1041  return SVN_NO_ERROR;
1042}
1043
1044static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1045                                    const char *name,
1046                                    svn_string_t **value, apr_pool_t *pool)
1047{
1048  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1049  svn_ra_svn_conn_t *conn = sess_baton->conn;
1050
1051  SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
1052  SVN_ERR(handle_auth_request(sess_baton, pool));
1053  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
1054  return SVN_NO_ERROR;
1055}
1056
1057static svn_error_t *ra_svn_end_commit(void *baton)
1058{
1059  ra_svn_commit_callback_baton_t *ccb = baton;
1060  svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
1061
1062  SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
1063  SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
1064                                 "r(?c)(?c)?(?c)",
1065                                 &(commit_info->revision),
1066                                 &(commit_info->date),
1067                                 &(commit_info->author),
1068                                 &(commit_info->post_commit_err)));
1069
1070  commit_info->repos_root = apr_pstrdup(ccb->pool,
1071                                        ccb->sess_baton->conn->repos_root);
1072
1073  if (ccb->callback)
1074    SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
1075
1076  return SVN_NO_ERROR;
1077}
1078
1079static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
1080                                  const svn_delta_editor_t **editor,
1081                                  void **edit_baton,
1082                                  apr_hash_t *revprop_table,
1083                                  svn_commit_callback2_t callback,
1084                                  void *callback_baton,
1085                                  apr_hash_t *lock_tokens,
1086                                  svn_boolean_t keep_locks,
1087                                  apr_pool_t *pool)
1088{
1089  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1090  svn_ra_svn_conn_t *conn = sess_baton->conn;
1091  ra_svn_commit_callback_baton_t *ccb;
1092  apr_hash_index_t *hi;
1093  apr_pool_t *iterpool;
1094  const svn_string_t *log_msg = svn_hash_gets(revprop_table,
1095                                              SVN_PROP_REVISION_LOG);
1096
1097  if (log_msg == NULL &&
1098      ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1099    {
1100      return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
1101                               _("ra_svn does not support not specifying "
1102                                 "a log message with pre-1.5 servers; "
1103                                 "consider passing an empty one, or upgrading "
1104                                 "the server"));
1105    }
1106  else if (log_msg == NULL)
1107    /* 1.5+ server.  Set LOG_MSG to something, since the 'logmsg' argument
1108       to the 'commit' protocol command is non-optional; on the server side,
1109       only REVPROP_TABLE will be used, and LOG_MSG will be ignored.  The
1110       "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
1111       will have a NULL log message (not just "", really NULL).
1112
1113       svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
1114       present; this was elevated to a protocol promise in r1498550 (and
1115       later documented in this comment) in order to fix the segmentation
1116       fault bug described in the log message of r1498550.*/
1117    log_msg = svn_string_create("", pool);
1118
1119  /* If we're sending revprops other than svn:log, make sure the server won't
1120     silently ignore them. */
1121  if (apr_hash_count(revprop_table) > 1 &&
1122      ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1123    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1124                            _("Server doesn't support setting arbitrary "
1125                              "revision properties during commit"));
1126
1127  /* If the server supports ephemeral txnprops, add the one that
1128     reports the client's version level string. */
1129  if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1130      svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1131    {
1132      svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1133                    svn_string_create(SVN_VER_NUMBER, pool));
1134      svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1135                    svn_string_create(sess_baton->useragent, pool));
1136    }
1137
1138  /* Tell the server we're starting the commit.
1139     Send log message here for backwards compatibility with servers
1140     before 1.5. */
1141  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1142                                  log_msg->data));
1143  if (lock_tokens)
1144    {
1145      iterpool = svn_pool_create(pool);
1146      for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1147        {
1148          const void *key;
1149          void *val;
1150          const char *path, *token;
1151
1152          svn_pool_clear(iterpool);
1153          apr_hash_this(hi, &key, NULL, &val);
1154          path = key;
1155          token = val;
1156          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1157        }
1158      svn_pool_destroy(iterpool);
1159    }
1160  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1161  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1162  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1163  SVN_ERR(handle_auth_request(sess_baton, pool));
1164  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1165
1166  /* Remember a few arguments for when the commit is over. */
1167  ccb = apr_palloc(pool, sizeof(*ccb));
1168  ccb->sess_baton = sess_baton;
1169  ccb->pool = pool;
1170  ccb->new_rev = NULL;
1171  ccb->callback = callback;
1172  ccb->callback_baton = callback_baton;
1173
1174  /* Fetch an editor for the caller to drive.  The editor will call
1175   * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1176   * in the new_rev, committed_date, and committed_author values. */
1177  svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1178                        ra_svn_end_commit, ccb);
1179  return SVN_NO_ERROR;
1180}
1181
1182/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1183   const char * repos relative paths and properties for those paths, storing
1184   the result as an array of svn_prop_inherited_item_t *items. */
1185static svn_error_t *
1186parse_iproplist(apr_array_header_t **inherited_props,
1187                const apr_array_header_t *iproplist,
1188                svn_ra_session_t *session,
1189                apr_pool_t *result_pool,
1190                apr_pool_t *scratch_pool)
1191
1192{
1193  int i;
1194  apr_pool_t *iterpool;
1195
1196  if (iproplist == NULL)
1197    {
1198      /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1199         capability we shouldn't be asking for inherited props, but if we
1200         did and the server sent back nothing then we'll want to handle
1201         that. */
1202      *inherited_props = NULL;
1203      return SVN_NO_ERROR;
1204    }
1205
1206  *inherited_props = apr_array_make(
1207    result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1208
1209  iterpool = svn_pool_create(scratch_pool);
1210
1211  for (i = 0; i < iproplist->nelts; i++)
1212    {
1213      apr_array_header_t *iprop_list;
1214      char *parent_rel_path;
1215      apr_hash_t *iprops;
1216      apr_hash_index_t *hi;
1217      svn_prop_inherited_item_t *new_iprop =
1218        apr_palloc(result_pool, sizeof(*new_iprop));
1219      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1220                                              svn_ra_svn_item_t);
1221      if (elt->kind != SVN_RA_SVN_LIST)
1222        return svn_error_create(
1223          SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1224          _("Inherited proplist element not a list"));
1225
1226      svn_pool_clear(iterpool);
1227
1228      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1229                                      &parent_rel_path, &iprop_list));
1230      SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1231      new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path);
1232      new_iprop->prop_hash = svn_hash__make(result_pool);
1233      for (hi = apr_hash_first(iterpool, iprops);
1234           hi;
1235           hi = apr_hash_next(hi))
1236        {
1237          const char *name = apr_hash_this_key(hi);
1238          svn_string_t *value = apr_hash_this_val(hi);
1239          svn_hash_sets(new_iprop->prop_hash,
1240                        apr_pstrdup(result_pool, name),
1241                        svn_string_dup(value, result_pool));
1242        }
1243      APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1244        new_iprop;
1245    }
1246  svn_pool_destroy(iterpool);
1247  return SVN_NO_ERROR;
1248}
1249
1250static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1251                                    svn_revnum_t rev, svn_stream_t *stream,
1252                                    svn_revnum_t *fetched_rev,
1253                                    apr_hash_t **props,
1254                                    apr_pool_t *pool)
1255{
1256  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1257  svn_ra_svn_conn_t *conn = sess_baton->conn;
1258  apr_array_header_t *proplist;
1259  const char *expected_digest;
1260  svn_checksum_t *expected_checksum = NULL;
1261  svn_checksum_ctx_t *checksum_ctx;
1262  apr_pool_t *iterpool;
1263
1264  SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1265                                         (props != NULL), (stream != NULL)));
1266  SVN_ERR(handle_auth_request(sess_baton, pool));
1267  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1268                                        &expected_digest,
1269                                        &rev, &proplist));
1270
1271  if (fetched_rev)
1272    *fetched_rev = rev;
1273  if (props)
1274    SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1275
1276  /* We're done if the contents weren't wanted. */
1277  if (!stream)
1278    return SVN_NO_ERROR;
1279
1280  if (expected_digest)
1281    {
1282      SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1283                                     expected_digest, pool));
1284      checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1285    }
1286
1287  /* Read the file's contents. */
1288  iterpool = svn_pool_create(pool);
1289  while (1)
1290    {
1291      svn_ra_svn_item_t *item;
1292
1293      svn_pool_clear(iterpool);
1294      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1295      if (item->kind != SVN_RA_SVN_STRING)
1296        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1297                                _("Non-string as part of file contents"));
1298      if (item->u.string->len == 0)
1299        break;
1300
1301      if (expected_checksum)
1302        SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1303                                    item->u.string->len));
1304
1305      SVN_ERR(svn_stream_write(stream, item->u.string->data,
1306                               &item->u.string->len));
1307    }
1308  svn_pool_destroy(iterpool);
1309
1310  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1311
1312  if (expected_checksum)
1313    {
1314      svn_checksum_t *checksum;
1315
1316      SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1317      if (!svn_checksum_match(checksum, expected_checksum))
1318        return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1319                                         _("Checksum mismatch for '%s'"),
1320                                         path);
1321    }
1322
1323  return SVN_NO_ERROR;
1324}
1325
1326static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1327                                   apr_hash_t **dirents,
1328                                   svn_revnum_t *fetched_rev,
1329                                   apr_hash_t **props,
1330                                   const char *path,
1331                                   svn_revnum_t rev,
1332                                   apr_uint32_t dirent_fields,
1333                                   apr_pool_t *pool)
1334{
1335  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1336  svn_ra_svn_conn_t *conn = sess_baton->conn;
1337  apr_array_header_t *proplist, *dirlist;
1338  int i;
1339
1340  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1341                                  rev, (props != NULL), (dirents != NULL)));
1342  if (dirent_fields & SVN_DIRENT_KIND)
1343    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1344  if (dirent_fields & SVN_DIRENT_SIZE)
1345    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1346  if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1347    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1348  if (dirent_fields & SVN_DIRENT_CREATED_REV)
1349    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1350  if (dirent_fields & SVN_DIRENT_TIME)
1351    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1352  if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1353    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1354
1355  /* Always send the, nominally optional, want-iprops as "false" to
1356     workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1357     to see "true" if it is omitted. */
1358  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1359
1360  SVN_ERR(handle_auth_request(sess_baton, pool));
1361  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1362                                        &dirlist));
1363
1364  if (fetched_rev)
1365    *fetched_rev = rev;
1366  if (props)
1367    SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1368
1369  /* We're done if dirents aren't wanted. */
1370  if (!dirents)
1371    return SVN_NO_ERROR;
1372
1373  /* Interpret the directory list. */
1374  *dirents = svn_hash__make(pool);
1375  for (i = 0; i < dirlist->nelts; i++)
1376    {
1377      const char *name, *kind, *cdate, *cauthor;
1378      svn_boolean_t has_props;
1379      svn_dirent_t *dirent;
1380      apr_uint64_t size;
1381      svn_revnum_t crev;
1382      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1383
1384      if (elt->kind != SVN_RA_SVN_LIST)
1385        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1386                                _("Dirlist element not a list"));
1387      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1388                                      &name, &kind, &size, &has_props,
1389                                      &crev, &cdate, &cauthor));
1390
1391      /* Nothing to sanitize here.  Any multi-segment path is simply
1392         illegal in the hash returned by svn_ra_get_dir2. */
1393      if (strchr(name, '/'))
1394        return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1395                                 _("Invalid directory entry name '%s'"),
1396                                 name);
1397
1398      dirent = svn_dirent_create(pool);
1399      dirent->kind = svn_node_kind_from_word(kind);
1400      dirent->size = size;/* FIXME: svn_filesize_t */
1401      dirent->has_props = has_props;
1402      dirent->created_rev = crev;
1403      /* NOTE: the tuple's format string says CDATE may be NULL. But this
1404         function does not allow that. The server has always sent us some
1405         random date, however, so this just happens to work. But let's
1406         be wary of servers that are (improperly) fixed to send NULL.
1407
1408         Note: they should NOT be "fixed" to send NULL, as that would break
1409         any older clients which received that NULL. But we may as well
1410         be defensive against a malicous server.  */
1411      if (cdate == NULL)
1412        dirent->time = 0;
1413      else
1414        SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1415      dirent->last_author = cauthor;
1416      svn_hash_sets(*dirents, name, dirent);
1417    }
1418
1419  return SVN_NO_ERROR;
1420}
1421
1422/* Converts a apr_uint64_t with values TRUE, FALSE or
1423   SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1424   to a svn_tristate_t */
1425static svn_tristate_t
1426optbool_to_tristate(apr_uint64_t v)
1427{
1428  if (v == TRUE)  /* not just non-zero but exactly equal to 'TRUE' */
1429    return svn_tristate_true;
1430  if (v == FALSE)
1431    return svn_tristate_false;
1432
1433  return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1434}
1435
1436/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1437   server, which defaults to youngest. */
1438static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1439                                         svn_mergeinfo_catalog_t *catalog,
1440                                         const apr_array_header_t *paths,
1441                                         svn_revnum_t revision,
1442                                         svn_mergeinfo_inheritance_t inherit,
1443                                         svn_boolean_t include_descendants,
1444                                         apr_pool_t *pool)
1445{
1446  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1447  svn_ra_svn_conn_t *conn = sess_baton->conn;
1448  int i;
1449  apr_array_header_t *mergeinfo_tuple;
1450  svn_ra_svn_item_t *elt;
1451  const char *path;
1452
1453  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1454  for (i = 0; i < paths->nelts; i++)
1455    {
1456      path = APR_ARRAY_IDX(paths, i, const char *);
1457      SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1458    }
1459  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1460                                  svn_inheritance_to_word(inherit),
1461                                  include_descendants));
1462
1463  SVN_ERR(handle_auth_request(sess_baton, pool));
1464  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1465
1466  *catalog = NULL;
1467  if (mergeinfo_tuple->nelts > 0)
1468    {
1469      *catalog = svn_hash__make(pool);
1470      for (i = 0; i < mergeinfo_tuple->nelts; i++)
1471        {
1472          svn_mergeinfo_t for_path;
1473          const char *to_parse;
1474
1475          elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1476          if (elt->kind != SVN_RA_SVN_LIST)
1477            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1478                                    _("Mergeinfo element is not a list"));
1479          SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1480                                          &path, &to_parse));
1481          SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1482          /* Correct for naughty servers that send "relative" paths
1483             with leading slashes! */
1484          svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1485        }
1486    }
1487
1488  return SVN_NO_ERROR;
1489}
1490
1491static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1492                                  const svn_ra_reporter3_t **reporter,
1493                                  void **report_baton, svn_revnum_t rev,
1494                                  const char *target, svn_depth_t depth,
1495                                  svn_boolean_t send_copyfrom_args,
1496                                  svn_boolean_t ignore_ancestry,
1497                                  const svn_delta_editor_t *update_editor,
1498                                  void *update_baton,
1499                                  apr_pool_t *pool,
1500                                  apr_pool_t *scratch_pool)
1501{
1502  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1503  svn_ra_svn_conn_t *conn = sess_baton->conn;
1504  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1505
1506  /* Tell the server we want to start an update. */
1507  SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1508                                       depth, send_copyfrom_args,
1509                                       ignore_ancestry));
1510  SVN_ERR(handle_auth_request(sess_baton, pool));
1511
1512  /* Fetch a reporter for the caller to drive.  The reporter will drive
1513   * update_editor upon finish_report(). */
1514  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1515                              target, depth, reporter, report_baton));
1516  return SVN_NO_ERROR;
1517}
1518
1519static svn_error_t *
1520ra_svn_switch(svn_ra_session_t *session,
1521              const svn_ra_reporter3_t **reporter,
1522              void **report_baton, svn_revnum_t rev,
1523              const char *target, svn_depth_t depth,
1524              const char *switch_url,
1525              svn_boolean_t send_copyfrom_args,
1526              svn_boolean_t ignore_ancestry,
1527              const svn_delta_editor_t *update_editor,
1528              void *update_baton,
1529              apr_pool_t *result_pool,
1530              apr_pool_t *scratch_pool)
1531{
1532  apr_pool_t *pool = result_pool;
1533  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1534  svn_ra_svn_conn_t *conn = sess_baton->conn;
1535  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1536
1537  /* Tell the server we want to start a switch. */
1538  SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1539                                       switch_url, depth,
1540                                       send_copyfrom_args, ignore_ancestry));
1541  SVN_ERR(handle_auth_request(sess_baton, pool));
1542
1543  /* Fetch a reporter for the caller to drive.  The reporter will drive
1544   * update_editor upon finish_report(). */
1545  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1546                              target, depth, reporter, report_baton));
1547  return SVN_NO_ERROR;
1548}
1549
1550static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1551                                  const svn_ra_reporter3_t **reporter,
1552                                  void **report_baton,
1553                                  const char *target, svn_revnum_t rev,
1554                                  svn_depth_t depth,
1555                                  const svn_delta_editor_t *status_editor,
1556                                  void *status_baton, apr_pool_t *pool)
1557{
1558  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1559  svn_ra_svn_conn_t *conn = sess_baton->conn;
1560  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1561
1562  /* Tell the server we want to start a status operation. */
1563  SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1564                                       depth));
1565  SVN_ERR(handle_auth_request(sess_baton, pool));
1566
1567  /* Fetch a reporter for the caller to drive.  The reporter will drive
1568   * status_editor upon finish_report(). */
1569  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1570                              target, depth, reporter, report_baton));
1571  return SVN_NO_ERROR;
1572}
1573
1574static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1575                                const svn_ra_reporter3_t **reporter,
1576                                void **report_baton,
1577                                svn_revnum_t rev, const char *target,
1578                                svn_depth_t depth,
1579                                svn_boolean_t ignore_ancestry,
1580                                svn_boolean_t text_deltas,
1581                                const char *versus_url,
1582                                const svn_delta_editor_t *diff_editor,
1583                                void *diff_baton, apr_pool_t *pool)
1584{
1585  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1586  svn_ra_svn_conn_t *conn = sess_baton->conn;
1587  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1588
1589  /* Tell the server we want to start a diff. */
1590  SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1591                                     ignore_ancestry, versus_url,
1592                                     text_deltas, depth));
1593  SVN_ERR(handle_auth_request(sess_baton, pool));
1594
1595  /* Fetch a reporter for the caller to drive.  The reporter will drive
1596   * diff_editor upon finish_report(). */
1597  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1598                              target, depth, reporter, report_baton));
1599  return SVN_NO_ERROR;
1600}
1601
1602
1603static svn_error_t *
1604perform_ra_svn_log(svn_error_t **outer_error,
1605                   svn_ra_session_t *session,
1606                   const apr_array_header_t *paths,
1607                   svn_revnum_t start, svn_revnum_t end,
1608                   int limit,
1609                   svn_boolean_t discover_changed_paths,
1610                   svn_boolean_t strict_node_history,
1611                   svn_boolean_t include_merged_revisions,
1612                   const apr_array_header_t *revprops,
1613                   svn_log_entry_receiver_t receiver,
1614                   void *receiver_baton,
1615                   apr_pool_t *pool)
1616{
1617  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1618  svn_ra_svn_conn_t *conn = sess_baton->conn;
1619  apr_pool_t *iterpool;
1620  int i;
1621  int nest_level = 0;
1622  const char *path;
1623  char *name;
1624  svn_boolean_t want_custom_revprops;
1625  svn_boolean_t want_author = FALSE;
1626  svn_boolean_t want_message = FALSE;
1627  svn_boolean_t want_date = FALSE;
1628  int nreceived = 0;
1629
1630  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1631  if (paths)
1632    {
1633      for (i = 0; i < paths->nelts; i++)
1634        {
1635          path = APR_ARRAY_IDX(paths, i, const char *);
1636          SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1637        }
1638    }
1639  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1640                                  discover_changed_paths, strict_node_history,
1641                                  (apr_uint64_t) limit,
1642                                  include_merged_revisions));
1643  if (revprops)
1644    {
1645      want_custom_revprops = FALSE;
1646      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1647      for (i = 0; i < revprops->nelts; i++)
1648        {
1649          name = APR_ARRAY_IDX(revprops, i, char *);
1650          SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1651
1652          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1653            want_author = TRUE;
1654          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1655            want_date = TRUE;
1656          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1657            want_message = TRUE;
1658          else
1659            want_custom_revprops = TRUE;
1660        }
1661      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1662    }
1663  else
1664    {
1665      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1666
1667      want_author = TRUE;
1668      want_date = TRUE;
1669      want_message = TRUE;
1670      want_custom_revprops = TRUE;
1671    }
1672
1673  SVN_ERR(handle_auth_request(sess_baton, pool));
1674
1675  /* Read the log messages. */
1676  iterpool = svn_pool_create(pool);
1677  while (1)
1678    {
1679      apr_uint64_t has_children_param, invalid_revnum_param;
1680      apr_uint64_t has_subtractive_merge_param;
1681      svn_string_t *author, *date, *message;
1682      apr_array_header_t *cplist, *rplist;
1683      svn_log_entry_t *log_entry;
1684      svn_boolean_t has_children;
1685      svn_boolean_t subtractive_merge = FALSE;
1686      apr_uint64_t revprop_count;
1687      svn_ra_svn_item_t *item;
1688      apr_hash_t *cphash;
1689      svn_revnum_t rev;
1690
1691      svn_pool_clear(iterpool);
1692      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1693      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1694        break;
1695      if (item->kind != SVN_RA_SVN_LIST)
1696        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1697                                _("Log entry not a list"));
1698      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1699                                      "lr(?s)(?s)(?s)?BBnl?B",
1700                                      &cplist, &rev, &author, &date,
1701                                      &message, &has_children_param,
1702                                      &invalid_revnum_param,
1703                                      &revprop_count, &rplist,
1704                                      &has_subtractive_merge_param));
1705      if (want_custom_revprops && rplist == NULL)
1706        {
1707          /* Caller asked for custom revprops, but server is too old. */
1708          return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1709                                  _("Server does not support custom revprops"
1710                                    " via log"));
1711        }
1712
1713      if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1714        has_children = FALSE;
1715      else
1716        has_children = (svn_boolean_t) has_children_param;
1717
1718      if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1719        subtractive_merge = FALSE;
1720      else
1721        subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1722
1723      /* Because the svn protocol won't let us send an invalid revnum, we have
1724         to recover that fact using the extra parameter. */
1725      if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1726            && invalid_revnum_param)
1727        rev = SVN_INVALID_REVNUM;
1728
1729      if (cplist->nelts > 0)
1730        {
1731          /* Interpret the changed-paths list. */
1732          cphash = svn_hash__make(iterpool);
1733          for (i = 0; i < cplist->nelts; i++)
1734            {
1735              svn_log_changed_path2_t *change;
1736              svn_string_t *cpath;
1737              const char *copy_path, *action, *kind_str;
1738              apr_uint64_t text_mods, prop_mods;
1739              svn_revnum_t copy_rev;
1740              svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1741                                                      svn_ra_svn_item_t);
1742
1743              if (elt->kind != SVN_RA_SVN_LIST)
1744                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1745                                        _("Changed-path entry not a list"));
1746              SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list,
1747                                              &cpath, &action, &copy_path,
1748                                              &copy_rev, &kind_str,
1749                                              &text_mods, &prop_mods));
1750
1751              if (!svn_fspath__is_canonical(cpath->data))
1752                {
1753                  cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1754                  cpath->len = strlen(cpath->data);
1755                }
1756              if (copy_path && !svn_fspath__is_canonical(copy_path))
1757                copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1758
1759              change = svn_log_changed_path2_create(iterpool);
1760              change->action = *action;
1761              change->copyfrom_path = copy_path;
1762              change->copyfrom_rev = copy_rev;
1763              change->node_kind = svn_node_kind_from_word(kind_str);
1764              change->text_modified = optbool_to_tristate(text_mods);
1765              change->props_modified = optbool_to_tristate(prop_mods);
1766              apr_hash_set(cphash, cpath->data, cpath->len, change);
1767            }
1768        }
1769      else
1770        cphash = NULL;
1771
1772      /* Invoke RECEIVER
1773          - Except if the server sends more than a >= 1 limit top level items
1774          - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
1775            in an earlier invocation. */
1776      if (! (limit && (nest_level == 0) && (++nreceived > limit))
1777          && ! *outer_error)
1778        {
1779          svn_error_t *err;
1780          log_entry = svn_log_entry_create(iterpool);
1781
1782          log_entry->changed_paths = cphash;
1783          log_entry->changed_paths2 = cphash;
1784          log_entry->revision = rev;
1785          log_entry->has_children = has_children;
1786          log_entry->subtractive_merge = subtractive_merge;
1787          if (rplist)
1788            SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1789                                               &log_entry->revprops));
1790          if (log_entry->revprops == NULL)
1791            log_entry->revprops = svn_hash__make(iterpool);
1792
1793          if (author && want_author)
1794            svn_hash_sets(log_entry->revprops,
1795                          SVN_PROP_REVISION_AUTHOR, author);
1796          if (date && want_date)
1797            svn_hash_sets(log_entry->revprops,
1798                          SVN_PROP_REVISION_DATE, date);
1799          if (message && want_message)
1800            svn_hash_sets(log_entry->revprops,
1801                          SVN_PROP_REVISION_LOG, message);
1802
1803          err = receiver(receiver_baton, log_entry, iterpool);
1804          if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1805            {
1806              *outer_error = svn_error_trace(
1807                                svn_error_compose_create(*outer_error, err));
1808            }
1809          else
1810            SVN_ERR(err);
1811
1812          if (log_entry->has_children)
1813            {
1814              nest_level++;
1815            }
1816          if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1817            {
1818              SVN_ERR_ASSERT(nest_level);
1819              nest_level--;
1820            }
1821        }
1822    }
1823  svn_pool_destroy(iterpool);
1824
1825  /* Read the response. */
1826  return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1827}
1828
1829static svn_error_t *
1830ra_svn_log(svn_ra_session_t *session,
1831           const apr_array_header_t *paths,
1832           svn_revnum_t start, svn_revnum_t end,
1833           int limit,
1834           svn_boolean_t discover_changed_paths,
1835           svn_boolean_t strict_node_history,
1836           svn_boolean_t include_merged_revisions,
1837           const apr_array_header_t *revprops,
1838           svn_log_entry_receiver_t receiver,
1839           void *receiver_baton, apr_pool_t *pool)
1840{
1841  svn_error_t *outer_error = NULL;
1842  svn_error_t *err;
1843
1844  err = svn_error_trace(perform_ra_svn_log(&outer_error,
1845                                           session, paths,
1846                                           start, end,
1847                                           limit,
1848                                           discover_changed_paths,
1849                                           strict_node_history,
1850                                           include_merged_revisions,
1851                                           revprops,
1852                                           receiver, receiver_baton,
1853                                           pool));
1854  return svn_error_trace(
1855            svn_error_compose_create(outer_error,
1856                                     err));
1857}
1858
1859
1860
1861static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1862                                      const char *path, svn_revnum_t rev,
1863                                      svn_node_kind_t *kind, apr_pool_t *pool)
1864{
1865  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1866  svn_ra_svn_conn_t *conn = sess_baton->conn;
1867  const char *kind_word;
1868
1869  SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1870  SVN_ERR(handle_auth_request(sess_baton, pool));
1871  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1872  *kind = svn_node_kind_from_word(kind_word);
1873  return SVN_NO_ERROR;
1874}
1875
1876
1877/* If ERR is a command not supported error, wrap it in a
1878   SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG.  Else, return err. */
1879static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1880                                           const char *msg)
1881{
1882  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1883    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1884                            _(msg));
1885  return err;
1886}
1887
1888
1889static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1890                                const char *path, svn_revnum_t rev,
1891                                svn_dirent_t **dirent, apr_pool_t *pool)
1892{
1893  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1894  svn_ra_svn_conn_t *conn = sess_baton->conn;
1895  apr_array_header_t *list = NULL;
1896  svn_dirent_t *the_dirent;
1897
1898  SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1899  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1900                                 N_("'stat' not implemented")));
1901  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1902
1903  if (! list)
1904    {
1905      *dirent = NULL;
1906    }
1907  else
1908    {
1909      const char *kind, *cdate, *cauthor;
1910      svn_boolean_t has_props;
1911      svn_revnum_t crev;
1912      apr_uint64_t size;
1913
1914      SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1915                                      &kind, &size, &has_props,
1916                                      &crev, &cdate, &cauthor));
1917
1918      the_dirent = svn_dirent_create(pool);
1919      the_dirent->kind = svn_node_kind_from_word(kind);
1920      the_dirent->size = size;/* FIXME: svn_filesize_t */
1921      the_dirent->has_props = has_props;
1922      the_dirent->created_rev = crev;
1923      SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1924      the_dirent->last_author = cauthor;
1925
1926      *dirent = the_dirent;
1927    }
1928
1929  return SVN_NO_ERROR;
1930}
1931
1932
1933static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1934                                         apr_hash_t **locations,
1935                                         const char *path,
1936                                         svn_revnum_t peg_revision,
1937                                         const apr_array_header_t *location_revisions,
1938                                         apr_pool_t *pool)
1939{
1940  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1941  svn_ra_svn_conn_t *conn = sess_baton->conn;
1942  svn_revnum_t revision;
1943  svn_boolean_t is_done;
1944  int i;
1945
1946  /* Transmit the parameters. */
1947  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1948                                  "get-locations", path, peg_revision));
1949  for (i = 0; i < location_revisions->nelts; i++)
1950    {
1951      revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1952      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1953    }
1954
1955  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1956
1957  /* Servers before 1.1 don't support this command. Check for this here. */
1958  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1959                                 N_("'get-locations' not implemented")));
1960
1961  /* Read the hash items. */
1962  is_done = FALSE;
1963  *locations = apr_hash_make(pool);
1964  while (!is_done)
1965    {
1966      svn_ra_svn_item_t *item;
1967      const char *ret_path;
1968
1969      SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1970      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1971        is_done = 1;
1972      else if (item->kind != SVN_RA_SVN_LIST)
1973        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1974                                _("Location entry not a list"));
1975      else
1976        {
1977          SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1978                                          &revision, &ret_path));
1979          ret_path = svn_fspath__canonicalize(ret_path, pool);
1980          apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1981                                               sizeof(revision)),
1982                       sizeof(revision), ret_path);
1983        }
1984    }
1985
1986  /* Read the response. This is so the server would have a chance to
1987   * report an error. */
1988  return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1989}
1990
1991static svn_error_t *
1992ra_svn_get_location_segments(svn_ra_session_t *session,
1993                             const char *path,
1994                             svn_revnum_t peg_revision,
1995                             svn_revnum_t start_rev,
1996                             svn_revnum_t end_rev,
1997                             svn_location_segment_receiver_t receiver,
1998                             void *receiver_baton,
1999                             apr_pool_t *pool)
2000{
2001  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2002  svn_ra_svn_conn_t *conn = sess_baton->conn;
2003  svn_boolean_t is_done;
2004  apr_pool_t *iterpool = svn_pool_create(pool);
2005
2006  /* Transmit the parameters. */
2007  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2008                                  "get-location-segments",
2009                                  path, peg_revision, start_rev, end_rev));
2010
2011  /* Servers before 1.5 don't support this command. Check for this here. */
2012  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2013                                 N_("'get-location-segments'"
2014                                    " not implemented")));
2015
2016  /* Parse the response. */
2017  is_done = FALSE;
2018  while (!is_done)
2019    {
2020      svn_revnum_t range_start, range_end;
2021      svn_ra_svn_item_t *item;
2022      const char *ret_path;
2023
2024      svn_pool_clear(iterpool);
2025      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2026      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2027        is_done = 1;
2028      else if (item->kind != SVN_RA_SVN_LIST)
2029        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2030                                _("Location segment entry not a list"));
2031      else
2032        {
2033          svn_location_segment_t *segment = apr_pcalloc(iterpool,
2034                                                        sizeof(*segment));
2035          SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
2036                                          &range_start, &range_end, &ret_path));
2037          if (! (SVN_IS_VALID_REVNUM(range_start)
2038                 && SVN_IS_VALID_REVNUM(range_end)))
2039            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2040                                    _("Expected valid revision range"));
2041          if (ret_path)
2042            ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2043          segment->path = ret_path;
2044          segment->range_start = range_start;
2045          segment->range_end = range_end;
2046          SVN_ERR(receiver(segment, receiver_baton, iterpool));
2047        }
2048    }
2049  svn_pool_destroy(iterpool);
2050
2051  /* Read the response. This is so the server would have a chance to
2052   * report an error. */
2053  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2054
2055  return SVN_NO_ERROR;
2056}
2057
2058static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2059                                         const char *path,
2060                                         svn_revnum_t start, svn_revnum_t end,
2061                                         svn_boolean_t include_merged_revisions,
2062                                         svn_file_rev_handler_t handler,
2063                                         void *handler_baton, apr_pool_t *pool)
2064{
2065  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2066  apr_pool_t *rev_pool, *chunk_pool;
2067  svn_boolean_t has_txdelta;
2068  svn_boolean_t had_revision = FALSE;
2069
2070  /* One sub-pool for each revision and one for each txdelta chunk.
2071     Note that the rev_pool must live during the following txdelta. */
2072  rev_pool = svn_pool_create(pool);
2073  chunk_pool = svn_pool_create(pool);
2074
2075  SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2076                                              path, start, end,
2077                                              include_merged_revisions));
2078
2079  /* Servers before 1.1 don't support this command.  Check for this here. */
2080  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2081                                 N_("'get-file-revs' not implemented")));
2082
2083  while (1)
2084    {
2085      apr_array_header_t *rev_proplist, *proplist;
2086      apr_uint64_t merged_rev_param;
2087      apr_array_header_t *props;
2088      svn_ra_svn_item_t *item;
2089      apr_hash_t *rev_props;
2090      svn_revnum_t rev;
2091      const char *p;
2092      svn_boolean_t merged_rev;
2093      svn_txdelta_window_handler_t d_handler;
2094      void *d_baton;
2095
2096      svn_pool_clear(rev_pool);
2097      svn_pool_clear(chunk_pool);
2098      SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2099      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2100        break;
2101      /* Either we've got a correct revision or we will error out below. */
2102      had_revision = TRUE;
2103      if (item->kind != SVN_RA_SVN_LIST)
2104        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2105                                _("Revision entry not a list"));
2106
2107      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
2108                                      "crll?B", &p, &rev, &rev_proplist,
2109                                      &proplist, &merged_rev_param));
2110      p = svn_fspath__canonicalize(p, rev_pool);
2111      SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2112      SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2113      if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2114        merged_rev = FALSE;
2115      else
2116        merged_rev = (svn_boolean_t) merged_rev_param;
2117
2118      /* Get the first delta chunk so we know if there is a delta. */
2119      SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2120      if (item->kind != SVN_RA_SVN_STRING)
2121        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2122                                _("Text delta chunk not a string"));
2123      has_txdelta = item->u.string->len > 0;
2124
2125      SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2126                      has_txdelta ? &d_handler : NULL, &d_baton,
2127                      props, rev_pool));
2128
2129      /* Process the text delta if any. */
2130      if (has_txdelta)
2131        {
2132          svn_stream_t *stream;
2133
2134          if (d_handler && d_handler != svn_delta_noop_window_handler)
2135            stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2136                                               rev_pool);
2137          else
2138            stream = NULL;
2139          while (item->u.string->len > 0)
2140            {
2141              apr_size_t size;
2142
2143              size = item->u.string->len;
2144              if (stream)
2145                SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2146              svn_pool_clear(chunk_pool);
2147
2148              SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2149                                            &item));
2150              if (item->kind != SVN_RA_SVN_STRING)
2151                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2152                                        _("Text delta chunk not a string"));
2153            }
2154          if (stream)
2155            SVN_ERR(svn_stream_close(stream));
2156        }
2157    }
2158
2159  SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2160
2161  /* Return error if we didn't get any revisions. */
2162  if (!had_revision)
2163    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2164                            _("The get-file-revs command didn't return "
2165                              "any revisions"));
2166
2167  svn_pool_destroy(chunk_pool);
2168  svn_pool_destroy(rev_pool);
2169
2170  return SVN_NO_ERROR;
2171}
2172
2173/* For each path in PATH_REVS, send a 'lock' command to the server.
2174   Used with 1.2.x series servers which support locking, but of only
2175   one path at a time.  ra_svn_lock(), which supports 'lock-many'
2176   is now the default.  See svn_ra_lock() docstring for interface details. */
2177static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2178                                       apr_hash_t *path_revs,
2179                                       const char *comment,
2180                                       svn_boolean_t steal_lock,
2181                                       svn_ra_lock_callback_t lock_func,
2182                                       void *lock_baton,
2183                                       apr_pool_t *pool)
2184{
2185  svn_ra_svn__session_baton_t *sess = session->priv;
2186  svn_ra_svn_conn_t* conn = sess->conn;
2187  apr_array_header_t *list;
2188  apr_hash_index_t *hi;
2189  apr_pool_t *iterpool = svn_pool_create(pool);
2190
2191  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2192    {
2193      svn_lock_t *lock;
2194      const void *key;
2195      const char *path;
2196      void *val;
2197      svn_revnum_t *revnum;
2198      svn_error_t *err, *callback_err = NULL;
2199
2200      svn_pool_clear(iterpool);
2201
2202      apr_hash_this(hi, &key, NULL, &val);
2203      path = key;
2204      revnum = val;
2205
2206      SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2207                                         steal_lock, *revnum));
2208
2209      /* Servers before 1.2 doesn't support locking.  Check this here. */
2210      SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2211                                     N_("Server doesn't support "
2212                                        "the lock command")));
2213
2214      err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2215
2216      if (!err)
2217        SVN_ERR(parse_lock(list, iterpool, &lock));
2218
2219      if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2220        return err;
2221
2222      if (lock_func)
2223        callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2224                                 err, iterpool);
2225
2226      svn_error_clear(err);
2227
2228      if (callback_err)
2229        return callback_err;
2230    }
2231
2232  svn_pool_destroy(iterpool);
2233
2234  return SVN_NO_ERROR;
2235}
2236
2237/* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2238   Used with 1.2.x series servers which support unlocking, but of only
2239   one path at a time.  ra_svn_unlock(), which supports 'unlock-many' is
2240   now the default.  See svn_ra_unlock() docstring for interface details. */
2241static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2242                                         apr_hash_t *path_tokens,
2243                                         svn_boolean_t break_lock,
2244                                         svn_ra_lock_callback_t lock_func,
2245                                         void *lock_baton,
2246                                         apr_pool_t *pool)
2247{
2248  svn_ra_svn__session_baton_t *sess = session->priv;
2249  svn_ra_svn_conn_t* conn = sess->conn;
2250  apr_hash_index_t *hi;
2251  apr_pool_t *iterpool = svn_pool_create(pool);
2252
2253  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2254    {
2255      const void *key;
2256      const char *path;
2257      void *val;
2258      const char *token;
2259      svn_error_t *err, *callback_err = NULL;
2260
2261      svn_pool_clear(iterpool);
2262
2263      apr_hash_this(hi, &key, NULL, &val);
2264      path = key;
2265      if (strcmp(val, "") != 0)
2266        token = val;
2267      else
2268        token = NULL;
2269
2270      SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2271                                           break_lock));
2272
2273      /* Servers before 1.2 don't support locking.  Check this here. */
2274      SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2275                                     N_("Server doesn't support the unlock "
2276                                        "command")));
2277
2278      err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2279
2280      if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2281        return err;
2282
2283      if (lock_func)
2284        callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2285
2286      svn_error_clear(err);
2287
2288      if (callback_err)
2289        return callback_err;
2290    }
2291
2292  svn_pool_destroy(iterpool);
2293
2294  return SVN_NO_ERROR;
2295}
2296
2297/* Tell the server to lock all paths in PATH_REVS.
2298   See svn_ra_lock() for interface details. */
2299static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2300                                apr_hash_t *path_revs,
2301                                const char *comment,
2302                                svn_boolean_t steal_lock,
2303                                svn_ra_lock_callback_t lock_func,
2304                                void *lock_baton,
2305                                apr_pool_t *pool)
2306{
2307  svn_ra_svn__session_baton_t *sess = session->priv;
2308  svn_ra_svn_conn_t *conn = sess->conn;
2309  apr_hash_index_t *hi;
2310  svn_error_t *err;
2311  apr_pool_t *iterpool = svn_pool_create(pool);
2312
2313  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2314                                  comment, steal_lock));
2315
2316  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2317    {
2318      const void *key;
2319      const char *path;
2320      void *val;
2321      svn_revnum_t *revnum;
2322
2323      svn_pool_clear(iterpool);
2324      apr_hash_this(hi, &key, NULL, &val);
2325      path = key;
2326      revnum = val;
2327
2328      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2329    }
2330
2331  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2332
2333  err = handle_auth_request(sess, pool);
2334
2335  /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2336   * to 'lock'. */
2337  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2338    {
2339      svn_error_clear(err);
2340      return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2341                                lock_func, lock_baton, pool);
2342    }
2343
2344  if (err)
2345    return err;
2346
2347  /* Loop over responses to get lock information. */
2348  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2349    {
2350      svn_ra_svn_item_t *elt;
2351      const void *key;
2352      const char *path;
2353      svn_error_t *callback_err;
2354      const char *status;
2355      svn_lock_t *lock;
2356      apr_array_header_t *list;
2357
2358      apr_hash_this(hi, &key, NULL, NULL);
2359      path = key;
2360
2361      svn_pool_clear(iterpool);
2362      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2363
2364      /* The server might have encountered some sort of fatal error in
2365         the middle of the request list.  If this happens, it will
2366         transmit "done" to end the lock-info early, and then the
2367         overall command response will talk about the fatal error. */
2368      if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2369        break;
2370
2371      if (elt->kind != SVN_RA_SVN_LIST)
2372        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2373                                _("Lock response not a list"));
2374
2375      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2376                                      &list));
2377
2378      if (strcmp(status, "failure") == 0)
2379        err = svn_ra_svn__handle_failure_status(list, iterpool);
2380      else if (strcmp(status, "success") == 0)
2381        {
2382          SVN_ERR(parse_lock(list, iterpool, &lock));
2383          err = NULL;
2384        }
2385      else
2386        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2387                                _("Unknown status for lock command"));
2388
2389      if (lock_func)
2390        callback_err = lock_func(lock_baton, path, TRUE,
2391                                 err ? NULL : lock,
2392                                 err, iterpool);
2393      else
2394        callback_err = SVN_NO_ERROR;
2395
2396      svn_error_clear(err);
2397
2398      if (callback_err)
2399        return callback_err;
2400    }
2401
2402  /* If we didn't break early above, and the whole hash was traversed,
2403     read the final "done" from the server. */
2404  if (!hi)
2405    {
2406      svn_ra_svn_item_t *elt;
2407
2408      SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2409      if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2410        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2411                                _("Didn't receive end marker for lock "
2412                                  "responses"));
2413    }
2414
2415  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2416
2417  svn_pool_destroy(iterpool);
2418
2419  return SVN_NO_ERROR;
2420}
2421
2422/* Tell the server to unlock all paths in PATH_TOKENS.
2423   See svn_ra_unlock() for interface details. */
2424static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2425                                  apr_hash_t *path_tokens,
2426                                  svn_boolean_t break_lock,
2427                                  svn_ra_lock_callback_t lock_func,
2428                                  void *lock_baton,
2429                                  apr_pool_t *pool)
2430{
2431  svn_ra_svn__session_baton_t *sess = session->priv;
2432  svn_ra_svn_conn_t *conn = sess->conn;
2433  apr_hash_index_t *hi;
2434  apr_pool_t *iterpool = svn_pool_create(pool);
2435  svn_error_t *err;
2436  const char *path;
2437
2438  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2439                                  break_lock));
2440
2441  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2442    {
2443      void *val;
2444      const void *key;
2445      const char *token;
2446
2447      svn_pool_clear(iterpool);
2448      apr_hash_this(hi, &key, NULL, &val);
2449      path = key;
2450
2451      if (strcmp(val, "") != 0)
2452        token = val;
2453      else
2454        token = NULL;
2455
2456      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2457    }
2458
2459  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2460
2461  err = handle_auth_request(sess, pool);
2462
2463  /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2464   * to 'unlock'.
2465   */
2466  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2467    {
2468      svn_error_clear(err);
2469      return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2470                                  lock_baton, pool);
2471    }
2472
2473  if (err)
2474    return err;
2475
2476  /* Loop over responses to unlock files. */
2477  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2478    {
2479      svn_ra_svn_item_t *elt;
2480      const void *key;
2481      svn_error_t *callback_err;
2482      const char *status;
2483      apr_array_header_t *list;
2484
2485      svn_pool_clear(iterpool);
2486
2487      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2488
2489      /* The server might have encountered some sort of fatal error in
2490         the middle of the request list.  If this happens, it will
2491         transmit "done" to end the lock-info early, and then the
2492         overall command response will talk about the fatal error. */
2493      if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2494        break;
2495
2496      apr_hash_this(hi, &key, NULL, NULL);
2497      path = key;
2498
2499      if (elt->kind != SVN_RA_SVN_LIST)
2500        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2501                                _("Unlock response not a list"));
2502
2503      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2504                                      &list));
2505
2506      if (strcmp(status, "failure") == 0)
2507        err = svn_ra_svn__handle_failure_status(list, iterpool);
2508      else if (strcmp(status, "success") == 0)
2509        {
2510          SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2511          err = SVN_NO_ERROR;
2512        }
2513      else
2514        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2515                                _("Unknown status for unlock command"));
2516
2517      if (lock_func)
2518        callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2519                                 iterpool);
2520      else
2521        callback_err = SVN_NO_ERROR;
2522
2523      svn_error_clear(err);
2524
2525      if (callback_err)
2526        return callback_err;
2527    }
2528
2529  /* If we didn't break early above, and the whole hash was traversed,
2530     read the final "done" from the server. */
2531  if (!hi)
2532    {
2533      svn_ra_svn_item_t *elt;
2534
2535      SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2536      if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2537        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2538                                _("Didn't receive end marker for unlock "
2539                                  "responses"));
2540    }
2541
2542  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2543
2544  svn_pool_destroy(iterpool);
2545
2546  return SVN_NO_ERROR;
2547}
2548
2549static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2550                                    svn_lock_t **lock,
2551                                    const char *path,
2552                                    apr_pool_t *pool)
2553{
2554  svn_ra_svn__session_baton_t *sess = session->priv;
2555  svn_ra_svn_conn_t* conn = sess->conn;
2556  apr_array_header_t *list;
2557
2558  SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2559
2560  /* Servers before 1.2 doesn't support locking.  Check this here. */
2561  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2562                                 N_("Server doesn't support the get-lock "
2563                                    "command")));
2564
2565  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2566  if (list)
2567    SVN_ERR(parse_lock(list, pool, lock));
2568  else
2569    *lock = NULL;
2570
2571  return SVN_NO_ERROR;
2572}
2573
2574/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2575   to prevent a dependency cycle. */
2576static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2577                                          const char **rel_path,
2578                                          const char *url,
2579                                          apr_pool_t *pool)
2580{
2581  const char *root_url;
2582
2583  SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2584  *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2585  if (! *rel_path)
2586    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2587                             _("'%s' isn't a child of repository root "
2588                               "URL '%s'"),
2589                             url, root_url);
2590  return SVN_NO_ERROR;
2591}
2592
2593static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2594                                     apr_hash_t **locks,
2595                                     const char *path,
2596                                     svn_depth_t depth,
2597                                     apr_pool_t *pool)
2598{
2599  svn_ra_svn__session_baton_t *sess = session->priv;
2600  svn_ra_svn_conn_t* conn = sess->conn;
2601  apr_array_header_t *list;
2602  const char *full_url, *abs_path;
2603  int i;
2604
2605  /* Figure out the repository abspath from PATH. */
2606  full_url = svn_path_url_add_component2(sess->url, path, pool);
2607  SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2608  abs_path = svn_fspath__canonicalize(abs_path, pool);
2609
2610  SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2611
2612  /* Servers before 1.2 doesn't support locking.  Check this here. */
2613  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2614                                 N_("Server doesn't support the get-lock "
2615                                    "command")));
2616
2617  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2618
2619  *locks = apr_hash_make(pool);
2620
2621  for (i = 0; i < list->nelts; ++i)
2622    {
2623      svn_lock_t *lock;
2624      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2625
2626      if (elt->kind != SVN_RA_SVN_LIST)
2627        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2628                                _("Lock element not a list"));
2629      SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2630
2631      /* Filter out unwanted paths.  Since Subversion only allows
2632         locks on files, we can treat depth=immediates the same as
2633         depth=files for filtering purposes.  Meaning, we'll keep
2634         this lock if:
2635
2636         a) its path is the very path we queried, or
2637         b) we've asked for a fully recursive answer, or
2638         c) we've asked for depth=files or depth=immediates, and this
2639            lock is on an immediate child of our query path.
2640      */
2641      if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2642        {
2643          svn_hash_sets(*locks, lock->path, lock);
2644        }
2645      else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2646        {
2647          const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2648          if (relpath && (svn_path_component_count(relpath) == 1))
2649            svn_hash_sets(*locks, lock->path, lock);
2650        }
2651    }
2652
2653  return SVN_NO_ERROR;
2654}
2655
2656
2657static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2658                                  svn_revnum_t revision,
2659                                  svn_revnum_t low_water_mark,
2660                                  svn_boolean_t send_deltas,
2661                                  const svn_delta_editor_t *editor,
2662                                  void *edit_baton,
2663                                  apr_pool_t *pool)
2664{
2665  svn_ra_svn__session_baton_t *sess = session->priv;
2666
2667  SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2668                                       low_water_mark, send_deltas));
2669
2670  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2671                                 N_("Server doesn't support the replay "
2672                                    "command")));
2673
2674  SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2675                                   NULL, TRUE));
2676
2677  return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2678}
2679
2680
2681static svn_error_t *
2682ra_svn_replay_range(svn_ra_session_t *session,
2683                    svn_revnum_t start_revision,
2684                    svn_revnum_t end_revision,
2685                    svn_revnum_t low_water_mark,
2686                    svn_boolean_t send_deltas,
2687                    svn_ra_replay_revstart_callback_t revstart_func,
2688                    svn_ra_replay_revfinish_callback_t revfinish_func,
2689                    void *replay_baton,
2690                    apr_pool_t *pool)
2691{
2692  svn_ra_svn__session_baton_t *sess = session->priv;
2693  apr_pool_t *iterpool;
2694  svn_revnum_t rev;
2695  svn_boolean_t drive_aborted = FALSE;
2696
2697  SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2698                                             start_revision, end_revision,
2699                                             low_water_mark, send_deltas));
2700
2701  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2702                                 N_("Server doesn't support the "
2703                                    "replay-range command")));
2704
2705  iterpool = svn_pool_create(pool);
2706  for (rev = start_revision; rev <= end_revision; rev++)
2707    {
2708      const svn_delta_editor_t *editor;
2709      void *edit_baton;
2710      apr_hash_t *rev_props;
2711      const char *word;
2712      apr_array_header_t *list;
2713
2714      svn_pool_clear(iterpool);
2715
2716      SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2717                                     "wl", &word, &list));
2718      if (strcmp(word, "revprops") != 0)
2719        return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2720                                 _("Expected 'revprops', found '%s'"),
2721                                 word);
2722
2723      SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2724
2725      SVN_ERR(revstart_func(rev, replay_baton,
2726                            &editor, &edit_baton,
2727                            rev_props,
2728                            iterpool));
2729      SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2730                                       editor, edit_baton,
2731                                       &drive_aborted, TRUE));
2732      /* If drive_editor2() aborted the commit, do NOT try to call
2733         revfinish_func and commit the transaction! */
2734      if (drive_aborted) {
2735        svn_pool_destroy(iterpool);
2736        return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2737                                _("Error while replaying commit"));
2738      }
2739      SVN_ERR(revfinish_func(rev, replay_baton,
2740                             editor, edit_baton,
2741                             rev_props,
2742                             iterpool));
2743    }
2744  svn_pool_destroy(iterpool);
2745
2746  return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2747}
2748
2749
2750static svn_error_t *
2751ra_svn_has_capability(svn_ra_session_t *session,
2752                      svn_boolean_t *has,
2753                      const char *capability,
2754                      apr_pool_t *pool)
2755{
2756  svn_ra_svn__session_baton_t *sess = session->priv;
2757  static const char* capabilities[][2] =
2758  {
2759      /* { ra capability string, svn:// wire capability string} */
2760      {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2761      {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2762      {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2763      {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2764      {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2765      {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2766      {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2767      {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2768                                          SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2769      {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2770                                       SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2771
2772      {NULL, NULL} /* End of list marker */
2773  };
2774  int i;
2775
2776  *has = FALSE;
2777
2778  for (i = 0; capabilities[i][0]; i++)
2779    {
2780      if (strcmp(capability, capabilities[i][0]) == 0)
2781        {
2782          *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2783          return SVN_NO_ERROR;
2784        }
2785    }
2786
2787  return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2788                           _("Don't know anything about capability '%s'"),
2789                           capability);
2790}
2791
2792static svn_error_t *
2793ra_svn_get_deleted_rev(svn_ra_session_t *session,
2794                       const char *path,
2795                       svn_revnum_t peg_revision,
2796                       svn_revnum_t end_revision,
2797                       svn_revnum_t *revision_deleted,
2798                       apr_pool_t *pool)
2799
2800{
2801  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2802  svn_ra_svn_conn_t *conn = sess_baton->conn;
2803
2804  /* Transmit the parameters. */
2805  SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2806                                               peg_revision, end_revision));
2807
2808  /* Servers before 1.6 don't support this command.  Check for this here. */
2809  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2810                                 N_("'get-deleted-rev' not implemented")));
2811
2812  return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
2813                                                       revision_deleted));
2814}
2815
2816static svn_error_t *
2817ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2818                                      svn_delta_shim_callbacks_t *callbacks)
2819{
2820  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2821  svn_ra_svn_conn_t *conn = sess_baton->conn;
2822
2823  conn->shim_callbacks = callbacks;
2824
2825  return SVN_NO_ERROR;
2826}
2827
2828static svn_error_t *
2829ra_svn_get_inherited_props(svn_ra_session_t *session,
2830                           apr_array_header_t **iprops,
2831                           const char *path,
2832                           svn_revnum_t revision,
2833                           apr_pool_t *result_pool,
2834                           apr_pool_t *scratch_pool)
2835{
2836  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2837  svn_ra_svn_conn_t *conn = sess_baton->conn;
2838  apr_array_header_t *iproplist;
2839  svn_boolean_t iprop_capable;
2840
2841  SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
2842                                SVN_RA_CAPABILITY_INHERITED_PROPS,
2843                                scratch_pool));
2844
2845  /* If we don't support native iprop handling, use the implementation
2846     in libsvn_ra */
2847  if (!iprop_capable)
2848    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
2849
2850  SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2851                                           path, revision));
2852  SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2853  SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2854  SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2855                          scratch_pool));
2856
2857  return SVN_NO_ERROR;
2858}
2859
2860static const svn_ra__vtable_t ra_svn_vtable = {
2861  svn_ra_svn_version,
2862  ra_svn_get_description,
2863  ra_svn_get_schemes,
2864  ra_svn_open,
2865  ra_svn_dup_session,
2866  ra_svn_reparent,
2867  ra_svn_get_session_url,
2868  ra_svn_get_latest_rev,
2869  ra_svn_get_dated_rev,
2870  ra_svn_change_rev_prop,
2871  ra_svn_rev_proplist,
2872  ra_svn_rev_prop,
2873  ra_svn_commit,
2874  ra_svn_get_file,
2875  ra_svn_get_dir,
2876  ra_svn_get_mergeinfo,
2877  ra_svn_update,
2878  ra_svn_switch,
2879  ra_svn_status,
2880  ra_svn_diff,
2881  ra_svn_log,
2882  ra_svn_check_path,
2883  ra_svn_stat,
2884  ra_svn_get_uuid,
2885  ra_svn_get_repos_root,
2886  ra_svn_get_locations,
2887  ra_svn_get_location_segments,
2888  ra_svn_get_file_revs,
2889  ra_svn_lock,
2890  ra_svn_unlock,
2891  ra_svn_get_lock,
2892  ra_svn_get_locks,
2893  ra_svn_replay,
2894  ra_svn_has_capability,
2895  ra_svn_replay_range,
2896  ra_svn_get_deleted_rev,
2897  ra_svn_register_editor_shim_callbacks,
2898  ra_svn_get_inherited_props
2899};
2900
2901svn_error_t *
2902svn_ra_svn__init(const svn_version_t *loader_version,
2903                 const svn_ra__vtable_t **vtable,
2904                 apr_pool_t *pool)
2905{
2906  static const svn_version_checklist_t checklist[] =
2907    {
2908      { "svn_subr",  svn_subr_version },
2909      { "svn_delta", svn_delta_version },
2910      { NULL, NULL }
2911    };
2912
2913  SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2914
2915  /* Simplified version check to make sure we can safely use the
2916     VTABLE parameter. The RA loader does a more exhaustive check. */
2917  if (loader_version->major != SVN_VER_MAJOR)
2918    {
2919      return svn_error_createf
2920        (SVN_ERR_VERSION_MISMATCH, NULL,
2921         _("Unsupported RA loader version (%d) for ra_svn"),
2922         loader_version->major);
2923    }
2924
2925  *vtable = &ra_svn_vtable;
2926
2927#ifdef SVN_HAVE_SASL
2928  SVN_ERR(svn_ra_svn__sasl_init());
2929#endif
2930
2931  return SVN_NO_ERROR;
2932}
2933
2934/* Compatibility wrapper for the 1.1 and before API. */
2935#define NAME "ra_svn"
2936#define DESCRIPTION RA_SVN_DESCRIPTION
2937#define VTBL ra_svn_vtable
2938#define INITFUNC svn_ra_svn__init
2939#define COMPAT_INITFUNC svn_ra_svn_init
2940#include "../libsvn_ra/wrapper_template.h"
2941