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