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