2 * editorp.c : Driving and consuming an editor across an svn connection
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
26 #define APR_WANT_STRFUNC
28 #include <apr_general.h>
29 #include <apr_strings.h>
32 #include "svn_types.h"
33 #include "svn_string.h"
34 #include "svn_error.h"
35 #include "svn_delta.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_ra_svn.h"
39 #include "svn_pools.h"
40 #include "svn_private_config.h"
42 #include "private/svn_atomic.h"
43 #include "private/svn_fspath.h"
44 #include "private/svn_editor.h"
45 #include "private/svn_string_private.h"
46 #include "private/svn_subr_private.h"
51 * Both the client and server in the svn protocol need to drive and
52 * consume editors. For a commit, the client drives and the server
53 * consumes; for an update/switch/status/diff, the server drives and
54 * the client consumes. This file provides a generic framework for
55 * marshalling and unmarshalling editor operations over an svn
56 * connection; both ends are useful for both server and client.
59 typedef struct ra_svn_edit_baton_t {
60 svn_ra_svn_conn_t *conn;
61 svn_ra_svn_edit_callback callback; /* Called on successful completion. */
63 apr_uint64_t next_token;
64 svn_boolean_t got_status;
65 } ra_svn_edit_baton_t;
67 /* Works for both directories and files. */
68 typedef struct ra_svn_baton_t {
69 svn_ra_svn_conn_t *conn;
71 ra_svn_edit_baton_t *eb;
75 /* Forward declaration. */
76 typedef struct ra_svn_token_entry_t ra_svn_token_entry_t;
78 typedef struct ra_svn_driver_state_t {
79 const svn_delta_editor_t *editor;
83 /* Entry for the last token seen. May be NULL. */
84 ra_svn_token_entry_t *last_token;
85 svn_boolean_t *aborted;
88 apr_pool_t *file_pool;
90 svn_boolean_t for_replay;
91 } ra_svn_driver_state_t;
93 /* Works for both directories and files; however, the pool handling is
94 different for files. To save space during commits (where file
95 batons generally last until the end of the commit), token entries
96 for files are all created in a single reference-counted pool (the
97 file_pool member of the driver state structure), which is cleared
98 at close_file time when the reference count hits zero. So the pool
99 field in this structure is vestigial for files, and we use it for a
100 different purpose instead: at apply-textdelta time, we set it to a
101 subpool of the file pool, which is destroyed in textdelta-end. */
102 struct ra_svn_token_entry_t {
105 svn_boolean_t is_file;
106 svn_stream_t *dstream; /* svndiff stream for apply_textdelta */
110 /* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
112 static svn_string_t *
113 make_token(char type,
114 ra_svn_edit_baton_t *eb,
118 char buffer[1 + SVN_INT64_BUFFER_SIZE];
120 len = 1 + svn__ui64toa(&buffer[1], eb->next_token++);
122 return svn_string_ncreate(buffer, len, pool);
125 static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
127 ra_svn_edit_baton_t *eb,
132 b = apr_palloc(pool, sizeof(*b));
140 /* Check for an early error status report from the consumer. If we
141 * get one, abort the edit and return the error. */
143 check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
145 svn_boolean_t available;
146 SVN_ERR_ASSERT(!eb->got_status);
148 /* reset TX counter */
149 eb->conn->written_since_error_check = 0;
151 /* if we weren't asked to always check, wait for at least the next TX */
152 eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
154 /* any incoming data? */
155 SVN_ERR(svn_ra_svn__data_available(eb->conn, &available));
158 eb->got_status = TRUE;
159 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
160 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
161 /* We shouldn't get here if the consumer is doing its job. */
162 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
163 _("Successful edit status returned too soon"));
169 check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
171 return eb->conn->may_check_for_error
172 ? check_for_error_internal(eb, pool)
176 static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
179 ra_svn_edit_baton_t *eb = edit_baton;
181 SVN_ERR(check_for_error(eb, pool));
182 SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
186 static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
187 apr_pool_t *pool, void **root_baton)
189 ra_svn_edit_baton_t *eb = edit_baton;
190 svn_string_t *token = make_token('d', eb, pool);
192 SVN_ERR(check_for_error(eb, pool));
193 SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
194 *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
198 static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
199 void *parent_baton, apr_pool_t *pool)
201 ra_svn_baton_t *b = parent_baton;
203 SVN_ERR(check_for_error(b->eb, pool));
204 SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
205 path, rev, b->token));
209 static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
210 const char *copy_path,
211 svn_revnum_t copy_rev,
212 apr_pool_t *pool, void **child_baton)
214 ra_svn_baton_t *b = parent_baton;
215 svn_string_t *token = make_token('d', b->eb, pool);
217 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
218 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
219 SVN_ERR(check_for_error(b->eb, pool));
220 SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
221 token, copy_path, copy_rev));
222 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
226 static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
227 svn_revnum_t rev, apr_pool_t *pool,
230 ra_svn_baton_t *b = parent_baton;
231 svn_string_t *token = make_token('d', b->eb, pool);
233 SVN_ERR(check_for_error(b->eb, pool));
234 SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
236 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
240 static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
241 const svn_string_t *value,
244 ra_svn_baton_t *b = dir_baton;
246 SVN_ERR(check_for_error(b->eb, pool));
247 SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
252 static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
254 ra_svn_baton_t *b = dir_baton;
256 SVN_ERR(check_for_error(b->eb, pool));
257 SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
261 static svn_error_t *ra_svn_absent_dir(const char *path,
262 void *parent_baton, apr_pool_t *pool)
264 ra_svn_baton_t *b = parent_baton;
266 /* Avoid sending an unknown command if the other end doesn't support
268 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
271 SVN_ERR(check_for_error(b->eb, pool));
272 SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
276 static svn_error_t *ra_svn_add_file(const char *path,
278 const char *copy_path,
279 svn_revnum_t copy_rev,
283 ra_svn_baton_t *b = parent_baton;
284 svn_string_t *token = make_token('c', b->eb, pool);
286 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
287 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
288 SVN_ERR(check_for_error(b->eb, pool));
289 SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool, path, b->token,
290 token, copy_path, copy_rev));
291 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
295 static svn_error_t *ra_svn_open_file(const char *path,
301 ra_svn_baton_t *b = parent_baton;
302 svn_string_t *token = make_token('c', b->eb, pool);
304 SVN_ERR(check_for_error(b->eb, b->pool));
305 SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
307 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
311 static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
314 ra_svn_baton_t *b = baton;
317 SVN_ERR(check_for_error(b->eb, b->pool));
320 return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
324 static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
326 ra_svn_baton_t *b = baton;
328 SVN_ERR(check_for_error(b->eb, b->pool));
329 SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
333 static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
334 const char *base_checksum,
336 svn_txdelta_window_handler_t *wh,
339 ra_svn_baton_t *b = file_baton;
340 svn_stream_t *diff_stream;
342 /* Tell the other side we're starting a text delta. */
343 SVN_ERR(check_for_error(b->eb, pool));
344 SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
347 /* Transform the window stream to an svndiff stream. Reuse the
348 * file baton for the stream handler, since it has all the
349 * needed information. */
350 diff_stream = svn_stream_create(b, pool);
351 svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
352 svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
354 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream,
355 svn_ra_svn__svndiff_version(b->conn),
356 b->conn->compression_level, pool);
360 static svn_error_t *ra_svn_change_file_prop(void *file_baton,
362 const svn_string_t *value,
365 ra_svn_baton_t *b = file_baton;
367 SVN_ERR(check_for_error(b->eb, pool));
368 SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
369 b->token, name, value));
373 static svn_error_t *ra_svn_close_file(void *file_baton,
374 const char *text_checksum,
377 ra_svn_baton_t *b = file_baton;
379 SVN_ERR(check_for_error(b->eb, pool));
380 SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
381 b->token, text_checksum));
385 static svn_error_t *ra_svn_absent_file(const char *path,
386 void *parent_baton, apr_pool_t *pool)
388 ra_svn_baton_t *b = parent_baton;
390 /* Avoid sending an unknown command if the other end doesn't support
392 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
395 SVN_ERR(check_for_error(b->eb, pool));
396 SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
400 static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
402 ra_svn_edit_baton_t *eb = edit_baton;
405 SVN_ERR_ASSERT(!eb->got_status);
406 eb->got_status = TRUE;
407 SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
408 err = svn_error_trace(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
411 return svn_error_compose_create(
414 svn_ra_svn__write_cmd_abort_edit(eb->conn, pool)));
417 SVN_ERR(eb->callback(eb->callback_baton));
421 static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
423 ra_svn_edit_baton_t *eb = edit_baton;
427 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
428 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
432 void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
433 void **edit_baton, svn_ra_svn_conn_t *conn,
435 svn_ra_svn_edit_callback callback,
436 void *callback_baton)
438 svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
439 ra_svn_edit_baton_t *eb;
441 eb = apr_palloc(pool, sizeof(*eb));
443 eb->callback = callback;
444 eb->callback_baton = callback_baton;
446 eb->got_status = FALSE;
448 ra_svn_editor->set_target_revision = ra_svn_target_rev;
449 ra_svn_editor->open_root = ra_svn_open_root;
450 ra_svn_editor->delete_entry = ra_svn_delete_entry;
451 ra_svn_editor->add_directory = ra_svn_add_dir;
452 ra_svn_editor->open_directory = ra_svn_open_dir;
453 ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
454 ra_svn_editor->close_directory = ra_svn_close_dir;
455 ra_svn_editor->absent_directory = ra_svn_absent_dir;
456 ra_svn_editor->add_file = ra_svn_add_file;
457 ra_svn_editor->open_file = ra_svn_open_file;
458 ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
459 ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
460 ra_svn_editor->close_file = ra_svn_close_file;
461 ra_svn_editor->absent_file = ra_svn_absent_file;
462 ra_svn_editor->close_edit = ra_svn_close_edit;
463 ra_svn_editor->abort_edit = ra_svn_abort_edit;
465 *editor = ra_svn_editor;
468 svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
469 *edit_baton, NULL, NULL,
470 conn->shim_callbacks,
474 /* --- DRIVING AN EDITOR --- */
476 /* Store a token entry. The token string will be copied into pool. */
477 static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
480 svn_boolean_t is_file,
483 ra_svn_token_entry_t *entry;
485 entry = apr_palloc(pool, sizeof(*entry));
486 entry->token = svn_string_dup(token, pool);
487 entry->baton = baton;
488 entry->is_file = is_file;
489 entry->dstream = NULL;
492 apr_hash_set(ds->tokens, entry->token->data, entry->token->len, entry);
493 ds->last_token = entry;
498 static svn_error_t *lookup_token(ra_svn_driver_state_t *ds,
500 svn_boolean_t is_file,
501 ra_svn_token_entry_t **entry)
503 if (ds->last_token && svn_string_compare(ds->last_token->token, token))
505 *entry = ds->last_token;
509 *entry = apr_hash_get(ds->tokens, token->data, token->len);
510 ds->last_token = *entry;
513 if (!*entry || (*entry)->is_file != is_file)
514 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
515 _("Invalid file or dir token during edit"));
519 /* Remove a TOKEN entry from DS. */
520 static void remove_token(ra_svn_driver_state_t *ds,
523 apr_hash_set(ds->tokens, token->data, token->len, NULL);
525 /* Reset this unconditionally. In most cases, LAST_TOKEN->TOKEN will
526 match TOKEN anyway and if it doesn't, lookup_token() will suffer only
527 a minor performance hit. */
528 ds->last_token = NULL;
532 ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
534 const svn_ra_svn__list_t *params,
535 ra_svn_driver_state_t *ds)
539 SVN_ERR(svn_ra_svn__parse_tuple(params, "r", &rev));
540 SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
545 ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
547 const svn_ra_svn__list_t *params,
548 ra_svn_driver_state_t *ds)
555 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)s", &rev, &token));
556 subpool = svn_pool_create(ds->pool);
557 SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
559 store_token(ds, root_baton, token, FALSE, subpool);
564 ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
566 const svn_ra_svn__list_t *params,
567 ra_svn_driver_state_t *ds)
572 ra_svn_token_entry_t *entry;
574 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)s",
575 &path, &rev, &token));
576 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
577 path = svn_relpath_canonicalize(path, pool);
578 SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
583 ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
585 const svn_ra_svn__list_t *params,
586 ra_svn_driver_state_t *ds)
588 const char *path, *copy_path;
589 svn_string_t *token, *child_token;
590 svn_revnum_t copy_rev;
591 ra_svn_token_entry_t *entry;
595 SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?cr)", &path, &token,
596 &child_token, ©_path, ©_rev));
597 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
598 subpool = svn_pool_create(entry->pool);
599 path = svn_relpath_canonicalize(path, pool);
601 /* Some operations pass COPY_PATH as a full URL (commits, etc.).
602 Others (replay, e.g.) deliver an fspath. That's ... annoying. */
605 if (svn_path_is_url(copy_path))
606 copy_path = svn_uri_canonicalize(copy_path, pool);
608 copy_path = svn_fspath__canonicalize(copy_path, pool);
611 SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
612 copy_rev, subpool, &child_baton));
613 store_token(ds, child_baton, child_token, FALSE, subpool);
618 ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
620 const svn_ra_svn__list_t *params,
621 ra_svn_driver_state_t *ds)
624 svn_string_t *token, *child_token;
626 ra_svn_token_entry_t *entry;
630 SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?r)", &path, &token,
631 &child_token, &rev));
632 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
633 subpool = svn_pool_create(entry->pool);
634 path = svn_relpath_canonicalize(path, pool);
635 SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
637 store_token(ds, child_baton, child_token, FALSE, subpool);
642 ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
644 const svn_ra_svn__list_t *params,
645 ra_svn_driver_state_t *ds)
650 ra_svn_token_entry_t *entry;
652 SVN_ERR(svn_ra_svn__parse_tuple(params, "sc(?s)", &token, &name,
654 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
655 SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
661 ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
663 const svn_ra_svn__list_t *params,
664 ra_svn_driver_state_t *ds)
667 ra_svn_token_entry_t *entry;
669 /* Parse and look up the directory token. */
670 SVN_ERR(svn_ra_svn__parse_tuple(params, "s", &token));
671 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
673 /* Close the directory and destroy the baton. */
674 SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
675 remove_token(ds, token);
676 svn_pool_destroy(entry->pool);
681 ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
683 const svn_ra_svn__list_t *params,
684 ra_svn_driver_state_t *ds)
688 ra_svn_token_entry_t *entry;
690 /* Parse parameters and look up the directory token. */
691 SVN_ERR(svn_ra_svn__parse_tuple(params, "cs", &path, &token));
692 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
694 /* Call the editor. */
695 SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
700 ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
702 const svn_ra_svn__list_t *params,
703 ra_svn_driver_state_t *ds)
705 const char *path, *copy_path;
706 svn_string_t *token, *file_token;
707 svn_revnum_t copy_rev;
708 ra_svn_token_entry_t *entry, *file_entry;
710 SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?cr)", &path, &token,
711 &file_token, ©_path, ©_rev));
712 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
715 /* The PATH should be canonical .. but never trust incoming data. */
716 if (!svn_relpath_is_canonical(path))
717 path = svn_relpath_canonicalize(path, pool);
719 /* Some operations pass COPY_PATH as a full URL (commits, etc.).
720 Others (replay, e.g.) deliver an fspath. That's ... annoying. */
723 if (svn_path_is_url(copy_path))
724 copy_path = svn_uri_canonicalize(copy_path, pool);
726 copy_path = svn_fspath__canonicalize(copy_path, pool);
729 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
730 SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
731 ds->file_pool, &file_entry->baton));
736 ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
738 const svn_ra_svn__list_t *params,
739 ra_svn_driver_state_t *ds)
742 svn_string_t *token, *file_token;
744 ra_svn_token_entry_t *entry, *file_entry;
746 SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?r)", &path, &token,
748 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
751 /* The PATH should be canonical .. but never trust incoming data. */
752 if (!svn_relpath_is_canonical(path))
753 path = svn_relpath_canonicalize(path, pool);
755 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
756 SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
757 &file_entry->baton));
762 ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
764 const svn_ra_svn__list_t *params,
765 ra_svn_driver_state_t *ds)
768 ra_svn_token_entry_t *entry;
769 svn_txdelta_window_handler_t wh;
773 /* Parse arguments and look up the token. */
774 SVN_ERR(svn_ra_svn__parse_tuple(params, "s(?c)",
775 &token, &base_checksum));
776 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
778 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
779 _("Apply-textdelta already active"));
780 entry->pool = svn_pool_create(ds->file_pool);
781 SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
782 entry->pool, &wh, &wh_baton));
783 entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
788 ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
790 const svn_ra_svn__list_t *params,
791 ra_svn_driver_state_t *ds)
794 ra_svn_token_entry_t *entry;
797 /* Parse arguments and look up the token. */
798 SVN_ERR(svn_ra_svn__parse_tuple(params, "ss", &token, &str));
799 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
801 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
802 _("Apply-textdelta not active"));
803 SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
808 ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
810 const svn_ra_svn__list_t *params,
811 ra_svn_driver_state_t *ds)
814 ra_svn_token_entry_t *entry;
816 /* Parse arguments and look up the token. */
817 SVN_ERR(svn_ra_svn__parse_tuple(params, "s", &token));
818 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
820 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
821 _("Apply-textdelta not active"));
822 SVN_CMD_ERR(svn_stream_close(entry->dstream));
823 entry->dstream = NULL;
824 svn_pool_destroy(entry->pool);
829 ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
831 const svn_ra_svn__list_t *params,
832 ra_svn_driver_state_t *ds)
835 svn_string_t *token, *value;
836 ra_svn_token_entry_t *entry;
838 SVN_ERR(svn_ra_svn__parse_tuple(params, "sc(?s)", &token, &name,
840 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
841 SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
846 ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
848 const svn_ra_svn__list_t *params,
849 ra_svn_driver_state_t *ds)
852 ra_svn_token_entry_t *entry;
853 const char *text_checksum;
855 /* Parse arguments and look up the file token. */
856 SVN_ERR(svn_ra_svn__parse_tuple(params, "s(?c)",
857 &token, &text_checksum));
858 SVN_ERR(lookup_token(ds, token, TRUE, &entry));
860 /* Close the file and destroy the baton. */
861 SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
862 remove_token(ds, token);
863 if (--ds->file_refs == 0)
864 svn_pool_clear(ds->file_pool);
869 ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
871 const svn_ra_svn__list_t *params,
872 ra_svn_driver_state_t *ds)
876 ra_svn_token_entry_t *entry;
878 /* Parse parameters and look up the parent directory token. */
879 SVN_ERR(svn_ra_svn__parse_tuple(params, "cs", &path, &token));
880 SVN_ERR(lookup_token(ds, token, FALSE, &entry));
882 /* Call the editor. */
883 SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
888 ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
890 const svn_ra_svn__list_t *params,
891 ra_svn_driver_state_t *ds)
893 SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
896 /* Before enabling this in non-maintainer mode:
897 * Note that this code is used on both client *and* server */
898 if (apr_hash_count(ds->tokens) != 0)
899 return svn_error_create(
900 SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL,
901 _("Closing editor with directories or files open"));
904 *ds->aborted = FALSE;
905 return svn_ra_svn__write_cmd_response(conn, pool, "");
909 ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
911 const svn_ra_svn__list_t *params,
912 ra_svn_driver_state_t *ds)
917 SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
918 return svn_ra_svn__write_cmd_response(conn, pool, "");
922 ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
924 const svn_ra_svn__list_t *params,
925 ra_svn_driver_state_t *ds)
928 return svn_error_createf
929 (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
930 _("Command 'finish-replay' invalid outside of replays"));
933 *ds->aborted = FALSE;
937 /* Common function signature for all editor command handlers. */
938 typedef svn_error_t *(*cmd_handler_t)(svn_ra_svn_conn_t *conn,
940 const svn_ra_svn__list_t *params,
941 ra_svn_driver_state_t *ds);
943 static const struct {
945 cmd_handler_t handler;
946 } ra_svn_edit_cmds[] = {
947 { "change-file-prop", ra_svn_handle_change_file_prop },
948 { "open-file", ra_svn_handle_open_file },
949 { "apply-textdelta", ra_svn_handle_apply_textdelta },
950 { "textdelta-chunk", ra_svn_handle_textdelta_chunk },
951 { "close-file", ra_svn_handle_close_file },
952 { "add-dir", ra_svn_handle_add_dir },
953 { "open-dir", ra_svn_handle_open_dir },
954 { "change-dir-prop", ra_svn_handle_change_dir_prop },
955 { "delete-entry", ra_svn_handle_delete_entry },
956 { "close-dir", ra_svn_handle_close_dir },
957 { "absent-dir", ra_svn_handle_absent_dir },
958 { "add-file", ra_svn_handle_add_file },
959 { "textdelta-end", ra_svn_handle_textdelta_end },
960 { "absent-file", ra_svn_handle_absent_file },
961 { "abort-edit", ra_svn_handle_abort_edit },
962 { "finish-replay", ra_svn_handle_finish_replay },
963 { "target-rev", ra_svn_handle_target_rev },
964 { "open-root", ra_svn_handle_open_root },
965 { "close-edit", ra_svn_handle_close_edit },
969 /* All editor commands are kept in a collision-free hash table. */
972 It is similar to ra_svn_edit_cmds but uses our SVN string type. */
973 typedef struct cmd_t {
975 cmd_handler_t handler;
978 /* The actual hash table. It will be filled once before first usage.
980 If you add more commands, you may have to tweak the table size to
981 eliminate collisions. Alternatively, you may modify the hash function.
983 Be sure to initialize all elements with 0 as the has conflict detection
984 will rely on it (see init_cmd_hash).
986 #define CMD_HASH_SIZE 67
987 static cmd_t cmd_hash[CMD_HASH_SIZE] = { { { NULL } } };
989 /* Init flag that controls CMD_HASH's atomic initialization. */
990 static volatile svn_atomic_t cmd_hash_initialized = FALSE;
992 /* Super-fast hash function that works very well with the structure of our
993 command words. It produces no conflicts for them.
995 Return the index within CMD_HASH that a command NAME of LEN chars would
999 cmd_hash_func(const char *name,
1002 apr_size_t value = (apr_byte_t)(name[0] - 'a') % 8
1003 + 1 * (apr_byte_t)(name[len - 1] - 'a') % 8
1005 return value % CMD_HASH_SIZE;
1008 /* svn_atomic__init_once callback that fills the CMD_HASH table. It will
1009 error out on hash collisions. BATON and POOL are not used. */
1010 static svn_error_t *
1011 init_cmd_hash(void *baton,
1015 for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
1017 apr_size_t len = strlen(ra_svn_edit_cmds[i].cmd);
1018 apr_size_t value = cmd_hash_func(ra_svn_edit_cmds[i].cmd, len);
1019 SVN_ERR_ASSERT(cmd_hash[value].cmd.data == NULL);
1021 cmd_hash[value].cmd.data = ra_svn_edit_cmds[i].cmd;
1022 cmd_hash[value].cmd.len = len;
1023 cmd_hash[value].handler = ra_svn_edit_cmds[i].handler;
1026 return SVN_NO_ERROR;
1029 /* Return the command handler function for the command name CMD.
1030 Return NULL if no such handler exists */
1031 static cmd_handler_t
1032 cmd_lookup(const char *cmd)
1035 apr_size_t len = strlen(cmd);
1037 /* Malicious data that our hash function may not like? */
1042 value = cmd_hash_func(cmd, len);
1045 if (cmd_hash[value].cmd.len != len)
1048 if (memcmp(cmd_hash[value].cmd.data, cmd, len))
1052 return cmd_hash[value].handler;
1055 static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1058 ra_svn_driver_state_t *ds = baton;
1060 svn_ra_svn__list_t *params;
1062 /* We blocked trying to send an error. Read and discard an editing
1063 * command in order to avoid deadlock. */
1064 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, ¶ms));
1065 if (strcmp(cmd, "abort-edit") == 0)
1068 svn_ra_svn__set_block_handler(conn, NULL, NULL);
1070 return SVN_NO_ERROR;
1073 svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
1075 const svn_delta_editor_t *editor,
1077 svn_boolean_t *aborted,
1078 svn_boolean_t for_replay)
1080 ra_svn_driver_state_t state;
1081 apr_pool_t *subpool = svn_pool_create(pool);
1083 svn_error_t *err, *write_err;
1084 svn_ra_svn__list_t *params;
1086 SVN_ERR(svn_atomic__init_once(&cmd_hash_initialized, init_cmd_hash, NULL,
1089 state.editor = editor;
1090 state.edit_baton = edit_baton;
1091 state.tokens = svn_hash__make(pool);
1092 state.last_token = NULL;
1093 state.aborted = aborted;
1096 state.file_pool = svn_pool_create(pool);
1097 state.file_refs = 0;
1098 state.for_replay = for_replay;
1102 svn_pool_clear(subpool);
1104 /* WRT to applying I/O limits, treat each editor command as a separate
1105 * protocol command. */
1106 svn_ra_svn__reset_command_io_counters(conn);
1109 cmd_handler_t handler;
1110 SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms));
1111 handler = cmd_lookup(cmd);
1114 err = (*handler)(conn, subpool, params, &state);
1115 else if (strcmp(cmd, "failure") == 0)
1117 /* While not really an editor command this can occur when
1118 reporter->finish_report() fails before the first editor
1122 err = svn_ra_svn__handle_failure_status(params);
1123 return svn_error_compose_create(
1125 editor->abort_edit(edit_baton, subpool));
1129 err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
1130 _("Unknown editor command '%s'"), cmd);
1131 err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
1136 const char* command = NULL;
1137 SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
1138 if (strcmp(command, "close-edit") == 0)
1143 err = svn_ra_svn__write_cmd_response(conn, pool, "");
1149 if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
1155 /* Abort the edit and use non-blocking I/O to write the error. */
1158 err = svn_error_compose_create(
1160 svn_error_trace(editor->abort_edit(edit_baton,
1163 svn_ra_svn__set_block_handler(conn, blocked_write, &state);
1165 write_err = svn_ra_svn__write_cmd_failure(
1167 svn_ra_svn__locate_real_error_child(err));
1169 write_err = svn_ra_svn__flush(conn, subpool);
1170 svn_ra_svn__set_block_handler(conn, NULL, NULL);
1171 svn_error_clear(err); /* We just sent this error */
1178 /* Read and discard editing commands until the edit is complete.
1179 Hopefully, the other side will call another editor command, run
1180 check_for_error, notice the error, write "abort-edit" at us, and
1181 throw the error up a few levels on its side (possibly even
1182 tossing it right back at us, which is why we can return
1183 SVN_NO_ERROR below).
1185 However, if the other side is way ahead of us, it might
1186 completely finish the edit (or sequence of edit/revprops, for
1187 "replay-range") before we send over our "failure". So we should
1188 also stop if we see "success". (Then the other side will try to
1189 interpret our "failure" as a command, which will itself fail...
1190 The net effect is that whatever error we wrote to the other side
1191 will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.)
1195 svn_pool_clear(subpool);
1196 err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms);
1197 if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
1199 /* Other side disconnected; that's no error. */
1200 svn_error_clear(err);
1201 svn_pool_destroy(subpool);
1202 return SVN_NO_ERROR;
1204 svn_error_clear(err);
1205 if (strcmp(cmd, "abort-edit") == 0
1206 || strcmp(cmd, "success") == 0)
1210 svn_pool_destroy(subpool);
1211 return SVN_NO_ERROR;
1214 svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1215 const svn_delta_editor_t *editor,
1217 svn_boolean_t *aborted)
1219 return svn_ra_svn_drive_editor2(conn,