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