]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_svn/client.c
Import libucl 0.8.0
[FreeBSD/FreeBSD.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   const char *repos_root_url;
1195   apr_pool_t *iterpool;
1196
1197   if (iproplist == NULL)
1198     {
1199       /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1200          capability we shouldn't be asking for inherited props, but if we
1201          did and the server sent back nothing then we'll want to handle
1202          that. */
1203       *inherited_props = NULL;
1204       return SVN_NO_ERROR;
1205     }
1206
1207   SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
1208
1209   *inherited_props = apr_array_make(
1210     result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1211
1212   iterpool = svn_pool_create(scratch_pool);
1213
1214   for (i = 0; i < iproplist->nelts; i++)
1215     {
1216       apr_array_header_t *iprop_list;
1217       char *parent_rel_path;
1218       apr_hash_t *iprops;
1219       apr_hash_index_t *hi;
1220       svn_prop_inherited_item_t *new_iprop =
1221         apr_palloc(result_pool, sizeof(*new_iprop));
1222       svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1223                                               svn_ra_svn_item_t);
1224       if (elt->kind != SVN_RA_SVN_LIST)
1225         return svn_error_create(
1226           SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1227           _("Inherited proplist element not a list"));
1228
1229       svn_pool_clear(iterpool);
1230
1231       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1232                                       &parent_rel_path, &iprop_list));
1233       SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1234       new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
1235                                                            parent_rel_path,
1236                                                            result_pool);
1237       new_iprop->prop_hash = svn_hash__make(result_pool);
1238       for (hi = apr_hash_first(iterpool, iprops);
1239            hi;
1240            hi = apr_hash_next(hi))
1241         {
1242           const char *name = apr_hash_this_key(hi);
1243           svn_string_t *value = apr_hash_this_val(hi);
1244           svn_hash_sets(new_iprop->prop_hash,
1245                         apr_pstrdup(result_pool, name),
1246                         svn_string_dup(value, result_pool));
1247         }
1248       APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1249         new_iprop;
1250     }
1251   svn_pool_destroy(iterpool);
1252   return SVN_NO_ERROR;
1253 }
1254
1255 static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1256                                     svn_revnum_t rev, svn_stream_t *stream,
1257                                     svn_revnum_t *fetched_rev,
1258                                     apr_hash_t **props,
1259                                     apr_pool_t *pool)
1260 {
1261   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1262   svn_ra_svn_conn_t *conn = sess_baton->conn;
1263   apr_array_header_t *proplist;
1264   const char *expected_digest;
1265   svn_checksum_t *expected_checksum = NULL;
1266   svn_checksum_ctx_t *checksum_ctx;
1267   apr_pool_t *iterpool;
1268
1269   SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1270                                          (props != NULL), (stream != NULL)));
1271   SVN_ERR(handle_auth_request(sess_baton, pool));
1272   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1273                                         &expected_digest,
1274                                         &rev, &proplist));
1275
1276   if (fetched_rev)
1277     *fetched_rev = rev;
1278   if (props)
1279     SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1280
1281   /* We're done if the contents weren't wanted. */
1282   if (!stream)
1283     return SVN_NO_ERROR;
1284
1285   if (expected_digest)
1286     {
1287       SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1288                                      expected_digest, pool));
1289       checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1290     }
1291
1292   /* Read the file's contents. */
1293   iterpool = svn_pool_create(pool);
1294   while (1)
1295     {
1296       svn_ra_svn_item_t *item;
1297
1298       svn_pool_clear(iterpool);
1299       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1300       if (item->kind != SVN_RA_SVN_STRING)
1301         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1302                                 _("Non-string as part of file contents"));
1303       if (item->u.string->len == 0)
1304         break;
1305
1306       if (expected_checksum)
1307         SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1308                                     item->u.string->len));
1309
1310       SVN_ERR(svn_stream_write(stream, item->u.string->data,
1311                                &item->u.string->len));
1312     }
1313   svn_pool_destroy(iterpool);
1314
1315   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1316
1317   if (expected_checksum)
1318     {
1319       svn_checksum_t *checksum;
1320
1321       SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1322       if (!svn_checksum_match(checksum, expected_checksum))
1323         return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1324                                          _("Checksum mismatch for '%s'"),
1325                                          path);
1326     }
1327
1328   return SVN_NO_ERROR;
1329 }
1330
1331 static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1332                                    apr_hash_t **dirents,
1333                                    svn_revnum_t *fetched_rev,
1334                                    apr_hash_t **props,
1335                                    const char *path,
1336                                    svn_revnum_t rev,
1337                                    apr_uint32_t dirent_fields,
1338                                    apr_pool_t *pool)
1339 {
1340   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1341   svn_ra_svn_conn_t *conn = sess_baton->conn;
1342   apr_array_header_t *proplist, *dirlist;
1343   int i;
1344
1345   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1346                                   rev, (props != NULL), (dirents != NULL)));
1347   if (dirent_fields & SVN_DIRENT_KIND)
1348     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1349   if (dirent_fields & SVN_DIRENT_SIZE)
1350     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1351   if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1352     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1353   if (dirent_fields & SVN_DIRENT_CREATED_REV)
1354     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1355   if (dirent_fields & SVN_DIRENT_TIME)
1356     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1357   if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1358     SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1359
1360   /* Always send the, nominally optional, want-iprops as "false" to
1361      workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1362      to see "true" if it is omitted. */
1363   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1364
1365   SVN_ERR(handle_auth_request(sess_baton, pool));
1366   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1367                                         &dirlist));
1368
1369   if (fetched_rev)
1370     *fetched_rev = rev;
1371   if (props)
1372     SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1373
1374   /* We're done if dirents aren't wanted. */
1375   if (!dirents)
1376     return SVN_NO_ERROR;
1377
1378   /* Interpret the directory list. */
1379   *dirents = svn_hash__make(pool);
1380   for (i = 0; i < dirlist->nelts; i++)
1381     {
1382       const char *name, *kind, *cdate, *cauthor;
1383       svn_boolean_t has_props;
1384       svn_dirent_t *dirent;
1385       apr_uint64_t size;
1386       svn_revnum_t crev;
1387       svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1388
1389       if (elt->kind != SVN_RA_SVN_LIST)
1390         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1391                                 _("Dirlist element not a list"));
1392       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1393                                       &name, &kind, &size, &has_props,
1394                                       &crev, &cdate, &cauthor));
1395
1396       /* Nothing to sanitize here.  Any multi-segment path is simply
1397          illegal in the hash returned by svn_ra_get_dir2. */
1398       if (strchr(name, '/'))
1399         return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1400                                  _("Invalid directory entry name '%s'"),
1401                                  name);
1402
1403       dirent = svn_dirent_create(pool);
1404       dirent->kind = svn_node_kind_from_word(kind);
1405       dirent->size = size;/* FIXME: svn_filesize_t */
1406       dirent->has_props = has_props;
1407       dirent->created_rev = crev;
1408       /* NOTE: the tuple's format string says CDATE may be NULL. But this
1409          function does not allow that. The server has always sent us some
1410          random date, however, so this just happens to work. But let's
1411          be wary of servers that are (improperly) fixed to send NULL.
1412
1413          Note: they should NOT be "fixed" to send NULL, as that would break
1414          any older clients which received that NULL. But we may as well
1415          be defensive against a malicous server.  */
1416       if (cdate == NULL)
1417         dirent->time = 0;
1418       else
1419         SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1420       dirent->last_author = cauthor;
1421       svn_hash_sets(*dirents, name, dirent);
1422     }
1423
1424   return SVN_NO_ERROR;
1425 }
1426
1427 /* Converts a apr_uint64_t with values TRUE, FALSE or
1428    SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1429    to a svn_tristate_t */
1430 static svn_tristate_t
1431 optbool_to_tristate(apr_uint64_t v)
1432 {
1433   if (v == TRUE)  /* not just non-zero but exactly equal to 'TRUE' */
1434     return svn_tristate_true;
1435   if (v == FALSE)
1436     return svn_tristate_false;
1437
1438   return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1439 }
1440
1441 /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1442    server, which defaults to youngest. */
1443 static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1444                                          svn_mergeinfo_catalog_t *catalog,
1445                                          const apr_array_header_t *paths,
1446                                          svn_revnum_t revision,
1447                                          svn_mergeinfo_inheritance_t inherit,
1448                                          svn_boolean_t include_descendants,
1449                                          apr_pool_t *pool)
1450 {
1451   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1452   svn_ra_svn_conn_t *conn = sess_baton->conn;
1453   int i;
1454   apr_array_header_t *mergeinfo_tuple;
1455   svn_ra_svn_item_t *elt;
1456   const char *path;
1457
1458   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1459   for (i = 0; i < paths->nelts; i++)
1460     {
1461       path = APR_ARRAY_IDX(paths, i, const char *);
1462       SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1463     }
1464   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1465                                   svn_inheritance_to_word(inherit),
1466                                   include_descendants));
1467
1468   SVN_ERR(handle_auth_request(sess_baton, pool));
1469   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1470
1471   *catalog = NULL;
1472   if (mergeinfo_tuple->nelts > 0)
1473     {
1474       *catalog = svn_hash__make(pool);
1475       for (i = 0; i < mergeinfo_tuple->nelts; i++)
1476         {
1477           svn_mergeinfo_t for_path;
1478           const char *to_parse;
1479
1480           elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1481           if (elt->kind != SVN_RA_SVN_LIST)
1482             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1483                                     _("Mergeinfo element is not a list"));
1484           SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1485                                           &path, &to_parse));
1486           SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1487           /* Correct for naughty servers that send "relative" paths
1488              with leading slashes! */
1489           svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1490         }
1491     }
1492
1493   return SVN_NO_ERROR;
1494 }
1495
1496 static svn_error_t *ra_svn_update(svn_ra_session_t *session,
1497                                   const svn_ra_reporter3_t **reporter,
1498                                   void **report_baton, svn_revnum_t rev,
1499                                   const char *target, svn_depth_t depth,
1500                                   svn_boolean_t send_copyfrom_args,
1501                                   svn_boolean_t ignore_ancestry,
1502                                   const svn_delta_editor_t *update_editor,
1503                                   void *update_baton,
1504                                   apr_pool_t *pool,
1505                                   apr_pool_t *scratch_pool)
1506 {
1507   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1508   svn_ra_svn_conn_t *conn = sess_baton->conn;
1509   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1510
1511   /* Tell the server we want to start an update. */
1512   SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1513                                        depth, send_copyfrom_args,
1514                                        ignore_ancestry));
1515   SVN_ERR(handle_auth_request(sess_baton, pool));
1516
1517   /* Fetch a reporter for the caller to drive.  The reporter will drive
1518    * update_editor upon finish_report(). */
1519   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1520                               target, depth, reporter, report_baton));
1521   return SVN_NO_ERROR;
1522 }
1523
1524 static svn_error_t *
1525 ra_svn_switch(svn_ra_session_t *session,
1526               const svn_ra_reporter3_t **reporter,
1527               void **report_baton, svn_revnum_t rev,
1528               const char *target, svn_depth_t depth,
1529               const char *switch_url,
1530               svn_boolean_t send_copyfrom_args,
1531               svn_boolean_t ignore_ancestry,
1532               const svn_delta_editor_t *update_editor,
1533               void *update_baton,
1534               apr_pool_t *result_pool,
1535               apr_pool_t *scratch_pool)
1536 {
1537   apr_pool_t *pool = result_pool;
1538   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1539   svn_ra_svn_conn_t *conn = sess_baton->conn;
1540   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1541
1542   /* Tell the server we want to start a switch. */
1543   SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1544                                        switch_url, depth,
1545                                        send_copyfrom_args, ignore_ancestry));
1546   SVN_ERR(handle_auth_request(sess_baton, pool));
1547
1548   /* Fetch a reporter for the caller to drive.  The reporter will drive
1549    * update_editor upon finish_report(). */
1550   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1551                               target, depth, reporter, report_baton));
1552   return SVN_NO_ERROR;
1553 }
1554
1555 static svn_error_t *ra_svn_status(svn_ra_session_t *session,
1556                                   const svn_ra_reporter3_t **reporter,
1557                                   void **report_baton,
1558                                   const char *target, svn_revnum_t rev,
1559                                   svn_depth_t depth,
1560                                   const svn_delta_editor_t *status_editor,
1561                                   void *status_baton, apr_pool_t *pool)
1562 {
1563   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1564   svn_ra_svn_conn_t *conn = sess_baton->conn;
1565   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1566
1567   /* Tell the server we want to start a status operation. */
1568   SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1569                                        depth));
1570   SVN_ERR(handle_auth_request(sess_baton, pool));
1571
1572   /* Fetch a reporter for the caller to drive.  The reporter will drive
1573    * status_editor upon finish_report(). */
1574   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1575                               target, depth, reporter, report_baton));
1576   return SVN_NO_ERROR;
1577 }
1578
1579 static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1580                                 const svn_ra_reporter3_t **reporter,
1581                                 void **report_baton,
1582                                 svn_revnum_t rev, const char *target,
1583                                 svn_depth_t depth,
1584                                 svn_boolean_t ignore_ancestry,
1585                                 svn_boolean_t text_deltas,
1586                                 const char *versus_url,
1587                                 const svn_delta_editor_t *diff_editor,
1588                                 void *diff_baton, apr_pool_t *pool)
1589 {
1590   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1591   svn_ra_svn_conn_t *conn = sess_baton->conn;
1592   svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1593
1594   /* Tell the server we want to start a diff. */
1595   SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1596                                      ignore_ancestry, versus_url,
1597                                      text_deltas, depth));
1598   SVN_ERR(handle_auth_request(sess_baton, pool));
1599
1600   /* Fetch a reporter for the caller to drive.  The reporter will drive
1601    * diff_editor upon finish_report(). */
1602   SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1603                               target, depth, reporter, report_baton));
1604   return SVN_NO_ERROR;
1605 }
1606
1607
1608 static svn_error_t *
1609 perform_ra_svn_log(svn_error_t **outer_error,
1610                    svn_ra_session_t *session,
1611                    const apr_array_header_t *paths,
1612                    svn_revnum_t start, svn_revnum_t end,
1613                    int limit,
1614                    svn_boolean_t discover_changed_paths,
1615                    svn_boolean_t strict_node_history,
1616                    svn_boolean_t include_merged_revisions,
1617                    const apr_array_header_t *revprops,
1618                    svn_log_entry_receiver_t receiver,
1619                    void *receiver_baton,
1620                    apr_pool_t *pool)
1621 {
1622   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1623   svn_ra_svn_conn_t *conn = sess_baton->conn;
1624   apr_pool_t *iterpool;
1625   int i;
1626   int nest_level = 0;
1627   const char *path;
1628   char *name;
1629   svn_boolean_t want_custom_revprops;
1630   svn_boolean_t want_author = FALSE;
1631   svn_boolean_t want_message = FALSE;
1632   svn_boolean_t want_date = FALSE;
1633   int nreceived = 0;
1634
1635   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1636   if (paths)
1637     {
1638       for (i = 0; i < paths->nelts; i++)
1639         {
1640           path = APR_ARRAY_IDX(paths, i, const char *);
1641           SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1642         }
1643     }
1644   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1645                                   discover_changed_paths, strict_node_history,
1646                                   (apr_uint64_t) limit,
1647                                   include_merged_revisions));
1648   if (revprops)
1649     {
1650       want_custom_revprops = FALSE;
1651       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1652       for (i = 0; i < revprops->nelts; i++)
1653         {
1654           name = APR_ARRAY_IDX(revprops, i, char *);
1655           SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1656
1657           if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1658             want_author = TRUE;
1659           else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1660             want_date = TRUE;
1661           else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1662             want_message = TRUE;
1663           else
1664             want_custom_revprops = TRUE;
1665         }
1666       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1667     }
1668   else
1669     {
1670       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1671
1672       want_author = TRUE;
1673       want_date = TRUE;
1674       want_message = TRUE;
1675       want_custom_revprops = TRUE;
1676     }
1677
1678   SVN_ERR(handle_auth_request(sess_baton, pool));
1679
1680   /* Read the log messages. */
1681   iterpool = svn_pool_create(pool);
1682   while (1)
1683     {
1684       apr_uint64_t has_children_param, invalid_revnum_param;
1685       apr_uint64_t has_subtractive_merge_param;
1686       svn_string_t *author, *date, *message;
1687       apr_array_header_t *cplist, *rplist;
1688       svn_log_entry_t *log_entry;
1689       svn_boolean_t has_children;
1690       svn_boolean_t subtractive_merge = FALSE;
1691       apr_uint64_t revprop_count;
1692       svn_ra_svn_item_t *item;
1693       apr_hash_t *cphash;
1694       svn_revnum_t rev;
1695
1696       svn_pool_clear(iterpool);
1697       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1698       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1699         break;
1700       if (item->kind != SVN_RA_SVN_LIST)
1701         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1702                                 _("Log entry not a list"));
1703       SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1704                                       "lr(?s)(?s)(?s)?BBnl?B",
1705                                       &cplist, &rev, &author, &date,
1706                                       &message, &has_children_param,
1707                                       &invalid_revnum_param,
1708                                       &revprop_count, &rplist,
1709                                       &has_subtractive_merge_param));
1710       if (want_custom_revprops && rplist == NULL)
1711         {
1712           /* Caller asked for custom revprops, but server is too old. */
1713           return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1714                                   _("Server does not support custom revprops"
1715                                     " via log"));
1716         }
1717
1718       if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1719         has_children = FALSE;
1720       else
1721         has_children = (svn_boolean_t) has_children_param;
1722
1723       if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1724         subtractive_merge = FALSE;
1725       else
1726         subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1727
1728       /* Because the svn protocol won't let us send an invalid revnum, we have
1729          to recover that fact using the extra parameter. */
1730       if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1731             && invalid_revnum_param)
1732         rev = SVN_INVALID_REVNUM;
1733
1734       if (cplist->nelts > 0)
1735         {
1736           /* Interpret the changed-paths list. */
1737           cphash = svn_hash__make(iterpool);
1738           for (i = 0; i < cplist->nelts; i++)
1739             {
1740               svn_log_changed_path2_t *change;
1741               svn_string_t *cpath;
1742               const char *copy_path, *action, *kind_str;
1743               apr_uint64_t text_mods, prop_mods;
1744               svn_revnum_t copy_rev;
1745               svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1746                                                       svn_ra_svn_item_t);
1747
1748               if (elt->kind != SVN_RA_SVN_LIST)
1749                 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1750                                         _("Changed-path entry not a list"));
1751               SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list,
1752                                               &cpath, &action, &copy_path,
1753                                               &copy_rev, &kind_str,
1754                                               &text_mods, &prop_mods));
1755
1756               if (!svn_fspath__is_canonical(cpath->data))
1757                 {
1758                   cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1759                   cpath->len = strlen(cpath->data);
1760                 }
1761               if (copy_path && !svn_fspath__is_canonical(copy_path))
1762                 copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1763
1764               change = svn_log_changed_path2_create(iterpool);
1765               change->action = *action;
1766               change->copyfrom_path = copy_path;
1767               change->copyfrom_rev = copy_rev;
1768               change->node_kind = svn_node_kind_from_word(kind_str);
1769               change->text_modified = optbool_to_tristate(text_mods);
1770               change->props_modified = optbool_to_tristate(prop_mods);
1771               apr_hash_set(cphash, cpath->data, cpath->len, change);
1772             }
1773         }
1774       else
1775         cphash = NULL;
1776
1777       /* Invoke RECEIVER
1778           - Except if the server sends more than a >= 1 limit top level items
1779           - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
1780             in an earlier invocation. */
1781       if (! (limit && (nest_level == 0) && (++nreceived > limit))
1782           && ! *outer_error)
1783         {
1784           svn_error_t *err;
1785           log_entry = svn_log_entry_create(iterpool);
1786
1787           log_entry->changed_paths = cphash;
1788           log_entry->changed_paths2 = cphash;
1789           log_entry->revision = rev;
1790           log_entry->has_children = has_children;
1791           log_entry->subtractive_merge = subtractive_merge;
1792           if (rplist)
1793             SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1794                                                &log_entry->revprops));
1795           if (log_entry->revprops == NULL)
1796             log_entry->revprops = svn_hash__make(iterpool);
1797
1798           if (author && want_author)
1799             svn_hash_sets(log_entry->revprops,
1800                           SVN_PROP_REVISION_AUTHOR, author);
1801           if (date && want_date)
1802             svn_hash_sets(log_entry->revprops,
1803                           SVN_PROP_REVISION_DATE, date);
1804           if (message && want_message)
1805             svn_hash_sets(log_entry->revprops,
1806                           SVN_PROP_REVISION_LOG, message);
1807
1808           err = receiver(receiver_baton, log_entry, iterpool);
1809           if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1810             {
1811               *outer_error = svn_error_trace(
1812                                 svn_error_compose_create(*outer_error, err));
1813             }
1814           else
1815             SVN_ERR(err);
1816
1817           if (log_entry->has_children)
1818             {
1819               nest_level++;
1820             }
1821           if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1822             {
1823               SVN_ERR_ASSERT(nest_level);
1824               nest_level--;
1825             }
1826         }
1827     }
1828   svn_pool_destroy(iterpool);
1829
1830   /* Read the response. */
1831   return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1832 }
1833
1834 static svn_error_t *
1835 ra_svn_log(svn_ra_session_t *session,
1836            const apr_array_header_t *paths,
1837            svn_revnum_t start, svn_revnum_t end,
1838            int limit,
1839            svn_boolean_t discover_changed_paths,
1840            svn_boolean_t strict_node_history,
1841            svn_boolean_t include_merged_revisions,
1842            const apr_array_header_t *revprops,
1843            svn_log_entry_receiver_t receiver,
1844            void *receiver_baton, apr_pool_t *pool)
1845 {
1846   svn_error_t *outer_error = NULL;
1847   svn_error_t *err;
1848
1849   err = svn_error_trace(perform_ra_svn_log(&outer_error,
1850                                            session, paths,
1851                                            start, end,
1852                                            limit,
1853                                            discover_changed_paths,
1854                                            strict_node_history,
1855                                            include_merged_revisions,
1856                                            revprops,
1857                                            receiver, receiver_baton,
1858                                            pool));
1859   return svn_error_trace(
1860             svn_error_compose_create(outer_error,
1861                                      err));
1862 }
1863
1864
1865
1866 static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1867                                       const char *path, svn_revnum_t rev,
1868                                       svn_node_kind_t *kind, apr_pool_t *pool)
1869 {
1870   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1871   svn_ra_svn_conn_t *conn = sess_baton->conn;
1872   const char *kind_word;
1873
1874   SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1875   SVN_ERR(handle_auth_request(sess_baton, pool));
1876   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1877   *kind = svn_node_kind_from_word(kind_word);
1878   return SVN_NO_ERROR;
1879 }
1880
1881
1882 /* If ERR is a command not supported error, wrap it in a
1883    SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG.  Else, return err. */
1884 static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1885                                            const char *msg)
1886 {
1887   if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1888     return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1889                             _(msg));
1890   return err;
1891 }
1892
1893
1894 static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1895                                 const char *path, svn_revnum_t rev,
1896                                 svn_dirent_t **dirent, apr_pool_t *pool)
1897 {
1898   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1899   svn_ra_svn_conn_t *conn = sess_baton->conn;
1900   apr_array_header_t *list = NULL;
1901   svn_dirent_t *the_dirent;
1902
1903   SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1904   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1905                                  N_("'stat' not implemented")));
1906   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1907
1908   if (! list)
1909     {
1910       *dirent = NULL;
1911     }
1912   else
1913     {
1914       const char *kind, *cdate, *cauthor;
1915       svn_boolean_t has_props;
1916       svn_revnum_t crev;
1917       apr_uint64_t size;
1918
1919       SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1920                                       &kind, &size, &has_props,
1921                                       &crev, &cdate, &cauthor));
1922
1923       the_dirent = svn_dirent_create(pool);
1924       the_dirent->kind = svn_node_kind_from_word(kind);
1925       the_dirent->size = size;/* FIXME: svn_filesize_t */
1926       the_dirent->has_props = has_props;
1927       the_dirent->created_rev = crev;
1928       SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1929       the_dirent->last_author = cauthor;
1930
1931       *dirent = the_dirent;
1932     }
1933
1934   return SVN_NO_ERROR;
1935 }
1936
1937
1938 static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1939                                          apr_hash_t **locations,
1940                                          const char *path,
1941                                          svn_revnum_t peg_revision,
1942                                          const apr_array_header_t *location_revisions,
1943                                          apr_pool_t *pool)
1944 {
1945   svn_ra_svn__session_baton_t *sess_baton = session->priv;
1946   svn_ra_svn_conn_t *conn = sess_baton->conn;
1947   svn_revnum_t revision;
1948   svn_boolean_t is_done;
1949   int i;
1950
1951   /* Transmit the parameters. */
1952   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1953                                   "get-locations", path, peg_revision));
1954   for (i = 0; i < location_revisions->nelts; i++)
1955     {
1956       revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1957       SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1958     }
1959
1960   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1961
1962   /* Servers before 1.1 don't support this command. Check for this here. */
1963   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1964                                  N_("'get-locations' not implemented")));
1965
1966   /* Read the hash items. */
1967   is_done = FALSE;
1968   *locations = apr_hash_make(pool);
1969   while (!is_done)
1970     {
1971       svn_ra_svn_item_t *item;
1972       const char *ret_path;
1973
1974       SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1975       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1976         is_done = 1;
1977       else if (item->kind != SVN_RA_SVN_LIST)
1978         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1979                                 _("Location entry not a list"));
1980       else
1981         {
1982           SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1983                                           &revision, &ret_path));
1984           ret_path = svn_fspath__canonicalize(ret_path, pool);
1985           apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1986                                                sizeof(revision)),
1987                        sizeof(revision), ret_path);
1988         }
1989     }
1990
1991   /* Read the response. This is so the server would have a chance to
1992    * report an error. */
1993   return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1994 }
1995
1996 static svn_error_t *
1997 ra_svn_get_location_segments(svn_ra_session_t *session,
1998                              const char *path,
1999                              svn_revnum_t peg_revision,
2000                              svn_revnum_t start_rev,
2001                              svn_revnum_t end_rev,
2002                              svn_location_segment_receiver_t receiver,
2003                              void *receiver_baton,
2004                              apr_pool_t *pool)
2005 {
2006   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2007   svn_ra_svn_conn_t *conn = sess_baton->conn;
2008   svn_boolean_t is_done;
2009   apr_pool_t *iterpool = svn_pool_create(pool);
2010
2011   /* Transmit the parameters. */
2012   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2013                                   "get-location-segments",
2014                                   path, peg_revision, start_rev, end_rev));
2015
2016   /* Servers before 1.5 don't support this command. Check for this here. */
2017   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2018                                  N_("'get-location-segments'"
2019                                     " not implemented")));
2020
2021   /* Parse the response. */
2022   is_done = FALSE;
2023   while (!is_done)
2024     {
2025       svn_revnum_t range_start, range_end;
2026       svn_ra_svn_item_t *item;
2027       const char *ret_path;
2028
2029       svn_pool_clear(iterpool);
2030       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2031       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2032         is_done = 1;
2033       else if (item->kind != SVN_RA_SVN_LIST)
2034         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2035                                 _("Location segment entry not a list"));
2036       else
2037         {
2038           svn_location_segment_t *segment = apr_pcalloc(iterpool,
2039                                                         sizeof(*segment));
2040           SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
2041                                           &range_start, &range_end, &ret_path));
2042           if (! (SVN_IS_VALID_REVNUM(range_start)
2043                  && SVN_IS_VALID_REVNUM(range_end)))
2044             return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2045                                     _("Expected valid revision range"));
2046           if (ret_path)
2047             ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2048           segment->path = ret_path;
2049           segment->range_start = range_start;
2050           segment->range_end = range_end;
2051           SVN_ERR(receiver(segment, receiver_baton, iterpool));
2052         }
2053     }
2054   svn_pool_destroy(iterpool);
2055
2056   /* Read the response. This is so the server would have a chance to
2057    * report an error. */
2058   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2059
2060   return SVN_NO_ERROR;
2061 }
2062
2063 static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2064                                          const char *path,
2065                                          svn_revnum_t start, svn_revnum_t end,
2066                                          svn_boolean_t include_merged_revisions,
2067                                          svn_file_rev_handler_t handler,
2068                                          void *handler_baton, apr_pool_t *pool)
2069 {
2070   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2071   apr_pool_t *rev_pool, *chunk_pool;
2072   svn_boolean_t has_txdelta;
2073   svn_boolean_t had_revision = FALSE;
2074
2075   /* One sub-pool for each revision and one for each txdelta chunk.
2076      Note that the rev_pool must live during the following txdelta. */
2077   rev_pool = svn_pool_create(pool);
2078   chunk_pool = svn_pool_create(pool);
2079
2080   SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2081                                               path, start, end,
2082                                               include_merged_revisions));
2083
2084   /* Servers before 1.1 don't support this command.  Check for this here. */
2085   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2086                                  N_("'get-file-revs' not implemented")));
2087
2088   while (1)
2089     {
2090       apr_array_header_t *rev_proplist, *proplist;
2091       apr_uint64_t merged_rev_param;
2092       apr_array_header_t *props;
2093       svn_ra_svn_item_t *item;
2094       apr_hash_t *rev_props;
2095       svn_revnum_t rev;
2096       const char *p;
2097       svn_boolean_t merged_rev;
2098       svn_txdelta_window_handler_t d_handler;
2099       void *d_baton;
2100
2101       svn_pool_clear(rev_pool);
2102       svn_pool_clear(chunk_pool);
2103       SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2104       if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2105         break;
2106       /* Either we've got a correct revision or we will error out below. */
2107       had_revision = TRUE;
2108       if (item->kind != SVN_RA_SVN_LIST)
2109         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2110                                 _("Revision entry not a list"));
2111
2112       SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
2113                                       "crll?B", &p, &rev, &rev_proplist,
2114                                       &proplist, &merged_rev_param));
2115       p = svn_fspath__canonicalize(p, rev_pool);
2116       SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2117       SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2118       if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2119         merged_rev = FALSE;
2120       else
2121         merged_rev = (svn_boolean_t) merged_rev_param;
2122
2123       /* Get the first delta chunk so we know if there is a delta. */
2124       SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2125       if (item->kind != SVN_RA_SVN_STRING)
2126         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2127                                 _("Text delta chunk not a string"));
2128       has_txdelta = item->u.string->len > 0;
2129
2130       SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2131                       has_txdelta ? &d_handler : NULL, &d_baton,
2132                       props, rev_pool));
2133
2134       /* Process the text delta if any. */
2135       if (has_txdelta)
2136         {
2137           svn_stream_t *stream;
2138
2139           if (d_handler && d_handler != svn_delta_noop_window_handler)
2140             stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2141                                                rev_pool);
2142           else
2143             stream = NULL;
2144           while (item->u.string->len > 0)
2145             {
2146               apr_size_t size;
2147
2148               size = item->u.string->len;
2149               if (stream)
2150                 SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2151               svn_pool_clear(chunk_pool);
2152
2153               SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2154                                             &item));
2155               if (item->kind != SVN_RA_SVN_STRING)
2156                 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2157                                         _("Text delta chunk not a string"));
2158             }
2159           if (stream)
2160             SVN_ERR(svn_stream_close(stream));
2161         }
2162     }
2163
2164   SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2165
2166   /* Return error if we didn't get any revisions. */
2167   if (!had_revision)
2168     return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2169                             _("The get-file-revs command didn't return "
2170                               "any revisions"));
2171
2172   svn_pool_destroy(chunk_pool);
2173   svn_pool_destroy(rev_pool);
2174
2175   return SVN_NO_ERROR;
2176 }
2177
2178 /* For each path in PATH_REVS, send a 'lock' command to the server.
2179    Used with 1.2.x series servers which support locking, but of only
2180    one path at a time.  ra_svn_lock(), which supports 'lock-many'
2181    is now the default.  See svn_ra_lock() docstring for interface details. */
2182 static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2183                                        apr_hash_t *path_revs,
2184                                        const char *comment,
2185                                        svn_boolean_t steal_lock,
2186                                        svn_ra_lock_callback_t lock_func,
2187                                        void *lock_baton,
2188                                        apr_pool_t *pool)
2189 {
2190   svn_ra_svn__session_baton_t *sess = session->priv;
2191   svn_ra_svn_conn_t* conn = sess->conn;
2192   apr_array_header_t *list;
2193   apr_hash_index_t *hi;
2194   apr_pool_t *iterpool = svn_pool_create(pool);
2195
2196   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2197     {
2198       svn_lock_t *lock;
2199       const void *key;
2200       const char *path;
2201       void *val;
2202       svn_revnum_t *revnum;
2203       svn_error_t *err, *callback_err = NULL;
2204
2205       svn_pool_clear(iterpool);
2206
2207       apr_hash_this(hi, &key, NULL, &val);
2208       path = key;
2209       revnum = val;
2210
2211       SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2212                                          steal_lock, *revnum));
2213
2214       /* Servers before 1.2 doesn't support locking.  Check this here. */
2215       SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2216                                      N_("Server doesn't support "
2217                                         "the lock command")));
2218
2219       err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2220
2221       if (!err)
2222         SVN_ERR(parse_lock(list, iterpool, &lock));
2223
2224       if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2225         return err;
2226
2227       if (lock_func)
2228         callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2229                                  err, iterpool);
2230
2231       svn_error_clear(err);
2232
2233       if (callback_err)
2234         return callback_err;
2235     }
2236
2237   svn_pool_destroy(iterpool);
2238
2239   return SVN_NO_ERROR;
2240 }
2241
2242 /* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2243    Used with 1.2.x series servers which support unlocking, but of only
2244    one path at a time.  ra_svn_unlock(), which supports 'unlock-many' is
2245    now the default.  See svn_ra_unlock() docstring for interface details. */
2246 static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2247                                          apr_hash_t *path_tokens,
2248                                          svn_boolean_t break_lock,
2249                                          svn_ra_lock_callback_t lock_func,
2250                                          void *lock_baton,
2251                                          apr_pool_t *pool)
2252 {
2253   svn_ra_svn__session_baton_t *sess = session->priv;
2254   svn_ra_svn_conn_t* conn = sess->conn;
2255   apr_hash_index_t *hi;
2256   apr_pool_t *iterpool = svn_pool_create(pool);
2257
2258   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2259     {
2260       const void *key;
2261       const char *path;
2262       void *val;
2263       const char *token;
2264       svn_error_t *err, *callback_err = NULL;
2265
2266       svn_pool_clear(iterpool);
2267
2268       apr_hash_this(hi, &key, NULL, &val);
2269       path = key;
2270       if (strcmp(val, "") != 0)
2271         token = val;
2272       else
2273         token = NULL;
2274
2275       SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2276                                            break_lock));
2277
2278       /* Servers before 1.2 don't support locking.  Check this here. */
2279       SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2280                                      N_("Server doesn't support the unlock "
2281                                         "command")));
2282
2283       err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2284
2285       if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2286         return err;
2287
2288       if (lock_func)
2289         callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2290
2291       svn_error_clear(err);
2292
2293       if (callback_err)
2294         return callback_err;
2295     }
2296
2297   svn_pool_destroy(iterpool);
2298
2299   return SVN_NO_ERROR;
2300 }
2301
2302 /* Tell the server to lock all paths in PATH_REVS.
2303    See svn_ra_lock() for interface details. */
2304 static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2305                                 apr_hash_t *path_revs,
2306                                 const char *comment,
2307                                 svn_boolean_t steal_lock,
2308                                 svn_ra_lock_callback_t lock_func,
2309                                 void *lock_baton,
2310                                 apr_pool_t *pool)
2311 {
2312   svn_ra_svn__session_baton_t *sess = session->priv;
2313   svn_ra_svn_conn_t *conn = sess->conn;
2314   apr_hash_index_t *hi;
2315   svn_error_t *err;
2316   apr_pool_t *iterpool = svn_pool_create(pool);
2317
2318   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2319                                   comment, steal_lock));
2320
2321   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2322     {
2323       const void *key;
2324       const char *path;
2325       void *val;
2326       svn_revnum_t *revnum;
2327
2328       svn_pool_clear(iterpool);
2329       apr_hash_this(hi, &key, NULL, &val);
2330       path = key;
2331       revnum = val;
2332
2333       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2334     }
2335
2336   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2337
2338   err = handle_auth_request(sess, pool);
2339
2340   /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2341    * to 'lock'. */
2342   if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2343     {
2344       svn_error_clear(err);
2345       return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2346                                 lock_func, lock_baton, pool);
2347     }
2348
2349   if (err)
2350     return err;
2351
2352   /* Loop over responses to get lock information. */
2353   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2354     {
2355       svn_ra_svn_item_t *elt;
2356       const void *key;
2357       const char *path;
2358       svn_error_t *callback_err;
2359       const char *status;
2360       svn_lock_t *lock;
2361       apr_array_header_t *list;
2362
2363       apr_hash_this(hi, &key, NULL, NULL);
2364       path = key;
2365
2366       svn_pool_clear(iterpool);
2367       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2368
2369       /* The server might have encountered some sort of fatal error in
2370          the middle of the request list.  If this happens, it will
2371          transmit "done" to end the lock-info early, and then the
2372          overall command response will talk about the fatal error. */
2373       if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2374         break;
2375
2376       if (elt->kind != SVN_RA_SVN_LIST)
2377         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2378                                 _("Lock response not a list"));
2379
2380       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2381                                       &list));
2382
2383       if (strcmp(status, "failure") == 0)
2384         err = svn_ra_svn__handle_failure_status(list, iterpool);
2385       else if (strcmp(status, "success") == 0)
2386         {
2387           SVN_ERR(parse_lock(list, iterpool, &lock));
2388           err = NULL;
2389         }
2390       else
2391         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2392                                 _("Unknown status for lock command"));
2393
2394       if (lock_func)
2395         callback_err = lock_func(lock_baton, path, TRUE,
2396                                  err ? NULL : lock,
2397                                  err, iterpool);
2398       else
2399         callback_err = SVN_NO_ERROR;
2400
2401       svn_error_clear(err);
2402
2403       if (callback_err)
2404         return callback_err;
2405     }
2406
2407   /* If we didn't break early above, and the whole hash was traversed,
2408      read the final "done" from the server. */
2409   if (!hi)
2410     {
2411       svn_ra_svn_item_t *elt;
2412
2413       SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2414       if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2415         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2416                                 _("Didn't receive end marker for lock "
2417                                   "responses"));
2418     }
2419
2420   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2421
2422   svn_pool_destroy(iterpool);
2423
2424   return SVN_NO_ERROR;
2425 }
2426
2427 /* Tell the server to unlock all paths in PATH_TOKENS.
2428    See svn_ra_unlock() for interface details. */
2429 static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2430                                   apr_hash_t *path_tokens,
2431                                   svn_boolean_t break_lock,
2432                                   svn_ra_lock_callback_t lock_func,
2433                                   void *lock_baton,
2434                                   apr_pool_t *pool)
2435 {
2436   svn_ra_svn__session_baton_t *sess = session->priv;
2437   svn_ra_svn_conn_t *conn = sess->conn;
2438   apr_hash_index_t *hi;
2439   apr_pool_t *iterpool = svn_pool_create(pool);
2440   svn_error_t *err;
2441   const char *path;
2442
2443   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2444                                   break_lock));
2445
2446   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2447     {
2448       void *val;
2449       const void *key;
2450       const char *token;
2451
2452       svn_pool_clear(iterpool);
2453       apr_hash_this(hi, &key, NULL, &val);
2454       path = key;
2455
2456       if (strcmp(val, "") != 0)
2457         token = val;
2458       else
2459         token = NULL;
2460
2461       SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2462     }
2463
2464   SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2465
2466   err = handle_auth_request(sess, pool);
2467
2468   /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2469    * to 'unlock'.
2470    */
2471   if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2472     {
2473       svn_error_clear(err);
2474       return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2475                                   lock_baton, pool);
2476     }
2477
2478   if (err)
2479     return err;
2480
2481   /* Loop over responses to unlock files. */
2482   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2483     {
2484       svn_ra_svn_item_t *elt;
2485       const void *key;
2486       svn_error_t *callback_err;
2487       const char *status;
2488       apr_array_header_t *list;
2489
2490       svn_pool_clear(iterpool);
2491
2492       SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2493
2494       /* The server might have encountered some sort of fatal error in
2495          the middle of the request list.  If this happens, it will
2496          transmit "done" to end the lock-info early, and then the
2497          overall command response will talk about the fatal error. */
2498       if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2499         break;
2500
2501       apr_hash_this(hi, &key, NULL, NULL);
2502       path = key;
2503
2504       if (elt->kind != SVN_RA_SVN_LIST)
2505         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2506                                 _("Unlock response not a list"));
2507
2508       SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2509                                       &list));
2510
2511       if (strcmp(status, "failure") == 0)
2512         err = svn_ra_svn__handle_failure_status(list, iterpool);
2513       else if (strcmp(status, "success") == 0)
2514         {
2515           SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2516           err = SVN_NO_ERROR;
2517         }
2518       else
2519         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2520                                 _("Unknown status for unlock command"));
2521
2522       if (lock_func)
2523         callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2524                                  iterpool);
2525       else
2526         callback_err = SVN_NO_ERROR;
2527
2528       svn_error_clear(err);
2529
2530       if (callback_err)
2531         return callback_err;
2532     }
2533
2534   /* If we didn't break early above, and the whole hash was traversed,
2535      read the final "done" from the server. */
2536   if (!hi)
2537     {
2538       svn_ra_svn_item_t *elt;
2539
2540       SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2541       if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2542         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2543                                 _("Didn't receive end marker for unlock "
2544                                   "responses"));
2545     }
2546
2547   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2548
2549   svn_pool_destroy(iterpool);
2550
2551   return SVN_NO_ERROR;
2552 }
2553
2554 static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2555                                     svn_lock_t **lock,
2556                                     const char *path,
2557                                     apr_pool_t *pool)
2558 {
2559   svn_ra_svn__session_baton_t *sess = session->priv;
2560   svn_ra_svn_conn_t* conn = sess->conn;
2561   apr_array_header_t *list;
2562
2563   SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2564
2565   /* Servers before 1.2 doesn't support locking.  Check this here. */
2566   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2567                                  N_("Server doesn't support the get-lock "
2568                                     "command")));
2569
2570   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2571   if (list)
2572     SVN_ERR(parse_lock(list, pool, lock));
2573   else
2574     *lock = NULL;
2575
2576   return SVN_NO_ERROR;
2577 }
2578
2579 /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2580    to prevent a dependency cycle. */
2581 static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2582                                           const char **rel_path,
2583                                           const char *url,
2584                                           apr_pool_t *pool)
2585 {
2586   const char *root_url;
2587
2588   SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2589   *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2590   if (! *rel_path)
2591     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2592                              _("'%s' isn't a child of repository root "
2593                                "URL '%s'"),
2594                              url, root_url);
2595   return SVN_NO_ERROR;
2596 }
2597
2598 static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2599                                      apr_hash_t **locks,
2600                                      const char *path,
2601                                      svn_depth_t depth,
2602                                      apr_pool_t *pool)
2603 {
2604   svn_ra_svn__session_baton_t *sess = session->priv;
2605   svn_ra_svn_conn_t* conn = sess->conn;
2606   apr_array_header_t *list;
2607   const char *full_url, *abs_path;
2608   int i;
2609
2610   /* Figure out the repository abspath from PATH. */
2611   full_url = svn_path_url_add_component2(sess->url, path, pool);
2612   SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2613   abs_path = svn_fspath__canonicalize(abs_path, pool);
2614
2615   SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2616
2617   /* Servers before 1.2 doesn't support locking.  Check this here. */
2618   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2619                                  N_("Server doesn't support the get-lock "
2620                                     "command")));
2621
2622   SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2623
2624   *locks = apr_hash_make(pool);
2625
2626   for (i = 0; i < list->nelts; ++i)
2627     {
2628       svn_lock_t *lock;
2629       svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2630
2631       if (elt->kind != SVN_RA_SVN_LIST)
2632         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2633                                 _("Lock element not a list"));
2634       SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2635
2636       /* Filter out unwanted paths.  Since Subversion only allows
2637          locks on files, we can treat depth=immediates the same as
2638          depth=files for filtering purposes.  Meaning, we'll keep
2639          this lock if:
2640
2641          a) its path is the very path we queried, or
2642          b) we've asked for a fully recursive answer, or
2643          c) we've asked for depth=files or depth=immediates, and this
2644             lock is on an immediate child of our query path.
2645       */
2646       if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2647         {
2648           svn_hash_sets(*locks, lock->path, lock);
2649         }
2650       else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2651         {
2652           const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2653           if (relpath && (svn_path_component_count(relpath) == 1))
2654             svn_hash_sets(*locks, lock->path, lock);
2655         }
2656     }
2657
2658   return SVN_NO_ERROR;
2659 }
2660
2661
2662 static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2663                                   svn_revnum_t revision,
2664                                   svn_revnum_t low_water_mark,
2665                                   svn_boolean_t send_deltas,
2666                                   const svn_delta_editor_t *editor,
2667                                   void *edit_baton,
2668                                   apr_pool_t *pool)
2669 {
2670   svn_ra_svn__session_baton_t *sess = session->priv;
2671
2672   SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2673                                        low_water_mark, send_deltas));
2674
2675   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2676                                  N_("Server doesn't support the replay "
2677                                     "command")));
2678
2679   SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2680                                    NULL, TRUE));
2681
2682   return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2683 }
2684
2685
2686 static svn_error_t *
2687 ra_svn_replay_range(svn_ra_session_t *session,
2688                     svn_revnum_t start_revision,
2689                     svn_revnum_t end_revision,
2690                     svn_revnum_t low_water_mark,
2691                     svn_boolean_t send_deltas,
2692                     svn_ra_replay_revstart_callback_t revstart_func,
2693                     svn_ra_replay_revfinish_callback_t revfinish_func,
2694                     void *replay_baton,
2695                     apr_pool_t *pool)
2696 {
2697   svn_ra_svn__session_baton_t *sess = session->priv;
2698   apr_pool_t *iterpool;
2699   svn_revnum_t rev;
2700   svn_boolean_t drive_aborted = FALSE;
2701
2702   SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2703                                              start_revision, end_revision,
2704                                              low_water_mark, send_deltas));
2705
2706   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2707                                  N_("Server doesn't support the "
2708                                     "replay-range command")));
2709
2710   iterpool = svn_pool_create(pool);
2711   for (rev = start_revision; rev <= end_revision; rev++)
2712     {
2713       const svn_delta_editor_t *editor;
2714       void *edit_baton;
2715       apr_hash_t *rev_props;
2716       const char *word;
2717       apr_array_header_t *list;
2718
2719       svn_pool_clear(iterpool);
2720
2721       SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2722                                      "wl", &word, &list));
2723       if (strcmp(word, "revprops") != 0)
2724         return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2725                                  _("Expected 'revprops', found '%s'"),
2726                                  word);
2727
2728       SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2729
2730       SVN_ERR(revstart_func(rev, replay_baton,
2731                             &editor, &edit_baton,
2732                             rev_props,
2733                             iterpool));
2734       SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2735                                        editor, edit_baton,
2736                                        &drive_aborted, TRUE));
2737       /* If drive_editor2() aborted the commit, do NOT try to call
2738          revfinish_func and commit the transaction! */
2739       if (drive_aborted) {
2740         svn_pool_destroy(iterpool);
2741         return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2742                                 _("Error while replaying commit"));
2743       }
2744       SVN_ERR(revfinish_func(rev, replay_baton,
2745                              editor, edit_baton,
2746                              rev_props,
2747                              iterpool));
2748     }
2749   svn_pool_destroy(iterpool);
2750
2751   return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2752 }
2753
2754
2755 static svn_error_t *
2756 ra_svn_has_capability(svn_ra_session_t *session,
2757                       svn_boolean_t *has,
2758                       const char *capability,
2759                       apr_pool_t *pool)
2760 {
2761   svn_ra_svn__session_baton_t *sess = session->priv;
2762   static const char* capabilities[][2] =
2763   {
2764       /* { ra capability string, svn:// wire capability string} */
2765       {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2766       {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2767       {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2768       {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2769       {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2770       {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2771       {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2772       {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2773                                           SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2774       {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2775                                        SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2776
2777       {NULL, NULL} /* End of list marker */
2778   };
2779   int i;
2780
2781   *has = FALSE;
2782
2783   for (i = 0; capabilities[i][0]; i++)
2784     {
2785       if (strcmp(capability, capabilities[i][0]) == 0)
2786         {
2787           *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2788           return SVN_NO_ERROR;
2789         }
2790     }
2791
2792   return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2793                            _("Don't know anything about capability '%s'"),
2794                            capability);
2795 }
2796
2797 static svn_error_t *
2798 ra_svn_get_deleted_rev(svn_ra_session_t *session,
2799                        const char *path,
2800                        svn_revnum_t peg_revision,
2801                        svn_revnum_t end_revision,
2802                        svn_revnum_t *revision_deleted,
2803                        apr_pool_t *pool)
2804
2805 {
2806   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2807   svn_ra_svn_conn_t *conn = sess_baton->conn;
2808
2809   /* Transmit the parameters. */
2810   SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2811                                                peg_revision, end_revision));
2812
2813   /* Servers before 1.6 don't support this command.  Check for this here. */
2814   SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2815                                  N_("'get-deleted-rev' not implemented")));
2816
2817   return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
2818                                                        revision_deleted));
2819 }
2820
2821 static svn_error_t *
2822 ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2823                                       svn_delta_shim_callbacks_t *callbacks)
2824 {
2825   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2826   svn_ra_svn_conn_t *conn = sess_baton->conn;
2827
2828   conn->shim_callbacks = callbacks;
2829
2830   return SVN_NO_ERROR;
2831 }
2832
2833 static svn_error_t *
2834 ra_svn_get_inherited_props(svn_ra_session_t *session,
2835                            apr_array_header_t **iprops,
2836                            const char *path,
2837                            svn_revnum_t revision,
2838                            apr_pool_t *result_pool,
2839                            apr_pool_t *scratch_pool)
2840 {
2841   svn_ra_svn__session_baton_t *sess_baton = session->priv;
2842   svn_ra_svn_conn_t *conn = sess_baton->conn;
2843   apr_array_header_t *iproplist;
2844   svn_boolean_t iprop_capable;
2845
2846   SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
2847                                 SVN_RA_CAPABILITY_INHERITED_PROPS,
2848                                 scratch_pool));
2849
2850   /* If we don't support native iprop handling, use the implementation
2851      in libsvn_ra */
2852   if (!iprop_capable)
2853     return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
2854
2855   SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2856                                            path, revision));
2857   SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2858   SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2859   SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2860                           scratch_pool));
2861
2862   return SVN_NO_ERROR;
2863 }
2864
2865 static const svn_ra__vtable_t ra_svn_vtable = {
2866   svn_ra_svn_version,
2867   ra_svn_get_description,
2868   ra_svn_get_schemes,
2869   ra_svn_open,
2870   ra_svn_dup_session,
2871   ra_svn_reparent,
2872   ra_svn_get_session_url,
2873   ra_svn_get_latest_rev,
2874   ra_svn_get_dated_rev,
2875   ra_svn_change_rev_prop,
2876   ra_svn_rev_proplist,
2877   ra_svn_rev_prop,
2878   ra_svn_commit,
2879   ra_svn_get_file,
2880   ra_svn_get_dir,
2881   ra_svn_get_mergeinfo,
2882   ra_svn_update,
2883   ra_svn_switch,
2884   ra_svn_status,
2885   ra_svn_diff,
2886   ra_svn_log,
2887   ra_svn_check_path,
2888   ra_svn_stat,
2889   ra_svn_get_uuid,
2890   ra_svn_get_repos_root,
2891   ra_svn_get_locations,
2892   ra_svn_get_location_segments,
2893   ra_svn_get_file_revs,
2894   ra_svn_lock,
2895   ra_svn_unlock,
2896   ra_svn_get_lock,
2897   ra_svn_get_locks,
2898   ra_svn_replay,
2899   ra_svn_has_capability,
2900   ra_svn_replay_range,
2901   ra_svn_get_deleted_rev,
2902   ra_svn_register_editor_shim_callbacks,
2903   ra_svn_get_inherited_props
2904 };
2905
2906 svn_error_t *
2907 svn_ra_svn__init(const svn_version_t *loader_version,
2908                  const svn_ra__vtable_t **vtable,
2909                  apr_pool_t *pool)
2910 {
2911   static const svn_version_checklist_t checklist[] =
2912     {
2913       { "svn_subr",  svn_subr_version },
2914       { "svn_delta", svn_delta_version },
2915       { NULL, NULL }
2916     };
2917
2918   SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2919
2920   /* Simplified version check to make sure we can safely use the
2921      VTABLE parameter. The RA loader does a more exhaustive check. */
2922   if (loader_version->major != SVN_VER_MAJOR)
2923     {
2924       return svn_error_createf
2925         (SVN_ERR_VERSION_MISMATCH, NULL,
2926          _("Unsupported RA loader version (%d) for ra_svn"),
2927          loader_version->major);
2928     }
2929
2930   *vtable = &ra_svn_vtable;
2931
2932 #ifdef SVN_HAVE_SASL
2933   SVN_ERR(svn_ra_svn__sasl_init());
2934 #endif
2935
2936   return SVN_NO_ERROR;
2937 }
2938
2939 /* Compatibility wrapper for the 1.1 and before API. */
2940 #define NAME "ra_svn"
2941 #define DESCRIPTION RA_SVN_DESCRIPTION
2942 #define VTBL ra_svn_vtable
2943 #define INITFUNC svn_ra_svn__init
2944 #define COMPAT_INITFUNC svn_ra_svn_init
2945 #include "../libsvn_ra/wrapper_template.h"