1251881Speter/*
2251881Speter * editorp.c :  Driving and consuming an editor across an svn connection
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#define APR_WANT_STRFUNC
27251881Speter#include <apr_want.h>
28251881Speter#include <apr_general.h>
29251881Speter#include <apr_strings.h>
30251881Speter
31251881Speter#include "svn_hash.h"
32251881Speter#include "svn_types.h"
33251881Speter#include "svn_string.h"
34251881Speter#include "svn_error.h"
35251881Speter#include "svn_delta.h"
36251881Speter#include "svn_dirent_uri.h"
37251881Speter#include "svn_ra_svn.h"
38251881Speter#include "svn_path.h"
39251881Speter#include "svn_pools.h"
40251881Speter#include "svn_private_config.h"
41251881Speter
42251881Speter#include "private/svn_fspath.h"
43251881Speter#include "private/svn_editor.h"
44251881Speter
45251881Speter#include "ra_svn.h"
46251881Speter
47251881Speter/*
48251881Speter * Both the client and server in the svn protocol need to drive and
49251881Speter * consume editors.  For a commit, the client drives and the server
50251881Speter * consumes; for an update/switch/status/diff, the server drives and
51251881Speter * the client consumes.  This file provides a generic framework for
52251881Speter * marshalling and unmarshalling editor operations over an svn
53251881Speter * connection; both ends are useful for both server and client.
54251881Speter */
55251881Speter
56251881Spetertypedef struct ra_svn_edit_baton_t {
57251881Speter  svn_ra_svn_conn_t *conn;
58251881Speter  svn_ra_svn_edit_callback callback;    /* Called on successful completion. */
59251881Speter  void *callback_baton;
60251881Speter  int next_token;
61251881Speter  svn_boolean_t got_status;
62251881Speter} ra_svn_edit_baton_t;
63251881Speter
64251881Speter/* Works for both directories and files. */
65251881Spetertypedef struct ra_svn_baton_t {
66251881Speter  svn_ra_svn_conn_t *conn;
67251881Speter  apr_pool_t *pool;
68251881Speter  ra_svn_edit_baton_t *eb;
69251881Speter  const char *token;
70251881Speter} ra_svn_baton_t;
71251881Speter
72251881Spetertypedef struct ra_svn_driver_state_t {
73251881Speter  const svn_delta_editor_t *editor;
74251881Speter  void *edit_baton;
75251881Speter  apr_hash_t *tokens;
76251881Speter  svn_boolean_t *aborted;
77251881Speter  svn_boolean_t done;
78251881Speter  apr_pool_t *pool;
79251881Speter  apr_pool_t *file_pool;
80251881Speter  int file_refs;
81251881Speter  svn_boolean_t for_replay;
82251881Speter} ra_svn_driver_state_t;
83251881Speter
84251881Speter/* Works for both directories and files; however, the pool handling is
85251881Speter   different for files.  To save space during commits (where file
86251881Speter   batons generally last until the end of the commit), token entries
87251881Speter   for files are all created in a single reference-counted pool (the
88251881Speter   file_pool member of the driver state structure), which is cleared
89251881Speter   at close_file time when the reference count hits zero.  So the pool
90251881Speter   field in this structure is vestigial for files, and we use it for a
91251881Speter   different purpose instead: at apply-textdelta time, we set it to a
92251881Speter   subpool of the file pool, which is destroyed in textdelta-end. */
93251881Spetertypedef struct ra_svn_token_entry_t {
94299742Sdim  svn_string_t *token;
95251881Speter  void *baton;
96251881Speter  svn_boolean_t is_file;
97251881Speter  svn_stream_t *dstream;  /* svndiff stream for apply_textdelta */
98251881Speter  apr_pool_t *pool;
99251881Speter} ra_svn_token_entry_t;
100251881Speter
101251881Speter/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
102251881Speter
103251881Speterstatic const char *make_token(char type, ra_svn_edit_baton_t *eb,
104251881Speter                              apr_pool_t *pool)
105251881Speter{
106251881Speter  return apr_psprintf(pool, "%c%d", type, eb->next_token++);
107251881Speter}
108251881Speter
109251881Speterstatic ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
110251881Speter                                         apr_pool_t *pool,
111251881Speter                                         ra_svn_edit_baton_t *eb,
112251881Speter                                         const char *token)
113251881Speter{
114251881Speter  ra_svn_baton_t *b;
115251881Speter
116251881Speter  b = apr_palloc(pool, sizeof(*b));
117251881Speter  b->conn = conn;
118251881Speter  b->pool = pool;
119251881Speter  b->eb = eb;
120251881Speter  b->token = token;
121251881Speter  return b;
122251881Speter}
123251881Speter
124251881Speter/* Check for an early error status report from the consumer.  If we
125251881Speter * get one, abort the edit and return the error. */
126251881Speterstatic svn_error_t *
127251881Spetercheck_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
128251881Speter{
129299742Sdim  svn_boolean_t available;
130251881Speter  SVN_ERR_ASSERT(!eb->got_status);
131251881Speter
132251881Speter  /* reset TX counter */
133251881Speter  eb->conn->written_since_error_check = 0;
134251881Speter
135251881Speter  /* if we weren't asked to always check, wait for at least the next TX */
136251881Speter  eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
137251881Speter
138251881Speter  /* any incoming data? */
139299742Sdim  SVN_ERR(svn_ra_svn__data_available(eb->conn, &available));
140299742Sdim  if (available)
141251881Speter    {
142251881Speter      eb->got_status = TRUE;
143251881Speter      SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
144251881Speter      SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
145251881Speter      /* We shouldn't get here if the consumer is doing its job. */
146251881Speter      return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
147251881Speter                              _("Successful edit status returned too soon"));
148251881Speter    }
149251881Speter  return SVN_NO_ERROR;
150251881Speter}
151251881Speter
152251881Speterstatic svn_error_t *
153251881Spetercheck_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
154251881Speter{
155251881Speter  return eb->conn->may_check_for_error
156251881Speter    ? check_for_error_internal(eb, pool)
157251881Speter    : SVN_NO_ERROR;
158251881Speter}
159251881Speter
160251881Speterstatic svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
161251881Speter                                      apr_pool_t *pool)
162251881Speter{
163251881Speter  ra_svn_edit_baton_t *eb = edit_baton;
164251881Speter
165251881Speter  SVN_ERR(check_for_error(eb, pool));
166251881Speter  SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
167251881Speter  return SVN_NO_ERROR;
168251881Speter}
169251881Speter
170251881Speterstatic svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
171251881Speter                                     apr_pool_t *pool, void **root_baton)
172251881Speter{
173251881Speter  ra_svn_edit_baton_t *eb = edit_baton;
174251881Speter  const char *token = make_token('d', eb, pool);
175251881Speter
176251881Speter  SVN_ERR(check_for_error(eb, pool));
177251881Speter  SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
178251881Speter  *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
179251881Speter  return SVN_NO_ERROR;
180251881Speter}
181251881Speter
182251881Speterstatic svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
183251881Speter                                        void *parent_baton, apr_pool_t *pool)
184251881Speter{
185251881Speter  ra_svn_baton_t *b = parent_baton;
186251881Speter
187251881Speter  SVN_ERR(check_for_error(b->eb, pool));
188251881Speter  SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
189251881Speter                                             path, rev, b->token));
190251881Speter  return SVN_NO_ERROR;
191251881Speter}
192251881Speter
193251881Speterstatic svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
194251881Speter                                   const char *copy_path,
195251881Speter                                   svn_revnum_t copy_rev,
196251881Speter                                   apr_pool_t *pool, void **child_baton)
197251881Speter{
198251881Speter  ra_svn_baton_t *b = parent_baton;
199251881Speter  const char *token = make_token('d', b->eb, pool);
200251881Speter
201251881Speter  SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
202251881Speter                 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
203251881Speter  SVN_ERR(check_for_error(b->eb, pool));
204251881Speter  SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
205251881Speter                                        token, copy_path, copy_rev));
206251881Speter  *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
207251881Speter  return SVN_NO_ERROR;
208251881Speter}
209251881Speter
210251881Speterstatic svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
211251881Speter                                    svn_revnum_t rev, apr_pool_t *pool,
212251881Speter                                    void **child_baton)
213251881Speter{
214251881Speter  ra_svn_baton_t *b = parent_baton;
215251881Speter  const char *token = make_token('d', b->eb, pool);
216251881Speter
217251881Speter  SVN_ERR(check_for_error(b->eb, pool));
218251881Speter  SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
219251881Speter                                         token, rev));
220251881Speter  *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
221251881Speter  return SVN_NO_ERROR;
222251881Speter}
223251881Speter
224251881Speterstatic svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
225251881Speter                                           const svn_string_t *value,
226251881Speter                                           apr_pool_t *pool)
227251881Speter{
228251881Speter  ra_svn_baton_t *b = dir_baton;
229251881Speter
230251881Speter  SVN_ERR(check_for_error(b->eb, pool));
231251881Speter  SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
232251881Speter                                                name, value));
233251881Speter  return SVN_NO_ERROR;
234251881Speter}
235251881Speter
236251881Speterstatic svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
237251881Speter{
238251881Speter  ra_svn_baton_t *b = dir_baton;
239251881Speter
240251881Speter  SVN_ERR(check_for_error(b->eb, pool));
241251881Speter  SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
242251881Speter  return SVN_NO_ERROR;
243251881Speter}
244251881Speter
245251881Speterstatic svn_error_t *ra_svn_absent_dir(const char *path,
246251881Speter                                      void *parent_baton, apr_pool_t *pool)
247251881Speter{
248251881Speter  ra_svn_baton_t *b = parent_baton;
249251881Speter
250251881Speter  /* Avoid sending an unknown command if the other end doesn't support
251251881Speter     absent-dir. */
252251881Speter  if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
253251881Speter    return SVN_NO_ERROR;
254251881Speter
255251881Speter  SVN_ERR(check_for_error(b->eb, pool));
256251881Speter  SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
257251881Speter  return SVN_NO_ERROR;
258251881Speter}
259251881Speter
260251881Speterstatic svn_error_t *ra_svn_add_file(const char *path,
261251881Speter                                    void *parent_baton,
262251881Speter                                    const char *copy_path,
263251881Speter                                    svn_revnum_t copy_rev,
264251881Speter                                    apr_pool_t *pool,
265251881Speter                                    void **file_baton)
266251881Speter{
267251881Speter  ra_svn_baton_t *b = parent_baton;
268251881Speter  const char *token = make_token('c', b->eb, pool);
269251881Speter
270251881Speter  SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
271251881Speter                 || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
272251881Speter  SVN_ERR(check_for_error(b->eb, pool));
273251881Speter  SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool,  path, b->token,
274251881Speter                                         token, copy_path, copy_rev));
275251881Speter  *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
276251881Speter  return SVN_NO_ERROR;
277251881Speter}
278251881Speter
279251881Speterstatic svn_error_t *ra_svn_open_file(const char *path,
280251881Speter                                     void *parent_baton,
281251881Speter                                     svn_revnum_t rev,
282251881Speter                                     apr_pool_t *pool,
283251881Speter                                     void **file_baton)
284251881Speter{
285251881Speter  ra_svn_baton_t *b = parent_baton;
286251881Speter  const char *token = make_token('c', b->eb, pool);
287251881Speter
288251881Speter  SVN_ERR(check_for_error(b->eb, b->pool));
289251881Speter  SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
290251881Speter                                          token, rev));
291251881Speter  *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
292251881Speter  return SVN_NO_ERROR;
293251881Speter}
294251881Speter
295251881Speterstatic svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
296251881Speter                                           apr_size_t *len)
297251881Speter{
298251881Speter  ra_svn_baton_t *b = baton;
299251881Speter  svn_string_t str;
300251881Speter
301251881Speter  SVN_ERR(check_for_error(b->eb, b->pool));
302251881Speter  str.data = data;
303251881Speter  str.len = *len;
304251881Speter  return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
305251881Speter                                               b->token, &str);
306251881Speter}
307251881Speter
308251881Speterstatic svn_error_t *ra_svn_svndiff_close_handler(void *baton)
309251881Speter{
310251881Speter  ra_svn_baton_t *b = baton;
311251881Speter
312251881Speter  SVN_ERR(check_for_error(b->eb, b->pool));
313251881Speter  SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
314251881Speter  return SVN_NO_ERROR;
315251881Speter}
316251881Speter
317251881Speterstatic svn_error_t *ra_svn_apply_textdelta(void *file_baton,
318251881Speter                                           const char *base_checksum,
319251881Speter                                           apr_pool_t *pool,
320251881Speter                                           svn_txdelta_window_handler_t *wh,
321251881Speter                                           void **wh_baton)
322251881Speter{
323251881Speter  ra_svn_baton_t *b = file_baton;
324251881Speter  svn_stream_t *diff_stream;
325251881Speter
326251881Speter  /* Tell the other side we're starting a text delta. */
327251881Speter  SVN_ERR(check_for_error(b->eb, pool));
328251881Speter  SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
329251881Speter                                                base_checksum));
330251881Speter
331251881Speter  /* Transform the window stream to an svndiff stream.  Reuse the
332251881Speter   * file baton for the stream handler, since it has all the
333251881Speter   * needed information. */
334251881Speter  diff_stream = svn_stream_create(b, pool);
335251881Speter  svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
336251881Speter  svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
337251881Speter
338251881Speter  /* If the connection does not support SVNDIFF1 or if we don't want to use
339251881Speter   * compression, use the non-compressing "version 0" implementation */
340251881Speter  if (   svn_ra_svn_compression_level(b->conn) > 0
341251881Speter      && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1))
342251881Speter    svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1,
343251881Speter                            b->conn->compression_level, pool);
344251881Speter  else
345251881Speter    svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0,
346251881Speter                            b->conn->compression_level, pool);
347251881Speter  return SVN_NO_ERROR;
348251881Speter}
349251881Speter
350251881Speterstatic svn_error_t *ra_svn_change_file_prop(void *file_baton,
351251881Speter                                            const char *name,
352251881Speter                                            const svn_string_t *value,
353251881Speter                                            apr_pool_t *pool)
354251881Speter{
355251881Speter  ra_svn_baton_t *b = file_baton;
356251881Speter
357251881Speter  SVN_ERR(check_for_error(b->eb, pool));
358251881Speter  SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
359251881Speter                                                 b->token, name, value));
360251881Speter  return SVN_NO_ERROR;
361251881Speter}
362251881Speter
363251881Speterstatic svn_error_t *ra_svn_close_file(void *file_baton,
364251881Speter                                      const char *text_checksum,
365251881Speter                                      apr_pool_t *pool)
366251881Speter{
367251881Speter  ra_svn_baton_t *b = file_baton;
368251881Speter
369251881Speter  SVN_ERR(check_for_error(b->eb, pool));
370251881Speter  SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
371251881Speter                                           b->token, text_checksum));
372251881Speter  return SVN_NO_ERROR;
373251881Speter}
374251881Speter
375251881Speterstatic svn_error_t *ra_svn_absent_file(const char *path,
376251881Speter                                       void *parent_baton, apr_pool_t *pool)
377251881Speter{
378251881Speter  ra_svn_baton_t *b = parent_baton;
379251881Speter
380251881Speter  /* Avoid sending an unknown command if the other end doesn't support
381251881Speter     absent-file. */
382251881Speter  if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
383251881Speter    return SVN_NO_ERROR;
384251881Speter
385251881Speter  SVN_ERR(check_for_error(b->eb, pool));
386251881Speter  SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
387251881Speter  return SVN_NO_ERROR;
388251881Speter}
389251881Speter
390251881Speterstatic svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
391251881Speter{
392251881Speter  ra_svn_edit_baton_t *eb = edit_baton;
393251881Speter  svn_error_t *err;
394251881Speter
395251881Speter  SVN_ERR_ASSERT(!eb->got_status);
396251881Speter  eb->got_status = TRUE;
397251881Speter  SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
398299742Sdim  err = svn_error_trace(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
399251881Speter  if (err)
400251881Speter    {
401299742Sdim      return svn_error_compose_create(
402299742Sdim                    err,
403299742Sdim                    svn_error_trace(
404299742Sdim                        svn_ra_svn__write_cmd_abort_edit(eb->conn, pool)));
405251881Speter    }
406251881Speter  if (eb->callback)
407251881Speter    SVN_ERR(eb->callback(eb->callback_baton));
408251881Speter  return SVN_NO_ERROR;
409251881Speter}
410251881Speter
411251881Speterstatic svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
412251881Speter{
413251881Speter  ra_svn_edit_baton_t *eb = edit_baton;
414251881Speter
415251881Speter  if (eb->got_status)
416251881Speter    return SVN_NO_ERROR;
417251881Speter  SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
418251881Speter  SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
419251881Speter  return SVN_NO_ERROR;
420251881Speter}
421251881Speter
422251881Spetervoid svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
423251881Speter                           void **edit_baton, svn_ra_svn_conn_t *conn,
424251881Speter                           apr_pool_t *pool,
425251881Speter                           svn_ra_svn_edit_callback callback,
426251881Speter                           void *callback_baton)
427251881Speter{
428251881Speter  svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
429251881Speter  ra_svn_edit_baton_t *eb;
430251881Speter
431251881Speter  eb = apr_palloc(pool, sizeof(*eb));
432251881Speter  eb->conn = conn;
433251881Speter  eb->callback = callback;
434251881Speter  eb->callback_baton = callback_baton;
435251881Speter  eb->next_token = 0;
436251881Speter  eb->got_status = FALSE;
437251881Speter
438251881Speter  ra_svn_editor->set_target_revision = ra_svn_target_rev;
439251881Speter  ra_svn_editor->open_root = ra_svn_open_root;
440251881Speter  ra_svn_editor->delete_entry = ra_svn_delete_entry;
441251881Speter  ra_svn_editor->add_directory = ra_svn_add_dir;
442251881Speter  ra_svn_editor->open_directory = ra_svn_open_dir;
443251881Speter  ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
444251881Speter  ra_svn_editor->close_directory = ra_svn_close_dir;
445251881Speter  ra_svn_editor->absent_directory = ra_svn_absent_dir;
446251881Speter  ra_svn_editor->add_file = ra_svn_add_file;
447251881Speter  ra_svn_editor->open_file = ra_svn_open_file;
448251881Speter  ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
449251881Speter  ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
450251881Speter  ra_svn_editor->close_file = ra_svn_close_file;
451251881Speter  ra_svn_editor->absent_file = ra_svn_absent_file;
452251881Speter  ra_svn_editor->close_edit = ra_svn_close_edit;
453251881Speter  ra_svn_editor->abort_edit = ra_svn_abort_edit;
454251881Speter
455251881Speter  *editor = ra_svn_editor;
456251881Speter  *edit_baton = eb;
457251881Speter
458251881Speter  svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
459251881Speter                                           *edit_baton, NULL, NULL,
460251881Speter                                           conn->shim_callbacks,
461251881Speter                                           pool, pool));
462251881Speter}
463251881Speter
464251881Speter/* --- DRIVING AN EDITOR --- */
465251881Speter
466251881Speter/* Store a token entry.  The token string will be copied into pool. */
467251881Speterstatic ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
468299742Sdim                                         void *baton,
469299742Sdim                                         svn_string_t *token,
470251881Speter                                         svn_boolean_t is_file,
471251881Speter                                         apr_pool_t *pool)
472251881Speter{
473251881Speter  ra_svn_token_entry_t *entry;
474251881Speter
475251881Speter  entry = apr_palloc(pool, sizeof(*entry));
476299742Sdim  entry->token = svn_string_dup(token, pool);
477251881Speter  entry->baton = baton;
478251881Speter  entry->is_file = is_file;
479251881Speter  entry->dstream = NULL;
480251881Speter  entry->pool = pool;
481299742Sdim
482299742Sdim  apr_hash_set(ds->tokens, entry->token->data, entry->token->len, entry);
483299742Sdim
484251881Speter  return entry;
485251881Speter}
486251881Speter
487299742Sdimstatic svn_error_t *lookup_token(ra_svn_driver_state_t *ds,
488299742Sdim                                 svn_string_t *token,
489251881Speter                                 svn_boolean_t is_file,
490251881Speter                                 ra_svn_token_entry_t **entry)
491251881Speter{
492299742Sdim  *entry = apr_hash_get(ds->tokens, token->data, token->len);
493251881Speter  if (!*entry || (*entry)->is_file != is_file)
494251881Speter    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
495251881Speter                            _("Invalid file or dir token during edit"));
496251881Speter  return SVN_NO_ERROR;
497251881Speter}
498251881Speter
499251881Speterstatic svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
500251881Speter                                             apr_pool_t *pool,
501251881Speter                                             const apr_array_header_t *params,
502251881Speter                                             ra_svn_driver_state_t *ds)
503251881Speter{
504251881Speter  svn_revnum_t rev;
505251881Speter
506251881Speter  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
507251881Speter  SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
508251881Speter  return SVN_NO_ERROR;
509251881Speter}
510251881Speter
511251881Speterstatic svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
512251881Speter                                            apr_pool_t *pool,
513251881Speter                                            const apr_array_header_t *params,
514251881Speter                                            ra_svn_driver_state_t *ds)
515251881Speter{
516251881Speter  svn_revnum_t rev;
517251881Speter  apr_pool_t *subpool;
518299742Sdim  svn_string_t *token;
519251881Speter  void *root_baton;
520251881Speter
521299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)s", &rev, &token));
522251881Speter  subpool = svn_pool_create(ds->pool);
523251881Speter  SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
524251881Speter                                    &root_baton));
525251881Speter  store_token(ds, root_baton, token, FALSE, subpool);
526251881Speter  return SVN_NO_ERROR;
527251881Speter}
528251881Speter
529251881Speterstatic svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
530251881Speter                                               apr_pool_t *pool,
531251881Speter                                               const apr_array_header_t *params,
532251881Speter                                               ra_svn_driver_state_t *ds)
533251881Speter{
534299742Sdim  const char *path;
535299742Sdim  svn_string_t *token;
536251881Speter  svn_revnum_t rev;
537251881Speter  ra_svn_token_entry_t *entry;
538251881Speter
539299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)s",
540251881Speter                                  &path, &rev, &token));
541251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
542251881Speter  path = svn_relpath_canonicalize(path, pool);
543251881Speter  SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
544251881Speter  return SVN_NO_ERROR;
545251881Speter}
546251881Speter
547251881Speterstatic svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
548251881Speter                                          apr_pool_t *pool,
549251881Speter                                          const apr_array_header_t *params,
550251881Speter                                          ra_svn_driver_state_t *ds)
551251881Speter{
552299742Sdim  const char *path, *copy_path;
553299742Sdim  svn_string_t *token, *child_token;
554251881Speter  svn_revnum_t copy_rev;
555251881Speter  ra_svn_token_entry_t *entry;
556251881Speter  apr_pool_t *subpool;
557251881Speter  void *child_baton;
558251881Speter
559299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token,
560251881Speter                                  &child_token, &copy_path, &copy_rev));
561251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
562251881Speter  subpool = svn_pool_create(entry->pool);
563251881Speter  path = svn_relpath_canonicalize(path, pool);
564251881Speter
565251881Speter  /* Some operations pass COPY_PATH as a full URL (commits, etc.).
566251881Speter     Others (replay, e.g.) deliver an fspath.  That's ... annoying. */
567251881Speter  if (copy_path)
568251881Speter    {
569251881Speter      if (svn_path_is_url(copy_path))
570251881Speter        copy_path = svn_uri_canonicalize(copy_path, pool);
571251881Speter      else
572251881Speter        copy_path = svn_fspath__canonicalize(copy_path, pool);
573251881Speter    }
574251881Speter
575251881Speter  SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
576251881Speter                                        copy_rev, subpool, &child_baton));
577251881Speter  store_token(ds, child_baton, child_token, FALSE, subpool);
578251881Speter  return SVN_NO_ERROR;
579251881Speter}
580251881Speter
581251881Speterstatic svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
582251881Speter                                           apr_pool_t *pool,
583251881Speter                                           const apr_array_header_t *params,
584251881Speter                                           ra_svn_driver_state_t *ds)
585251881Speter{
586299742Sdim  const char *path;
587299742Sdim  svn_string_t *token, *child_token;
588251881Speter  svn_revnum_t rev;
589251881Speter  ra_svn_token_entry_t *entry;
590251881Speter  apr_pool_t *subpool;
591251881Speter  void *child_baton;
592251881Speter
593299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token,
594251881Speter                                  &child_token, &rev));
595251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
596251881Speter  subpool = svn_pool_create(entry->pool);
597251881Speter  path = svn_relpath_canonicalize(path, pool);
598251881Speter  SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
599251881Speter                                         &child_baton));
600251881Speter  store_token(ds, child_baton, child_token, FALSE, subpool);
601251881Speter  return SVN_NO_ERROR;
602251881Speter}
603251881Speter
604251881Speterstatic svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
605251881Speter                                                  apr_pool_t *pool,
606251881Speter                                                  const apr_array_header_t *params,
607251881Speter                                                  ra_svn_driver_state_t *ds)
608251881Speter{
609299742Sdim  svn_string_t *token;
610299742Sdim  const char *name;
611251881Speter  svn_string_t *value;
612251881Speter  ra_svn_token_entry_t *entry;
613251881Speter
614299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name,
615251881Speter                                  &value));
616251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
617251881Speter  SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
618251881Speter                                          entry->pool));
619251881Speter  return SVN_NO_ERROR;
620251881Speter}
621251881Speter
622251881Speterstatic svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
623251881Speter                                            apr_pool_t *pool,
624251881Speter                                            const apr_array_header_t *params,
625251881Speter                                            ra_svn_driver_state_t *ds)
626251881Speter{
627299742Sdim  svn_string_t *token;
628251881Speter  ra_svn_token_entry_t *entry;
629251881Speter
630251881Speter  /* Parse and look up the directory token. */
631299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token));
632251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
633251881Speter
634251881Speter  /* Close the directory and destroy the baton. */
635251881Speter  SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
636299742Sdim  apr_hash_set(ds->tokens, token->data, token->len, NULL);
637251881Speter  svn_pool_destroy(entry->pool);
638251881Speter  return SVN_NO_ERROR;
639251881Speter}
640251881Speter
641251881Speterstatic svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
642251881Speter                                             apr_pool_t *pool,
643251881Speter                                             const apr_array_header_t *params,
644251881Speter                                             ra_svn_driver_state_t *ds)
645251881Speter{
646251881Speter  const char *path;
647299742Sdim  svn_string_t *token;
648251881Speter  ra_svn_token_entry_t *entry;
649251881Speter
650251881Speter  /* Parse parameters and look up the directory token. */
651299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token));
652251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
653251881Speter
654251881Speter  /* Call the editor. */
655251881Speter  SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
656251881Speter  return SVN_NO_ERROR;
657251881Speter}
658251881Speter
659251881Speterstatic svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
660251881Speter                                           apr_pool_t *pool,
661251881Speter                                           const apr_array_header_t *params,
662251881Speter                                           ra_svn_driver_state_t *ds)
663251881Speter{
664299742Sdim  const char *path, *copy_path;
665299742Sdim  svn_string_t *token, *file_token;
666251881Speter  svn_revnum_t copy_rev;
667251881Speter  ra_svn_token_entry_t *entry, *file_entry;
668251881Speter
669299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?cr)", &path, &token,
670251881Speter                                  &file_token, &copy_path, &copy_rev));
671251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
672251881Speter  ds->file_refs++;
673251881Speter
674299742Sdim  /* The PATH should be canonical .. but never trust incoming data. */
675299742Sdim  if (!svn_relpath_is_canonical(path))
676299742Sdim    path = svn_relpath_canonicalize(path, pool);
677299742Sdim
678251881Speter  /* Some operations pass COPY_PATH as a full URL (commits, etc.).
679251881Speter     Others (replay, e.g.) deliver an fspath.  That's ... annoying. */
680251881Speter  if (copy_path)
681251881Speter    {
682251881Speter      if (svn_path_is_url(copy_path))
683251881Speter        copy_path = svn_uri_canonicalize(copy_path, pool);
684251881Speter      else
685251881Speter        copy_path = svn_fspath__canonicalize(copy_path, pool);
686251881Speter    }
687251881Speter
688251881Speter  file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
689251881Speter  SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
690251881Speter                                   ds->file_pool, &file_entry->baton));
691251881Speter  return SVN_NO_ERROR;
692251881Speter}
693251881Speter
694251881Speterstatic svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
695251881Speter                                            apr_pool_t *pool,
696251881Speter                                            const apr_array_header_t *params,
697251881Speter                                            ra_svn_driver_state_t *ds)
698251881Speter{
699299742Sdim  const char *path;
700299742Sdim  svn_string_t *token, *file_token;
701251881Speter  svn_revnum_t rev;
702251881Speter  ra_svn_token_entry_t *entry, *file_entry;
703251881Speter
704299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "css(?r)", &path, &token,
705251881Speter                                  &file_token, &rev));
706251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
707251881Speter  ds->file_refs++;
708299742Sdim
709299742Sdim  /* The PATH should be canonical .. but never trust incoming data. */
710299742Sdim  if (!svn_relpath_is_canonical(path))
711299742Sdim    path = svn_relpath_canonicalize(path, pool);
712299742Sdim
713251881Speter  file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
714251881Speter  SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
715251881Speter                                    &file_entry->baton));
716251881Speter  return SVN_NO_ERROR;
717251881Speter}
718251881Speter
719251881Speterstatic svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
720251881Speter                                                  apr_pool_t *pool,
721251881Speter                                                  const apr_array_header_t *params,
722251881Speter                                                  ra_svn_driver_state_t *ds)
723251881Speter{
724299742Sdim  svn_string_t *token;
725251881Speter  ra_svn_token_entry_t *entry;
726251881Speter  svn_txdelta_window_handler_t wh;
727251881Speter  void *wh_baton;
728251881Speter  char *base_checksum;
729251881Speter
730251881Speter  /* Parse arguments and look up the token. */
731299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)",
732251881Speter                                  &token, &base_checksum));
733251881Speter  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
734251881Speter  if (entry->dstream)
735251881Speter    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
736251881Speter                            _("Apply-textdelta already active"));
737251881Speter  entry->pool = svn_pool_create(ds->file_pool);
738251881Speter  SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
739251881Speter                                          entry->pool, &wh, &wh_baton));
740251881Speter  entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
741251881Speter  return SVN_NO_ERROR;
742251881Speter}
743251881Speter
744251881Speterstatic svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
745251881Speter                                                  apr_pool_t *pool,
746251881Speter                                                  const apr_array_header_t *params,
747251881Speter                                                  ra_svn_driver_state_t *ds)
748251881Speter{
749299742Sdim  svn_string_t *token;
750251881Speter  ra_svn_token_entry_t *entry;
751251881Speter  svn_string_t *str;
752251881Speter
753251881Speter  /* Parse arguments and look up the token. */
754299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ss", &token, &str));
755251881Speter  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
756251881Speter  if (!entry->dstream)
757251881Speter    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
758251881Speter                            _("Apply-textdelta not active"));
759251881Speter  SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
760251881Speter  return SVN_NO_ERROR;
761251881Speter}
762251881Speter
763251881Speterstatic svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
764251881Speter                                                apr_pool_t *pool,
765251881Speter                                                const apr_array_header_t *params,
766251881Speter                                                ra_svn_driver_state_t *ds)
767251881Speter{
768299742Sdim  svn_string_t *token;
769251881Speter  ra_svn_token_entry_t *entry;
770251881Speter
771251881Speter  /* Parse arguments and look up the token. */
772299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s", &token));
773251881Speter  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
774251881Speter  if (!entry->dstream)
775251881Speter    return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
776251881Speter                            _("Apply-textdelta not active"));
777251881Speter  SVN_CMD_ERR(svn_stream_close(entry->dstream));
778251881Speter  entry->dstream = NULL;
779251881Speter  svn_pool_destroy(entry->pool);
780251881Speter  return SVN_NO_ERROR;
781251881Speter}
782251881Speter
783251881Speterstatic svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
784251881Speter                                                   apr_pool_t *pool,
785251881Speter                                                   const apr_array_header_t *params,
786251881Speter                                                   ra_svn_driver_state_t *ds)
787251881Speter{
788299742Sdim  const char *name;
789299742Sdim  svn_string_t *token, *value;
790251881Speter  ra_svn_token_entry_t *entry;
791251881Speter
792299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "sc(?s)", &token, &name,
793251881Speter                                  &value));
794251881Speter  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
795251881Speter  SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
796251881Speter  return SVN_NO_ERROR;
797251881Speter}
798251881Speter
799251881Speterstatic svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
800251881Speter                                             apr_pool_t *pool,
801251881Speter                                             const apr_array_header_t *params,
802251881Speter                                             ra_svn_driver_state_t *ds)
803251881Speter{
804299742Sdim  svn_string_t *token;
805251881Speter  ra_svn_token_entry_t *entry;
806251881Speter  const char *text_checksum;
807251881Speter
808251881Speter  /* Parse arguments and look up the file token. */
809299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "s(?c)",
810251881Speter                                  &token, &text_checksum));
811251881Speter  SVN_ERR(lookup_token(ds, token, TRUE, &entry));
812251881Speter
813251881Speter  /* Close the file and destroy the baton. */
814251881Speter  SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
815299742Sdim  apr_hash_set(ds->tokens, token->data, token->len, NULL);
816251881Speter  if (--ds->file_refs == 0)
817251881Speter    svn_pool_clear(ds->file_pool);
818251881Speter  return SVN_NO_ERROR;
819251881Speter}
820251881Speter
821251881Speterstatic svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
822251881Speter                                              apr_pool_t *pool,
823251881Speter                                              const apr_array_header_t *params,
824251881Speter                                              ra_svn_driver_state_t *ds)
825251881Speter{
826251881Speter  const char *path;
827299742Sdim  svn_string_t *token;
828251881Speter  ra_svn_token_entry_t *entry;
829251881Speter
830251881Speter  /* Parse parameters and look up the parent directory token. */
831299742Sdim  SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &path, &token));
832251881Speter  SVN_ERR(lookup_token(ds, token, FALSE, &entry));
833251881Speter
834251881Speter  /* Call the editor. */
835251881Speter  SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
836251881Speter  return SVN_NO_ERROR;
837251881Speter}
838251881Speter
839251881Speterstatic svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
840251881Speter                                             apr_pool_t *pool,
841251881Speter                                             const apr_array_header_t *params,
842251881Speter                                             ra_svn_driver_state_t *ds)
843251881Speter{
844251881Speter  SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
845251881Speter  ds->done = TRUE;
846251881Speter  if (ds->aborted)
847251881Speter    *ds->aborted = FALSE;
848251881Speter  return svn_ra_svn__write_cmd_response(conn, pool, "");
849251881Speter}
850251881Speter
851251881Speterstatic svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
852251881Speter                                             apr_pool_t *pool,
853251881Speter                                             const apr_array_header_t *params,
854251881Speter                                             ra_svn_driver_state_t *ds)
855251881Speter{
856251881Speter  ds->done = TRUE;
857251881Speter  if (ds->aborted)
858251881Speter    *ds->aborted = TRUE;
859251881Speter  SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
860251881Speter  return svn_ra_svn__write_cmd_response(conn, pool, "");
861251881Speter}
862251881Speter
863251881Speterstatic svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
864251881Speter                                                apr_pool_t *pool,
865251881Speter                                                const apr_array_header_t *params,
866251881Speter                                                ra_svn_driver_state_t *ds)
867251881Speter{
868251881Speter  if (!ds->for_replay)
869251881Speter    return svn_error_createf
870251881Speter      (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
871251881Speter       _("Command 'finish-replay' invalid outside of replays"));
872251881Speter  ds->done = TRUE;
873251881Speter  if (ds->aborted)
874251881Speter    *ds->aborted = FALSE;
875251881Speter  return SVN_NO_ERROR;
876251881Speter}
877251881Speter
878251881Speterstatic const struct {
879251881Speter  const char *cmd;
880251881Speter  svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
881251881Speter                          const apr_array_header_t *params,
882251881Speter                          ra_svn_driver_state_t *ds);
883251881Speter} ra_svn_edit_cmds[] = {
884251881Speter  { "change-file-prop", ra_svn_handle_change_file_prop },
885251881Speter  { "open-file",        ra_svn_handle_open_file },
886251881Speter  { "apply-textdelta",  ra_svn_handle_apply_textdelta },
887251881Speter  { "textdelta-chunk",  ra_svn_handle_textdelta_chunk },
888251881Speter  { "close-file",       ra_svn_handle_close_file },
889251881Speter  { "add-dir",          ra_svn_handle_add_dir },
890251881Speter  { "open-dir",         ra_svn_handle_open_dir },
891251881Speter  { "change-dir-prop",  ra_svn_handle_change_dir_prop },
892251881Speter  { "delete-entry",     ra_svn_handle_delete_entry },
893251881Speter  { "close-dir",        ra_svn_handle_close_dir },
894251881Speter  { "absent-dir",       ra_svn_handle_absent_dir },
895251881Speter  { "add-file",         ra_svn_handle_add_file },
896251881Speter  { "textdelta-end",    ra_svn_handle_textdelta_end },
897251881Speter  { "absent-file",      ra_svn_handle_absent_file },
898251881Speter  { "abort-edit",       ra_svn_handle_abort_edit },
899251881Speter  { "finish-replay",    ra_svn_handle_finish_replay },
900251881Speter  { "target-rev",       ra_svn_handle_target_rev },
901251881Speter  { "open-root",        ra_svn_handle_open_root },
902251881Speter  { "close-edit",       ra_svn_handle_close_edit },
903251881Speter  { NULL }
904251881Speter};
905251881Speter
906251881Speterstatic svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
907251881Speter                                  void *baton)
908251881Speter{
909251881Speter  ra_svn_driver_state_t *ds = baton;
910251881Speter  const char *cmd;
911251881Speter  apr_array_header_t *params;
912251881Speter
913251881Speter  /* We blocked trying to send an error.  Read and discard an editing
914251881Speter   * command in order to avoid deadlock. */
915251881Speter  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, &params));
916251881Speter  if (strcmp(cmd, "abort-edit") == 0)
917251881Speter    {
918251881Speter      ds->done = TRUE;
919251881Speter      svn_ra_svn__set_block_handler(conn, NULL, NULL);
920251881Speter    }
921251881Speter  return SVN_NO_ERROR;
922251881Speter}
923251881Speter
924251881Spetersvn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
925251881Speter                                      apr_pool_t *pool,
926251881Speter                                      const svn_delta_editor_t *editor,
927251881Speter                                      void *edit_baton,
928251881Speter                                      svn_boolean_t *aborted,
929251881Speter                                      svn_boolean_t for_replay)
930251881Speter{
931251881Speter  ra_svn_driver_state_t state;
932251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
933251881Speter  const char *cmd;
934251881Speter  int i;
935251881Speter  svn_error_t *err, *write_err;
936251881Speter  apr_array_header_t *params;
937251881Speter
938251881Speter  state.editor = editor;
939251881Speter  state.edit_baton = edit_baton;
940251881Speter  state.tokens = apr_hash_make(pool);
941251881Speter  state.aborted = aborted;
942251881Speter  state.done = FALSE;
943251881Speter  state.pool = pool;
944251881Speter  state.file_pool = svn_pool_create(pool);
945251881Speter  state.file_refs = 0;
946251881Speter  state.for_replay = for_replay;
947251881Speter
948251881Speter  while (!state.done)
949251881Speter    {
950251881Speter      svn_pool_clear(subpool);
951251881Speter      if (editor)
952251881Speter        {
953251881Speter          SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params));
954251881Speter          for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
955251881Speter              if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
956251881Speter                break;
957251881Speter
958251881Speter          if (ra_svn_edit_cmds[i].cmd)
959251881Speter            err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
960251881Speter          else if (strcmp(cmd, "failure") == 0)
961251881Speter            {
962251881Speter              /* While not really an editor command this can occur when
963251881Speter                reporter->finish_report() fails before the first editor
964251881Speter                command */
965251881Speter              if (aborted)
966251881Speter                *aborted = TRUE;
967251881Speter              err = svn_ra_svn__handle_failure_status(params, pool);
968251881Speter              return svn_error_compose_create(
969251881Speter                                err,
970251881Speter                                editor->abort_edit(edit_baton, subpool));
971251881Speter            }
972251881Speter          else
973251881Speter            {
974251881Speter              err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
975251881Speter                                      _("Unknown editor command '%s'"), cmd);
976251881Speter              err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
977251881Speter            }
978251881Speter        }
979251881Speter      else
980251881Speter        {
981251881Speter          const char* command = NULL;
982251881Speter          SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
983251881Speter          if (strcmp(command, "close-edit") == 0)
984251881Speter            {
985251881Speter              state.done = TRUE;
986251881Speter              if (aborted)
987251881Speter                *aborted = FALSE;
988251881Speter              err = svn_ra_svn__write_cmd_response(conn, pool, "");
989251881Speter            }
990251881Speter          else
991251881Speter            err = NULL;
992251881Speter        }
993251881Speter
994251881Speter      if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
995251881Speter        {
996251881Speter          if (aborted)
997251881Speter            *aborted = TRUE;
998251881Speter          if (!state.done)
999251881Speter            {
1000251881Speter              /* Abort the edit and use non-blocking I/O to write the error. */
1001251881Speter              if (editor)
1002299742Sdim                {
1003299742Sdim                  err = svn_error_compose_create(
1004299742Sdim                          err,
1005299742Sdim                          svn_error_trace(editor->abort_edit(edit_baton,
1006299742Sdim                                                             subpool)));
1007299742Sdim                }
1008251881Speter              svn_ra_svn__set_block_handler(conn, blocked_write, &state);
1009251881Speter            }
1010251881Speter          write_err = svn_ra_svn__write_cmd_failure(
1011251881Speter                          conn, subpool,
1012251881Speter                          svn_ra_svn__locate_real_error_child(err));
1013251881Speter          if (!write_err)
1014251881Speter            write_err = svn_ra_svn__flush(conn, subpool);
1015251881Speter          svn_ra_svn__set_block_handler(conn, NULL, NULL);
1016299742Sdim          svn_error_clear(err); /* We just sent this error */
1017251881Speter          SVN_ERR(write_err);
1018251881Speter          break;
1019251881Speter        }
1020251881Speter      SVN_ERR(err);
1021251881Speter    }
1022251881Speter
1023251881Speter  /* Read and discard editing commands until the edit is complete.
1024251881Speter     Hopefully, the other side will call another editor command, run
1025251881Speter     check_for_error, notice the error, write "abort-edit" at us, and
1026251881Speter     throw the error up a few levels on its side (possibly even
1027251881Speter     tossing it right back at us, which is why we can return
1028251881Speter     SVN_NO_ERROR below).
1029251881Speter
1030251881Speter     However, if the other side is way ahead of us, it might
1031251881Speter     completely finish the edit (or sequence of edit/revprops, for
1032251881Speter     "replay-range") before we send over our "failure".  So we should
1033251881Speter     also stop if we see "success".  (Then the other side will try to
1034251881Speter     interpret our "failure" as a command, which will itself fail...
1035251881Speter     The net effect is that whatever error we wrote to the other side
1036251881Speter     will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.)
1037251881Speter   */
1038251881Speter  while (!state.done)
1039251881Speter    {
1040251881Speter      svn_pool_clear(subpool);
1041251881Speter      err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params);
1042251881Speter      if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
1043251881Speter        {
1044251881Speter          /* Other side disconnected; that's no error. */
1045251881Speter          svn_error_clear(err);
1046251881Speter          svn_pool_destroy(subpool);
1047251881Speter          return SVN_NO_ERROR;
1048251881Speter        }
1049251881Speter      svn_error_clear(err);
1050251881Speter      if (strcmp(cmd, "abort-edit") == 0
1051251881Speter          || strcmp(cmd, "success") == 0)
1052251881Speter        state.done = TRUE;
1053251881Speter    }
1054251881Speter
1055251881Speter  svn_pool_destroy(subpool);
1056251881Speter  return SVN_NO_ERROR;
1057251881Speter}
1058251881Speter
1059251881Spetersvn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1060251881Speter                                     const svn_delta_editor_t *editor,
1061251881Speter                                     void *edit_baton,
1062251881Speter                                     svn_boolean_t *aborted)
1063251881Speter{
1064251881Speter  return svn_ra_svn_drive_editor2(conn,
1065251881Speter                                  pool,
1066251881Speter                                  editor,
1067251881Speter                                  edit_baton,
1068251881Speter                                  aborted,
1069251881Speter                                  FALSE);
1070251881Speter}
1071