]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_base/revs-txns.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_base / revs-txns.c
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 \f
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 *
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 \f
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 *
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 *
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 *
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 *
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 *
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 *
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 *
194 svn_fs_base__revision_proplist(apr_hash_t **table_p,
195                                svn_fs_t *fs,
196                                svn_revnum_t rev,
197                                apr_pool_t *pool)
198 {
199   struct revision_proplist_args args;
200   apr_hash_t *table;
201
202   SVN_ERR(svn_fs__check_fs(fs, TRUE));
203
204   args.table_p = &table;
205   args.rev = rev;
206   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
207                                  FALSE, pool));
208
209   *table_p = table ? table : apr_hash_make(pool);
210   return SVN_NO_ERROR;
211 }
212
213
214 svn_error_t *
215 svn_fs_base__revision_prop(svn_string_t **value_p,
216                            svn_fs_t *fs,
217                            svn_revnum_t rev,
218                            const char *propname,
219                            apr_pool_t *pool)
220 {
221   struct revision_proplist_args args;
222   apr_hash_t *table;
223
224   SVN_ERR(svn_fs__check_fs(fs, TRUE));
225
226   /* Get the proplist. */
227   args.table_p = &table;
228   args.rev = rev;
229   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
230                                  FALSE, pool));
231
232   /* And then the prop from that list (if there was a list). */
233   *value_p = svn_hash_gets(table, propname);
234
235   return SVN_NO_ERROR;
236 }
237
238
239 svn_error_t *
240 svn_fs_base__set_rev_prop(svn_fs_t *fs,
241                           svn_revnum_t rev,
242                           const char *name,
243                           const svn_string_t *const *old_value_p,
244                           const svn_string_t *value,
245                           trail_t *trail,
246                           apr_pool_t *pool)
247 {
248   transaction_t *txn;
249   const char *txn_id;
250
251   SVN_ERR(get_rev_txn(&txn, &txn_id, fs, rev, trail, pool));
252
253   /* If there's no proplist, but we're just deleting a property, exit now. */
254   if ((! txn->proplist) && (! value))
255     return SVN_NO_ERROR;
256
257   /* Now, if there's no proplist, we know we need to make one. */
258   if (! txn->proplist)
259     txn->proplist = apr_hash_make(pool);
260
261   /* Set the property. */
262   if (old_value_p)
263     {
264       const svn_string_t *wanted_value = *old_value_p;
265       const svn_string_t *present_value = svn_hash_gets(txn->proplist, name);
266       if ((!wanted_value != !present_value)
267           || (wanted_value && present_value
268               && !svn_string_compare(wanted_value, present_value)))
269         {
270           /* What we expected isn't what we found. */
271           return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
272                                    _("revprop '%s' has unexpected value in "
273                                      "filesystem"),
274                                    name);
275         }
276       /* Fall through. */
277     }
278   svn_hash_sets(txn->proplist, name, value);
279
280   /* Overwrite the revision. */
281   return put_txn(fs, txn, txn_id, trail, pool);
282 }
283
284
285 struct change_rev_prop_args {
286   svn_revnum_t rev;
287   const char *name;
288   const svn_string_t *const *old_value_p;
289   const svn_string_t *value;
290 };
291
292
293 static svn_error_t *
294 txn_body_change_rev_prop(void *baton, trail_t *trail)
295 {
296   struct change_rev_prop_args *args = baton;
297
298   return svn_fs_base__set_rev_prop(trail->fs, args->rev,
299                                    args->name, args->old_value_p, args->value,
300                                    trail, trail->pool);
301 }
302
303
304 svn_error_t *
305 svn_fs_base__change_rev_prop(svn_fs_t *fs,
306                              svn_revnum_t rev,
307                              const char *name,
308                              const svn_string_t *const *old_value_p,
309                              const svn_string_t *value,
310                              apr_pool_t *pool)
311 {
312   struct change_rev_prop_args args;
313
314   SVN_ERR(svn_fs__check_fs(fs, TRUE));
315
316   args.rev = rev;
317   args.name = name;
318   args.old_value_p = old_value_p;
319   args.value = value;
320   return svn_fs_base__retry_txn(fs, txn_body_change_rev_prop, &args,
321                                 TRUE, pool);
322 }
323
324
325 \f
326 /*** Transactions ***/
327
328 svn_error_t *
329 svn_fs_base__txn_make_committed(svn_fs_t *fs,
330                                 const char *txn_name,
331                                 svn_revnum_t revision,
332                                 trail_t *trail,
333                                 apr_pool_t *pool)
334 {
335   transaction_t *txn;
336
337   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
338
339   /* Make sure the TXN is not committed already. */
340   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
341   if (txn->kind != transaction_kind_normal)
342     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
343
344   /* Convert TXN to a committed transaction. */
345   txn->base_id = NULL;
346   txn->revision = revision;
347   txn->kind = transaction_kind_committed;
348   return put_txn(fs, txn, txn_name, trail, pool);
349 }
350
351
352 svn_error_t *
353 svn_fs_base__txn_get_revision(svn_revnum_t *revision,
354                               svn_fs_t *fs,
355                               const char *txn_name,
356                               trail_t *trail,
357                               apr_pool_t *pool)
358 {
359   transaction_t *txn;
360   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
361   *revision = txn->revision;
362   return SVN_NO_ERROR;
363 }
364
365
366 svn_error_t *
367 svn_fs_base__get_txn_ids(const svn_fs_id_t **root_id_p,
368                          const svn_fs_id_t **base_root_id_p,
369                          svn_fs_t *fs,
370                          const char *txn_name,
371                          trail_t *trail,
372                          apr_pool_t *pool)
373 {
374   transaction_t *txn;
375
376   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
377   if (txn->kind != transaction_kind_normal)
378     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
379
380   *root_id_p = txn->root_id;
381   *base_root_id_p = txn->base_id;
382   return SVN_NO_ERROR;
383 }
384
385
386 svn_error_t *
387 svn_fs_base__set_txn_root(svn_fs_t *fs,
388                           const char *txn_name,
389                           const svn_fs_id_t *new_id,
390                           trail_t *trail,
391                           apr_pool_t *pool)
392 {
393   transaction_t *txn;
394
395   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
396   if (txn->kind != transaction_kind_normal)
397     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
398
399   if (! svn_fs_base__id_eq(txn->root_id, new_id))
400     {
401       txn->root_id = new_id;
402       SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
403     }
404   return SVN_NO_ERROR;
405 }
406
407
408 svn_error_t *
409 svn_fs_base__set_txn_base(svn_fs_t *fs,
410                           const char *txn_name,
411                           const svn_fs_id_t *new_id,
412                           trail_t *trail,
413                           apr_pool_t *pool)
414 {
415   transaction_t *txn;
416
417   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
418   if (txn->kind != transaction_kind_normal)
419     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
420
421   if (! svn_fs_base__id_eq(txn->base_id, new_id))
422     {
423       txn->base_id = new_id;
424       SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
425     }
426   return SVN_NO_ERROR;
427 }
428
429
430 svn_error_t *
431 svn_fs_base__add_txn_copy(svn_fs_t *fs,
432                           const char *txn_name,
433                           const char *copy_id,
434                           trail_t *trail,
435                           apr_pool_t *pool)
436 {
437   transaction_t *txn;
438
439   /* Get the transaction and ensure its mutability. */
440   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
441   if (txn->kind != transaction_kind_normal)
442     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
443
444   /* Allocate a new array if this transaction has no copies. */
445   if (! txn->copies)
446     txn->copies = apr_array_make(pool, 1, sizeof(copy_id));
447
448   /* Add COPY_ID to the array. */
449   APR_ARRAY_PUSH(txn->copies, const char *) = copy_id;
450
451   /* Finally, write out the transaction. */
452   return put_txn(fs, txn, txn_name, trail, pool);
453 }
454
455
456 \f
457 /* Generic transaction operations.  */
458
459 struct txn_proplist_args {
460   apr_hash_t **table_p;
461   const char *id;
462 };
463
464
465 static svn_error_t *
466 txn_body_txn_proplist(void *baton, trail_t *trail)
467 {
468   transaction_t *txn;
469   struct txn_proplist_args *args = baton;
470
471   SVN_ERR(get_txn(&txn, trail->fs, args->id, FALSE, trail, trail->pool));
472   if (txn->kind != transaction_kind_normal)
473     return svn_fs_base__err_txn_not_mutable(trail->fs, args->id);
474
475   *(args->table_p) = txn->proplist;
476   return SVN_NO_ERROR;
477 }
478
479
480
481 svn_error_t *
482 svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p,
483                                    const char *txn_id,
484                                    trail_t *trail)
485 {
486   struct txn_proplist_args args;
487   apr_hash_t *table;
488
489   args.table_p = &table;
490   args.id = txn_id;
491   SVN_ERR(txn_body_txn_proplist(&args, trail));
492
493   *table_p = table ? table : apr_hash_make(trail->pool);
494   return SVN_NO_ERROR;
495 }
496
497
498
499 svn_error_t *
500 svn_fs_base__txn_proplist(apr_hash_t **table_p,
501                           svn_fs_txn_t *txn,
502                           apr_pool_t *pool)
503 {
504   struct txn_proplist_args args;
505   apr_hash_t *table;
506   svn_fs_t *fs = txn->fs;
507
508   SVN_ERR(svn_fs__check_fs(fs, TRUE));
509
510   args.table_p = &table;
511   args.id = txn->id;
512   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
513                                  FALSE, pool));
514
515   *table_p = table ? table : apr_hash_make(pool);
516   return SVN_NO_ERROR;
517 }
518
519
520 svn_error_t *
521 svn_fs_base__txn_prop(svn_string_t **value_p,
522                       svn_fs_txn_t *txn,
523                       const char *propname,
524                       apr_pool_t *pool)
525 {
526   struct txn_proplist_args args;
527   apr_hash_t *table;
528   svn_fs_t *fs = txn->fs;
529
530   SVN_ERR(svn_fs__check_fs(fs, TRUE));
531
532   /* Get the proplist. */
533   args.table_p = &table;
534   args.id = txn->id;
535   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
536                                  FALSE, pool));
537
538   /* And then the prop from that list (if there was a list). */
539   *value_p = svn_hash_gets(table, propname);
540
541   return SVN_NO_ERROR;
542 }
543
544
545
546 struct change_txn_prop_args {
547   svn_fs_t *fs;
548   const char *id;
549   const char *name;
550   const svn_string_t *value;
551 };
552
553
554 svn_error_t *
555 svn_fs_base__set_txn_prop(svn_fs_t *fs,
556                           const char *txn_name,
557                           const char *name,
558                           const svn_string_t *value,
559                           trail_t *trail,
560                           apr_pool_t *pool)
561 {
562   transaction_t *txn;
563
564   SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
565   if (txn->kind != transaction_kind_normal)
566     return svn_fs_base__err_txn_not_mutable(fs, txn_name);
567
568   /* If there's no proplist, but we're just deleting a property, exit now. */
569   if ((! txn->proplist) && (! value))
570     return SVN_NO_ERROR;
571
572   /* Now, if there's no proplist, we know we need to make one. */
573   if (! txn->proplist)
574     txn->proplist = apr_hash_make(pool);
575
576   /* Set the property. */
577   if (svn_hash_gets(txn->proplist, SVN_FS__PROP_TXN_CLIENT_DATE)
578       && !strcmp(name, SVN_PROP_REVISION_DATE))
579     svn_hash_sets(txn->proplist, SVN_FS__PROP_TXN_CLIENT_DATE,
580                   svn_string_create("1", pool));
581   svn_hash_sets(txn->proplist, name, value);
582
583   /* Now overwrite the transaction. */
584   return put_txn(fs, txn, txn_name, trail, pool);
585 }
586
587
588 static svn_error_t *
589 txn_body_change_txn_prop(void *baton, trail_t *trail)
590 {
591   struct change_txn_prop_args *args = baton;
592   return svn_fs_base__set_txn_prop(trail->fs, args->id, args->name,
593                                    args->value, trail, trail->pool);
594 }
595
596
597 svn_error_t *
598 svn_fs_base__change_txn_prop(svn_fs_txn_t *txn,
599                              const char *name,
600                              const svn_string_t *value,
601                              apr_pool_t *pool)
602 {
603   struct change_txn_prop_args args;
604   svn_fs_t *fs = txn->fs;
605
606   SVN_ERR(svn_fs__check_fs(fs, TRUE));
607
608   args.id = txn->id;
609   args.name = name;
610   args.value = value;
611   return svn_fs_base__retry_txn(fs, txn_body_change_txn_prop, &args,
612                                 TRUE, pool);
613 }
614
615
616 svn_error_t *
617 svn_fs_base__change_txn_props(svn_fs_txn_t *txn,
618                               const apr_array_header_t *props,
619                               apr_pool_t *pool)
620 {
621   apr_pool_t *iterpool = svn_pool_create(pool);
622   int i;
623
624   for (i = 0; i < props->nelts; i++)
625     {
626       svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
627
628       svn_pool_clear(iterpool);
629
630       SVN_ERR(svn_fs_base__change_txn_prop(txn, prop->name,
631                                            prop->value, iterpool));
632     }
633   svn_pool_destroy(iterpool);
634
635   return SVN_NO_ERROR;
636 }
637
638 \f
639 /* Creating a transaction */
640
641 static txn_vtable_t txn_vtable = {
642   svn_fs_base__commit_txn,
643   svn_fs_base__abort_txn,
644   svn_fs_base__txn_prop,
645   svn_fs_base__txn_proplist,
646   svn_fs_base__change_txn_prop,
647   svn_fs_base__txn_root,
648   svn_fs_base__change_txn_props
649 };
650
651
652 /* Allocate and return a new transaction object in POOL for FS whose
653    transaction ID is ID.  ID is not copied.  */
654 static svn_fs_txn_t *
655 make_txn(svn_fs_t *fs,
656          const char *id,
657          svn_revnum_t base_rev,
658          apr_pool_t *pool)
659 {
660   svn_fs_txn_t *txn = apr_pcalloc(pool, sizeof(*txn));
661
662   txn->fs = fs;
663   txn->id = id;
664   txn->base_rev = base_rev;
665   txn->vtable = &txn_vtable;
666   txn->fsap_data = NULL;
667
668   return txn;
669 }
670
671
672 struct begin_txn_args
673 {
674   svn_fs_txn_t **txn_p;
675   svn_revnum_t base_rev;
676   apr_uint32_t flags;
677 };
678
679
680 static svn_error_t *
681 txn_body_begin_txn(void *baton, trail_t *trail)
682 {
683   struct begin_txn_args *args = baton;
684   const svn_fs_id_t *root_id;
685   const char *txn_id;
686
687   SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->base_rev,
688                                     trail, trail->pool));
689   SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id,
690                                  trail, trail->pool));
691
692   if (args->flags & SVN_FS_TXN_CHECK_OOD)
693     {
694       struct change_txn_prop_args cpargs;
695       cpargs.fs = trail->fs;
696       cpargs.id = txn_id;
697       cpargs.name = SVN_FS__PROP_TXN_CHECK_OOD;
698       cpargs.value = svn_string_create("true", trail->pool);
699
700       SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
701     }
702
703   if (args->flags & SVN_FS_TXN_CHECK_LOCKS)
704     {
705       struct change_txn_prop_args cpargs;
706       cpargs.fs = trail->fs;
707       cpargs.id = txn_id;
708       cpargs.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
709       cpargs.value = svn_string_create("true", trail->pool);
710
711       SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
712     }
713
714   /* Put a datestamp on the newly created txn, so we always know
715      exactly how old it is.  (This will help sysadmins identify
716      long-abandoned txns that may need to be manually removed.) Do
717      this before setting CLIENT_DATE so that it is not recorded as an
718      explicit setting. */
719   {
720     struct change_txn_prop_args cpargs;
721     svn_string_t date;
722     cpargs.fs = trail->fs;
723     cpargs.id = txn_id;
724     cpargs.name = SVN_PROP_REVISION_DATE;
725     date.data  = svn_time_to_cstring(apr_time_now(), trail->pool);
726     date.len = strlen(date.data);
727     cpargs.value = &date;
728     SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
729   }
730
731   if (args->flags & SVN_FS_TXN_CLIENT_DATE)
732     {
733       struct change_txn_prop_args cpargs;
734       cpargs.fs = trail->fs;
735       cpargs.id = txn_id;
736       cpargs.name = SVN_FS__PROP_TXN_CLIENT_DATE;
737       cpargs.value = svn_string_create("0", trail->pool);
738
739       SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
740     }
741
742   *args->txn_p = make_txn(trail->fs, txn_id, args->base_rev, trail->pool);
743   return SVN_NO_ERROR;
744 }
745
746 /* Note:  it is acceptable for this function to call back into
747    public FS API interfaces because it does not itself use trails.  */
748 svn_error_t *
749 svn_fs_base__begin_txn(svn_fs_txn_t **txn_p,
750                        svn_fs_t *fs,
751                        svn_revnum_t rev,
752                        apr_uint32_t flags,
753                        apr_pool_t *pool)
754 {
755   svn_fs_txn_t *txn;
756   struct begin_txn_args args;
757
758   SVN_ERR(svn_fs__check_fs(fs, TRUE));
759
760   args.txn_p = &txn;
761   args.base_rev = rev;
762   args.flags = flags;
763   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_txn, &args, FALSE, pool));
764
765   *txn_p = txn;
766
767   return SVN_NO_ERROR;
768 }
769
770
771 struct open_txn_args
772 {
773   svn_fs_txn_t **txn_p;
774   const char *name;
775 };
776
777
778 static svn_error_t *
779 txn_body_open_txn(void *baton, trail_t *trail)
780 {
781   struct open_txn_args *args = baton;
782   transaction_t *fstxn;
783   svn_revnum_t base_rev = SVN_INVALID_REVNUM;
784   const char *txn_id;
785
786   SVN_ERR(get_txn(&fstxn, trail->fs, args->name, FALSE, trail, trail->pool));
787   if (fstxn->kind != transaction_kind_committed)
788     {
789       txn_id = svn_fs_base__id_txn_id(fstxn->base_id);
790       SVN_ERR(svn_fs_base__txn_get_revision(&base_rev, trail->fs, txn_id,
791                                             trail, trail->pool));
792     }
793
794   *args->txn_p = make_txn(trail->fs, args->name, base_rev, trail->pool);
795   return SVN_NO_ERROR;
796 }
797
798
799 svn_error_t *
800 svn_fs_base__open_txn(svn_fs_txn_t **txn_p,
801                       svn_fs_t *fs,
802                       const char *name,
803                       apr_pool_t *pool)
804 {
805   svn_fs_txn_t *txn;
806   struct open_txn_args args;
807
808   SVN_ERR(svn_fs__check_fs(fs, TRUE));
809
810   args.txn_p = &txn;
811   args.name = name;
812   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_open_txn, &args, FALSE, pool));
813
814   *txn_p = txn;
815   return SVN_NO_ERROR;
816 }
817
818
819 struct cleanup_txn_args
820 {
821   transaction_t **txn_p;
822   const char *name;
823 };
824
825
826 static svn_error_t *
827 txn_body_cleanup_txn(void *baton, trail_t *trail)
828 {
829   struct cleanup_txn_args *args = baton;
830   return get_txn(args->txn_p, trail->fs, args->name, TRUE,
831                  trail, trail->pool);
832 }
833
834
835 static svn_error_t *
836 txn_body_cleanup_txn_copy(void *baton, trail_t *trail)
837 {
838   const char *copy_id = *(const char **)baton;
839   svn_error_t *err = svn_fs_bdb__delete_copy(trail->fs, copy_id, trail,
840                                              trail->pool);
841
842   /* Copy doesn't exist?  No sweat. */
843   if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_COPY))
844     {
845       svn_error_clear(err);
846       err = SVN_NO_ERROR;
847     }
848   return svn_error_trace(err);
849 }
850
851
852 static svn_error_t *
853 txn_body_cleanup_txn_changes(void *baton, trail_t *trail)
854 {
855   const char *key = *(const char **)baton;
856
857   return svn_fs_bdb__changes_delete(trail->fs, key, trail, trail->pool);
858 }
859
860
861 struct get_dirents_args
862 {
863   apr_hash_t **dirents;
864   const svn_fs_id_t *id;
865   const char *txn_id;
866 };
867
868
869 static svn_error_t *
870 txn_body_get_dirents(void *baton, trail_t *trail)
871 {
872   struct get_dirents_args *args = baton;
873   dag_node_t *node;
874
875   /* Get the node. */
876   SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
877                                     trail, trail->pool));
878
879   /* If immutable, do nothing and return. */
880   if (! svn_fs_base__dag_check_mutable(node, args->txn_id))
881     return SVN_NO_ERROR;
882
883   /* If a directory, do nothing and return. */
884   *(args->dirents) = NULL;
885   if (svn_fs_base__dag_node_kind(node) != svn_node_dir)
886     return SVN_NO_ERROR;
887
888   /* Else it's mutable.  Get its dirents. */
889   return svn_fs_base__dag_dir_entries(args->dirents, node,
890                                       trail, trail->pool);
891 }
892
893
894 struct remove_node_args
895 {
896   const svn_fs_id_t *id;
897   const char *txn_id;
898 };
899
900
901 static svn_error_t *
902 txn_body_remove_node(void *baton, trail_t *trail)
903 {
904   struct remove_node_args *args = baton;
905   return svn_fs_base__dag_remove_node(trail->fs, args->id, args->txn_id,
906                                       trail, trail->pool);
907 }
908
909
910 static svn_error_t *
911 delete_txn_tree(svn_fs_t *fs,
912                 const svn_fs_id_t *id,
913                 const char *txn_id,
914                 apr_pool_t *pool)
915 {
916   struct get_dirents_args dirent_args;
917   struct remove_node_args rm_args;
918   apr_hash_t *dirents = NULL;
919   apr_hash_index_t *hi;
920   svn_error_t *err;
921
922   /* If this sucker isn't mutable, there's nothing to do. */
923   if (strcmp(svn_fs_base__id_txn_id(id), txn_id) != 0)
924     return SVN_NO_ERROR;
925
926   /* See if the thing has dirents that need to be recursed upon.  If
927      you can't find the thing itself, don't sweat it.  We probably
928      already cleaned it up. */
929   dirent_args.dirents = &dirents;
930   dirent_args.id = id;
931   dirent_args.txn_id = txn_id;
932   err = svn_fs_base__retry_txn(fs, txn_body_get_dirents, &dirent_args,
933                                FALSE, pool);
934   if (err && (err->apr_err == SVN_ERR_FS_ID_NOT_FOUND))
935     {
936       svn_error_clear(err);
937       return SVN_NO_ERROR;
938     }
939   SVN_ERR(err);
940
941   /* If there are dirents upon which to recurse ... recurse. */
942   if (dirents)
943     {
944       apr_pool_t *subpool = svn_pool_create(pool);
945
946       /* Loop over hash entries */
947       for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
948         {
949           void *val;
950           svn_fs_dirent_t *dirent;
951
952           svn_pool_clear(subpool);
953           apr_hash_this(hi, NULL, NULL, &val);
954           dirent = val;
955           SVN_ERR(delete_txn_tree(fs, dirent->id, txn_id, subpool));
956         }
957       svn_pool_destroy(subpool);
958     }
959
960   /* Remove the node. */
961   rm_args.id = id;
962   rm_args.txn_id = txn_id;
963   return svn_fs_base__retry_txn(fs, txn_body_remove_node, &rm_args,
964                                 TRUE, pool);
965 }
966
967
968 static svn_error_t *
969 txn_body_delete_txn(void *baton, trail_t *trail)
970 {
971   const char *txn_id = *(const char **)baton;
972
973   return svn_fs_bdb__delete_txn(trail->fs, txn_id, trail, trail->pool);
974 }
975
976
977 svn_error_t *
978 svn_fs_base__purge_txn(svn_fs_t *fs,
979                        const char *txn_id,
980                        apr_pool_t *pool)
981 {
982   struct cleanup_txn_args args;
983   transaction_t *txn;
984
985   SVN_ERR(svn_fs__check_fs(fs, TRUE));
986
987   /* Open the transaction, expecting it to be dead. */
988   args.txn_p = &txn;
989   args.name = txn_id;
990   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn, &args,
991                                  FALSE, pool));
992
993   /* Delete the mutable portion of the tree hanging from the
994      transaction (which should gracefully recover if we've already
995      done this). */
996   SVN_ERR(delete_txn_tree(fs, txn->root_id, txn_id, pool));
997
998   /* Kill the transaction's changes (which should gracefully recover
999      if...). */
1000   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn_changes,
1001                                  &txn_id, TRUE, pool));
1002
1003   /* Kill the transaction's copies (which should gracefully...). */
1004   if (txn->copies)
1005     {
1006       int i;
1007
1008       for (i = 0; i < txn->copies->nelts; i++)
1009         {
1010           SVN_ERR(svn_fs_base__retry_txn
1011                   (fs, txn_body_cleanup_txn_copy,
1012                    &APR_ARRAY_IDX(txn->copies, i, const char *),
1013                    TRUE, pool));
1014         }
1015     }
1016
1017   /* Kill the transaction itself (which ... just kidding -- this has
1018      no graceful failure mode). */
1019   return svn_fs_base__retry_txn(fs, txn_body_delete_txn, &txn_id,
1020                                 TRUE, pool);
1021 }
1022
1023
1024 static svn_error_t *
1025 txn_body_abort_txn(void *baton, trail_t *trail)
1026 {
1027   svn_fs_txn_t *txn = baton;
1028   transaction_t *fstxn;
1029
1030   /* Get the transaction by its id, set it to "dead", and store the
1031      transaction. */
1032   SVN_ERR(get_txn(&fstxn, txn->fs, txn->id, FALSE, trail, trail->pool));
1033   if (fstxn->kind != transaction_kind_normal)
1034     return svn_fs_base__err_txn_not_mutable(txn->fs, txn->id);
1035
1036   fstxn->kind = transaction_kind_dead;
1037   return put_txn(txn->fs, fstxn, txn->id, trail, trail->pool);
1038 }
1039
1040
1041 svn_error_t *
1042 svn_fs_base__abort_txn(svn_fs_txn_t *txn,
1043                        apr_pool_t *pool)
1044 {
1045   SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1046
1047   /* Set the transaction to "dead". */
1048   SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_abort_txn, txn,
1049                                  TRUE, pool));
1050
1051   /* Now, purge it. */
1052   SVN_ERR_W(svn_fs_base__purge_txn(txn->fs, txn->id, pool),
1053             _("Transaction aborted, but cleanup failed"));
1054
1055   return SVN_NO_ERROR;
1056 }
1057
1058
1059 struct list_transactions_args
1060 {
1061   apr_array_header_t **names_p;
1062   apr_pool_t *pool;
1063 };
1064
1065 static svn_error_t *
1066 txn_body_list_transactions(void* baton, trail_t *trail)
1067 {
1068   struct list_transactions_args *args = baton;
1069   return svn_fs_bdb__get_txn_list(args->names_p, trail->fs,
1070                                   trail, args->pool);
1071 }
1072
1073 svn_error_t *
1074 svn_fs_base__list_transactions(apr_array_header_t **names_p,
1075                                svn_fs_t *fs,
1076                                apr_pool_t *pool)
1077 {
1078   apr_array_header_t *names;
1079   struct list_transactions_args args;
1080
1081   SVN_ERR(svn_fs__check_fs(fs, TRUE));
1082
1083   args.names_p = &names;
1084   args.pool = pool;
1085   SVN_ERR(svn_fs_base__retry(fs, txn_body_list_transactions, &args,
1086                              FALSE, pool));
1087
1088   *names_p = names;
1089   return SVN_NO_ERROR;
1090 }