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{
236251881Speter  return 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
371251881Speter/* (Note: *ARGV is an output parameter.) */
372251881Speterstatic svn_error_t *find_tunnel_agent(const char *tunnel,
373251881Speter                                      const char *hostinfo,
374251881Speter                                      const char ***argv,
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;
380251881Speter  apr_size_t len;
381251881Speter  apr_status_t status;
382251881Speter  int n;
383251881Speter
384251881Speter  /* Look up the tunnel specification in config. */
385251881Speter  cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
386251881Speter  svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
387251881Speter
388251881Speter  /* We have one predefined tunnel scheme, if it isn't overridden by config. */
389251881Speter  if (!val && strcmp(tunnel, "ssh") == 0)
390251881Speter    {
391251881Speter      /* Killing the tunnel agent with SIGTERM leads to unsightly
392251881Speter       * stderr output from ssh, unless we pass -q.
393251881Speter       * The "-q" option to ssh is widely supported: all versions of
394251881Speter       * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
395251881Speter       * versions have it too. If the user is using some other ssh
396251881Speter       * implementation that doesn't accept it, they can override it
397251881Speter       * in the [tunnels] section of the config. */
398251881Speter      val = "$SVN_SSH ssh -q";
399251881Speter    }
400251881Speter
401251881Speter  if (!val || !*val)
402251881Speter    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
403251881Speter                             _("Undefined tunnel scheme '%s'"), tunnel);
404251881Speter
405251881Speter  /* If the scheme definition begins with "$varname", it means there
406251881Speter   * is an environment variable which can override the command. */
407251881Speter  if (*val == '$')
408251881Speter    {
409251881Speter      val++;
410251881Speter      len = strcspn(val, " ");
411251881Speter      var = apr_pstrmemdup(pool, val, len);
412251881Speter      cmd = getenv(var);
413251881Speter      if (!cmd)
414251881Speter        {
415251881Speter          cmd = val + len;
416251881Speter          while (*cmd == ' ')
417251881Speter            cmd++;
418251881Speter          if (!*cmd)
419251881Speter            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
420251881Speter                                     _("Tunnel scheme %s requires environment "
421251881Speter                                       "variable %s to be defined"), tunnel,
422251881Speter                                     var);
423251881Speter        }
424251881Speter    }
425251881Speter  else
426251881Speter    cmd = val;
427251881Speter
428251881Speter  /* Tokenize the command into a list of arguments. */
429251881Speter  status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
430251881Speter  if (status != APR_SUCCESS)
431251881Speter    return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
432251881Speter
433251881Speter  /* Append the fixed arguments to the result. */
434251881Speter  for (n = 0; cmd_argv[n] != NULL; n++)
435251881Speter    ;
436251881Speter  *argv = apr_palloc(pool, (n + 4) * sizeof(char *));
437251881Speter  memcpy((void *) *argv, cmd_argv, n * sizeof(char *));
438251881Speter  (*argv)[n++] = svn_path_uri_decode(hostinfo, pool);
439251881Speter  (*argv)[n++] = "svnserve";
440251881Speter  (*argv)[n++] = "-t";
441251881Speter  (*argv)[n] = NULL;
442251881Speter
443251881Speter  return SVN_NO_ERROR;
444251881Speter}
445251881Speter
446251881Speter/* This function handles any errors which occur in the child process
447251881Speter * created for a tunnel agent.  We write the error out as a command
448251881Speter * failure; the code in ra_svn_open() to read the server's greeting
449251881Speter * will see the error and return it to the caller. */
450251881Speterstatic void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
451251881Speter                                       const char *desc)
452251881Speter{
453251881Speter  svn_ra_svn_conn_t *conn;
454251881Speter  apr_file_t *in_file, *out_file;
455251881Speter  svn_error_t *err;
456251881Speter
457251881Speter  if (apr_file_open_stdin(&in_file, pool)
458251881Speter      || apr_file_open_stdout(&out_file, pool))
459251881Speter    return;
460251881Speter
461251881Speter  conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
462251881Speter                                 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
463251881Speter                                 0, pool);
464251881Speter  err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
465251881Speter  svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
466251881Speter  svn_error_clear(err);
467251881Speter  svn_error_clear(svn_ra_svn__flush(conn, pool));
468251881Speter}
469251881Speter
470251881Speter/* (Note: *CONN is an output parameter.) */
471251881Speterstatic svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
472251881Speter                                apr_pool_t *pool)
473251881Speter{
474251881Speter  apr_status_t status;
475251881Speter  apr_proc_t *proc;
476251881Speter  apr_procattr_t *attr;
477251881Speter  svn_error_t *err;
478251881Speter
479251881Speter  status = apr_procattr_create(&attr, pool);
480251881Speter  if (status == APR_SUCCESS)
481251881Speter    status = apr_procattr_io_set(attr, 1, 1, 0);
482251881Speter  if (status == APR_SUCCESS)
483251881Speter    status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
484251881Speter  if (status == APR_SUCCESS)
485251881Speter    status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
486251881Speter  proc = apr_palloc(pool, sizeof(*proc));
487251881Speter  if (status == APR_SUCCESS)
488251881Speter    status = apr_proc_create(proc, *args, args, NULL, attr, pool);
489251881Speter  if (status != APR_SUCCESS)
490251881Speter    return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
491251881Speter                            svn_error_wrap_apr(status,
492251881Speter                                               _("Can't create tunnel")), NULL);
493251881Speter
494251881Speter  /* Arrange for the tunnel agent to get a SIGTERM on pool
495251881Speter   * cleanup.  This is a little extreme, but the alternatives
496251881Speter   * weren't working out.
497251881Speter   *
498251881Speter   * Closing the pipes and waiting for the process to die
499251881Speter   * was prone to mysterious hangs which are difficult to
500251881Speter   * diagnose (e.g. svnserve dumps core due to unrelated bug;
501251881Speter   * sshd goes into zombie state; ssh connection is never
502251881Speter   * closed; ssh never terminates).
503251881Speter   * See also the long dicussion in issue #2580 if you really
504251881Speter   * want to know various reasons for these problems and
505251881Speter   * the different opinions on this issue.
506251881Speter   *
507251881Speter   * On Win32, APR does not support KILL_ONLY_ONCE. It only has
508251881Speter   * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
509251881Speter   * KILL_ALWAYS, which immediately calls TerminateProcess().
510251881Speter   * This instantly kills the tunnel, leaving sshd and svnserve
511251881Speter   * on a remote machine running indefinitely. These processes
512251881Speter   * accumulate. The problem is most often seen with a fast client
513251881Speter   * machine and a modest internet connection, as the tunnel
514251881Speter   * is killed before being able to gracefully complete the
515251881Speter   * session. In that case, svn is unusable 100% of the time on
516251881Speter   * the windows machine. Thus, on Win32, we use KILL_NEVER and
517251881Speter   * take the lesser of two evils.
518251881Speter   */
519251881Speter#ifdef WIN32
520251881Speter  apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
521251881Speter#else
522251881Speter  apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
523251881Speter#endif
524251881Speter
525251881Speter  /* APR pipe objects inherit by default.  But we don't want the
526251881Speter   * tunnel agent's pipes held open by future child processes
527251881Speter   * (such as other ra_svn sessions), so turn that off. */
528251881Speter  apr_file_inherit_unset(proc->in);
529251881Speter  apr_file_inherit_unset(proc->out);
530251881Speter
531251881Speter  /* Guard against dotfile output to stdout on the server. */
532251881Speter  *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in,
533251881Speter                                  SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
534251881Speter                                  0, 0, pool);
535251881Speter  err = svn_ra_svn__skip_leading_garbage(*conn, pool);
536251881Speter  if (err)
537251881Speter    return svn_error_quick_wrap(
538251881Speter             err,
539251881Speter             _("To better debug SSH connection problems, remove the -q "
540251881Speter               "option from 'ssh' in the [tunnels] section of your "
541251881Speter               "Subversion configuration file."));
542251881Speter
543251881Speter  return SVN_NO_ERROR;
544251881Speter}
545251881Speter
546251881Speter/* Parse URL inot URI, validating it and setting the default port if none
547251881Speter   was given.  Allocate the URI fileds out of POOL. */
548251881Speterstatic svn_error_t *parse_url(const char *url, apr_uri_t *uri,
549251881Speter                              apr_pool_t *pool)
550251881Speter{
551251881Speter  apr_status_t apr_err;
552251881Speter
553251881Speter  apr_err = apr_uri_parse(pool, url, uri);
554251881Speter
555251881Speter  if (apr_err != 0)
556251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
557251881Speter                             _("Illegal svn repository URL '%s'"), url);
558251881Speter
559251881Speter  if (! uri->port)
560251881Speter    uri->port = SVN_RA_SVN_PORT;
561251881Speter
562251881Speter  return SVN_NO_ERROR;
563251881Speter}
564251881Speter
565251881Speter/* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
566251881Speter   URI is a parsed version of URL.  CALLBACKS and CALLBACKS_BATON
567251881Speter   are provided by the caller of ra_svn_open. If tunnel_argv is non-null,
568251881Speter   it points to a program argument list to use when invoking the tunnel agent.
569251881Speter*/
570251881Speterstatic svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
571251881Speter                                 const char *url,
572251881Speter                                 const apr_uri_t *uri,
573251881Speter                                 const char **tunnel_argv,
574251881Speter                                 const svn_ra_callbacks2_t *callbacks,
575251881Speter                                 void *callbacks_baton,
576251881Speter                                 apr_pool_t *pool)
577251881Speter{
578251881Speter  svn_ra_svn__session_baton_t *sess;
579251881Speter  svn_ra_svn_conn_t *conn;
580251881Speter  apr_socket_t *sock;
581251881Speter  apr_uint64_t minver, maxver;
582251881Speter  apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
583251881Speter  const char *client_string = NULL;
584251881Speter
585251881Speter  sess = apr_palloc(pool, sizeof(*sess));
586251881Speter  sess->pool = pool;
587251881Speter  sess->is_tunneled = (tunnel_argv != NULL);
588251881Speter  sess->url = apr_pstrdup(pool, url);
589251881Speter  sess->user = uri->user;
590251881Speter  sess->hostname = uri->hostname;
591251881Speter  sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
592251881Speter                                    uri->port);
593251881Speter  sess->tunnel_argv = tunnel_argv;
594251881Speter  sess->callbacks = callbacks;
595251881Speter  sess->callbacks_baton = callbacks_baton;
596251881Speter  sess->bytes_read = sess->bytes_written = 0;
597251881Speter
598251881Speter  if (tunnel_argv)
599251881Speter    SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
600251881Speter  else
601251881Speter    {
602251881Speter      SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool));
603251881Speter      conn = svn_ra_svn_create_conn3(sock, NULL, NULL,
604251881Speter                                     SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
605251881Speter                                     0, 0, pool);
606251881Speter    }
607251881Speter
608251881Speter  /* Build the useragent string, querying the client for any
609251881Speter     customizations it wishes to note.  For historical reasons, we
610251881Speter     still deliver the hard-coded client version info
611251881Speter     (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
612251881Speter     separately in the protocol/capabilities handshake below.  But the
613251881Speter     commit logic wants the combined form for use with the
614251881Speter     SVN_PROP_TXN_USER_AGENT ephemeral property because that's
615251881Speter     consistent with our DAV approach.  */
616251881Speter  if (sess->callbacks->get_client_string != NULL)
617251881Speter    SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
618251881Speter                                               &client_string, pool));
619251881Speter  if (client_string)
620251881Speter    sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
621251881Speter                                  client_string, (char *)NULL);
622251881Speter  else
623251881Speter    sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
624251881Speter
625251881Speter  /* Make sure we set conn->session before reading from it,
626251881Speter   * because the reader and writer functions expect a non-NULL value. */
627251881Speter  sess->conn = conn;
628251881Speter  conn->session = sess;
629251881Speter
630251881Speter  /* Read server's greeting. */
631251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
632251881Speter                                        &mechlist, &server_caplist));
633251881Speter
634251881Speter  /* We support protocol version 2. */
635251881Speter  if (minver > 2)
636251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
637251881Speter                             _("Server requires minimum version %d"),
638251881Speter                             (int) minver);
639251881Speter  if (maxver < 2)
640251881Speter    return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
641251881Speter                             _("Server only supports versions up to %d"),
642251881Speter                             (int) maxver);
643251881Speter  SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
644251881Speter
645251881Speter  /* All released versions of Subversion support edit-pipeline,
646251881Speter   * so we do not support servers that do not. */
647251881Speter  if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
648251881Speter    return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
649251881Speter                            _("Server does not support edit pipelining"));
650251881Speter
651251881Speter  /* In protocol version 2, we send back our protocol version, our
652251881Speter   * capability list, and the URL, and subsequently there is an auth
653251881Speter   * request. */
654251881Speter  /* Client-side capabilities list: */
655251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
656251881Speter                                  (apr_uint64_t) 2,
657251881Speter                                  SVN_RA_SVN_CAP_EDIT_PIPELINE,
658251881Speter                                  SVN_RA_SVN_CAP_SVNDIFF1,
659251881Speter                                  SVN_RA_SVN_CAP_ABSENT_ENTRIES,
660251881Speter                                  SVN_RA_SVN_CAP_DEPTH,
661251881Speter                                  SVN_RA_SVN_CAP_MERGEINFO,
662251881Speter                                  SVN_RA_SVN_CAP_LOG_REVPROPS,
663251881Speter                                  url,
664251881Speter                                  SVN_RA_SVN__DEFAULT_USERAGENT,
665251881Speter                                  client_string));
666251881Speter  SVN_ERR(handle_auth_request(sess, pool));
667251881Speter
668251881Speter  /* This is where the security layer would go into effect if we
669251881Speter   * supported security layers, which is a ways off. */
670251881Speter
671251881Speter  /* Read the repository's uuid and root URL, and perhaps learn more
672251881Speter     capabilities that weren't available before now. */
673251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
674251881Speter                                        &conn->repos_root, &repos_caplist));
675251881Speter  if (repos_caplist)
676251881Speter    SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
677251881Speter
678251881Speter  if (conn->repos_root)
679251881Speter    {
680251881Speter      conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
681251881Speter      /* We should check that the returned string is a prefix of url, since
682251881Speter         that's the API guarantee, but this isn't true for 1.0 servers.
683251881Speter         Checking the length prevents client crashes. */
684251881Speter      if (strlen(conn->repos_root) > strlen(url))
685251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
686251881Speter                                _("Impossibly long repository root from "
687251881Speter                                  "server"));
688251881Speter    }
689251881Speter
690251881Speter  *sess_p = sess;
691251881Speter
692251881Speter  return SVN_NO_ERROR;
693251881Speter}
694251881Speter
695251881Speter
696251881Speter#ifdef SVN_HAVE_SASL
697251881Speter#define RA_SVN_DESCRIPTION \
698251881Speter  N_("Module for accessing a repository using the svn network protocol.\n" \
699251881Speter     "  - with Cyrus SASL authentication")
700251881Speter#else
701251881Speter#define RA_SVN_DESCRIPTION \
702251881Speter  N_("Module for accessing a repository using the svn network protocol.")
703251881Speter#endif
704251881Speter
705262253Speterstatic const char *ra_svn_get_description(apr_pool_t *pool)
706251881Speter{
707251881Speter  return _(RA_SVN_DESCRIPTION);
708251881Speter}
709251881Speter
710251881Speterstatic const char * const *
711251881Speterra_svn_get_schemes(apr_pool_t *pool)
712251881Speter{
713251881Speter  static const char *schemes[] = { "svn", NULL };
714251881Speter
715251881Speter  return schemes;
716251881Speter}
717251881Speter
718251881Speter
719251881Speter
720251881Speterstatic svn_error_t *ra_svn_open(svn_ra_session_t *session,
721251881Speter                                const char **corrected_url,
722251881Speter                                const char *url,
723251881Speter                                const svn_ra_callbacks2_t *callbacks,
724251881Speter                                void *callback_baton,
725251881Speter                                apr_hash_t *config,
726251881Speter                                apr_pool_t *pool)
727251881Speter{
728251881Speter  apr_pool_t *sess_pool = svn_pool_create(pool);
729251881Speter  svn_ra_svn__session_baton_t *sess;
730251881Speter  const char *tunnel, **tunnel_argv;
731251881Speter  apr_uri_t uri;
732251881Speter  svn_config_t *cfg, *cfg_client;
733251881Speter
734251881Speter  /* We don't support server-prescribed redirections in ra-svn. */
735251881Speter  if (corrected_url)
736251881Speter    *corrected_url = NULL;
737251881Speter
738251881Speter  SVN_ERR(parse_url(url, &uri, sess_pool));
739251881Speter
740251881Speter  parse_tunnel(url, &tunnel, pool);
741251881Speter
742251881Speter  if (tunnel)
743251881Speter    SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
744251881Speter                              pool));
745251881Speter  else
746251881Speter    tunnel_argv = NULL;
747251881Speter
748251881Speter  cfg_client = config
749251881Speter               ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
750251881Speter               : NULL;
751251881Speter  cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
752251881Speter  svn_auth_set_parameter(callbacks->auth_baton,
753251881Speter                         SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
754251881Speter  svn_auth_set_parameter(callbacks->auth_baton,
755251881Speter                         SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
756251881Speter
757251881Speter  /* We open the session in a subpool so we can get rid of it if we
758251881Speter     reparent with a server that doesn't support reparenting. */
759251881Speter  SVN_ERR(open_session(&sess, url, &uri, tunnel_argv,
760251881Speter                       callbacks, callback_baton, sess_pool));
761251881Speter  session->priv = sess;
762251881Speter
763251881Speter  return SVN_NO_ERROR;
764251881Speter}
765251881Speter
766251881Speterstatic svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
767251881Speter                                    const char *url,
768251881Speter                                    apr_pool_t *pool)
769251881Speter{
770251881Speter  svn_ra_svn__session_baton_t *sess = ra_session->priv;
771251881Speter  svn_ra_svn_conn_t *conn = sess->conn;
772251881Speter  svn_error_t *err;
773251881Speter  apr_pool_t *sess_pool;
774251881Speter  svn_ra_svn__session_baton_t *new_sess;
775251881Speter  apr_uri_t uri;
776251881Speter
777251881Speter  SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
778251881Speter  err = handle_auth_request(sess, pool);
779251881Speter  if (! err)
780251881Speter    {
781251881Speter      SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
782251881Speter      sess->url = apr_pstrdup(sess->pool, url);
783251881Speter      return SVN_NO_ERROR;
784251881Speter    }
785251881Speter  else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
786251881Speter    return err;
787251881Speter
788251881Speter  /* Servers before 1.4 doesn't support this command; try to reconnect
789251881Speter     instead. */
790251881Speter  svn_error_clear(err);
791251881Speter  /* Create a new subpool of the RA session pool. */
792251881Speter  sess_pool = svn_pool_create(ra_session->pool);
793251881Speter  err = parse_url(url, &uri, sess_pool);
794251881Speter  if (! err)
795251881Speter    err = open_session(&new_sess, url, &uri, sess->tunnel_argv,
796251881Speter                       sess->callbacks, sess->callbacks_baton, sess_pool);
797251881Speter  /* We destroy the new session pool on error, since it is allocated in
798251881Speter     the main session pool. */
799251881Speter  if (err)
800251881Speter    {
801251881Speter      svn_pool_destroy(sess_pool);
802251881Speter      return err;
803251881Speter    }
804251881Speter
805251881Speter  /* We have a new connection, assign it and destroy the old. */
806251881Speter  ra_session->priv = new_sess;
807251881Speter  svn_pool_destroy(sess->pool);
808251881Speter
809251881Speter  return SVN_NO_ERROR;
810251881Speter}
811251881Speter
812251881Speterstatic svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
813251881Speter                                           const char **url, apr_pool_t *pool)
814251881Speter{
815251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
816251881Speter  *url = apr_pstrdup(pool, sess->url);
817251881Speter  return SVN_NO_ERROR;
818251881Speter}
819251881Speter
820251881Speterstatic svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
821251881Speter                                          svn_revnum_t *rev, apr_pool_t *pool)
822251881Speter{
823251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
824251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
825251881Speter
826251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
827251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
828251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
829251881Speter  return SVN_NO_ERROR;
830251881Speter}
831251881Speter
832251881Speterstatic svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
833251881Speter                                         svn_revnum_t *rev, apr_time_t tm,
834251881Speter                                         apr_pool_t *pool)
835251881Speter{
836251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
837251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
838251881Speter
839251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
840251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
841251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
842251881Speter  return SVN_NO_ERROR;
843251881Speter}
844251881Speter
845251881Speter/* Forward declaration. */
846251881Speterstatic svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
847251881Speter                                          svn_boolean_t *has,
848251881Speter                                          const char *capability,
849251881Speter                                          apr_pool_t *pool);
850251881Speter
851251881Speterstatic svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
852251881Speter                                           const char *name,
853251881Speter                                           const svn_string_t *const *old_value_p,
854251881Speter                                           const svn_string_t *value,
855251881Speter                                           apr_pool_t *pool)
856251881Speter{
857251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
858251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
859251881Speter  svn_boolean_t dont_care;
860251881Speter  const svn_string_t *old_value;
861251881Speter  svn_boolean_t has_atomic_revprops;
862251881Speter
863251881Speter  SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
864251881Speter                                SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
865251881Speter                                pool));
866251881Speter
867251881Speter  if (old_value_p)
868251881Speter    {
869251881Speter      /* How did you get past the same check in svn_ra_change_rev_prop2()? */
870251881Speter      SVN_ERR_ASSERT(has_atomic_revprops);
871251881Speter
872251881Speter      dont_care = FALSE;
873251881Speter      old_value = *old_value_p;
874251881Speter    }
875251881Speter  else
876251881Speter    {
877251881Speter      dont_care = TRUE;
878251881Speter      old_value = NULL;
879251881Speter    }
880251881Speter
881251881Speter  if (has_atomic_revprops)
882251881Speter    SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
883251881Speter                                                   value, dont_care,
884251881Speter                                                   old_value));
885251881Speter  else
886251881Speter    SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
887251881Speter                                                  value));
888251881Speter
889251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
890251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
891251881Speter  return SVN_NO_ERROR;
892251881Speter}
893251881Speter
894251881Speterstatic svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
895251881Speter                                    apr_pool_t *pool)
896251881Speter{
897251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
898251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
899251881Speter
900251881Speter  *uuid = conn->uuid;
901251881Speter  return SVN_NO_ERROR;
902251881Speter}
903251881Speter
904251881Speterstatic svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
905251881Speter                                          apr_pool_t *pool)
906251881Speter{
907251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
908251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
909251881Speter
910251881Speter  if (!conn->repos_root)
911251881Speter    return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
912251881Speter                            _("Server did not send repository root"));
913251881Speter  *url = conn->repos_root;
914251881Speter  return SVN_NO_ERROR;
915251881Speter}
916251881Speter
917251881Speterstatic svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
918251881Speter                                        apr_hash_t **props, apr_pool_t *pool)
919251881Speter{
920251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
921251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
922251881Speter  apr_array_header_t *proplist;
923251881Speter
924251881Speter  SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
925251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
926251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
927251881Speter  SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
928251881Speter  return SVN_NO_ERROR;
929251881Speter}
930251881Speter
931251881Speterstatic svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
932251881Speter                                    const char *name,
933251881Speter                                    svn_string_t **value, apr_pool_t *pool)
934251881Speter{
935251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
936251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
937251881Speter
938251881Speter  SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
939251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
940251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
941251881Speter  return SVN_NO_ERROR;
942251881Speter}
943251881Speter
944251881Speterstatic svn_error_t *ra_svn_end_commit(void *baton)
945251881Speter{
946251881Speter  ra_svn_commit_callback_baton_t *ccb = baton;
947251881Speter  svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
948251881Speter
949251881Speter  SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
950251881Speter  SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
951251881Speter                                 "r(?c)(?c)?(?c)",
952251881Speter                                 &(commit_info->revision),
953251881Speter                                 &(commit_info->date),
954251881Speter                                 &(commit_info->author),
955251881Speter                                 &(commit_info->post_commit_err)));
956251881Speter
957251881Speter  if (ccb->callback)
958251881Speter    SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
959251881Speter
960251881Speter  return SVN_NO_ERROR;
961251881Speter}
962251881Speter
963251881Speterstatic svn_error_t *ra_svn_commit(svn_ra_session_t *session,
964251881Speter                                  const svn_delta_editor_t **editor,
965251881Speter                                  void **edit_baton,
966251881Speter                                  apr_hash_t *revprop_table,
967251881Speter                                  svn_commit_callback2_t callback,
968251881Speter                                  void *callback_baton,
969251881Speter                                  apr_hash_t *lock_tokens,
970251881Speter                                  svn_boolean_t keep_locks,
971251881Speter                                  apr_pool_t *pool)
972251881Speter{
973251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
974251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
975251881Speter  ra_svn_commit_callback_baton_t *ccb;
976251881Speter  apr_hash_index_t *hi;
977251881Speter  apr_pool_t *iterpool;
978251881Speter  const svn_string_t *log_msg = svn_hash_gets(revprop_table,
979251881Speter                                              SVN_PROP_REVISION_LOG);
980251881Speter
981253734Speter  if (log_msg == NULL &&
982253734Speter      ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
983253734Speter    {
984253734Speter      return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL,
985253734Speter                               _("ra_svn does not support not specifying "
986253734Speter                                 "a log message with pre-1.5 servers; "
987253734Speter                                 "consider passing an empty one, or upgrading "
988253734Speter                                 "the server"));
989253734Speter    }
990253734Speter  else if (log_msg == NULL)
991253734Speter    /* 1.5+ server.  Set LOG_MSG to something, since the 'logmsg' argument
992253734Speter       to the 'commit' protocol command is non-optional; on the server side,
993253734Speter       only REVPROP_TABLE will be used, and LOG_MSG will be ignored.  The
994253734Speter       "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit
995253734Speter       will have a NULL log message (not just "", really NULL).
996253734Speter
997253734Speter       svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was
998253734Speter       present; this was elevated to a protocol promise in r1498550 (and
999253734Speter       later documented in this comment) in order to fix the segmentation
1000253734Speter       fault bug described in the log message of r1498550.*/
1001253734Speter    log_msg = svn_string_create("", pool);
1002253734Speter
1003251881Speter  /* If we're sending revprops other than svn:log, make sure the server won't
1004251881Speter     silently ignore them. */
1005251881Speter  if (apr_hash_count(revprop_table) > 1 &&
1006251881Speter      ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
1007251881Speter    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1008251881Speter                            _("Server doesn't support setting arbitrary "
1009251881Speter                              "revision properties during commit"));
1010251881Speter
1011251881Speter  /* If the server supports ephemeral txnprops, add the one that
1012251881Speter     reports the client's version level string. */
1013251881Speter  if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
1014251881Speter      svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
1015251881Speter    {
1016251881Speter      svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
1017251881Speter                    svn_string_create(SVN_VER_NUMBER, pool));
1018251881Speter      svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
1019251881Speter                    svn_string_create(sess_baton->useragent, pool));
1020251881Speter    }
1021251881Speter
1022251881Speter  /* Tell the server we're starting the commit.
1023251881Speter     Send log message here for backwards compatibility with servers
1024251881Speter     before 1.5. */
1025251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
1026251881Speter                                  log_msg->data));
1027251881Speter  if (lock_tokens)
1028251881Speter    {
1029251881Speter      iterpool = svn_pool_create(pool);
1030251881Speter      for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
1031251881Speter        {
1032251881Speter          const void *key;
1033251881Speter          void *val;
1034251881Speter          const char *path, *token;
1035251881Speter
1036251881Speter          svn_pool_clear(iterpool);
1037251881Speter          apr_hash_this(hi, &key, NULL, &val);
1038251881Speter          path = key;
1039251881Speter          token = val;
1040251881Speter          SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
1041251881Speter        }
1042251881Speter      svn_pool_destroy(iterpool);
1043251881Speter    }
1044251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
1045251881Speter  SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
1046251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1047251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1048251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1049251881Speter
1050251881Speter  /* Remember a few arguments for when the commit is over. */
1051251881Speter  ccb = apr_palloc(pool, sizeof(*ccb));
1052251881Speter  ccb->sess_baton = sess_baton;
1053251881Speter  ccb->pool = pool;
1054251881Speter  ccb->new_rev = NULL;
1055251881Speter  ccb->callback = callback;
1056251881Speter  ccb->callback_baton = callback_baton;
1057251881Speter
1058251881Speter  /* Fetch an editor for the caller to drive.  The editor will call
1059251881Speter   * ra_svn_end_commit() upon close_edit(), at which point we'll fill
1060251881Speter   * in the new_rev, committed_date, and committed_author values. */
1061251881Speter  svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
1062251881Speter                        ra_svn_end_commit, ccb);
1063251881Speter  return SVN_NO_ERROR;
1064251881Speter}
1065251881Speter
1066251881Speter/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
1067251881Speter   const char * repos relative paths and properties for those paths, storing
1068251881Speter   the result as an array of svn_prop_inherited_item_t *items. */
1069251881Speterstatic svn_error_t *
1070251881Speterparse_iproplist(apr_array_header_t **inherited_props,
1071251881Speter                const apr_array_header_t *iproplist,
1072251881Speter                svn_ra_session_t *session,
1073251881Speter                apr_pool_t *result_pool,
1074251881Speter                apr_pool_t *scratch_pool)
1075251881Speter
1076251881Speter{
1077251881Speter  int i;
1078251881Speter  const char *repos_root_url;
1079251881Speter  apr_pool_t *iterpool;
1080251881Speter
1081251881Speter  if (iproplist == NULL)
1082251881Speter    {
1083251881Speter      /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
1084251881Speter         capability we shouldn't be asking for inherited props, but if we
1085251881Speter         did and the server sent back nothing then we'll want to handle
1086251881Speter         that. */
1087251881Speter      *inherited_props = NULL;
1088251881Speter      return SVN_NO_ERROR;
1089251881Speter    }
1090251881Speter
1091251881Speter  SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
1092251881Speter
1093251881Speter  *inherited_props = apr_array_make(
1094251881Speter    result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
1095251881Speter
1096251881Speter  iterpool = svn_pool_create(scratch_pool);
1097251881Speter
1098251881Speter  for (i = 0; i < iproplist->nelts; i++)
1099251881Speter    {
1100251881Speter      apr_array_header_t *iprop_list;
1101251881Speter      char *parent_rel_path;
1102251881Speter      apr_hash_t *iprops;
1103251881Speter      apr_hash_index_t *hi;
1104251881Speter      svn_prop_inherited_item_t *new_iprop =
1105251881Speter        apr_palloc(result_pool, sizeof(*new_iprop));
1106251881Speter      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
1107251881Speter                                              svn_ra_svn_item_t);
1108251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
1109251881Speter        return svn_error_create(
1110251881Speter          SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1111251881Speter          _("Inherited proplist element not a list"));
1112251881Speter
1113251881Speter      svn_pool_clear(iterpool);
1114251881Speter
1115251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
1116251881Speter                                      &parent_rel_path, &iprop_list));
1117251881Speter      SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
1118251881Speter      new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
1119251881Speter                                                           parent_rel_path,
1120251881Speter                                                           result_pool);
1121251881Speter      new_iprop->prop_hash = apr_hash_make(result_pool);
1122251881Speter      for (hi = apr_hash_first(iterpool, iprops);
1123251881Speter           hi;
1124251881Speter           hi = apr_hash_next(hi))
1125251881Speter        {
1126251881Speter          const char *name = svn__apr_hash_index_key(hi);
1127251881Speter          svn_string_t *value = svn__apr_hash_index_val(hi);
1128251881Speter          svn_hash_sets(new_iprop->prop_hash,
1129251881Speter                        apr_pstrdup(result_pool, name),
1130251881Speter                        svn_string_dup(value, result_pool));
1131251881Speter        }
1132251881Speter      APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
1133251881Speter        new_iprop;
1134251881Speter    }
1135251881Speter  svn_pool_destroy(iterpool);
1136251881Speter  return SVN_NO_ERROR;
1137251881Speter}
1138251881Speter
1139251881Speterstatic svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
1140251881Speter                                    svn_revnum_t rev, svn_stream_t *stream,
1141251881Speter                                    svn_revnum_t *fetched_rev,
1142251881Speter                                    apr_hash_t **props,
1143251881Speter                                    apr_pool_t *pool)
1144251881Speter{
1145251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1146251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1147251881Speter  apr_array_header_t *proplist;
1148251881Speter  const char *expected_digest;
1149251881Speter  svn_checksum_t *expected_checksum = NULL;
1150251881Speter  svn_checksum_ctx_t *checksum_ctx;
1151251881Speter  apr_pool_t *iterpool;
1152251881Speter
1153251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
1154251881Speter                                         (props != NULL), (stream != NULL)));
1155251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1156251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
1157251881Speter                                        &expected_digest,
1158251881Speter                                        &rev, &proplist));
1159251881Speter
1160251881Speter  if (fetched_rev)
1161251881Speter    *fetched_rev = rev;
1162251881Speter  if (props)
1163251881Speter    SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1164251881Speter
1165251881Speter  /* We're done if the contents weren't wanted. */
1166251881Speter  if (!stream)
1167251881Speter    return SVN_NO_ERROR;
1168251881Speter
1169251881Speter  if (expected_digest)
1170251881Speter    {
1171251881Speter      SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
1172251881Speter                                     expected_digest, pool));
1173251881Speter      checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1174251881Speter    }
1175251881Speter
1176251881Speter  /* Read the file's contents. */
1177251881Speter  iterpool = svn_pool_create(pool);
1178251881Speter  while (1)
1179251881Speter    {
1180251881Speter      svn_ra_svn_item_t *item;
1181251881Speter
1182251881Speter      svn_pool_clear(iterpool);
1183251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1184251881Speter      if (item->kind != SVN_RA_SVN_STRING)
1185251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1186251881Speter                                _("Non-string as part of file contents"));
1187251881Speter      if (item->u.string->len == 0)
1188251881Speter        break;
1189251881Speter
1190251881Speter      if (expected_checksum)
1191251881Speter        SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
1192251881Speter                                    item->u.string->len));
1193251881Speter
1194251881Speter      SVN_ERR(svn_stream_write(stream, item->u.string->data,
1195251881Speter                               &item->u.string->len));
1196251881Speter    }
1197251881Speter  svn_pool_destroy(iterpool);
1198251881Speter
1199251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1200251881Speter
1201251881Speter  if (expected_checksum)
1202251881Speter    {
1203251881Speter      svn_checksum_t *checksum;
1204251881Speter
1205251881Speter      SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
1206251881Speter      if (!svn_checksum_match(checksum, expected_checksum))
1207251881Speter        return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
1208251881Speter                                         _("Checksum mismatch for '%s'"),
1209251881Speter                                         path);
1210251881Speter    }
1211251881Speter
1212251881Speter  return SVN_NO_ERROR;
1213251881Speter}
1214251881Speter
1215251881Speterstatic svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
1216251881Speter                                   apr_hash_t **dirents,
1217251881Speter                                   svn_revnum_t *fetched_rev,
1218251881Speter                                   apr_hash_t **props,
1219251881Speter                                   const char *path,
1220251881Speter                                   svn_revnum_t rev,
1221251881Speter                                   apr_uint32_t dirent_fields,
1222251881Speter                                   apr_pool_t *pool)
1223251881Speter{
1224251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1225251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1226251881Speter  apr_array_header_t *proplist, *dirlist;
1227251881Speter  int i;
1228251881Speter
1229251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
1230251881Speter                                  rev, (props != NULL), (dirents != NULL)));
1231251881Speter  if (dirent_fields & SVN_DIRENT_KIND)
1232251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
1233251881Speter  if (dirent_fields & SVN_DIRENT_SIZE)
1234251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
1235251881Speter  if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1236251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
1237251881Speter  if (dirent_fields & SVN_DIRENT_CREATED_REV)
1238251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
1239251881Speter  if (dirent_fields & SVN_DIRENT_TIME)
1240251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
1241251881Speter  if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1242251881Speter    SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
1243251881Speter
1244251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1245251881Speter
1246251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1247251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
1248251881Speter                                        &dirlist));
1249251881Speter
1250251881Speter  if (fetched_rev)
1251251881Speter    *fetched_rev = rev;
1252251881Speter  if (props)
1253251881Speter    SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
1254251881Speter
1255251881Speter  /* We're done if dirents aren't wanted. */
1256251881Speter  if (!dirents)
1257251881Speter    return SVN_NO_ERROR;
1258251881Speter
1259251881Speter  /* Interpret the directory list. */
1260251881Speter  *dirents = apr_hash_make(pool);
1261251881Speter  for (i = 0; i < dirlist->nelts; i++)
1262251881Speter    {
1263251881Speter      const char *name, *kind, *cdate, *cauthor;
1264251881Speter      svn_boolean_t has_props;
1265251881Speter      svn_dirent_t *dirent;
1266251881Speter      apr_uint64_t size;
1267251881Speter      svn_revnum_t crev;
1268251881Speter      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
1269251881Speter
1270251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
1271251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1272251881Speter                                _("Dirlist element not a list"));
1273251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
1274251881Speter                                      &name, &kind, &size, &has_props,
1275251881Speter                                      &crev, &cdate, &cauthor));
1276251881Speter      name = svn_relpath_canonicalize(name, pool);
1277251881Speter      dirent = svn_dirent_create(pool);
1278251881Speter      dirent->kind = svn_node_kind_from_word(kind);
1279251881Speter      dirent->size = size;/* FIXME: svn_filesize_t */
1280251881Speter      dirent->has_props = has_props;
1281251881Speter      dirent->created_rev = crev;
1282251881Speter      /* NOTE: the tuple's format string says CDATE may be NULL. But this
1283251881Speter         function does not allow that. The server has always sent us some
1284251881Speter         random date, however, so this just happens to work. But let's
1285251881Speter         be wary of servers that are (improperly) fixed to send NULL.
1286251881Speter
1287251881Speter         Note: they should NOT be "fixed" to send NULL, as that would break
1288251881Speter         any older clients which received that NULL. But we may as well
1289251881Speter         be defensive against a malicous server.  */
1290251881Speter      if (cdate == NULL)
1291251881Speter        dirent->time = 0;
1292251881Speter      else
1293251881Speter        SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
1294251881Speter      dirent->last_author = cauthor;
1295251881Speter      svn_hash_sets(*dirents, name, dirent);
1296251881Speter    }
1297251881Speter
1298251881Speter  return SVN_NO_ERROR;
1299251881Speter}
1300251881Speter
1301251881Speter/* Converts a apr_uint64_t with values TRUE, FALSE or
1302251881Speter   SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
1303251881Speter   to a svn_tristate_t */
1304251881Speterstatic svn_tristate_t
1305251881Speteroptbool_to_tristate(apr_uint64_t v)
1306251881Speter{
1307251881Speter  if (v == TRUE)  /* not just non-zero but exactly equal to 'TRUE' */
1308251881Speter    return svn_tristate_true;
1309251881Speter  if (v == FALSE)
1310251881Speter    return svn_tristate_false;
1311251881Speter
1312251881Speter  return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
1313251881Speter}
1314251881Speter
1315251881Speter/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
1316251881Speter   server, which defaults to youngest. */
1317251881Speterstatic svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
1318251881Speter                                         svn_mergeinfo_catalog_t *catalog,
1319251881Speter                                         const apr_array_header_t *paths,
1320251881Speter                                         svn_revnum_t revision,
1321251881Speter                                         svn_mergeinfo_inheritance_t inherit,
1322251881Speter                                         svn_boolean_t include_descendants,
1323251881Speter                                         apr_pool_t *pool)
1324251881Speter{
1325251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1326251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1327251881Speter  int i;
1328251881Speter  apr_array_header_t *mergeinfo_tuple;
1329251881Speter  svn_ra_svn_item_t *elt;
1330251881Speter  const char *path;
1331251881Speter
1332251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
1333251881Speter  for (i = 0; i < paths->nelts; i++)
1334251881Speter    {
1335251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
1336251881Speter      SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1337251881Speter    }
1338251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
1339251881Speter                                  svn_inheritance_to_word(inherit),
1340251881Speter                                  include_descendants));
1341251881Speter
1342251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1343251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
1344251881Speter
1345251881Speter  *catalog = NULL;
1346251881Speter  if (mergeinfo_tuple->nelts > 0)
1347251881Speter    {
1348251881Speter      *catalog = apr_hash_make(pool);
1349251881Speter      for (i = 0; i < mergeinfo_tuple->nelts; i++)
1350251881Speter        {
1351251881Speter          svn_mergeinfo_t for_path;
1352251881Speter          const char *to_parse;
1353251881Speter
1354251881Speter          elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
1355251881Speter          if (elt->kind != SVN_RA_SVN_LIST)
1356251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1357251881Speter                                    _("Mergeinfo element is not a list"));
1358251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
1359251881Speter                                          &path, &to_parse));
1360251881Speter          SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
1361251881Speter          /* Correct for naughty servers that send "relative" paths
1362251881Speter             with leading slashes! */
1363251881Speter          svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
1364251881Speter        }
1365251881Speter    }
1366251881Speter
1367251881Speter  return SVN_NO_ERROR;
1368251881Speter}
1369251881Speter
1370251881Speterstatic svn_error_t *ra_svn_update(svn_ra_session_t *session,
1371251881Speter                                  const svn_ra_reporter3_t **reporter,
1372251881Speter                                  void **report_baton, svn_revnum_t rev,
1373251881Speter                                  const char *target, svn_depth_t depth,
1374251881Speter                                  svn_boolean_t send_copyfrom_args,
1375251881Speter                                  svn_boolean_t ignore_ancestry,
1376251881Speter                                  const svn_delta_editor_t *update_editor,
1377251881Speter                                  void *update_baton,
1378251881Speter                                  apr_pool_t *pool,
1379251881Speter                                  apr_pool_t *scratch_pool)
1380251881Speter{
1381251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1382251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1383251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1384251881Speter
1385251881Speter  /* Tell the server we want to start an update. */
1386251881Speter  SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
1387251881Speter                                       depth, send_copyfrom_args,
1388251881Speter                                       ignore_ancestry));
1389251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1390251881Speter
1391251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1392251881Speter   * update_editor upon finish_report(). */
1393251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1394251881Speter                              target, depth, reporter, report_baton));
1395251881Speter  return SVN_NO_ERROR;
1396251881Speter}
1397251881Speter
1398251881Speterstatic svn_error_t *
1399251881Speterra_svn_switch(svn_ra_session_t *session,
1400251881Speter              const svn_ra_reporter3_t **reporter,
1401251881Speter              void **report_baton, svn_revnum_t rev,
1402251881Speter              const char *target, svn_depth_t depth,
1403251881Speter              const char *switch_url,
1404251881Speter              svn_boolean_t send_copyfrom_args,
1405251881Speter              svn_boolean_t ignore_ancestry,
1406251881Speter              const svn_delta_editor_t *update_editor,
1407251881Speter              void *update_baton,
1408251881Speter              apr_pool_t *result_pool,
1409251881Speter              apr_pool_t *scratch_pool)
1410251881Speter{
1411251881Speter  apr_pool_t *pool = result_pool;
1412251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1413251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1414251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1415251881Speter
1416251881Speter  /* Tell the server we want to start a switch. */
1417251881Speter  SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
1418251881Speter                                       switch_url, depth,
1419251881Speter                                       send_copyfrom_args, ignore_ancestry));
1420251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1421251881Speter
1422251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1423251881Speter   * update_editor upon finish_report(). */
1424251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
1425251881Speter                              target, depth, reporter, report_baton));
1426251881Speter  return SVN_NO_ERROR;
1427251881Speter}
1428251881Speter
1429251881Speterstatic svn_error_t *ra_svn_status(svn_ra_session_t *session,
1430251881Speter                                  const svn_ra_reporter3_t **reporter,
1431251881Speter                                  void **report_baton,
1432251881Speter                                  const char *target, svn_revnum_t rev,
1433251881Speter                                  svn_depth_t depth,
1434251881Speter                                  const svn_delta_editor_t *status_editor,
1435251881Speter                                  void *status_baton, apr_pool_t *pool)
1436251881Speter{
1437251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1438251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1439251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1440251881Speter
1441251881Speter  /* Tell the server we want to start a status operation. */
1442251881Speter  SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
1443251881Speter                                       depth));
1444251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1445251881Speter
1446251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1447251881Speter   * status_editor upon finish_report(). */
1448251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
1449251881Speter                              target, depth, reporter, report_baton));
1450251881Speter  return SVN_NO_ERROR;
1451251881Speter}
1452251881Speter
1453251881Speterstatic svn_error_t *ra_svn_diff(svn_ra_session_t *session,
1454251881Speter                                const svn_ra_reporter3_t **reporter,
1455251881Speter                                void **report_baton,
1456251881Speter                                svn_revnum_t rev, const char *target,
1457251881Speter                                svn_depth_t depth,
1458251881Speter                                svn_boolean_t ignore_ancestry,
1459251881Speter                                svn_boolean_t text_deltas,
1460251881Speter                                const char *versus_url,
1461251881Speter                                const svn_delta_editor_t *diff_editor,
1462251881Speter                                void *diff_baton, apr_pool_t *pool)
1463251881Speter{
1464251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1465251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1466251881Speter  svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
1467251881Speter
1468251881Speter  /* Tell the server we want to start a diff. */
1469251881Speter  SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
1470251881Speter                                     ignore_ancestry, versus_url,
1471251881Speter                                     text_deltas, depth));
1472251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1473251881Speter
1474251881Speter  /* Fetch a reporter for the caller to drive.  The reporter will drive
1475251881Speter   * diff_editor upon finish_report(). */
1476251881Speter  SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
1477251881Speter                              target, depth, reporter, report_baton));
1478251881Speter  return SVN_NO_ERROR;
1479251881Speter}
1480251881Speter
1481251881Speter
1482253734Speterstatic svn_error_t *
1483253734Speterperform_ra_svn_log(svn_error_t **outer_error,
1484253734Speter                   svn_ra_session_t *session,
1485253734Speter                   const apr_array_header_t *paths,
1486253734Speter                   svn_revnum_t start, svn_revnum_t end,
1487253734Speter                   int limit,
1488253734Speter                   svn_boolean_t discover_changed_paths,
1489253734Speter                   svn_boolean_t strict_node_history,
1490253734Speter                   svn_boolean_t include_merged_revisions,
1491253734Speter                   const apr_array_header_t *revprops,
1492253734Speter                   svn_log_entry_receiver_t receiver,
1493253734Speter                   void *receiver_baton,
1494253734Speter                   apr_pool_t *pool)
1495251881Speter{
1496251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1497251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1498251881Speter  apr_pool_t *iterpool;
1499251881Speter  int i;
1500251881Speter  int nest_level = 0;
1501251881Speter  const char *path;
1502251881Speter  char *name;
1503251881Speter  svn_boolean_t want_custom_revprops;
1504251881Speter
1505251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
1506251881Speter  if (paths)
1507251881Speter    {
1508251881Speter      for (i = 0; i < paths->nelts; i++)
1509251881Speter        {
1510251881Speter          path = APR_ARRAY_IDX(paths, i, const char *);
1511251881Speter          SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
1512251881Speter        }
1513251881Speter    }
1514251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
1515251881Speter                                  discover_changed_paths, strict_node_history,
1516251881Speter                                  (apr_uint64_t) limit,
1517251881Speter                                  include_merged_revisions));
1518251881Speter  if (revprops)
1519251881Speter    {
1520251881Speter      want_custom_revprops = FALSE;
1521251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
1522251881Speter      for (i = 0; i < revprops->nelts; i++)
1523251881Speter        {
1524251881Speter          name = APR_ARRAY_IDX(revprops, i, char *);
1525251881Speter          SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
1526251881Speter          if (!want_custom_revprops
1527251881Speter              && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
1528251881Speter              && strcmp(name, SVN_PROP_REVISION_DATE) != 0
1529251881Speter              && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
1530251881Speter            want_custom_revprops = TRUE;
1531251881Speter        }
1532251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1533251881Speter    }
1534251881Speter  else
1535251881Speter    {
1536251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
1537251881Speter      want_custom_revprops = TRUE;
1538251881Speter    }
1539251881Speter
1540251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1541251881Speter
1542251881Speter  /* Read the log messages. */
1543251881Speter  iterpool = svn_pool_create(pool);
1544251881Speter  while (1)
1545251881Speter    {
1546251881Speter      apr_uint64_t has_children_param, invalid_revnum_param;
1547251881Speter      apr_uint64_t has_subtractive_merge_param;
1548251881Speter      svn_string_t *author, *date, *message;
1549251881Speter      apr_array_header_t *cplist, *rplist;
1550251881Speter      svn_log_entry_t *log_entry;
1551251881Speter      svn_boolean_t has_children;
1552251881Speter      svn_boolean_t subtractive_merge = FALSE;
1553251881Speter      apr_uint64_t revprop_count;
1554251881Speter      svn_ra_svn_item_t *item;
1555251881Speter      apr_hash_t *cphash;
1556251881Speter      svn_revnum_t rev;
1557251881Speter      int nreceived;
1558251881Speter
1559251881Speter      svn_pool_clear(iterpool);
1560251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1561251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1562251881Speter        break;
1563251881Speter      if (item->kind != SVN_RA_SVN_LIST)
1564251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1565251881Speter                                _("Log entry not a list"));
1566251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
1567251881Speter                                      "lr(?s)(?s)(?s)?BBnl?B",
1568251881Speter                                      &cplist, &rev, &author, &date,
1569251881Speter                                      &message, &has_children_param,
1570251881Speter                                      &invalid_revnum_param,
1571251881Speter                                      &revprop_count, &rplist,
1572251881Speter                                      &has_subtractive_merge_param));
1573251881Speter      if (want_custom_revprops && rplist == NULL)
1574251881Speter        {
1575251881Speter          /* Caller asked for custom revprops, but server is too old. */
1576251881Speter          return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
1577251881Speter                                  _("Server does not support custom revprops"
1578251881Speter                                    " via log"));
1579251881Speter        }
1580251881Speter
1581251881Speter      if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1582251881Speter        has_children = FALSE;
1583251881Speter      else
1584251881Speter        has_children = (svn_boolean_t) has_children_param;
1585251881Speter
1586251881Speter      if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1587251881Speter        subtractive_merge = FALSE;
1588251881Speter      else
1589251881Speter        subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
1590251881Speter
1591251881Speter      /* Because the svn protocol won't let us send an invalid revnum, we have
1592251881Speter         to recover that fact using the extra parameter. */
1593251881Speter      if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
1594251881Speter            && invalid_revnum_param)
1595251881Speter        rev = SVN_INVALID_REVNUM;
1596251881Speter
1597251881Speter      if (cplist->nelts > 0)
1598251881Speter        {
1599251881Speter          /* Interpret the changed-paths list. */
1600251881Speter          cphash = apr_hash_make(iterpool);
1601251881Speter          for (i = 0; i < cplist->nelts; i++)
1602251881Speter            {
1603251881Speter              svn_log_changed_path2_t *change;
1604251881Speter              const char *copy_path, *action, *cpath, *kind_str;
1605251881Speter              apr_uint64_t text_mods, prop_mods;
1606251881Speter              svn_revnum_t copy_rev;
1607251881Speter              svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
1608251881Speter                                                      svn_ra_svn_item_t);
1609251881Speter
1610251881Speter              if (elt->kind != SVN_RA_SVN_LIST)
1611251881Speter                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1612251881Speter                                        _("Changed-path entry not a list"));
1613251881Speter              SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool,
1614251881Speter                                              "cw(?cr)?(?c?BB)",
1615251881Speter                                              &cpath, &action, &copy_path,
1616251881Speter                                              &copy_rev, &kind_str,
1617251881Speter                                              &text_mods, &prop_mods));
1618251881Speter              cpath = svn_fspath__canonicalize(cpath, iterpool);
1619251881Speter              if (copy_path)
1620251881Speter                copy_path = svn_fspath__canonicalize(copy_path, iterpool);
1621251881Speter              change = svn_log_changed_path2_create(iterpool);
1622251881Speter              change->action = *action;
1623251881Speter              change->copyfrom_path = copy_path;
1624251881Speter              change->copyfrom_rev = copy_rev;
1625251881Speter              change->node_kind = svn_node_kind_from_word(kind_str);
1626251881Speter              change->text_modified = optbool_to_tristate(text_mods);
1627251881Speter              change->props_modified = optbool_to_tristate(prop_mods);
1628251881Speter              svn_hash_sets(cphash, cpath, change);
1629251881Speter            }
1630251881Speter        }
1631251881Speter      else
1632251881Speter        cphash = NULL;
1633251881Speter
1634251881Speter      nreceived = 0;
1635253734Speter      if (! (limit && (nest_level == 0) && (++nreceived > limit))
1636253734Speter          && ! *outer_error)
1637251881Speter        {
1638253734Speter          svn_error_t *err;
1639251881Speter          log_entry = svn_log_entry_create(iterpool);
1640251881Speter
1641251881Speter          log_entry->changed_paths = cphash;
1642251881Speter          log_entry->changed_paths2 = cphash;
1643251881Speter          log_entry->revision = rev;
1644251881Speter          log_entry->has_children = has_children;
1645251881Speter          log_entry->subtractive_merge = subtractive_merge;
1646251881Speter          if (rplist)
1647251881Speter            SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
1648251881Speter                                               &log_entry->revprops));
1649251881Speter          if (log_entry->revprops == NULL)
1650251881Speter            log_entry->revprops = apr_hash_make(iterpool);
1651251881Speter          if (revprops == NULL)
1652251881Speter            {
1653251881Speter              /* Caller requested all revprops; set author/date/log. */
1654251881Speter              if (author)
1655251881Speter                svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1656251881Speter                              author);
1657251881Speter              if (date)
1658251881Speter                svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1659251881Speter                              date);
1660251881Speter              if (message)
1661251881Speter                svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG,
1662251881Speter                              message);
1663251881Speter            }
1664251881Speter          else
1665251881Speter            {
1666251881Speter              /* Caller requested some; maybe set author/date/log. */
1667251881Speter              for (i = 0; i < revprops->nelts; i++)
1668251881Speter                {
1669251881Speter                  name = APR_ARRAY_IDX(revprops, i, char *);
1670251881Speter                  if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
1671251881Speter                    svn_hash_sets(log_entry->revprops,
1672251881Speter                                  SVN_PROP_REVISION_AUTHOR, author);
1673251881Speter                  if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
1674251881Speter                    svn_hash_sets(log_entry->revprops,
1675251881Speter                                  SVN_PROP_REVISION_DATE, date);
1676251881Speter                  if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
1677251881Speter                    svn_hash_sets(log_entry->revprops,
1678251881Speter                                  SVN_PROP_REVISION_LOG, message);
1679251881Speter                }
1680251881Speter            }
1681253734Speter          err = receiver(receiver_baton, log_entry, iterpool);
1682253734Speter          if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
1683253734Speter            {
1684253734Speter              *outer_error = svn_error_trace(
1685253734Speter                                svn_error_compose_create(*outer_error, err));
1686253734Speter            }
1687253734Speter          else
1688253734Speter            SVN_ERR(err);
1689253734Speter
1690251881Speter          if (log_entry->has_children)
1691251881Speter            {
1692251881Speter              nest_level++;
1693251881Speter            }
1694251881Speter          if (! SVN_IS_VALID_REVNUM(log_entry->revision))
1695251881Speter            {
1696251881Speter              SVN_ERR_ASSERT(nest_level);
1697251881Speter              nest_level--;
1698251881Speter            }
1699251881Speter        }
1700251881Speter    }
1701251881Speter  svn_pool_destroy(iterpool);
1702251881Speter
1703251881Speter  /* Read the response. */
1704253734Speter  return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, ""));
1705251881Speter}
1706251881Speter
1707253734Speterstatic svn_error_t *
1708253734Speterra_svn_log(svn_ra_session_t *session,
1709253734Speter           const apr_array_header_t *paths,
1710253734Speter           svn_revnum_t start, svn_revnum_t end,
1711253734Speter           int limit,
1712253734Speter           svn_boolean_t discover_changed_paths,
1713253734Speter           svn_boolean_t strict_node_history,
1714253734Speter           svn_boolean_t include_merged_revisions,
1715253734Speter           const apr_array_header_t *revprops,
1716253734Speter           svn_log_entry_receiver_t receiver,
1717253734Speter           void *receiver_baton, apr_pool_t *pool)
1718253734Speter{
1719253734Speter  svn_error_t *outer_error = NULL;
1720253734Speter  svn_error_t *err;
1721251881Speter
1722253734Speter  err = svn_error_trace(perform_ra_svn_log(&outer_error,
1723253734Speter                                           session, paths,
1724253734Speter                                           start, end,
1725253734Speter                                           limit,
1726253734Speter                                           discover_changed_paths,
1727253734Speter                                           strict_node_history,
1728253734Speter                                           include_merged_revisions,
1729253734Speter                                           revprops,
1730253734Speter                                           receiver, receiver_baton,
1731253734Speter                                           pool));
1732253734Speter  return svn_error_trace(
1733253734Speter            svn_error_compose_create(outer_error,
1734253734Speter                                     err));
1735253734Speter}
1736253734Speter
1737253734Speter
1738253734Speter
1739251881Speterstatic svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
1740251881Speter                                      const char *path, svn_revnum_t rev,
1741251881Speter                                      svn_node_kind_t *kind, apr_pool_t *pool)
1742251881Speter{
1743251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1744251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1745251881Speter  const char *kind_word;
1746251881Speter
1747251881Speter  SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
1748251881Speter  SVN_ERR(handle_auth_request(sess_baton, pool));
1749251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
1750251881Speter  *kind = svn_node_kind_from_word(kind_word);
1751251881Speter  return SVN_NO_ERROR;
1752251881Speter}
1753251881Speter
1754251881Speter
1755251881Speter/* If ERR is a command not supported error, wrap it in a
1756251881Speter   SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG.  Else, return err. */
1757251881Speterstatic svn_error_t *handle_unsupported_cmd(svn_error_t *err,
1758251881Speter                                           const char *msg)
1759251881Speter{
1760251881Speter  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
1761251881Speter    return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
1762251881Speter                            _(msg));
1763251881Speter  return err;
1764251881Speter}
1765251881Speter
1766251881Speter
1767251881Speterstatic svn_error_t *ra_svn_stat(svn_ra_session_t *session,
1768251881Speter                                const char *path, svn_revnum_t rev,
1769251881Speter                                svn_dirent_t **dirent, apr_pool_t *pool)
1770251881Speter{
1771251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1772251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1773251881Speter  apr_array_header_t *list = NULL;
1774251881Speter  svn_dirent_t *the_dirent;
1775251881Speter
1776251881Speter  SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
1777251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1778251881Speter                                 N_("'stat' not implemented")));
1779251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
1780251881Speter
1781251881Speter  if (! list)
1782251881Speter    {
1783251881Speter      *dirent = NULL;
1784251881Speter    }
1785251881Speter  else
1786251881Speter    {
1787251881Speter      const char *kind, *cdate, *cauthor;
1788251881Speter      svn_boolean_t has_props;
1789251881Speter      svn_revnum_t crev;
1790251881Speter      apr_uint64_t size;
1791251881Speter
1792251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
1793251881Speter                                      &kind, &size, &has_props,
1794251881Speter                                      &crev, &cdate, &cauthor));
1795251881Speter
1796251881Speter      the_dirent = svn_dirent_create(pool);
1797251881Speter      the_dirent->kind = svn_node_kind_from_word(kind);
1798251881Speter      the_dirent->size = size;/* FIXME: svn_filesize_t */
1799251881Speter      the_dirent->has_props = has_props;
1800251881Speter      the_dirent->created_rev = crev;
1801251881Speter      SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
1802251881Speter      the_dirent->last_author = cauthor;
1803251881Speter
1804251881Speter      *dirent = the_dirent;
1805251881Speter    }
1806251881Speter
1807251881Speter  return SVN_NO_ERROR;
1808251881Speter}
1809251881Speter
1810251881Speter
1811251881Speterstatic svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
1812251881Speter                                         apr_hash_t **locations,
1813251881Speter                                         const char *path,
1814251881Speter                                         svn_revnum_t peg_revision,
1815251881Speter                                         const apr_array_header_t *location_revisions,
1816251881Speter                                         apr_pool_t *pool)
1817251881Speter{
1818251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1819251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1820251881Speter  svn_revnum_t revision;
1821251881Speter  svn_boolean_t is_done;
1822251881Speter  int i;
1823251881Speter
1824251881Speter  /* Transmit the parameters. */
1825251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
1826251881Speter                                  "get-locations", path, peg_revision));
1827251881Speter  for (i = 0; i < location_revisions->nelts; i++)
1828251881Speter    {
1829251881Speter      revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
1830251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
1831251881Speter    }
1832251881Speter
1833251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1834251881Speter
1835251881Speter  /* Servers before 1.1 don't support this command. Check for this here. */
1836251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1837251881Speter                                 N_("'get-locations' not implemented")));
1838251881Speter
1839251881Speter  /* Read the hash items. */
1840251881Speter  is_done = FALSE;
1841251881Speter  *locations = apr_hash_make(pool);
1842251881Speter  while (!is_done)
1843251881Speter    {
1844251881Speter      svn_ra_svn_item_t *item;
1845251881Speter      const char *ret_path;
1846251881Speter
1847251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
1848251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1849251881Speter        is_done = 1;
1850251881Speter      else if (item->kind != SVN_RA_SVN_LIST)
1851251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1852251881Speter                                _("Location entry not a list"));
1853251881Speter      else
1854251881Speter        {
1855251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
1856251881Speter                                          &revision, &ret_path));
1857251881Speter          ret_path = svn_fspath__canonicalize(ret_path, pool);
1858251881Speter          apr_hash_set(*locations, apr_pmemdup(pool, &revision,
1859251881Speter                                               sizeof(revision)),
1860251881Speter                       sizeof(revision), ret_path);
1861251881Speter        }
1862251881Speter    }
1863251881Speter
1864251881Speter  /* Read the response. This is so the server would have a chance to
1865251881Speter   * report an error. */
1866251881Speter  return svn_ra_svn__read_cmd_response(conn, pool, "");
1867251881Speter}
1868251881Speter
1869251881Speterstatic svn_error_t *
1870251881Speterra_svn_get_location_segments(svn_ra_session_t *session,
1871251881Speter                             const char *path,
1872251881Speter                             svn_revnum_t peg_revision,
1873251881Speter                             svn_revnum_t start_rev,
1874251881Speter                             svn_revnum_t end_rev,
1875251881Speter                             svn_location_segment_receiver_t receiver,
1876251881Speter                             void *receiver_baton,
1877251881Speter                             apr_pool_t *pool)
1878251881Speter{
1879251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1880251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
1881251881Speter  svn_boolean_t is_done;
1882251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
1883251881Speter
1884251881Speter  /* Transmit the parameters. */
1885251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
1886251881Speter                                  "get-location-segments",
1887251881Speter                                  path, peg_revision, start_rev, end_rev));
1888251881Speter
1889251881Speter  /* Servers before 1.5 don't support this command. Check for this here. */
1890251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
1891251881Speter                                 N_("'get-location-segments'"
1892251881Speter                                    " not implemented")));
1893251881Speter
1894251881Speter  /* Parse the response. */
1895251881Speter  is_done = FALSE;
1896251881Speter  while (!is_done)
1897251881Speter    {
1898251881Speter      svn_revnum_t range_start, range_end;
1899251881Speter      svn_ra_svn_item_t *item;
1900251881Speter      const char *ret_path;
1901251881Speter
1902251881Speter      svn_pool_clear(iterpool);
1903251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
1904251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1905251881Speter        is_done = 1;
1906251881Speter      else if (item->kind != SVN_RA_SVN_LIST)
1907251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1908251881Speter                                _("Location segment entry not a list"));
1909251881Speter      else
1910251881Speter        {
1911251881Speter          svn_location_segment_t *segment = apr_pcalloc(iterpool,
1912251881Speter                                                        sizeof(*segment));
1913251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
1914251881Speter                                          &range_start, &range_end, &ret_path));
1915251881Speter          if (! (SVN_IS_VALID_REVNUM(range_start)
1916251881Speter                 && SVN_IS_VALID_REVNUM(range_end)))
1917251881Speter            return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1918251881Speter                                    _("Expected valid revision range"));
1919251881Speter          if (ret_path)
1920251881Speter            ret_path = svn_relpath_canonicalize(ret_path, iterpool);
1921251881Speter          segment->path = ret_path;
1922251881Speter          segment->range_start = range_start;
1923251881Speter          segment->range_end = range_end;
1924251881Speter          SVN_ERR(receiver(segment, receiver_baton, iterpool));
1925251881Speter        }
1926251881Speter    }
1927251881Speter  svn_pool_destroy(iterpool);
1928251881Speter
1929251881Speter  /* Read the response. This is so the server would have a chance to
1930251881Speter   * report an error. */
1931251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
1932251881Speter
1933251881Speter  return SVN_NO_ERROR;
1934251881Speter}
1935251881Speter
1936251881Speterstatic svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
1937251881Speter                                         const char *path,
1938251881Speter                                         svn_revnum_t start, svn_revnum_t end,
1939251881Speter                                         svn_boolean_t include_merged_revisions,
1940251881Speter                                         svn_file_rev_handler_t handler,
1941251881Speter                                         void *handler_baton, apr_pool_t *pool)
1942251881Speter{
1943251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
1944251881Speter  apr_pool_t *rev_pool, *chunk_pool;
1945251881Speter  svn_boolean_t has_txdelta;
1946251881Speter  svn_boolean_t had_revision = FALSE;
1947251881Speter
1948251881Speter  /* One sub-pool for each revision and one for each txdelta chunk.
1949251881Speter     Note that the rev_pool must live during the following txdelta. */
1950251881Speter  rev_pool = svn_pool_create(pool);
1951251881Speter  chunk_pool = svn_pool_create(pool);
1952251881Speter
1953251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
1954251881Speter                                              path, start, end,
1955251881Speter                                              include_merged_revisions));
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-file-revs' not implemented")));
1960251881Speter
1961251881Speter  while (1)
1962251881Speter    {
1963251881Speter      apr_array_header_t *rev_proplist, *proplist;
1964251881Speter      apr_uint64_t merged_rev_param;
1965251881Speter      apr_array_header_t *props;
1966251881Speter      svn_ra_svn_item_t *item;
1967251881Speter      apr_hash_t *rev_props;
1968251881Speter      svn_revnum_t rev;
1969251881Speter      const char *p;
1970251881Speter      svn_boolean_t merged_rev;
1971251881Speter      svn_txdelta_window_handler_t d_handler;
1972251881Speter      void *d_baton;
1973251881Speter
1974251881Speter      svn_pool_clear(rev_pool);
1975251881Speter      svn_pool_clear(chunk_pool);
1976251881Speter      SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
1977251881Speter      if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
1978251881Speter        break;
1979251881Speter      /* Either we've got a correct revision or we will error out below. */
1980251881Speter      had_revision = TRUE;
1981251881Speter      if (item->kind != SVN_RA_SVN_LIST)
1982251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1983251881Speter                                _("Revision entry not a list"));
1984251881Speter
1985251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
1986251881Speter                                      "crll?B", &p, &rev, &rev_proplist,
1987251881Speter                                      &proplist, &merged_rev_param));
1988251881Speter      p = svn_fspath__canonicalize(p, rev_pool);
1989251881Speter      SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
1990251881Speter      SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
1991251881Speter      if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1992251881Speter        merged_rev = FALSE;
1993251881Speter      else
1994251881Speter        merged_rev = (svn_boolean_t) merged_rev_param;
1995251881Speter
1996251881Speter      /* Get the first delta chunk so we know if there is a delta. */
1997251881Speter      SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
1998251881Speter      if (item->kind != SVN_RA_SVN_STRING)
1999251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2000251881Speter                                _("Text delta chunk not a string"));
2001251881Speter      has_txdelta = item->u.string->len > 0;
2002251881Speter
2003251881Speter      SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
2004251881Speter                      has_txdelta ? &d_handler : NULL, &d_baton,
2005251881Speter                      props, rev_pool));
2006251881Speter
2007251881Speter      /* Process the text delta if any. */
2008251881Speter      if (has_txdelta)
2009251881Speter        {
2010251881Speter          svn_stream_t *stream;
2011251881Speter
2012251881Speter          if (d_handler)
2013251881Speter            stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
2014251881Speter                                               rev_pool);
2015251881Speter          else
2016251881Speter            stream = NULL;
2017251881Speter          while (item->u.string->len > 0)
2018251881Speter            {
2019251881Speter              apr_size_t size;
2020251881Speter
2021251881Speter              size = item->u.string->len;
2022251881Speter              if (stream)
2023251881Speter                SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
2024251881Speter              svn_pool_clear(chunk_pool);
2025251881Speter
2026251881Speter              SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
2027251881Speter                                            &item));
2028251881Speter              if (item->kind != SVN_RA_SVN_STRING)
2029251881Speter                return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2030251881Speter                                        _("Text delta chunk not a string"));
2031251881Speter            }
2032251881Speter          if (stream)
2033251881Speter            SVN_ERR(svn_stream_close(stream));
2034251881Speter        }
2035251881Speter    }
2036251881Speter
2037251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
2038251881Speter
2039251881Speter  /* Return error if we didn't get any revisions. */
2040251881Speter  if (!had_revision)
2041251881Speter    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2042251881Speter                            _("The get-file-revs command didn't return "
2043251881Speter                              "any revisions"));
2044251881Speter
2045251881Speter  svn_pool_destroy(chunk_pool);
2046251881Speter  svn_pool_destroy(rev_pool);
2047251881Speter
2048251881Speter  return SVN_NO_ERROR;
2049251881Speter}
2050251881Speter
2051251881Speter/* For each path in PATH_REVS, send a 'lock' command to the server.
2052251881Speter   Used with 1.2.x series servers which support locking, but of only
2053251881Speter   one path at a time.  ra_svn_lock(), which supports 'lock-many'
2054251881Speter   is now the default.  See svn_ra_lock() docstring for interface details. */
2055251881Speterstatic svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
2056251881Speter                                       apr_hash_t *path_revs,
2057251881Speter                                       const char *comment,
2058251881Speter                                       svn_boolean_t steal_lock,
2059251881Speter                                       svn_ra_lock_callback_t lock_func,
2060251881Speter                                       void *lock_baton,
2061251881Speter                                       apr_pool_t *pool)
2062251881Speter{
2063251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2064251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2065251881Speter  apr_array_header_t *list;
2066251881Speter  apr_hash_index_t *hi;
2067251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2068251881Speter
2069251881Speter  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2070251881Speter    {
2071251881Speter      svn_lock_t *lock;
2072251881Speter      const void *key;
2073251881Speter      const char *path;
2074251881Speter      void *val;
2075251881Speter      svn_revnum_t *revnum;
2076251881Speter      svn_error_t *err, *callback_err = NULL;
2077251881Speter
2078251881Speter      svn_pool_clear(iterpool);
2079251881Speter
2080251881Speter      apr_hash_this(hi, &key, NULL, &val);
2081251881Speter      path = key;
2082251881Speter      revnum = val;
2083251881Speter
2084251881Speter      SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
2085251881Speter                                         steal_lock, *revnum));
2086251881Speter
2087251881Speter      /* Servers before 1.2 doesn't support locking.  Check this here. */
2088251881Speter      SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2089251881Speter                                     N_("Server doesn't support "
2090251881Speter                                        "the lock command")));
2091251881Speter
2092251881Speter      err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
2093251881Speter
2094251881Speter      if (!err)
2095251881Speter        SVN_ERR(parse_lock(list, iterpool, &lock));
2096251881Speter
2097251881Speter      if (err && !SVN_ERR_IS_LOCK_ERROR(err))
2098251881Speter        return err;
2099251881Speter
2100251881Speter      if (lock_func)
2101251881Speter        callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
2102251881Speter                                 err, iterpool);
2103251881Speter
2104251881Speter      svn_error_clear(err);
2105251881Speter
2106251881Speter      if (callback_err)
2107251881Speter        return callback_err;
2108251881Speter    }
2109251881Speter
2110251881Speter  svn_pool_destroy(iterpool);
2111251881Speter
2112251881Speter  return SVN_NO_ERROR;
2113251881Speter}
2114251881Speter
2115251881Speter/* For each path in PATH_TOKENS, send an 'unlock' command to the server.
2116251881Speter   Used with 1.2.x series servers which support unlocking, but of only
2117251881Speter   one path at a time.  ra_svn_unlock(), which supports 'unlock-many' is
2118251881Speter   now the default.  See svn_ra_unlock() docstring for interface details. */
2119251881Speterstatic svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
2120251881Speter                                         apr_hash_t *path_tokens,
2121251881Speter                                         svn_boolean_t break_lock,
2122251881Speter                                         svn_ra_lock_callback_t lock_func,
2123251881Speter                                         void *lock_baton,
2124251881Speter                                         apr_pool_t *pool)
2125251881Speter{
2126251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2127251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2128251881Speter  apr_hash_index_t *hi;
2129251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2130251881Speter
2131251881Speter  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2132251881Speter    {
2133251881Speter      const void *key;
2134251881Speter      const char *path;
2135251881Speter      void *val;
2136251881Speter      const char *token;
2137251881Speter      svn_error_t *err, *callback_err = NULL;
2138251881Speter
2139251881Speter      svn_pool_clear(iterpool);
2140251881Speter
2141251881Speter      apr_hash_this(hi, &key, NULL, &val);
2142251881Speter      path = key;
2143251881Speter      if (strcmp(val, "") != 0)
2144251881Speter        token = val;
2145251881Speter      else
2146251881Speter        token = NULL;
2147251881Speter
2148251881Speter      SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
2149251881Speter                                           break_lock));
2150251881Speter
2151251881Speter      /* Servers before 1.2 don't support locking.  Check this here. */
2152251881Speter      SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
2153251881Speter                                     N_("Server doesn't support the unlock "
2154251881Speter                                        "command")));
2155251881Speter
2156251881Speter      err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
2157251881Speter
2158251881Speter      if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
2159251881Speter        return err;
2160251881Speter
2161251881Speter      if (lock_func)
2162251881Speter        callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
2163251881Speter
2164251881Speter      svn_error_clear(err);
2165251881Speter
2166251881Speter      if (callback_err)
2167251881Speter        return callback_err;
2168251881Speter    }
2169251881Speter
2170251881Speter  svn_pool_destroy(iterpool);
2171251881Speter
2172251881Speter  return SVN_NO_ERROR;
2173251881Speter}
2174251881Speter
2175251881Speter/* Tell the server to lock all paths in PATH_REVS.
2176251881Speter   See svn_ra_lock() for interface details. */
2177251881Speterstatic svn_error_t *ra_svn_lock(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_hash_index_t *hi;
2188251881Speter  svn_error_t *err;
2189251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2190251881Speter
2191251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
2192251881Speter                                  comment, steal_lock));
2193251881Speter
2194251881Speter  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2195251881Speter    {
2196251881Speter      const void *key;
2197251881Speter      const char *path;
2198251881Speter      void *val;
2199251881Speter      svn_revnum_t *revnum;
2200251881Speter
2201251881Speter      svn_pool_clear(iterpool);
2202251881Speter      apr_hash_this(hi, &key, NULL, &val);
2203251881Speter      path = key;
2204251881Speter      revnum = val;
2205251881Speter
2206251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
2207251881Speter    }
2208251881Speter
2209251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2210251881Speter
2211251881Speter  err = handle_auth_request(sess, pool);
2212251881Speter
2213251881Speter  /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
2214251881Speter   * to 'lock'. */
2215251881Speter  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2216251881Speter    {
2217251881Speter      svn_error_clear(err);
2218251881Speter      return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
2219251881Speter                                lock_func, lock_baton, pool);
2220251881Speter    }
2221251881Speter
2222251881Speter  if (err)
2223251881Speter    return err;
2224251881Speter
2225251881Speter  /* Loop over responses to get lock information. */
2226251881Speter  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
2227251881Speter    {
2228251881Speter      svn_ra_svn_item_t *elt;
2229251881Speter      const void *key;
2230251881Speter      const char *path;
2231251881Speter      svn_error_t *callback_err;
2232251881Speter      const char *status;
2233251881Speter      svn_lock_t *lock;
2234251881Speter      apr_array_header_t *list;
2235251881Speter
2236251881Speter      apr_hash_this(hi, &key, NULL, NULL);
2237251881Speter      path = key;
2238251881Speter
2239251881Speter      svn_pool_clear(iterpool);
2240251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2241251881Speter
2242251881Speter      /* The server might have encountered some sort of fatal error in
2243251881Speter         the middle of the request list.  If this happens, it will
2244251881Speter         transmit "done" to end the lock-info early, and then the
2245251881Speter         overall command response will talk about the fatal error. */
2246251881Speter      if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
2247251881Speter        break;
2248251881Speter
2249251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
2250251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2251251881Speter                                _("Lock response not a list"));
2252251881Speter
2253251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2254251881Speter                                      &list));
2255251881Speter
2256251881Speter      if (strcmp(status, "failure") == 0)
2257251881Speter        err = svn_ra_svn__handle_failure_status(list, iterpool);
2258251881Speter      else if (strcmp(status, "success") == 0)
2259251881Speter        {
2260251881Speter          SVN_ERR(parse_lock(list, iterpool, &lock));
2261251881Speter          err = NULL;
2262251881Speter        }
2263251881Speter      else
2264251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2265251881Speter                                _("Unknown status for lock command"));
2266251881Speter
2267251881Speter      if (lock_func)
2268251881Speter        callback_err = lock_func(lock_baton, path, TRUE,
2269251881Speter                                 err ? NULL : lock,
2270251881Speter                                 err, iterpool);
2271251881Speter      else
2272251881Speter        callback_err = SVN_NO_ERROR;
2273251881Speter
2274251881Speter      svn_error_clear(err);
2275251881Speter
2276251881Speter      if (callback_err)
2277251881Speter        return callback_err;
2278251881Speter    }
2279251881Speter
2280251881Speter  /* If we didn't break early above, and the whole hash was traversed,
2281251881Speter     read the final "done" from the server. */
2282251881Speter  if (!hi)
2283251881Speter    {
2284251881Speter      svn_ra_svn_item_t *elt;
2285251881Speter
2286251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2287251881Speter      if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2288251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2289251881Speter                                _("Didn't receive end marker for lock "
2290251881Speter                                  "responses"));
2291251881Speter    }
2292251881Speter
2293251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2294251881Speter
2295251881Speter  svn_pool_destroy(iterpool);
2296251881Speter
2297251881Speter  return SVN_NO_ERROR;
2298251881Speter}
2299251881Speter
2300251881Speter/* Tell the server to unlock all paths in PATH_TOKENS.
2301251881Speter   See svn_ra_unlock() for interface details. */
2302251881Speterstatic svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
2303251881Speter                                  apr_hash_t *path_tokens,
2304251881Speter                                  svn_boolean_t break_lock,
2305251881Speter                                  svn_ra_lock_callback_t lock_func,
2306251881Speter                                  void *lock_baton,
2307251881Speter                                  apr_pool_t *pool)
2308251881Speter{
2309251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2310251881Speter  svn_ra_svn_conn_t *conn = sess->conn;
2311251881Speter  apr_hash_index_t *hi;
2312251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2313251881Speter  svn_error_t *err;
2314251881Speter  const char *path;
2315251881Speter
2316251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
2317251881Speter                                  break_lock));
2318251881Speter
2319251881Speter  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2320251881Speter    {
2321251881Speter      void *val;
2322251881Speter      const void *key;
2323251881Speter      const char *token;
2324251881Speter
2325251881Speter      svn_pool_clear(iterpool);
2326251881Speter      apr_hash_this(hi, &key, NULL, &val);
2327251881Speter      path = key;
2328251881Speter
2329251881Speter      if (strcmp(val, "") != 0)
2330251881Speter        token = val;
2331251881Speter      else
2332251881Speter        token = NULL;
2333251881Speter
2334251881Speter      SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
2335251881Speter    }
2336251881Speter
2337251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2338251881Speter
2339251881Speter  err = handle_auth_request(sess, pool);
2340251881Speter
2341251881Speter  /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
2342251881Speter   * to 'unlock'.
2343251881Speter   */
2344251881Speter  if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
2345251881Speter    {
2346251881Speter      svn_error_clear(err);
2347251881Speter      return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
2348251881Speter                                  lock_baton, pool);
2349251881Speter    }
2350251881Speter
2351251881Speter  if (err)
2352251881Speter    return err;
2353251881Speter
2354251881Speter  /* Loop over responses to unlock files. */
2355251881Speter  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
2356251881Speter    {
2357251881Speter      svn_ra_svn_item_t *elt;
2358251881Speter      const void *key;
2359251881Speter      svn_error_t *callback_err;
2360251881Speter      const char *status;
2361251881Speter      apr_array_header_t *list;
2362251881Speter
2363251881Speter      svn_pool_clear(iterpool);
2364251881Speter
2365251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
2366251881Speter
2367251881Speter      /* The server might have encountered some sort of fatal error in
2368251881Speter         the middle of the request list.  If this happens, it will
2369251881Speter         transmit "done" to end the lock-info early, and then the
2370251881Speter         overall command response will talk about the fatal error. */
2371251881Speter      if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
2372251881Speter        break;
2373251881Speter
2374251881Speter      apr_hash_this(hi, &key, NULL, NULL);
2375251881Speter      path = key;
2376251881Speter
2377251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
2378251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2379251881Speter                                _("Unlock response not a list"));
2380251881Speter
2381251881Speter      SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
2382251881Speter                                      &list));
2383251881Speter
2384251881Speter      if (strcmp(status, "failure") == 0)
2385251881Speter        err = svn_ra_svn__handle_failure_status(list, iterpool);
2386251881Speter      else if (strcmp(status, "success") == 0)
2387251881Speter        {
2388251881Speter          SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
2389251881Speter          err = SVN_NO_ERROR;
2390251881Speter        }
2391251881Speter      else
2392251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2393251881Speter                                _("Unknown status for unlock command"));
2394251881Speter
2395251881Speter      if (lock_func)
2396251881Speter        callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
2397251881Speter                                 iterpool);
2398251881Speter      else
2399251881Speter        callback_err = SVN_NO_ERROR;
2400251881Speter
2401251881Speter      svn_error_clear(err);
2402251881Speter
2403251881Speter      if (callback_err)
2404251881Speter        return callback_err;
2405251881Speter    }
2406251881Speter
2407251881Speter  /* If we didn't break early above, and the whole hash was traversed,
2408251881Speter     read the final "done" from the server. */
2409251881Speter  if (!hi)
2410251881Speter    {
2411251881Speter      svn_ra_svn_item_t *elt;
2412251881Speter
2413251881Speter      SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
2414251881Speter      if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
2415251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2416251881Speter                                _("Didn't receive end marker for unlock "
2417251881Speter                                  "responses"));
2418251881Speter    }
2419251881Speter
2420251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
2421251881Speter
2422251881Speter  svn_pool_destroy(iterpool);
2423251881Speter
2424251881Speter  return SVN_NO_ERROR;
2425251881Speter}
2426251881Speter
2427251881Speterstatic svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
2428251881Speter                                    svn_lock_t **lock,
2429251881Speter                                    const char *path,
2430251881Speter                                    apr_pool_t *pool)
2431251881Speter{
2432251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2433251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2434251881Speter  apr_array_header_t *list;
2435251881Speter
2436251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
2437251881Speter
2438251881Speter  /* Servers before 1.2 doesn't support locking.  Check this here. */
2439251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2440251881Speter                                 N_("Server doesn't support the get-lock "
2441251881Speter                                    "command")));
2442251881Speter
2443251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
2444251881Speter  if (list)
2445251881Speter    SVN_ERR(parse_lock(list, pool, lock));
2446251881Speter  else
2447251881Speter    *lock = NULL;
2448251881Speter
2449251881Speter  return SVN_NO_ERROR;
2450251881Speter}
2451251881Speter
2452251881Speter/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
2453251881Speter   to prevent a dependency cycle. */
2454251881Speterstatic svn_error_t *path_relative_to_root(svn_ra_session_t *session,
2455251881Speter                                          const char **rel_path,
2456251881Speter                                          const char *url,
2457251881Speter                                          apr_pool_t *pool)
2458251881Speter{
2459251881Speter  const char *root_url;
2460251881Speter
2461251881Speter  SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
2462251881Speter  *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
2463251881Speter  if (! *rel_path)
2464251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2465251881Speter                             _("'%s' isn't a child of repository root "
2466251881Speter                               "URL '%s'"),
2467251881Speter                             url, root_url);
2468251881Speter  return SVN_NO_ERROR;
2469251881Speter}
2470251881Speter
2471251881Speterstatic svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
2472251881Speter                                     apr_hash_t **locks,
2473251881Speter                                     const char *path,
2474251881Speter                                     svn_depth_t depth,
2475251881Speter                                     apr_pool_t *pool)
2476251881Speter{
2477251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2478251881Speter  svn_ra_svn_conn_t* conn = sess->conn;
2479251881Speter  apr_array_header_t *list;
2480251881Speter  const char *full_url, *abs_path;
2481251881Speter  int i;
2482251881Speter
2483251881Speter  /* Figure out the repository abspath from PATH. */
2484251881Speter  full_url = svn_path_url_add_component2(sess->url, path, pool);
2485251881Speter  SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
2486251881Speter  abs_path = svn_fspath__canonicalize(abs_path, pool);
2487251881Speter
2488251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
2489251881Speter
2490251881Speter  /* Servers before 1.2 doesn't support locking.  Check this here. */
2491251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2492251881Speter                                 N_("Server doesn't support the get-lock "
2493251881Speter                                    "command")));
2494251881Speter
2495251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
2496251881Speter
2497251881Speter  *locks = apr_hash_make(pool);
2498251881Speter
2499251881Speter  for (i = 0; i < list->nelts; ++i)
2500251881Speter    {
2501251881Speter      svn_lock_t *lock;
2502251881Speter      svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
2503251881Speter
2504251881Speter      if (elt->kind != SVN_RA_SVN_LIST)
2505251881Speter        return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2506251881Speter                                _("Lock element not a list"));
2507251881Speter      SVN_ERR(parse_lock(elt->u.list, pool, &lock));
2508251881Speter
2509251881Speter      /* Filter out unwanted paths.  Since Subversion only allows
2510251881Speter         locks on files, we can treat depth=immediates the same as
2511251881Speter         depth=files for filtering purposes.  Meaning, we'll keep
2512251881Speter         this lock if:
2513251881Speter
2514251881Speter         a) its path is the very path we queried, or
2515251881Speter         b) we've asked for a fully recursive answer, or
2516251881Speter         c) we've asked for depth=files or depth=immediates, and this
2517251881Speter            lock is on an immediate child of our query path.
2518251881Speter      */
2519251881Speter      if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
2520251881Speter        {
2521251881Speter          svn_hash_sets(*locks, lock->path, lock);
2522251881Speter        }
2523251881Speter      else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
2524251881Speter        {
2525251881Speter          const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
2526251881Speter          if (relpath && (svn_path_component_count(relpath) == 1))
2527251881Speter            svn_hash_sets(*locks, lock->path, lock);
2528251881Speter        }
2529251881Speter    }
2530251881Speter
2531251881Speter  return SVN_NO_ERROR;
2532251881Speter}
2533251881Speter
2534251881Speter
2535251881Speterstatic svn_error_t *ra_svn_replay(svn_ra_session_t *session,
2536251881Speter                                  svn_revnum_t revision,
2537251881Speter                                  svn_revnum_t low_water_mark,
2538251881Speter                                  svn_boolean_t send_deltas,
2539251881Speter                                  const svn_delta_editor_t *editor,
2540251881Speter                                  void *edit_baton,
2541251881Speter                                  apr_pool_t *pool)
2542251881Speter{
2543251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2544251881Speter
2545251881Speter  SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
2546251881Speter                                       low_water_mark, send_deltas));
2547251881Speter
2548251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2549251881Speter                                 N_("Server doesn't support the replay "
2550251881Speter                                    "command")));
2551251881Speter
2552251881Speter  SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
2553251881Speter                                   NULL, TRUE));
2554251881Speter
2555251881Speter  return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
2556251881Speter}
2557251881Speter
2558251881Speter
2559251881Speterstatic svn_error_t *
2560251881Speterra_svn_replay_range(svn_ra_session_t *session,
2561251881Speter                    svn_revnum_t start_revision,
2562251881Speter                    svn_revnum_t end_revision,
2563251881Speter                    svn_revnum_t low_water_mark,
2564251881Speter                    svn_boolean_t send_deltas,
2565251881Speter                    svn_ra_replay_revstart_callback_t revstart_func,
2566251881Speter                    svn_ra_replay_revfinish_callback_t revfinish_func,
2567251881Speter                    void *replay_baton,
2568251881Speter                    apr_pool_t *pool)
2569251881Speter{
2570251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2571251881Speter  apr_pool_t *iterpool;
2572251881Speter  svn_revnum_t rev;
2573251881Speter  svn_boolean_t drive_aborted = FALSE;
2574251881Speter
2575251881Speter  SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
2576251881Speter                                             start_revision, end_revision,
2577251881Speter                                             low_water_mark, send_deltas));
2578251881Speter
2579251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
2580251881Speter                                 N_("Server doesn't support the "
2581251881Speter                                    "replay-range command")));
2582251881Speter
2583251881Speter  iterpool = svn_pool_create(pool);
2584251881Speter  for (rev = start_revision; rev <= end_revision; rev++)
2585251881Speter    {
2586251881Speter      const svn_delta_editor_t *editor;
2587251881Speter      void *edit_baton;
2588251881Speter      apr_hash_t *rev_props;
2589251881Speter      const char *word;
2590251881Speter      apr_array_header_t *list;
2591251881Speter
2592251881Speter      svn_pool_clear(iterpool);
2593251881Speter
2594251881Speter      SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
2595251881Speter                                     "wl", &word, &list));
2596251881Speter      if (strcmp(word, "revprops") != 0)
2597251881Speter        return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2598251881Speter                                 _("Expected 'revprops', found '%s'"),
2599251881Speter                                 word);
2600251881Speter
2601251881Speter      SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
2602251881Speter
2603251881Speter      SVN_ERR(revstart_func(rev, replay_baton,
2604251881Speter                            &editor, &edit_baton,
2605251881Speter                            rev_props,
2606251881Speter                            iterpool));
2607251881Speter      SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
2608251881Speter                                       editor, edit_baton,
2609251881Speter                                       &drive_aborted, TRUE));
2610251881Speter      /* If drive_editor2() aborted the commit, do NOT try to call
2611251881Speter         revfinish_func and commit the transaction! */
2612251881Speter      if (drive_aborted) {
2613251881Speter        svn_pool_destroy(iterpool);
2614251881Speter        return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
2615251881Speter                                _("Error while replaying commit"));
2616251881Speter      }
2617251881Speter      SVN_ERR(revfinish_func(rev, replay_baton,
2618251881Speter                             editor, edit_baton,
2619251881Speter                             rev_props,
2620251881Speter                             iterpool));
2621251881Speter    }
2622251881Speter  svn_pool_destroy(iterpool);
2623251881Speter
2624251881Speter  return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
2625251881Speter}
2626251881Speter
2627251881Speter
2628251881Speterstatic svn_error_t *
2629251881Speterra_svn_has_capability(svn_ra_session_t *session,
2630251881Speter                      svn_boolean_t *has,
2631251881Speter                      const char *capability,
2632251881Speter                      apr_pool_t *pool)
2633251881Speter{
2634251881Speter  svn_ra_svn__session_baton_t *sess = session->priv;
2635251881Speter  static const char* capabilities[][2] =
2636251881Speter  {
2637251881Speter      /* { ra capability string, svn:// wire capability string} */
2638251881Speter      {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
2639251881Speter      {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
2640251881Speter      {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
2641251881Speter      {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
2642251881Speter      {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
2643251881Speter      {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
2644251881Speter      {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
2645251881Speter      {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2646251881Speter                                          SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
2647251881Speter      {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
2648251881Speter                                       SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
2649251881Speter
2650251881Speter      {NULL, NULL} /* End of list marker */
2651251881Speter  };
2652251881Speter  int i;
2653251881Speter
2654251881Speter  *has = FALSE;
2655251881Speter
2656251881Speter  for (i = 0; capabilities[i][0]; i++)
2657251881Speter    {
2658251881Speter      if (strcmp(capability, capabilities[i][0]) == 0)
2659251881Speter        {
2660251881Speter          *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
2661251881Speter          return SVN_NO_ERROR;
2662251881Speter        }
2663251881Speter    }
2664251881Speter
2665251881Speter  return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
2666251881Speter                           _("Don't know anything about capability '%s'"),
2667251881Speter                           capability);
2668251881Speter}
2669251881Speter
2670251881Speterstatic svn_error_t *
2671251881Speterra_svn_get_deleted_rev(svn_ra_session_t *session,
2672251881Speter                       const char *path,
2673251881Speter                       svn_revnum_t peg_revision,
2674251881Speter                       svn_revnum_t end_revision,
2675251881Speter                       svn_revnum_t *revision_deleted,
2676251881Speter                       apr_pool_t *pool)
2677251881Speter
2678251881Speter{
2679251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2680251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
2681251881Speter
2682251881Speter  /* Transmit the parameters. */
2683251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
2684251881Speter                                               peg_revision, end_revision));
2685251881Speter
2686251881Speter  /* Servers before 1.6 don't support this command.  Check for this here. */
2687251881Speter  SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
2688251881Speter                                 N_("'get-deleted-rev' not implemented")));
2689251881Speter
2690251881Speter  return svn_ra_svn__read_cmd_response(conn, pool, "r", revision_deleted);
2691251881Speter}
2692251881Speter
2693251881Speterstatic svn_error_t *
2694251881Speterra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
2695251881Speter                                      svn_delta_shim_callbacks_t *callbacks)
2696251881Speter{
2697251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2698251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
2699251881Speter
2700251881Speter  conn->shim_callbacks = callbacks;
2701251881Speter
2702251881Speter  return SVN_NO_ERROR;
2703251881Speter}
2704251881Speter
2705251881Speterstatic svn_error_t *
2706251881Speterra_svn_get_inherited_props(svn_ra_session_t *session,
2707251881Speter                           apr_array_header_t **iprops,
2708251881Speter                           const char *path,
2709251881Speter                           svn_revnum_t revision,
2710251881Speter                           apr_pool_t *result_pool,
2711251881Speter                           apr_pool_t *scratch_pool)
2712251881Speter{
2713251881Speter  svn_ra_svn__session_baton_t *sess_baton = session->priv;
2714251881Speter  svn_ra_svn_conn_t *conn = sess_baton->conn;
2715251881Speter  apr_array_header_t *iproplist;
2716251881Speter
2717251881Speter  SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
2718251881Speter                                           path, revision));
2719251881Speter  SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
2720251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
2721251881Speter  SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
2722251881Speter                          scratch_pool));
2723251881Speter
2724251881Speter  return SVN_NO_ERROR;
2725251881Speter}
2726251881Speter
2727251881Speterstatic const svn_ra__vtable_t ra_svn_vtable = {
2728251881Speter  svn_ra_svn_version,
2729251881Speter  ra_svn_get_description,
2730251881Speter  ra_svn_get_schemes,
2731251881Speter  ra_svn_open,
2732251881Speter  ra_svn_reparent,
2733251881Speter  ra_svn_get_session_url,
2734251881Speter  ra_svn_get_latest_rev,
2735251881Speter  ra_svn_get_dated_rev,
2736251881Speter  ra_svn_change_rev_prop,
2737251881Speter  ra_svn_rev_proplist,
2738251881Speter  ra_svn_rev_prop,
2739251881Speter  ra_svn_commit,
2740251881Speter  ra_svn_get_file,
2741251881Speter  ra_svn_get_dir,
2742251881Speter  ra_svn_get_mergeinfo,
2743251881Speter  ra_svn_update,
2744251881Speter  ra_svn_switch,
2745251881Speter  ra_svn_status,
2746251881Speter  ra_svn_diff,
2747251881Speter  ra_svn_log,
2748251881Speter  ra_svn_check_path,
2749251881Speter  ra_svn_stat,
2750251881Speter  ra_svn_get_uuid,
2751251881Speter  ra_svn_get_repos_root,
2752251881Speter  ra_svn_get_locations,
2753251881Speter  ra_svn_get_location_segments,
2754251881Speter  ra_svn_get_file_revs,
2755251881Speter  ra_svn_lock,
2756251881Speter  ra_svn_unlock,
2757251881Speter  ra_svn_get_lock,
2758251881Speter  ra_svn_get_locks,
2759251881Speter  ra_svn_replay,
2760251881Speter  ra_svn_has_capability,
2761251881Speter  ra_svn_replay_range,
2762251881Speter  ra_svn_get_deleted_rev,
2763251881Speter  ra_svn_register_editor_shim_callbacks,
2764251881Speter  ra_svn_get_inherited_props
2765251881Speter};
2766251881Speter
2767251881Spetersvn_error_t *
2768251881Spetersvn_ra_svn__init(const svn_version_t *loader_version,
2769251881Speter                 const svn_ra__vtable_t **vtable,
2770251881Speter                 apr_pool_t *pool)
2771251881Speter{
2772251881Speter  static const svn_version_checklist_t checklist[] =
2773251881Speter    {
2774251881Speter      { "svn_subr",  svn_subr_version },
2775251881Speter      { "svn_delta", svn_delta_version },
2776251881Speter      { NULL, NULL }
2777251881Speter    };
2778251881Speter
2779262253Speter  SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal));
2780251881Speter
2781251881Speter  /* Simplified version check to make sure we can safely use the
2782251881Speter     VTABLE parameter. The RA loader does a more exhaustive check. */
2783251881Speter  if (loader_version->major != SVN_VER_MAJOR)
2784251881Speter    {
2785251881Speter      return svn_error_createf
2786251881Speter        (SVN_ERR_VERSION_MISMATCH, NULL,
2787251881Speter         _("Unsupported RA loader version (%d) for ra_svn"),
2788251881Speter         loader_version->major);
2789251881Speter    }
2790251881Speter
2791251881Speter  *vtable = &ra_svn_vtable;
2792251881Speter
2793251881Speter#ifdef SVN_HAVE_SASL
2794251881Speter  SVN_ERR(svn_ra_svn__sasl_init());
2795251881Speter#endif
2796251881Speter
2797251881Speter  return SVN_NO_ERROR;
2798251881Speter}
2799251881Speter
2800251881Speter/* Compatibility wrapper for the 1.1 and before API. */
2801251881Speter#define NAME "ra_svn"
2802251881Speter#define DESCRIPTION RA_SVN_DESCRIPTION
2803251881Speter#define VTBL ra_svn_vtable
2804251881Speter#define INITFUNC svn_ra_svn__init
2805251881Speter#define COMPAT_INITFUNC svn_ra_svn_init
2806251881Speter#include "../libsvn_ra/wrapper_template.h"
2807