1251881Speter/*
2251881Speter * client.c :  Functions for repository access via the Subversion protocol
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include "svn_private_config.h"
27251881Speter
28251881Speter#define APR_WANT_STRFUNC
29251881Speter#include <apr_want.h>
30251881Speter#include <apr_general.h>
31251881Speter#include <apr_strings.h>
32251881Speter#include <apr_network_io.h>
33251881Speter#include <apr_uri.h>
34251881Speter
35251881Speter#include "svn_hash.h"
36251881Speter#include "svn_types.h"
37251881Speter#include "svn_string.h"
38251881Speter#include "svn_dirent_uri.h"
39251881Speter#include "svn_error.h"
40251881Speter#include "svn_time.h"
41251881Speter#include "svn_path.h"
42251881Speter#include "svn_pools.h"
43251881Speter#include "svn_config.h"
44251881Speter#include "svn_ra.h"
45251881Speter#include "svn_ra_svn.h"
46251881Speter#include "svn_props.h"
47251881Speter#include "svn_mergeinfo.h"
48251881Speter#include "svn_version.h"
49251881Speter
50251881Speter#include "svn_private_config.h"
51251881Speter
52251881Speter#include "private/svn_fspath.h"
53262253Speter#include "private/svn_subr_private.h"
54251881Speter
55251881Speter#include "../libsvn_ra/ra_loader.h"
56251881Speter
57251881Speter#include "ra_svn.h"
58251881Speter
59251881Speter#ifdef SVN_HAVE_SASL
60251881Speter#define DO_AUTH svn_ra_svn__do_cyrus_auth
61251881Speter#else
62251881Speter#define DO_AUTH svn_ra_svn__do_internal_auth
63251881Speter#endif
64251881Speter
65251881Speter/* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
66251881Speter   whatever reason) deems svn_depth_immediates as non-recursive, which
67251881Speter   is ... kinda true, but not true enough for our purposes.  We need
68251881Speter   our requested recursion level to be *at least* as recursive as the
69251881Speter   real depth we're looking for.
70251881Speter */
71251881Speter#define DEPTH_TO_RECURSE(d)    \
72251881Speter        ((d) == svn_depth_unknown || (d) > svn_depth_files)
73251881Speter
74251881Spetertypedef struct ra_svn_commit_callback_baton_t {
75251881Speter  svn_ra_svn__session_baton_t *sess_baton;
76251881Speter  apr_pool_t *pool;
77251881Speter  svn_revnum_t *new_rev;
78251881Speter  svn_commit_callback2_t callback;
79251881Speter  void *callback_baton;
80251881Speter} ra_svn_commit_callback_baton_t;
81251881Speter
82251881Spetertypedef struct ra_svn_reporter_baton_t {
83251881Speter  svn_ra_svn__session_baton_t *sess_baton;
84251881Speter  svn_ra_svn_conn_t *conn;
85251881Speter  apr_pool_t *pool;
86251881Speter  const svn_delta_editor_t *editor;
87251881Speter  void *edit_baton;
88251881Speter} ra_svn_reporter_baton_t;
89251881Speter
90251881Speter/* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
91251881Speter   portion. */
92251881Speterstatic void parse_tunnel(const char *url, const char **tunnel,
93251881Speter                         apr_pool_t *pool)
94251881Speter{
95251881Speter  *tunnel = NULL;
96251881Speter
97251881Speter  if (strncasecmp(url, "svn", 3) != 0)
98251881Speter    return;
99251881Speter  url += 3;
100251881Speter
101251881Speter  /* Get the tunnel specification, if any. */
102251881Speter  if (*url == '+')
103251881Speter    {
104251881Speter      const char *p;
105251881Speter
106251881Speter      url++;
107251881Speter      p = strchr(url, ':');
108251881Speter      if (!p)
109251881Speter        return;
110251881Speter      *tunnel = apr_pstrmemdup(pool, url, p - url);
111251881Speter    }
112251881Speter}
113251881Speter
114251881Speterstatic svn_error_t *make_connection(const char *hostname, unsigned short port,
115251881Speter                                    apr_socket_t **sock, apr_pool_t *pool)
116251881Speter{
117251881Speter  apr_sockaddr_t *sa;
118251881Speter  apr_status_t status;
119251881Speter  int family = APR_INET;
120251881Speter
121251881Speter  /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
122251881Speter     APR_UNSPEC, because it may give us back an IPV6 address even if we can't
123251881Speter     create IPV6 sockets.  */
124251881Speter
125251881Speter#if APR_HAVE_IPV6
126251881Speter#ifdef MAX_SECS_TO_LINGER
127251881Speter  status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
128251881Speter#else
129251881Speter  status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
130251881Speter                             APR_PROTO_TCP, pool);
131251881Speter#endif
132251881Speter  if (status == 0)
133251881Speter    {
134251881Speter      apr_socket_close(*sock);
135251881Speter      family = APR_UNSPEC;
136251881Speter    }
137251881Speter#endif
138251881Speter
139251881Speter  /* Resolve the hostname. */
140251881Speter  status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
141251881Speter  if (status)
142251881Speter    return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
143251881Speter                             hostname);
144251881Speter  /* Iterate through the returned list of addresses attempting to
145251881Speter   * connect to each in turn. */
146251881Speter  do
147251881Speter    {
148251881Speter      /* Create the socket. */
149251881Speter#ifdef MAX_SECS_TO_LINGER
150251881Speter      /* ### old APR interface */
151251881Speter      status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
152251881Speter#else
153251881Speter      status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
154251881Speter                                 pool);
155251881Speter#endif
156251881Speter      if (status == APR_SUCCESS)
157251881Speter        {
158251881Speter          status = apr_socket_connect(*sock, sa);
159251881Speter          if (status != APR_SUCCESS)
160251881Speter            apr_socket_close(*sock);
161251881Speter        }
162251881Speter      sa = sa->next;
163251881Speter    }
164251881Speter  while (status != APR_SUCCESS && sa);
165251881Speter
166251881Speter  if (status)
167251881Speter    return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
168251881Speter                              hostname);
169251881Speter
170251881Speter  /* Enable TCP keep-alives on the socket so we time out when
171251881Speter   * the connection breaks due to network-layer problems.
172251881Speter   * If the peer has dropped the connection due to a network partition
173251881Speter   * or a crash, or if the peer no longer considers the connection
174251881Speter   * valid because we are behind a NAT and our public IP has changed,
175251881Speter   * it will respond to the keep-alive probe with a RST instead of an
176251881Speter   * acknowledgment segment, which will cause svn to abort the session
177251881Speter   * even while it is currently blocked waiting for data from the peer.
178251881Speter   * See issue #3347. */
179251881Speter  status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
180251881Speter  if (status)
181251881Speter    {
182251881Speter      /* It's not a fatal error if we cannot enable keep-alives. */
183251881Speter    }
184251881Speter
185251881Speter  return SVN_NO_ERROR;
186251881Speter}
187251881Speter
188251881Speter/* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
189251881Speter   property diffs in LIST, received from the server. */
190251881Speterstatic svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
191251881Speter                                     apr_pool_t *pool,
192251881Speter                                     apr_array_header_t **diffs)
193251881Speter{
194251881Speter  int i;
195251881Speter
196251881Speter  *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
197251881Speter
198251881Speter  for (i = 0; i < list->nelts; i++)
199251881Speter    {
200251881Speter      svn_prop_t *prop;
201251881Speter      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
202251881Speter
203251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
204251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
205251881Speter                                _("Prop diffs element not a list"));
206251881Speter      prop = apr_array_push(*diffs);
207251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
208251881Speter                                      &prop->value));
209251881Speter    }
210251881Speter  return SVN_NO_ERROR;
211251881Speter}
212251881Speter
213251881Speter/* Parse a lockdesc, provided in LIST as specified by the protocol into
214251881Speter   LOCK, allocated in POOL. */
215251881Speterstatic svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
216251881Speter                               svn_lock_t **lock)
217251881Speter{
218251881Speter  const char *cdate, *edate;
219251881Speter  *lock = svn_lock_create(pool);
220251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
221251881Speter                                  &(*lock)->token, &(*lock)->owner,
222251881Speter                                  &(*lock)->comment, &cdate, &edate));
223251881Speter  (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
224251881Speter  SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
225251881Speter  if (edate)
226251881Speter    SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
227251881Speter  return SVN_NO_ERROR;
228251881Speter}
229251881Speter
230251881Speter/* --- AUTHENTICATION ROUTINES --- */
231251881Speter
232251881Spetersvn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
233251881Speter                                       apr_pool_t *pool,
234251881Speter                                       const char *mech, const char *mech_arg)
235251881Speter{
236299742Sdim  return svn_error_trace(svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg));
237251881Speter}
238251881Speter
239251881Speterstatic svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
240251881Speter                                        apr_pool_t *pool)
241251881Speter{
242251881Speter  svn_ra_svn_conn_t *conn = sess->conn;
243251881Speter  apr_array_header_t *mechlist;
244251881Speter  const char *realm;
245251881Speter
246251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
247251881Speter  if (mechlist->nelts == 0)
248251881Speter    return SVN_NO_ERROR;
249251881Speter  return DO_AUTH(sess, mechlist, realm, pool);
250251881Speter}
251251881Speter
252251881Speter/* --- REPORTER IMPLEMENTATION --- */
253251881Speter
254251881Speterstatic svn_error_t *ra_svn_set_path(void *baton, const char *path,
255251881Speter                                    svn_revnum_t rev,
256251881Speter                                    svn_depth_t depth,
257251881Speter                                    svn_boolean_t start_empty,
258251881Speter                                    const char *lock_token,
259251881Speter                                    apr_pool_t *pool)
260251881Speter{
261251881Speter  ra_svn_reporter_baton_t *b = baton;
262251881Speter
263251881Speter  SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
264251881Speter                                         start_empty, lock_token, depth));
265251881Speter  return SVN_NO_ERROR;
266251881Speter}
267251881Speter
268251881Speterstatic svn_error_t *ra_svn_delete_path(void *baton, const char *path,
269251881Speter                                       apr_pool_t *pool)
270251881Speter{
271251881Speter  ra_svn_reporter_baton_t *b = baton;
272251881Speter
273251881Speter  SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
274251881Speter  return SVN_NO_ERROR;
275251881Speter}
276251881Speter
277251881Speterstatic svn_error_t *ra_svn_link_path(void *baton, const char *path,
278251881Speter                                     const char *url,
279251881Speter                                     svn_revnum_t rev,
280251881Speter                                     svn_depth_t depth,
281251881Speter                                     svn_boolean_t start_empty,
282251881Speter                                     const char *lock_token,
283251881Speter                                     apr_pool_t *pool)
284251881Speter{
285251881Speter  ra_svn_reporter_baton_t *b = baton;
286251881Speter
287251881Speter  SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
288251881Speter                                          start_empty, lock_token, depth));
289251881Speter  return SVN_NO_ERROR;
290251881Speter}
291251881Speter
292251881Speterstatic svn_error_t *ra_svn_finish_report(void *baton,
293251881Speter                                         apr_pool_t *pool)
294251881Speter{
295251881Speter  ra_svn_reporter_baton_t *b = baton;
296251881Speter
297251881Speter  SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
298251881Speter  SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
299251881Speter  SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
300251881Speter                                   NULL, FALSE));
301251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
302251881Speter  return SVN_NO_ERROR;
303251881Speter}
304251881Speter
305251881Speterstatic svn_error_t *ra_svn_abort_report(void *baton,
306251881Speter                                        apr_pool_t *pool)
307251881Speter{
308251881Speter  ra_svn_reporter_baton_t *b = baton;
309251881Speter
310251881Speter  SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
311251881Speter  return SVN_NO_ERROR;
312251881Speter}
313251881Speter
314251881Speterstatic svn_ra_reporter3_t ra_svn_reporter = {
315251881Speter  ra_svn_set_path,
316251881Speter  ra_svn_delete_path,
317251881Speter  ra_svn_link_path,
318251881Speter  ra_svn_finish_report,
319251881Speter  ra_svn_abort_report
320251881Speter};
321251881Speter
322251881Speter/* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
323251881Speter * EDITOR/EDIT_BATON when it gets the finish_report() call.
324251881Speter *
325251881Speter * Allocate the new reporter in POOL.
326251881Speter */
327251881Speterstatic svn_error_t *
328251881Speterra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
329251881Speter                    apr_pool_t *pool,
330251881Speter                    const svn_delta_editor_t *editor,
331251881Speter                    void *edit_baton,
332251881Speter                    const char *target,
333251881Speter                    svn_depth_t depth,
334251881Speter                    const svn_ra_reporter3_t **reporter,
335251881Speter                    void **report_baton)
336251881Speter{
337251881Speter  ra_svn_reporter_baton_t *b;
338251881Speter  const svn_delta_editor_t *filter_editor;
339251881Speter  void *filter_baton;
340251881Speter
341251881Speter  /* We can skip the depth filtering when the user requested
342251881Speter     depth_files or depth_infinity because the server will
343251881Speter     transmit the right stuff anyway. */
344251881Speter  if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
345251881Speter      && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
346251881Speter    {
347251881Speter      SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
348251881Speter                                            &filter_baton,
349251881Speter                                            editor, edit_baton, depth,
350251881Speter                                            *target != '\0',
351251881Speter                                            pool));
352251881Speter      editor = filter_editor;
353251881Speter      edit_baton = filter_baton;
354251881Speter    }
355251881Speter
356251881Speter  b = apr_palloc(pool, sizeof(*b));
357251881Speter  b->sess_baton = sess_baton;
358251881Speter  b->conn = sess_baton->conn;
359251881Speter  b->pool = pool;
360251881Speter  b->editor = editor;
361251881Speter  b->edit_baton = edit_baton;
362251881Speter
363251881Speter  *reporter = &ra_svn_reporter;
364251881Speter  *report_baton = b;
365251881Speter
366251881Speter  return SVN_NO_ERROR;
367251881Speter}
368251881Speter
369251881Speter/* --- RA LAYER IMPLEMENTATION --- */
370251881Speter
371299742Sdim/* (Note: *ARGV_P is an output parameter.) */
372251881Speterstatic svn_error_t *find_tunnel_agent(const char *tunnel,
373251881Speter                                      const char *hostinfo,
374299742Sdim                                      const char ***argv_p,
375251881Speter                                      apr_hash_t *config, apr_pool_t *pool)
376251881Speter{
377251881Speter  svn_config_t *cfg;
378251881Speter  const char *val, *var, *cmd;
379251881Speter  char **cmd_argv;
380299742Sdim  const char **argv;
381251881Speter  apr_size_t len;
382251881Speter  apr_status_t status;
383251881Speter  int n;
384251881Speter
385251881Speter  /* Look up the tunnel specification in config. */
386251881Speter  cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
387251881Speter  svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
388251881Speter
389251881Speter  /* We have one predefined tunnel scheme, if it isn't overridden by config. */
390251881Speter  if (!val && strcmp(tunnel, "ssh") == 0)
391251881Speter    {
392251881Speter      /* Killing the tunnel agent with SIGTERM leads to unsightly
393251881Speter       * stderr output from ssh, unless we pass -q.
394251881Speter       * The "-q" option to ssh is widely supported: all versions of
395251881Speter       * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
396251881Speter       * versions have it too. If the user is using some other ssh
397251881Speter       * implementation that doesn't accept it, they can override it
398251881Speter       * in the [tunnels] section of the config. */
399251881Speter      val = "$SVN_SSH ssh -q";
400251881Speter    }
401251881Speter
402251881Speter  if (!val || !*val)
403251881Speter    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
404251881Speter                             _("Undefined tunnel scheme '%s'"), tunnel);
405251881Speter
406251881Speter  /* If the scheme definition begins with "$varname", it means there
407251881Speter   * is an environment variable which can override the command. */
408251881Speter  if (*val == '$')
409251881Speter    {
410251881Speter      val++;
411251881Speter      len = strcspn(val, " ");
412251881Speter      var = apr_pstrmemdup(pool, val, len);
413251881Speter      cmd = getenv(var);
414251881Speter      if (!cmd)
415251881Speter        {
416251881Speter          cmd = val + len;
417251881Speter          while (*cmd == ' ')
418251881Speter            cmd++;
419251881Speter          if (!*cmd)
420251881Speter            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
421251881Speter                                     _("Tunnel scheme %s requires environment "
422251881Speter                                       "variable %s to be defined"), tunnel,
423251881Speter                                     var);
424251881Speter        }
425251881Speter    }
426251881Speter  else
427251881Speter    cmd = val;
428251881Speter
429251881Speter  /* Tokenize the command into a list of arguments. */
430251881Speter  status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
431251881Speter  if (status != APR_SUCCESS)
432251881Speter    return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
433251881Speter
434299742Sdim  /* Calc number of the fixed arguments. */
435251881Speter  for (n = 0; cmd_argv[n] != NULL; n++)
436251881Speter    ;
437251881Speter
438299742Sdim  argv = apr_palloc(pool, (n + 4) * sizeof(char *));
439299742Sdim
440299742Sdim  /* Append the fixed arguments to the result. */
441299742Sdim  for (n = 0; cmd_argv[n] != NULL; n++)
442299742Sdim    argv[n] = cmd_argv[n];
443299742Sdim
444299742Sdim  argv[n++] = svn_path_uri_decode(hostinfo, pool);
445299742Sdim  argv[n++] = "svnserve";
446299742Sdim  argv[n++] = "-t";
447299742Sdim  argv[n] = NULL;
448299742Sdim
449299742Sdim  *argv_p = argv;
450251881Speter  return SVN_NO_ERROR;
451251881Speter}
452251881Speter
453251881Speter/* This function handles any errors which occur in the child process
454251881Speter * created for a tunnel agent.  We write the error out as a command
455251881Speter * failure; the code in ra_svn_open() to read the server's greeting
456251881Speter * will see the error and return it to the caller. */
457251881Speterstatic void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
458251881Speter                                       const char *desc)
459251881Speter{
460251881Speter  svn_ra_svn_conn_t *conn;
461251881Speter  apr_file_t *in_file, *out_file;
462299742Sdim  svn_stream_t *in_stream, *out_stream;
463251881Speter  svn_error_t *err;
464251881Speter
465251881Speter  if (apr_file_open_stdin(&in_file, pool)
466251881Speter      || apr_file_open_stdout(&out_file, pool))
467251881Speter    return;
468251881Speter
469299742Sdim  in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool);
470299742Sdim  out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool);
471299742Sdim
472299742Sdim  conn = svn_ra_svn_create_conn4(NULL, in_stream, out_stream,
473251881Speter                                 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
474251881Speter                                 0, pool);
475251881Speter  err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
476251881Speter  svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
477251881Speter  svn_error_clear(err);
478251881Speter  svn_error_clear(svn_ra_svn__flush(conn, pool));
479251881Speter}
480251881Speter
481251881Speter/* (Note: *CONN is an output parameter.) */
482251881Speterstatic svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
483251881Speter                                apr_pool_t *pool)
484251881Speter{
485251881Speter  apr_status_t status;
486251881Speter  apr_proc_t *proc;
487251881Speter  apr_procattr_t *attr;
488251881Speter  svn_error_t *err;
489251881Speter
490251881Speter  status = apr_procattr_create(&attr, pool);
491251881Speter  if (status == APR_SUCCESS)
492251881Speter    status = apr_procattr_io_set(attr, 1, 1, 0);
493251881Speter  if (status == APR_SUCCESS)
494251881Speter    status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
495251881Speter  if (status == APR_SUCCESS)
496251881Speter    status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
497251881Speter  proc = apr_palloc(pool, sizeof(*proc));
498251881Speter  if (status == APR_SUCCESS)
499251881Speter    status = apr_proc_create(proc, *args, args, NULL, attr, pool);
500251881Speter  if (status != APR_SUCCESS)
501251881Speter    return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
502251881Speter                            svn_error_wrap_apr(status,
503251881Speter                                               _("Can't create tunnel")), NULL);
504251881Speter
505251881Speter  /* Arrange for the tunnel agent to get a SIGTERM on pool
506251881Speter   * cleanup.  This is a little extreme, but the alternatives
507251881Speter   * weren't working out.
508251881Speter   *
509251881Speter   * Closing the pipes and waiting for the process to die
510251881Speter   * was prone to mysterious hangs which are difficult to
511251881Speter   * diagnose (e.g. svnserve dumps core due to unrelated bug;
512251881Speter   * sshd goes into zombie state; ssh connection is never
513251881Speter   * closed; ssh never terminates).
514251881Speter   * See also the long dicussion in issue #2580 if you really
515251881Speter   * want to know various reasons for these problems and
516251881Speter   * the different opinions on this issue.
517251881Speter   *
518251881Speter   * On Win32, APR does not support KILL_ONLY_ONCE. It only has
519251881Speter   * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
520251881Speter   * KILL_ALWAYS, which immediately calls TerminateProcess().
521251881Speter   * This instantly kills the tunnel, leaving sshd and svnserve
522251881Speter   * on a remote machine running indefinitely. These processes
523251881Speter   * accumulate. The problem is most often seen with a fast client
524251881Speter   * machine and a modest internet connection, as the tunnel
525251881Speter   * is killed before being able to gracefully complete the
526251881Speter   * session. In that case, svn is unusable 100% of the time on
527251881Speter   * the windows machine. Thus, on Win32, we use KILL_NEVER and
528251881Speter   * take the lesser of two evils.
529251881Speter   */
530251881Speter#ifdef WIN32
531251881Speter  apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
532251881Speter#else
533251881Speter  apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
534251881Speter#endif
535251881Speter
536251881Speter  /* APR pipe objects inherit by default.  But we don't want the
537251881Speter   * tunnel agent's pipes held open by future child processes
538251881Speter   * (such as other ra_svn sessions), so turn that off. */
539251881Speter  apr_file_inherit_unset(proc->in);
540251881Speter  apr_file_inherit_unset(proc->out);
541251881Speter
542251881Speter  /* Guard against dotfile output to stdout on the server. */
543299742Sdim  *conn = svn_ra_svn_create_conn4(NULL,
544299742Sdim                                  svn_stream_from_aprfile2(proc->out, FALSE,
545299742Sdim                                                           pool),
546299742Sdim                                  svn_stream_from_aprfile2(proc->in, FALSE,
547299742Sdim                                                           pool),
548251881Speter                                  SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
549251881Speter                                  0, 0, pool);
550251881Speter  err = svn_ra_svn__skip_leading_garbage(*conn, pool);
551251881Speter  if (err)
552251881Speter    return svn_error_quick_wrap(
553251881Speter             err,
554251881Speter             _("To better debug SSH connection problems, remove the -q "
555251881Speter               "option from 'ssh' in the [tunnels] section of your "
556251881Speter               "Subversion configuration file."));
557251881Speter
558251881Speter  return SVN_NO_ERROR;
559251881Speter}
560251881Speter
561251881Speter/* Parse URL inot URI, validating it and setting the default port if none
562251881Speter   was given.  Allocate the URI fileds out of POOL. */
563251881Speterstatic svn_error_t *parse_url(const char *url, apr_uri_t *uri,
564251881Speter                              apr_pool_t *pool)
565251881Speter{
566251881Speter  apr_status_t apr_err;
567251881Speter
568251881Speter  apr_err = apr_uri_parse(pool, url, uri);
569251881Speter
570251881Speter  if (apr_err != 0)
571251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
572251881Speter                             _("Illegal svn repository URL '%s'"), url);
573251881Speter
574251881Speter  return SVN_NO_ERROR;
575251881Speter}
576251881Speter
577299742Sdim/* This structure is used as a baton for the pool cleanup function to
578299742Sdim   store tunnel parameters used by the close-tunnel callback. */
579299742Sdimstruct tunnel_data_t {
580299742Sdim  void *tunnel_context;
581299742Sdim  void *tunnel_baton;
582299742Sdim  svn_ra_close_tunnel_func_t close_tunnel;
583299742Sdim  svn_stream_t *request;
584299742Sdim  svn_stream_t *response;
585299742Sdim};
586299742Sdim
587299742Sdim/* Pool cleanup function that invokes the close-tunnel callback. */
588299742Sdimstatic apr_status_t close_tunnel_cleanup(void *baton)
589299742Sdim{
590299742Sdim  const struct tunnel_data_t *const td = baton;
591299742Sdim
592299742Sdim  if (td->close_tunnel)
593299742Sdim    td->close_tunnel(td->tunnel_context, td->tunnel_baton);
594299742Sdim
595299742Sdim  svn_error_clear(svn_stream_close(td->request));
596299742Sdim
597299742Sdim  /* We might have one stream to use for both request and response! */
598299742Sdim  if (td->request != td->response)
599299742Sdim    svn_error_clear(svn_stream_close(td->response));
600299742Sdim
601299742Sdim  return APR_SUCCESS; /* ignored */
602299742Sdim}
603299742Sdim
604251881Speter/* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
605251881Speter   URI is a parsed version of URL.  CALLBACKS and CALLBACKS_BATON
606299742Sdim   are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL,
607299742Sdim   it is the name of the tunnel type parsed from the URL scheme.
608299742Sdim   If TUNNEL_ARGV is not NULL, it points to a program argument list to use
609299742Sdim   when invoking the tunnel agent.
610251881Speter*/
611251881Speterstatic svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
612251881Speter                                 const char *url,
613251881Speter                                 const apr_uri_t *uri,
614299742Sdim                                 const char *tunnel_name,
615251881Speter                                 const char **tunnel_argv,
616299742Sdim                                 apr_hash_t *config,
617251881Speter                                 const svn_ra_callbacks2_t *callbacks,
618251881Speter                                 void *callbacks_baton,
619299742Sdim                                 svn_auth_baton_t *auth_baton,
620299742Sdim                                 apr_pool_t *result_pool,
621299742Sdim                                 apr_pool_t *scratch_pool)
622251881Speter{
623251881Speter  svn_ra_svn__session_baton_t *sess;
624251881Speter  svn_ra_svn_conn_t *conn;
625251881Speter  apr_socket_t *sock;
626251881Speter  apr_uint64_t minver, maxver;
627251881Speter  apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
628251881Speter  const char *client_string = NULL;
629299742Sdim  apr_pool_t *pool = result_pool;
630251881Speter
631251881Speter  sess = apr_palloc(pool, sizeof(*sess));
632251881Speter  sess->pool = pool;
633299742Sdim  sess->is_tunneled = (tunnel_name != NULL);
634251881Speter  sess->url = apr_pstrdup(pool, url);
635251881Speter  sess->user = uri->user;
636251881Speter  sess->hostname = uri->hostname;
637299742Sdim  sess->tunnel_name = tunnel_name;
638251881Speter  sess->tunnel_argv = tunnel_argv;
639251881Speter  sess->callbacks = callbacks;
640251881Speter  sess->callbacks_baton = callbacks_baton;
641251881Speter  sess->bytes_read = sess->bytes_written = 0;
642299742Sdim  sess->auth_baton = auth_baton;
643251881Speter
644299742Sdim  if (config)
645299742Sdim    SVN_ERR(svn_config_copy_config(&sess->config, config, pool));
646251881Speter  else
647299742Sdim    sess->config = NULL;
648299742Sdim
649299742Sdim  if (tunnel_name)
650251881Speter    {
651299742Sdim      sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>",
652299742Sdim                                        tunnel_name,
653299742Sdim                                        uri->hostname, uri->port);
654299742Sdim
655299742Sdim      if (tunnel_argv)
656299742Sdim        SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
657299742Sdim      else
658299742Sdim        {
659299742Sdim          struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td));
660299742Sdim
661299742Sdim          td->tunnel_baton = callbacks->tunnel_baton;
662299742Sdim          td->close_tunnel = NULL;
663299742Sdim
664299742Sdim          SVN_ERR(callbacks->open_tunnel_func(
665299742Sdim                      &td->request, &td->response,
666299742Sdim                      &td->close_tunnel, &td->tunnel_context,
667299742Sdim                      callbacks->tunnel_baton, tunnel_name,
668299742Sdim                      uri->user, uri->hostname, uri->port,
669299742Sdim                      callbacks->cancel_func, callbacks_baton,
670299742Sdim                      pool));
671299742Sdim
672299742Sdim          apr_pool_cleanup_register(pool, td, close_tunnel_cleanup,
673299742Sdim                                    apr_pool_cleanup_null);
674299742Sdim
675299742Sdim          conn = svn_ra_svn_create_conn4(NULL, td->response, td->request,
676299742Sdim                                         SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
677299742Sdim                                         0, 0, pool);
678299742Sdim          SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool));
679299742Sdim        }
680299742Sdim    }
681299742Sdim  else
682299742Sdim    {
683299742Sdim      sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
684299742Sdim                                        uri->port ? uri->port : SVN_RA_SVN_PORT);
685299742Sdim
686299742Sdim      SVN_ERR(make_connection(uri->hostname,
687299742Sdim                              uri->port ? uri->port : SVN_RA_SVN_PORT,
688299742Sdim                              &sock, pool));
689299742Sdim      conn = svn_ra_svn_create_conn4(sock, NULL, NULL,
690251881Speter                                     SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
691251881Speter                                     0, 0, pool);
692251881Speter    }
693251881Speter
694251881Speter  /* Build the useragent string, querying the client for any
695251881Speter     customizations it wishes to note.  For historical reasons, we
696251881Speter     still deliver the hard-coded client version info
697251881Speter     (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
698251881Speter     separately in the protocol/capabilities handshake below.  But the
699251881Speter     commit logic wants the combined form for use with the
700251881Speter     SVN_PROP_TXN_USER_AGENT ephemeral property because that's
701251881Speter     consistent with our DAV approach.  */
702251881Speter  if (sess->callbacks->get_client_string != NULL)
703251881Speter    SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
704251881Speter                                               &client_string, pool));
705251881Speter  if (client_string)
706251881Speter    sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
707299742Sdim                                  client_string, SVN_VA_NULL);
708251881Speter  else
709251881Speter    sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
710251881Speter
711251881Speter  /* Make sure we set conn->session before reading from it,
712251881Speter   * because the reader and writer functions expect a non-NULL value. */
713251881Speter  sess->conn = conn;
714251881Speter  conn->session = sess;
715251881Speter
716251881Speter  /* Read server's greeting. */
717251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
718251881Speter                                        &mechlist, &server_caplist));
719251881Speter
720251881Speter  /* We support protocol version 2. */
721251881Speter  if (minver > 2)
722251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
723251881Speter                             _("Server requires minimum version %d"),
724251881Speter                             (int) minver);
725251881Speter  if (maxver < 2)
726251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
727251881Speter                             _("Server only supports versions up to %d"),
728251881Speter                             (int) maxver);
729251881Speter  SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
730251881Speter
731251881Speter  /* All released versions of Subversion support edit-pipeline,
732251881Speter   * so we do not support servers that do not. */
733251881Speter  if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
734251881Speter    return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
735251881Speter                            _("Server does not support edit pipelining"));
736251881Speter
737251881Speter  /* In protocol version 2, we send back our protocol version, our
738251881Speter   * capability list, and the URL, and subsequently there is an auth
739251881Speter   * request. */
740251881Speter  /* Client-side capabilities list: */
741251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
742251881Speter                                  (apr_uint64_t) 2,
743251881Speter                                  SVN_RA_SVN_CAP_EDIT_PIPELINE,
744251881Speter                                  SVN_RA_SVN_CAP_SVNDIFF1,
745251881Speter                                  SVN_RA_SVN_CAP_ABSENT_ENTRIES,
746251881Speter                                  SVN_RA_SVN_CAP_DEPTH,
747251881Speter                                  SVN_RA_SVN_CAP_MERGEINFO,
748251881Speter                                  SVN_RA_SVN_CAP_LOG_REVPROPS,
749251881Speter                                  url,
750251881Speter                                  SVN_RA_SVN__DEFAULT_USERAGENT,
751251881Speter                                  client_string));
752251881Speter  SVN_ERR(handle_auth_request(sess, pool));
753251881Speter
754251881Speter  /* This is where the security layer would go into effect if we
755251881Speter   * supported security layers, which is a ways off. */
756251881Speter
757251881Speter  /* Read the repository's uuid and root URL, and perhaps learn more
758251881Speter     capabilities that weren't available before now. */
759251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
760251881Speter                                        &conn->repos_root, &repos_caplist));
761251881Speter  if (repos_caplist)
762251881Speter    SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
763251881Speter
764251881Speter  if (conn->repos_root)
765251881Speter    {
766251881Speter      conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
767251881Speter      /* We should check that the returned string is a prefix of url, since
768251881Speter         that's the API guarantee, but this isn't true for 1.0 servers.
769251881Speter         Checking the length prevents client crashes. */
770251881Speter      if (strlen(conn->repos_root) > strlen(url))
771251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
772251881Speter                                _("Impossibly long repository root from "
773251881Speter                                  "server"));
774251881Speter    }
775251881Speter
776251881Speter  *sess_p = sess;
777251881Speter
778251881Speter  return SVN_NO_ERROR;
779251881Speter}
780251881Speter
781251881Speter
782251881Speter#ifdef SVN_HAVE_SASL
783251881Speter#define RA_SVN_DESCRIPTION \
784251881Speter  N_("Module for accessing a repository using the svn network protocol.\n" \
785251881Speter     "  - with Cyrus SASL authentication")
786251881Speter#else
787251881Speter#define RA_SVN_DESCRIPTION \
788251881Speter  N_("Module for accessing a repository using the svn network protocol.")
789251881Speter#endif
790251881Speter
791262253Speterstatic const char *ra_svn_get_description(apr_pool_t *pool)
792251881Speter{
793251881Speter  return _(RA_SVN_DESCRIPTION);
794251881Speter}
795251881Speter
796251881Speterstatic const char * const *
797251881Speterra_svn_get_schemes(apr_pool_t *pool)
798251881Speter{
799251881Speter  static const char *schemes[] = { "svn", NULL };
800251881Speter
801251881Speter  return schemes;
802251881Speter}
803251881Speter
804251881Speter
805251881Speter
806251881Speterstatic svn_error_t *ra_svn_open(svn_ra_session_t *session,
807251881Speter                                const char **corrected_url,
808251881Speter                                const char *url,
809251881Speter                                const svn_ra_callbacks2_t *callbacks,
810251881Speter                                void *callback_baton,
811299742Sdim                                svn_auth_baton_t *auth_baton,
812251881Speter                                apr_hash_t *config,
813299742Sdim                                apr_pool_t *result_pool,
814299742Sdim                                apr_pool_t *scratch_pool)
815251881Speter{
816299742Sdim  apr_pool_t *sess_pool = svn_pool_create(result_pool);
817251881Speter  svn_ra_svn__session_baton_t *sess;
818251881Speter  const char *tunnel, **tunnel_argv;
819251881Speter  apr_uri_t uri;
820251881Speter  svn_config_t *cfg, *cfg_client;
821251881Speter
822251881Speter  /* We don't support server-prescribed redirections in ra-svn. */
823251881Speter  if (corrected_url)
824251881Speter    *corrected_url = NULL;
825251881Speter
826251881Speter  SVN_ERR(parse_url(url, &uri, sess_pool));
827251881Speter
828299742Sdim  parse_tunnel(url, &tunnel, result_pool);
829251881Speter
830299742Sdim  /* Use the default tunnel implementation if we got a tunnel name,
831299742Sdim     but either do not have tunnel handler callbacks installed, or
832299742Sdim     the handlers don't like the tunnel name. */
833299742Sdim  if (tunnel
834299742Sdim      && (!callbacks->open_tunnel_func
835299742Sdim          || (callbacks->check_tunnel_func && callbacks->open_tunnel_func
836299742Sdim              && !callbacks->check_tunnel_func(callbacks->tunnel_baton,
837299742Sdim                                               tunnel))))
838251881Speter    SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
839299742Sdim                              result_pool));
840251881Speter  else
841251881Speter    tunnel_argv = NULL;
842251881Speter
843251881Speter  cfg_client = config
844251881Speter               ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
845251881Speter               : NULL;
846251881Speter  cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
847299742Sdim  svn_auth_set_parameter(auth_baton,
848251881Speter                         SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
849299742Sdim  svn_auth_set_parameter(auth_baton,
850251881Speter                         SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
851251881Speter
852251881Speter  /* We open the session in a subpool so we can get rid of it if we
853251881Speter     reparent with a server that doesn't support reparenting. */
854299742Sdim  SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config,
855299742Sdim                       callbacks, callback_baton,
856299742Sdim                       auth_baton, sess_pool, scratch_pool));
857251881Speter  session->priv = sess;
858251881Speter
859251881Speter  return SVN_NO_ERROR;
860251881Speter}
861251881Speter
862299742Sdimstatic svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session,
863299742Sdim                                       svn_ra_session_t *old_session,
864299742Sdim                                       const char *new_session_url,
865299742Sdim                                       apr_pool_t *result_pool,
866299742Sdim                                       apr_pool_t *scratch_pool)
867299742Sdim{
868299742Sdim  svn_ra_svn__session_baton_t *old_sess = old_session->priv;
869299742Sdim
870299742Sdim  SVN_ERR(ra_svn_open(new_session, NULL, new_session_url,
871299742Sdim                      old_sess->callbacks, old_sess->callbacks_baton,
872299742Sdim                      old_sess->auth_baton, old_sess->config,
873299742Sdim                      result_pool, scratch_pool));
874299742Sdim
875299742Sdim  return SVN_NO_ERROR;
876299742Sdim}
877299742Sdim
878251881Speterstatic svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
879251881Speter                                    const char *url,
880251881Speter                                    apr_pool_t *pool)
881251881Speter{
882251881Speter  svn_ra_svn__session_baton_t *sess = ra_session->priv;
883251881Speter  svn_ra_svn_conn_t *conn = sess->conn;
884251881Speter  svn_error_t *err;
885251881Speter  apr_pool_t *sess_pool;
886251881Speter  svn_ra_svn__session_baton_t *new_sess;
887251881Speter  apr_uri_t uri;
888251881Speter
889251881Speter  SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
890251881Speter  err = handle_auth_request(sess, pool);
891251881Speter  if (! err)
892251881Speter    {
893251881Speter      SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
894251881Speter      sess->url = apr_pstrdup(sess->pool, url);
895251881Speter      return SVN_NO_ERROR;
896251881Speter    }
897251881Speter  else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
898251881Speter    return err;
899251881Speter
900251881Speter  /* Servers before 1.4 doesn't support this command; try to reconnect
901251881Speter     instead. */
902251881Speter  svn_error_clear(err);
903251881Speter  /* Create a new subpool of the RA session pool. */
904251881Speter  sess_pool = svn_pool_create(ra_session->pool);
905251881Speter  err = parse_url(url, &uri, sess_pool);
906251881Speter  if (! err)
907299742Sdim    err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv,
908299742Sdim                       sess->config, sess->callbacks, sess->callbacks_baton,
909299742Sdim                       sess->auth_baton, sess_pool, sess_pool);
910251881Speter  /* We destroy the new session pool on error, since it is allocated in
911251881Speter     the main session pool. */
912251881Speter  if (err)
913251881Speter    {
914251881Speter      svn_pool_destroy(sess_pool);
915251881Speter      return err;
916251881Speter    }
917251881Speter
918251881Speter  /* We have a new connection, assign it and destroy the old. */
919251881Speter  ra_session->priv = new_sess;
920251881Speter  svn_pool_destroy(sess->pool);
921251881Speter
922251881Speter  return SVN_NO_ERROR;
923251881Speter}
924251881Speter
925251881Speterstatic svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
926251881Speter                                           const char **url, apr_pool_t *pool)
927251881Speter{
928251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
929251881Speter  *url = apr_pstrdup(pool, sess->url);
930251881Speter  return SVN_NO_ERROR;
931251881Speter}
932251881Speter
933251881Speterstatic svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
934251881Speter                                          svn_revnum_t *rev, apr_pool_t *pool)
935251881Speter{
936251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
937251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
938251881Speter
939251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
940251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
941251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
942251881Speter  return SVN_NO_ERROR;
943251881Speter}
944251881Speter
945251881Speterstatic svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
946251881Speter                                         svn_revnum_t *rev, apr_time_t tm,
947251881Speter                                         apr_pool_t *pool)
948251881Speter{
949251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
950251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
951251881Speter
952251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
953251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
954251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
955251881Speter  return SVN_NO_ERROR;
956251881Speter}
957251881Speter
958251881Speter/* Forward declaration. */
959251881Speterstatic svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
960251881Speter                                          svn_boolean_t *has,
961251881Speter                                          const char *capability,
962251881Speter                                          apr_pool_t *pool);
963251881Speter
964251881Speterstatic svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
965251881Speter                                           const char *name,
966251881Speter                                           const svn_string_t *const *old_value_p,
967251881Speter                                           const svn_string_t *value,
968251881Speter                                           apr_pool_t *pool)
969251881Speter{
970251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
971251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
972251881Speter  svn_boolean_t dont_care;
973251881Speter  const svn_string_t *old_value;
974251881Speter  svn_boolean_t has_atomic_revprops;
975251881Speter
976251881Speter  SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
977251881Speter                                SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
978251881Speter                                pool));
979251881Speter
980251881Speter  if (old_value_p)
981251881Speter    {
982251881Speter      /* How did you get past the same check in svn_ra_change_rev_prop2()? */
983251881Speter      SVN_ERR_ASSERT(has_atomic_revprops);
984251881Speter
985251881Speter      dont_care = FALSE;
986251881Speter      old_value = *old_value_p;
987251881Speter    }
988251881Speter  else
989251881Speter    {
990251881Speter      dont_care = TRUE;
991251881Speter      old_value = NULL;
992251881Speter    }
993251881Speter
994251881Speter  if (has_atomic_revprops)
995251881Speter    SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
996251881Speter                                                   value, dont_care,
997251881Speter                                                   old_value));
998251881Speter  else
999251881Speter    SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
1000251881Speter                                                  value));
1001251881Speter
1002251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1003251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1004251881Speter  return SVN_NO_ERROR;
1005251881Speter}
1006251881Speter
1007251881Speterstatic svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
1008251881Speter                                    apr_pool_t *pool)
1009251881Speter{
1010251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1011251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1012251881Speter
1013251881Speter  *uuid = conn->uuid;
1014251881Speter  return SVN_NO_ERROR;
1015251881Speter}
1016251881Speter
1017251881Speterstatic svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
1018251881Speter                                          apr_pool_t *pool)
1019251881Speter{
1020251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1021251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1022251881Speter
1023251881Speter  if (!conn->repos_root)
1024251881Speter    return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
1025251881Speter                            _("Server did not send repository root"));
1026251881Speter  *url = conn->repos_root;
1027251881Speter  return SVN_NO_ERROR;
1028251881Speter}
1029251881Speter
1030251881Speterstatic svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
1031251881Speter                                        apr_hash_t **props, apr_pool_t *pool)
1032251881Speter{
1033251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1034251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1035251881Speter  apr_array_header_t *proplist;
1036251881Speter
1037251881Speter  SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
1038251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1039251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
1040251881Speter  SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1041251881Speter  return SVN_NO_ERROR;
1042251881Speter}
1043251881Speter
1044251881Speterstatic svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
1045251881Speter                                    const char *name,
1046251881Speter                                    svn_string_t **value, apr_pool_t *pool)
1047251881Speter{
1048251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1049251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1050251881Speter
1051251881Speter  SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
1052251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1053251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
1054251881Speter  return SVN_NO_ERROR;
1055251881Speter}
1056251881Speter
1057251881Speterstatic svn_error_t *ra_svn_end_commit(void *baton)
1058251881Speter{
1059251881Speter  ra_svn_commit_callback_baton_t *ccb = baton;
1060251881Speter  svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
1061251881Speter
1062251881Speter  SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
1063251881Speter  SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
1064251881Speter                                 "r(?c)(?c)?(?c)",
1065251881Speter                                 &(commit_info->revision),
1066251881Speter                                 &(commit_info->date),
1067251881Speter                                 &(commit_info->author),
1068251881Speter                                 &(commit_info->post_commit_err)));
1069251881Speter
1070299742Sdim  commit_info->repos_root = apr_pstrdup(ccb->pool,
1071299742Sdim                                        ccb->sess_baton->conn->repos_root);
1072299742Sdim
1073251881Speter  if (ccb->callback)
1074251881Speter    SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
1075251881Speter
1076251881Speter  return SVN_NO_ERROR;
1077251881Speter}
1078251881Speter
1079251881Speterstatic svn_error_t *ra_svn_commit(svn_ra_session_t *session,
1080251881Speter                                  const svn_delta_editor_t **editor,
1081251881Speter                                  void **edit_baton,
1082251881Speter                                  apr_hash_t *revprop_table,
1083251881Speter                                  svn_commit_callback2_t callback,
1084251881Speter                                  void *callback_baton,
1085251881Speter                                  apr_hash_t *lock_tokens,
1086251881Speter                                  svn_boolean_t keep_locks,
1087251881Speter                                  apr_pool_t *pool)
1088251881Speter{
1089251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1090251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1091251881Speter  ra_svn_commit_callback_baton_t *ccb;
1092251881Speter  apr_hash_index_t *hi;
1093251881Speter  apr_pool_t *iterpool;
1094251881Speter  const svn_string_t *log_msg = svn_hash_gets(revprop_table,
1095251881Speter                                              SVN_PROP_REVISION_LOG);
1096251881Speter
1097253734Speter  if (log_msg == NULL &&
1098253734Speter      ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1099253734Speter    {
1100253734Speter      return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
1101253734Speter                               _("ra_svn does not support not specifying "
1102253734Speter                                 "a log message with pre-1.5 servers; "
1103253734Speter                                 "consider passing an empty one, or upgrading "
1104253734Speter                                 "the server"));
1105299742Sdim    }
1106253734Speter  else if (log_msg == NULL)
1107253734Speter    /* 1.5+ server.  Set LOG_MSG to something, since the 'logmsg' argument
1108253734Speter       to the 'commit' protocol command is non-optional; on the server side,
1109299742Sdim       only REVPROP_TABLE will be used, and LOG_MSG will be ignored.  The
1110253734Speter       "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
1111253734Speter       will have a NULL log message (not just "", really NULL).
1112253734Speter
1113253734Speter       svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
1114253734Speter       present; this was elevated to a protocol promise in r1498550 (and
1115253734Speter       later documented in this comment) in order to fix the segmentation
1116253734Speter       fault bug described in the log message of r1498550.*/
1117253734Speter    log_msg = svn_string_create("", pool);
1118253734Speter
1119251881Speter  /* If we're sending revprops other than svn:log, make sure the server won't
1120251881Speter     silently ignore them. */
1121251881Speter  if (apr_hash_count(revprop_table) > 1 &&
1122251881Speter      ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1123251881Speter    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1124251881Speter                            _("Server doesn't support setting arbitrary "
1125251881Speter                              "revision properties during commit"));
1126251881Speter
1127251881Speter  /* If the server supports ephemeral txnprops, add the one that
1128251881Speter     reports the client's version level string. */
1129251881Speter  if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1130251881Speter      svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1131251881Speter    {
1132251881Speter      svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1133251881Speter                    svn_string_create(SVN_VER_NUMBER, pool));
1134251881Speter      svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1135251881Speter                    svn_string_create(sess_baton->useragent, pool));
1136251881Speter    }
1137251881Speter
1138251881Speter  /* Tell the server we're starting the commit.
1139251881Speter     Send log message here for backwards compatibility with servers
1140251881Speter     before 1.5. */
1141251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1142251881Speter                                  log_msg->data));
1143251881Speter  if (lock_tokens)
1144251881Speter    {
1145251881Speter      iterpool = svn_pool_create(pool);
1146251881Speter      for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1147251881Speter        {
1148251881Speter          const void *key;
1149251881Speter          void *val;
1150251881Speter          const char *path, *token;
1151251881Speter
1152251881Speter          svn_pool_clear(iterpool);
1153251881Speter          apr_hash_this(hi, &key, NULL, &val);
1154251881Speter          path = key;
1155251881Speter          token = val;
1156251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1157251881Speter        }
1158251881Speter      svn_pool_destroy(iterpool);
1159251881Speter    }
1160251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1161251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1162251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1163251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1164251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1165251881Speter
1166251881Speter  /* Remember a few arguments for when the commit is over. */
1167251881Speter  ccb = apr_palloc(pool, sizeof(*ccb));
1168251881Speter  ccb->sess_baton = sess_baton;
1169251881Speter  ccb->pool = pool;
1170251881Speter  ccb->new_rev = NULL;
1171251881Speter  ccb->callback = callback;
1172251881Speter  ccb->callback_baton = callback_baton;
1173251881Speter
1174251881Speter  /* Fetch an editor for the caller to drive.  The editor will call
1175251881Speter   * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1176251881Speter   * in the new_rev, committed_date, and committed_author values. */
1177251881Speter  svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1178251881Speter                        ra_svn_end_commit, ccb);
1179251881Speter  return SVN_NO_ERROR;
1180251881Speter}
1181251881Speter
1182251881Speter/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1183251881Speter   const char * repos relative paths and properties for those paths, storing
1184251881Speter   the result as an array of svn_prop_inherited_item_t *items. */
1185251881Speterstatic svn_error_t *
1186251881Speterparse_iproplist(apr_array_header_t **inherited_props,
1187251881Speter                const apr_array_header_t *iproplist,
1188251881Speter                svn_ra_session_t *session,
1189251881Speter                apr_pool_t *result_pool,
1190251881Speter                apr_pool_t *scratch_pool)
1191251881Speter
1192251881Speter{
1193251881Speter  int i;
1194251881Speter  apr_pool_t *iterpool;
1195251881Speter
1196251881Speter  if (iproplist == NULL)
1197251881Speter    {
1198251881Speter      /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1199251881Speter         capability we shouldn't be asking for inherited props, but if we
1200251881Speter         did and the server sent back nothing then we'll want to handle
1201251881Speter         that. */
1202251881Speter      *inherited_props = NULL;
1203251881Speter      return SVN_NO_ERROR;
1204251881Speter    }
1205251881Speter
1206251881Speter  *inherited_props = apr_array_make(
1207251881Speter    result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1208251881Speter
1209251881Speter  iterpool = svn_pool_create(scratch_pool);
1210251881Speter
1211251881Speter  for (i = 0; i < iproplist->nelts; i++)
1212251881Speter    {
1213251881Speter      apr_array_header_t *iprop_list;
1214251881Speter      char *parent_rel_path;
1215251881Speter      apr_hash_t *iprops;
1216251881Speter      apr_hash_index_t *hi;
1217251881Speter      svn_prop_inherited_item_t *new_iprop =
1218251881Speter        apr_palloc(result_pool, sizeof(*new_iprop));
1219251881Speter      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1220251881Speter                                              svn_ra_svn_item_t);
1221251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
1222251881Speter        return svn_error_create(
1223251881Speter          SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1224251881Speter          _("Inherited proplist element not a list"));
1225251881Speter
1226251881Speter      svn_pool_clear(iterpool);
1227251881Speter
1228251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1229251881Speter                                      &parent_rel_path, &iprop_list));
1230251881Speter      SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1231299742Sdim      new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path);
1232299742Sdim      new_iprop->prop_hash = svn_hash__make(result_pool);
1233251881Speter      for (hi = apr_hash_first(iterpool, iprops);
1234251881Speter           hi;
1235251881Speter           hi = apr_hash_next(hi))
1236251881Speter        {
1237299742Sdim          const char *name = apr_hash_this_key(hi);
1238299742Sdim          svn_string_t *value = apr_hash_this_val(hi);
1239251881Speter          svn_hash_sets(new_iprop->prop_hash,
1240251881Speter                        apr_pstrdup(result_pool, name),
1241251881Speter                        svn_string_dup(value, result_pool));
1242251881Speter        }
1243251881Speter      APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1244251881Speter        new_iprop;
1245251881Speter    }
1246251881Speter  svn_pool_destroy(iterpool);
1247251881Speter  return SVN_NO_ERROR;
1248251881Speter}
1249251881Speter
1250251881Speterstatic svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1251251881Speter                                    svn_revnum_t rev, svn_stream_t *stream,
1252251881Speter                                    svn_revnum_t *fetched_rev,
1253251881Speter                                    apr_hash_t **props,
1254251881Speter                                    apr_pool_t *pool)
1255251881Speter{
1256251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1257251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1258251881Speter  apr_array_header_t *proplist;
1259251881Speter  const char *expected_digest;
1260251881Speter  svn_checksum_t *expected_checksum = NULL;
1261251881Speter  svn_checksum_ctx_t *checksum_ctx;
1262251881Speter  apr_pool_t *iterpool;
1263251881Speter
1264251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1265251881Speter                                         (props != NULL), (stream != NULL)));
1266251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1267251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1268251881Speter                                        &expected_digest,
1269251881Speter                                        &rev, &proplist));
1270251881Speter
1271251881Speter  if (fetched_rev)
1272251881Speter    *fetched_rev = rev;
1273251881Speter  if (props)
1274251881Speter    SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1275251881Speter
1276251881Speter  /* We're done if the contents weren't wanted. */
1277251881Speter  if (!stream)
1278251881Speter    return SVN_NO_ERROR;
1279251881Speter
1280251881Speter  if (expected_digest)
1281251881Speter    {
1282251881Speter      SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1283251881Speter                                     expected_digest, pool));
1284251881Speter      checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1285251881Speter    }
1286251881Speter
1287251881Speter  /* Read the file's contents. */
1288251881Speter  iterpool = svn_pool_create(pool);
1289251881Speter  while (1)
1290251881Speter    {
1291251881Speter      svn_ra_svn_item_t *item;
1292251881Speter
1293251881Speter      svn_pool_clear(iterpool);
1294251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1295251881Speter      if (item->kind != SVN_RA_SVN_STRING)
1296251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1297251881Speter                                _("Non-string as part of file contents"));
1298251881Speter      if (item->u.string->len == 0)
1299251881Speter        break;
1300251881Speter
1301251881Speter      if (expected_checksum)
1302251881Speter        SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1303251881Speter                                    item->u.string->len));
1304251881Speter
1305251881Speter      SVN_ERR(svn_stream_write(stream, item->u.string->data,
1306251881Speter                               &item->u.string->len));
1307251881Speter    }
1308251881Speter  svn_pool_destroy(iterpool);
1309251881Speter
1310251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1311251881Speter
1312251881Speter  if (expected_checksum)
1313251881Speter    {
1314251881Speter      svn_checksum_t *checksum;
1315251881Speter
1316251881Speter      SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1317251881Speter      if (!svn_checksum_match(checksum, expected_checksum))
1318251881Speter        return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1319251881Speter                                         _("Checksum mismatch for '%s'"),
1320251881Speter                                         path);
1321251881Speter    }
1322251881Speter
1323251881Speter  return SVN_NO_ERROR;
1324251881Speter}
1325251881Speter
1326251881Speterstatic svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1327251881Speter                                   apr_hash_t **dirents,
1328251881Speter                                   svn_revnum_t *fetched_rev,
1329251881Speter                                   apr_hash_t **props,
1330251881Speter                                   const char *path,
1331251881Speter                                   svn_revnum_t rev,
1332251881Speter                                   apr_uint32_t dirent_fields,
1333251881Speter                                   apr_pool_t *pool)
1334251881Speter{
1335251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1336251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1337251881Speter  apr_array_header_t *proplist, *dirlist;
1338251881Speter  int i;
1339251881Speter
1340251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1341251881Speter                                  rev, (props != NULL), (dirents != NULL)));
1342251881Speter  if (dirent_fields & SVN_DIRENT_KIND)
1343251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1344251881Speter  if (dirent_fields & SVN_DIRENT_SIZE)
1345251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1346251881Speter  if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1347251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1348251881Speter  if (dirent_fields & SVN_DIRENT_CREATED_REV)
1349251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1350251881Speter  if (dirent_fields & SVN_DIRENT_TIME)
1351251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1352251881Speter  if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1353251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1354251881Speter
1355299742Sdim  /* Always send the, nominally optional, want-iprops as "false" to
1356299742Sdim     workaround a bug in svnserve 1.8.0-1.8.8 that causes the server
1357299742Sdim     to see "true" if it is omitted. */
1358299742Sdim  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE));
1359251881Speter
1360251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1361251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1362251881Speter                                        &dirlist));
1363251881Speter
1364251881Speter  if (fetched_rev)
1365251881Speter    *fetched_rev = rev;
1366251881Speter  if (props)
1367251881Speter    SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1368251881Speter
1369251881Speter  /* We're done if dirents aren't wanted. */
1370251881Speter  if (!dirents)
1371251881Speter    return SVN_NO_ERROR;
1372251881Speter
1373251881Speter  /* Interpret the directory list. */
1374299742Sdim  *dirents = svn_hash__make(pool);
1375251881Speter  for (i = 0; i < dirlist->nelts; i++)
1376251881Speter    {
1377251881Speter      const char *name, *kind, *cdate, *cauthor;
1378251881Speter      svn_boolean_t has_props;
1379251881Speter      svn_dirent_t *dirent;
1380251881Speter      apr_uint64_t size;
1381251881Speter      svn_revnum_t crev;
1382251881Speter      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1383251881Speter
1384251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
1385251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1386251881Speter                                _("Dirlist element not a list"));
1387251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1388251881Speter                                      &name, &kind, &size, &has_props,
1389251881Speter                                      &crev, &cdate, &cauthor));
1390299742Sdim
1391299742Sdim      /* Nothing to sanitize here.  Any multi-segment path is simply
1392299742Sdim         illegal in the hash returned by svn_ra_get_dir2. */
1393299742Sdim      if (strchr(name, '/'))
1394299742Sdim        return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1395299742Sdim                                 _("Invalid directory entry name '%s'"),
1396299742Sdim                                 name);
1397299742Sdim
1398251881Speter      dirent = svn_dirent_create(pool);
1399251881Speter      dirent->kind = svn_node_kind_from_word(kind);
1400251881Speter      dirent->size = size;/* FIXME: svn_filesize_t */
1401251881Speter      dirent->has_props = has_props;
1402251881Speter      dirent->created_rev = crev;
1403251881Speter      /* NOTE: the tuple's format string says CDATE may be NULL. But this
1404251881Speter         function does not allow that. The server has always sent us some
1405251881Speter         random date, however, so this just happens to work. But let's
1406251881Speter         be wary of servers that are (improperly) fixed to send NULL.
1407251881Speter
1408251881Speter         Note: they should NOT be "fixed" to send NULL, as that would break
1409251881Speter         any older clients which received that NULL. But we may as well
1410251881Speter         be defensive against a malicous server.  */
1411251881Speter      if (cdate == NULL)
1412251881Speter        dirent->time = 0;
1413251881Speter      else
1414251881Speter        SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1415251881Speter      dirent->last_author = cauthor;
1416251881Speter      svn_hash_sets(*dirents, name, dirent);
1417251881Speter    }
1418251881Speter
1419251881Speter  return SVN_NO_ERROR;
1420251881Speter}
1421251881Speter
1422251881Speter/* Converts a apr_uint64_t with values TRUE, FALSE or
1423251881Speter   SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1424251881Speter   to a svn_tristate_t */
1425251881Speterstatic svn_tristate_t
1426251881Speteroptbool_to_tristate(apr_uint64_t v)
1427251881Speter{
1428251881Speter  if (v == TRUE)  /* not just non-zero but exactly equal to 'TRUE' */
1429251881Speter    return svn_tristate_true;
1430251881Speter  if (v == FALSE)
1431251881Speter    return svn_tristate_false;
1432251881Speter
1433251881Speter  return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1434251881Speter}
1435251881Speter
1436251881Speter/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1437251881Speter   server, which defaults to youngest. */
1438251881Speterstatic svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1439251881Speter                                         svn_mergeinfo_catalog_t *catalog,
1440251881Speter                                         const apr_array_header_t *paths,
1441251881Speter                                         svn_revnum_t revision,
1442251881Speter                                         svn_mergeinfo_inheritance_t inherit,
1443251881Speter                                         svn_boolean_t include_descendants,
1444251881Speter                                         apr_pool_t *pool)
1445251881Speter{
1446251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1447251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1448251881Speter  int i;
1449251881Speter  apr_array_header_t *mergeinfo_tuple;
1450251881Speter  svn_ra_svn_item_t *elt;
1451251881Speter  const char *path;
1452251881Speter
1453251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1454251881Speter  for (i = 0; i < paths->nelts; i++)
1455251881Speter    {
1456251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
1457251881Speter      SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1458251881Speter    }
1459251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1460251881Speter                                  svn_inheritance_to_word(inherit),
1461251881Speter                                  include_descendants));
1462251881Speter
1463251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1464251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1465251881Speter
1466251881Speter  *catalog = NULL;
1467251881Speter  if (mergeinfo_tuple->nelts > 0)
1468251881Speter    {
1469299742Sdim      *catalog = svn_hash__make(pool);
1470251881Speter      for (i = 0; i < mergeinfo_tuple->nelts; i++)
1471251881Speter        {
1472251881Speter          svn_mergeinfo_t for_path;
1473251881Speter          const char *to_parse;
1474251881Speter
1475251881Speter          elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1476251881Speter          if (elt->kind != SVN_RA_SVN_LIST)
1477251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1478251881Speter                                    _("Mergeinfo element is not a list"));
1479251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1480251881Speter                                          &path, &to_parse));
1481251881Speter          SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1482251881Speter          /* Correct for naughty servers that send "relative" paths
1483251881Speter             with leading slashes! */
1484251881Speter          svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1485251881Speter        }
1486251881Speter    }
1487251881Speter
1488251881Speter  return SVN_NO_ERROR;
1489251881Speter}
1490251881Speter
1491251881Speterstatic svn_error_t *ra_svn_update(svn_ra_session_t *session,
1492251881Speter                                  const svn_ra_reporter3_t **reporter,
1493251881Speter                                  void **report_baton, svn_revnum_t rev,
1494251881Speter                                  const char *target, svn_depth_t depth,
1495251881Speter                                  svn_boolean_t send_copyfrom_args,
1496251881Speter                                  svn_boolean_t ignore_ancestry,
1497251881Speter                                  const svn_delta_editor_t *update_editor,
1498251881Speter                                  void *update_baton,
1499251881Speter                                  apr_pool_t *pool,
1500251881Speter                                  apr_pool_t *scratch_pool)
1501251881Speter{
1502251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1503251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1504251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1505251881Speter
1506251881Speter  /* Tell the server we want to start an update. */
1507251881Speter  SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1508251881Speter                                       depth, send_copyfrom_args,
1509251881Speter                                       ignore_ancestry));
1510251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1511251881Speter
1512251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1513251881Speter   * update_editor upon finish_report(). */
1514251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1515251881Speter                              target, depth, reporter, report_baton));
1516251881Speter  return SVN_NO_ERROR;
1517251881Speter}
1518251881Speter
1519251881Speterstatic svn_error_t *
1520251881Speterra_svn_switch(svn_ra_session_t *session,
1521251881Speter              const svn_ra_reporter3_t **reporter,
1522251881Speter              void **report_baton, svn_revnum_t rev,
1523251881Speter              const char *target, svn_depth_t depth,
1524251881Speter              const char *switch_url,
1525251881Speter              svn_boolean_t send_copyfrom_args,
1526251881Speter              svn_boolean_t ignore_ancestry,
1527251881Speter              const svn_delta_editor_t *update_editor,
1528251881Speter              void *update_baton,
1529251881Speter              apr_pool_t *result_pool,
1530251881Speter              apr_pool_t *scratch_pool)
1531251881Speter{
1532251881Speter  apr_pool_t *pool = result_pool;
1533251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1534251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1535251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1536251881Speter
1537251881Speter  /* Tell the server we want to start a switch. */
1538251881Speter  SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1539251881Speter                                       switch_url, depth,
1540251881Speter                                       send_copyfrom_args, ignore_ancestry));
1541251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1542251881Speter
1543251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1544251881Speter   * update_editor upon finish_report(). */
1545251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1546251881Speter                              target, depth, reporter, report_baton));
1547251881Speter  return SVN_NO_ERROR;
1548251881Speter}
1549251881Speter
1550251881Speterstatic svn_error_t *ra_svn_status(svn_ra_session_t *session,
1551251881Speter                                  const svn_ra_reporter3_t **reporter,
1552251881Speter                                  void **report_baton,
1553251881Speter                                  const char *target, svn_revnum_t rev,
1554251881Speter                                  svn_depth_t depth,
1555251881Speter                                  const svn_delta_editor_t *status_editor,
1556251881Speter                                  void *status_baton, apr_pool_t *pool)
1557251881Speter{
1558251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1559251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1560251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1561251881Speter
1562251881Speter  /* Tell the server we want to start a status operation. */
1563251881Speter  SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1564251881Speter                                       depth));
1565251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1566251881Speter
1567251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1568251881Speter   * status_editor upon finish_report(). */
1569251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1570251881Speter                              target, depth, reporter, report_baton));
1571251881Speter  return SVN_NO_ERROR;
1572251881Speter}
1573251881Speter
1574251881Speterstatic svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1575251881Speter                                const svn_ra_reporter3_t **reporter,
1576251881Speter                                void **report_baton,
1577251881Speter                                svn_revnum_t rev, const char *target,
1578251881Speter                                svn_depth_t depth,
1579251881Speter                                svn_boolean_t ignore_ancestry,
1580251881Speter                                svn_boolean_t text_deltas,
1581251881Speter                                const char *versus_url,
1582251881Speter                                const svn_delta_editor_t *diff_editor,
1583251881Speter                                void *diff_baton, apr_pool_t *pool)
1584251881Speter{
1585251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1586251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1587251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1588251881Speter
1589251881Speter  /* Tell the server we want to start a diff. */
1590251881Speter  SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1591251881Speter                                     ignore_ancestry, versus_url,
1592251881Speter                                     text_deltas, depth));
1593251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1594251881Speter
1595251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1596251881Speter   * diff_editor upon finish_report(). */
1597251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1598251881Speter                              target, depth, reporter, report_baton));
1599251881Speter  return SVN_NO_ERROR;
1600251881Speter}
1601251881Speter
1602251881Speter
1603253734Speterstatic svn_error_t *
1604253734Speterperform_ra_svn_log(svn_error_t **outer_error,
1605253734Speter                   svn_ra_session_t *session,
1606253734Speter                   const apr_array_header_t *paths,
1607253734Speter                   svn_revnum_t start, svn_revnum_t end,
1608253734Speter                   int limit,
1609253734Speter                   svn_boolean_t discover_changed_paths,
1610253734Speter                   svn_boolean_t strict_node_history,
1611253734Speter                   svn_boolean_t include_merged_revisions,
1612253734Speter                   const apr_array_header_t *revprops,
1613253734Speter                   svn_log_entry_receiver_t receiver,
1614253734Speter                   void *receiver_baton,
1615253734Speter                   apr_pool_t *pool)
1616251881Speter{
1617251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1618251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1619251881Speter  apr_pool_t *iterpool;
1620251881Speter  int i;
1621251881Speter  int nest_level = 0;
1622251881Speter  const char *path;
1623251881Speter  char *name;
1624251881Speter  svn_boolean_t want_custom_revprops;
1625299742Sdim  svn_boolean_t want_author = FALSE;
1626299742Sdim  svn_boolean_t want_message = FALSE;
1627299742Sdim  svn_boolean_t want_date = FALSE;
1628299742Sdim  int nreceived = 0;
1629251881Speter
1630251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1631251881Speter  if (paths)
1632251881Speter    {
1633251881Speter      for (i = 0; i < paths->nelts; i++)
1634251881Speter        {
1635251881Speter          path = APR_ARRAY_IDX(paths, i, const char *);
1636251881Speter          SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1637251881Speter        }
1638251881Speter    }
1639251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1640251881Speter                                  discover_changed_paths, strict_node_history,
1641251881Speter                                  (apr_uint64_t) limit,
1642251881Speter                                  include_merged_revisions));
1643251881Speter  if (revprops)
1644251881Speter    {
1645251881Speter      want_custom_revprops = FALSE;
1646251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1647251881Speter      for (i = 0; i < revprops->nelts; i++)
1648251881Speter        {
1649251881Speter          name = APR_ARRAY_IDX(revprops, i, char *);
1650251881Speter          SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1651299742Sdim
1652299742Sdim          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1653299742Sdim            want_author = TRUE;
1654299742Sdim          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1655299742Sdim            want_date = TRUE;
1656299742Sdim          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1657299742Sdim            want_message = TRUE;
1658299742Sdim          else
1659251881Speter            want_custom_revprops = TRUE;
1660251881Speter        }
1661251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1662251881Speter    }
1663251881Speter  else
1664251881Speter    {
1665251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1666299742Sdim
1667299742Sdim      want_author = TRUE;
1668299742Sdim      want_date = TRUE;
1669299742Sdim      want_message = TRUE;
1670251881Speter      want_custom_revprops = TRUE;
1671251881Speter    }
1672251881Speter
1673251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1674251881Speter
1675251881Speter  /* Read the log messages. */
1676251881Speter  iterpool = svn_pool_create(pool);
1677251881Speter  while (1)
1678251881Speter    {
1679251881Speter      apr_uint64_t has_children_param, invalid_revnum_param;
1680251881Speter      apr_uint64_t has_subtractive_merge_param;
1681251881Speter      svn_string_t *author, *date, *message;
1682251881Speter      apr_array_header_t *cplist, *rplist;
1683251881Speter      svn_log_entry_t *log_entry;
1684251881Speter      svn_boolean_t has_children;
1685251881Speter      svn_boolean_t subtractive_merge = FALSE;
1686251881Speter      apr_uint64_t revprop_count;
1687251881Speter      svn_ra_svn_item_t *item;
1688251881Speter      apr_hash_t *cphash;
1689251881Speter      svn_revnum_t rev;
1690251881Speter
1691251881Speter      svn_pool_clear(iterpool);
1692251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1693251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1694251881Speter        break;
1695251881Speter      if (item->kind != SVN_RA_SVN_LIST)
1696251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1697251881Speter                                _("Log entry not a list"));
1698251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1699251881Speter                                      "lr(?s)(?s)(?s)?BBnl?B",
1700251881Speter                                      &cplist, &rev, &author, &date,
1701251881Speter                                      &message, &has_children_param,
1702251881Speter                                      &invalid_revnum_param,
1703251881Speter                                      &revprop_count, &rplist,
1704251881Speter                                      &has_subtractive_merge_param));
1705251881Speter      if (want_custom_revprops && rplist == NULL)
1706251881Speter        {
1707251881Speter          /* Caller asked for custom revprops, but server is too old. */
1708251881Speter          return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1709251881Speter                                  _("Server does not support custom revprops"
1710251881Speter                                    " via log"));
1711251881Speter        }
1712251881Speter
1713251881Speter      if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1714251881Speter        has_children = FALSE;
1715251881Speter      else
1716251881Speter        has_children = (svn_boolean_t) has_children_param;
1717251881Speter
1718251881Speter      if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1719251881Speter        subtractive_merge = FALSE;
1720251881Speter      else
1721251881Speter        subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1722251881Speter
1723251881Speter      /* Because the svn protocol won't let us send an invalid revnum, we have
1724251881Speter         to recover that fact using the extra parameter. */
1725251881Speter      if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1726251881Speter            && invalid_revnum_param)
1727251881Speter        rev = SVN_INVALID_REVNUM;
1728251881Speter
1729251881Speter      if (cplist->nelts > 0)
1730251881Speter        {
1731251881Speter          /* Interpret the changed-paths list. */
1732299742Sdim          cphash = svn_hash__make(iterpool);
1733251881Speter          for (i = 0; i < cplist->nelts; i++)
1734251881Speter            {
1735251881Speter              svn_log_changed_path2_t *change;
1736299742Sdim              svn_string_t *cpath;
1737299742Sdim              const char *copy_path, *action, *kind_str;
1738251881Speter              apr_uint64_t text_mods, prop_mods;
1739251881Speter              svn_revnum_t copy_rev;
1740251881Speter              svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1741251881Speter                                                      svn_ra_svn_item_t);
1742251881Speter
1743251881Speter              if (elt->kind != SVN_RA_SVN_LIST)
1744251881Speter                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1745251881Speter                                        _("Changed-path entry not a list"));
1746299742Sdim              SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list,
1747251881Speter                                              &cpath, &action, &copy_path,
1748251881Speter                                              &copy_rev, &kind_str,
1749251881Speter                                              &text_mods, &prop_mods));
1750299742Sdim
1751299742Sdim              if (!svn_fspath__is_canonical(cpath->data))
1752299742Sdim                {
1753299742Sdim                  cpath->data = svn_fspath__canonicalize(cpath->data, iterpool);
1754299742Sdim                  cpath->len = strlen(cpath->data);
1755299742Sdim                }
1756299742Sdim              if (copy_path && !svn_fspath__is_canonical(copy_path))
1757251881Speter                copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1758299742Sdim
1759251881Speter              change = svn_log_changed_path2_create(iterpool);
1760251881Speter              change->action = *action;
1761251881Speter              change->copyfrom_path = copy_path;
1762251881Speter              change->copyfrom_rev = copy_rev;
1763251881Speter              change->node_kind = svn_node_kind_from_word(kind_str);
1764251881Speter              change->text_modified = optbool_to_tristate(text_mods);
1765251881Speter              change->props_modified = optbool_to_tristate(prop_mods);
1766299742Sdim              apr_hash_set(cphash, cpath->data, cpath->len, change);
1767251881Speter            }
1768251881Speter        }
1769251881Speter      else
1770251881Speter        cphash = NULL;
1771251881Speter
1772299742Sdim      /* Invoke RECEIVER
1773299742Sdim          - Except if the server sends more than a >= 1 limit top level items
1774299742Sdim          - Or when the callback reported a SVN_ERR_CEASE_INVOCATION
1775299742Sdim            in an earlier invocation. */
1776253734Speter      if (! (limit && (nest_level == 0) && (++nreceived > limit))
1777253734Speter          && ! *outer_error)
1778251881Speter        {
1779253734Speter          svn_error_t *err;
1780251881Speter          log_entry = svn_log_entry_create(iterpool);
1781251881Speter
1782251881Speter          log_entry->changed_paths = cphash;
1783251881Speter          log_entry->changed_paths2 = cphash;
1784251881Speter          log_entry->revision = rev;
1785251881Speter          log_entry->has_children = has_children;
1786251881Speter          log_entry->subtractive_merge = subtractive_merge;
1787251881Speter          if (rplist)
1788251881Speter            SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1789251881Speter                                               &log_entry->revprops));
1790251881Speter          if (log_entry->revprops == NULL)
1791299742Sdim            log_entry->revprops = svn_hash__make(iterpool);
1792299742Sdim
1793299742Sdim          if (author && want_author)
1794299742Sdim            svn_hash_sets(log_entry->revprops,
1795299742Sdim                          SVN_PROP_REVISION_AUTHOR, author);
1796299742Sdim          if (date && want_date)
1797299742Sdim            svn_hash_sets(log_entry->revprops,
1798299742Sdim                          SVN_PROP_REVISION_DATE, date);
1799299742Sdim          if (message && want_message)
1800299742Sdim            svn_hash_sets(log_entry->revprops,
1801299742Sdim                          SVN_PROP_REVISION_LOG, message);
1802299742Sdim
1803253734Speter          err = receiver(receiver_baton, log_entry, iterpool);
1804253734Speter          if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1805253734Speter            {
1806253734Speter              *outer_error = svn_error_trace(
1807253734Speter                                svn_error_compose_create(*outer_error, err));
1808253734Speter            }
1809253734Speter          else
1810253734Speter            SVN_ERR(err);
1811253734Speter
1812251881Speter          if (log_entry->has_children)
1813251881Speter            {
1814251881Speter              nest_level++;
1815251881Speter            }
1816251881Speter          if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1817251881Speter            {
1818251881Speter              SVN_ERR_ASSERT(nest_level);
1819251881Speter              nest_level--;
1820251881Speter            }
1821251881Speter        }
1822251881Speter    }
1823251881Speter  svn_pool_destroy(iterpool);
1824251881Speter
1825251881Speter  /* Read the response. */
1826253734Speter  return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1827251881Speter}
1828251881Speter
1829253734Speterstatic svn_error_t *
1830253734Speterra_svn_log(svn_ra_session_t *session,
1831253734Speter           const apr_array_header_t *paths,
1832253734Speter           svn_revnum_t start, svn_revnum_t end,
1833253734Speter           int limit,
1834253734Speter           svn_boolean_t discover_changed_paths,
1835253734Speter           svn_boolean_t strict_node_history,
1836253734Speter           svn_boolean_t include_merged_revisions,
1837253734Speter           const apr_array_header_t *revprops,
1838253734Speter           svn_log_entry_receiver_t receiver,
1839253734Speter           void *receiver_baton, apr_pool_t *pool)
1840253734Speter{
1841253734Speter  svn_error_t *outer_error = NULL;
1842253734Speter  svn_error_t *err;
1843251881Speter
1844253734Speter  err = svn_error_trace(perform_ra_svn_log(&outer_error,
1845253734Speter                                           session, paths,
1846253734Speter                                           start, end,
1847253734Speter                                           limit,
1848253734Speter                                           discover_changed_paths,
1849253734Speter                                           strict_node_history,
1850253734Speter                                           include_merged_revisions,
1851253734Speter                                           revprops,
1852253734Speter                                           receiver, receiver_baton,
1853253734Speter                                           pool));
1854253734Speter  return svn_error_trace(
1855253734Speter            svn_error_compose_create(outer_error,
1856253734Speter                                     err));
1857253734Speter}
1858253734Speter
1859253734Speter
1860253734Speter
1861251881Speterstatic svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1862251881Speter                                      const char *path, svn_revnum_t rev,
1863251881Speter                                      svn_node_kind_t *kind, apr_pool_t *pool)
1864251881Speter{
1865251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1866251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1867251881Speter  const char *kind_word;
1868251881Speter
1869251881Speter  SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1870251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1871251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1872251881Speter  *kind = svn_node_kind_from_word(kind_word);
1873251881Speter  return SVN_NO_ERROR;
1874251881Speter}
1875251881Speter
1876251881Speter
1877251881Speter/* If ERR is a command not supported error, wrap it in a
1878251881Speter   SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG.  Else, return err. */
1879251881Speterstatic svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1880251881Speter                                           const char *msg)
1881251881Speter{
1882251881Speter  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1883251881Speter    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1884251881Speter                            _(msg));
1885251881Speter  return err;
1886251881Speter}
1887251881Speter
1888251881Speter
1889251881Speterstatic svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1890251881Speter                                const char *path, svn_revnum_t rev,
1891251881Speter                                svn_dirent_t **dirent, apr_pool_t *pool)
1892251881Speter{
1893251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1894251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1895251881Speter  apr_array_header_t *list = NULL;
1896251881Speter  svn_dirent_t *the_dirent;
1897251881Speter
1898251881Speter  SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1899251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1900251881Speter                                 N_("'stat' not implemented")));
1901251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1902251881Speter
1903251881Speter  if (! list)
1904251881Speter    {
1905251881Speter      *dirent = NULL;
1906251881Speter    }
1907251881Speter  else
1908251881Speter    {
1909251881Speter      const char *kind, *cdate, *cauthor;
1910251881Speter      svn_boolean_t has_props;
1911251881Speter      svn_revnum_t crev;
1912251881Speter      apr_uint64_t size;
1913251881Speter
1914251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1915251881Speter                                      &kind, &size, &has_props,
1916251881Speter                                      &crev, &cdate, &cauthor));
1917251881Speter
1918251881Speter      the_dirent = svn_dirent_create(pool);
1919251881Speter      the_dirent->kind = svn_node_kind_from_word(kind);
1920251881Speter      the_dirent->size = size;/* FIXME: svn_filesize_t */
1921251881Speter      the_dirent->has_props = has_props;
1922251881Speter      the_dirent->created_rev = crev;
1923251881Speter      SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1924251881Speter      the_dirent->last_author = cauthor;
1925251881Speter
1926251881Speter      *dirent = the_dirent;
1927251881Speter    }
1928251881Speter
1929251881Speter  return SVN_NO_ERROR;
1930251881Speter}
1931251881Speter
1932251881Speter
1933251881Speterstatic svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1934251881Speter                                         apr_hash_t **locations,
1935251881Speter                                         const char *path,
1936251881Speter                                         svn_revnum_t peg_revision,
1937251881Speter                                         const apr_array_header_t *location_revisions,
1938251881Speter                                         apr_pool_t *pool)
1939251881Speter{
1940251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1941251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1942251881Speter  svn_revnum_t revision;
1943251881Speter  svn_boolean_t is_done;
1944251881Speter  int i;
1945251881Speter
1946251881Speter  /* Transmit the parameters. */
1947251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1948251881Speter                                  "get-locations", path, peg_revision));
1949251881Speter  for (i = 0; i < location_revisions->nelts; i++)
1950251881Speter    {
1951251881Speter      revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1952251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1953251881Speter    }
1954251881Speter
1955251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1956251881Speter
1957251881Speter  /* Servers before 1.1 don't support this command. Check for this here. */
1958251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1959251881Speter                                 N_("'get-locations' not implemented")));
1960251881Speter
1961251881Speter  /* Read the hash items. */
1962251881Speter  is_done = FALSE;
1963251881Speter  *locations = apr_hash_make(pool);
1964251881Speter  while (!is_done)
1965251881Speter    {
1966251881Speter      svn_ra_svn_item_t *item;
1967251881Speter      const char *ret_path;
1968251881Speter
1969251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1970251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1971251881Speter        is_done = 1;
1972251881Speter      else if (item->kind != SVN_RA_SVN_LIST)
1973251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1974251881Speter                                _("Location entry not a list"));
1975251881Speter      else
1976251881Speter        {
1977251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1978251881Speter                                          &revision, &ret_path));
1979251881Speter          ret_path = svn_fspath__canonicalize(ret_path, pool);
1980251881Speter          apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1981251881Speter                                               sizeof(revision)),
1982251881Speter                       sizeof(revision), ret_path);
1983251881Speter        }
1984251881Speter    }
1985251881Speter
1986251881Speter  /* Read the response. This is so the server would have a chance to
1987251881Speter   * report an error. */
1988299742Sdim  return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1989251881Speter}
1990251881Speter
1991251881Speterstatic svn_error_t *
1992251881Speterra_svn_get_location_segments(svn_ra_session_t *session,
1993251881Speter                             const char *path,
1994251881Speter                             svn_revnum_t peg_revision,
1995251881Speter                             svn_revnum_t start_rev,
1996251881Speter                             svn_revnum_t end_rev,
1997251881Speter                             svn_location_segment_receiver_t receiver,
1998251881Speter                             void *receiver_baton,
1999251881Speter                             apr_pool_t *pool)
2000251881Speter{
2001251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2002251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
2003251881Speter  svn_boolean_t is_done;
2004251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2005251881Speter
2006251881Speter  /* Transmit the parameters. */
2007251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
2008251881Speter                                  "get-location-segments",
2009251881Speter                                  path, peg_revision, start_rev, end_rev));
2010251881Speter
2011251881Speter  /* Servers before 1.5 don't support this command. Check for this here. */
2012251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2013251881Speter                                 N_("'get-location-segments'"
2014251881Speter                                    " not implemented")));
2015251881Speter
2016251881Speter  /* Parse the response. */
2017251881Speter  is_done = FALSE;
2018251881Speter  while (!is_done)
2019251881Speter    {
2020251881Speter      svn_revnum_t range_start, range_end;
2021251881Speter      svn_ra_svn_item_t *item;
2022251881Speter      const char *ret_path;
2023251881Speter
2024251881Speter      svn_pool_clear(iterpool);
2025251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
2026251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2027251881Speter        is_done = 1;
2028251881Speter      else if (item->kind != SVN_RA_SVN_LIST)
2029251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2030251881Speter                                _("Location segment entry not a list"));
2031251881Speter      else
2032251881Speter        {
2033251881Speter          svn_location_segment_t *segment = apr_pcalloc(iterpool,
2034251881Speter                                                        sizeof(*segment));
2035251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
2036251881Speter                                          &range_start, &range_end, &ret_path));
2037251881Speter          if (! (SVN_IS_VALID_REVNUM(range_start)
2038251881Speter                 && SVN_IS_VALID_REVNUM(range_end)))
2039251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2040251881Speter                                    _("Expected valid revision range"));
2041251881Speter          if (ret_path)
2042251881Speter            ret_path = svn_relpath_canonicalize(ret_path, iterpool);
2043251881Speter          segment->path = ret_path;
2044251881Speter          segment->range_start = range_start;
2045251881Speter          segment->range_end = range_end;
2046251881Speter          SVN_ERR(receiver(segment, receiver_baton, iterpool));
2047251881Speter        }
2048251881Speter    }
2049251881Speter  svn_pool_destroy(iterpool);
2050251881Speter
2051251881Speter  /* Read the response. This is so the server would have a chance to
2052251881Speter   * report an error. */
2053251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2054251881Speter
2055251881Speter  return SVN_NO_ERROR;
2056251881Speter}
2057251881Speter
2058251881Speterstatic svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
2059251881Speter                                         const char *path,
2060251881Speter                                         svn_revnum_t start, svn_revnum_t end,
2061251881Speter                                         svn_boolean_t include_merged_revisions,
2062251881Speter                                         svn_file_rev_handler_t handler,
2063251881Speter                                         void *handler_baton, apr_pool_t *pool)
2064251881Speter{
2065251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2066251881Speter  apr_pool_t *rev_pool, *chunk_pool;
2067251881Speter  svn_boolean_t has_txdelta;
2068251881Speter  svn_boolean_t had_revision = FALSE;
2069251881Speter
2070251881Speter  /* One sub-pool for each revision and one for each txdelta chunk.
2071251881Speter     Note that the rev_pool must live during the following txdelta. */
2072251881Speter  rev_pool = svn_pool_create(pool);
2073251881Speter  chunk_pool = svn_pool_create(pool);
2074251881Speter
2075251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
2076251881Speter                                              path, start, end,
2077251881Speter                                              include_merged_revisions));
2078251881Speter
2079251881Speter  /* Servers before 1.1 don't support this command.  Check for this here. */
2080251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2081251881Speter                                 N_("'get-file-revs' not implemented")));
2082251881Speter
2083251881Speter  while (1)
2084251881Speter    {
2085251881Speter      apr_array_header_t *rev_proplist, *proplist;
2086251881Speter      apr_uint64_t merged_rev_param;
2087251881Speter      apr_array_header_t *props;
2088251881Speter      svn_ra_svn_item_t *item;
2089251881Speter      apr_hash_t *rev_props;
2090251881Speter      svn_revnum_t rev;
2091251881Speter      const char *p;
2092251881Speter      svn_boolean_t merged_rev;
2093251881Speter      svn_txdelta_window_handler_t d_handler;
2094251881Speter      void *d_baton;
2095251881Speter
2096251881Speter      svn_pool_clear(rev_pool);
2097251881Speter      svn_pool_clear(chunk_pool);
2098251881Speter      SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
2099251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
2100251881Speter        break;
2101251881Speter      /* Either we've got a correct revision or we will error out below. */
2102251881Speter      had_revision = TRUE;
2103251881Speter      if (item->kind != SVN_RA_SVN_LIST)
2104251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2105251881Speter                                _("Revision entry not a list"));
2106251881Speter
2107251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
2108251881Speter                                      "crll?B", &p, &rev, &rev_proplist,
2109251881Speter                                      &proplist, &merged_rev_param));
2110251881Speter      p = svn_fspath__canonicalize(p, rev_pool);
2111251881Speter      SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
2112251881Speter      SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
2113251881Speter      if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2114251881Speter        merged_rev = FALSE;
2115251881Speter      else
2116251881Speter        merged_rev = (svn_boolean_t) merged_rev_param;
2117251881Speter
2118251881Speter      /* Get the first delta chunk so we know if there is a delta. */
2119251881Speter      SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
2120251881Speter      if (item->kind != SVN_RA_SVN_STRING)
2121251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2122251881Speter                                _("Text delta chunk not a string"));
2123251881Speter      has_txdelta = item->u.string->len > 0;
2124251881Speter
2125251881Speter      SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2126251881Speter                      has_txdelta ? &d_handler : NULL, &d_baton,
2127251881Speter                      props, rev_pool));
2128251881Speter
2129251881Speter      /* Process the text delta if any. */
2130251881Speter      if (has_txdelta)
2131251881Speter        {
2132251881Speter          svn_stream_t *stream;
2133251881Speter
2134299742Sdim          if (d_handler && d_handler != svn_delta_noop_window_handler)
2135251881Speter            stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2136251881Speter                                               rev_pool);
2137251881Speter          else
2138251881Speter            stream = NULL;
2139251881Speter          while (item->u.string->len > 0)
2140251881Speter            {
2141251881Speter              apr_size_t size;
2142251881Speter
2143251881Speter              size = item->u.string->len;
2144251881Speter              if (stream)
2145251881Speter                SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2146251881Speter              svn_pool_clear(chunk_pool);
2147251881Speter
2148251881Speter              SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2149251881Speter                                            &item));
2150251881Speter              if (item->kind != SVN_RA_SVN_STRING)
2151251881Speter                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2152251881Speter                                        _("Text delta chunk not a string"));
2153251881Speter            }
2154251881Speter          if (stream)
2155251881Speter            SVN_ERR(svn_stream_close(stream));
2156251881Speter        }
2157251881Speter    }
2158251881Speter
2159251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2160251881Speter
2161251881Speter  /* Return error if we didn't get any revisions. */
2162251881Speter  if (!had_revision)
2163251881Speter    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2164251881Speter                            _("The get-file-revs command didn't return "
2165251881Speter                              "any revisions"));
2166251881Speter
2167251881Speter  svn_pool_destroy(chunk_pool);
2168251881Speter  svn_pool_destroy(rev_pool);
2169251881Speter
2170251881Speter  return SVN_NO_ERROR;
2171251881Speter}
2172251881Speter
2173251881Speter/* For each path in PATH_REVS, send a 'lock' command to the server.
2174251881Speter   Used with 1.2.x series servers which support locking, but of only
2175251881Speter   one path at a time.  ra_svn_lock(), which supports 'lock-many'
2176251881Speter   is now the default.  See svn_ra_lock() docstring for interface details. */
2177251881Speterstatic svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2178251881Speter                                       apr_hash_t *path_revs,
2179251881Speter                                       const char *comment,
2180251881Speter                                       svn_boolean_t steal_lock,
2181251881Speter                                       svn_ra_lock_callback_t lock_func,
2182251881Speter                                       void *lock_baton,
2183251881Speter                                       apr_pool_t *pool)
2184251881Speter{
2185251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2186251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2187251881Speter  apr_array_header_t *list;
2188251881Speter  apr_hash_index_t *hi;
2189251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2190251881Speter
2191251881Speter  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2192251881Speter    {
2193251881Speter      svn_lock_t *lock;
2194251881Speter      const void *key;
2195251881Speter      const char *path;
2196251881Speter      void *val;
2197251881Speter      svn_revnum_t *revnum;
2198251881Speter      svn_error_t *err, *callback_err = NULL;
2199251881Speter
2200251881Speter      svn_pool_clear(iterpool);
2201251881Speter
2202251881Speter      apr_hash_this(hi, &key, NULL, &val);
2203251881Speter      path = key;
2204251881Speter      revnum = val;
2205251881Speter
2206251881Speter      SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2207251881Speter                                         steal_lock, *revnum));
2208251881Speter
2209251881Speter      /* Servers before 1.2 doesn't support locking.  Check this here. */
2210251881Speter      SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2211251881Speter                                     N_("Server doesn't support "
2212251881Speter                                        "the lock command")));
2213251881Speter
2214251881Speter      err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2215251881Speter
2216251881Speter      if (!err)
2217251881Speter        SVN_ERR(parse_lock(list, iterpool, &lock));
2218251881Speter
2219251881Speter      if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2220251881Speter        return err;
2221251881Speter
2222251881Speter      if (lock_func)
2223251881Speter        callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2224251881Speter                                 err, iterpool);
2225251881Speter
2226251881Speter      svn_error_clear(err);
2227251881Speter
2228251881Speter      if (callback_err)
2229251881Speter        return callback_err;
2230251881Speter    }
2231251881Speter
2232251881Speter  svn_pool_destroy(iterpool);
2233251881Speter
2234251881Speter  return SVN_NO_ERROR;
2235251881Speter}
2236251881Speter
2237251881Speter/* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2238251881Speter   Used with 1.2.x series servers which support unlocking, but of only
2239251881Speter   one path at a time.  ra_svn_unlock(), which supports 'unlock-many' is
2240251881Speter   now the default.  See svn_ra_unlock() docstring for interface details. */
2241251881Speterstatic svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2242251881Speter                                         apr_hash_t *path_tokens,
2243251881Speter                                         svn_boolean_t break_lock,
2244251881Speter                                         svn_ra_lock_callback_t lock_func,
2245251881Speter                                         void *lock_baton,
2246251881Speter                                         apr_pool_t *pool)
2247251881Speter{
2248251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2249251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2250251881Speter  apr_hash_index_t *hi;
2251251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2252251881Speter
2253251881Speter  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2254251881Speter    {
2255251881Speter      const void *key;
2256251881Speter      const char *path;
2257251881Speter      void *val;
2258251881Speter      const char *token;
2259251881Speter      svn_error_t *err, *callback_err = NULL;
2260251881Speter
2261251881Speter      svn_pool_clear(iterpool);
2262251881Speter
2263251881Speter      apr_hash_this(hi, &key, NULL, &val);
2264251881Speter      path = key;
2265251881Speter      if (strcmp(val, "") != 0)
2266251881Speter        token = val;
2267251881Speter      else
2268251881Speter        token = NULL;
2269251881Speter
2270251881Speter      SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2271251881Speter                                           break_lock));
2272251881Speter
2273251881Speter      /* Servers before 1.2 don't support locking.  Check this here. */
2274251881Speter      SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2275251881Speter                                     N_("Server doesn't support the unlock "
2276251881Speter                                        "command")));
2277251881Speter
2278251881Speter      err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2279251881Speter
2280251881Speter      if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2281251881Speter        return err;
2282251881Speter
2283251881Speter      if (lock_func)
2284251881Speter        callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2285251881Speter
2286251881Speter      svn_error_clear(err);
2287251881Speter
2288251881Speter      if (callback_err)
2289251881Speter        return callback_err;
2290251881Speter    }
2291251881Speter
2292251881Speter  svn_pool_destroy(iterpool);
2293251881Speter
2294251881Speter  return SVN_NO_ERROR;
2295251881Speter}
2296251881Speter
2297251881Speter/* Tell the server to lock all paths in PATH_REVS.
2298251881Speter   See svn_ra_lock() for interface details. */
2299251881Speterstatic svn_error_t *ra_svn_lock(svn_ra_session_t *session,
2300251881Speter                                apr_hash_t *path_revs,
2301251881Speter                                const char *comment,
2302251881Speter                                svn_boolean_t steal_lock,
2303251881Speter                                svn_ra_lock_callback_t lock_func,
2304251881Speter                                void *lock_baton,
2305251881Speter                                apr_pool_t *pool)
2306251881Speter{
2307251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2308251881Speter  svn_ra_svn_conn_t *conn = sess->conn;
2309251881Speter  apr_hash_index_t *hi;
2310251881Speter  svn_error_t *err;
2311251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2312251881Speter
2313251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2314251881Speter                                  comment, steal_lock));
2315251881Speter
2316251881Speter  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2317251881Speter    {
2318251881Speter      const void *key;
2319251881Speter      const char *path;
2320251881Speter      void *val;
2321251881Speter      svn_revnum_t *revnum;
2322251881Speter
2323251881Speter      svn_pool_clear(iterpool);
2324251881Speter      apr_hash_this(hi, &key, NULL, &val);
2325251881Speter      path = key;
2326251881Speter      revnum = val;
2327251881Speter
2328251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2329251881Speter    }
2330251881Speter
2331251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2332251881Speter
2333251881Speter  err = handle_auth_request(sess, pool);
2334251881Speter
2335251881Speter  /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2336251881Speter   * to 'lock'. */
2337251881Speter  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2338251881Speter    {
2339251881Speter      svn_error_clear(err);
2340251881Speter      return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2341251881Speter                                lock_func, lock_baton, pool);
2342251881Speter    }
2343251881Speter
2344251881Speter  if (err)
2345251881Speter    return err;
2346251881Speter
2347251881Speter  /* Loop over responses to get lock information. */
2348251881Speter  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2349251881Speter    {
2350251881Speter      svn_ra_svn_item_t *elt;
2351251881Speter      const void *key;
2352251881Speter      const char *path;
2353251881Speter      svn_error_t *callback_err;
2354251881Speter      const char *status;
2355251881Speter      svn_lock_t *lock;
2356251881Speter      apr_array_header_t *list;
2357251881Speter
2358251881Speter      apr_hash_this(hi, &key, NULL, NULL);
2359251881Speter      path = key;
2360251881Speter
2361251881Speter      svn_pool_clear(iterpool);
2362251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2363251881Speter
2364251881Speter      /* The server might have encountered some sort of fatal error in
2365251881Speter         the middle of the request list.  If this happens, it will
2366251881Speter         transmit "done" to end the lock-info early, and then the
2367251881Speter         overall command response will talk about the fatal error. */
2368251881Speter      if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2369251881Speter        break;
2370251881Speter
2371251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
2372251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2373251881Speter                                _("Lock response not a list"));
2374251881Speter
2375251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2376251881Speter                                      &list));
2377251881Speter
2378251881Speter      if (strcmp(status, "failure") == 0)
2379251881Speter        err = svn_ra_svn__handle_failure_status(list, iterpool);
2380251881Speter      else if (strcmp(status, "success") == 0)
2381251881Speter        {
2382251881Speter          SVN_ERR(parse_lock(list, iterpool, &lock));
2383251881Speter          err = NULL;
2384251881Speter        }
2385251881Speter      else
2386251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2387251881Speter                                _("Unknown status for lock command"));
2388251881Speter
2389251881Speter      if (lock_func)
2390251881Speter        callback_err = lock_func(lock_baton, path, TRUE,
2391251881Speter                                 err ? NULL : lock,
2392251881Speter                                 err, iterpool);
2393251881Speter      else
2394251881Speter        callback_err = SVN_NO_ERROR;
2395251881Speter
2396251881Speter      svn_error_clear(err);
2397251881Speter
2398251881Speter      if (callback_err)
2399251881Speter        return callback_err;
2400251881Speter    }
2401251881Speter
2402251881Speter  /* If we didn't break early above, and the whole hash was traversed,
2403251881Speter     read the final "done" from the server. */
2404251881Speter  if (!hi)
2405251881Speter    {
2406251881Speter      svn_ra_svn_item_t *elt;
2407251881Speter
2408251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2409251881Speter      if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2410251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2411251881Speter                                _("Didn't receive end marker for lock "
2412251881Speter                                  "responses"));
2413251881Speter    }
2414251881Speter
2415251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2416251881Speter
2417251881Speter  svn_pool_destroy(iterpool);
2418251881Speter
2419251881Speter  return SVN_NO_ERROR;
2420251881Speter}
2421251881Speter
2422251881Speter/* Tell the server to unlock all paths in PATH_TOKENS.
2423251881Speter   See svn_ra_unlock() for interface details. */
2424251881Speterstatic svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2425251881Speter                                  apr_hash_t *path_tokens,
2426251881Speter                                  svn_boolean_t break_lock,
2427251881Speter                                  svn_ra_lock_callback_t lock_func,
2428251881Speter                                  void *lock_baton,
2429251881Speter                                  apr_pool_t *pool)
2430251881Speter{
2431251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2432251881Speter  svn_ra_svn_conn_t *conn = sess->conn;
2433251881Speter  apr_hash_index_t *hi;
2434251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2435251881Speter  svn_error_t *err;
2436251881Speter  const char *path;
2437251881Speter
2438251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2439251881Speter                                  break_lock));
2440251881Speter
2441251881Speter  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2442251881Speter    {
2443251881Speter      void *val;
2444251881Speter      const void *key;
2445251881Speter      const char *token;
2446251881Speter
2447251881Speter      svn_pool_clear(iterpool);
2448251881Speter      apr_hash_this(hi, &key, NULL, &val);
2449251881Speter      path = key;
2450251881Speter
2451251881Speter      if (strcmp(val, "") != 0)
2452251881Speter        token = val;
2453251881Speter      else
2454251881Speter        token = NULL;
2455251881Speter
2456251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2457251881Speter    }
2458251881Speter
2459251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2460251881Speter
2461251881Speter  err = handle_auth_request(sess, pool);
2462251881Speter
2463251881Speter  /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2464251881Speter   * to 'unlock'.
2465251881Speter   */
2466251881Speter  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2467251881Speter    {
2468251881Speter      svn_error_clear(err);
2469251881Speter      return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2470251881Speter                                  lock_baton, pool);
2471251881Speter    }
2472251881Speter
2473251881Speter  if (err)
2474251881Speter    return err;
2475251881Speter
2476251881Speter  /* Loop over responses to unlock files. */
2477251881Speter  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2478251881Speter    {
2479251881Speter      svn_ra_svn_item_t *elt;
2480251881Speter      const void *key;
2481251881Speter      svn_error_t *callback_err;
2482251881Speter      const char *status;
2483251881Speter      apr_array_header_t *list;
2484251881Speter
2485251881Speter      svn_pool_clear(iterpool);
2486251881Speter
2487251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2488251881Speter
2489251881Speter      /* The server might have encountered some sort of fatal error in
2490251881Speter         the middle of the request list.  If this happens, it will
2491251881Speter         transmit "done" to end the lock-info early, and then the
2492251881Speter         overall command response will talk about the fatal error. */
2493251881Speter      if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2494251881Speter        break;
2495251881Speter
2496251881Speter      apr_hash_this(hi, &key, NULL, NULL);
2497251881Speter      path = key;
2498251881Speter
2499251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
2500251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2501251881Speter                                _("Unlock response not a list"));
2502251881Speter
2503251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2504251881Speter                                      &list));
2505251881Speter
2506251881Speter      if (strcmp(status, "failure") == 0)
2507251881Speter        err = svn_ra_svn__handle_failure_status(list, iterpool);
2508251881Speter      else if (strcmp(status, "success") == 0)
2509251881Speter        {
2510251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2511251881Speter          err = SVN_NO_ERROR;
2512251881Speter        }
2513251881Speter      else
2514251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2515251881Speter                                _("Unknown status for unlock command"));
2516251881Speter
2517251881Speter      if (lock_func)
2518251881Speter        callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2519251881Speter                                 iterpool);
2520251881Speter      else
2521251881Speter        callback_err = SVN_NO_ERROR;
2522251881Speter
2523251881Speter      svn_error_clear(err);
2524251881Speter
2525251881Speter      if (callback_err)
2526251881Speter        return callback_err;
2527251881Speter    }
2528251881Speter
2529251881Speter  /* If we didn't break early above, and the whole hash was traversed,
2530251881Speter     read the final "done" from the server. */
2531251881Speter  if (!hi)
2532251881Speter    {
2533251881Speter      svn_ra_svn_item_t *elt;
2534251881Speter
2535251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2536251881Speter      if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2537251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2538251881Speter                                _("Didn't receive end marker for unlock "
2539251881Speter                                  "responses"));
2540251881Speter    }
2541251881Speter
2542251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2543251881Speter
2544251881Speter  svn_pool_destroy(iterpool);
2545251881Speter
2546251881Speter  return SVN_NO_ERROR;
2547251881Speter}
2548251881Speter
2549251881Speterstatic svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2550251881Speter                                    svn_lock_t **lock,
2551251881Speter                                    const char *path,
2552251881Speter                                    apr_pool_t *pool)
2553251881Speter{
2554251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2555251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2556251881Speter  apr_array_header_t *list;
2557251881Speter
2558251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2559251881Speter
2560251881Speter  /* Servers before 1.2 doesn't support locking.  Check this here. */
2561251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2562251881Speter                                 N_("Server doesn't support the get-lock "
2563251881Speter                                    "command")));
2564251881Speter
2565251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2566251881Speter  if (list)
2567251881Speter    SVN_ERR(parse_lock(list, pool, lock));
2568251881Speter  else
2569251881Speter    *lock = NULL;
2570251881Speter
2571251881Speter  return SVN_NO_ERROR;
2572251881Speter}
2573251881Speter
2574251881Speter/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2575251881Speter   to prevent a dependency cycle. */
2576251881Speterstatic svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2577251881Speter                                          const char **rel_path,
2578251881Speter                                          const char *url,
2579251881Speter                                          apr_pool_t *pool)
2580251881Speter{
2581251881Speter  const char *root_url;
2582251881Speter
2583251881Speter  SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2584251881Speter  *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2585251881Speter  if (! *rel_path)
2586251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2587251881Speter                             _("'%s' isn't a child of repository root "
2588251881Speter                               "URL '%s'"),
2589251881Speter                             url, root_url);
2590251881Speter  return SVN_NO_ERROR;
2591251881Speter}
2592251881Speter
2593251881Speterstatic svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2594251881Speter                                     apr_hash_t **locks,
2595251881Speter                                     const char *path,
2596251881Speter                                     svn_depth_t depth,
2597251881Speter                                     apr_pool_t *pool)
2598251881Speter{
2599251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2600251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2601251881Speter  apr_array_header_t *list;
2602251881Speter  const char *full_url, *abs_path;
2603251881Speter  int i;
2604251881Speter
2605251881Speter  /* Figure out the repository abspath from PATH. */
2606251881Speter  full_url = svn_path_url_add_component2(sess->url, path, pool);
2607251881Speter  SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2608251881Speter  abs_path = svn_fspath__canonicalize(abs_path, pool);
2609251881Speter
2610251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2611251881Speter
2612251881Speter  /* Servers before 1.2 doesn't support locking.  Check this here. */
2613251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2614251881Speter                                 N_("Server doesn't support the get-lock "
2615251881Speter                                    "command")));
2616251881Speter
2617251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2618251881Speter
2619251881Speter  *locks = apr_hash_make(pool);
2620251881Speter
2621251881Speter  for (i = 0; i < list->nelts; ++i)
2622251881Speter    {
2623251881Speter      svn_lock_t *lock;
2624251881Speter      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2625251881Speter
2626251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
2627251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2628251881Speter                                _("Lock element not a list"));
2629251881Speter      SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2630251881Speter
2631251881Speter      /* Filter out unwanted paths.  Since Subversion only allows
2632251881Speter         locks on files, we can treat depth=immediates the same as
2633251881Speter         depth=files for filtering purposes.  Meaning, we'll keep
2634251881Speter         this lock if:
2635251881Speter
2636251881Speter         a) its path is the very path we queried, or
2637251881Speter         b) we've asked for a fully recursive answer, or
2638251881Speter         c) we've asked for depth=files or depth=immediates, and this
2639251881Speter            lock is on an immediate child of our query path.
2640251881Speter      */
2641251881Speter      if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2642251881Speter        {
2643251881Speter          svn_hash_sets(*locks, lock->path, lock);
2644251881Speter        }
2645251881Speter      else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2646251881Speter        {
2647251881Speter          const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2648251881Speter          if (relpath && (svn_path_component_count(relpath) == 1))
2649251881Speter            svn_hash_sets(*locks, lock->path, lock);
2650251881Speter        }
2651251881Speter    }
2652251881Speter
2653251881Speter  return SVN_NO_ERROR;
2654251881Speter}
2655251881Speter
2656251881Speter
2657251881Speterstatic svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2658251881Speter                                  svn_revnum_t revision,
2659251881Speter                                  svn_revnum_t low_water_mark,
2660251881Speter                                  svn_boolean_t send_deltas,
2661251881Speter                                  const svn_delta_editor_t *editor,
2662251881Speter                                  void *edit_baton,
2663251881Speter                                  apr_pool_t *pool)
2664251881Speter{
2665251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2666251881Speter
2667251881Speter  SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2668251881Speter                                       low_water_mark, send_deltas));
2669251881Speter
2670251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2671251881Speter                                 N_("Server doesn't support the replay "
2672251881Speter                                    "command")));
2673251881Speter
2674251881Speter  SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2675251881Speter                                   NULL, TRUE));
2676251881Speter
2677299742Sdim  return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2678251881Speter}
2679251881Speter
2680251881Speter
2681251881Speterstatic svn_error_t *
2682251881Speterra_svn_replay_range(svn_ra_session_t *session,
2683251881Speter                    svn_revnum_t start_revision,
2684251881Speter                    svn_revnum_t end_revision,
2685251881Speter                    svn_revnum_t low_water_mark,
2686251881Speter                    svn_boolean_t send_deltas,
2687251881Speter                    svn_ra_replay_revstart_callback_t revstart_func,
2688251881Speter                    svn_ra_replay_revfinish_callback_t revfinish_func,
2689251881Speter                    void *replay_baton,
2690251881Speter                    apr_pool_t *pool)
2691251881Speter{
2692251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2693251881Speter  apr_pool_t *iterpool;
2694251881Speter  svn_revnum_t rev;
2695251881Speter  svn_boolean_t drive_aborted = FALSE;
2696251881Speter
2697251881Speter  SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2698251881Speter                                             start_revision, end_revision,
2699251881Speter                                             low_water_mark, send_deltas));
2700251881Speter
2701251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2702251881Speter                                 N_("Server doesn't support the "
2703251881Speter                                    "replay-range command")));
2704251881Speter
2705251881Speter  iterpool = svn_pool_create(pool);
2706251881Speter  for (rev = start_revision; rev <= end_revision; rev++)
2707251881Speter    {
2708251881Speter      const svn_delta_editor_t *editor;
2709251881Speter      void *edit_baton;
2710251881Speter      apr_hash_t *rev_props;
2711251881Speter      const char *word;
2712251881Speter      apr_array_header_t *list;
2713251881Speter
2714251881Speter      svn_pool_clear(iterpool);
2715251881Speter
2716251881Speter      SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2717251881Speter                                     "wl", &word, &list));
2718251881Speter      if (strcmp(word, "revprops") != 0)
2719251881Speter        return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2720251881Speter                                 _("Expected 'revprops', found '%s'"),
2721251881Speter                                 word);
2722251881Speter
2723251881Speter      SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2724251881Speter
2725251881Speter      SVN_ERR(revstart_func(rev, replay_baton,
2726251881Speter                            &editor, &edit_baton,
2727251881Speter                            rev_props,
2728251881Speter                            iterpool));
2729251881Speter      SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2730251881Speter                                       editor, edit_baton,
2731251881Speter                                       &drive_aborted, TRUE));
2732251881Speter      /* If drive_editor2() aborted the commit, do NOT try to call
2733251881Speter         revfinish_func and commit the transaction! */
2734251881Speter      if (drive_aborted) {
2735251881Speter        svn_pool_destroy(iterpool);
2736251881Speter        return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2737251881Speter                                _("Error while replaying commit"));
2738251881Speter      }
2739251881Speter      SVN_ERR(revfinish_func(rev, replay_baton,
2740251881Speter                             editor, edit_baton,
2741251881Speter                             rev_props,
2742251881Speter                             iterpool));
2743251881Speter    }
2744251881Speter  svn_pool_destroy(iterpool);
2745251881Speter
2746299742Sdim  return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, ""));
2747251881Speter}
2748251881Speter
2749251881Speter
2750251881Speterstatic svn_error_t *
2751251881Speterra_svn_has_capability(svn_ra_session_t *session,
2752251881Speter                      svn_boolean_t *has,
2753251881Speter                      const char *capability,
2754251881Speter                      apr_pool_t *pool)
2755251881Speter{
2756251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2757251881Speter  static const char* capabilities[][2] =
2758251881Speter  {
2759251881Speter      /* { ra capability string, svn:// wire capability string} */
2760251881Speter      {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2761251881Speter      {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2762251881Speter      {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2763251881Speter      {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2764251881Speter      {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2765251881Speter      {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2766251881Speter      {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2767251881Speter      {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2768251881Speter                                          SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2769251881Speter      {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2770251881Speter                                       SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2771251881Speter
2772251881Speter      {NULL, NULL} /* End of list marker */
2773251881Speter  };
2774251881Speter  int i;
2775251881Speter
2776251881Speter  *has = FALSE;
2777251881Speter
2778251881Speter  for (i = 0; capabilities[i][0]; i++)
2779251881Speter    {
2780251881Speter      if (strcmp(capability, capabilities[i][0]) == 0)
2781251881Speter        {
2782251881Speter          *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2783251881Speter          return SVN_NO_ERROR;
2784251881Speter        }
2785251881Speter    }
2786251881Speter
2787251881Speter  return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2788251881Speter                           _("Don't know anything about capability '%s'"),
2789251881Speter                           capability);
2790251881Speter}
2791251881Speter
2792251881Speterstatic svn_error_t *
2793251881Speterra_svn_get_deleted_rev(svn_ra_session_t *session,
2794251881Speter                       const char *path,
2795251881Speter                       svn_revnum_t peg_revision,
2796251881Speter                       svn_revnum_t end_revision,
2797251881Speter                       svn_revnum_t *revision_deleted,
2798251881Speter                       apr_pool_t *pool)
2799251881Speter
2800251881Speter{
2801251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2802251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
2803251881Speter
2804251881Speter  /* Transmit the parameters. */
2805251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2806251881Speter                                               peg_revision, end_revision));
2807251881Speter
2808251881Speter  /* Servers before 1.6 don't support this command.  Check for this here. */
2809251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2810251881Speter                                 N_("'get-deleted-rev' not implemented")));
2811251881Speter
2812299742Sdim  return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r",
2813299742Sdim                                                       revision_deleted));
2814251881Speter}
2815251881Speter
2816251881Speterstatic svn_error_t *
2817251881Speterra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2818251881Speter                                      svn_delta_shim_callbacks_t *callbacks)
2819251881Speter{
2820251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2821251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
2822251881Speter
2823251881Speter  conn->shim_callbacks = callbacks;
2824251881Speter
2825251881Speter  return SVN_NO_ERROR;
2826251881Speter}
2827251881Speter
2828251881Speterstatic svn_error_t *
2829251881Speterra_svn_get_inherited_props(svn_ra_session_t *session,
2830251881Speter                           apr_array_header_t **iprops,
2831251881Speter                           const char *path,
2832251881Speter                           svn_revnum_t revision,
2833251881Speter                           apr_pool_t *result_pool,
2834251881Speter                           apr_pool_t *scratch_pool)
2835251881Speter{
2836251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2837251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
2838251881Speter  apr_array_header_t *iproplist;
2839299742Sdim  svn_boolean_t iprop_capable;
2840251881Speter
2841299742Sdim  SVN_ERR(ra_svn_has_capability(session, &iprop_capable,
2842299742Sdim                                SVN_RA_CAPABILITY_INHERITED_PROPS,
2843299742Sdim                                scratch_pool));
2844299742Sdim
2845299742Sdim  /* If we don't support native iprop handling, use the implementation
2846299742Sdim     in libsvn_ra */
2847299742Sdim  if (!iprop_capable)
2848299742Sdim    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
2849299742Sdim
2850251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2851251881Speter                                           path, revision));
2852251881Speter  SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2853251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2854251881Speter  SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2855251881Speter                          scratch_pool));
2856251881Speter
2857251881Speter  return SVN_NO_ERROR;
2858251881Speter}
2859251881Speter
2860251881Speterstatic const svn_ra__vtable_t ra_svn_vtable = {
2861251881Speter  svn_ra_svn_version,
2862251881Speter  ra_svn_get_description,
2863251881Speter  ra_svn_get_schemes,
2864251881Speter  ra_svn_open,
2865299742Sdim  ra_svn_dup_session,
2866251881Speter  ra_svn_reparent,
2867251881Speter  ra_svn_get_session_url,
2868251881Speter  ra_svn_get_latest_rev,
2869251881Speter  ra_svn_get_dated_rev,
2870251881Speter  ra_svn_change_rev_prop,
2871251881Speter  ra_svn_rev_proplist,
2872251881Speter  ra_svn_rev_prop,
2873251881Speter  ra_svn_commit,
2874251881Speter  ra_svn_get_file,
2875251881Speter  ra_svn_get_dir,
2876251881Speter  ra_svn_get_mergeinfo,
2877251881Speter  ra_svn_update,
2878251881Speter  ra_svn_switch,
2879251881Speter  ra_svn_status,
2880251881Speter  ra_svn_diff,
2881251881Speter  ra_svn_log,
2882251881Speter  ra_svn_check_path,
2883251881Speter  ra_svn_stat,
2884251881Speter  ra_svn_get_uuid,
2885251881Speter  ra_svn_get_repos_root,
2886251881Speter  ra_svn_get_locations,
2887251881Speter  ra_svn_get_location_segments,
2888251881Speter  ra_svn_get_file_revs,
2889251881Speter  ra_svn_lock,
2890251881Speter  ra_svn_unlock,
2891251881Speter  ra_svn_get_lock,
2892251881Speter  ra_svn_get_locks,
2893251881Speter  ra_svn_replay,
2894251881Speter  ra_svn_has_capability,
2895251881Speter  ra_svn_replay_range,
2896251881Speter  ra_svn_get_deleted_rev,
2897251881Speter  ra_svn_register_editor_shim_callbacks,
2898251881Speter  ra_svn_get_inherited_props
2899251881Speter};
2900251881Speter
2901251881Spetersvn_error_t *
2902251881Spetersvn_ra_svn__init(const svn_version_t *loader_version,
2903251881Speter                 const svn_ra__vtable_t **vtable,
2904251881Speter                 apr_pool_t *pool)
2905251881Speter{
2906251881Speter  static const svn_version_checklist_t checklist[] =
2907251881Speter    {
2908251881Speter      { "svn_subr",  svn_subr_version },
2909251881Speter      { "svn_delta", svn_delta_version },
2910251881Speter      { NULL, NULL }
2911251881Speter    };
2912251881Speter
2913262253Speter  SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2914251881Speter
2915251881Speter  /* Simplified version check to make sure we can safely use the
2916251881Speter     VTABLE parameter. The RA loader does a more exhaustive check. */
2917251881Speter  if (loader_version->major != SVN_VER_MAJOR)
2918251881Speter    {
2919251881Speter      return svn_error_createf
2920251881Speter        (SVN_ERR_VERSION_MISMATCH, NULL,
2921251881Speter         _("Unsupported RA loader version (%d) for ra_svn"),
2922251881Speter         loader_version->major);
2923251881Speter    }
2924251881Speter
2925251881Speter  *vtable = &ra_svn_vtable;
2926251881Speter
2927251881Speter#ifdef SVN_HAVE_SASL
2928251881Speter  SVN_ERR(svn_ra_svn__sasl_init());
2929251881Speter#endif
2930251881Speter
2931251881Speter  return SVN_NO_ERROR;
2932251881Speter}
2933251881Speter
2934251881Speter/* Compatibility wrapper for the 1.1 and before API. */
2935251881Speter#define NAME "ra_svn"
2936251881Speter#define DESCRIPTION RA_SVN_DESCRIPTION
2937251881Speter#define VTBL ra_svn_vtable
2938251881Speter#define INITFUNC svn_ra_svn__init
2939251881Speter#define COMPAT_INITFUNC svn_ra_svn_init
2940251881Speter#include "../libsvn_ra/wrapper_template.h"
2941