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