editorp.c revision 299742
1/*
2 * editorp.c :  Driving and consuming an editor across an svn connection
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_want.h>
28#include <apr_general.h>
29#include <apr_strings.h>
30
31#include "svn_hash.h"
32#include "svn_types.h"
33#include "svn_string.h"
34#include "svn_error.h"
35#include "svn_delta.h"
36#include "svn_dirent_uri.h"
37#include "svn_ra_svn.h"
38#include "svn_path.h"
39#include "svn_pools.h"
40#include "svn_private_config.h"
41
42#include "private/svn_fspath.h"
43#include "private/svn_editor.h"
44
45#include "ra_svn.h"
46
47/*
48 * Both the client and server in the svn protocol need to drive and
49 * consume editors.  For a commit, the client drives and the server
50 * consumes; for an update/switch/status/diff, the server drives and
51 * the client consumes.  This file provides a generic framework for
52 * marshalling and unmarshalling editor operations over an svn
53 * connection; both ends are useful for both server and client.
54 */
55
56typedef struct ra_svn_edit_baton_t {
57  svn_ra_svn_conn_t *conn;
58  svn_ra_svn_edit_callback callback;    /* Called on successful completion. */
59  void *callback_baton;
60  int next_token;
61  svn_boolean_t got_status;
62} ra_svn_edit_baton_t;
63
64/* Works for both directories and files. */
65typedef struct ra_svn_baton_t {
66  svn_ra_svn_conn_t *conn;
67  apr_pool_t *pool;
68  ra_svn_edit_baton_t *eb;
69  const char *token;
70} ra_svn_baton_t;
71
72typedef struct ra_svn_driver_state_t {
73  const svn_delta_editor_t *editor;
74  void *edit_baton;
75  apr_hash_t *tokens;
76  svn_boolean_t *aborted;
77  svn_boolean_t done;
78  apr_pool_t *pool;
79  apr_pool_t *file_pool;
80  int file_refs;
81  svn_boolean_t for_replay;
82} ra_svn_driver_state_t;
83
84/* Works for both directories and files; however, the pool handling is
85   different for files.  To save space during commits (where file
86   batons generally last until the end of the commit), token entries
87   for files are all created in a single reference-counted pool (the
88   file_pool member of the driver state structure), which is cleared
89   at close_file time when the reference count hits zero.  So the pool
90   field in this structure is vestigial for files, and we use it for a
91   different purpose instead: at apply-textdelta time, we set it to a
92   subpool of the file pool, which is destroyed in textdelta-end. */
93typedef struct ra_svn_token_entry_t {
94  svn_string_t *token;
95  void *baton;
96  svn_boolean_t is_file;
97  svn_stream_t *dstream;  /* svndiff stream for apply_textdelta */
98  apr_pool_t *pool;
99} ra_svn_token_entry_t;
100
101/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
102
103static const char *make_token(char type, ra_svn_edit_baton_t *eb,
104                              apr_pool_t *pool)
105{
106  return apr_psprintf(pool, "%c%d", type, eb->next_token++);
107}
108
109static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
110                                         apr_pool_t *pool,
111                                         ra_svn_edit_baton_t *eb,
112                                         const char *token)
113{
114  ra_svn_baton_t *b;
115
116  b = apr_palloc(pool, sizeof(*b));
117  b->conn = conn;
118  b->pool = pool;
119  b->eb = eb;
120  b->token = token;
121  return b;
122}
123
124/* Check for an early error status report from the consumer.  If we
125 * get one, abort the edit and return the error. */
126static svn_error_t *
127check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
128{
129  svn_boolean_t available;
130  SVN_ERR_ASSERT(!eb->got_status);
131
132  /* reset TX counter */
133  eb->conn->written_since_error_check = 0;
134
135  /* if we weren't asked to always check, wait for at least the next TX */
136  eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
137
138  /* any incoming data? */
139  SVN_ERR(svn_ra_svn__data_available(eb->conn, &available));
140  if (available)
141    {
142      eb->got_status = TRUE;
143      SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
144      SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
145      /* We shouldn't get here if the consumer is doing its job. */
146      return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
147                              _("Successful edit status returned too soon"));
148    }
149  return SVN_NO_ERROR;
150}
151
152static svn_error_t *
153check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
154{
155  return eb->conn->may_check_for_error
156    ? check_for_error_internal(eb, pool)
157    : SVN_NO_ERROR;
158}
159
160static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
161                                      apr_pool_t *pool)
162{
163  ra_svn_edit_baton_t *eb = edit_baton;
164
165  SVN_ERR(check_for_error(eb, pool));
166  SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
167  return SVN_NO_ERROR;
168}
169
170static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
171                                     apr_pool_t *pool, void **root_baton)
172{
173  ra_svn_edit_baton_t *eb = edit_baton;
174  const char *token = make_token('d', eb, pool);
175
176  SVN_ERR(check_for_error(eb, pool));
177  SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
178  *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
179  return SVN_NO_ERROR;
180}
181
182static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
183                                        void *parent_baton, apr_pool_t *pool)
184{
185  ra_svn_baton_t *b = parent_baton;
186
187  SVN_ERR(check_for_error(b->eb, pool));
188  SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
189                                             path, rev, b->token));
190  return SVN_NO_ERROR;
191}
192
193static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
194                                   const char *copy_path,
195                                   svn_revnum_t copy_rev,
196                                   apr_pool_t *pool, void **child_baton)
197{
198  ra_svn_baton_t *b = parent_baton;
199  const char *token = make_token('d', b->eb, pool);
200
201  SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
202                 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
203  SVN_ERR(check_for_error(b->eb, pool));
204  SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
205                                        token, copy_path, copy_rev));
206  *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
207  return SVN_NO_ERROR;
208}
209
210static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
211                                    svn_revnum_t rev, apr_pool_t *pool,
212                                    void **child_baton)
213{
214  ra_svn_baton_t *b = parent_baton;
215  const char *token = make_token('d', b->eb, pool);
216
217  SVN_ERR(check_for_error(b->eb, pool));
218  SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
219                                         token, rev));
220  *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
221  return SVN_NO_ERROR;
222}
223
224static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
225                                           const svn_string_t *value,
226                                           apr_pool_t *pool)
227{
228  ra_svn_baton_t *b = dir_baton;
229
230  SVN_ERR(check_for_error(b->eb, pool));
231  SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
232                                                name, value));
233  return SVN_NO_ERROR;
234}
235
236static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
237{
238  ra_svn_baton_t *b = dir_baton;
239
240  SVN_ERR(check_for_error(b->eb, pool));
241  SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
242  return SVN_NO_ERROR;
243}
244
245static svn_error_t *ra_svn_absent_dir(const char *path,
246                                      void *parent_baton, apr_pool_t *pool)
247{
248  ra_svn_baton_t *b = parent_baton;
249
250  /* Avoid sending an unknown command if the other end doesn't support
251     absent-dir. */
252  if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
253    return SVN_NO_ERROR;
254
255  SVN_ERR(check_for_error(b->eb, pool));
256  SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
257  return SVN_NO_ERROR;
258}
259
260static svn_error_t *ra_svn_add_file(const char *path,
261                                    void *parent_baton,
262                                    const char *copy_path,
263                                    svn_revnum_t copy_rev,
264                                    apr_pool_t *pool,
265                                    void **file_baton)
266{
267  ra_svn_baton_t *b = parent_baton;
268  const char *token = make_token('c', b->eb, pool);
269
270  SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
271                 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
272  SVN_ERR(check_for_error(b->eb, pool));
273  SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool,  path, b->token,
274                                         token, copy_path, copy_rev));
275  *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
276  return SVN_NO_ERROR;
277}
278
279static svn_error_t *ra_svn_open_file(const char *path,
280                                     void *parent_baton,
281                                     svn_revnum_t rev,
282                                     apr_pool_t *pool,
283                                     void **file_baton)
284{
285  ra_svn_baton_t *b = parent_baton;
286  const char *token = make_token('c', b->eb, pool);
287
288  SVN_ERR(check_for_error(b->eb, b->pool));
289  SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
290                                          token, rev));
291  *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
292  return SVN_NO_ERROR;
293}
294
295static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
296                                           apr_size_t *len)
297{
298  ra_svn_baton_t *b = baton;
299  svn_string_t str;
300
301  SVN_ERR(check_for_error(b->eb, b->pool));
302  str.data = data;
303  str.len = *len;
304  return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
305                                               b->token, &str);
306}
307
308static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
309{
310  ra_svn_baton_t *b = baton;
311
312  SVN_ERR(check_for_error(b->eb, b->pool));
313  SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
314  return SVN_NO_ERROR;
315}
316
317static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
318                                           const char *base_checksum,
319                                           apr_pool_t *pool,
320                                           svn_txdelta_window_handler_t *wh,
321                                           void **wh_baton)
322{
323  ra_svn_baton_t *b = file_baton;
324  svn_stream_t *diff_stream;
325
326  /* Tell the other side we're starting a text delta. */
327  SVN_ERR(check_for_error(b->eb, pool));
328  SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
329                                                base_checksum));
330
331  /* Transform the window stream to an svndiff stream.  Reuse the
332   * file baton for the stream handler, since it has all the
333   * needed information. */
334  diff_stream = svn_stream_create(b, pool);
335  svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
336  svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
337
338  /* If the connection does not support SVNDIFF1 or if we don't want to use
339   * compression, use the non-compressing "version 0" implementation */
340  if (   svn_ra_svn_compression_level(b->conn) > 0
341      && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1))
342    svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1,
343                            b->conn->compression_level, pool);
344  else
345    svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0,
346                            b->conn->compression_level, pool);
347  return SVN_NO_ERROR;
348}
349
350static svn_error_t *ra_svn_change_file_prop(void *file_baton,
351                                            const char *name,
352                                            const svn_string_t *value,
353                                            apr_pool_t *pool)
354{
355  ra_svn_baton_t *b = file_baton;
356
357  SVN_ERR(check_for_error(b->eb, pool));
358  SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
359                                                 b->token, name, value));
360  return SVN_NO_ERROR;
361}
362
363static svn_error_t *ra_svn_close_file(void *file_baton,
364                                      const char *text_checksum,
365                                      apr_pool_t *pool)
366{
367  ra_svn_baton_t *b = file_baton;
368
369  SVN_ERR(check_for_error(b->eb, pool));
370  SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
371                                           b->token, text_checksum));
372  return SVN_NO_ERROR;
373}
374
375static svn_error_t *ra_svn_absent_file(const char *path,
376                                       void *parent_baton, apr_pool_t *pool)
377{
378  ra_svn_baton_t *b = parent_baton;
379
380  /* Avoid sending an unknown command if the other end doesn't support
381     absent-file. */
382  if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
383    return SVN_NO_ERROR;
384
385  SVN_ERR(check_for_error(b->eb, pool));
386  SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
387  return SVN_NO_ERROR;
388}
389
390static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
391{
392  ra_svn_edit_baton_t *eb = edit_baton;
393  svn_error_t *err;
394
395  SVN_ERR_ASSERT(!eb->got_status);
396  eb->got_status = TRUE;
397  SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
398  err = svn_error_trace(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
399  if (err)
400    {
401      return svn_error_compose_create(
402                    err,
403                    svn_error_trace(
404                        svn_ra_svn__write_cmd_abort_edit(eb->conn, pool)));
405    }
406  if (eb->callback)
407    SVN_ERR(eb->callback(eb->callback_baton));
408  return SVN_NO_ERROR;
409}
410
411static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
412{
413  ra_svn_edit_baton_t *eb = edit_baton;
414
415  if (eb->got_status)
416    return SVN_NO_ERROR;
417  SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
418  SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
419  return SVN_NO_ERROR;
420}
421
422void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
423                           void **edit_baton, svn_ra_svn_conn_t *conn,
424                           apr_pool_t *pool,
425                           svn_ra_svn_edit_callback callback,
426                           void *callback_baton)
427{
428  svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
429  ra_svn_edit_baton_t *eb;
430
431  eb = apr_palloc(pool, sizeof(*eb));
432  eb->conn = conn;
433  eb->callback = callback;
434  eb->callback_baton = callback_baton;
435  eb->next_token = 0;
436  eb->got_status = FALSE;
437
438  ra_svn_editor->set_target_revision = ra_svn_target_rev;
439  ra_svn_editor->open_root = ra_svn_open_root;
440  ra_svn_editor->delete_entry = ra_svn_delete_entry;
441  ra_svn_editor->add_directory = ra_svn_add_dir;
442  ra_svn_editor->open_directory = ra_svn_open_dir;
443  ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
444  ra_svn_editor->close_directory = ra_svn_close_dir;
445  ra_svn_editor->absent_directory = ra_svn_absent_dir;
446  ra_svn_editor->add_file = ra_svn_add_file;
447  ra_svn_editor->open_file = ra_svn_open_file;
448  ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
449  ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
450  ra_svn_editor->close_file = ra_svn_close_file;
451  ra_svn_editor->absent_file = ra_svn_absent_file;
452  ra_svn_editor->close_edit = ra_svn_close_edit;
453  ra_svn_editor->abort_edit = ra_svn_abort_edit;
454
455  *editor = ra_svn_editor;
456  *edit_baton = eb;
457
458  svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
459                                           *edit_baton, NULL, NULL,
460                                           conn->shim_callbacks,
461                                           pool, pool));
462}
463
464/* --- DRIVING AN EDITOR --- */
465
466/* Store a token entry.  The token string will be copied into pool. */
467static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
468                                         void *baton,
469                                         svn_string_t *token,
470                                         svn_boolean_t is_file,
471                                         apr_pool_t *pool)
472{
473  ra_svn_token_entry_t *entry;
474
475  entry = apr_palloc(pool, sizeof(*entry));
476  entry->token = svn_string_dup(token, pool);
477  entry->baton = baton;
478  entry->is_file = is_file;
479  entry->dstream = NULL;
480  entry->pool = pool;
481
482  apr_hash_set(ds->tokens, entry->token->data, entry->token->len, entry);
483
484  return entry;
485}
486
487static svn_error_t *lookup_token(ra_svn_driver_state_t *ds,
488                                 svn_string_t *token,
489                                 svn_boolean_t is_file,
490                                 ra_svn_token_entry_t **entry)
491{
492  *entry = apr_hash_get(ds->tokens, token->data, token->len);
493  if (!*entry || (*entry)->is_file != is_file)
494    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
495                            _("Invalid file or dir token during edit"));
496  return SVN_NO_ERROR;
497}
498
499static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
500                                             apr_pool_t *pool,
501                                             const apr_array_header_t *params,
502                                             ra_svn_driver_state_t *ds)
503{
504  svn_revnum_t rev;
505
506  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
507  SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
508  return SVN_NO_ERROR;
509}
510
511static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
512                                            apr_pool_t *pool,
513                                            const apr_array_header_t *params,
514                                            ra_svn_driver_state_t *ds)
515{
516  svn_revnum_t rev;
517  apr_pool_t *subpool;
518  svn_string_t *token;
519  void *root_baton;
520
521  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)s", &rev, &token));
522  subpool = svn_pool_create(ds->pool);
523  SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
524                                    &root_baton));
525  store_token(ds, root_baton, token, FALSE, subpool);
526  return SVN_NO_ERROR;
527}
528
529static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
530                                               apr_pool_t *pool,
531                                               const apr_array_header_t *params,
532                                               ra_svn_driver_state_t *ds)
533{
534  const char *path;
535  svn_string_t *token;
536  svn_revnum_t rev;
537  ra_svn_token_entry_t *entry;
538
539  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)s",
540                                  &path, &rev, &token));
541  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
542  path = svn_relpath_canonicalize(path, pool);
543  SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
544  return SVN_NO_ERROR;
545}
546
547static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
548                                          apr_pool_t *pool,
549                                          const apr_array_header_t *params,
550                                          ra_svn_driver_state_t *ds)
551{
552  const char *path, *copy_path;
553  svn_string_t *token, *child_token;
554  svn_revnum_t copy_rev;
555  ra_svn_token_entry_t *entry;
556  apr_pool_t *subpool;
557  void *child_baton;
558
559  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token,
560                                  &child_token, &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
581static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
582                                           apr_pool_t *pool,
583                                           const apr_array_header_t *params,
584                                           ra_svn_driver_state_t *ds)
585{
586  const char *path;
587  svn_string_t *token, *child_token;
588  svn_revnum_t rev;
589  ra_svn_token_entry_t *entry;
590  apr_pool_t *subpool;
591  void *child_baton;
592
593  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token,
594                                  &child_token, &rev));
595  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
596  subpool = svn_pool_create(entry->pool);
597  path = svn_relpath_canonicalize(path, pool);
598  SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
599                                         &child_baton));
600  store_token(ds, child_baton, child_token, FALSE, subpool);
601  return SVN_NO_ERROR;
602}
603
604static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
605                                                  apr_pool_t *pool,
606                                                  const apr_array_header_t *params,
607                                                  ra_svn_driver_state_t *ds)
608{
609  svn_string_t *token;
610  const char *name;
611  svn_string_t *value;
612  ra_svn_token_entry_t *entry;
613
614  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name,
615                                  &value));
616  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
617  SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
618                                          entry->pool));
619  return SVN_NO_ERROR;
620}
621
622static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
623                                            apr_pool_t *pool,
624                                            const apr_array_header_t *params,
625                                            ra_svn_driver_state_t *ds)
626{
627  svn_string_t *token;
628  ra_svn_token_entry_t *entry;
629
630  /* Parse and look up the directory token. */
631  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token));
632  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
633
634  /* Close the directory and destroy the baton. */
635  SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
636  apr_hash_set(ds->tokens, token->data, token->len, NULL);
637  svn_pool_destroy(entry->pool);
638  return SVN_NO_ERROR;
639}
640
641static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
642                                             apr_pool_t *pool,
643                                             const apr_array_header_t *params,
644                                             ra_svn_driver_state_t *ds)
645{
646  const char *path;
647  svn_string_t *token;
648  ra_svn_token_entry_t *entry;
649
650  /* Parse parameters and look up the directory token. */
651  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token));
652  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
653
654  /* Call the editor. */
655  SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
656  return SVN_NO_ERROR;
657}
658
659static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
660                                           apr_pool_t *pool,
661                                           const apr_array_header_t *params,
662                                           ra_svn_driver_state_t *ds)
663{
664  const char *path, *copy_path;
665  svn_string_t *token, *file_token;
666  svn_revnum_t copy_rev;
667  ra_svn_token_entry_t *entry, *file_entry;
668
669  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token,
670                                  &file_token, &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
694static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
695                                            apr_pool_t *pool,
696                                            const apr_array_header_t *params,
697                                            ra_svn_driver_state_t *ds)
698{
699  const char *path;
700  svn_string_t *token, *file_token;
701  svn_revnum_t rev;
702  ra_svn_token_entry_t *entry, *file_entry;
703
704  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token,
705                                  &file_token, &rev));
706  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
707  ds->file_refs++;
708
709  /* The PATH should be canonical .. but never trust incoming data. */
710  if (!svn_relpath_is_canonical(path))
711    path = svn_relpath_canonicalize(path, pool);
712
713  file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
714  SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
715                                    &file_entry->baton));
716  return SVN_NO_ERROR;
717}
718
719static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
720                                                  apr_pool_t *pool,
721                                                  const apr_array_header_t *params,
722                                                  ra_svn_driver_state_t *ds)
723{
724  svn_string_t *token;
725  ra_svn_token_entry_t *entry;
726  svn_txdelta_window_handler_t wh;
727  void *wh_baton;
728  char *base_checksum;
729
730  /* Parse arguments and look up the token. */
731  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)",
732                                  &token, &base_checksum));
733  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
734  if (entry->dstream)
735    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
736                            _("Apply-textdelta already active"));
737  entry->pool = svn_pool_create(ds->file_pool);
738  SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
739                                          entry->pool, &wh, &wh_baton));
740  entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
741  return SVN_NO_ERROR;
742}
743
744static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
745                                                  apr_pool_t *pool,
746                                                  const apr_array_header_t *params,
747                                                  ra_svn_driver_state_t *ds)
748{
749  svn_string_t *token;
750  ra_svn_token_entry_t *entry;
751  svn_string_t *str;
752
753  /* Parse arguments and look up the token. */
754  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ss", &token, &str));
755  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
756  if (!entry->dstream)
757    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
758                            _("Apply-textdelta not active"));
759  SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
760  return SVN_NO_ERROR;
761}
762
763static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
764                                                apr_pool_t *pool,
765                                                const apr_array_header_t *params,
766                                                ra_svn_driver_state_t *ds)
767{
768  svn_string_t *token;
769  ra_svn_token_entry_t *entry;
770
771  /* Parse arguments and look up the token. */
772  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token));
773  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
774  if (!entry->dstream)
775    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
776                            _("Apply-textdelta not active"));
777  SVN_CMD_ERR(svn_stream_close(entry->dstream));
778  entry->dstream = NULL;
779  svn_pool_destroy(entry->pool);
780  return SVN_NO_ERROR;
781}
782
783static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
784                                                   apr_pool_t *pool,
785                                                   const apr_array_header_t *params,
786                                                   ra_svn_driver_state_t *ds)
787{
788  const char *name;
789  svn_string_t *token, *value;
790  ra_svn_token_entry_t *entry;
791
792  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name,
793                                  &value));
794  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
795  SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
796  return SVN_NO_ERROR;
797}
798
799static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
800                                             apr_pool_t *pool,
801                                             const apr_array_header_t *params,
802                                             ra_svn_driver_state_t *ds)
803{
804  svn_string_t *token;
805  ra_svn_token_entry_t *entry;
806  const char *text_checksum;
807
808  /* Parse arguments and look up the file token. */
809  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)",
810                                  &token, &text_checksum));
811  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
812
813  /* Close the file and destroy the baton. */
814  SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
815  apr_hash_set(ds->tokens, token->data, token->len, NULL);
816  if (--ds->file_refs == 0)
817    svn_pool_clear(ds->file_pool);
818  return SVN_NO_ERROR;
819}
820
821static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
822                                              apr_pool_t *pool,
823                                              const apr_array_header_t *params,
824                                              ra_svn_driver_state_t *ds)
825{
826  const char *path;
827  svn_string_t *token;
828  ra_svn_token_entry_t *entry;
829
830  /* Parse parameters and look up the parent directory token. */
831  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token));
832  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
833
834  /* Call the editor. */
835  SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
836  return SVN_NO_ERROR;
837}
838
839static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
840                                             apr_pool_t *pool,
841                                             const apr_array_header_t *params,
842                                             ra_svn_driver_state_t *ds)
843{
844  SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
845  ds->done = TRUE;
846  if (ds->aborted)
847    *ds->aborted = FALSE;
848  return svn_ra_svn__write_cmd_response(conn, pool, "");
849}
850
851static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
852                                             apr_pool_t *pool,
853                                             const apr_array_header_t *params,
854                                             ra_svn_driver_state_t *ds)
855{
856  ds->done = TRUE;
857  if (ds->aborted)
858    *ds->aborted = TRUE;
859  SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
860  return svn_ra_svn__write_cmd_response(conn, pool, "");
861}
862
863static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
864                                                apr_pool_t *pool,
865                                                const apr_array_header_t *params,
866                                                ra_svn_driver_state_t *ds)
867{
868  if (!ds->for_replay)
869    return svn_error_createf
870      (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
871       _("Command 'finish-replay' invalid outside of replays"));
872  ds->done = TRUE;
873  if (ds->aborted)
874    *ds->aborted = FALSE;
875  return SVN_NO_ERROR;
876}
877
878static const struct {
879  const char *cmd;
880  svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
881                          const apr_array_header_t *params,
882                          ra_svn_driver_state_t *ds);
883} ra_svn_edit_cmds[] = {
884  { "change-file-prop", ra_svn_handle_change_file_prop },
885  { "open-file",        ra_svn_handle_open_file },
886  { "apply-textdelta",  ra_svn_handle_apply_textdelta },
887  { "textdelta-chunk",  ra_svn_handle_textdelta_chunk },
888  { "close-file",       ra_svn_handle_close_file },
889  { "add-dir",          ra_svn_handle_add_dir },
890  { "open-dir",         ra_svn_handle_open_dir },
891  { "change-dir-prop",  ra_svn_handle_change_dir_prop },
892  { "delete-entry",     ra_svn_handle_delete_entry },
893  { "close-dir",        ra_svn_handle_close_dir },
894  { "absent-dir",       ra_svn_handle_absent_dir },
895  { "add-file",         ra_svn_handle_add_file },
896  { "textdelta-end",    ra_svn_handle_textdelta_end },
897  { "absent-file",      ra_svn_handle_absent_file },
898  { "abort-edit",       ra_svn_handle_abort_edit },
899  { "finish-replay",    ra_svn_handle_finish_replay },
900  { "target-rev",       ra_svn_handle_target_rev },
901  { "open-root",        ra_svn_handle_open_root },
902  { "close-edit",       ra_svn_handle_close_edit },
903  { NULL }
904};
905
906static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
907                                  void *baton)
908{
909  ra_svn_driver_state_t *ds = baton;
910  const char *cmd;
911  apr_array_header_t *params;
912
913  /* We blocked trying to send an error.  Read and discard an editing
914   * command in order to avoid deadlock. */
915  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, &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
924svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
925                                      apr_pool_t *pool,
926                                      const svn_delta_editor_t *editor,
927                                      void *edit_baton,
928                                      svn_boolean_t *aborted,
929                                      svn_boolean_t for_replay)
930{
931  ra_svn_driver_state_t state;
932  apr_pool_t *subpool = svn_pool_create(pool);
933  const char *cmd;
934  int i;
935  svn_error_t *err, *write_err;
936  apr_array_header_t *params;
937
938  state.editor = editor;
939  state.edit_baton = edit_baton;
940  state.tokens = apr_hash_make(pool);
941  state.aborted = aborted;
942  state.done = FALSE;
943  state.pool = pool;
944  state.file_pool = svn_pool_create(pool);
945  state.file_refs = 0;
946  state.for_replay = for_replay;
947
948  while (!state.done)
949    {
950      svn_pool_clear(subpool);
951      if (editor)
952        {
953          SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &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
1059svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1060                                     const svn_delta_editor_t *editor,
1061                                     void *edit_baton,
1062                                     svn_boolean_t *aborted)
1063{
1064  return svn_ra_svn_drive_editor2(conn,
1065                                  pool,
1066                                  editor,
1067                                  edit_baton,
1068                                  aborted,
1069                                  FALSE);
1070}
1071