revs-txns.c revision 299742
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. */
60static svn_error_t *
61get_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.  */
91static svn_error_t *
92get_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
118svn_error_t *
119svn_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
136svn_error_t *
137svn_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
154static svn_error_t *
155txn_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
161svn_error_t *
162svn_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
175struct revision_proplist_args {
176  apr_hash_t **table_p;
177  svn_revnum_t rev;
178};
179
180
181static svn_error_t *
182txn_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
193svn_error_t *
194svn_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
214svn_error_t *
215svn_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
239svn_error_t *
240svn_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
285struct 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
293static svn_error_t *
294txn_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
304svn_error_t *
305svn_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
326/*** Transactions ***/
327
328svn_error_t *
329svn_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
352svn_error_t *
353svn_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
366svn_error_t *
367svn_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
386svn_error_t *
387svn_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
408svn_error_t *
409svn_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
430svn_error_t *
431svn_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
457/* Generic transaction operations.  */
458
459struct txn_proplist_args {
460  apr_hash_t **table_p;
461  const char *id;
462};
463
464
465static svn_error_t *
466txn_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
481svn_error_t *
482svn_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
499svn_error_t *
500svn_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
520svn_error_t *
521svn_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
546struct 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
554svn_error_t *
555svn_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
588static svn_error_t *
589txn_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
597svn_error_t *
598svn_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
616svn_error_t *
617svn_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
639/* Creating a transaction */
640
641static 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.  */
654static svn_fs_txn_t *
655make_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
672struct 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
680static svn_error_t *
681txn_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.  */
748svn_error_t *
749svn_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
771struct open_txn_args
772{
773  svn_fs_txn_t **txn_p;
774  const char *name;
775};
776
777
778static svn_error_t *
779txn_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
799svn_error_t *
800svn_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
819struct cleanup_txn_args
820{
821  transaction_t **txn_p;
822  const char *name;
823};
824
825
826static svn_error_t *
827txn_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
835static svn_error_t *
836txn_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
852static svn_error_t *
853txn_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
861struct get_dirents_args
862{
863  apr_hash_t **dirents;
864  const svn_fs_id_t *id;
865  const char *txn_id;
866};
867
868
869static svn_error_t *
870txn_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
894struct remove_node_args
895{
896  const svn_fs_id_t *id;
897  const char *txn_id;
898};
899
900
901static svn_error_t *
902txn_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
910static svn_error_t *
911delete_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
968static svn_error_t *
969txn_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
977svn_error_t *
978svn_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
1024static svn_error_t *
1025txn_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
1041svn_error_t *
1042svn_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
1059struct list_transactions_args
1060{
1061  apr_array_header_t **names_p;
1062  apr_pool_t *pool;
1063};
1064
1065static svn_error_t *
1066txn_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
1073svn_error_t *
1074svn_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}
1091