editorp.c revision 362181
1/* 2 * editorp.c : Driving and consuming an editor across an svn connection 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#define APR_WANT_STRFUNC 27#include <apr_want.h> 28#include <apr_general.h> 29#include <apr_strings.h> 30 31#include "svn_hash.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" 38#include "svn_path.h" 39#include "svn_pools.h" 40#include "svn_private_config.h" 41 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" 47 48#include "ra_svn.h" 49 50/* 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. 57 */ 58 59typedef struct ra_svn_edit_baton_t { 60 svn_ra_svn_conn_t *conn; 61 svn_ra_svn_edit_callback callback; /* Called on successful completion. */ 62 void *callback_baton; 63 apr_uint64_t next_token; 64 svn_boolean_t got_status; 65} ra_svn_edit_baton_t; 66 67/* Works for both directories and files. */ 68typedef struct ra_svn_baton_t { 69 svn_ra_svn_conn_t *conn; 70 apr_pool_t *pool; 71 ra_svn_edit_baton_t *eb; 72 svn_string_t *token; 73} ra_svn_baton_t; 74 75/* Forward declaration. */ 76typedef struct ra_svn_token_entry_t ra_svn_token_entry_t; 77 78typedef struct ra_svn_driver_state_t { 79 const svn_delta_editor_t *editor; 80 void *edit_baton; 81 apr_hash_t *tokens; 82 83 /* Entry for the last token seen. May be NULL. */ 84 ra_svn_token_entry_t *last_token; 85 svn_boolean_t *aborted; 86 svn_boolean_t done; 87 apr_pool_t *pool; 88 apr_pool_t *file_pool; 89 int file_refs; 90 svn_boolean_t for_replay; 91} ra_svn_driver_state_t; 92 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. */ 102struct ra_svn_token_entry_t { 103 svn_string_t *token; 104 void *baton; 105 svn_boolean_t is_file; 106 svn_stream_t *dstream; /* svndiff stream for apply_textdelta */ 107 apr_pool_t *pool; 108}; 109 110/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */ 111 112static svn_string_t * 113make_token(char type, 114 ra_svn_edit_baton_t *eb, 115 apr_pool_t *pool) 116{ 117 apr_size_t len; 118 char buffer[1 + SVN_INT64_BUFFER_SIZE]; 119 buffer[0] = type; 120 len = 1 + svn__ui64toa(&buffer[1], eb->next_token++); 121 122 return svn_string_ncreate(buffer, len, pool); 123} 124 125static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn, 126 apr_pool_t *pool, 127 ra_svn_edit_baton_t *eb, 128 svn_string_t *token) 129{ 130 ra_svn_baton_t *b; 131 132 b = apr_palloc(pool, sizeof(*b)); 133 b->conn = conn; 134 b->pool = pool; 135 b->eb = eb; 136 b->token = token; 137 return b; 138} 139 140/* Check for an early error status report from the consumer. If we 141 * get one, abort the edit and return the error. */ 142static svn_error_t * 143check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool) 144{ 145 svn_boolean_t available; 146 SVN_ERR_ASSERT(!eb->got_status); 147 148 /* reset TX counter */ 149 eb->conn->written_since_error_check = 0; 150 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; 153 154 /* any incoming data? */ 155 SVN_ERR(svn_ra_svn__data_available(eb->conn, &available)); 156 if (available) 157 { 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")); 164 } 165 return SVN_NO_ERROR; 166} 167 168static svn_error_t * 169check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool) 170{ 171 return eb->conn->may_check_for_error 172 ? check_for_error_internal(eb, pool) 173 : SVN_NO_ERROR; 174} 175 176static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev, 177 apr_pool_t *pool) 178{ 179 ra_svn_edit_baton_t *eb = edit_baton; 180 181 SVN_ERR(check_for_error(eb, pool)); 182 SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev)); 183 return SVN_NO_ERROR; 184} 185 186static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev, 187 apr_pool_t *pool, void **root_baton) 188{ 189 ra_svn_edit_baton_t *eb = edit_baton; 190 svn_string_t *token = make_token('d', eb, pool); 191 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); 195 return SVN_NO_ERROR; 196} 197 198static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev, 199 void *parent_baton, apr_pool_t *pool) 200{ 201 ra_svn_baton_t *b = parent_baton; 202 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)); 206 return SVN_NO_ERROR; 207} 208 209static 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) 213{ 214 ra_svn_baton_t *b = parent_baton; 215 svn_string_t *token = make_token('d', b->eb, pool); 216 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); 223 return SVN_NO_ERROR; 224} 225 226static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton, 227 svn_revnum_t rev, apr_pool_t *pool, 228 void **child_baton) 229{ 230 ra_svn_baton_t *b = parent_baton; 231 svn_string_t *token = make_token('d', b->eb, pool); 232 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, 235 token, rev)); 236 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); 237 return SVN_NO_ERROR; 238} 239 240static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name, 241 const svn_string_t *value, 242 apr_pool_t *pool) 243{ 244 ra_svn_baton_t *b = dir_baton; 245 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, 248 name, value)); 249 return SVN_NO_ERROR; 250} 251 252static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool) 253{ 254 ra_svn_baton_t *b = dir_baton; 255 256 SVN_ERR(check_for_error(b->eb, pool)); 257 SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token)); 258 return SVN_NO_ERROR; 259} 260 261static svn_error_t *ra_svn_absent_dir(const char *path, 262 void *parent_baton, apr_pool_t *pool) 263{ 264 ra_svn_baton_t *b = parent_baton; 265 266 /* Avoid sending an unknown command if the other end doesn't support 267 absent-dir. */ 268 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES)) 269 return SVN_NO_ERROR; 270 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)); 273 return SVN_NO_ERROR; 274} 275 276static svn_error_t *ra_svn_add_file(const char *path, 277 void *parent_baton, 278 const char *copy_path, 279 svn_revnum_t copy_rev, 280 apr_pool_t *pool, 281 void **file_baton) 282{ 283 ra_svn_baton_t *b = parent_baton; 284 svn_string_t *token = make_token('c', b->eb, pool); 285 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); 292 return SVN_NO_ERROR; 293} 294 295static svn_error_t *ra_svn_open_file(const char *path, 296 void *parent_baton, 297 svn_revnum_t rev, 298 apr_pool_t *pool, 299 void **file_baton) 300{ 301 ra_svn_baton_t *b = parent_baton; 302 svn_string_t *token = make_token('c', b->eb, pool); 303 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, 306 token, rev)); 307 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); 308 return SVN_NO_ERROR; 309} 310 311static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data, 312 apr_size_t *len) 313{ 314 ra_svn_baton_t *b = baton; 315 svn_string_t str; 316 317 SVN_ERR(check_for_error(b->eb, b->pool)); 318 str.data = data; 319 str.len = *len; 320 return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool, 321 b->token, &str); 322} 323 324static svn_error_t *ra_svn_svndiff_close_handler(void *baton) 325{ 326 ra_svn_baton_t *b = baton; 327 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)); 330 return SVN_NO_ERROR; 331} 332 333static svn_error_t *ra_svn_apply_textdelta(void *file_baton, 334 const char *base_checksum, 335 apr_pool_t *pool, 336 svn_txdelta_window_handler_t *wh, 337 void **wh_baton) 338{ 339 ra_svn_baton_t *b = file_baton; 340 svn_stream_t *diff_stream; 341 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, 345 base_checksum)); 346 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); 353 354 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 355 svn_ra_svn__svndiff_version(b->conn), 356 b->conn->compression_level, pool); 357 return SVN_NO_ERROR; 358} 359 360static svn_error_t *ra_svn_change_file_prop(void *file_baton, 361 const char *name, 362 const svn_string_t *value, 363 apr_pool_t *pool) 364{ 365 ra_svn_baton_t *b = file_baton; 366 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)); 370 return SVN_NO_ERROR; 371} 372 373static svn_error_t *ra_svn_close_file(void *file_baton, 374 const char *text_checksum, 375 apr_pool_t *pool) 376{ 377 ra_svn_baton_t *b = file_baton; 378 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)); 382 return SVN_NO_ERROR; 383} 384 385static svn_error_t *ra_svn_absent_file(const char *path, 386 void *parent_baton, apr_pool_t *pool) 387{ 388 ra_svn_baton_t *b = parent_baton; 389 390 /* Avoid sending an unknown command if the other end doesn't support 391 absent-file. */ 392 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES)) 393 return SVN_NO_ERROR; 394 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)); 397 return SVN_NO_ERROR; 398} 399 400static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool) 401{ 402 ra_svn_edit_baton_t *eb = edit_baton; 403 svn_error_t *err; 404 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, "")); 409 if (err) 410 { 411 return svn_error_compose_create( 412 err, 413 svn_error_trace( 414 svn_ra_svn__write_cmd_abort_edit(eb->conn, pool))); 415 } 416 if (eb->callback) 417 SVN_ERR(eb->callback(eb->callback_baton)); 418 return SVN_NO_ERROR; 419} 420 421static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool) 422{ 423 ra_svn_edit_baton_t *eb = edit_baton; 424 425 if (eb->got_status) 426 return SVN_NO_ERROR; 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, "")); 429 return SVN_NO_ERROR; 430} 431 432void svn_ra_svn_get_editor(const svn_delta_editor_t **editor, 433 void **edit_baton, svn_ra_svn_conn_t *conn, 434 apr_pool_t *pool, 435 svn_ra_svn_edit_callback callback, 436 void *callback_baton) 437{ 438 svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool); 439 ra_svn_edit_baton_t *eb; 440 441 eb = apr_palloc(pool, sizeof(*eb)); 442 eb->conn = conn; 443 eb->callback = callback; 444 eb->callback_baton = callback_baton; 445 eb->next_token = 0; 446 eb->got_status = FALSE; 447 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; 464 465 *editor = ra_svn_editor; 466 *edit_baton = eb; 467 468 svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor, 469 *edit_baton, NULL, NULL, 470 conn->shim_callbacks, 471 pool, pool)); 472} 473 474/* --- DRIVING AN EDITOR --- */ 475 476/* Store a token entry. The token string will be copied into pool. */ 477static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds, 478 void *baton, 479 svn_string_t *token, 480 svn_boolean_t is_file, 481 apr_pool_t *pool) 482{ 483 ra_svn_token_entry_t *entry; 484 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; 490 entry->pool = pool; 491 492 apr_hash_set(ds->tokens, entry->token->data, entry->token->len, entry); 493 ds->last_token = entry; 494 495 return entry; 496} 497 498static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, 499 svn_string_t *token, 500 svn_boolean_t is_file, 501 ra_svn_token_entry_t **entry) 502{ 503 if (ds->last_token && svn_string_compare(ds->last_token->token, token)) 504 { 505 *entry = ds->last_token; 506 } 507 else 508 { 509 *entry = apr_hash_get(ds->tokens, token->data, token->len); 510 ds->last_token = *entry; 511 } 512 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")); 516 return SVN_NO_ERROR; 517} 518 519/* Remove a TOKEN entry from DS. */ 520static void remove_token(ra_svn_driver_state_t *ds, 521 svn_string_t *token) 522{ 523 apr_hash_set(ds->tokens, token->data, token->len, NULL); 524 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; 529} 530 531static svn_error_t * 532ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn, 533 apr_pool_t *pool, 534 const svn_ra_svn__list_t *params, 535 ra_svn_driver_state_t *ds) 536{ 537 svn_revnum_t rev; 538 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)); 541 return SVN_NO_ERROR; 542} 543 544static svn_error_t * 545ra_svn_handle_open_root(svn_ra_svn_conn_t *conn, 546 apr_pool_t *pool, 547 const svn_ra_svn__list_t *params, 548 ra_svn_driver_state_t *ds) 549{ 550 svn_revnum_t rev; 551 apr_pool_t *subpool; 552 svn_string_t *token; 553 void *root_baton; 554 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, 558 &root_baton)); 559 store_token(ds, root_baton, token, FALSE, subpool); 560 return SVN_NO_ERROR; 561} 562 563static svn_error_t * 564ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn, 565 apr_pool_t *pool, 566 const svn_ra_svn__list_t *params, 567 ra_svn_driver_state_t *ds) 568{ 569 const char *path; 570 svn_string_t *token; 571 svn_revnum_t rev; 572 ra_svn_token_entry_t *entry; 573 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)); 579 return SVN_NO_ERROR; 580} 581 582static svn_error_t * 583ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn, 584 apr_pool_t *pool, 585 const svn_ra_svn__list_t *params, 586 ra_svn_driver_state_t *ds) 587{ 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; 592 apr_pool_t *subpool; 593 void *child_baton; 594 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); 600 601 /* Some operations pass COPY_PATH as a full URL (commits, etc.). 602 Others (replay, e.g.) deliver an fspath. That's ... annoying. */ 603 if (copy_path) 604 { 605 if (svn_path_is_url(copy_path)) 606 copy_path = svn_uri_canonicalize(copy_path, pool); 607 else 608 copy_path = svn_fspath__canonicalize(copy_path, pool); 609 } 610 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); 614 return SVN_NO_ERROR; 615} 616 617static svn_error_t * 618ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn, 619 apr_pool_t *pool, 620 const svn_ra_svn__list_t *params, 621 ra_svn_driver_state_t *ds) 622{ 623 const char *path; 624 svn_string_t *token, *child_token; 625 svn_revnum_t rev; 626 ra_svn_token_entry_t *entry; 627 apr_pool_t *subpool; 628 void *child_baton; 629 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, 636 &child_baton)); 637 store_token(ds, child_baton, child_token, FALSE, subpool); 638 return SVN_NO_ERROR; 639} 640 641static svn_error_t * 642ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn, 643 apr_pool_t *pool, 644 const svn_ra_svn__list_t *params, 645 ra_svn_driver_state_t *ds) 646{ 647 svn_string_t *token; 648 const char *name; 649 svn_string_t *value; 650 ra_svn_token_entry_t *entry; 651 652 SVN_ERR(svn_ra_svn__parse_tuple(params, "sc(?s)", &token, &name, 653 &value)); 654 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 655 SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value, 656 entry->pool)); 657 return SVN_NO_ERROR; 658} 659 660static svn_error_t * 661ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn, 662 apr_pool_t *pool, 663 const svn_ra_svn__list_t *params, 664 ra_svn_driver_state_t *ds) 665{ 666 svn_string_t *token; 667 ra_svn_token_entry_t *entry; 668 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)); 672 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); 677 return SVN_NO_ERROR; 678} 679 680static svn_error_t * 681ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn, 682 apr_pool_t *pool, 683 const svn_ra_svn__list_t *params, 684 ra_svn_driver_state_t *ds) 685{ 686 const char *path; 687 svn_string_t *token; 688 ra_svn_token_entry_t *entry; 689 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)); 693 694 /* Call the editor. */ 695 SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool)); 696 return SVN_NO_ERROR; 697} 698 699static svn_error_t * 700ra_svn_handle_add_file(svn_ra_svn_conn_t *conn, 701 apr_pool_t *pool, 702 const svn_ra_svn__list_t *params, 703 ra_svn_driver_state_t *ds) 704{ 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; 709 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)); 713 ds->file_refs++; 714 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); 718 719 /* Some operations pass COPY_PATH as a full URL (commits, etc.). 720 Others (replay, e.g.) deliver an fspath. That's ... annoying. */ 721 if (copy_path) 722 { 723 if (svn_path_is_url(copy_path)) 724 copy_path = svn_uri_canonicalize(copy_path, pool); 725 else 726 copy_path = svn_fspath__canonicalize(copy_path, pool); 727 } 728 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)); 732 return SVN_NO_ERROR; 733} 734 735static svn_error_t * 736ra_svn_handle_open_file(svn_ra_svn_conn_t *conn, 737 apr_pool_t *pool, 738 const svn_ra_svn__list_t *params, 739 ra_svn_driver_state_t *ds) 740{ 741 const char *path; 742 svn_string_t *token, *file_token; 743 svn_revnum_t rev; 744 ra_svn_token_entry_t *entry, *file_entry; 745 746 SVN_ERR(svn_ra_svn__parse_tuple(params, "css(?r)", &path, &token, 747 &file_token, &rev)); 748 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 749 ds->file_refs++; 750 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); 754 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)); 758 return SVN_NO_ERROR; 759} 760 761static svn_error_t * 762ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn, 763 apr_pool_t *pool, 764 const svn_ra_svn__list_t *params, 765 ra_svn_driver_state_t *ds) 766{ 767 svn_string_t *token; 768 ra_svn_token_entry_t *entry; 769 svn_txdelta_window_handler_t wh; 770 void *wh_baton; 771 char *base_checksum; 772 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)); 777 if (entry->dstream) 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); 784 return SVN_NO_ERROR; 785} 786 787static svn_error_t * 788ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn, 789 apr_pool_t *pool, 790 const svn_ra_svn__list_t *params, 791 ra_svn_driver_state_t *ds) 792{ 793 svn_string_t *token; 794 ra_svn_token_entry_t *entry; 795 svn_string_t *str; 796 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)); 800 if (!entry->dstream) 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)); 804 return SVN_NO_ERROR; 805} 806 807static svn_error_t * 808ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn, 809 apr_pool_t *pool, 810 const svn_ra_svn__list_t *params, 811 ra_svn_driver_state_t *ds) 812{ 813 svn_string_t *token; 814 ra_svn_token_entry_t *entry; 815 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)); 819 if (!entry->dstream) 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); 825 return SVN_NO_ERROR; 826} 827 828static svn_error_t * 829ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn, 830 apr_pool_t *pool, 831 const svn_ra_svn__list_t *params, 832 ra_svn_driver_state_t *ds) 833{ 834 const char *name; 835 svn_string_t *token, *value; 836 ra_svn_token_entry_t *entry; 837 838 SVN_ERR(svn_ra_svn__parse_tuple(params, "sc(?s)", &token, &name, 839 &value)); 840 SVN_ERR(lookup_token(ds, token, TRUE, &entry)); 841 SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool)); 842 return SVN_NO_ERROR; 843} 844 845static svn_error_t * 846ra_svn_handle_close_file(svn_ra_svn_conn_t *conn, 847 apr_pool_t *pool, 848 const svn_ra_svn__list_t *params, 849 ra_svn_driver_state_t *ds) 850{ 851 svn_string_t *token; 852 ra_svn_token_entry_t *entry; 853 const char *text_checksum; 854 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)); 859 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); 865 return SVN_NO_ERROR; 866} 867 868static svn_error_t * 869ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn, 870 apr_pool_t *pool, 871 const svn_ra_svn__list_t *params, 872 ra_svn_driver_state_t *ds) 873{ 874 const char *path; 875 svn_string_t *token; 876 ra_svn_token_entry_t *entry; 877 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)); 881 882 /* Call the editor. */ 883 SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool)); 884 return SVN_NO_ERROR; 885} 886 887static svn_error_t * 888ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn, 889 apr_pool_t *pool, 890 const svn_ra_svn__list_t *params, 891 ra_svn_driver_state_t *ds) 892{ 893 SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool)); 894 ds->done = TRUE; 895#ifdef SVN_DEBUG 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")); 902#endif 903 if (ds->aborted) 904 *ds->aborted = FALSE; 905 return svn_ra_svn__write_cmd_response(conn, pool, ""); 906} 907 908static svn_error_t * 909ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn, 910 apr_pool_t *pool, 911 const svn_ra_svn__list_t *params, 912 ra_svn_driver_state_t *ds) 913{ 914 ds->done = TRUE; 915 if (ds->aborted) 916 *ds->aborted = TRUE; 917 SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool)); 918 return svn_ra_svn__write_cmd_response(conn, pool, ""); 919} 920 921static svn_error_t * 922ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn, 923 apr_pool_t *pool, 924 const svn_ra_svn__list_t *params, 925 ra_svn_driver_state_t *ds) 926{ 927 if (!ds->for_replay) 928 return svn_error_createf 929 (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL, 930 _("Command 'finish-replay' invalid outside of replays")); 931 ds->done = TRUE; 932 if (ds->aborted) 933 *ds->aborted = FALSE; 934 return SVN_NO_ERROR; 935} 936 937/* Common function signature for all editor command handlers. */ 938typedef svn_error_t *(*cmd_handler_t)(svn_ra_svn_conn_t *conn, 939 apr_pool_t *pool, 940 const svn_ra_svn__list_t *params, 941 ra_svn_driver_state_t *ds); 942 943static const struct { 944 const char *cmd; 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 }, 966 { NULL } 967}; 968 969/* All editor commands are kept in a collision-free hash table. */ 970 971/* Hash table entry. 972 It is similar to ra_svn_edit_cmds but uses our SVN string type. */ 973typedef struct cmd_t { 974 svn_string_t cmd; 975 cmd_handler_t handler; 976} cmd_t; 977 978/* The actual hash table. It will be filled once before first usage. 979 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. 982 983 Be sure to initialize all elements with 0 as the has conflict detection 984 will rely on it (see init_cmd_hash). 985 */ 986#define CMD_HASH_SIZE 67 987static cmd_t cmd_hash[CMD_HASH_SIZE] = { { { NULL } } }; 988 989/* Init flag that controls CMD_HASH's atomic initialization. */ 990static volatile svn_atomic_t cmd_hash_initialized = FALSE; 991 992/* Super-fast hash function that works very well with the structure of our 993 command words. It produces no conflicts for them. 994 995 Return the index within CMD_HASH that a command NAME of LEN chars would 996 be found. LEN > 0. 997 */ 998static apr_size_t 999cmd_hash_func(const char *name, 1000 apr_size_t len) 1001{ 1002 apr_size_t value = (apr_byte_t)(name[0] - 'a') % 8 1003 + 1 * (apr_byte_t)(name[len - 1] - 'a') % 8 1004 + 10 * (len - 7); 1005 return value % CMD_HASH_SIZE; 1006} 1007 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. */ 1010static svn_error_t * 1011init_cmd_hash(void *baton, 1012 apr_pool_t *pool) 1013{ 1014 int i; 1015 for (i = 0; ra_svn_edit_cmds[i].cmd; i++) 1016 { 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); 1020 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; 1024 } 1025 1026 return SVN_NO_ERROR; 1027} 1028 1029/* Return the command handler function for the command name CMD. 1030 Return NULL if no such handler exists */ 1031static cmd_handler_t 1032cmd_lookup(const char *cmd) 1033{ 1034 apr_size_t value; 1035 apr_size_t len = strlen(cmd); 1036 1037 /* Malicious data that our hash function may not like? */ 1038 if (len == 0) 1039 return NULL; 1040 1041 /* Hash lookup. */ 1042 value = cmd_hash_func(cmd, len); 1043 1044 /* Hit? */ 1045 if (cmd_hash[value].cmd.len != len) 1046 return NULL; 1047 1048 if (memcmp(cmd_hash[value].cmd.data, cmd, len)) 1049 return NULL; 1050 1051 /* Yes! */ 1052 return cmd_hash[value].handler; 1053} 1054 1055static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1056 void *baton) 1057{ 1058 ra_svn_driver_state_t *ds = baton; 1059 const char *cmd; 1060 svn_ra_svn__list_t *params; 1061 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) 1066 { 1067 ds->done = TRUE; 1068 svn_ra_svn__set_block_handler(conn, NULL, NULL); 1069 } 1070 return SVN_NO_ERROR; 1071} 1072 1073svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn, 1074 apr_pool_t *pool, 1075 const svn_delta_editor_t *editor, 1076 void *edit_baton, 1077 svn_boolean_t *aborted, 1078 svn_boolean_t for_replay) 1079{ 1080 ra_svn_driver_state_t state; 1081 apr_pool_t *subpool = svn_pool_create(pool); 1082 const char *cmd; 1083 svn_error_t *err, *write_err; 1084 svn_ra_svn__list_t *params; 1085 1086 SVN_ERR(svn_atomic__init_once(&cmd_hash_initialized, init_cmd_hash, NULL, 1087 pool)); 1088 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; 1094 state.done = FALSE; 1095 state.pool = pool; 1096 state.file_pool = svn_pool_create(pool); 1097 state.file_refs = 0; 1098 state.for_replay = for_replay; 1099 1100 while (!state.done) 1101 { 1102 svn_pool_clear(subpool); 1103 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); 1107 if (editor) 1108 { 1109 cmd_handler_t handler; 1110 SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms)); 1111 handler = cmd_lookup(cmd); 1112 1113 if (handler) 1114 err = (*handler)(conn, subpool, params, &state); 1115 else if (strcmp(cmd, "failure") == 0) 1116 { 1117 /* While not really an editor command this can occur when 1118 reporter->finish_report() fails before the first editor 1119 command */ 1120 if (aborted) 1121 *aborted = TRUE; 1122 err = svn_ra_svn__handle_failure_status(params); 1123 return svn_error_compose_create( 1124 err, 1125 editor->abort_edit(edit_baton, subpool)); 1126 } 1127 else 1128 { 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); 1132 } 1133 } 1134 else 1135 { 1136 const char* command = NULL; 1137 SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command)); 1138 if (strcmp(command, "close-edit") == 0) 1139 { 1140 state.done = TRUE; 1141 if (aborted) 1142 *aborted = FALSE; 1143 err = svn_ra_svn__write_cmd_response(conn, pool, ""); 1144 } 1145 else 1146 err = NULL; 1147 } 1148 1149 if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR) 1150 { 1151 if (aborted) 1152 *aborted = TRUE; 1153 if (!state.done) 1154 { 1155 /* Abort the edit and use non-blocking I/O to write the error. */ 1156 if (editor) 1157 { 1158 err = svn_error_compose_create( 1159 err, 1160 svn_error_trace(editor->abort_edit(edit_baton, 1161 subpool))); 1162 } 1163 svn_ra_svn__set_block_handler(conn, blocked_write, &state); 1164 } 1165 write_err = svn_ra_svn__write_cmd_failure( 1166 conn, subpool, 1167 svn_ra_svn__locate_real_error_child(err)); 1168 if (!write_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 */ 1172 SVN_ERR(write_err); 1173 break; 1174 } 1175 SVN_ERR(err); 1176 } 1177 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). 1184 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.) 1192 */ 1193 while (!state.done) 1194 { 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) 1198 { 1199 /* Other side disconnected; that's no error. */ 1200 svn_error_clear(err); 1201 svn_pool_destroy(subpool); 1202 return SVN_NO_ERROR; 1203 } 1204 svn_error_clear(err); 1205 if (strcmp(cmd, "abort-edit") == 0 1206 || strcmp(cmd, "success") == 0) 1207 state.done = TRUE; 1208 } 1209 1210 svn_pool_destroy(subpool); 1211 return SVN_NO_ERROR; 1212} 1213 1214svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1215 const svn_delta_editor_t *editor, 1216 void *edit_baton, 1217 svn_boolean_t *aborted) 1218{ 1219 return svn_ra_svn_drive_editor2(conn, 1220 pool, 1221 editor, 1222 edit_baton, 1223 aborted, 1224 FALSE); 1225} 1226