]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_svn/editorp.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_svn / editorp.c
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 \f
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
56 typedef 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. */
65 typedef 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
72 typedef 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. */
93 typedef 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
103 static 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
109 static 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. */
126 static svn_error_t *
127 check_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
152 static svn_error_t *
153 check_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
160 static 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
170 static 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
182 static 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
193 static 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
210 static 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
224 static 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
236 static 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
245 static 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
260 static 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
279 static 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
295 static 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
308 static 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
317 static 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
350 static 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
363 static 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
375 static 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
390 static 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
411 static 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
422 void 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. */
467 static 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
487 static 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
499 static 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
511 static 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
529 static 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
547 static 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, &copy_path, &copy_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
581 static 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
604 static 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
622 static 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
641 static 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
659 static 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, &copy_path, &copy_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
694 static 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
719 static 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
744 static 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
763 static 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
783 static 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
799 static 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
821 static 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
839 static 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
851 static 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
863 static 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
878 static 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
906 static 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, &params));
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
924 svn_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, &params));
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, &params);
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
1059 svn_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 }