1 /*
2  * copy_foreign.c:  copy from other repository support.
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 /*** Includes. ***/
27 
28 #include <string.h>
29 #include "svn_hash.h"
30 #include "svn_client.h"
31 #include "svn_delta.h"
32 #include "svn_dirent_uri.h"
33 #include "svn_error.h"
34 #include "svn_error_codes.h"
35 #include "svn_path.h"
36 #include "svn_pools.h"
37 #include "svn_props.h"
38 #include "svn_ra.h"
39 #include "svn_wc.h"
40 
41 #include <apr_md5.h>
42 
43 #include "client.h"
44 #include "private/svn_subr_private.h"
45 #include "private/svn_wc_private.h"
46 #include "svn_private_config.h"
47 
48 struct edit_baton_t
49 {
50   apr_pool_t *pool;
51   const char *anchor_abspath;
52 
53   svn_wc_context_t *wc_ctx;
54   svn_wc_notify_func2_t notify_func;
55   void *notify_baton;
56 };
57 
58 struct dir_baton_t
59 {
60   apr_pool_t *pool;
61 
62   struct dir_baton_t *pb;
63   struct edit_baton_t *eb;
64 
65   const char *local_abspath;
66 
67   svn_boolean_t created;
68   apr_hash_t *properties;
69 
70   int users;
71 };
72 
73 /* svn_delta_editor_t function */
74 static svn_error_t *
edit_open(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * result_pool,void ** root_baton)75 edit_open(void *edit_baton,
76           svn_revnum_t base_revision,
77           apr_pool_t *result_pool,
78           void **root_baton)
79 {
80   struct edit_baton_t *eb = edit_baton;
81   apr_pool_t *dir_pool = svn_pool_create(eb->pool);
82   struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
83 
84   db->pool = dir_pool;
85   db->eb = eb;
86   db->users = 1;
87   db->local_abspath = eb->anchor_abspath;
88 
89   SVN_ERR(svn_io_make_dir_recursively(eb->anchor_abspath, dir_pool));
90 
91   *root_baton = db;
92 
93   return SVN_NO_ERROR;
94 }
95 
96 /* svn_delta_editor_t function */
97 static svn_error_t *
edit_close(void * edit_baton,apr_pool_t * scratch_pool)98 edit_close(void *edit_baton,
99            apr_pool_t *scratch_pool)
100 {
101   return SVN_NO_ERROR;
102 }
103 
104 static svn_error_t *
dir_add(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** child_baton)105 dir_add(const char *path,
106         void *parent_baton,
107         const char *copyfrom_path,
108         svn_revnum_t copyfrom_revision,
109         apr_pool_t *result_pool,
110         void **child_baton)
111 {
112   struct dir_baton_t *pb = parent_baton;
113   struct edit_baton_t *eb = pb->eb;
114   apr_pool_t *dir_pool = svn_pool_create(pb->pool);
115   struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
116   svn_boolean_t under_root;
117 
118   pb->users++;
119 
120   db->pb = pb;
121   db->eb = pb->eb;
122   db->pool = dir_pool;
123   db->users = 1;
124 
125   SVN_ERR(svn_dirent_is_under_root(&under_root, &db->local_abspath,
126                                    eb->anchor_abspath, path, db->pool));
127   if (! under_root)
128     {
129       return svn_error_createf(
130                     SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
131                     _("Path '%s' is not in the working copy"),
132                     svn_dirent_local_style(path, db->pool));
133     }
134 
135   SVN_ERR(svn_io_make_dir_recursively(db->local_abspath, db->pool));
136 
137   *child_baton = db;
138   return SVN_NO_ERROR;
139 }
140 
141 static svn_error_t *
dir_change_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)142 dir_change_prop(void *dir_baton,
143                 const char *name,
144                 const svn_string_t *value,
145                 apr_pool_t *scratch_pool)
146 {
147   struct dir_baton_t *db = dir_baton;
148   struct edit_baton_t *eb = db->eb;
149   svn_prop_kind_t prop_kind;
150 
151   prop_kind = svn_property_kind2(name);
152 
153   if (prop_kind != svn_prop_regular_kind
154       || ! strcmp(name, SVN_PROP_MERGEINFO))
155     {
156       /* We can't handle DAV, ENTRY and merge specific props here */
157       return SVN_NO_ERROR;
158     }
159 
160   if (! db->created)
161     {
162       /* We can still store them in the hash for immediate addition
163          with the svn_wc_add_from_disk3() call */
164       if (! db->properties)
165         db->properties = apr_hash_make(db->pool);
166 
167       if (value != NULL)
168         svn_hash_sets(db->properties, apr_pstrdup(db->pool, name),
169                       svn_string_dup(value, db->pool));
170     }
171   else
172     {
173       /* We have already notified for this directory, so don't do that again */
174       SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
175                                svn_depth_empty, FALSE, NULL,
176                                NULL, NULL, /* Cancellation */
177                                NULL, NULL, /* Notification */
178                                scratch_pool));
179     }
180 
181   return SVN_NO_ERROR;
182 }
183 
184 /* Releases the directory baton if there are no more users */
185 static svn_error_t *
maybe_done(struct dir_baton_t * db)186 maybe_done(struct dir_baton_t *db)
187 {
188   db->users--;
189 
190   if (db->users == 0)
191     {
192       struct dir_baton_t *pb = db->pb;
193 
194       svn_pool_clear(db->pool);
195 
196       if (pb)
197         SVN_ERR(maybe_done(pb));
198     }
199 
200   return SVN_NO_ERROR;
201 }
202 
203 static svn_error_t *
ensure_added(struct dir_baton_t * db,apr_pool_t * scratch_pool)204 ensure_added(struct dir_baton_t *db,
205              apr_pool_t *scratch_pool)
206 {
207   if (db->created)
208     return SVN_NO_ERROR;
209 
210   if (db->pb)
211     SVN_ERR(ensure_added(db->pb, scratch_pool));
212 
213   db->created = TRUE;
214 
215   /* Add the directory with all the already collected properties */
216   SVN_ERR(svn_wc_add_from_disk3(db->eb->wc_ctx,
217                                 db->local_abspath,
218                                 db->properties,
219                                 TRUE /* skip checks */,
220                                 db->eb->notify_func,
221                                 db->eb->notify_baton,
222                                 scratch_pool));
223 
224   return SVN_NO_ERROR;
225 }
226 
227 static svn_error_t *
dir_close(void * dir_baton,apr_pool_t * scratch_pool)228 dir_close(void *dir_baton,
229           apr_pool_t *scratch_pool)
230 {
231   struct dir_baton_t *db = dir_baton;
232   /*struct edit_baton_t *eb = db->eb;*/
233 
234   SVN_ERR(ensure_added(db, scratch_pool));
235 
236   SVN_ERR(maybe_done(db));
237 
238   return SVN_NO_ERROR;
239 }
240 
241 struct file_baton_t
242 {
243   apr_pool_t *pool;
244 
245   struct dir_baton_t *pb;
246   struct edit_baton_t *eb;
247 
248   const char *local_abspath;
249   apr_hash_t *properties;
250 
251   svn_boolean_t writing;
252   unsigned char digest[APR_MD5_DIGESTSIZE];
253 
254   const char *tmp_path;
255 };
256 
257 static svn_error_t *
file_add(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * result_pool,void ** file_baton)258 file_add(const char *path,
259          void *parent_baton,
260          const char *copyfrom_path,
261          svn_revnum_t copyfrom_revision,
262          apr_pool_t *result_pool,
263          void **file_baton)
264 {
265   struct dir_baton_t *pb = parent_baton;
266   struct edit_baton_t *eb = pb->eb;
267   apr_pool_t *file_pool = svn_pool_create(pb->pool);
268   struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
269   svn_boolean_t under_root;
270 
271   pb->users++;
272 
273   fb->pool = file_pool;
274   fb->eb = eb;
275   fb->pb = pb;
276 
277   SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath,
278                                    eb->anchor_abspath, path, fb->pool));
279   if (! under_root)
280     {
281       return svn_error_createf(
282                     SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
283                     _("Path '%s' is not in the working copy"),
284                     svn_dirent_local_style(path, fb->pool));
285     }
286 
287   *file_baton = fb;
288   return SVN_NO_ERROR;
289 }
290 
291 static svn_error_t *
file_change_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)292 file_change_prop(void *file_baton,
293                  const char *name,
294                  const svn_string_t *value,
295                  apr_pool_t *scratch_pool)
296 {
297   struct file_baton_t *fb = file_baton;
298   svn_prop_kind_t prop_kind;
299 
300   prop_kind = svn_property_kind2(name);
301 
302   if (prop_kind != svn_prop_regular_kind
303       || ! strcmp(name, SVN_PROP_MERGEINFO))
304     {
305       /* We can't handle DAV, ENTRY and merge specific props here */
306       return SVN_NO_ERROR;
307     }
308 
309   /* We store all properties in the hash for immediate addition
310       with the svn_wc_add_from_disk3() call */
311   if (! fb->properties)
312     fb->properties = apr_hash_make(fb->pool);
313 
314   if (value != NULL)
315     svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name),
316                   svn_string_dup(value, fb->pool));
317 
318   return SVN_NO_ERROR;
319 }
320 
321 static svn_error_t *
file_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * result_pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)322 file_textdelta(void *file_baton,
323                const char *base_checksum,
324                apr_pool_t *result_pool,
325                svn_txdelta_window_handler_t *handler,
326                void **handler_baton)
327 {
328   struct file_baton_t *fb = file_baton;
329   svn_stream_t *target;
330 
331   SVN_ERR_ASSERT(! fb->writing);
332 
333   SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool,
334                                    fb->pool));
335 
336   fb->writing = TRUE;
337   svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */,
338                     target,
339                     fb->digest,
340                     fb->local_abspath,
341                     fb->pool,
342                     /* Provide the handler directly */
343                     handler, handler_baton);
344 
345   return SVN_NO_ERROR;
346 }
347 
348 static svn_error_t *
file_close(void * file_baton,const char * text_checksum,apr_pool_t * scratch_pool)349 file_close(void *file_baton,
350            const char *text_checksum,
351            apr_pool_t *scratch_pool)
352 {
353   struct file_baton_t *fb = file_baton;
354   struct edit_baton_t *eb = fb->eb;
355   struct dir_baton_t *pb = fb->pb;
356 
357   SVN_ERR(ensure_added(pb, fb->pool));
358 
359   if (text_checksum)
360     {
361       svn_checksum_t *expected_checksum;
362       svn_checksum_t *actual_checksum;
363 
364       SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
365                                      text_checksum, fb->pool));
366       actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
367 
368       if (! svn_checksum_match(expected_checksum, actual_checksum))
369         return svn_error_trace(
370                     svn_checksum_mismatch_err(expected_checksum,
371                                               actual_checksum,
372                                               fb->pool,
373                                          _("Checksum mismatch for '%s'"),
374                                               svn_dirent_local_style(
375                                                     fb->local_abspath,
376                                                     fb->pool)));
377     }
378 
379   SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, fb->local_abspath, fb->properties,
380                                 TRUE /* skip checks */,
381                                 eb->notify_func, eb->notify_baton,
382                                 fb->pool));
383 
384   svn_pool_destroy(fb->pool);
385   SVN_ERR(maybe_done(pb));
386 
387   return SVN_NO_ERROR;
388 }
389 
390 static svn_error_t *
copy_foreign_dir(svn_ra_session_t * ra_session,svn_client__pathrev_t * location,svn_wc_context_t * wc_ctx,const char * dst_abspath,svn_depth_t depth,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)391 copy_foreign_dir(svn_ra_session_t *ra_session,
392                  svn_client__pathrev_t *location,
393                  svn_wc_context_t *wc_ctx,
394                  const char *dst_abspath,
395                  svn_depth_t depth,
396                  svn_wc_notify_func2_t notify_func,
397                  void *notify_baton,
398                  svn_cancel_func_t cancel_func,
399                  void *cancel_baton,
400                  apr_pool_t *scratch_pool)
401 {
402   struct edit_baton_t eb;
403   svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool);
404   const svn_delta_editor_t *wrapped_editor;
405   void *wrapped_baton;
406   const svn_ra_reporter3_t *reporter;
407   void *reporter_baton;
408 
409   eb.pool = scratch_pool;
410   eb.anchor_abspath = dst_abspath;
411 
412   eb.wc_ctx = wc_ctx;
413   eb.notify_func = notify_func;
414   eb.notify_baton  = notify_baton;
415 
416   editor->open_root = edit_open;
417   editor->close_edit = edit_close;
418 
419   editor->add_directory = dir_add;
420   editor->change_dir_prop = dir_change_prop;
421   editor->close_directory = dir_close;
422 
423   editor->add_file = file_add;
424   editor->change_file_prop = file_change_prop;
425   editor->apply_textdelta = file_textdelta;
426   editor->close_file = file_close;
427 
428   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
429                                             editor, &eb,
430                                             &wrapped_editor, &wrapped_baton,
431                                             scratch_pool));
432 
433   SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
434                             location->rev, "", svn_depth_infinity,
435                             FALSE, FALSE, wrapped_editor, wrapped_baton,
436                             scratch_pool, scratch_pool));
437 
438   SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth,
439                              TRUE /* incomplete */,
440                              NULL, scratch_pool));
441 
442   SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
443 
444   return SVN_NO_ERROR;
445 }
446 
447 
448 svn_error_t *
svn_client__copy_foreign(const char * url,const char * dst_abspath,svn_opt_revision_t * peg_revision,svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t make_parents,svn_boolean_t already_locked,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)449 svn_client__copy_foreign(const char *url,
450                          const char *dst_abspath,
451                          svn_opt_revision_t *peg_revision,
452                          svn_opt_revision_t *revision,
453                          svn_depth_t depth,
454                          svn_boolean_t make_parents,
455                          svn_boolean_t already_locked,
456                          svn_client_ctx_t *ctx,
457                          apr_pool_t *scratch_pool)
458 {
459   svn_ra_session_t *ra_session;
460   svn_client__pathrev_t *loc;
461   svn_node_kind_t kind;
462   svn_node_kind_t wc_kind;
463   const char *dir_abspath;
464 
465   SVN_ERR_ASSERT(svn_path_is_url(url));
466   SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
467 
468   /* Do we need to validate/update revisions? */
469 
470   SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
471                                             url, NULL,
472                                             peg_revision,
473                                             revision, ctx,
474                                             scratch_pool));
475 
476   SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool));
477 
478   if (kind != svn_node_file && kind != svn_node_dir)
479     return svn_error_createf(
480                 SVN_ERR_ILLEGAL_TARGET, NULL,
481                 _("'%s' is not a valid location inside a repository"),
482                 url);
483 
484   SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE,
485                             scratch_pool));
486 
487   if (wc_kind != svn_node_none)
488     {
489       return svn_error_createf(
490                 SVN_ERR_ENTRY_EXISTS, NULL,
491                 _("'%s' is already under version control"),
492                 svn_dirent_local_style(dst_abspath, scratch_pool));
493     }
494 
495   dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
496   SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
497                             FALSE, FALSE, scratch_pool));
498 
499   if (wc_kind == svn_node_none)
500     {
501       if (make_parents)
502         SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx,
503                                                scratch_pool));
504 
505       SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
506                                 FALSE, FALSE, scratch_pool));
507     }
508 
509   if (wc_kind != svn_node_dir)
510     return svn_error_createf(
511                 SVN_ERR_ENTRY_NOT_FOUND, NULL,
512                 _("Can't add '%s', because no parent directory is found"),
513                 svn_dirent_local_style(dst_abspath, scratch_pool));
514 
515 
516   if (kind == svn_node_file)
517     {
518       svn_stream_t *target;
519       apr_hash_t *props;
520       apr_hash_index_t *hi;
521       SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool,
522                                        scratch_pool));
523 
524       SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props,
525                               scratch_pool));
526 
527       if (props != NULL)
528         for (hi = apr_hash_first(scratch_pool, props); hi;
529              hi = apr_hash_next(hi))
530           {
531             const char *name = apr_hash_this_key(hi);
532 
533             if (svn_property_kind2(name) != svn_prop_regular_kind
534                 || ! strcmp(name, SVN_PROP_MERGEINFO))
535               {
536                 /* We can't handle DAV, ENTRY and merge specific props here */
537                 svn_hash_sets(props, name, NULL);
538               }
539           }
540 
541       if (!already_locked)
542         SVN_WC__CALL_WITH_WRITE_LOCK(
543               svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
544                                     TRUE /* skip checks */,
545                                     ctx->notify_func2, ctx->notify_baton2,
546                                     scratch_pool),
547               ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
548       else
549         SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
550                                       TRUE /* skip checks */,
551                                       ctx->notify_func2, ctx->notify_baton2,
552                                       scratch_pool));
553     }
554   else
555     {
556       if (!already_locked)
557         SVN_WC__CALL_WITH_WRITE_LOCK(
558               copy_foreign_dir(ra_session, loc,
559                                ctx->wc_ctx, dst_abspath,
560                                depth,
561                                ctx->notify_func2, ctx->notify_baton2,
562                                ctx->cancel_func, ctx->cancel_baton,
563                                scratch_pool),
564               ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
565       else
566         SVN_ERR(copy_foreign_dir(ra_session, loc,
567                                  ctx->wc_ctx, dst_abspath,
568                                  depth,
569                                  ctx->notify_func2, ctx->notify_baton2,
570                                  ctx->cancel_func, ctx->cancel_baton,
571                                  scratch_pool));
572     }
573 
574   return SVN_NO_ERROR;
575 }
576