1 /* revs-txns.c : operations on revision and transactions
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22 
23 #include <string.h>
24 
25 #include <apr_tables.h>
26 #include <apr_pools.h>
27 
28 #include "svn_pools.h"
29 #include "svn_time.h"
30 #include "svn_fs.h"
31 #include "svn_props.h"
32 #include "svn_hash.h"
33 #include "svn_io.h"
34 
35 #include "fs.h"
36 #include "dag.h"
37 #include "err.h"
38 #include "trail.h"
39 #include "tree.h"
40 #include "revs-txns.h"
41 #include "key-gen.h"
42 #include "id.h"
43 #include "bdb/rev-table.h"
44 #include "bdb/txn-table.h"
45 #include "bdb/copies-table.h"
46 #include "bdb/changes-table.h"
47 #include "../libsvn_fs/fs-loader.h"
48 
49 #include "svn_private_config.h"
50 #include "private/svn_fs_util.h"
51 
52 
53 /*** Helpers ***/
54 
55 /* Set *txn_p to a transaction object allocated in POOL for the
56    transaction in FS whose id is TXN_ID.  If EXPECT_DEAD is set, this
57    transaction must be a dead one, else an error is returned.  If
58    EXPECT_DEAD is not set, the transaction must *not* be a dead one,
59    else an error is returned. */
60 static svn_error_t *
get_txn(transaction_t ** txn_p,svn_fs_t * fs,const char * txn_id,svn_boolean_t expect_dead,trail_t * trail,apr_pool_t * pool)61 get_txn(transaction_t **txn_p,
62         svn_fs_t *fs,
63         const char *txn_id,
64         svn_boolean_t expect_dead,
65         trail_t *trail,
66         apr_pool_t *pool)
67 {
68   transaction_t *txn;
69   SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_id, trail, pool));
70   if (expect_dead && (txn->kind != transaction_kind_dead))
71     return svn_error_createf(SVN_ERR_FS_TRANSACTION_NOT_DEAD, 0,
72                              _("Transaction is not dead: '%s'"), txn_id);
73   if ((! expect_dead) && (txn->kind == transaction_kind_dead))
74     return svn_error_createf(SVN_ERR_FS_TRANSACTION_DEAD, 0,
75                              _("Transaction is dead: '%s'"), txn_id);
76   *txn_p = txn;
77   return SVN_NO_ERROR;
78 }
79 
80 
81 /* This is only for symmetry with the get_txn() helper. */
82 #define put_txn svn_fs_bdb__put_txn
83 
84 
85 
86 /*** Revisions ***/
87 
88 /* Return the committed transaction record *TXN_P and its ID *TXN_ID
89    (as long as those parameters aren't NULL) for the revision REV in
90    FS as part of TRAIL.  */
91 static svn_error_t *
get_rev_txn(transaction_t ** txn_p,const char ** txn_id,svn_fs_t * fs,svn_revnum_t rev,trail_t * trail,apr_pool_t * pool)92 get_rev_txn(transaction_t **txn_p,
93             const char **txn_id,
94             svn_fs_t *fs,
95             svn_revnum_t rev,
96             trail_t *trail,
97             apr_pool_t *pool)
98 {
99   revision_t *revision;
100   transaction_t *txn;
101 
102   SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool));
103   if (revision->txn_id == NULL)
104     return svn_fs_base__err_corrupt_fs_revision(fs, rev);
105 
106   SVN_ERR(get_txn(&txn, fs, revision->txn_id, FALSE, trail, pool));
107   if (txn->revision != rev)
108     return svn_fs_base__err_corrupt_txn(fs, revision->txn_id);
109 
110   if (txn_p)
111     *txn_p = txn;
112   if (txn_id)
113     *txn_id = revision->txn_id;
114   return SVN_NO_ERROR;
115 }
116 
117 
118 svn_error_t *
svn_fs_base__rev_get_root(const svn_fs_id_t ** root_id_p,svn_fs_t * fs,svn_revnum_t rev,trail_t * trail,apr_pool_t * pool)119 svn_fs_base__rev_get_root(const svn_fs_id_t **root_id_p,
120                           svn_fs_t *fs,
121                           svn_revnum_t rev,
122                           trail_t *trail,
123                           apr_pool_t *pool)
124 {
125   transaction_t *txn;
126 
127   SVN_ERR(get_rev_txn(&txn, NULL, fs, rev, trail, pool));
128   if (txn->root_id == NULL)
129     return svn_fs_base__err_corrupt_fs_revision(fs, rev);
130 
131   *root_id_p = txn->root_id;
132   return SVN_NO_ERROR;
133 }
134 
135 
136 svn_error_t *
svn_fs_base__rev_get_txn_id(const char ** txn_id_p,svn_fs_t * fs,svn_revnum_t rev,trail_t * trail,apr_pool_t * pool)137 svn_fs_base__rev_get_txn_id(const char **txn_id_p,
138                             svn_fs_t *fs,
139                             svn_revnum_t rev,
140                             trail_t *trail,
141                             apr_pool_t *pool)
142 {
143   revision_t *revision;
144 
145   SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool));
146   if (revision->txn_id == NULL)
147     return svn_fs_base__err_corrupt_fs_revision(fs, rev);
148 
149   *txn_id_p = revision->txn_id;
150   return SVN_NO_ERROR;
151 }
152 
153 
154 static svn_error_t *
txn_body_youngest_rev(void * baton,trail_t * trail)155 txn_body_youngest_rev(void *baton, trail_t *trail)
156 {
157   return svn_fs_bdb__youngest_rev(baton, trail->fs, trail, trail->pool);
158 }
159 
160 
161 svn_error_t *
svn_fs_base__youngest_rev(svn_revnum_t * youngest_p,svn_fs_t * fs,apr_pool_t * pool)162 svn_fs_base__youngest_rev(svn_revnum_t *youngest_p,
163                           svn_fs_t *fs,
164                           apr_pool_t *pool)
165 {
166   svn_revnum_t youngest;
167   SVN_ERR(svn_fs__check_fs(fs, TRUE));
168   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_youngest_rev, &youngest,
169                                  TRUE, pool));
170   *youngest_p = youngest;
171   return SVN_NO_ERROR;
172 }
173 
174 
175 struct revision_proplist_args {
176   apr_hash_t **table_p;
177   svn_revnum_t rev;
178 };
179 
180 
181 static svn_error_t *
txn_body_revision_proplist(void * baton,trail_t * trail)182 txn_body_revision_proplist(void *baton, trail_t *trail)
183 {
184   struct revision_proplist_args *args = baton;
185   transaction_t *txn;
186 
187   SVN_ERR(get_rev_txn(&txn, NULL, trail->fs, args->rev, trail, trail->pool));
188   *(args->table_p) = txn->proplist;
189   return SVN_NO_ERROR;
190 }
191 
192 
193 svn_error_t *
svn_fs_base__revision_proplist(apr_hash_t ** table_p,svn_fs_t * fs,svn_revnum_t rev,svn_boolean_t refresh,apr_pool_t * result_pool,apr_pool_t * scratch_pool)194 svn_fs_base__revision_proplist(apr_hash_t **table_p,
195                                svn_fs_t *fs,
196                                svn_revnum_t rev,
197                                svn_boolean_t refresh,
198                                apr_pool_t *result_pool,
199                                apr_pool_t *scratch_pool)
200 {
201   struct revision_proplist_args args;
202   apr_hash_t *table;
203 
204   SVN_ERR(svn_fs__check_fs(fs, TRUE));
205 
206   args.table_p = &table;
207   args.rev = rev;
208   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
209                                  FALSE, result_pool));
210 
211   *table_p = table ? table : apr_hash_make(result_pool);
212   return SVN_NO_ERROR;
213 }
214 
215 
216 svn_error_t *
svn_fs_base__revision_prop(svn_string_t ** value_p,svn_fs_t * fs,svn_revnum_t rev,const char * propname,svn_boolean_t refresh,apr_pool_t * result_pool,apr_pool_t * scratch_pool)217 svn_fs_base__revision_prop(svn_string_t **value_p,
218                            svn_fs_t *fs,
219                            svn_revnum_t rev,
220                            const char *propname,
221                            svn_boolean_t refresh,
222                            apr_pool_t *result_pool,
223                            apr_pool_t *scratch_pool)
224 {
225   struct revision_proplist_args args;
226   apr_hash_t *table;
227 
228   SVN_ERR(svn_fs__check_fs(fs, TRUE));
229 
230   /* Get the proplist. */
231   args.table_p = &table;
232   args.rev = rev;
233   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
234                                  FALSE, result_pool));
235 
236   /* And then the prop from that list (if there was a list). */
237   *value_p = svn_hash_gets(table, propname);
238 
239   return SVN_NO_ERROR;
240 }
241 
242 
243 svn_error_t *
svn_fs_base__set_rev_prop(svn_fs_t * fs,svn_revnum_t rev,const char * name,const svn_string_t * const * old_value_p,const svn_string_t * value,trail_t * trail,apr_pool_t * pool)244 svn_fs_base__set_rev_prop(svn_fs_t *fs,
245                           svn_revnum_t rev,
246                           const char *name,
247                           const svn_string_t *const *old_value_p,
248                           const svn_string_t *value,
249                           trail_t *trail,
250                           apr_pool_t *pool)
251 {
252   transaction_t *txn;
253   const char *txn_id;
254   const svn_string_t *present_value;
255 
256   SVN_ERR(get_rev_txn(&txn, &txn_id, fs, rev, trail, pool));
257   present_value = svn_hash_gets(txn->proplist, name);
258 
259   /* If there's no proplist, but we're just deleting a property, exit now. */
260   if ((! txn->proplist) && (! value))
261     return SVN_NO_ERROR;
262 
263   /* Now, if there's no proplist, we know we need to make one. */
264   if (! txn->proplist)
265     txn->proplist = apr_hash_make(pool);
266 
267   /* Set the property. */
268   if (old_value_p)
269     {
270       const svn_string_t *wanted_value = *old_value_p;
271       if ((!wanted_value != !present_value)
272           || (wanted_value && present_value
273               && !svn_string_compare(wanted_value, present_value)))
274         {
275           /* What we expected isn't what we found. */
276           return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
277                                    _("revprop '%s' has unexpected value in "
278                                      "filesystem"),
279                                    name);
280         }
281       /* Fall through. */
282     }
283 
284   /* If the prop-set is a no-op, skip the actual write. */
285   if ((!present_value && !value)
286       || (present_value && value
287           && svn_string_compare(present_value, value)))
288     return SVN_NO_ERROR;
289 
290   svn_hash_sets(txn->proplist, name, value);
291 
292   /* Overwrite the revision. */
293   return put_txn(fs, txn, txn_id, trail, pool);
294 }
295 
296 
297 struct change_rev_prop_args {
298   svn_revnum_t rev;
299   const char *name;
300   const svn_string_t *const *old_value_p;
301   const svn_string_t *value;
302 };
303 
304 
305 static svn_error_t *
txn_body_change_rev_prop(void * baton,trail_t * trail)306 txn_body_change_rev_prop(void *baton, trail_t *trail)
307 {
308   struct change_rev_prop_args *args = baton;
309 
310   return svn_fs_base__set_rev_prop(trail->fs, args->rev,
311                                    args->name, args->old_value_p, args->value,
312                                    trail, trail->pool);
313 }
314 
315 
316 svn_error_t *
svn_fs_base__change_rev_prop(svn_fs_t * fs,svn_revnum_t rev,const char * name,const svn_string_t * const * old_value_p,const svn_string_t * value,apr_pool_t * pool)317 svn_fs_base__change_rev_prop(svn_fs_t *fs,
318                              svn_revnum_t rev,
319                              const char *name,
320                              const svn_string_t *const *old_value_p,
321                              const svn_string_t *value,
322                              apr_pool_t *pool)
323 {
324   struct change_rev_prop_args args;
325 
326   SVN_ERR(svn_fs__check_fs(fs, TRUE));
327 
328   args.rev = rev;
329   args.name = name;
330   args.old_value_p = old_value_p;
331   args.value = value;
332   return svn_fs_base__retry_txn(fs, txn_body_change_rev_prop, &args,
333                                 TRUE, pool);
334 }
335 
336 
337 
338 /*** Transactions ***/
339 
340 svn_error_t *
svn_fs_base__txn_make_committed(svn_fs_t * fs,const char * txn_name,svn_revnum_t revision,trail_t * trail,apr_pool_t * pool)341 svn_fs_base__txn_make_committed(svn_fs_t *fs,
342                                 const char *txn_name,
343                                 svn_revnum_t revision,
344                                 trail_t *trail,
345                                 apr_pool_t *pool)
346 {
347   transaction_t *txn;
348 
349   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
350 
351   /* Make sure the TXN is not committed already. */
352   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
353   if (txn->kind != transaction_kind_normal)
354     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
355 
356   /* Convert TXN to a committed transaction. */
357   txn->base_id = NULL;
358   txn->revision = revision;
359   txn->kind = transaction_kind_committed;
360   return put_txn(fs, txn, txn_name, trail, pool);
361 }
362 
363 
364 svn_error_t *
svn_fs_base__txn_get_revision(svn_revnum_t * revision,svn_fs_t * fs,const char * txn_name,trail_t * trail,apr_pool_t * pool)365 svn_fs_base__txn_get_revision(svn_revnum_t *revision,
366                               svn_fs_t *fs,
367                               const char *txn_name,
368                               trail_t *trail,
369                               apr_pool_t *pool)
370 {
371   transaction_t *txn;
372   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
373   *revision = txn->revision;
374   return SVN_NO_ERROR;
375 }
376 
377 
378 svn_error_t *
svn_fs_base__get_txn_ids(const svn_fs_id_t ** root_id_p,const svn_fs_id_t ** base_root_id_p,svn_fs_t * fs,const char * txn_name,trail_t * trail,apr_pool_t * pool)379 svn_fs_base__get_txn_ids(const svn_fs_id_t **root_id_p,
380                          const svn_fs_id_t **base_root_id_p,
381                          svn_fs_t *fs,
382                          const char *txn_name,
383                          trail_t *trail,
384                          apr_pool_t *pool)
385 {
386   transaction_t *txn;
387 
388   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
389   if (txn->kind != transaction_kind_normal)
390     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
391 
392   *root_id_p = txn->root_id;
393   *base_root_id_p = txn->base_id;
394   return SVN_NO_ERROR;
395 }
396 
397 
398 svn_error_t *
svn_fs_base__set_txn_root(svn_fs_t * fs,const char * txn_name,const svn_fs_id_t * new_id,trail_t * trail,apr_pool_t * pool)399 svn_fs_base__set_txn_root(svn_fs_t *fs,
400                           const char *txn_name,
401                           const svn_fs_id_t *new_id,
402                           trail_t *trail,
403                           apr_pool_t *pool)
404 {
405   transaction_t *txn;
406 
407   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
408   if (txn->kind != transaction_kind_normal)
409     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
410 
411   if (! svn_fs_base__id_eq(txn->root_id, new_id))
412     {
413       txn->root_id = new_id;
414       SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
415     }
416   return SVN_NO_ERROR;
417 }
418 
419 
420 svn_error_t *
svn_fs_base__set_txn_base(svn_fs_t * fs,const char * txn_name,const svn_fs_id_t * new_id,trail_t * trail,apr_pool_t * pool)421 svn_fs_base__set_txn_base(svn_fs_t *fs,
422                           const char *txn_name,
423                           const svn_fs_id_t *new_id,
424                           trail_t *trail,
425                           apr_pool_t *pool)
426 {
427   transaction_t *txn;
428 
429   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
430   if (txn->kind != transaction_kind_normal)
431     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
432 
433   if (! svn_fs_base__id_eq(txn->base_id, new_id))
434     {
435       txn->base_id = new_id;
436       SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
437     }
438   return SVN_NO_ERROR;
439 }
440 
441 
442 svn_error_t *
svn_fs_base__add_txn_copy(svn_fs_t * fs,const char * txn_name,const char * copy_id,trail_t * trail,apr_pool_t * pool)443 svn_fs_base__add_txn_copy(svn_fs_t *fs,
444                           const char *txn_name,
445                           const char *copy_id,
446                           trail_t *trail,
447                           apr_pool_t *pool)
448 {
449   transaction_t *txn;
450 
451   /* Get the transaction and ensure its mutability. */
452   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
453   if (txn->kind != transaction_kind_normal)
454     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
455 
456   /* Allocate a new array if this transaction has no copies. */
457   if (! txn->copies)
458     txn->copies = apr_array_make(pool, 1, sizeof(copy_id));
459 
460   /* Add COPY_ID to the array. */
461   APR_ARRAY_PUSH(txn->copies, const char *) = copy_id;
462 
463   /* Finally, write out the transaction. */
464   return put_txn(fs, txn, txn_name, trail, pool);
465 }
466 
467 
468 
469 /* Generic transaction operations.  */
470 
471 struct txn_proplist_args {
472   apr_hash_t **table_p;
473   const char *id;
474 };
475 
476 
477 static svn_error_t *
txn_body_txn_proplist(void * baton,trail_t * trail)478 txn_body_txn_proplist(void *baton, trail_t *trail)
479 {
480   transaction_t *txn;
481   struct txn_proplist_args *args = baton;
482 
483   SVN_ERR(get_txn(&txn, trail->fs, args->id, FALSE, trail, trail->pool));
484   if (txn->kind != transaction_kind_normal)
485     return svn_fs_base__err_txn_not_mutable(trail->fs, args->id);
486 
487   *(args->table_p) = txn->proplist;
488   return SVN_NO_ERROR;
489 }
490 
491 
492 
493 svn_error_t *
svn_fs_base__txn_proplist_in_trail(apr_hash_t ** table_p,const char * txn_id,trail_t * trail)494 svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p,
495                                    const char *txn_id,
496                                    trail_t *trail)
497 {
498   struct txn_proplist_args args;
499   apr_hash_t *table;
500 
501   args.table_p = &table;
502   args.id = txn_id;
503   SVN_ERR(txn_body_txn_proplist(&args, trail));
504 
505   *table_p = table ? table : apr_hash_make(trail->pool);
506   return SVN_NO_ERROR;
507 }
508 
509 
510 
511 svn_error_t *
svn_fs_base__txn_proplist(apr_hash_t ** table_p,svn_fs_txn_t * txn,apr_pool_t * pool)512 svn_fs_base__txn_proplist(apr_hash_t **table_p,
513                           svn_fs_txn_t *txn,
514                           apr_pool_t *pool)
515 {
516   struct txn_proplist_args args;
517   apr_hash_t *table;
518   svn_fs_t *fs = txn->fs;
519 
520   SVN_ERR(svn_fs__check_fs(fs, TRUE));
521 
522   args.table_p = &table;
523   args.id = txn->id;
524   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
525                                  FALSE, pool));
526 
527   *table_p = table ? table : apr_hash_make(pool);
528   return SVN_NO_ERROR;
529 }
530 
531 
532 svn_error_t *
svn_fs_base__txn_prop(svn_string_t ** value_p,svn_fs_txn_t * txn,const char * propname,apr_pool_t * pool)533 svn_fs_base__txn_prop(svn_string_t **value_p,
534                       svn_fs_txn_t *txn,
535                       const char *propname,
536                       apr_pool_t *pool)
537 {
538   struct txn_proplist_args args;
539   apr_hash_t *table;
540   svn_fs_t *fs = txn->fs;
541 
542   SVN_ERR(svn_fs__check_fs(fs, TRUE));
543 
544   /* Get the proplist. */
545   args.table_p = &table;
546   args.id = txn->id;
547   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
548                                  FALSE, pool));
549 
550   /* And then the prop from that list (if there was a list). */
551   *value_p = svn_hash_gets(table, propname);
552 
553   return SVN_NO_ERROR;
554 }
555 
556 
557 
558 struct change_txn_prop_args {
559   svn_fs_t *fs;
560   const char *id;
561   const char *name;
562   const svn_string_t *value;
563 };
564 
565 
566 svn_error_t *
svn_fs_base__set_txn_prop(svn_fs_t * fs,const char * txn_name,const char * name,const svn_string_t * value,trail_t * trail,apr_pool_t * pool)567 svn_fs_base__set_txn_prop(svn_fs_t *fs,
568                           const char *txn_name,
569                           const char *name,
570                           const svn_string_t *value,
571                           trail_t *trail,
572                           apr_pool_t *pool)
573 {
574   transaction_t *txn;
575 
576   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
577   if (txn->kind != transaction_kind_normal)
578     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
579 
580   /* If there's no proplist, but we're just deleting a property, exit now. */
581   if ((! txn->proplist) && (! value))
582     return SVN_NO_ERROR;
583 
584   /* Now, if there's no proplist, we know we need to make one. */
585   if (! txn->proplist)
586     txn->proplist = apr_hash_make(pool);
587 
588   /* Set the property. */
589   if (svn_hash_gets(txn->proplist, SVN_FS__PROP_TXN_CLIENT_DATE)
590       && !strcmp(name, SVN_PROP_REVISION_DATE))
591     svn_hash_sets(txn->proplist, SVN_FS__PROP_TXN_CLIENT_DATE,
592                   svn_string_create("1", pool));
593   svn_hash_sets(txn->proplist, name, value);
594 
595   /* Now overwrite the transaction. */
596   return put_txn(fs, txn, txn_name, trail, pool);
597 }
598 
599 
600 static svn_error_t *
txn_body_change_txn_prop(void * baton,trail_t * trail)601 txn_body_change_txn_prop(void *baton, trail_t *trail)
602 {
603   struct change_txn_prop_args *args = baton;
604   return svn_fs_base__set_txn_prop(trail->fs, args->id, args->name,
605                                    args->value, trail, trail->pool);
606 }
607 
608 
609 svn_error_t *
svn_fs_base__change_txn_prop(svn_fs_txn_t * txn,const char * name,const svn_string_t * value,apr_pool_t * pool)610 svn_fs_base__change_txn_prop(svn_fs_txn_t *txn,
611                              const char *name,
612                              const svn_string_t *value,
613                              apr_pool_t *pool)
614 {
615   struct change_txn_prop_args args;
616   svn_fs_t *fs = txn->fs;
617 
618   SVN_ERR(svn_fs__check_fs(fs, TRUE));
619 
620   args.id = txn->id;
621   args.name = name;
622   args.value = value;
623   return svn_fs_base__retry_txn(fs, txn_body_change_txn_prop, &args,
624                                 TRUE, pool);
625 }
626 
627 
628 svn_error_t *
svn_fs_base__change_txn_props(svn_fs_txn_t * txn,const apr_array_header_t * props,apr_pool_t * pool)629 svn_fs_base__change_txn_props(svn_fs_txn_t *txn,
630                               const apr_array_header_t *props,
631                               apr_pool_t *pool)
632 {
633   apr_pool_t *iterpool = svn_pool_create(pool);
634   int i;
635 
636   for (i = 0; i < props->nelts; i++)
637     {
638       svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
639 
640       svn_pool_clear(iterpool);
641 
642       SVN_ERR(svn_fs_base__change_txn_prop(txn, prop->name,
643                                            prop->value, iterpool));
644     }
645   svn_pool_destroy(iterpool);
646 
647   return SVN_NO_ERROR;
648 }
649 
650 
651 /* Creating a transaction */
652 
653 static txn_vtable_t txn_vtable = {
654   svn_fs_base__commit_txn,
655   svn_fs_base__abort_txn,
656   svn_fs_base__txn_prop,
657   svn_fs_base__txn_proplist,
658   svn_fs_base__change_txn_prop,
659   svn_fs_base__txn_root,
660   svn_fs_base__change_txn_props
661 };
662 
663 
664 /* Allocate and return a new transaction object in POOL for FS whose
665    transaction ID is ID.  ID is not copied.  */
666 static svn_fs_txn_t *
make_txn(svn_fs_t * fs,const char * id,svn_revnum_t base_rev,apr_pool_t * pool)667 make_txn(svn_fs_t *fs,
668          const char *id,
669          svn_revnum_t base_rev,
670          apr_pool_t *pool)
671 {
672   svn_fs_txn_t *txn = apr_pcalloc(pool, sizeof(*txn));
673 
674   txn->fs = fs;
675   txn->id = id;
676   txn->base_rev = base_rev;
677   txn->vtable = &txn_vtable;
678   txn->fsap_data = NULL;
679 
680   return txn;
681 }
682 
683 
684 struct begin_txn_args
685 {
686   svn_fs_txn_t **txn_p;
687   svn_revnum_t base_rev;
688   apr_uint32_t flags;
689 };
690 
691 
692 static svn_error_t *
txn_body_begin_txn(void * baton,trail_t * trail)693 txn_body_begin_txn(void *baton, trail_t *trail)
694 {
695   struct begin_txn_args *args = baton;
696   const svn_fs_id_t *root_id;
697   const char *txn_id;
698 
699   SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->base_rev,
700                                     trail, trail->pool));
701   SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id,
702                                  trail, trail->pool));
703 
704   if (args->flags & SVN_FS_TXN_CHECK_OOD)
705     {
706       struct change_txn_prop_args cpargs;
707       cpargs.fs = trail->fs;
708       cpargs.id = txn_id;
709       cpargs.name = SVN_FS__PROP_TXN_CHECK_OOD;
710       cpargs.value = svn_string_create("true", trail->pool);
711 
712       SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
713     }
714 
715   if (args->flags & SVN_FS_TXN_CHECK_LOCKS)
716     {
717       struct change_txn_prop_args cpargs;
718       cpargs.fs = trail->fs;
719       cpargs.id = txn_id;
720       cpargs.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
721       cpargs.value = svn_string_create("true", trail->pool);
722 
723       SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
724     }
725 
726   /* Put a datestamp on the newly created txn, so we always know
727      exactly how old it is.  (This will help sysadmins identify
728      long-abandoned txns that may need to be manually removed.) Do
729      this before setting CLIENT_DATE so that it is not recorded as an
730      explicit setting. */
731   {
732     struct change_txn_prop_args cpargs;
733     svn_string_t date;
734     cpargs.fs = trail->fs;
735     cpargs.id = txn_id;
736     cpargs.name = SVN_PROP_REVISION_DATE;
737     date.data  = svn_time_to_cstring(apr_time_now(), trail->pool);
738     date.len = strlen(date.data);
739     cpargs.value = &date;
740     SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
741   }
742 
743   if (args->flags & SVN_FS_TXN_CLIENT_DATE)
744     {
745       struct change_txn_prop_args cpargs;
746       cpargs.fs = trail->fs;
747       cpargs.id = txn_id;
748       cpargs.name = SVN_FS__PROP_TXN_CLIENT_DATE;
749       cpargs.value = svn_string_create("0", trail->pool);
750 
751       SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
752     }
753 
754   *args->txn_p = make_txn(trail->fs, txn_id, args->base_rev, trail->pool);
755   return SVN_NO_ERROR;
756 }
757 
758 /* Note:  it is acceptable for this function to call back into
759    public FS API interfaces because it does not itself use trails.  */
760 svn_error_t *
svn_fs_base__begin_txn(svn_fs_txn_t ** txn_p,svn_fs_t * fs,svn_revnum_t rev,apr_uint32_t flags,apr_pool_t * pool)761 svn_fs_base__begin_txn(svn_fs_txn_t **txn_p,
762                        svn_fs_t *fs,
763                        svn_revnum_t rev,
764                        apr_uint32_t flags,
765                        apr_pool_t *pool)
766 {
767   svn_fs_txn_t *txn;
768   struct begin_txn_args args;
769 
770   SVN_ERR(svn_fs__check_fs(fs, TRUE));
771 
772   args.txn_p = &txn;
773   args.base_rev = rev;
774   args.flags = flags;
775   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_txn, &args, FALSE, pool));
776 
777   *txn_p = txn;
778 
779   return SVN_NO_ERROR;
780 }
781 
782 
783 struct open_txn_args
784 {
785   svn_fs_txn_t **txn_p;
786   const char *name;
787 };
788 
789 
790 static svn_error_t *
txn_body_open_txn(void * baton,trail_t * trail)791 txn_body_open_txn(void *baton, trail_t *trail)
792 {
793   struct open_txn_args *args = baton;
794   transaction_t *fstxn;
795   svn_revnum_t base_rev = SVN_INVALID_REVNUM;
796   const char *txn_id;
797 
798   SVN_ERR(get_txn(&fstxn, trail->fs, args->name, FALSE, trail, trail->pool));
799   if (fstxn->kind != transaction_kind_committed)
800     {
801       txn_id = svn_fs_base__id_txn_id(fstxn->base_id);
802       SVN_ERR(svn_fs_base__txn_get_revision(&base_rev, trail->fs, txn_id,
803                                             trail, trail->pool));
804     }
805 
806   *args->txn_p = make_txn(trail->fs, args->name, base_rev, trail->pool);
807   return SVN_NO_ERROR;
808 }
809 
810 
811 svn_error_t *
svn_fs_base__open_txn(svn_fs_txn_t ** txn_p,svn_fs_t * fs,const char * name,apr_pool_t * pool)812 svn_fs_base__open_txn(svn_fs_txn_t **txn_p,
813                       svn_fs_t *fs,
814                       const char *name,
815                       apr_pool_t *pool)
816 {
817   svn_fs_txn_t *txn;
818   struct open_txn_args args;
819 
820   SVN_ERR(svn_fs__check_fs(fs, TRUE));
821 
822   args.txn_p = &txn;
823   args.name = name;
824   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_open_txn, &args, FALSE, pool));
825 
826   *txn_p = txn;
827   return SVN_NO_ERROR;
828 }
829 
830 
831 struct cleanup_txn_args
832 {
833   transaction_t **txn_p;
834   const char *name;
835 };
836 
837 
838 static svn_error_t *
txn_body_cleanup_txn(void * baton,trail_t * trail)839 txn_body_cleanup_txn(void *baton, trail_t *trail)
840 {
841   struct cleanup_txn_args *args = baton;
842   return get_txn(args->txn_p, trail->fs, args->name, TRUE,
843                  trail, trail->pool);
844 }
845 
846 
847 static svn_error_t *
txn_body_cleanup_txn_copy(void * baton,trail_t * trail)848 txn_body_cleanup_txn_copy(void *baton, trail_t *trail)
849 {
850   const char *copy_id = *(const char **)baton;
851   svn_error_t *err = svn_fs_bdb__delete_copy(trail->fs, copy_id, trail,
852                                              trail->pool);
853 
854   /* Copy doesn't exist?  No sweat. */
855   if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_COPY))
856     {
857       svn_error_clear(err);
858       err = SVN_NO_ERROR;
859     }
860   return svn_error_trace(err);
861 }
862 
863 
864 static svn_error_t *
txn_body_cleanup_txn_changes(void * baton,trail_t * trail)865 txn_body_cleanup_txn_changes(void *baton, trail_t *trail)
866 {
867   const char *key = *(const char **)baton;
868 
869   return svn_fs_bdb__changes_delete(trail->fs, key, trail, trail->pool);
870 }
871 
872 
873 struct get_dirents_args
874 {
875   apr_hash_t **dirents;
876   const svn_fs_id_t *id;
877   const char *txn_id;
878 };
879 
880 
881 static svn_error_t *
txn_body_get_dirents(void * baton,trail_t * trail)882 txn_body_get_dirents(void *baton, trail_t *trail)
883 {
884   struct get_dirents_args *args = baton;
885   dag_node_t *node;
886 
887   /* Get the node. */
888   SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
889                                     trail, trail->pool));
890 
891   /* If immutable, do nothing and return. */
892   if (! svn_fs_base__dag_check_mutable(node, args->txn_id))
893     return SVN_NO_ERROR;
894 
895   /* If a directory, do nothing and return. */
896   *(args->dirents) = NULL;
897   if (svn_fs_base__dag_node_kind(node) != svn_node_dir)
898     return SVN_NO_ERROR;
899 
900   /* Else it's mutable.  Get its dirents. */
901   return svn_fs_base__dag_dir_entries(args->dirents, node,
902                                       trail, trail->pool);
903 }
904 
905 
906 struct remove_node_args
907 {
908   const svn_fs_id_t *id;
909   const char *txn_id;
910 };
911 
912 
913 static svn_error_t *
txn_body_remove_node(void * baton,trail_t * trail)914 txn_body_remove_node(void *baton, trail_t *trail)
915 {
916   struct remove_node_args *args = baton;
917   return svn_fs_base__dag_remove_node(trail->fs, args->id, args->txn_id,
918                                       trail, trail->pool);
919 }
920 
921 
922 static svn_error_t *
delete_txn_tree(svn_fs_t * fs,const svn_fs_id_t * id,const char * txn_id,apr_pool_t * pool)923 delete_txn_tree(svn_fs_t *fs,
924                 const svn_fs_id_t *id,
925                 const char *txn_id,
926                 apr_pool_t *pool)
927 {
928   struct get_dirents_args dirent_args;
929   struct remove_node_args rm_args;
930   apr_hash_t *dirents = NULL;
931   apr_hash_index_t *hi;
932   svn_error_t *err;
933 
934   /* If this sucker isn't mutable, there's nothing to do. */
935   if (strcmp(svn_fs_base__id_txn_id(id), txn_id) != 0)
936     return SVN_NO_ERROR;
937 
938   /* See if the thing has dirents that need to be recursed upon.  If
939      you can't find the thing itself, don't sweat it.  We probably
940      already cleaned it up. */
941   dirent_args.dirents = &dirents;
942   dirent_args.id = id;
943   dirent_args.txn_id = txn_id;
944   err = svn_fs_base__retry_txn(fs, txn_body_get_dirents, &dirent_args,
945                                FALSE, pool);
946   if (err && (err->apr_err == SVN_ERR_FS_ID_NOT_FOUND))
947     {
948       svn_error_clear(err);
949       return SVN_NO_ERROR;
950     }
951   SVN_ERR(err);
952 
953   /* If there are dirents upon which to recurse ... recurse. */
954   if (dirents)
955     {
956       apr_pool_t *subpool = svn_pool_create(pool);
957 
958       /* Loop over hash entries */
959       for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
960         {
961           void *val;
962           svn_fs_dirent_t *dirent;
963 
964           svn_pool_clear(subpool);
965           apr_hash_this(hi, NULL, NULL, &val);
966           dirent = val;
967           SVN_ERR(delete_txn_tree(fs, dirent->id, txn_id, subpool));
968         }
969       svn_pool_destroy(subpool);
970     }
971 
972   /* Remove the node. */
973   rm_args.id = id;
974   rm_args.txn_id = txn_id;
975   return svn_fs_base__retry_txn(fs, txn_body_remove_node, &rm_args,
976                                 TRUE, pool);
977 }
978 
979 
980 static svn_error_t *
txn_body_delete_txn(void * baton,trail_t * trail)981 txn_body_delete_txn(void *baton, trail_t *trail)
982 {
983   const char *txn_id = *(const char **)baton;
984 
985   return svn_fs_bdb__delete_txn(trail->fs, txn_id, trail, trail->pool);
986 }
987 
988 
989 svn_error_t *
svn_fs_base__purge_txn(svn_fs_t * fs,const char * txn_id,apr_pool_t * pool)990 svn_fs_base__purge_txn(svn_fs_t *fs,
991                        const char *txn_id,
992                        apr_pool_t *pool)
993 {
994   struct cleanup_txn_args args;
995   transaction_t *txn;
996 
997   SVN_ERR(svn_fs__check_fs(fs, TRUE));
998 
999   /* Open the transaction, expecting it to be dead. */
1000   args.txn_p = &txn;
1001   args.name = txn_id;
1002   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn, &args,
1003                                  FALSE, pool));
1004 
1005   /* Delete the mutable portion of the tree hanging from the
1006      transaction (which should gracefully recover if we've already
1007      done this). */
1008   SVN_ERR(delete_txn_tree(fs, txn->root_id, txn_id, pool));
1009 
1010   /* Kill the transaction's changes (which should gracefully recover
1011      if...). */
1012   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn_changes,
1013                                  &txn_id, TRUE, pool));
1014 
1015   /* Kill the transaction's copies (which should gracefully...). */
1016   if (txn->copies)
1017     {
1018       int i;
1019 
1020       for (i = 0; i < txn->copies->nelts; i++)
1021         {
1022           SVN_ERR(svn_fs_base__retry_txn
1023                   (fs, txn_body_cleanup_txn_copy,
1024                    &APR_ARRAY_IDX(txn->copies, i, const char *),
1025                    TRUE, pool));
1026         }
1027     }
1028 
1029   /* Kill the transaction itself (which ... just kidding -- this has
1030      no graceful failure mode). */
1031   return svn_fs_base__retry_txn(fs, txn_body_delete_txn, &txn_id,
1032                                 TRUE, pool);
1033 }
1034 
1035 
1036 static svn_error_t *
txn_body_abort_txn(void * baton,trail_t * trail)1037 txn_body_abort_txn(void *baton, trail_t *trail)
1038 {
1039   svn_fs_txn_t *txn = baton;
1040   transaction_t *fstxn;
1041 
1042   /* Get the transaction by its id, set it to "dead", and store the
1043      transaction. */
1044   SVN_ERR(get_txn(&fstxn, txn->fs, txn->id, FALSE, trail, trail->pool));
1045   if (fstxn->kind != transaction_kind_normal)
1046     return svn_fs_base__err_txn_not_mutable(txn->fs, txn->id);
1047 
1048   fstxn->kind = transaction_kind_dead;
1049   return put_txn(txn->fs, fstxn, txn->id, trail, trail->pool);
1050 }
1051 
1052 
1053 svn_error_t *
svn_fs_base__abort_txn(svn_fs_txn_t * txn,apr_pool_t * pool)1054 svn_fs_base__abort_txn(svn_fs_txn_t *txn,
1055                        apr_pool_t *pool)
1056 {
1057   SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1058 
1059   /* Set the transaction to "dead". */
1060   SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_abort_txn, txn,
1061                                  TRUE, pool));
1062 
1063   /* Now, purge it. */
1064   SVN_ERR_W(svn_fs_base__purge_txn(txn->fs, txn->id, pool),
1065             _("Transaction aborted, but cleanup failed"));
1066 
1067   return SVN_NO_ERROR;
1068 }
1069 
1070 
1071 struct list_transactions_args
1072 {
1073   apr_array_header_t **names_p;
1074   apr_pool_t *pool;
1075 };
1076 
1077 static svn_error_t *
txn_body_list_transactions(void * baton,trail_t * trail)1078 txn_body_list_transactions(void* baton, trail_t *trail)
1079 {
1080   struct list_transactions_args *args = baton;
1081   return svn_fs_bdb__get_txn_list(args->names_p, trail->fs,
1082                                   trail, args->pool);
1083 }
1084 
1085 svn_error_t *
svn_fs_base__list_transactions(apr_array_header_t ** names_p,svn_fs_t * fs,apr_pool_t * pool)1086 svn_fs_base__list_transactions(apr_array_header_t **names_p,
1087                                svn_fs_t *fs,
1088                                apr_pool_t *pool)
1089 {
1090   apr_array_header_t *names;
1091   struct list_transactions_args args;
1092 
1093   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1094 
1095   args.names_p = &names;
1096   args.pool = pool;
1097   SVN_ERR(svn_fs_base__retry(fs, txn_body_list_transactions, &args,
1098                              FALSE, pool));
1099 
1100   *names_p = names;
1101   return SVN_NO_ERROR;
1102 }
1103