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