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