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