]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_svn/editorp.c
MFV r348548: 9617 too-frequent TXG sync causes excessive write inflation
[FreeBSD/FreeBSD.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_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
59 typedef struct ra_svn_edit_baton_t {
60   svn_ra_svn_conn_t *conn;
61   svn_ra_svn_edit_callback callback;    /* Called on successful completion. */
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. */
68 typedef 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. */
76 typedef struct ra_svn_token_entry_t ra_svn_token_entry_t;
77
78 typedef 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. */
102 struct 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
112 static svn_string_t *
113 make_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
125 static 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. */
142 static svn_error_t *
143 check_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
168 static svn_error_t *
169 check_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
176 static 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
186 static 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
198 static 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
209 static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
210                                    const char *copy_path,
211                                    svn_revnum_t copy_rev,
212                                    apr_pool_t *pool, void **child_baton)
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
226 static 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
240 static 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
252 static 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
261 static 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
276 static 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
295 static 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
311 static 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
324 static 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
333 static 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
360 static 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
373 static 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
385 static 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
400 static 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
421 static 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
432 void 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. */
477 static 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
498 static 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. */
520 static 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
531 static svn_error_t *
532 ra_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
544 static svn_error_t *
545 ra_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
563 static svn_error_t *
564 ra_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
582 static svn_error_t *
583 ra_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, &copy_path, &copy_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
617 static svn_error_t *
618 ra_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
641 static svn_error_t *
642 ra_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
660 static svn_error_t *
661 ra_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
680 static svn_error_t *
681 ra_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
699 static svn_error_t *
700 ra_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, &copy_path, &copy_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
735 static svn_error_t *
736 ra_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
761 static svn_error_t *
762 ra_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
787 static svn_error_t *
788 ra_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
807 static svn_error_t *
808 ra_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
828 static svn_error_t *
829 ra_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
845 static svn_error_t *
846 ra_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
868 static svn_error_t *
869 ra_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
887 static svn_error_t *
888 ra_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
908 static svn_error_t *
909 ra_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
921 static svn_error_t *
922 ra_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. */
938 typedef 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
943 static 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. */
973 typedef 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
987 static cmd_t cmd_hash[CMD_HASH_SIZE] = { { { NULL } } };
988
989 /* Init flag that controls CMD_HASH's atomic initialization. */
990 static 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  */
998 static apr_size_t
999 cmd_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. */
1010 static svn_error_t *
1011 init_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 */
1031 static cmd_handler_t
1032 cmd_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
1055 static 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, &params));
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
1073 svn_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, &params));
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, &params);
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
1214 svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1215                                      const svn_delta_editor_t *editor,
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 }