editorp.c revision 299742
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_fspath.h" 43#include "private/svn_editor.h" 44 45#include "ra_svn.h" 46 47/* 48 * Both the client and server in the svn protocol need to drive and 49 * consume editors. For a commit, the client drives and the server 50 * consumes; for an update/switch/status/diff, the server drives and 51 * the client consumes. This file provides a generic framework for 52 * marshalling and unmarshalling editor operations over an svn 53 * connection; both ends are useful for both server and client. 54 */ 55 56typedef struct ra_svn_edit_baton_t { 57 svn_ra_svn_conn_t *conn; 58 svn_ra_svn_edit_callback callback; /* Called on successful completion. */ 59 void *callback_baton; 60 int next_token; 61 svn_boolean_t got_status; 62} ra_svn_edit_baton_t; 63 64/* Works for both directories and files. */ 65typedef struct ra_svn_baton_t { 66 svn_ra_svn_conn_t *conn; 67 apr_pool_t *pool; 68 ra_svn_edit_baton_t *eb; 69 const char *token; 70} ra_svn_baton_t; 71 72typedef struct ra_svn_driver_state_t { 73 const svn_delta_editor_t *editor; 74 void *edit_baton; 75 apr_hash_t *tokens; 76 svn_boolean_t *aborted; 77 svn_boolean_t done; 78 apr_pool_t *pool; 79 apr_pool_t *file_pool; 80 int file_refs; 81 svn_boolean_t for_replay; 82} ra_svn_driver_state_t; 83 84/* Works for both directories and files; however, the pool handling is 85 different for files. To save space during commits (where file 86 batons generally last until the end of the commit), token entries 87 for files are all created in a single reference-counted pool (the 88 file_pool member of the driver state structure), which is cleared 89 at close_file time when the reference count hits zero. So the pool 90 field in this structure is vestigial for files, and we use it for a 91 different purpose instead: at apply-textdelta time, we set it to a 92 subpool of the file pool, which is destroyed in textdelta-end. */ 93typedef struct ra_svn_token_entry_t { 94 svn_string_t *token; 95 void *baton; 96 svn_boolean_t is_file; 97 svn_stream_t *dstream; /* svndiff stream for apply_textdelta */ 98 apr_pool_t *pool; 99} ra_svn_token_entry_t; 100 101/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */ 102 103static const char *make_token(char type, ra_svn_edit_baton_t *eb, 104 apr_pool_t *pool) 105{ 106 return apr_psprintf(pool, "%c%d", type, eb->next_token++); 107} 108 109static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn, 110 apr_pool_t *pool, 111 ra_svn_edit_baton_t *eb, 112 const char *token) 113{ 114 ra_svn_baton_t *b; 115 116 b = apr_palloc(pool, sizeof(*b)); 117 b->conn = conn; 118 b->pool = pool; 119 b->eb = eb; 120 b->token = token; 121 return b; 122} 123 124/* Check for an early error status report from the consumer. If we 125 * get one, abort the edit and return the error. */ 126static svn_error_t * 127check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool) 128{ 129 svn_boolean_t available; 130 SVN_ERR_ASSERT(!eb->got_status); 131 132 /* reset TX counter */ 133 eb->conn->written_since_error_check = 0; 134 135 /* if we weren't asked to always check, wait for at least the next TX */ 136 eb->conn->may_check_for_error = eb->conn->error_check_interval == 0; 137 138 /* any incoming data? */ 139 SVN_ERR(svn_ra_svn__data_available(eb->conn, &available)); 140 if (available) 141 { 142 eb->got_status = TRUE; 143 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool)); 144 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, "")); 145 /* We shouldn't get here if the consumer is doing its job. */ 146 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 147 _("Successful edit status returned too soon")); 148 } 149 return SVN_NO_ERROR; 150} 151 152static svn_error_t * 153check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool) 154{ 155 return eb->conn->may_check_for_error 156 ? check_for_error_internal(eb, pool) 157 : SVN_NO_ERROR; 158} 159 160static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev, 161 apr_pool_t *pool) 162{ 163 ra_svn_edit_baton_t *eb = edit_baton; 164 165 SVN_ERR(check_for_error(eb, pool)); 166 SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev)); 167 return SVN_NO_ERROR; 168} 169 170static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev, 171 apr_pool_t *pool, void **root_baton) 172{ 173 ra_svn_edit_baton_t *eb = edit_baton; 174 const char *token = make_token('d', eb, pool); 175 176 SVN_ERR(check_for_error(eb, pool)); 177 SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token)); 178 *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token); 179 return SVN_NO_ERROR; 180} 181 182static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev, 183 void *parent_baton, apr_pool_t *pool) 184{ 185 ra_svn_baton_t *b = parent_baton; 186 187 SVN_ERR(check_for_error(b->eb, pool)); 188 SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool, 189 path, rev, b->token)); 190 return SVN_NO_ERROR; 191} 192 193static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton, 194 const char *copy_path, 195 svn_revnum_t copy_rev, 196 apr_pool_t *pool, void **child_baton) 197{ 198 ra_svn_baton_t *b = parent_baton; 199 const char *token = make_token('d', b->eb, pool); 200 201 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev)) 202 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev))); 203 SVN_ERR(check_for_error(b->eb, pool)); 204 SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token, 205 token, copy_path, copy_rev)); 206 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); 207 return SVN_NO_ERROR; 208} 209 210static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton, 211 svn_revnum_t rev, apr_pool_t *pool, 212 void **child_baton) 213{ 214 ra_svn_baton_t *b = parent_baton; 215 const char *token = make_token('d', b->eb, pool); 216 217 SVN_ERR(check_for_error(b->eb, pool)); 218 SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token, 219 token, rev)); 220 *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); 221 return SVN_NO_ERROR; 222} 223 224static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name, 225 const svn_string_t *value, 226 apr_pool_t *pool) 227{ 228 ra_svn_baton_t *b = dir_baton; 229 230 SVN_ERR(check_for_error(b->eb, pool)); 231 SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token, 232 name, value)); 233 return SVN_NO_ERROR; 234} 235 236static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool) 237{ 238 ra_svn_baton_t *b = dir_baton; 239 240 SVN_ERR(check_for_error(b->eb, pool)); 241 SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token)); 242 return SVN_NO_ERROR; 243} 244 245static svn_error_t *ra_svn_absent_dir(const char *path, 246 void *parent_baton, apr_pool_t *pool) 247{ 248 ra_svn_baton_t *b = parent_baton; 249 250 /* Avoid sending an unknown command if the other end doesn't support 251 absent-dir. */ 252 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES)) 253 return SVN_NO_ERROR; 254 255 SVN_ERR(check_for_error(b->eb, pool)); 256 SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token)); 257 return SVN_NO_ERROR; 258} 259 260static svn_error_t *ra_svn_add_file(const char *path, 261 void *parent_baton, 262 const char *copy_path, 263 svn_revnum_t copy_rev, 264 apr_pool_t *pool, 265 void **file_baton) 266{ 267 ra_svn_baton_t *b = parent_baton; 268 const char *token = make_token('c', b->eb, pool); 269 270 SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev)) 271 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev))); 272 SVN_ERR(check_for_error(b->eb, pool)); 273 SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool, path, b->token, 274 token, copy_path, copy_rev)); 275 *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token); 276 return SVN_NO_ERROR; 277} 278 279static svn_error_t *ra_svn_open_file(const char *path, 280 void *parent_baton, 281 svn_revnum_t rev, 282 apr_pool_t *pool, 283 void **file_baton) 284{ 285 ra_svn_baton_t *b = parent_baton; 286 const char *token = make_token('c', b->eb, pool); 287 288 SVN_ERR(check_for_error(b->eb, b->pool)); 289 SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token, 290 token, 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_svndiff_handler(void *baton, const char *data, 296 apr_size_t *len) 297{ 298 ra_svn_baton_t *b = baton; 299 svn_string_t str; 300 301 SVN_ERR(check_for_error(b->eb, b->pool)); 302 str.data = data; 303 str.len = *len; 304 return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool, 305 b->token, &str); 306} 307 308static svn_error_t *ra_svn_svndiff_close_handler(void *baton) 309{ 310 ra_svn_baton_t *b = baton; 311 312 SVN_ERR(check_for_error(b->eb, b->pool)); 313 SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token)); 314 return SVN_NO_ERROR; 315} 316 317static svn_error_t *ra_svn_apply_textdelta(void *file_baton, 318 const char *base_checksum, 319 apr_pool_t *pool, 320 svn_txdelta_window_handler_t *wh, 321 void **wh_baton) 322{ 323 ra_svn_baton_t *b = file_baton; 324 svn_stream_t *diff_stream; 325 326 /* Tell the other side we're starting a text delta. */ 327 SVN_ERR(check_for_error(b->eb, pool)); 328 SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token, 329 base_checksum)); 330 331 /* Transform the window stream to an svndiff stream. Reuse the 332 * file baton for the stream handler, since it has all the 333 * needed information. */ 334 diff_stream = svn_stream_create(b, pool); 335 svn_stream_set_write(diff_stream, ra_svn_svndiff_handler); 336 svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler); 337 338 /* If the connection does not support SVNDIFF1 or if we don't want to use 339 * compression, use the non-compressing "version 0" implementation */ 340 if ( svn_ra_svn_compression_level(b->conn) > 0 341 && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1)) 342 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1, 343 b->conn->compression_level, pool); 344 else 345 svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0, 346 b->conn->compression_level, pool); 347 return SVN_NO_ERROR; 348} 349 350static svn_error_t *ra_svn_change_file_prop(void *file_baton, 351 const char *name, 352 const svn_string_t *value, 353 apr_pool_t *pool) 354{ 355 ra_svn_baton_t *b = file_baton; 356 357 SVN_ERR(check_for_error(b->eb, pool)); 358 SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool, 359 b->token, name, value)); 360 return SVN_NO_ERROR; 361} 362 363static svn_error_t *ra_svn_close_file(void *file_baton, 364 const char *text_checksum, 365 apr_pool_t *pool) 366{ 367 ra_svn_baton_t *b = file_baton; 368 369 SVN_ERR(check_for_error(b->eb, pool)); 370 SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool, 371 b->token, text_checksum)); 372 return SVN_NO_ERROR; 373} 374 375static svn_error_t *ra_svn_absent_file(const char *path, 376 void *parent_baton, apr_pool_t *pool) 377{ 378 ra_svn_baton_t *b = parent_baton; 379 380 /* Avoid sending an unknown command if the other end doesn't support 381 absent-file. */ 382 if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES)) 383 return SVN_NO_ERROR; 384 385 SVN_ERR(check_for_error(b->eb, pool)); 386 SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token)); 387 return SVN_NO_ERROR; 388} 389 390static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool) 391{ 392 ra_svn_edit_baton_t *eb = edit_baton; 393 svn_error_t *err; 394 395 SVN_ERR_ASSERT(!eb->got_status); 396 eb->got_status = TRUE; 397 SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool)); 398 err = svn_error_trace(svn_ra_svn__read_cmd_response(eb->conn, pool, "")); 399 if (err) 400 { 401 return svn_error_compose_create( 402 err, 403 svn_error_trace( 404 svn_ra_svn__write_cmd_abort_edit(eb->conn, pool))); 405 } 406 if (eb->callback) 407 SVN_ERR(eb->callback(eb->callback_baton)); 408 return SVN_NO_ERROR; 409} 410 411static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool) 412{ 413 ra_svn_edit_baton_t *eb = edit_baton; 414 415 if (eb->got_status) 416 return SVN_NO_ERROR; 417 SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool)); 418 SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, "")); 419 return SVN_NO_ERROR; 420} 421 422void svn_ra_svn_get_editor(const svn_delta_editor_t **editor, 423 void **edit_baton, svn_ra_svn_conn_t *conn, 424 apr_pool_t *pool, 425 svn_ra_svn_edit_callback callback, 426 void *callback_baton) 427{ 428 svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool); 429 ra_svn_edit_baton_t *eb; 430 431 eb = apr_palloc(pool, sizeof(*eb)); 432 eb->conn = conn; 433 eb->callback = callback; 434 eb->callback_baton = callback_baton; 435 eb->next_token = 0; 436 eb->got_status = FALSE; 437 438 ra_svn_editor->set_target_revision = ra_svn_target_rev; 439 ra_svn_editor->open_root = ra_svn_open_root; 440 ra_svn_editor->delete_entry = ra_svn_delete_entry; 441 ra_svn_editor->add_directory = ra_svn_add_dir; 442 ra_svn_editor->open_directory = ra_svn_open_dir; 443 ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop; 444 ra_svn_editor->close_directory = ra_svn_close_dir; 445 ra_svn_editor->absent_directory = ra_svn_absent_dir; 446 ra_svn_editor->add_file = ra_svn_add_file; 447 ra_svn_editor->open_file = ra_svn_open_file; 448 ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta; 449 ra_svn_editor->change_file_prop = ra_svn_change_file_prop; 450 ra_svn_editor->close_file = ra_svn_close_file; 451 ra_svn_editor->absent_file = ra_svn_absent_file; 452 ra_svn_editor->close_edit = ra_svn_close_edit; 453 ra_svn_editor->abort_edit = ra_svn_abort_edit; 454 455 *editor = ra_svn_editor; 456 *edit_baton = eb; 457 458 svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor, 459 *edit_baton, NULL, NULL, 460 conn->shim_callbacks, 461 pool, pool)); 462} 463 464/* --- DRIVING AN EDITOR --- */ 465 466/* Store a token entry. The token string will be copied into pool. */ 467static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds, 468 void *baton, 469 svn_string_t *token, 470 svn_boolean_t is_file, 471 apr_pool_t *pool) 472{ 473 ra_svn_token_entry_t *entry; 474 475 entry = apr_palloc(pool, sizeof(*entry)); 476 entry->token = svn_string_dup(token, pool); 477 entry->baton = baton; 478 entry->is_file = is_file; 479 entry->dstream = NULL; 480 entry->pool = pool; 481 482 apr_hash_set(ds->tokens, entry->token->data, entry->token->len, entry); 483 484 return entry; 485} 486 487static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, 488 svn_string_t *token, 489 svn_boolean_t is_file, 490 ra_svn_token_entry_t **entry) 491{ 492 *entry = apr_hash_get(ds->tokens, token->data, token->len); 493 if (!*entry || (*entry)->is_file != is_file) 494 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 495 _("Invalid file or dir token during edit")); 496 return SVN_NO_ERROR; 497} 498 499static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn, 500 apr_pool_t *pool, 501 const apr_array_header_t *params, 502 ra_svn_driver_state_t *ds) 503{ 504 svn_revnum_t rev; 505 506 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev)); 507 SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool)); 508 return SVN_NO_ERROR; 509} 510 511static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn, 512 apr_pool_t *pool, 513 const apr_array_header_t *params, 514 ra_svn_driver_state_t *ds) 515{ 516 svn_revnum_t rev; 517 apr_pool_t *subpool; 518 svn_string_t *token; 519 void *root_baton; 520 521 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)s", &rev, &token)); 522 subpool = svn_pool_create(ds->pool); 523 SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool, 524 &root_baton)); 525 store_token(ds, root_baton, token, FALSE, subpool); 526 return SVN_NO_ERROR; 527} 528 529static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn, 530 apr_pool_t *pool, 531 const apr_array_header_t *params, 532 ra_svn_driver_state_t *ds) 533{ 534 const char *path; 535 svn_string_t *token; 536 svn_revnum_t rev; 537 ra_svn_token_entry_t *entry; 538 539 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)s", 540 &path, &rev, &token)); 541 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 542 path = svn_relpath_canonicalize(path, pool); 543 SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool)); 544 return SVN_NO_ERROR; 545} 546 547static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn, 548 apr_pool_t *pool, 549 const apr_array_header_t *params, 550 ra_svn_driver_state_t *ds) 551{ 552 const char *path, *copy_path; 553 svn_string_t *token, *child_token; 554 svn_revnum_t copy_rev; 555 ra_svn_token_entry_t *entry; 556 apr_pool_t *subpool; 557 void *child_baton; 558 559 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token, 560 &child_token, ©_path, ©_rev)); 561 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 562 subpool = svn_pool_create(entry->pool); 563 path = svn_relpath_canonicalize(path, pool); 564 565 /* Some operations pass COPY_PATH as a full URL (commits, etc.). 566 Others (replay, e.g.) deliver an fspath. That's ... annoying. */ 567 if (copy_path) 568 { 569 if (svn_path_is_url(copy_path)) 570 copy_path = svn_uri_canonicalize(copy_path, pool); 571 else 572 copy_path = svn_fspath__canonicalize(copy_path, pool); 573 } 574 575 SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path, 576 copy_rev, subpool, &child_baton)); 577 store_token(ds, child_baton, child_token, FALSE, subpool); 578 return SVN_NO_ERROR; 579} 580 581static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn, 582 apr_pool_t *pool, 583 const apr_array_header_t *params, 584 ra_svn_driver_state_t *ds) 585{ 586 const char *path; 587 svn_string_t *token, *child_token; 588 svn_revnum_t rev; 589 ra_svn_token_entry_t *entry; 590 apr_pool_t *subpool; 591 void *child_baton; 592 593 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token, 594 &child_token, &rev)); 595 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 596 subpool = svn_pool_create(entry->pool); 597 path = svn_relpath_canonicalize(path, pool); 598 SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool, 599 &child_baton)); 600 store_token(ds, child_baton, child_token, FALSE, subpool); 601 return SVN_NO_ERROR; 602} 603 604static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn, 605 apr_pool_t *pool, 606 const apr_array_header_t *params, 607 ra_svn_driver_state_t *ds) 608{ 609 svn_string_t *token; 610 const char *name; 611 svn_string_t *value; 612 ra_svn_token_entry_t *entry; 613 614 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name, 615 &value)); 616 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 617 SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value, 618 entry->pool)); 619 return SVN_NO_ERROR; 620} 621 622static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn, 623 apr_pool_t *pool, 624 const apr_array_header_t *params, 625 ra_svn_driver_state_t *ds) 626{ 627 svn_string_t *token; 628 ra_svn_token_entry_t *entry; 629 630 /* Parse and look up the directory token. */ 631 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token)); 632 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 633 634 /* Close the directory and destroy the baton. */ 635 SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool)); 636 apr_hash_set(ds->tokens, token->data, token->len, NULL); 637 svn_pool_destroy(entry->pool); 638 return SVN_NO_ERROR; 639} 640 641static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn, 642 apr_pool_t *pool, 643 const apr_array_header_t *params, 644 ra_svn_driver_state_t *ds) 645{ 646 const char *path; 647 svn_string_t *token; 648 ra_svn_token_entry_t *entry; 649 650 /* Parse parameters and look up the directory token. */ 651 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token)); 652 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 653 654 /* Call the editor. */ 655 SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool)); 656 return SVN_NO_ERROR; 657} 658 659static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn, 660 apr_pool_t *pool, 661 const apr_array_header_t *params, 662 ra_svn_driver_state_t *ds) 663{ 664 const char *path, *copy_path; 665 svn_string_t *token, *file_token; 666 svn_revnum_t copy_rev; 667 ra_svn_token_entry_t *entry, *file_entry; 668 669 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token, 670 &file_token, ©_path, ©_rev)); 671 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 672 ds->file_refs++; 673 674 /* The PATH should be canonical .. but never trust incoming data. */ 675 if (!svn_relpath_is_canonical(path)) 676 path = svn_relpath_canonicalize(path, pool); 677 678 /* Some operations pass COPY_PATH as a full URL (commits, etc.). 679 Others (replay, e.g.) deliver an fspath. That's ... annoying. */ 680 if (copy_path) 681 { 682 if (svn_path_is_url(copy_path)) 683 copy_path = svn_uri_canonicalize(copy_path, pool); 684 else 685 copy_path = svn_fspath__canonicalize(copy_path, pool); 686 } 687 688 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool); 689 SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev, 690 ds->file_pool, &file_entry->baton)); 691 return SVN_NO_ERROR; 692} 693 694static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn, 695 apr_pool_t *pool, 696 const apr_array_header_t *params, 697 ra_svn_driver_state_t *ds) 698{ 699 const char *path; 700 svn_string_t *token, *file_token; 701 svn_revnum_t rev; 702 ra_svn_token_entry_t *entry, *file_entry; 703 704 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token, 705 &file_token, &rev)); 706 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 707 ds->file_refs++; 708 709 /* The PATH should be canonical .. but never trust incoming data. */ 710 if (!svn_relpath_is_canonical(path)) 711 path = svn_relpath_canonicalize(path, pool); 712 713 file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool); 714 SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool, 715 &file_entry->baton)); 716 return SVN_NO_ERROR; 717} 718 719static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn, 720 apr_pool_t *pool, 721 const apr_array_header_t *params, 722 ra_svn_driver_state_t *ds) 723{ 724 svn_string_t *token; 725 ra_svn_token_entry_t *entry; 726 svn_txdelta_window_handler_t wh; 727 void *wh_baton; 728 char *base_checksum; 729 730 /* Parse arguments and look up the token. */ 731 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)", 732 &token, &base_checksum)); 733 SVN_ERR(lookup_token(ds, token, TRUE, &entry)); 734 if (entry->dstream) 735 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 736 _("Apply-textdelta already active")); 737 entry->pool = svn_pool_create(ds->file_pool); 738 SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum, 739 entry->pool, &wh, &wh_baton)); 740 entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool); 741 return SVN_NO_ERROR; 742} 743 744static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn, 745 apr_pool_t *pool, 746 const apr_array_header_t *params, 747 ra_svn_driver_state_t *ds) 748{ 749 svn_string_t *token; 750 ra_svn_token_entry_t *entry; 751 svn_string_t *str; 752 753 /* Parse arguments and look up the token. */ 754 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ss", &token, &str)); 755 SVN_ERR(lookup_token(ds, token, TRUE, &entry)); 756 if (!entry->dstream) 757 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 758 _("Apply-textdelta not active")); 759 SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len)); 760 return SVN_NO_ERROR; 761} 762 763static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn, 764 apr_pool_t *pool, 765 const apr_array_header_t *params, 766 ra_svn_driver_state_t *ds) 767{ 768 svn_string_t *token; 769 ra_svn_token_entry_t *entry; 770 771 /* Parse arguments and look up the token. */ 772 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token)); 773 SVN_ERR(lookup_token(ds, token, TRUE, &entry)); 774 if (!entry->dstream) 775 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 776 _("Apply-textdelta not active")); 777 SVN_CMD_ERR(svn_stream_close(entry->dstream)); 778 entry->dstream = NULL; 779 svn_pool_destroy(entry->pool); 780 return SVN_NO_ERROR; 781} 782 783static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn, 784 apr_pool_t *pool, 785 const apr_array_header_t *params, 786 ra_svn_driver_state_t *ds) 787{ 788 const char *name; 789 svn_string_t *token, *value; 790 ra_svn_token_entry_t *entry; 791 792 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name, 793 &value)); 794 SVN_ERR(lookup_token(ds, token, TRUE, &entry)); 795 SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool)); 796 return SVN_NO_ERROR; 797} 798 799static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn, 800 apr_pool_t *pool, 801 const apr_array_header_t *params, 802 ra_svn_driver_state_t *ds) 803{ 804 svn_string_t *token; 805 ra_svn_token_entry_t *entry; 806 const char *text_checksum; 807 808 /* Parse arguments and look up the file token. */ 809 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)", 810 &token, &text_checksum)); 811 SVN_ERR(lookup_token(ds, token, TRUE, &entry)); 812 813 /* Close the file and destroy the baton. */ 814 SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool)); 815 apr_hash_set(ds->tokens, token->data, token->len, NULL); 816 if (--ds->file_refs == 0) 817 svn_pool_clear(ds->file_pool); 818 return SVN_NO_ERROR; 819} 820 821static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn, 822 apr_pool_t *pool, 823 const apr_array_header_t *params, 824 ra_svn_driver_state_t *ds) 825{ 826 const char *path; 827 svn_string_t *token; 828 ra_svn_token_entry_t *entry; 829 830 /* Parse parameters and look up the parent directory token. */ 831 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token)); 832 SVN_ERR(lookup_token(ds, token, FALSE, &entry)); 833 834 /* Call the editor. */ 835 SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool)); 836 return SVN_NO_ERROR; 837} 838 839static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn, 840 apr_pool_t *pool, 841 const apr_array_header_t *params, 842 ra_svn_driver_state_t *ds) 843{ 844 SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool)); 845 ds->done = TRUE; 846 if (ds->aborted) 847 *ds->aborted = FALSE; 848 return svn_ra_svn__write_cmd_response(conn, pool, ""); 849} 850 851static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn, 852 apr_pool_t *pool, 853 const apr_array_header_t *params, 854 ra_svn_driver_state_t *ds) 855{ 856 ds->done = TRUE; 857 if (ds->aborted) 858 *ds->aborted = TRUE; 859 SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool)); 860 return svn_ra_svn__write_cmd_response(conn, pool, ""); 861} 862 863static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn, 864 apr_pool_t *pool, 865 const apr_array_header_t *params, 866 ra_svn_driver_state_t *ds) 867{ 868 if (!ds->for_replay) 869 return svn_error_createf 870 (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL, 871 _("Command 'finish-replay' invalid outside of replays")); 872 ds->done = TRUE; 873 if (ds->aborted) 874 *ds->aborted = FALSE; 875 return SVN_NO_ERROR; 876} 877 878static const struct { 879 const char *cmd; 880 svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 881 const apr_array_header_t *params, 882 ra_svn_driver_state_t *ds); 883} ra_svn_edit_cmds[] = { 884 { "change-file-prop", ra_svn_handle_change_file_prop }, 885 { "open-file", ra_svn_handle_open_file }, 886 { "apply-textdelta", ra_svn_handle_apply_textdelta }, 887 { "textdelta-chunk", ra_svn_handle_textdelta_chunk }, 888 { "close-file", ra_svn_handle_close_file }, 889 { "add-dir", ra_svn_handle_add_dir }, 890 { "open-dir", ra_svn_handle_open_dir }, 891 { "change-dir-prop", ra_svn_handle_change_dir_prop }, 892 { "delete-entry", ra_svn_handle_delete_entry }, 893 { "close-dir", ra_svn_handle_close_dir }, 894 { "absent-dir", ra_svn_handle_absent_dir }, 895 { "add-file", ra_svn_handle_add_file }, 896 { "textdelta-end", ra_svn_handle_textdelta_end }, 897 { "absent-file", ra_svn_handle_absent_file }, 898 { "abort-edit", ra_svn_handle_abort_edit }, 899 { "finish-replay", ra_svn_handle_finish_replay }, 900 { "target-rev", ra_svn_handle_target_rev }, 901 { "open-root", ra_svn_handle_open_root }, 902 { "close-edit", ra_svn_handle_close_edit }, 903 { NULL } 904}; 905 906static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 907 void *baton) 908{ 909 ra_svn_driver_state_t *ds = baton; 910 const char *cmd; 911 apr_array_header_t *params; 912 913 /* We blocked trying to send an error. Read and discard an editing 914 * command in order to avoid deadlock. */ 915 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, ¶ms)); 916 if (strcmp(cmd, "abort-edit") == 0) 917 { 918 ds->done = TRUE; 919 svn_ra_svn__set_block_handler(conn, NULL, NULL); 920 } 921 return SVN_NO_ERROR; 922} 923 924svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn, 925 apr_pool_t *pool, 926 const svn_delta_editor_t *editor, 927 void *edit_baton, 928 svn_boolean_t *aborted, 929 svn_boolean_t for_replay) 930{ 931 ra_svn_driver_state_t state; 932 apr_pool_t *subpool = svn_pool_create(pool); 933 const char *cmd; 934 int i; 935 svn_error_t *err, *write_err; 936 apr_array_header_t *params; 937 938 state.editor = editor; 939 state.edit_baton = edit_baton; 940 state.tokens = apr_hash_make(pool); 941 state.aborted = aborted; 942 state.done = FALSE; 943 state.pool = pool; 944 state.file_pool = svn_pool_create(pool); 945 state.file_refs = 0; 946 state.for_replay = for_replay; 947 948 while (!state.done) 949 { 950 svn_pool_clear(subpool); 951 if (editor) 952 { 953 SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms)); 954 for (i = 0; ra_svn_edit_cmds[i].cmd; i++) 955 if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0) 956 break; 957 958 if (ra_svn_edit_cmds[i].cmd) 959 err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state); 960 else if (strcmp(cmd, "failure") == 0) 961 { 962 /* While not really an editor command this can occur when 963 reporter->finish_report() fails before the first editor 964 command */ 965 if (aborted) 966 *aborted = TRUE; 967 err = svn_ra_svn__handle_failure_status(params, pool); 968 return svn_error_compose_create( 969 err, 970 editor->abort_edit(edit_baton, subpool)); 971 } 972 else 973 { 974 err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL, 975 _("Unknown editor command '%s'"), cmd); 976 err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL); 977 } 978 } 979 else 980 { 981 const char* command = NULL; 982 SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command)); 983 if (strcmp(command, "close-edit") == 0) 984 { 985 state.done = TRUE; 986 if (aborted) 987 *aborted = FALSE; 988 err = svn_ra_svn__write_cmd_response(conn, pool, ""); 989 } 990 else 991 err = NULL; 992 } 993 994 if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR) 995 { 996 if (aborted) 997 *aborted = TRUE; 998 if (!state.done) 999 { 1000 /* Abort the edit and use non-blocking I/O to write the error. */ 1001 if (editor) 1002 { 1003 err = svn_error_compose_create( 1004 err, 1005 svn_error_trace(editor->abort_edit(edit_baton, 1006 subpool))); 1007 } 1008 svn_ra_svn__set_block_handler(conn, blocked_write, &state); 1009 } 1010 write_err = svn_ra_svn__write_cmd_failure( 1011 conn, subpool, 1012 svn_ra_svn__locate_real_error_child(err)); 1013 if (!write_err) 1014 write_err = svn_ra_svn__flush(conn, subpool); 1015 svn_ra_svn__set_block_handler(conn, NULL, NULL); 1016 svn_error_clear(err); /* We just sent this error */ 1017 SVN_ERR(write_err); 1018 break; 1019 } 1020 SVN_ERR(err); 1021 } 1022 1023 /* Read and discard editing commands until the edit is complete. 1024 Hopefully, the other side will call another editor command, run 1025 check_for_error, notice the error, write "abort-edit" at us, and 1026 throw the error up a few levels on its side (possibly even 1027 tossing it right back at us, which is why we can return 1028 SVN_NO_ERROR below). 1029 1030 However, if the other side is way ahead of us, it might 1031 completely finish the edit (or sequence of edit/revprops, for 1032 "replay-range") before we send over our "failure". So we should 1033 also stop if we see "success". (Then the other side will try to 1034 interpret our "failure" as a command, which will itself fail... 1035 The net effect is that whatever error we wrote to the other side 1036 will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.) 1037 */ 1038 while (!state.done) 1039 { 1040 svn_pool_clear(subpool); 1041 err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, ¶ms); 1042 if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED) 1043 { 1044 /* Other side disconnected; that's no error. */ 1045 svn_error_clear(err); 1046 svn_pool_destroy(subpool); 1047 return SVN_NO_ERROR; 1048 } 1049 svn_error_clear(err); 1050 if (strcmp(cmd, "abort-edit") == 0 1051 || strcmp(cmd, "success") == 0) 1052 state.done = TRUE; 1053 } 1054 1055 svn_pool_destroy(subpool); 1056 return SVN_NO_ERROR; 1057} 1058 1059svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool, 1060 const svn_delta_editor_t *editor, 1061 void *edit_baton, 1062 svn_boolean_t *aborted) 1063{ 1064 return svn_ra_svn_drive_editor2(conn, 1065 pool, 1066 editor, 1067 edit_baton, 1068 aborted, 1069 FALSE); 1070} 1071