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