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