]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_svn/client.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_svn / client.c
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 \f
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
74 typedef 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
82 typedef 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. */
92 static 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
114 static 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. */
190 static 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. */
215 static 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
232 svn_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
239 static 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
254 static 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
268 static 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
277 static 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
292 static 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
305 static 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
314 static 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  */
327 static svn_error_t *
328 ra_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.) */
372 static 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. */
457 static 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.) */
482 static 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. */
563 static 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. */
579 struct 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. */
588 static 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 */
611 static 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
791 static const char *ra_svn_get_description(apr_pool_t *pool)
792 {
793   return _(RA_SVN_DESCRIPTION);
794 }
795
796 static const char * const *
797 ra_svn_get_schemes(apr_pool_t *pool)
798 {
799   static const char *schemes[] = { "svn", NULL };
800
801   return schemes;
802 }
803
804
805
806 static 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
862 static 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
878 static 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
925 static 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
933 static 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
945 static 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. */
959 static 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
964 static 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
1007 static 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
1017 static 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
1030 static 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
1044 static 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
1057 static 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
1079 static 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. */
1185 static svn_error_t *
1186 parse_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
1250 static 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
1326 static 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 */
1425 static svn_tristate_t
1426 optbool_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. */
1438 static 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
1491 static 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
1519 static svn_error_t *
1520 ra_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
1550 static 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
1574 static 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
1603 static svn_error_t *
1604 perform_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
1829 static svn_error_t *
1830 ra_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
1861 static 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. */
1879 static 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
1889 static 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
1933 static 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
1991 static svn_error_t *
1992 ra_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
2058 static 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. */
2177 static 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. */
2241 static 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. */
2299 static 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. */
2424 static 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
2549 static 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. */
2576 static 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
2593 static 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
2657 static 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
2681 static svn_error_t *
2682 ra_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
2750 static svn_error_t *
2751 ra_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
2792 static svn_error_t *
2793 ra_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
2816 static svn_error_t *
2817 ra_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
2828 static svn_error_t *
2829 ra_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
2860 static 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
2901 svn_error_t *
2902 svn_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"