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