1 /*
2  * ra_loader.c:  logic for loading different RA library implementations
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 #define APR_WANT_STRFUNC
28 #include <apr_want.h>
29 
30 #include <apr.h>
31 #include <apr_strings.h>
32 #include <apr_pools.h>
33 #include <apr_hash.h>
34 #include <apr_uri.h>
35 
36 #include "svn_hash.h"
37 #include "svn_types.h"
38 #include "svn_error.h"
39 #include "svn_delta.h"
40 #include "svn_ra.h"
41 #include "svn_dirent_uri.h"
42 #include "svn_props.h"
43 #include "svn_iter.h"
44 
45 #include "private/svn_branch_compat.h"
46 #include "private/svn_branch_repos.h"
47 #include "private/svn_ra_private.h"
48 #include "private/svn_delta_private.h"
49 #include "private/svn_string_private.h"
50 #include "svnmover.h"
51 #include "svn_private_config.h"
52 
53 
54 /* Read the branching info string VALUE belonging to revision REVISION.
55  */
56 static svn_error_t *
read_rev_prop(svn_string_t ** value,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t revision,apr_pool_t * result_pool)57 read_rev_prop(svn_string_t **value,
58               svn_ra_session_t *ra_session,
59               const char *branch_info_dir,
60               svn_revnum_t revision,
61               apr_pool_t *result_pool)
62 {
63   apr_pool_t *scratch_pool = result_pool;
64 
65   if (branch_info_dir)
66     {
67       const char *file_path;
68       svn_stream_t *stream;
69       svn_error_t *err;
70 
71       file_path = svn_dirent_join(branch_info_dir,
72                                   apr_psprintf(scratch_pool, "branch-info-r%ld",
73                                                revision), scratch_pool);
74       err = svn_stream_open_readonly(&stream, file_path, scratch_pool, scratch_pool);
75       if (err)
76         {
77           svn_error_clear(err);
78           *value = NULL;
79           return SVN_NO_ERROR;
80         }
81       SVN_ERR(err);
82       SVN_ERR(svn_string_from_stream2(value, stream, 0, result_pool));
83     }
84   else
85     {
86       SVN_ERR(svn_ra_rev_prop(ra_session, revision, "svn-br-info", value,
87                               result_pool));
88     }
89   return SVN_NO_ERROR;
90 }
91 
92 /* Store the branching info string VALUE belonging to revision REVISION.
93  */
94 static svn_error_t *
write_rev_prop(svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t revision,svn_string_t * value,apr_pool_t * scratch_pool)95 write_rev_prop(svn_ra_session_t *ra_session,
96                const char *branch_info_dir,
97                svn_revnum_t revision,
98                svn_string_t *value,
99                apr_pool_t *scratch_pool)
100 {
101   if (branch_info_dir)
102     {
103       const char *file_path;
104       svn_error_t *err;
105 
106      file_path = svn_dirent_join(branch_info_dir,
107                                   apr_psprintf(scratch_pool, "branch-info-r%ld",
108                                                revision), scratch_pool);
109       err = svn_io_file_create(file_path, value->data, scratch_pool);
110       if (err)
111         {
112           svn_error_clear(err);
113           SVN_ERR(svn_io_dir_make(branch_info_dir, APR_FPROT_OS_DEFAULT,
114                                   scratch_pool));
115           err = svn_io_file_create(file_path, value->data, scratch_pool);
116         }
117       SVN_ERR(err);
118     }
119   else
120     {
121       SVN_ERR(svn_ra_change_rev_prop2(ra_session, revision, "svn-br-info",
122                                       NULL, value, scratch_pool));
123     }
124 
125   return SVN_NO_ERROR;
126 }
127 
128 /* Create a new revision-root object and read the move-tracking /
129  * branch-tracking metadata from the repository into it.
130  */
131 static svn_error_t *
branch_revision_fetch_info(svn_branch__txn_t ** txn_p,svn_branch__repos_t * repos,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)132 branch_revision_fetch_info(svn_branch__txn_t **txn_p,
133                            svn_branch__repos_t *repos,
134                            svn_ra_session_t *ra_session,
135                            const char *branch_info_dir,
136                            svn_revnum_t revision,
137                            apr_pool_t *result_pool,
138                            apr_pool_t *scratch_pool)
139 {
140   svn_string_t *value;
141   svn_stream_t *stream;
142   svn_branch__txn_t *txn;
143 
144   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
145 
146   /* Read initial state from repository */
147   SVN_ERR(read_rev_prop(&value, ra_session, branch_info_dir, revision,
148                         scratch_pool));
149   if (! value && revision == 0)
150     {
151       value = svn_branch__get_default_r0_metadata(scratch_pool);
152       /*SVN_DBG(("fetch_per_revision_info(r%ld): LOADED DEFAULT INFO:\n%s",
153                revision, value->data));*/
154       SVN_ERR(write_rev_prop(ra_session, branch_info_dir, revision, value,
155                              scratch_pool));
156     }
157   else if (! value)
158     {
159       return svn_error_createf(SVN_BRANCH__ERR, NULL,
160                                _("Move-tracking metadata not found in r%ld "
161                                  "in this repository. Run svnmover on an "
162                                  "empty repository to initialize the "
163                                  "metadata"), revision);
164     }
165   stream = svn_stream_from_string(value, scratch_pool);
166 
167   SVN_ERR(svn_branch__txn_parse(&txn, repos, stream,
168                                 result_pool, scratch_pool));
169 
170   /* Self-test: writing out the info should produce exactly the same string. */
171   {
172     svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
173 
174     stream = svn_stream_from_stringbuf(buf, scratch_pool);
175     SVN_ERR(svn_branch__txn_serialize(txn, stream, scratch_pool));
176     SVN_ERR(svn_stream_close(stream));
177 
178     SVN_ERR_ASSERT(svn_string_compare(value,
179                                       svn_stringbuf__morph_into_string(buf)));
180   }
181 
182   *txn_p = txn;
183   return SVN_NO_ERROR;
184 }
185 
186 /* Fetch all element payloads in TXN.
187  */
188 static svn_error_t *
txn_fetch_payloads(svn_branch__txn_t * txn,svn_branch__compat_fetch_func_t fetch_func,void * fetch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)189 txn_fetch_payloads(svn_branch__txn_t *txn,
190                    svn_branch__compat_fetch_func_t fetch_func,
191                    void *fetch_baton,
192                    apr_pool_t *result_pool,
193                    apr_pool_t *scratch_pool)
194 {
195   apr_array_header_t *branches = svn_branch__txn_get_branches(txn, scratch_pool);
196   int i;
197 
198   /* Read payload of each element.
199      (In a real implementation, of course, we'd delay this until demanded.) */
200   for (i = 0; i < branches->nelts; i++)
201     {
202       svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
203       svn_element__tree_t *element_tree;
204       apr_hash_index_t *hi;
205 
206       SVN_ERR(svn_branch__state_get_elements(branch, &element_tree,
207                                              scratch_pool));
208       for (hi = apr_hash_first(scratch_pool, element_tree->e_map);
209            hi; hi = apr_hash_next(hi))
210         {
211           int eid = svn_eid__hash_this_key(hi);
212           svn_element__content_t *element /*= apr_hash_this_val(hi)*/;
213 
214           SVN_ERR(svn_branch__state_get_element(branch, &element,
215                                                 eid, scratch_pool));
216           if (! element->payload->is_subbranch_root)
217             {
218               SVN_ERR(svn_branch__compat_fetch(&element->payload,
219                                                txn,
220                                                element->payload->branch_ref,
221                                                fetch_func, fetch_baton,
222                                                result_pool, scratch_pool));
223             }
224         }
225     }
226 
227   return SVN_NO_ERROR;
228 }
229 
230 /* Create a new repository object and read the move-tracking /
231  * branch-tracking metadata from the repository into it.
232  */
233 static svn_error_t *
branch_repos_fetch_info(svn_branch__repos_t ** repos_p,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_branch__compat_fetch_func_t fetch_func,void * fetch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)234 branch_repos_fetch_info(svn_branch__repos_t **repos_p,
235                         svn_ra_session_t *ra_session,
236                         const char *branch_info_dir,
237                         svn_branch__compat_fetch_func_t fetch_func,
238                         void *fetch_baton,
239                         apr_pool_t *result_pool,
240                         apr_pool_t *scratch_pool)
241 {
242   svn_branch__repos_t *repos
243     = svn_branch__repos_create(result_pool);
244   svn_revnum_t base_revision;
245   svn_revnum_t r;
246 
247   SVN_ERR(svn_ra_get_latest_revnum(ra_session, &base_revision, scratch_pool));
248 
249   for (r = 0; r <= base_revision; r++)
250     {
251       svn_branch__txn_t *txn;
252 
253       SVN_ERR(branch_revision_fetch_info(&txn,
254                                          repos, ra_session, branch_info_dir,
255                                          r,
256                                          result_pool, scratch_pool));
257       SVN_ERR(svn_branch__repos_add_revision(repos, txn));
258       SVN_ERR(txn_fetch_payloads(txn, fetch_func, fetch_baton,
259                                  result_pool, scratch_pool));
260       }
261 
262   *repos_p = repos;
263   return SVN_NO_ERROR;
264 }
265 
266 /* Return a mutable state based on revision BASE_REVISION in REPOS.
267  */
268 static svn_error_t *
branch_get_mutable_state(svn_branch__txn_t ** txn_p,svn_branch__repos_t * repos,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t base_revision,svn_branch__compat_fetch_func_t fetch_func,void * fetch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)269 branch_get_mutable_state(svn_branch__txn_t **txn_p,
270                          svn_branch__repos_t *repos,
271                          svn_ra_session_t *ra_session,
272                          const char *branch_info_dir,
273                          svn_revnum_t base_revision,
274                          svn_branch__compat_fetch_func_t fetch_func,
275                          void *fetch_baton,
276                          apr_pool_t *result_pool,
277                          apr_pool_t *scratch_pool)
278 {
279   svn_branch__txn_t *txn;
280   apr_array_header_t *branches;
281   int i;
282 
283   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(base_revision));
284 
285   SVN_ERR(branch_revision_fetch_info(&txn,
286                                      repos, ra_session, branch_info_dir,
287                                      base_revision,
288                                      result_pool, scratch_pool));
289   SVN_ERR_ASSERT(txn->rev == base_revision);
290   SVN_ERR(txn_fetch_payloads(txn, fetch_func, fetch_baton,
291                              result_pool, scratch_pool));
292 
293   /* Update all the 'predecessor' info to point to the BASE_REVISION instead
294      of to that revision's predecessor. */
295   txn->base_rev = base_revision;
296   txn->rev = SVN_INVALID_REVNUM;
297 
298   branches = svn_branch__txn_get_branches(txn, scratch_pool);
299   for (i = 0; i < branches->nelts; i++)
300     {
301       svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);
302       svn_branch__history_t *history
303         = svn_branch__history_create_empty(result_pool);
304 
305       /* Set each branch's parent to the branch in the base rev */
306       svn_branch__rev_bid_t *parent
307         = svn_branch__rev_bid_create(base_revision,
308                                      svn_branch__get_id(b, scratch_pool),
309                                      result_pool);
310 
311       svn_hash_sets(history->parents,
312                     apr_pstrdup(result_pool, b->bid), parent);
313       SVN_ERR(svn_branch__state_set_history(b, history, scratch_pool));
314     }
315 
316   *txn_p = txn;
317   return SVN_NO_ERROR;
318 }
319 
320 /* Store the move-tracking / branch-tracking metadata from TXN into the
321  * repository. TXN->rev is the newly committed revision number.
322  */
323 static svn_error_t *
store_repos_info(svn_branch__txn_t * txn,svn_ra_session_t * ra_session,const char * branch_info_dir,apr_pool_t * scratch_pool)324 store_repos_info(svn_branch__txn_t *txn,
325                  svn_ra_session_t *ra_session,
326                  const char *branch_info_dir,
327                  apr_pool_t *scratch_pool)
328 {
329   svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
330   svn_stream_t *stream = svn_stream_from_stringbuf(buf, scratch_pool);
331 
332   SVN_ERR(svn_branch__txn_serialize(txn, stream, scratch_pool));
333 
334   SVN_ERR(svn_stream_close(stream));
335   /*SVN_DBG(("store_repos_info: %s", buf->data));*/
336   SVN_ERR(write_rev_prop(ra_session, branch_info_dir, txn->rev,
337                          svn_stringbuf__morph_into_string(buf), scratch_pool));
338 
339   return SVN_NO_ERROR;
340 }
341 
342 struct ccw_baton
343 {
344   svn_commit_callback2_t original_callback;
345   void *original_baton;
346 
347   svn_ra_session_t *session;
348   const char *branch_info_dir;
349   svn_branch__txn_t *branching_txn;
350 };
351 
352 /* Wrapper which stores the branching/move-tracking info.
353  */
354 static svn_error_t *
commit_callback_wrapper(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)355 commit_callback_wrapper(const svn_commit_info_t *commit_info,
356                         void *baton,
357                         apr_pool_t *pool)
358 {
359   struct ccw_baton *ccwb = baton;
360 
361   /* if this commit used element-branching info, store the new info */
362   if (ccwb->branching_txn)
363     {
364       svn_branch__repos_t *repos = ccwb->branching_txn->repos;
365 
366       ccwb->branching_txn->rev = commit_info->revision;
367       SVN_ERR(svn_branch__repos_add_revision(repos, ccwb->branching_txn));
368       SVN_ERR(store_repos_info(ccwb->branching_txn, ccwb->session,
369                                ccwb->branch_info_dir, pool));
370     }
371 
372   /* call the wrapped callback */
373   if (ccwb->original_callback)
374     {
375       SVN_ERR(ccwb->original_callback(commit_info, ccwb->original_baton, pool));
376     }
377 
378   return SVN_NO_ERROR;
379 }
380 
381 
382 /* Some RA layers do not correctly fill in REPOS_ROOT in commit_info, or
383    they are third-party layers conforming to an older commit_info structure.
384    Interpose a utility function to ensure the field is valid.  */
385 static void
remap_commit_callback(svn_commit_callback2_t * callback,void ** callback_baton,svn_ra_session_t * session,svn_branch__txn_t * branching_txn,const char * branch_info_dir,svn_commit_callback2_t original_callback,void * original_baton,apr_pool_t * result_pool)386 remap_commit_callback(svn_commit_callback2_t *callback,
387                       void **callback_baton,
388                       svn_ra_session_t *session,
389                       svn_branch__txn_t *branching_txn,
390                       const char *branch_info_dir,
391                       svn_commit_callback2_t original_callback,
392                       void *original_baton,
393                       apr_pool_t *result_pool)
394 {
395   /* Allocate this in RESULT_POOL, since the callback will be called
396      long after this function has returned. */
397   struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb));
398 
399   ccwb->session = session;
400   ccwb->branch_info_dir = apr_pstrdup(result_pool, branch_info_dir);
401   ccwb->branching_txn = branching_txn;
402   ccwb->original_callback = original_callback;
403   ccwb->original_baton = original_baton;
404 
405   *callback = commit_callback_wrapper;
406   *callback_baton = ccwb;
407 }
408 
409 
410 /* Ev3 shims */
411 struct fb_baton {
412   /* A session parented at the repository root */
413   svn_ra_session_t *session;
414   const char *repos_root_url;
415   const char *session_path;
416 };
417 
418 /* Fetch kind and/or props and/or text.
419  *
420  * Implements svn_branch__compat_fetch_func_t. */
421 static svn_error_t *
fetch(svn_node_kind_t * kind_p,apr_hash_t ** props_p,svn_stringbuf_t ** file_text,apr_hash_t ** children_names,void * baton,const char * repos_relpath,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)422 fetch(svn_node_kind_t *kind_p,
423       apr_hash_t **props_p,
424       svn_stringbuf_t **file_text,
425       apr_hash_t **children_names,
426       void *baton,
427       const char *repos_relpath,
428       svn_revnum_t revision,
429       apr_pool_t *result_pool,
430       apr_pool_t *scratch_pool)
431 {
432   struct fb_baton *fbb = baton;
433   svn_node_kind_t kind;
434   apr_hash_index_t *hi;
435 
436   if (props_p)
437     *props_p = NULL;
438   if (file_text)
439     *file_text = NULL;
440   if (children_names)
441     *children_names = NULL;
442 
443   SVN_ERR(svn_ra_check_path(fbb->session, repos_relpath, revision,
444                             &kind, scratch_pool));
445   if (kind_p)
446     *kind_p = kind;
447   if (kind == svn_node_file && (props_p || file_text))
448     {
449       svn_stream_t *file_stream = NULL;
450 
451       if (file_text)
452         {
453           *file_text = svn_stringbuf_create_empty(result_pool);
454           file_stream = svn_stream_from_stringbuf(*file_text, scratch_pool);
455         }
456       SVN_ERR(svn_ra_get_file(fbb->session, repos_relpath, revision,
457                               file_stream, NULL, props_p, result_pool));
458       if (file_text)
459         {
460           SVN_ERR(svn_stream_close(file_stream));
461         }
462     }
463   else if (kind == svn_node_dir && (props_p || children_names))
464     {
465       SVN_ERR(svn_ra_get_dir2(fbb->session,
466                               children_names, NULL, props_p,
467                               repos_relpath, revision,
468                               0 /*minimal child info*/,
469                               result_pool));
470     }
471 
472   /* Remove non-regular props */
473   if (props_p && *props_p)
474     {
475       for (hi = apr_hash_first(scratch_pool, *props_p); hi; hi = apr_hash_next(hi))
476         {
477           const char *name = apr_hash_this_key(hi);
478 
479           if (svn_property_kind2(name) != svn_prop_regular_kind)
480             svn_hash_sets(*props_p, name, NULL);
481 
482         }
483     }
484 
485   return SVN_NO_ERROR;
486 }
487 
488 svn_error_t *
svn_ra_load_branching_state(svn_branch__txn_t ** branching_txn_p,svn_branch__compat_fetch_func_t * fetch_func,void ** fetch_baton,svn_ra_session_t * session,const char * branch_info_dir,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)489 svn_ra_load_branching_state(svn_branch__txn_t **branching_txn_p,
490                             svn_branch__compat_fetch_func_t *fetch_func,
491                             void **fetch_baton,
492                             svn_ra_session_t *session,
493                             const char *branch_info_dir,
494                             svn_revnum_t base_revision,
495                             apr_pool_t *result_pool,
496                             apr_pool_t *scratch_pool)
497 {
498   svn_branch__repos_t *repos;
499   const char *repos_root_url, *session_url, *base_relpath;
500   struct fb_baton *fbb = apr_palloc(result_pool, sizeof (*fbb));
501 
502   if (base_revision == SVN_INVALID_REVNUM)
503     {
504       SVN_ERR(svn_ra_get_latest_revnum(session, &base_revision, scratch_pool));
505     }
506 
507   /* fetcher */
508   SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, result_pool));
509   SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
510   base_relpath = svn_uri_skip_ancestor(repos_root_url, session_url, result_pool);
511   SVN_ERR(svn_ra__dup_session(&fbb->session, session, repos_root_url, result_pool, scratch_pool));
512   fbb->session_path = base_relpath;
513   fbb->repos_root_url = repos_root_url;
514   *fetch_func = fetch;
515   *fetch_baton = fbb;
516 
517   SVN_ERR(branch_repos_fetch_info(&repos,
518                                   session, branch_info_dir,
519                                   *fetch_func, *fetch_baton,
520                                   result_pool, scratch_pool));
521   SVN_ERR(branch_get_mutable_state(branching_txn_p,
522                                    repos, session, branch_info_dir,
523                                    base_revision,
524                                    *fetch_func, *fetch_baton,
525                                    result_pool, scratch_pool));
526 
527   return SVN_NO_ERROR;
528 }
529 
530 svn_error_t *
svn_ra_get_commit_txn(svn_ra_session_t * session,svn_branch__txn_t ** edit_txn_p,apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,apr_hash_t * lock_tokens,svn_boolean_t keep_locks,const char * branch_info_dir,apr_pool_t * pool)531 svn_ra_get_commit_txn(svn_ra_session_t *session,
532                       svn_branch__txn_t **edit_txn_p,
533                       apr_hash_t *revprop_table,
534                       svn_commit_callback2_t commit_callback,
535                       void *commit_baton,
536                       apr_hash_t *lock_tokens,
537                       svn_boolean_t keep_locks,
538                       const char *branch_info_dir,
539                       apr_pool_t *pool)
540 {
541   svn_branch__txn_t *branching_txn;
542   svn_branch__compat_fetch_func_t fetch_func;
543   void *fetch_baton;
544   const svn_delta_editor_t *deditor;
545   void *dedit_baton;
546   svn_branch__compat_shim_connector_t *shim_connector;
547 
548   /* load branching info
549    * ### Currently we always start from a single base revision, never from
550    *     a mixed-rev state */
551   SVN_ERR(svn_ra_load_branching_state(&branching_txn, &fetch_func, &fetch_baton,
552                                       session, branch_info_dir,
553                                       SVN_INVALID_REVNUM /*base_revision*/,
554                                       pool, pool));
555 
556   /* arrange for branching info to be stored after commit */
557   remap_commit_callback(&commit_callback, &commit_baton,
558                         session, branching_txn, branch_info_dir,
559                         commit_callback, commit_baton, pool);
560 
561   SVN_ERR(svn_ra_get_commit_editor3(session, &deditor, &dedit_baton,
562                                     revprop_table,
563                                     commit_callback, commit_baton,
564                                     lock_tokens, keep_locks, pool));
565 
566   /* Convert to Ev3 */
567   {
568     const char *repos_root_url;
569 
570     SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, pool));
571 
572     /*SVN_ERR(svn_delta__get_debug_editor(&deditor, &dedit_baton,
573                                           deditor, dedit_baton, "", pool));*/
574     SVN_ERR(svn_branch__compat_txn_from_delta_for_commit(
575                         edit_txn_p,
576                         &shim_connector,
577                         deditor, dedit_baton, branching_txn,
578                         repos_root_url,
579                         fetch_func, fetch_baton,
580                         NULL, NULL /*cancel*/,
581                         pool, pool));
582   }
583 
584   return SVN_NO_ERROR;
585 }
586 
587