Don't open a rev or revprop file whose number is larger than the
[svn.git] / subversion / libsvn_fs_fs / fs_fs.c
blobf472c81e22650e3361f886380761b7740426d8ba
1 /* fs_fs.c --- filesystem operations specific to fs_fs
3 * ====================================================================
4 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <assert.h>
23 #include <errno.h>
25 #include <apr_general.h>
26 #include <apr_pools.h>
27 #include <apr_file_io.h>
28 #include <apr_uuid.h>
29 #include <apr_lib.h>
30 #include <apr_md5.h>
31 #include <apr_thread_mutex.h>
33 #include "svn_pools.h"
34 #include "svn_fs.h"
35 #include "svn_path.h"
36 #include "svn_hash.h"
37 #include "svn_md5.h"
38 #include "svn_props.h"
39 #include "svn_sorts.h"
40 #include "svn_time.h"
41 #include "svn_mergeinfo.h"
43 #include "fs.h"
44 #include "err.h"
45 #include "tree.h"
46 #include "lock.h"
47 #include "key-gen.h"
48 #include "fs_fs.h"
49 #include "id.h"
51 #include "private/svn_fs_util.h"
52 #include "../libsvn_fs/fs-loader.h"
54 #include "svn_private_config.h"
56 /* An arbitrary maximum path length, so clients can't run us out of memory
57 * by giving us arbitrarily large paths. */
58 #define FSFS_MAX_PATH_LEN 4096
60 /* The default maximum number of files per directory to store in the
61 rev and revprops directory. The number below is somewhat arbitrary,
62 and can be overriden by defining the macro while compiling; the
63 figure of 1000 is reasonable for VFAT filesystems, which are by far
64 the worst performers in this area. */
65 #ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
66 #define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
67 #endif
69 /* Following are defines that specify the textual elements of the
70 native filesystem directories and revision files. */
72 /* Headers used to describe node-revision in the revision file. */
73 #define HEADER_ID "id"
74 #define HEADER_TYPE "type"
75 #define HEADER_COUNT "count"
76 #define HEADER_PROPS "props"
77 #define HEADER_TEXT "text"
78 #define HEADER_CPATH "cpath"
79 #define HEADER_PRED "pred"
80 #define HEADER_COPYFROM "copyfrom"
81 #define HEADER_COPYROOT "copyroot"
82 #define HEADER_FRESHTXNRT "is-fresh-txn-root"
83 #define HEADER_MINFO_HERE "minfo-here"
84 #define HEADER_MINFO_CNT "minfo-cnt"
86 /* Kinds that a change can be. */
87 #define ACTION_MODIFY "modify"
88 #define ACTION_ADD "add"
89 #define ACTION_DELETE "delete"
90 #define ACTION_REPLACE "replace"
91 #define ACTION_RESET "reset"
93 /* True and False flags. */
94 #define FLAG_TRUE "true"
95 #define FLAG_FALSE "false"
97 /* Kinds that a node-rev can be. */
98 #define KIND_FILE "file"
99 #define KIND_DIR "dir"
101 /* Kinds of representation. */
102 #define REP_PLAIN "PLAIN"
103 #define REP_DELTA "DELTA"
105 /* Notes:
107 To avoid opening and closing the rev-files all the time, it would
108 probably be advantageous to keep each rev-file open for the
109 lifetime of the transaction object. I'll leave that as a later
110 optimization for now.
112 I didn't keep track of pool lifetimes at all in this code. There
113 are likely some errors because of that.
117 /* The vtable associated with an open transaction object. */
118 static txn_vtable_t txn_vtable = {
119 svn_fs_fs__commit_txn,
120 svn_fs_fs__abort_txn,
121 svn_fs_fs__txn_prop,
122 svn_fs_fs__txn_proplist,
123 svn_fs_fs__change_txn_prop,
124 svn_fs_fs__txn_root,
125 svn_fs_fs__change_txn_props
128 /* Pathname helper functions */
130 static const char *
131 path_format(svn_fs_t *fs, apr_pool_t *pool)
133 return svn_path_join(fs->path, PATH_FORMAT, pool);
136 static APR_INLINE const char *
137 path_uuid(svn_fs_t *fs, apr_pool_t *pool)
139 return svn_path_join(fs->path, PATH_UUID, pool);
142 const char *
143 svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
145 return svn_path_join(fs->path, PATH_CURRENT, pool);
148 static APR_INLINE const char *
149 path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
151 return svn_path_join(fs->path, PATH_TXN_CURRENT, pool);
154 static APR_INLINE const char *
155 path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
157 return svn_path_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
160 static APR_INLINE const char *
161 path_lock(svn_fs_t *fs, apr_pool_t *pool)
163 return svn_path_join(fs->path, PATH_LOCK_FILE, pool);
166 static const char *
167 path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
169 fs_fs_data_t *ffd = fs->fsap_data;
171 assert(ffd->max_files_per_dir);
172 return svn_path_join_many(pool, fs->path, PATH_REVS_DIR,
173 apr_psprintf(pool, "%ld",
174 rev / ffd->max_files_per_dir),
175 NULL);
178 const char *
179 svn_fs_fs__path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
181 fs_fs_data_t *ffd = fs->fsap_data;
183 if (ffd->max_files_per_dir)
185 return svn_path_join(path_rev_shard(fs, rev, pool),
186 apr_psprintf(pool, "%ld", rev),
187 pool);
190 return svn_path_join_many(pool, fs->path, PATH_REVS_DIR,
191 apr_psprintf(pool, "%ld", rev), NULL);
194 static const char *
195 path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
197 fs_fs_data_t *ffd = fs->fsap_data;
199 assert(ffd->max_files_per_dir);
200 return svn_path_join_many(pool, fs->path, PATH_REVPROPS_DIR,
201 apr_psprintf(pool, "%ld",
202 rev / ffd->max_files_per_dir),
203 NULL);
206 static const char *
207 path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
209 fs_fs_data_t *ffd = fs->fsap_data;
211 if (ffd->max_files_per_dir)
213 return svn_path_join(path_revprops_shard(fs, rev, pool),
214 apr_psprintf(pool, "%ld", rev),
215 pool);
218 return svn_path_join_many(pool, fs->path, PATH_REVPROPS_DIR,
219 apr_psprintf(pool, "%ld", rev), NULL);
222 static APR_INLINE const char *
223 path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
225 return svn_path_join_many(pool, fs->path, PATH_TXNS_DIR,
226 apr_pstrcat(pool, txn_id, PATH_EXT_TXN, NULL),
227 NULL);
230 static APR_INLINE const char *
231 path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
233 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
236 static APR_INLINE const char *
237 path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
239 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
242 static APR_INLINE const char *
243 path_txn_mergeinfo(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
245 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_MERGEINFO,
246 pool);
249 static APR_INLINE const char *
250 path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
252 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
255 static APR_INLINE const char *
256 path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
258 fs_fs_data_t *ffd = fs->fsap_data;
259 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
260 return svn_path_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
261 apr_pstrcat(pool, txn_id, PATH_EXT_REV, NULL),
262 NULL);
263 else
264 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
267 static APR_INLINE const char *
268 path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
270 fs_fs_data_t *ffd = fs->fsap_data;
271 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
272 return svn_path_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
273 apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
274 NULL),
275 NULL);
276 else
277 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK, pool);
280 static const char *
281 path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
283 const char *txn_id = svn_fs_fs__id_txn_id(id);
284 const char *node_id = svn_fs_fs__id_node_id(id);
285 const char *copy_id = svn_fs_fs__id_copy_id(id);
286 const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
287 node_id, copy_id);
289 return svn_path_join(path_txn_dir(fs, txn_id, pool), name, pool);
292 static APR_INLINE const char *
293 path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
295 return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
296 NULL);
299 static APR_INLINE const char *
300 path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
302 return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
303 PATH_EXT_CHILDREN, NULL);
306 static APR_INLINE const char *
307 path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
309 return svn_path_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
310 node_id, NULL);
314 /* Functions for working with shared transaction data. */
316 /* Return the transaction object for transaction TXN_ID from the
317 transaction list of filesystem FS (which must already be locked via the
318 txn_list_lock mutex). If the transaction does not exist in the list,
319 then create a new transaction object and return it (if CREATE_NEW is
320 true) or return NULL (otherwise). */
321 static fs_fs_shared_txn_data_t *
322 get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
324 fs_fs_data_t *ffd = fs->fsap_data;
325 fs_fs_shared_data_t *ffsd = ffd->shared;
326 fs_fs_shared_txn_data_t *txn;
328 for (txn = ffsd->txns; txn; txn = txn->next)
329 if (strcmp(txn->txn_id, txn_id) == 0)
330 break;
332 if (txn || !create_new)
333 return txn;
335 /* Use the transaction object from the (single-object) freelist,
336 if one is available, or otherwise create a new object. */
337 if (ffsd->free_txn)
339 txn = ffsd->free_txn;
340 ffsd->free_txn = NULL;
342 else
344 apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
345 txn = apr_palloc(subpool, sizeof(*txn));
346 txn->pool = subpool;
349 assert(strlen(txn_id) < sizeof(txn->txn_id));
350 strcpy(txn->txn_id, txn_id);
351 txn->being_written = FALSE;
353 /* Link this transaction into the head of the list. We will typically
354 be dealing with only one active transaction at a time, so it makes
355 sense for searches through the transaction list to look at the
356 newest transactions first. */
357 txn->next = ffsd->txns;
358 ffsd->txns = txn;
360 return txn;
363 /* Free the transaction object for transaction TXN_ID, and remove it
364 from the transaction list of filesystem FS (which must already be
365 locked via the txn_list_lock mutex). Do nothing if the transaction
366 does not exist. */
367 static void
368 free_shared_txn(svn_fs_t *fs, const char *txn_id)
370 fs_fs_data_t *ffd = fs->fsap_data;
371 fs_fs_shared_data_t *ffsd = ffd->shared;
372 fs_fs_shared_txn_data_t *txn, *prev = NULL;
374 for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
375 if (strcmp(txn->txn_id, txn_id) == 0)
376 break;
378 if (!txn)
379 return;
381 if (prev)
382 prev->next = txn->next;
383 else
384 ffsd->txns = txn->next;
386 /* As we typically will be dealing with one transaction after another,
387 we will maintain a single-object free list so that we can hopefully
388 keep reusing the same transaction object. */
389 if (!ffsd->free_txn)
390 ffsd->free_txn = txn;
391 else
392 svn_pool_destroy(txn->pool);
396 /* Obtain a lock on the transaction list of filesystem FS, call BODY
397 with FS, BATON, and POOL, and then unlock the transaction list.
398 Return what BODY returned. */
399 static svn_error_t *
400 with_txnlist_lock(svn_fs_t *fs,
401 svn_error_t *(*body)(svn_fs_t *fs,
402 const void *baton,
403 apr_pool_t *pool),
404 void *baton,
405 apr_pool_t *pool)
407 svn_error_t *err;
408 #if APR_HAS_THREADS
409 fs_fs_data_t *ffd = fs->fsap_data;
410 fs_fs_shared_data_t *ffsd = ffd->shared;
411 apr_status_t apr_err;
413 apr_err = apr_thread_mutex_lock(ffsd->txn_list_lock);
414 if (apr_err)
415 return svn_error_wrap_apr(apr_err, _("Can't grab FSFS txn list mutex"));
416 #endif
418 err = body(fs, baton, pool);
420 #if APR_HAS_THREADS
421 apr_err = apr_thread_mutex_unlock(ffsd->txn_list_lock);
422 if (apr_err && !err)
423 return svn_error_wrap_apr(apr_err, _("Can't ungrab FSFS txn list mutex"));
424 #endif
426 return err;
430 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
431 static svn_error_t *
432 get_lock_on_filesystem(const char *lock_filename,
433 apr_pool_t *pool)
435 svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
437 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
439 /* No lock file? No big deal; these are just empty files
440 anyway. Create it and try again. */
441 svn_error_clear(err);
442 err = NULL;
444 SVN_ERR(svn_io_file_create(lock_filename, "", pool));
445 SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
448 return err;
451 /* Obtain a write lock on the file LOCK_FILENAME (protecting with
452 LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
453 BATON and that subpool, destroy the subpool (releasing the write
454 lock) and return what BODY returned. */
455 static svn_error_t *
456 with_some_lock(svn_error_t *(*body)(void *baton,
457 apr_pool_t *pool),
458 void *baton,
459 const char *lock_filename,
460 #if APR_HAS_THREADS
461 apr_thread_mutex_t *lock_mutex,
462 #endif
463 apr_pool_t *pool)
465 apr_pool_t *subpool = svn_pool_create(pool);
466 svn_error_t *err;
468 #if APR_HAS_THREADS
469 apr_status_t status;
471 /* POSIX fcntl locks are per-process, so we need to serialize locks
472 within the process. */
473 status = apr_thread_mutex_lock(lock_mutex);
474 if (status)
475 return svn_error_wrap_apr(status,
476 _("Can't grab FSFS mutex for '%s'"),
477 lock_filename);
478 #endif
480 err = get_lock_on_filesystem(lock_filename, subpool);
482 if (!err)
483 err = body(baton, subpool);
485 svn_pool_destroy(subpool);
487 #if APR_HAS_THREADS
488 status = apr_thread_mutex_unlock(lock_mutex);
489 if (status && !err)
490 return svn_error_wrap_apr(status,
491 _("Can't ungrab FSFS mutex for '%s'"),
492 lock_filename);
493 #endif
495 return err;
498 svn_error_t *
499 svn_fs_fs__with_write_lock(svn_fs_t *fs,
500 svn_error_t *(*body)(void *baton,
501 apr_pool_t *pool),
502 void *baton,
503 apr_pool_t *pool)
505 #if APR_HAS_THREADS
506 fs_fs_data_t *ffd = fs->fsap_data;
507 fs_fs_shared_data_t *ffsd = ffd->shared;
508 apr_thread_mutex_t *mutex = ffsd->fs_write_lock;
509 #endif
511 return with_some_lock(body, baton,
512 path_lock(fs, pool),
513 #if APR_HAS_THREADS
514 mutex,
515 #endif
516 pool);
519 /* Run BODY (with BATON and POOL) while the txn-current file
520 of FS is locked. */
521 static svn_error_t *
522 with_txn_current_lock(svn_fs_t *fs,
523 svn_error_t *(*body)(void *baton,
524 apr_pool_t *pool),
525 void *baton,
526 apr_pool_t *pool)
528 #if APR_HAS_THREADS
529 fs_fs_data_t *ffd = fs->fsap_data;
530 fs_fs_shared_data_t *ffsd = ffd->shared;
531 apr_thread_mutex_t *mutex = ffsd->txn_current_lock;
532 #endif
534 return with_some_lock(body, baton,
535 path_txn_current_lock(fs, pool),
536 #if APR_HAS_THREADS
537 mutex,
538 #endif
539 pool);
542 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
543 which see. */
544 struct unlock_proto_rev_baton
546 const char *txn_id;
547 void *lockcookie;
550 /* Callback used in the implementation of unlock_proto_rev(). */
551 static svn_error_t *
552 unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
554 const struct unlock_proto_rev_baton *b = baton;
555 const char *txn_id = b->txn_id;
556 apr_file_t *lockfile = b->lockcookie;
557 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
558 apr_status_t apr_err;
560 if (!txn)
561 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
562 _("Can't unlock unknown transaction '%s'"),
563 txn_id);
564 if (!txn->being_written)
565 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
566 _("Can't unlock nonlocked transaction '%s'"),
567 txn_id);
569 apr_err = apr_file_unlock(lockfile);
570 if (apr_err)
571 return svn_error_wrap_apr
572 (apr_err,
573 _("Can't unlock prototype revision lockfile for transaction '%s'"),
574 txn_id);
575 apr_err = apr_file_close(lockfile);
576 if (apr_err)
577 return svn_error_wrap_apr
578 (apr_err,
579 _("Can't close prototype revision lockfile for transaction '%s'"),
580 txn_id);
582 txn->being_written = FALSE;
584 return SVN_NO_ERROR;
587 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
588 FS using cookie LOCKCOOKIE. The original prototype revision file must
589 have been closed _before_ calling this function.
591 Perform temporary allocations in POOL. */
592 static svn_error_t *
593 unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
594 apr_pool_t *pool)
596 struct unlock_proto_rev_baton b;
598 b.txn_id = txn_id;
599 b.lockcookie = lockcookie;
600 return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
603 /* Same as unlock_proto_rev(), but requires that the transaction list
604 lock is already held. */
605 static svn_error_t *
606 unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
607 void *lockcookie,
608 apr_pool_t *pool)
610 struct unlock_proto_rev_baton b;
612 b.txn_id = txn_id;
613 b.lockcookie = lockcookie;
614 return unlock_proto_rev_body(fs, &b, pool);
617 /* A structure used by get_writable_proto_rev() and
618 get_writable_proto_rev_body(), which see. */
619 struct get_writable_proto_rev_baton
621 apr_file_t **file;
622 void **lockcookie;
623 const char *txn_id;
626 /* Callback used in the implementation of get_writable_proto_rev(). */
627 static svn_error_t *
628 get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
630 const struct get_writable_proto_rev_baton *b = baton;
631 apr_file_t **file = b->file;
632 void **lockcookie = b->lockcookie;
633 const char *txn_id = b->txn_id;
634 svn_error_t *err;
635 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
637 /* First, ensure that no thread in this process (including this one)
638 is currently writing to this transaction's proto-rev file. */
639 if (txn->being_written)
640 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
641 _("Cannot write to the prototype revision file "
642 "of transaction '%s' because a previous "
643 "representation is currently being written by "
644 "this process"),
645 txn_id);
648 /* We know that no thread in this process is writing to the proto-rev
649 file, and by extension, that no thread in this process is holding a
650 lock on the prototype revision lock file. It is therefore safe
651 for us to attempt to lock this file, to see if any other process
652 is holding a lock. */
655 apr_file_t *lockfile;
656 apr_status_t apr_err;
657 const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
659 /* Open the proto-rev lockfile, creating it if necessary, as it may
660 not exist if the transaction dates from before the lockfiles were
661 introduced.
663 ### We'd also like to use something like svn_io_file_lock2(), but
664 that forces us to create a subpool just to be able to unlock
665 the file, which seems a waste. */
666 SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
667 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
669 apr_err = apr_file_lock(lockfile,
670 APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
671 if (apr_err)
673 svn_error_clear(svn_io_file_close(lockfile, pool));
675 if (APR_STATUS_IS_EAGAIN(apr_err))
676 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
677 _("Cannot write to the prototype revision "
678 "file of transaction '%s' because a "
679 "previous representation is currently "
680 "being written by another process"),
681 txn_id);
683 return svn_error_wrap_apr(apr_err,
684 _("Can't get exclusive lock on file '%s'"),
685 svn_path_local_style(lockfile_path, pool));
688 *lockcookie = lockfile;
691 /* We've successfully locked the transaction; mark it as such. */
692 txn->being_written = TRUE;
695 /* Now open the prototype revision file and seek to the end. */
696 err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
697 APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
699 /* You might expect that we could dispense with the following seek
700 and achieve the same thing by opening the file using APR_APPEND.
701 Unfortunately, APR's buffered file implementation unconditionally
702 places its initial file pointer at the start of the file (even for
703 files opened with APR_APPEND), so we need this seek to reconcile
704 the APR file pointer to the OS file pointer (since we need to be
705 able to read the current file position later). */
706 if (!err)
708 apr_off_t offset = 0;
709 err = svn_io_file_seek(*file, APR_END, &offset, 0);
712 if (err)
714 svn_error_clear(unlock_proto_rev_list_locked(fs, txn_id, *lockcookie,
715 pool));
716 *lockcookie = NULL;
719 return err;
722 /* Get a handle to the prototype revision file for transaction TXN_ID in
723 filesystem FS, and lock it for writing. Return FILE, a file handle
724 positioned at the end of the file, and LOCKCOOKIE, a cookie that
725 should be passed to unlock_proto_rev() to unlock the file once FILE
726 has been closed.
728 If the prototype revision file is already locked, return error
729 SVN_ERR_FS_REP_BEING_WRITTEN.
731 Perform all allocations in POOL. */
732 static svn_error_t *
733 get_writable_proto_rev(apr_file_t **file,
734 void **lockcookie,
735 svn_fs_t *fs, const char *txn_id,
736 apr_pool_t *pool)
738 struct get_writable_proto_rev_baton b;
740 b.file = file;
741 b.lockcookie = lockcookie;
742 b.txn_id = txn_id;
744 return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
747 /* Callback used in the implementation of purge_shared_txn(). */
748 static svn_error_t *
749 purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
751 const char *txn_id = *(const char **)baton;
753 free_shared_txn(fs, txn_id);
754 return SVN_NO_ERROR;
757 /* Purge the shared data for transaction TXN_ID in filesystem FS.
758 Perform all allocations in POOL. */
759 static svn_error_t *
760 purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
762 return with_txnlist_lock(fs, purge_shared_txn_body, (char **) &txn_id, pool);
767 /* Fetch the current offset of FILE into *OFFSET_P. */
768 static svn_error_t *
769 get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
771 apr_off_t offset;
773 /* Note that, for buffered files, one (possibly surprising) side-effect
774 of this call is to flush any unwritten data to disk. */
775 offset = 0;
776 SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
777 *offset_p = offset;
779 return SVN_NO_ERROR;
783 /* Check that BUF, a buffer of text from format file PATH, contains
784 only digits, raising error SVN_ERR_BAD_VERSION_FILE_FORMAT if not.
786 Uses POOL for temporary allocation. */
787 static svn_error_t *
788 check_format_file_buffer_numeric(const char *buf, const char *path,
789 apr_pool_t *pool)
791 const char *p;
793 for (p = buf; *p; p++)
794 if (!apr_isdigit(*p))
795 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
796 _("Format file '%s' contains an unexpected non-digit"),
797 svn_path_local_style(path, pool));
799 return SVN_NO_ERROR;
802 /* Read the format number and maximum number of files per directory
803 from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
804 respectively.
806 *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
807 will be set to zero if a linear scheme should be used.
809 Use POOL for temporary allocation. */
810 static svn_error_t *
811 read_format(int *pformat, int *max_files_per_dir,
812 const char *path, apr_pool_t *pool)
814 svn_error_t *err;
815 apr_file_t *file;
816 char buf[80];
817 apr_size_t len;
819 err = svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
820 APR_OS_DEFAULT, pool);
821 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
823 /* Treat an absent format file as format 1. Do not try to
824 create the format file on the fly, because the repository
825 might be read-only for us, or this might be a read-only
826 operation, and the spirit of FSFS is to make no changes
827 whatseover in read-only operations. See thread starting at
828 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
829 for more. */
830 svn_error_clear(err);
831 *pformat = 1;
832 *max_files_per_dir = 0;
834 return SVN_NO_ERROR;
837 len = sizeof(buf);
838 err = svn_io_read_length_line(file, buf, &len, pool);
839 if (err && APR_STATUS_IS_EOF(err->apr_err))
841 /* Return a more useful error message. */
842 svn_error_clear(err);
843 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
844 _("Can't read first line of format file '%s'"),
845 svn_path_local_style(path, pool));
847 SVN_ERR(err);
849 /* Check that the first line contains only digits. */
850 SVN_ERR(check_format_file_buffer_numeric(buf, path, pool));
851 *pformat = atoi(buf);
853 /* Set the default values for anything that can be set via an option. */
854 *max_files_per_dir = 0;
856 /* Read any options. */
857 while (1)
859 len = sizeof(buf);
860 err = svn_io_read_length_line(file, buf, &len, pool);
861 if (err && APR_STATUS_IS_EOF(err->apr_err))
863 /* No more options; that's okay. */
864 svn_error_clear(err);
865 break;
867 SVN_ERR(err);
869 if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
870 strncmp(buf, "layout ", 7) == 0)
872 if (strcmp(buf+7, "linear") == 0)
874 *max_files_per_dir = 0;
875 continue;
878 if (strncmp(buf+7, "sharded ", 8) == 0)
880 /* Check that the argument is numeric. */
881 SVN_ERR(check_format_file_buffer_numeric(buf+15, path, pool));
882 *max_files_per_dir = atoi(buf+15);
883 continue;
887 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
888 _("'%s' contains invalid filesystem format option '%s'"),
889 svn_path_local_style(path, pool), buf);
892 SVN_ERR(svn_io_file_close(file, pool));
894 return SVN_NO_ERROR;
897 /* Write the format number and maximum number of files per directory
898 to a new format file in PATH.
900 Use POOL for temporary allocation. */
901 static svn_error_t *
902 write_format(const char *path, int format, int max_files_per_dir,
903 apr_pool_t *pool)
905 /* svn_io_write_version_file() does a load of magic to allow it to
906 replace version files that already exist. Luckily, we never need to
907 do that. */
908 const char *contents;
910 assert (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
911 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
913 if (max_files_per_dir)
914 contents = apr_psprintf(pool,
915 "%d\n"
916 "layout sharded %d\n",
917 format, max_files_per_dir);
918 else
919 contents = apr_psprintf(pool,
920 "%d\n"
921 "layout linear",
922 format);
924 else
926 contents = apr_psprintf(pool, "%d\n", format);
929 /* Create the file */
930 SVN_ERR(svn_io_file_create(path, contents, pool));
931 /* And set the perms to make it read only */
932 SVN_ERR(svn_io_set_file_read_only(path, FALSE, pool));
934 return SVN_NO_ERROR;
938 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
939 number is not the same as a format number supported by this
940 Subversion. */
941 static svn_error_t *
942 check_format(int format)
944 /* We support all formats from 1-current simultaneously */
945 if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
946 return SVN_NO_ERROR;
948 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
949 _("Expected FS format between '1' and '%d'; found format '%d'"),
950 SVN_FS_FS__FORMAT_NUMBER, format);
953 static svn_error_t *
954 get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
956 svn_error_t *
957 svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
959 fs_fs_data_t *ffd = fs->fsap_data;
960 apr_file_t *uuid_file;
961 int format, max_files_per_dir;
962 char buf[APR_UUID_FORMATTED_LENGTH + 2];
963 apr_size_t limit;
965 fs->path = apr_pstrdup(fs->pool, path);
967 /* Read the FS format number. */
968 SVN_ERR(read_format(&format, &max_files_per_dir,
969 path_format(fs, pool), pool));
971 /* Now we've got a format number no matter what. */
972 ffd->format = format;
973 ffd->max_files_per_dir = max_files_per_dir;
974 SVN_ERR(check_format(format));
976 /* Read in and cache the repository uuid. */
977 SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
978 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
980 limit = sizeof(buf);
981 SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
982 ffd->uuid = apr_pstrdup(fs->pool, buf);
984 SVN_ERR(svn_io_file_close(uuid_file, pool));
986 SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), path, pool));
988 return SVN_NO_ERROR;
991 /* SVN_ERR-like macros for dealing with ESTALE
993 * In NFS v3 and under, the server doesn't track opened files. If you
994 * unlink(2) or rename(2) a file held open by another process *on the
995 * same host*, that host's kernel typically renames the file to
996 * .nfsXXXX and automatically deletes that when it's no longer open,
997 * but this behavior is not required.
999 * For obvious reasons, this does not work *across hosts*. No one
1000 * knows about the opened file; not the server, and not the deleting
1001 * client. So the file vanishes, and the reader gets stale NFS file
1002 * handle. We have this problem with revprops files, current, and
1003 * txn-current.
1005 * Wrap opens and reads of such files with SVN_RETRY_ESTALE and closes
1006 * with SVN_IGNORE_ESTALE. Call these macros within a loop of
1007 * SVN_ESTALE_RETRY_COUNT iterations (though, realistically, the
1008 * second try will succeed). Make sure you put a break statement
1009 * after the close, at the end of your loop. Immediately after your
1010 * loop, return err if err.
1012 * You must initialize err to SVN_NO_ERROR, as these macros do not.
1015 #define SVN_ESTALE_RETRY_COUNT 10
1017 #ifdef ESTALE
1018 #define SVN_RETRY_ESTALE(err, expr) \
1020 /* Clear err here (svn_error_clear can safely be passed
1021 * SVN_NO_ERROR) rather than after finding ESTALE so we can return
1022 * the ESTALE error on the last iteration of the loop. */ \
1023 svn_error_clear(err); \
1024 err = (expr); \
1025 if (err) \
1027 if (APR_TO_OS_ERROR(err->apr_err) == ESTALE) \
1028 continue; \
1029 return err; \
1032 #define SVN_IGNORE_ESTALE(err, expr) \
1034 svn_error_clear(err); \
1035 err = (expr); \
1036 if (err) \
1038 if (APR_TO_OS_ERROR(err->apr_err) != ESTALE) \
1039 return err; \
1042 #else
1043 #define SVN_RETRY_ESTALE(err, expr) SVN_ERR(expr)
1044 #define SVN_IGNORE_ESTALE(err, expr) SVN_ERR(expr)
1045 #endif
1047 /* Long enough to hold: "<svn_revnum_t> <node id> <copy id>\0"
1048 * 19 bytes for svn_revnum_t (room for 32 or 64 bit values)
1049 * + 2 spaces
1050 * + 26 bytes for each id (these are actually unbounded, so we just
1051 * have to pick something; 2^64 is 13 bytes in base-36)
1052 * + 1 terminating null
1054 #define CURRENT_BUF_LEN 48
1056 /* Read the 'current' file FNAME and store the contents in *BUF.
1057 Allocations are performed in POOL. */
1058 static svn_error_t *
1059 read_current(const char *fname, char **buf, apr_pool_t *pool)
1061 apr_file_t *revision_file;
1062 apr_size_t len;
1063 int i;
1064 svn_error_t *err = SVN_NO_ERROR;
1065 apr_pool_t *iterpool;
1067 *buf = apr_palloc(pool, CURRENT_BUF_LEN);
1068 iterpool = svn_pool_create(pool);
1069 for (i = 0; i < SVN_ESTALE_RETRY_COUNT; i++)
1071 svn_pool_clear(iterpool);
1073 SVN_RETRY_ESTALE(err, svn_io_file_open(&revision_file, fname,
1074 APR_READ | APR_BUFFERED,
1075 APR_OS_DEFAULT, iterpool));
1077 len = CURRENT_BUF_LEN;
1078 SVN_RETRY_ESTALE(err, svn_io_read_length_line(revision_file,
1079 *buf, &len, iterpool));
1080 SVN_IGNORE_ESTALE(err, svn_io_file_close(revision_file, iterpool));
1082 break;
1084 svn_pool_destroy(iterpool);
1086 return err;
1089 /* Find the youngest revision in a repository at path FS_PATH and
1090 return it in *YOUNGEST_P. Perform temporary allocations in
1091 POOL. */
1092 static svn_error_t *
1093 get_youngest(svn_revnum_t *youngest_p,
1094 const char *fs_path,
1095 apr_pool_t *pool)
1097 char *buf;
1099 SVN_ERR(read_current(svn_path_join(fs_path, PATH_CURRENT, pool),
1100 &buf, pool));
1102 *youngest_p = SVN_STR_TO_REV(buf);
1104 return SVN_NO_ERROR;
1107 svn_error_t *
1108 svn_fs_fs__hotcopy(const char *src_path,
1109 const char *dst_path,
1110 apr_pool_t *pool)
1112 const char *src_subdir, *dst_subdir;
1113 svn_revnum_t youngest, rev;
1114 apr_pool_t *iterpool;
1115 svn_node_kind_t kind;
1116 int format, max_files_per_dir;
1118 /* Check format to be sure we know how to hotcopy this FS. */
1119 SVN_ERR(read_format(&format, &max_files_per_dir,
1120 svn_path_join(src_path, PATH_FORMAT, pool),
1121 pool));
1122 SVN_ERR(check_format(format));
1124 /* Copy the current file. */
1125 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_CURRENT, pool));
1127 /* Copy the uuid. */
1128 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_UUID, pool));
1130 /* Find the youngest revision from this current file. */
1131 SVN_ERR(get_youngest(&youngest, dst_path, pool));
1133 /* Copy the necessary rev files. */
1134 src_subdir = svn_path_join(src_path, PATH_REVS_DIR, pool);
1135 dst_subdir = svn_path_join(dst_path, PATH_REVS_DIR, pool);
1137 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1139 iterpool = svn_pool_create(pool);
1140 for (rev = 0; rev <= youngest; rev++)
1142 const char *src_subdir_shard = src_subdir,
1143 *dst_subdir_shard = dst_subdir;
1145 if (max_files_per_dir)
1147 const char *shard = apr_psprintf(iterpool, "%ld",
1148 rev / max_files_per_dir);
1149 src_subdir_shard = svn_path_join(src_subdir, shard, iterpool);
1150 dst_subdir_shard = svn_path_join(dst_subdir, shard, iterpool);
1152 if (rev % max_files_per_dir == 0)
1153 SVN_ERR(svn_io_dir_make(dst_subdir_shard, APR_OS_DEFAULT,
1154 iterpool));
1157 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
1158 apr_psprintf(iterpool, "%ld", rev),
1159 iterpool));
1160 svn_pool_clear(iterpool);
1163 /* Copy the necessary revprop files. */
1164 src_subdir = svn_path_join(src_path, PATH_REVPROPS_DIR, pool);
1165 dst_subdir = svn_path_join(dst_path, PATH_REVPROPS_DIR, pool);
1167 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1169 for (rev = 0; rev <= youngest; rev++)
1171 const char *src_subdir_shard = src_subdir,
1172 *dst_subdir_shard = dst_subdir;
1174 svn_pool_clear(iterpool);
1176 if (max_files_per_dir)
1178 const char *shard = apr_psprintf(iterpool, "%ld",
1179 rev / max_files_per_dir);
1180 src_subdir_shard = svn_path_join(src_subdir, shard, iterpool);
1181 dst_subdir_shard = svn_path_join(dst_subdir, shard, iterpool);
1183 if (rev % max_files_per_dir == 0)
1184 SVN_ERR(svn_io_dir_make(dst_subdir_shard, APR_OS_DEFAULT,
1185 iterpool));
1188 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
1189 apr_psprintf(iterpool, "%ld", rev),
1190 iterpool));
1193 svn_pool_destroy(iterpool);
1195 /* Make an empty transactions directory for now. Eventually some
1196 method of copying in progress transactions will need to be
1197 developed.*/
1198 dst_subdir = svn_path_join(dst_path, PATH_TXNS_DIR, pool);
1199 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1200 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1202 dst_subdir = svn_path_join(dst_path, PATH_TXN_PROTOS_DIR, pool);
1203 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1206 /* Now copy the locks tree. */
1207 src_subdir = svn_path_join(src_path, PATH_LOCKS_DIR, pool);
1208 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
1209 if (kind == svn_node_dir)
1210 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_path,
1211 PATH_LOCKS_DIR, TRUE, NULL,
1212 NULL, pool));
1214 /* Now copy the node-origins cache tree. */
1215 src_subdir = svn_path_join(src_path, PATH_NODE_ORIGINS_DIR, pool);
1216 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
1217 if (kind == svn_node_dir)
1218 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_path,
1219 PATH_NODE_ORIGINS_DIR, TRUE, NULL,
1220 NULL, pool));
1222 /* Copy the txn-current file. */
1223 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1224 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_TXN_CURRENT, pool));
1226 /* Hotcopied FS is complete. Stamp it with a format file. */
1227 SVN_ERR(write_format(svn_path_join(dst_path, PATH_FORMAT, pool),
1228 format, max_files_per_dir, pool));
1230 return SVN_NO_ERROR;
1233 svn_error_t *
1234 svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1235 svn_fs_t *fs,
1236 apr_pool_t *pool)
1238 fs_fs_data_t *ffd = fs->fsap_data;
1240 SVN_ERR(get_youngest(youngest_p, fs->path, pool));
1241 ffd->youngest_rev_cache = *youngest_p;
1243 return SVN_NO_ERROR;
1246 /* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN
1247 * bytes plus the stuff around them. */
1248 #define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1
1250 /* Given a revision file FILE that has been pre-positioned at the
1251 beginning of a Node-Rev header block, read in that header block and
1252 store it in the apr_hash_t HEADERS. All allocations will be from
1253 POOL. */
1254 static svn_error_t * read_header_block(apr_hash_t **headers,
1255 apr_file_t *file,
1256 apr_pool_t *pool)
1258 *headers = apr_hash_make(pool);
1260 while (1)
1262 char header_str[MAX_HEADERS_STR_LEN];
1263 const char *name, *value;
1264 apr_size_t i = 0, header_len;
1265 apr_size_t limit;
1266 char *local_name, *local_value;
1268 limit = sizeof(header_str);
1269 SVN_ERR(svn_io_read_length_line(file, header_str, &limit, pool));
1271 if (strlen(header_str) == 0)
1272 break; /* end of header block */
1274 header_len = strlen(header_str);
1276 while (header_str[i] != ':')
1278 if (header_str[i] == '\0')
1279 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1280 _("Found malformed header in "
1281 "revision file"));
1282 i++;
1285 /* Create a 'name' string and point to it. */
1286 header_str[i] = '\0';
1287 name = header_str;
1289 /* Skip over the NULL byte and the space following it. */
1290 i += 2;
1292 if (i > header_len)
1293 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1294 _("Found malformed header in "
1295 "revision file"));
1297 value = header_str + i;
1299 local_name = apr_pstrdup(pool, name);
1300 local_value = apr_pstrdup(pool, value);
1302 apr_hash_set(*headers, local_name, APR_HASH_KEY_STRING, local_value);
1305 return SVN_NO_ERROR;
1308 /* Throw an error if the given revision is newer than the current
1309 youngest revision. */
1310 static svn_error_t *
1311 ensure_revision_exists(svn_fs_t *fs,
1312 svn_revnum_t rev,
1313 apr_pool_t *pool)
1315 fs_fs_data_t *ffd = fs->fsap_data;
1317 if (! SVN_IS_VALID_REVNUM(rev))
1318 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1319 _("Invalid revision number '%ld'"), rev);
1322 /* Did the revision exist the last time we checked the current
1323 file? */
1324 if (rev <= ffd->youngest_rev_cache)
1325 return SVN_NO_ERROR;
1327 SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
1329 /* Check again. */
1330 if (rev <= ffd->youngest_rev_cache)
1331 return SVN_NO_ERROR;
1333 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1334 _("No such revision %ld"), rev);
1337 /* Open the revision file for revision REV in filesystem FS and store
1338 the newly opened file in FILE. Seek to location OFFSET before
1339 returning. Perform temporary allocations in POOL. */
1340 static svn_error_t *
1341 open_and_seek_revision(apr_file_t **file,
1342 svn_fs_t *fs,
1343 svn_revnum_t rev,
1344 apr_off_t offset,
1345 apr_pool_t *pool)
1347 apr_file_t *rev_file;
1349 SVN_ERR(ensure_revision_exists(fs, rev, pool));
1351 SVN_ERR(svn_io_file_open(&rev_file, svn_fs_fs__path_rev(fs, rev, pool),
1352 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1354 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1356 *file = rev_file;
1358 return SVN_NO_ERROR;
1361 /* Open the representation for a node-revision in transaction TXN_ID
1362 in filesystem FS and store the newly opened file in FILE. Seek to
1363 location OFFSET before returning. Perform temporary allocations in
1364 POOL. Only appropriate for file contents, nor props or directory
1365 contents. */
1366 static svn_error_t *
1367 open_and_seek_transaction(apr_file_t **file,
1368 svn_fs_t *fs,
1369 const char *txn_id,
1370 representation_t *rep,
1371 apr_pool_t *pool)
1373 apr_file_t *rev_file;
1374 apr_off_t offset;
1376 SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
1377 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1379 offset = rep->offset;
1380 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1382 *file = rev_file;
1384 return SVN_NO_ERROR;
1387 /* Given a node-id ID, and a representation REP in filesystem FS, open
1388 the correct file and seek to the correction location. Store this
1389 file in *FILE_P. Perform any allocations in POOL. */
1390 static svn_error_t *
1391 open_and_seek_representation(apr_file_t **file_p,
1392 svn_fs_t *fs,
1393 representation_t *rep,
1394 apr_pool_t *pool)
1396 if (! rep->txn_id)
1397 return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
1398 pool);
1399 else
1400 return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
1403 /* Parse the description of a representation from STRING and store it
1404 into *REP_P. If the representation is mutable (the revision is
1405 given as -1), then use TXN_ID for the representation's txn_id
1406 field. If MUTABLE_REP_TRUNCATED is true, then this representation
1407 is for property or directory contents, and no information will be
1408 expected except the "-1" revision number for a mutable
1409 representation. Allocate *REP_P in POOL. */
1410 static svn_error_t *
1411 read_rep_offsets(representation_t **rep_p,
1412 char *string,
1413 const char *txn_id,
1414 svn_boolean_t mutable_rep_truncated,
1415 apr_pool_t *pool)
1417 representation_t *rep;
1418 char *str, *last_str;
1419 int i;
1421 rep = apr_pcalloc(pool, sizeof(*rep));
1422 *rep_p = rep;
1424 str = apr_strtok(string, " ", &last_str);
1425 if (str == NULL)
1426 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1427 _("Malformed text rep offset line in node-rev"));
1430 rep->revision = SVN_STR_TO_REV(str);
1431 if (rep->revision == SVN_INVALID_REVNUM)
1433 rep->txn_id = txn_id;
1434 if (mutable_rep_truncated)
1435 return SVN_NO_ERROR;
1438 str = apr_strtok(NULL, " ", &last_str);
1439 if (str == NULL)
1440 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1441 _("Malformed text rep offset line in node-rev"));
1443 rep->offset = apr_atoi64(str);
1445 str = apr_strtok(NULL, " ", &last_str);
1446 if (str == NULL)
1447 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1448 _("Malformed text rep offset line in node-rev"));
1450 rep->size = apr_atoi64(str);
1452 str = apr_strtok(NULL, " ", &last_str);
1453 if (str == NULL)
1454 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1455 _("Malformed text rep offset line in node-rev"));
1457 rep->expanded_size = apr_atoi64(str);
1459 /* Read in the MD5 hash. */
1460 str = apr_strtok(NULL, " ", &last_str);
1461 if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
1462 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1463 _("Malformed text rep offset line in node-rev"));
1465 /* Parse the hex MD5 hash into digest form. */
1466 for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
1468 if ((! isxdigit(str[i * 2])) || (! isxdigit(str[i * 2 + 1])))
1469 return svn_error_create
1470 (SVN_ERR_FS_CORRUPT, NULL,
1471 _("Malformed text rep offset line in node-rev"));
1473 str[i * 2] = tolower(str[i * 2]);
1474 rep->checksum[i] = (str[i * 2] -
1475 ((str[i * 2] <= '9') ? '0' : ('a' - 10))) << 4;
1477 str[i * 2 + 1] = tolower(str[i * 2 + 1]);
1478 rep->checksum[i] |= (str[i * 2 + 1] -
1479 ((str[i * 2 + 1] <= '9') ? '0' : ('a' - 10)));
1482 return SVN_NO_ERROR;
1485 svn_error_t *
1486 svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
1487 svn_fs_t *fs,
1488 const svn_fs_id_t *id,
1489 apr_pool_t *pool)
1491 apr_file_t *revision_file;
1492 apr_hash_t *headers;
1493 node_revision_t *noderev;
1494 char *value;
1495 svn_error_t *err;
1497 if (svn_fs_fs__id_txn_id(id))
1499 /* This is a transaction node-rev. */
1500 err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
1501 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1503 else
1505 /* This is a revision node-rev. */
1506 err = open_and_seek_revision(&revision_file, fs,
1507 svn_fs_fs__id_rev(id),
1508 svn_fs_fs__id_offset(id),
1509 pool);
1512 if (err)
1514 if (APR_STATUS_IS_ENOENT(err->apr_err))
1516 svn_error_clear(err);
1517 return svn_fs_fs__err_dangling_id(fs, id);
1520 return err;
1523 SVN_ERR(read_header_block(&headers, revision_file, pool) );
1525 noderev = apr_pcalloc(pool, sizeof(*noderev));
1527 /* Read the node-rev id. */
1528 value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
1530 SVN_ERR(svn_io_file_close(revision_file, pool));
1532 noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
1534 /* Read the type. */
1535 value = apr_hash_get(headers, HEADER_TYPE, APR_HASH_KEY_STRING);
1537 if ((value == NULL) ||
1538 (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
1539 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1540 _("Missing kind field in node-rev"));
1542 noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
1543 : svn_node_dir;
1545 /* Read the 'count' field. */
1546 value = apr_hash_get(headers, HEADER_COUNT, APR_HASH_KEY_STRING);
1547 noderev->predecessor_count = (value == NULL) ? 0 : atoi(value);
1549 /* Get the properties location. */
1550 value = apr_hash_get(headers, HEADER_PROPS, APR_HASH_KEY_STRING);
1551 if (value)
1553 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
1554 svn_fs_fs__id_txn_id(id), TRUE, pool));
1557 /* Get the data location. */
1558 value = apr_hash_get(headers, HEADER_TEXT, APR_HASH_KEY_STRING);
1559 if (value)
1561 SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
1562 svn_fs_fs__id_txn_id(id),
1563 (noderev->kind == svn_node_dir), pool));
1566 /* Get the created path. */
1567 value = apr_hash_get(headers, HEADER_CPATH, APR_HASH_KEY_STRING);
1568 if (value == NULL)
1570 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1571 _("Missing cpath in node-rev"));
1573 else
1575 noderev->created_path = apr_pstrdup(pool, value);
1578 /* Get the predecessor ID. */
1579 value = apr_hash_get(headers, HEADER_PRED, APR_HASH_KEY_STRING);
1580 if (value)
1581 noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
1582 pool);
1584 /* Get the copyroot. */
1585 value = apr_hash_get(headers, HEADER_COPYROOT, APR_HASH_KEY_STRING);
1586 if (value == NULL)
1588 noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
1589 noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
1591 else
1593 char *str, *last_str;
1595 str = apr_strtok(value, " ", &last_str);
1596 if (str == NULL)
1597 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1598 _("Malformed copyroot line in node-rev"));
1600 noderev->copyroot_rev = atoi(str);
1602 if (last_str == NULL)
1603 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1604 _("Malformed copyroot line in node-rev"));
1605 noderev->copyroot_path = apr_pstrdup(pool, last_str);
1608 /* Get the copyfrom. */
1609 value = apr_hash_get(headers, HEADER_COPYFROM, APR_HASH_KEY_STRING);
1610 if (value == NULL)
1612 noderev->copyfrom_path = NULL;
1613 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1615 else
1617 char *str, *last_str;
1619 str = apr_strtok(value, " ", &last_str);
1620 if (str == NULL)
1621 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1622 _("Malformed copyfrom line in node-rev"));
1624 noderev->copyfrom_rev = atoi(str);
1626 if (last_str == NULL)
1627 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1628 _("Malformed copyfrom line in node-rev"));
1629 noderev->copyfrom_path = apr_pstrdup(pool, last_str);
1632 /* Get whether this is a fresh txn root. */
1633 value = apr_hash_get(headers, HEADER_FRESHTXNRT, APR_HASH_KEY_STRING);
1634 noderev->is_fresh_txn_root = (value != NULL);
1636 /* Get the mergeinfo count. */
1637 value = apr_hash_get(headers, HEADER_MINFO_CNT, APR_HASH_KEY_STRING);
1638 noderev->mergeinfo_count = (value == NULL) ? 0 : apr_atoi64(value);
1640 /* Get whether *this* node has mergeinfo. */
1641 value = apr_hash_get(headers, HEADER_MINFO_HERE, APR_HASH_KEY_STRING);
1642 noderev->has_mergeinfo = (value != NULL);
1644 *noderev_p = noderev;
1646 return SVN_NO_ERROR;
1649 /* Return a formatted string that represents the location of
1650 representation REP. If MUTABLE_REP_TRUNCATED is given, the rep is
1651 for props or dir contents, and only a "-1" revision number will be
1652 given for a mutable rep. Perform the allocation from POOL. */
1653 static const char *
1654 representation_string(representation_t *rep,
1655 svn_boolean_t mutable_rep_truncated, apr_pool_t *pool)
1657 if (rep->txn_id && mutable_rep_truncated)
1658 return "-1";
1659 else
1660 return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
1661 " %" SVN_FILESIZE_T_FMT " %s",
1662 rep->revision, rep->offset, rep->size,
1663 rep->expanded_size,
1664 svn_md5_digest_to_cstring_display(rep->checksum,
1665 pool));
1668 /* Write the node-revision NODEREV into the file FILE. Temporary
1669 allocations are from POOL. */
1670 static svn_error_t *
1671 write_noderev_txn(apr_file_t *file,
1672 node_revision_t *noderev,
1673 apr_pool_t *pool)
1675 svn_stream_t *outfile;
1677 outfile = svn_stream_from_aprfile(file, pool);
1679 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
1680 svn_fs_fs__id_unparse(noderev->id,
1681 pool)->data));
1683 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
1684 (noderev->kind == svn_node_file) ?
1685 KIND_FILE : KIND_DIR));
1687 if (noderev->predecessor_id)
1688 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
1689 svn_fs_fs__id_unparse(noderev->predecessor_id,
1690 pool)->data));
1692 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
1693 noderev->predecessor_count));
1695 if (noderev->data_rep)
1696 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
1697 representation_string(noderev->data_rep,
1698 (noderev->kind
1699 == svn_node_dir),
1700 pool)));
1702 if (noderev->prop_rep)
1703 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
1704 representation_string(noderev->prop_rep, TRUE,
1705 pool)));
1707 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
1708 noderev->created_path));
1710 if (noderev->copyfrom_path)
1711 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
1712 " %s\n",
1713 noderev->copyfrom_rev,
1714 noderev->copyfrom_path));
1716 if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
1717 (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
1718 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
1719 " %s\n",
1720 noderev->copyroot_rev,
1721 noderev->copyroot_path));
1723 if (noderev->is_fresh_txn_root)
1724 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_FRESHTXNRT ": y\n"));
1726 if (noderev->mergeinfo_count > 0)
1727 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
1728 APR_INT64_T_FMT "\n",
1729 noderev->mergeinfo_count));
1731 if (noderev->has_mergeinfo)
1732 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_HERE ": y\n"));
1734 SVN_ERR(svn_stream_printf(outfile, pool, "\n"));
1736 return SVN_NO_ERROR;
1739 svn_error_t *
1740 svn_fs_fs__put_node_revision(svn_fs_t *fs,
1741 const svn_fs_id_t *id,
1742 node_revision_t *noderev,
1743 svn_boolean_t fresh_txn_root,
1744 apr_pool_t *pool)
1746 apr_file_t *noderev_file;
1747 const char *txn_id = svn_fs_fs__id_txn_id(id);
1749 noderev->is_fresh_txn_root = fresh_txn_root;
1751 if (! txn_id)
1752 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1753 _("Attempted to write to non-transaction"));
1755 SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
1756 APR_WRITE | APR_CREATE | APR_TRUNCATE
1757 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1759 SVN_ERR(write_noderev_txn(noderev_file, noderev, pool));
1761 SVN_ERR(svn_io_file_close(noderev_file, pool));
1763 return SVN_NO_ERROR;
1767 /* This structure is used to hold the information associated with a
1768 REP line. */
1769 struct rep_args
1771 svn_boolean_t is_delta;
1772 svn_boolean_t is_delta_vs_empty;
1774 svn_revnum_t base_revision;
1775 apr_off_t base_offset;
1776 apr_size_t base_length;
1779 /* Read the next line from file FILE and parse it as a text
1780 representation entry. Return the parsed entry in *REP_ARGS_P.
1781 Perform all allocations in POOL. */
1782 static svn_error_t *
1783 read_rep_line(struct rep_args **rep_args_p,
1784 apr_file_t *file,
1785 apr_pool_t *pool)
1787 char buffer[160];
1788 apr_size_t limit;
1789 struct rep_args *rep_args;
1790 char *str, *last_str;
1792 limit = sizeof(buffer);
1793 SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
1795 rep_args = apr_pcalloc(pool, sizeof(*rep_args));
1796 rep_args->is_delta = FALSE;
1798 if (strcmp(buffer, REP_PLAIN) == 0)
1800 *rep_args_p = rep_args;
1801 return SVN_NO_ERROR;
1804 if (strcmp(buffer, REP_DELTA) == 0)
1806 /* This is a delta against the empty stream. */
1807 rep_args->is_delta = TRUE;
1808 rep_args->is_delta_vs_empty = TRUE;
1809 *rep_args_p = rep_args;
1810 return SVN_NO_ERROR;
1813 rep_args->is_delta = TRUE;
1814 rep_args->is_delta_vs_empty = FALSE;
1816 /* We have hopefully a DELTA vs. a non-empty base revision. */
1817 str = apr_strtok(buffer, " ", &last_str);
1818 if (! str || (strcmp(str, REP_DELTA) != 0)) goto err;
1820 str = apr_strtok(NULL, " ", &last_str);
1821 if (! str) goto err;
1822 rep_args->base_revision = atol(str);
1824 str = apr_strtok(NULL, " ", &last_str);
1825 if (! str) goto err;
1826 rep_args->base_offset = (apr_off_t) apr_atoi64(str);
1828 str = apr_strtok(NULL, " ", &last_str);
1829 if (! str) goto err;
1830 rep_args->base_length = (apr_size_t) apr_atoi64(str);
1832 *rep_args_p = rep_args;
1833 return SVN_NO_ERROR;
1835 err:
1836 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1837 _("Malformed representation header"));
1840 /* Given a revision file REV_FILE, find the Node-ID of the header
1841 located at OFFSET and store it in *ID_P. Allocate temporary
1842 variables from POOL. */
1843 static svn_error_t *
1844 get_fs_id_at_offset(svn_fs_id_t **id_p,
1845 apr_file_t *rev_file,
1846 apr_off_t offset,
1847 apr_pool_t *pool)
1849 svn_fs_id_t *id;
1850 apr_hash_t *headers;
1851 const char *node_id_str;
1853 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1855 SVN_ERR(read_header_block(&headers, rev_file, pool));
1857 node_id_str = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
1859 if (node_id_str == NULL)
1860 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1861 _("Missing node-id in node-rev"));
1863 id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
1865 if (id == NULL)
1866 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1867 _("Corrupt node-id in node-rev"));
1869 *id_p = id;
1871 return SVN_NO_ERROR;
1875 /* Given an open revision file REV_FILE, locate the trailer that
1876 specifies the offset to the root node-id and to the changed path
1877 information. Store the root node offset in *ROOT_OFFSET and the
1878 changed path offset in *CHANGES_OFFSET. If either of these
1879 pointers is NULL, do nothing with it. Allocate temporary variables
1880 from POOL. */
1881 static svn_error_t *
1882 get_root_changes_offset(apr_off_t *root_offset,
1883 apr_off_t *changes_offset,
1884 apr_file_t *rev_file,
1885 apr_pool_t *pool)
1887 apr_off_t offset;
1888 char buf[64];
1889 int i, num_bytes;
1890 apr_size_t len;
1892 /* We will assume that the last line containing the two offsets
1893 will never be longer than 64 characters. */
1894 offset = 0;
1895 SVN_ERR(svn_io_file_seek(rev_file, APR_END, &offset, pool));
1897 offset -= sizeof(buf);
1898 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1900 /* Read in this last block, from which we will identify the last line. */
1901 len = sizeof(buf);
1902 SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
1904 /* This cast should be safe since the maximum amount read, 64, will
1905 never be bigger than the size of an int. */
1906 num_bytes = (int) len;
1908 /* The last byte should be a newline. */
1909 if (buf[num_bytes - 1] != '\n')
1911 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1912 _("Revision file lacks trailing newline"));
1915 /* Look for the next previous newline. */
1916 for (i = num_bytes - 2; i >= 0; i--)
1918 if (buf[i] == '\n') break;
1921 if (i < 0)
1923 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1924 _("Final line in revision file longer than 64 "
1925 "characters"));
1928 i++;
1930 if (root_offset)
1931 *root_offset = apr_atoi64(&buf[i]);
1933 /* find the next space */
1934 for ( ; i < (num_bytes - 2) ; i++)
1935 if (buf[i] == ' ') break;
1937 if (i == (num_bytes - 2))
1938 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1939 _("Final line in revision file missing space"));
1941 i++;
1943 /* note that apr_atoi64() will stop reading as soon as it encounters
1944 the final newline. */
1945 if (changes_offset)
1946 *changes_offset = apr_atoi64(&buf[i]);
1948 return SVN_NO_ERROR;
1951 svn_error_t *
1952 svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
1953 svn_fs_t *fs,
1954 svn_revnum_t rev,
1955 apr_pool_t *pool)
1957 fs_fs_data_t *ffd = fs->fsap_data;
1958 apr_file_t *revision_file;
1959 apr_off_t root_offset;
1960 svn_fs_id_t *root_id;
1961 svn_error_t *err;
1962 const char *rev_str = apr_psprintf(ffd->rev_root_id_cache_pool, "%ld", rev);
1963 svn_fs_id_t *cached_id;
1965 SVN_ERR(ensure_revision_exists(fs, rev, pool));
1967 /* Calculate an index into the revroot id cache */
1968 cached_id = apr_hash_get(ffd->rev_root_id_cache,
1969 rev_str,
1970 APR_HASH_KEY_STRING);
1972 if (cached_id)
1974 *root_id_p = svn_fs_fs__id_copy(cached_id, pool);
1975 return SVN_NO_ERROR;
1978 err = svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool),
1979 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1980 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1982 svn_error_clear(err);
1983 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1984 _("No such revision %ld"), rev);
1986 else if (err)
1987 return err;
1990 SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, pool));
1992 SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, root_offset, pool));
1994 SVN_ERR(svn_io_file_close(revision_file, pool));
1996 /* Cache it */
1997 if (apr_hash_count(ffd->rev_root_id_cache) >= NUM_RRI_CACHE_ENTRIES)
1999 /* In order to only use one pool for the whole cache, we need to
2000 * completely wipe it to expire entries! */
2001 svn_pool_clear(ffd->rev_root_id_cache_pool);
2002 ffd->rev_root_id_cache = apr_hash_make(ffd->rev_root_id_cache_pool);
2004 apr_hash_set(ffd->rev_root_id_cache, rev_str, APR_HASH_KEY_STRING,
2005 svn_fs_fs__id_copy(root_id, ffd->rev_root_id_cache_pool));
2007 *root_id_p = root_id;
2009 return SVN_NO_ERROR;
2012 svn_error_t *
2013 svn_fs_fs__set_revision_proplist(svn_fs_t *fs,
2014 svn_revnum_t rev,
2015 apr_hash_t *proplist,
2016 apr_pool_t *pool)
2018 const char *final_path = path_revprops(fs, rev, pool);
2019 const char *tmp_path;
2020 apr_file_t *f;
2022 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2024 SVN_ERR(svn_io_open_unique_file2
2025 (&f, &tmp_path, final_path, ".tmp", svn_io_file_del_none, pool));
2026 SVN_ERR(svn_hash_write(proplist, f, pool));
2027 SVN_ERR(svn_io_file_close(f, pool));
2028 /* We use the rev file of this revision as the perms reference,
2029 because when setting revprops for the first time, the revprop
2030 file won't exist and therefore can't serve as its own reference.
2031 (Whereas the rev file should already exist at this point.) */
2032 SVN_ERR(svn_fs_fs__move_into_place(tmp_path, final_path,
2033 svn_fs_fs__path_rev(fs, rev, pool),
2034 pool));
2036 return SVN_NO_ERROR;
2039 svn_error_t *
2040 svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
2041 svn_fs_t *fs,
2042 svn_revnum_t rev,
2043 apr_pool_t *pool)
2045 apr_file_t *revprop_file;
2046 apr_hash_t *proplist;
2047 svn_error_t *err = SVN_NO_ERROR;
2048 int i;
2049 apr_pool_t *iterpool;
2051 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2053 proplist = apr_hash_make(pool);
2054 iterpool = svn_pool_create(pool);
2055 for (i = 0; i < SVN_ESTALE_RETRY_COUNT; i++)
2057 svn_pool_clear(iterpool);
2059 /* Clear err here (svn_error_clear can safely be passed
2060 * SVN_NO_ERROR) rather than after finding ESTALE so we can
2061 * return the ESTALE error on the last iteration of the loop. */
2062 svn_error_clear(err);
2063 err = svn_io_file_open(&revprop_file, path_revprops(fs, rev, iterpool),
2064 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
2065 iterpool);
2066 if (err)
2068 if (APR_STATUS_IS_ENOENT(err->apr_err))
2070 svn_error_clear(err);
2071 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
2072 _("No such revision %ld"), rev);
2074 #ifdef ESTALE
2075 else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE)
2076 continue;
2077 #endif
2078 return err;
2081 SVN_ERR(svn_hash__clear(proplist));
2082 SVN_RETRY_ESTALE(err,
2083 svn_hash_read2(proplist,
2084 svn_stream_from_aprfile(revprop_file,
2085 iterpool),
2086 SVN_HASH_TERMINATOR, pool));
2088 SVN_IGNORE_ESTALE(err, svn_io_file_close(revprop_file, iterpool));
2090 break;
2092 if (err)
2093 return err;
2094 svn_pool_destroy(iterpool);
2096 *proplist_p = proplist;
2098 return SVN_NO_ERROR;
2101 /* Represents where in the current svndiff data block each
2102 representation is. */
2103 struct rep_state
2105 apr_file_t *file;
2106 apr_off_t start; /* The starting offset for the raw
2107 svndiff/plaintext data minus header. */
2108 apr_off_t off; /* The current offset into the file. */
2109 apr_off_t end; /* The end offset of the raw data. */
2110 int ver; /* If a delta, what svndiff version? */
2111 int chunk_index;
2114 /* Read the rep args for REP in filesystem FS and create a rep_state
2115 for reading the representation. Return the rep_state in *REP_STATE
2116 and the rep args in *REP_ARGS, both allocated in POOL. */
2117 static svn_error_t *
2118 create_rep_state(struct rep_state **rep_state,
2119 struct rep_args **rep_args,
2120 representation_t *rep,
2121 svn_fs_t *fs,
2122 apr_pool_t *pool)
2124 struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
2125 struct rep_args *ra;
2126 unsigned char buf[4];
2128 SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
2129 SVN_ERR(read_rep_line(&ra, rs->file, pool));
2130 SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
2131 rs->off = rs->start;
2132 rs->end = rs->start + rep->size;
2133 *rep_state = rs;
2134 *rep_args = ra;
2136 if (ra->is_delta == FALSE)
2137 /* This is a plaintext, so just return the current rep_state. */
2138 return SVN_NO_ERROR;
2140 /* We are dealing with a delta, find out what version. */
2141 SVN_ERR(svn_io_file_read_full(rs->file, buf, sizeof(buf), NULL, pool));
2142 if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
2143 return svn_error_create
2144 (SVN_ERR_FS_CORRUPT, NULL,
2145 _("Malformed svndiff data in representation"));
2146 rs->ver = buf[3];
2147 rs->chunk_index = 0;
2148 rs->off += 4;
2150 return SVN_NO_ERROR;
2153 /* Build an array of rep_state structures in *LIST giving the delta
2154 reps from first_rep to a plain-text or self-compressed rep. Set
2155 *SRC_STATE to the plain-text rep we find at the end of the chain,
2156 or to NULL if the final delta representation is self-compressed.
2157 The representation to start from is designated by filesystem FS, id
2158 ID, and representation REP. */
2159 static svn_error_t *
2160 build_rep_list(apr_array_header_t **list,
2161 struct rep_state **src_state,
2162 svn_fs_t *fs,
2163 representation_t *first_rep,
2164 apr_pool_t *pool)
2166 representation_t rep;
2167 struct rep_state *rs;
2168 struct rep_args *rep_args;
2170 *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
2171 rep = *first_rep;
2173 while (1)
2175 SVN_ERR(create_rep_state(&rs, &rep_args, &rep, fs, pool));
2176 if (rep_args->is_delta == FALSE)
2178 /* This is a plaintext, so just return the current rep_state. */
2179 *src_state = rs;
2180 return SVN_NO_ERROR;
2183 /* Push this rep onto the list. If it's self-compressed, we're done. */
2184 APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
2185 if (rep_args->is_delta_vs_empty)
2187 *src_state = NULL;
2188 return SVN_NO_ERROR;
2191 rep.revision = rep_args->base_revision;
2192 rep.offset = rep_args->base_offset;
2193 rep.size = rep_args->base_length;
2194 rep.txn_id = NULL;
2199 struct rep_read_baton
2201 /* The FS from which we're reading. */
2202 svn_fs_t *fs;
2204 /* The state of all prior delta representations. */
2205 apr_array_header_t *rs_list;
2207 /* The plaintext state, if there is a plaintext. */
2208 struct rep_state *src_state;
2210 /* The index of the current delta chunk, if we are reading a delta. */
2211 int chunk_index;
2213 /* The buffer where we store undeltified data. */
2214 char *buf;
2215 apr_size_t buf_pos;
2216 apr_size_t buf_len;
2218 /* An MD5 context for summing the data read in order to verify it. */
2219 struct apr_md5_ctx_t md5_context;
2220 svn_boolean_t checksum_finalized;
2222 /* The stored checksum of the representation we are reading, its
2223 length, and the amount we've read so far. Some of this
2224 information is redundant with rs_list and src_state, but it's
2225 convenient for the checksumming code to have it here. */
2226 unsigned char checksum[APR_MD5_DIGESTSIZE];
2227 svn_filesize_t len;
2228 svn_filesize_t off;
2230 /* Used for temporary allocations during the read. */
2231 apr_pool_t *pool;
2233 /* Pool used to store file handles and other data that is persistant
2234 for the entire stream read. */
2235 apr_pool_t *filehandle_pool;
2238 /* Create a rep_read_baton structure for node revision NODEREV in
2239 filesystem FS and store it in *RB_P. Perform all allocations in
2240 POOL. If rep is mutable, it must be for file contents. */
2241 static svn_error_t *
2242 rep_read_get_baton(struct rep_read_baton **rb_p,
2243 svn_fs_t *fs,
2244 representation_t *rep,
2245 apr_pool_t *pool)
2247 struct rep_read_baton *b;
2249 b = apr_pcalloc(pool, sizeof(*b));
2250 b->fs = fs;
2251 b->chunk_index = 0;
2252 b->buf = NULL;
2253 apr_md5_init(&(b->md5_context));
2254 b->checksum_finalized = FALSE;
2255 memcpy(b->checksum, rep->checksum, sizeof(b->checksum));
2256 b->len = rep->expanded_size;
2257 b->off = 0;
2258 b->pool = svn_pool_create(pool);
2259 b->filehandle_pool = svn_pool_create(pool);
2261 SVN_ERR(build_rep_list(&b->rs_list, &b->src_state, fs, rep,
2262 b->filehandle_pool));
2264 /* Save our output baton. */
2265 *rb_p = b;
2267 return SVN_NO_ERROR;
2270 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
2271 window into *NWIN. */
2272 static svn_error_t *
2273 read_window(svn_txdelta_window_t **nwin, int this_chunk, struct rep_state *rs,
2274 apr_pool_t *pool)
2276 svn_stream_t *stream;
2278 assert(rs->chunk_index <= this_chunk);
2280 /* Skip windows to reach the current chunk if we aren't there yet. */
2281 while (rs->chunk_index < this_chunk)
2283 SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
2284 rs->chunk_index++;
2285 SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
2286 if (rs->off >= rs->end)
2287 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2288 _("Reading one svndiff window read "
2289 "beyond the end of the "
2290 "representation"));
2293 /* Read the next window. */
2294 stream = svn_stream_from_aprfile(rs->file, pool);
2295 SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
2296 rs->chunk_index++;
2297 SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
2299 if (rs->off > rs->end)
2300 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2301 _("Reading one svndiff window read beyond "
2302 "the end of the representation"));
2304 return SVN_NO_ERROR;
2307 /* Get one delta window that is a result of combining all but the last deltas
2308 from the current desired representation identified in *RB, to its
2309 final base representation. Store the window in *RESULT. */
2310 static svn_error_t *
2311 get_combined_window(svn_txdelta_window_t **result,
2312 struct rep_read_baton *rb)
2314 apr_pool_t *pool, *new_pool;
2315 int i;
2316 svn_txdelta_window_t *window, *nwin;
2317 struct rep_state *rs;
2319 assert(rb->rs_list->nelts >= 2);
2321 pool = svn_pool_create(rb->pool);
2323 /* Read the next window from the original rep. */
2324 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
2325 SVN_ERR(read_window(&window, rb->chunk_index, rs, pool));
2327 /* Combine in the windows from the other delta reps, if needed. */
2328 for (i = 1; i < rb->rs_list->nelts - 1; i++)
2330 if (window->src_ops == 0)
2331 break;
2333 rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
2335 SVN_ERR(read_window(&nwin, rb->chunk_index, rs, pool));
2337 /* Combine this window with the current one. Cycles pools so that we
2338 only need to hold three windows at a time. */
2339 new_pool = svn_pool_create(rb->pool);
2340 window = svn_txdelta_compose_windows(nwin, window, new_pool);
2341 svn_pool_destroy(pool);
2342 pool = new_pool;
2345 *result = window;
2346 return SVN_NO_ERROR;
2349 static svn_error_t *
2350 rep_read_contents_close(void *baton)
2352 struct rep_read_baton *rb = baton;
2354 svn_pool_destroy(rb->pool);
2355 svn_pool_destroy(rb->filehandle_pool);
2357 return SVN_NO_ERROR;
2360 /* Return the next *LEN bytes of the rep and store them in *BUF. */
2361 static svn_error_t *
2362 get_contents(struct rep_read_baton *rb,
2363 char *buf,
2364 apr_size_t *len)
2366 apr_size_t copy_len, remaining = *len, tlen;
2367 char *sbuf, *tbuf, *cur = buf;
2368 struct rep_state *rs;
2369 svn_txdelta_window_t *cwindow, *lwindow;
2371 /* Special case for when there are no delta reps, only a plain
2372 text. */
2373 if (rb->rs_list->nelts == 0)
2375 copy_len = remaining;
2376 rs = rb->src_state;
2377 if (((apr_off_t) copy_len) > rs->end - rs->off)
2378 copy_len = (apr_size_t) (rs->end - rs->off);
2379 SVN_ERR(svn_io_file_read_full(rs->file, cur, copy_len, NULL,
2380 rb->pool));
2381 rs->off += copy_len;
2382 *len = copy_len;
2383 return SVN_NO_ERROR;
2386 while (remaining > 0)
2388 /* If we have buffered data from a previous chunk, use that. */
2389 if (rb->buf)
2391 /* Determine how much to copy from the buffer. */
2392 copy_len = rb->buf_len - rb->buf_pos;
2393 if (copy_len > remaining)
2394 copy_len = remaining;
2396 /* Actually copy the data. */
2397 memcpy(cur, rb->buf + rb->buf_pos, copy_len);
2398 rb->buf_pos += copy_len;
2399 cur += copy_len;
2400 remaining -= copy_len;
2402 /* If the buffer is all used up, clear it and empty the
2403 local pool. */
2404 if (rb->buf_pos == rb->buf_len)
2406 svn_pool_clear(rb->pool);
2407 rb->buf = NULL;
2410 else
2413 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
2414 if (rs->off == rs->end)
2415 break;
2417 /* Get more buffered data by evaluating a chunk. */
2418 if (rb->rs_list->nelts > 1)
2419 SVN_ERR(get_combined_window(&cwindow, rb));
2420 else
2421 cwindow = NULL;
2422 if (!cwindow || cwindow->src_ops > 0)
2424 rs = APR_ARRAY_IDX(rb->rs_list, rb->rs_list->nelts - 1,
2425 struct rep_state *);
2426 /* Read window from last representation in list. */
2427 /* We apply this window directly instead of combining it with the
2428 others. We do this because vdelta is used for deltas against
2429 the empty stream, which will trigger quadratic behaviour in
2430 the delta combiner. */
2431 SVN_ERR(read_window(&lwindow, rb->chunk_index, rs, rb->pool));
2433 if (lwindow->src_ops > 0)
2435 if (! rb->src_state)
2436 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2437 _("svndiff data requested "
2438 "non-existent source"));
2439 rs = rb->src_state;
2440 sbuf = apr_palloc(rb->pool, lwindow->sview_len);
2441 if (! ((rs->start + lwindow->sview_offset) < rs->end))
2442 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2443 _("svndiff requested position "
2444 "beyond end of stream"));
2445 if ((rs->start + lwindow->sview_offset) != rs->off)
2447 rs->off = rs->start + lwindow->sview_offset;
2448 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off,
2449 rb->pool));
2451 SVN_ERR(svn_io_file_read_full(rs->file, sbuf,
2452 lwindow->sview_len,
2453 NULL, rb->pool));
2454 rs->off += lwindow->sview_len;
2456 else
2457 sbuf = NULL;
2459 /* Apply lwindow to source. */
2460 tlen = lwindow->tview_len;
2461 tbuf = apr_palloc(rb->pool, tlen);
2462 svn_txdelta_apply_instructions(lwindow, sbuf, tbuf,
2463 &tlen);
2464 if (tlen != lwindow->tview_len)
2465 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2466 _("svndiff window length is "
2467 "corrupt"));
2468 sbuf = tbuf;
2470 else
2471 sbuf = NULL;
2473 rb->chunk_index++;
2475 if (cwindow)
2477 rb->buf_len = cwindow->tview_len;
2478 rb->buf = apr_palloc(rb->pool, rb->buf_len);
2479 svn_txdelta_apply_instructions(cwindow, sbuf, rb->buf,
2480 &rb->buf_len);
2481 if (rb->buf_len != cwindow->tview_len)
2482 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2483 _("svndiff window length is "
2484 "corrupt"));
2486 else
2488 rb->buf_len = lwindow->tview_len;
2489 rb->buf = sbuf;
2492 rb->buf_pos = 0;
2496 *len = cur - buf;
2498 return SVN_NO_ERROR;
2501 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
2502 representation and store them in *BUF. Sum as we read and verify
2503 the MD5 sum at the end. */
2504 static svn_error_t *
2505 rep_read_contents(void *baton,
2506 char *buf,
2507 apr_size_t *len)
2509 struct rep_read_baton *rb = baton;
2511 /* Get the next block of data. */
2512 SVN_ERR(get_contents(rb, buf, len));
2514 /* Perform checksumming. We want to check the checksum as soon as
2515 the last byte of data is read, in case the caller never performs
2516 a short read, but we don't want to finalize the MD5 context
2517 twice. */
2518 if (!rb->checksum_finalized)
2520 apr_md5_update(&rb->md5_context, buf, *len);
2521 rb->off += *len;
2522 if (rb->off == rb->len)
2524 unsigned char checksum[APR_MD5_DIGESTSIZE];
2526 rb->checksum_finalized = TRUE;
2527 apr_md5_final(checksum, &rb->md5_context);
2528 if (! svn_md5_digests_match(checksum, rb->checksum))
2529 return svn_error_createf
2530 (SVN_ERR_FS_CORRUPT, NULL,
2531 _("Checksum mismatch while reading representation:\n"
2532 " expected: %s\n"
2533 " actual: %s\n"),
2534 svn_md5_digest_to_cstring_display(rb->checksum, rb->pool),
2535 svn_md5_digest_to_cstring_display(checksum, rb->pool));
2538 return SVN_NO_ERROR;
2541 /* Return a stream in *CONTENTS_P that will read the contents of a
2542 representation stored at the location given by REP. Appropriate
2543 for any kind of immutable representation, but only for file
2544 contents (not props or directory contents) in mutable
2545 representations.
2547 If REP is NULL, the representation is assumed to be empty, and the
2548 empty stream is returned.
2550 static svn_error_t *
2551 read_representation(svn_stream_t **contents_p,
2552 svn_fs_t *fs,
2553 representation_t *rep,
2554 apr_pool_t *pool)
2556 struct rep_read_baton *rb;
2558 if (! rep)
2560 *contents_p = svn_stream_empty(pool);
2562 else
2564 SVN_ERR(rep_read_get_baton(&rb, fs, rep, pool));
2565 *contents_p = svn_stream_create(rb, pool);
2566 svn_stream_set_read(*contents_p, rep_read_contents);
2567 svn_stream_set_close(*contents_p, rep_read_contents_close);
2570 return SVN_NO_ERROR;
2573 svn_error_t *
2574 svn_fs_fs__get_contents(svn_stream_t **contents_p,
2575 svn_fs_t *fs,
2576 node_revision_t *noderev,
2577 apr_pool_t *pool)
2579 return read_representation(contents_p, fs, noderev->data_rep, pool);
2582 /* Baton used when reading delta windows. */
2583 struct delta_read_baton
2585 struct rep_state *rs;
2586 unsigned char checksum[APR_MD5_DIGESTSIZE];
2589 /* This implements the svn_txdelta_next_window_fn_t interface. */
2590 static svn_error_t *
2591 delta_read_next_window(svn_txdelta_window_t **window, void *baton,
2592 apr_pool_t *pool)
2594 struct delta_read_baton *drb = baton;
2596 if (drb->rs->off == drb->rs->end)
2598 *window = NULL;
2599 return SVN_NO_ERROR;
2602 SVN_ERR(read_window(window, drb->rs->chunk_index, drb->rs, pool));
2604 return SVN_NO_ERROR;
2607 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
2608 static const unsigned char *
2609 delta_read_md5_digest(void *baton)
2611 struct delta_read_baton *drb = baton;
2613 return drb->checksum;
2616 svn_error_t *
2617 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
2618 svn_fs_t *fs,
2619 node_revision_t *source,
2620 node_revision_t *target,
2621 apr_pool_t *pool)
2623 svn_stream_t *source_stream, *target_stream;
2625 /* Try a shortcut: if the target is stored as a delta against the source,
2626 then just use that delta. */
2627 if (source && source->data_rep && target->data_rep)
2629 struct rep_state *rep_state;
2630 struct rep_args *rep_args;
2632 /* Read target's base rep if any. */
2633 SVN_ERR(create_rep_state(&rep_state, &rep_args, target->data_rep,
2634 fs, pool));
2635 /* If that matches source, then use this delta as is. */
2636 if (rep_args->is_delta
2637 && (rep_args->is_delta_vs_empty
2638 || (rep_args->base_revision == source->data_rep->revision
2639 && rep_args->base_offset == source->data_rep->offset)))
2641 /* Create the delta read baton. */
2642 struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
2643 drb->rs = rep_state;
2644 memcpy(drb->checksum, target->data_rep->checksum,
2645 sizeof(drb->checksum));
2646 *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
2647 delta_read_md5_digest, pool);
2648 return SVN_NO_ERROR;
2650 else
2651 SVN_ERR(svn_io_file_close(rep_state->file, pool));
2654 /* Read both fulltexts and construct a delta. */
2655 if (source)
2656 SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
2657 else
2658 source_stream = svn_stream_empty(pool);
2659 SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
2660 svn_txdelta(stream_p, source_stream, target_stream, pool);
2662 return SVN_NO_ERROR;
2666 /* Fetch the contents of a directory into ENTRIES. Values are stored
2667 as filename to string mappings; further conversion is necessary to
2668 convert them into svn_fs_dirent_t values. */
2669 static svn_error_t *
2670 get_dir_contents(apr_hash_t *entries,
2671 svn_fs_t *fs,
2672 node_revision_t *noderev,
2673 apr_pool_t *pool)
2675 svn_stream_t *contents;
2677 if (noderev->data_rep && noderev->data_rep->txn_id)
2679 apr_file_t *dir_file;
2680 const char *filename = path_txn_node_children(fs, noderev->id, pool);
2682 /* The representation is mutable. Read the old directory
2683 contents from the mutable children file, followed by the
2684 changes we've made in this transaction. */
2685 SVN_ERR(svn_io_file_open(&dir_file, filename, APR_READ | APR_BUFFERED,
2686 APR_OS_DEFAULT, pool));
2687 contents = svn_stream_from_aprfile(dir_file, pool);
2688 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
2689 SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
2690 SVN_ERR(svn_io_file_close(dir_file, pool));
2692 else if (noderev->data_rep)
2694 /* The representation is immutable. Read it normally. */
2695 SVN_ERR(read_representation(&contents, fs, noderev->data_rep, pool));
2696 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
2697 SVN_ERR(svn_stream_close(contents));
2700 return SVN_NO_ERROR;
2703 /* Return a copy of the directory hash ENTRIES in POOL. */
2704 static apr_hash_t *
2705 copy_dir_entries(apr_hash_t *entries,
2706 apr_pool_t *pool)
2708 apr_hash_t *new_entries = apr_hash_make(pool);
2709 apr_hash_index_t *hi;
2711 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2713 void *val;
2714 svn_fs_dirent_t *dirent, *new_dirent;
2716 apr_hash_this(hi, NULL, NULL, &val);
2717 dirent = val;
2718 new_dirent = apr_palloc(pool, sizeof(*new_dirent));
2719 new_dirent->name = apr_pstrdup(pool, dirent->name);
2720 new_dirent->kind = dirent->kind;
2721 new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool);
2722 apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING,
2723 new_dirent);
2725 return new_entries;
2729 svn_error_t *
2730 svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
2731 svn_fs_t *fs,
2732 node_revision_t *noderev,
2733 apr_pool_t *pool)
2735 fs_fs_data_t *ffd = fs->fsap_data;
2736 apr_hash_t *unparsed_entries, *parsed_entries;
2737 apr_hash_index_t *hi;
2738 unsigned int hid;
2740 /* Calculate an index into the dir entries cache. This should be
2741 completely ignored if this is a mutable noderev. */
2742 hid = DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev->id));
2744 /* If we have this directory cached, return it. */
2745 if (! svn_fs_fs__id_txn_id(noderev->id) &&
2746 ffd->dir_cache_id[hid] && svn_fs_fs__id_eq(ffd->dir_cache_id[hid],
2747 noderev->id))
2749 *entries_p = copy_dir_entries(ffd->dir_cache[hid], pool);
2750 return SVN_NO_ERROR;
2753 /* Read in the directory hash. */
2754 unparsed_entries = apr_hash_make(pool);
2755 SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
2757 parsed_entries = apr_hash_make(pool);
2759 /* Translate the string dir entries into real entries. */
2760 for (hi = apr_hash_first(pool, unparsed_entries); hi; hi = apr_hash_next(hi))
2762 const void *key;
2763 void *val;
2764 char *str_val;
2765 char *str, *last_str;
2766 svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
2768 apr_hash_this(hi, &key, NULL, &val);
2769 str_val = apr_pstrdup(pool, *((char **)val));
2770 dirent->name = apr_pstrdup(pool, key);
2772 str = apr_strtok(str_val, " ", &last_str);
2773 if (str == NULL)
2774 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2775 _("Directory entry corrupt"));
2777 if (strcmp(str, KIND_FILE) == 0)
2779 dirent->kind = svn_node_file;
2781 else if (strcmp(str, KIND_DIR) == 0)
2783 dirent->kind = svn_node_dir;
2785 else
2787 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2788 _("Directory entry corrupt"));
2791 str = apr_strtok(NULL, " ", &last_str);
2792 if (str == NULL)
2793 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2794 _("Directory entry corrupt"));
2796 dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
2798 apr_hash_set(parsed_entries, dirent->name, APR_HASH_KEY_STRING, dirent);
2801 /* If this is an immutable directory, let's cache the contents. */
2802 if (! svn_fs_fs__id_txn_id(noderev->id))
2804 /* Start by NULLing the ID field, so that we never leave the
2805 cache in an illegal state. */
2806 ffd->dir_cache_id[hid] = NULL;
2808 if (ffd->dir_cache_pool[hid])
2809 svn_pool_clear(ffd->dir_cache_pool[hid]);
2810 else
2811 ffd->dir_cache_pool[hid] = svn_pool_create(fs->pool);
2813 ffd->dir_cache[hid] = copy_dir_entries(parsed_entries,
2814 ffd->dir_cache_pool[hid]);
2815 ffd->dir_cache_id[hid] = svn_fs_fs__id_copy(noderev->id,
2816 ffd->dir_cache_pool[hid]);
2819 *entries_p = parsed_entries;
2820 return SVN_NO_ERROR;
2823 svn_error_t *
2824 svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
2825 svn_fs_t *fs,
2826 node_revision_t *noderev,
2827 apr_pool_t *pool)
2829 apr_hash_t *proplist;
2830 svn_stream_t *stream;
2832 proplist = apr_hash_make(pool);
2834 if (noderev->prop_rep && noderev->prop_rep->txn_id)
2836 apr_file_t *props_file;
2837 const char *filename = path_txn_node_props(fs, noderev->id, pool);
2839 SVN_ERR(svn_io_file_open(&props_file, filename,
2840 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
2841 pool));
2842 stream = svn_stream_from_aprfile(props_file, pool);
2843 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
2844 SVN_ERR(svn_io_file_close(props_file, pool));
2846 else if (noderev->prop_rep)
2848 SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
2849 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
2850 SVN_ERR(svn_stream_close(stream));
2853 *proplist_p = proplist;
2855 return SVN_NO_ERROR;
2858 svn_error_t *
2859 svn_fs_fs__file_length(svn_filesize_t *length,
2860 node_revision_t *noderev,
2861 apr_pool_t *pool)
2863 if (noderev->data_rep)
2864 *length = noderev->data_rep->expanded_size;
2865 else
2866 *length = 0;
2868 return SVN_NO_ERROR;
2871 svn_boolean_t
2872 svn_fs_fs__noderev_same_rep_key(representation_t *a,
2873 representation_t *b)
2875 if (a == b)
2876 return TRUE;
2878 if (a && (! b))
2879 return FALSE;
2881 if (b && (! a))
2882 return FALSE;
2884 if (a->offset != b->offset)
2885 return FALSE;
2887 if (a->revision != b->revision)
2888 return FALSE;
2890 return TRUE;
2893 svn_error_t *
2894 svn_fs_fs__file_checksum(unsigned char digest[],
2895 node_revision_t *noderev,
2896 apr_pool_t *pool)
2898 if (noderev->data_rep)
2899 memcpy(digest, noderev->data_rep->checksum, APR_MD5_DIGESTSIZE);
2900 else
2901 memset(digest, 0, APR_MD5_DIGESTSIZE);
2903 return SVN_NO_ERROR;
2906 representation_t *
2907 svn_fs_fs__rep_copy(representation_t *rep,
2908 apr_pool_t *pool)
2910 representation_t *rep_new;
2912 if (rep == NULL)
2913 return NULL;
2915 rep_new = apr_pcalloc(pool, sizeof(*rep_new));
2917 memcpy(rep_new, rep, sizeof(*rep_new));
2919 return rep_new;
2922 /* Merge the internal-use-only CHANGE into a hash of public-FS
2923 svn_fs_path_change_t CHANGES, collapsing multiple changes into a
2924 single summarical (is that real word?) change per path. Also keep
2925 the COPYFROM_HASH up to date with new adds and replaces. */
2926 static svn_error_t *
2927 fold_change(apr_hash_t *changes,
2928 const change_t *change,
2929 apr_hash_t *copyfrom_hash)
2931 apr_pool_t *pool = apr_hash_pool_get(changes);
2932 apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_hash);
2933 svn_fs_path_change_t *old_change, *new_change;
2934 const char *path, *copyfrom_string, *copyfrom_path = NULL;
2936 if ((old_change = apr_hash_get(changes, change->path, APR_HASH_KEY_STRING)))
2938 /* This path already exists in the hash, so we have to merge
2939 this change into the already existing one. */
2941 /* Get the existing copyfrom entry for this path. */
2942 copyfrom_string = apr_hash_get(copyfrom_hash, change->path,
2943 APR_HASH_KEY_STRING);
2945 /* If this entry existed in the copyfrom hash, we don't need to
2946 copy it. */
2947 if (copyfrom_string)
2948 copyfrom_path = change->path;
2950 /* Since the path already exists in the hash, we don't have to
2951 dup the allocation for the path itself. */
2952 path = change->path;
2953 /* Sanity check: only allow NULL node revision ID in the
2954 `reset' case. */
2955 if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
2956 return svn_error_create
2957 (SVN_ERR_FS_CORRUPT, NULL,
2958 _("Missing required node revision ID"));
2960 /* Sanity check: we should be talking about the same node
2961 revision ID as our last change except where the last change
2962 was a deletion. */
2963 if (change->noderev_id
2964 && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
2965 && (old_change->change_kind != svn_fs_path_change_delete))
2966 return svn_error_create
2967 (SVN_ERR_FS_CORRUPT, NULL,
2968 _("Invalid change ordering: new node revision ID "
2969 "without delete"));
2971 /* Sanity check: an add, replacement, or reset must be the first
2972 thing to follow a deletion. */
2973 if ((old_change->change_kind == svn_fs_path_change_delete)
2974 && (! ((change->kind == svn_fs_path_change_replace)
2975 || (change->kind == svn_fs_path_change_reset)
2976 || (change->kind == svn_fs_path_change_add))))
2977 return svn_error_create
2978 (SVN_ERR_FS_CORRUPT, NULL,
2979 _("Invalid change ordering: non-add change on deleted path"));
2981 /* Now, merge that change in. */
2982 switch (change->kind)
2984 case svn_fs_path_change_reset:
2985 /* A reset here will simply remove the path change from the
2986 hash. */
2987 old_change = NULL;
2988 copyfrom_string = NULL;
2989 break;
2991 case svn_fs_path_change_delete:
2992 if (old_change->change_kind == svn_fs_path_change_add)
2994 /* If the path was introduced in this transaction via an
2995 add, and we are deleting it, just remove the path
2996 altogether. */
2997 old_change = NULL;
2999 else
3001 /* A deletion overrules all previous changes. */
3002 old_change->change_kind = svn_fs_path_change_delete;
3003 old_change->text_mod = change->text_mod;
3004 old_change->prop_mod = change->prop_mod;
3006 copyfrom_string = NULL;
3007 break;
3009 case svn_fs_path_change_add:
3010 case svn_fs_path_change_replace:
3011 /* An add at this point must be following a previous delete,
3012 so treat it just like a replace. */
3013 old_change->change_kind = svn_fs_path_change_replace;
3014 old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
3015 pool);
3016 old_change->text_mod = change->text_mod;
3017 old_change->prop_mod = change->prop_mod;
3018 if (change->copyfrom_rev == SVN_INVALID_REVNUM)
3019 copyfrom_string = apr_pstrdup(copyfrom_pool, "");
3020 else
3022 copyfrom_string = apr_psprintf(copyfrom_pool,
3023 "%ld %s",
3024 change->copyfrom_rev,
3025 change->copyfrom_path);
3027 break;
3029 case svn_fs_path_change_modify:
3030 default:
3031 if (change->text_mod)
3032 old_change->text_mod = TRUE;
3033 if (change->prop_mod)
3034 old_change->prop_mod = TRUE;
3035 break;
3038 /* Point our new_change to our (possibly modified) old_change. */
3039 new_change = old_change;
3041 else
3043 /* This change is new to the hash, so make a new public change
3044 structure from the internal one (in the hash's pool), and dup
3045 the path into the hash's pool, too. */
3046 new_change = apr_pcalloc(pool, sizeof(*new_change));
3047 new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
3048 new_change->change_kind = change->kind;
3049 new_change->text_mod = change->text_mod;
3050 new_change->prop_mod = change->prop_mod;
3051 if (change->copyfrom_rev != SVN_INVALID_REVNUM)
3053 copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
3054 change->copyfrom_rev,
3055 change->copyfrom_path);
3057 else
3058 copyfrom_string = apr_pstrdup(copyfrom_pool, "");
3059 path = apr_pstrdup(pool, change->path);
3062 /* Add (or update) this path. */
3063 apr_hash_set(changes, path, APR_HASH_KEY_STRING, new_change);
3065 /* If copyfrom_path is non-NULL, the key is already present in the
3066 hash, so we don't need to duplicate it in the copyfrom pool. */
3067 if (! copyfrom_path)
3069 /* If copyfrom_string is NULL, the hash entry will be deleted,
3070 so we don't need to duplicate the key in the copyfrom
3071 pool. */
3072 copyfrom_path = copyfrom_string ? apr_pstrdup(copyfrom_pool, path)
3073 : path;
3076 apr_hash_set(copyfrom_hash, copyfrom_path, APR_HASH_KEY_STRING,
3077 copyfrom_string);
3079 return SVN_NO_ERROR;
3082 /* The 256 is an arbitrary size large enough to hold the node id and the
3083 * various flags. */
3084 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
3086 /* Read the next entry in the changes record from file FILE and store
3087 the resulting change in *CHANGE_P. If there is no next record,
3088 store NULL there. Perform all allocations from POOL. */
3089 static svn_error_t *
3090 read_change(change_t **change_p,
3091 apr_file_t *file,
3092 apr_pool_t *pool)
3094 char buf[MAX_CHANGE_LINE_LEN];
3095 apr_size_t len = sizeof(buf);
3096 change_t *change;
3097 char *str, *last_str;
3098 svn_error_t *err;
3100 /* Default return value. */
3101 *change_p = NULL;
3103 err = svn_io_read_length_line(file, buf, &len, pool);
3105 /* Check for a blank line. */
3106 if (err || (len == 0))
3108 if (err && APR_STATUS_IS_EOF(err->apr_err))
3110 svn_error_clear(err);
3111 return SVN_NO_ERROR;
3113 if ((len == 0) && (! err))
3114 return SVN_NO_ERROR;
3115 return err;
3118 change = apr_pcalloc(pool, sizeof(*change));
3120 /* Get the node-id of the change. */
3121 str = apr_strtok(buf, " ", &last_str);
3122 if (str == NULL)
3123 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3124 _("Invalid changes line in rev-file"));
3126 change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
3128 /* Get the change type. */
3129 str = apr_strtok(NULL, " ", &last_str);
3130 if (str == NULL)
3131 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3132 _("Invalid changes line in rev-file"));
3134 if (strcmp(str, ACTION_MODIFY) == 0)
3136 change->kind = svn_fs_path_change_modify;
3138 else if (strcmp(str, ACTION_ADD) == 0)
3140 change->kind = svn_fs_path_change_add;
3142 else if (strcmp(str, ACTION_DELETE) == 0)
3144 change->kind = svn_fs_path_change_delete;
3146 else if (strcmp(str, ACTION_REPLACE) == 0)
3148 change->kind = svn_fs_path_change_replace;
3150 else if (strcmp(str, ACTION_RESET) == 0)
3152 change->kind = svn_fs_path_change_reset;
3154 else
3156 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3157 _("Invalid change kind in rev file"));
3160 /* Get the text-mod flag. */
3161 str = apr_strtok(NULL, " ", &last_str);
3162 if (str == NULL)
3163 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3164 _("Invalid changes line in rev-file"));
3166 if (strcmp(str, FLAG_TRUE) == 0)
3168 change->text_mod = TRUE;
3170 else if (strcmp(str, FLAG_FALSE) == 0)
3172 change->text_mod = FALSE;
3174 else
3176 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3177 _("Invalid text-mod flag in rev-file"));
3180 /* Get the prop-mod flag. */
3181 str = apr_strtok(NULL, " ", &last_str);
3182 if (str == NULL)
3183 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3184 _("Invalid changes line in rev-file"));
3186 if (strcmp(str, FLAG_TRUE) == 0)
3188 change->prop_mod = TRUE;
3190 else if (strcmp(str, FLAG_FALSE) == 0)
3192 change->prop_mod = FALSE;
3194 else
3196 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3197 _("Invalid prop-mod flag in rev-file"));
3200 /* Get the changed path. */
3201 change->path = apr_pstrdup(pool, last_str);
3204 /* Read the next line, the copyfrom line. */
3205 len = sizeof(buf);
3206 SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3208 if (len == 0)
3210 change->copyfrom_rev = SVN_INVALID_REVNUM;
3211 change->copyfrom_path = NULL;
3213 else
3215 str = apr_strtok(buf, " ", &last_str);
3216 if (! str)
3217 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3218 _("Invalid changes line in rev-file"));
3219 change->copyfrom_rev = atol(str);
3221 if (! last_str)
3222 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3223 _("Invalid changes line in rev-file"));
3225 change->copyfrom_path = apr_pstrdup(pool, last_str);
3228 *change_p = change;
3230 return SVN_NO_ERROR;
3233 /* Fetch all the changed path entries from FILE and store then in
3234 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
3235 *data. Store a hash of paths to copyfrom revisions/paths in
3236 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
3237 the changed-path entries have already been folded (by
3238 write_final_changed_path_info) and may be out of order, so we shouldn't
3239 remove children of replaced or deleted directories. Do all
3240 allocations in POOL. */
3241 static svn_error_t *
3242 fetch_all_changes(apr_hash_t *changed_paths,
3243 apr_hash_t *copyfrom_hash,
3244 apr_file_t *file,
3245 svn_boolean_t prefolded,
3246 apr_pool_t *pool)
3248 change_t *change;
3249 apr_pool_t *iterpool = svn_pool_create(pool);
3250 apr_hash_t *my_hash;
3252 /* If we are passed a NULL copyfrom hash, manufacture one for the
3253 duration of this call. */
3254 my_hash = copyfrom_hash ? copyfrom_hash : apr_hash_make(pool);
3256 /* Read in the changes one by one, folding them into our local hash
3257 as necessary. */
3259 SVN_ERR(read_change(&change, file, iterpool));
3261 while (change)
3263 SVN_ERR(fold_change(changed_paths, change, my_hash));
3265 /* Now, if our change was a deletion or replacement, we have to
3266 blow away any changes thus far on paths that are (or, were)
3267 children of this path.
3268 ### i won't bother with another iteration pool here -- at
3269 most we talking about a few extra dups of paths into what
3270 is already a temporary subpool.
3273 if (((change->kind == svn_fs_path_change_delete)
3274 || (change->kind == svn_fs_path_change_replace))
3275 && ! prefolded)
3277 apr_hash_index_t *hi;
3279 for (hi = apr_hash_first(iterpool, changed_paths);
3281 hi = apr_hash_next(hi))
3283 /* KEY is the path. */
3284 const void *hashkey;
3285 apr_ssize_t klen;
3286 apr_hash_this(hi, &hashkey, &klen, NULL);
3288 /* If we come across our own path, ignore it. */
3289 if (strcmp(change->path, hashkey) == 0)
3290 continue;
3292 /* If we come across a child of our path, remove it. */
3293 if (svn_path_is_child(change->path, hashkey, iterpool))
3294 apr_hash_set(changed_paths, hashkey, klen, NULL);
3298 /* Clear the per-iteration subpool. */
3299 svn_pool_clear(iterpool);
3301 SVN_ERR(read_change(&change, file, iterpool));
3304 /* Destroy the per-iteration subpool. */
3305 svn_pool_destroy(iterpool);
3307 return SVN_NO_ERROR;
3310 svn_error_t *
3311 svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
3312 svn_fs_t *fs,
3313 const char *txn_id,
3314 apr_hash_t *copyfrom_cache,
3315 apr_pool_t *pool)
3317 apr_file_t *file;
3318 apr_hash_t *changed_paths = apr_hash_make(pool);
3320 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
3321 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3323 SVN_ERR(fetch_all_changes(changed_paths, copyfrom_cache, file, FALSE,
3324 pool));
3326 SVN_ERR(svn_io_file_close(file, pool));
3328 *changed_paths_p = changed_paths;
3330 return SVN_NO_ERROR;
3333 svn_error_t *
3334 svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
3335 svn_fs_t *fs,
3336 svn_revnum_t rev,
3337 apr_hash_t *copyfrom_cache,
3338 apr_pool_t *pool)
3340 apr_off_t changes_offset;
3341 apr_hash_t *changed_paths;
3342 apr_file_t *revision_file;
3344 SVN_ERR(ensure_revision_exists(fs, rev, pool));
3346 SVN_ERR(svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool),
3347 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3349 SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file,
3350 pool));
3352 SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
3354 changed_paths = apr_hash_make(pool);
3356 SVN_ERR(fetch_all_changes(changed_paths, copyfrom_cache, revision_file,
3357 TRUE, pool));
3359 /* Close the revision file. */
3360 SVN_ERR(svn_io_file_close(revision_file, pool));
3362 *changed_paths_p = changed_paths;
3364 return SVN_NO_ERROR;
3367 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
3368 the filesystem FS. This is only used to create the root of a transaction.
3369 Allocations are from POOL. */
3370 static svn_error_t *
3371 create_new_txn_noderev_from_rev(svn_fs_t *fs,
3372 const char *txn_id,
3373 svn_fs_id_t *src,
3374 apr_pool_t *pool)
3376 node_revision_t *noderev;
3377 const char *node_id, *copy_id;
3379 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
3381 if (svn_fs_fs__id_txn_id(noderev->id))
3382 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3383 _("Copying from transactions not allowed"));
3385 noderev->predecessor_id = noderev->id;
3386 noderev->predecessor_count++;
3387 noderev->copyfrom_path = NULL;
3388 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
3390 /* For the transaction root, the copyroot never changes. */
3392 node_id = svn_fs_fs__id_node_id(noderev->id);
3393 copy_id = svn_fs_fs__id_copy_id(noderev->id);
3394 noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
3396 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool));
3398 return SVN_NO_ERROR;
3401 /* A structure used by get_and_increment_txn_key_body(). */
3402 struct get_and_increment_txn_key_baton {
3403 svn_fs_t *fs;
3404 char *txn_id;
3405 apr_pool_t *pool;
3408 /* Callback used in the implementation of create_txn_dir(). This gets
3409 the current base 36 value in PATH_TXN_CURRENT and increments it.
3410 It returns the original value by the baton. */
3411 static svn_error_t *
3412 get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
3414 struct get_and_increment_txn_key_baton *cb = baton;
3415 const char *txn_current_filename = path_txn_current(cb->fs, pool);
3416 apr_file_t *txn_current_file;
3417 const char *tmp_filename;
3418 char next_txn_id[MAX_KEY_SIZE+3];
3419 svn_error_t *err = SVN_NO_ERROR;
3420 apr_pool_t *iterpool;
3421 apr_size_t len;
3422 int i;
3424 cb->txn_id = apr_palloc(cb->pool, MAX_KEY_SIZE);
3426 iterpool = svn_pool_create(pool);
3427 for (i = 0; i < SVN_ESTALE_RETRY_COUNT; ++i)
3429 svn_pool_clear(iterpool);
3431 SVN_RETRY_ESTALE(err, svn_io_file_open(&txn_current_file,
3432 txn_current_filename,
3433 APR_READ | APR_BUFFERED,
3434 APR_OS_DEFAULT, iterpool));
3435 len = MAX_KEY_SIZE;
3436 SVN_RETRY_ESTALE(err, svn_io_read_length_line(txn_current_file,
3437 cb->txn_id,
3438 &len,
3439 iterpool));
3440 SVN_IGNORE_ESTALE(err, svn_io_file_close(txn_current_file, iterpool));
3442 break;
3444 if (err)
3445 return err;
3447 svn_pool_destroy(iterpool);
3449 /* Increment the key and add a trailing \n to the string so the
3450 txn-current file has a newline in it. */
3451 svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
3452 next_txn_id[len] = '\n';
3453 ++len;
3454 next_txn_id[len] = '\0';
3456 SVN_ERR(svn_io_open_unique_file2(&txn_current_file, &tmp_filename,
3457 txn_current_filename, ".tmp",
3458 svn_io_file_del_none, pool));
3460 SVN_ERR(svn_io_file_write_full(txn_current_file,
3461 next_txn_id,
3462 len,
3463 NULL,
3464 pool));
3466 SVN_ERR(svn_io_file_flush_to_disk(txn_current_file, pool));
3468 SVN_ERR(svn_io_file_close(txn_current_file, pool));
3470 SVN_ERR(svn_fs_fs__move_into_place(tmp_filename, txn_current_filename,
3471 txn_current_filename, pool));
3473 return err;
3476 /* Create a unique directory for a transaction in FS based on revision
3477 REV. Return the ID for this transaction in *ID_P. Use a sequence
3478 value in the transaction ID to prevent reuse of transaction IDs. */
3479 static svn_error_t *
3480 create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
3481 apr_pool_t *pool)
3483 struct get_and_increment_txn_key_baton cb;
3484 const char *txn_dir;
3486 /* Get the current transaction sequence value, which is a base-36
3487 number, from the txn-current file, and write an
3488 incremented value back out to the file. Place the revision
3489 number the transaction is based off into the transaction id. */
3490 cb.pool = pool;
3491 cb.fs = fs;
3492 SVN_ERR(with_txn_current_lock(fs,
3493 get_and_increment_txn_key_body,
3494 &cb,
3495 pool));
3496 *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
3498 txn_dir = svn_path_join_many(pool,
3499 fs->path,
3500 PATH_TXNS_DIR,
3501 apr_pstrcat(pool, *id_p, PATH_EXT_TXN, NULL),
3502 NULL);
3504 SVN_ERR(svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool));
3506 return SVN_NO_ERROR;
3509 /* Create a unique directory for a transaction in FS based on revision
3510 REV. Return the ID for this transaction in *ID_P. This
3511 implementation is used in svn 1.4 and earlier repositories and is
3512 kept in 1.5 and greater to support the --pre-1.4-compatible and
3513 --pre-1.5-compatible repository creation options. Reused
3514 transaction IDs are possible with this implementation. */
3515 static svn_error_t *
3516 create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
3517 apr_pool_t *pool)
3519 unsigned int i;
3520 apr_pool_t *subpool;
3521 const char *unique_path, *prefix;
3523 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
3524 prefix = svn_path_join_many(pool, fs->path, PATH_TXNS_DIR,
3525 apr_psprintf(pool, "%ld", rev), NULL);
3527 subpool = svn_pool_create(pool);
3528 for (i = 1; i <= 99999; i++)
3530 svn_error_t *err;
3532 svn_pool_clear(subpool);
3533 unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
3534 err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
3535 if (! err)
3537 /* We succeeded. Return the basename minus the ".txn" extension. */
3538 const char *name = svn_path_basename(unique_path, subpool);
3539 *id_p = apr_pstrndup(pool, name,
3540 strlen(name) - strlen(PATH_EXT_TXN));
3541 svn_pool_destroy(subpool);
3542 return SVN_NO_ERROR;
3544 if (! APR_STATUS_IS_EEXIST(err->apr_err))
3545 return err;
3546 svn_error_clear(err);
3549 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
3550 NULL,
3551 _("Unable to create transaction directory "
3552 "in '%s' for revision %ld"),
3553 fs->path, rev);
3556 svn_error_t *
3557 svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
3558 svn_fs_t *fs,
3559 svn_revnum_t rev,
3560 apr_pool_t *pool)
3562 fs_fs_data_t *ffd = fs->fsap_data;
3563 svn_fs_txn_t *txn;
3564 svn_fs_id_t *root_id;
3566 txn = apr_pcalloc(pool, sizeof(*txn));
3568 /* Get the txn_id. */
3569 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
3570 SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
3571 else
3572 SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
3574 txn->fs = fs;
3575 txn->base_rev = rev;
3577 txn->vtable = &txn_vtable;
3578 *txn_p = txn;
3580 /* Create a new root node for this transaction. */
3581 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
3582 SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
3584 /* Create an empty rev file. */
3585 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
3586 pool));
3588 /* Create an empty rev-lock file. */
3589 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
3590 pool));
3592 /* Create an empty changes file. */
3593 SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
3594 pool));
3596 /* Create the next-ids file. */
3597 SVN_ERR(svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
3598 pool));
3600 return SVN_NO_ERROR;
3603 /* Store the property list for transaction TXN_ID in PROPLIST.
3604 Perform temporary allocations in POOL. */
3605 static svn_error_t *
3606 get_txn_proplist(apr_hash_t *proplist,
3607 svn_fs_t *fs,
3608 const char *txn_id,
3609 apr_pool_t *pool)
3611 apr_file_t *txn_prop_file;
3613 /* Open the transaction properties file. */
3614 SVN_ERR(svn_io_file_open(&txn_prop_file, path_txn_props(fs, txn_id, pool),
3615 APR_READ | APR_BUFFERED,
3616 APR_OS_DEFAULT, pool));
3618 /* Read in the property list. */
3619 SVN_ERR(svn_hash_read2(proplist,
3620 svn_stream_from_aprfile(txn_prop_file, pool),
3621 SVN_HASH_TERMINATOR, pool));
3623 SVN_ERR(svn_io_file_close(txn_prop_file, pool));
3625 return SVN_NO_ERROR;
3628 svn_error_t *
3629 svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
3630 const char *name,
3631 const svn_string_t *value,
3632 apr_pool_t *pool)
3634 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
3635 svn_prop_t prop;
3637 prop.name = name;
3638 prop.value = value;
3639 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
3641 return svn_fs_fs__change_txn_props(txn, props, pool);
3644 svn_error_t *
3645 svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
3646 apr_array_header_t *props,
3647 apr_pool_t *pool)
3649 apr_file_t *txn_prop_file;
3650 apr_hash_t *txn_prop = apr_hash_make(pool);
3651 int i;
3652 svn_error_t *err;
3654 err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
3655 /* Here - and here only - we need to deal with the possibility that the
3656 transaction property file doesn't yet exist. The rest of the
3657 implementation assumes that the file exists, but we're called to set the
3658 initial transaction properties as the transaction is being created. */
3659 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
3660 svn_error_clear(err);
3661 else if (err)
3662 return err;
3664 for (i = 0; i < props->nelts; i++)
3666 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
3668 apr_hash_set(txn_prop, prop->name, APR_HASH_KEY_STRING, prop->value);
3671 /* Create a new version of the file and write out the new props. */
3672 /* Open the transaction properties file. */
3673 SVN_ERR(svn_io_file_open(&txn_prop_file,
3674 path_txn_props(txn->fs, txn->id, pool),
3675 APR_WRITE | APR_CREATE | APR_TRUNCATE
3676 | APR_BUFFERED, APR_OS_DEFAULT, pool));
3678 SVN_ERR(svn_hash_write(txn_prop, txn_prop_file, pool));
3680 SVN_ERR(svn_io_file_close(txn_prop_file, pool));
3682 return SVN_NO_ERROR;
3685 /* Store the mergeinfo list for transaction TXN_ID into new hash
3686 *MINFO. Perform allocation of *MINFO and temporary allocations in
3687 *POOL. Sets *MINFO to NULL if no mergeinfo has changed. */
3689 static svn_error_t *
3690 get_txn_mergeinfo(apr_hash_t **minfo,
3691 svn_fs_t *fs,
3692 const char *txn_id,
3693 apr_pool_t *pool)
3695 apr_file_t *txn_minfo_file;
3696 apr_hash_t *result;
3698 /* Open the transaction mergeinfo file. */
3699 svn_error_t *err = svn_io_file_open(&txn_minfo_file,
3700 path_txn_mergeinfo(fs, txn_id, pool),
3701 APR_READ | APR_BUFFERED,
3702 APR_OS_DEFAULT, pool);
3703 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3705 svn_error_clear(err);
3706 *minfo = NULL;
3707 return SVN_NO_ERROR;
3709 SVN_ERR(err);
3711 result = apr_hash_make(pool);
3712 /* Read in the property list. */
3713 SVN_ERR(svn_hash_read2(result,
3714 svn_stream_from_aprfile(txn_minfo_file, pool),
3715 SVN_HASH_TERMINATOR, pool));
3717 SVN_ERR(svn_io_file_close(txn_minfo_file, pool));
3719 *minfo = result;
3721 return SVN_NO_ERROR;
3725 /* Change mergeinfo for path NAME in TXN to VALUE. */
3727 svn_error_t *
3728 svn_fs_fs__change_txn_mergeinfo(svn_fs_txn_t *txn,
3729 const char *name,
3730 const svn_string_t *value,
3731 apr_pool_t *pool)
3733 apr_file_t *txn_minfo_file;
3734 apr_hash_t *txn_minfo;
3736 SVN_ERR(get_txn_mergeinfo(&txn_minfo, txn->fs, txn->id, pool));
3737 if (txn_minfo == NULL) /* doesn't exist yet */
3738 txn_minfo = apr_hash_make(pool);
3740 apr_hash_set(txn_minfo, name, APR_HASH_KEY_STRING, value);
3742 /* Create a new version of the file and write out the new minfos. */
3743 /* Open the transaction minfoerties file. */
3744 SVN_ERR(svn_io_file_open(&txn_minfo_file,
3745 path_txn_mergeinfo(txn->fs, txn->id, pool),
3746 APR_WRITE | APR_CREATE | APR_TRUNCATE
3747 | APR_BUFFERED, APR_OS_DEFAULT, pool));
3749 SVN_ERR(svn_hash_write(txn_minfo, txn_minfo_file, pool));
3751 SVN_ERR(svn_io_file_close(txn_minfo_file, pool));
3753 return SVN_NO_ERROR;
3756 svn_error_t *
3757 svn_fs_fs__get_txn(transaction_t **txn_p,
3758 svn_fs_t *fs,
3759 const char *txn_id,
3760 apr_pool_t *pool)
3762 transaction_t *txn;
3763 node_revision_t *noderev;
3764 svn_fs_id_t *root_id;
3766 txn = apr_pcalloc(pool, sizeof(*txn));
3767 txn->proplist = apr_hash_make(pool);
3769 SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
3770 root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
3772 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
3774 txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
3775 txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
3776 txn->copies = NULL;
3778 *txn_p = txn;
3780 return SVN_NO_ERROR;
3783 /* Write out the currently available next node_id NODE_ID and copy_id
3784 COPY_ID for transaction TXN_ID in filesystem FS. Perform temporary
3785 allocations in POOL. */
3786 static svn_error_t *
3787 write_next_ids(svn_fs_t *fs,
3788 const char *txn_id,
3789 const char *node_id,
3790 const char *copy_id,
3791 apr_pool_t *pool)
3793 apr_file_t *file;
3794 svn_stream_t *out_stream;
3796 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
3797 APR_WRITE | APR_TRUNCATE,
3798 APR_OS_DEFAULT, pool));
3800 out_stream = svn_stream_from_aprfile(file, pool);
3802 SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
3804 SVN_ERR(svn_stream_close(out_stream));
3805 SVN_ERR(svn_io_file_close(file, pool));
3807 return SVN_NO_ERROR;
3810 /* Find out what the next unique node-id and copy-id are for
3811 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
3812 and *COPY_ID. Perform all allocations in POOL. */
3813 static svn_error_t *
3814 read_next_ids(const char **node_id,
3815 const char **copy_id,
3816 svn_fs_t *fs,
3817 const char *txn_id,
3818 apr_pool_t *pool)
3820 apr_file_t *file;
3821 char buf[MAX_KEY_SIZE*2+3];
3822 apr_size_t limit;
3823 char *str, *last_str;
3825 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
3826 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3828 limit = sizeof(buf);
3829 SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
3831 SVN_ERR(svn_io_file_close(file, pool));
3833 /* Parse this into two separate strings. */
3835 str = apr_strtok(buf, " ", &last_str);
3836 if (! str)
3837 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3838 _("next-id file corrupt"));
3840 *node_id = apr_pstrdup(pool, str);
3842 str = apr_strtok(NULL, " ", &last_str);
3843 if (! str)
3844 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3845 _("next-id file corrupt"));
3847 *copy_id = apr_pstrdup(pool, str);
3849 return SVN_NO_ERROR;
3852 /* Get a new and unique to this transaction node-id for transaction
3853 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
3854 Perform all allocations in POOL. */
3855 static svn_error_t *
3856 get_new_txn_node_id(const char **node_id_p,
3857 svn_fs_t *fs,
3858 const char *txn_id,
3859 apr_pool_t *pool)
3861 const char *cur_node_id, *cur_copy_id;
3862 char *node_id;
3863 apr_size_t len;
3865 /* First read in the current next-ids file. */
3866 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
3868 node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
3870 len = strlen(cur_node_id);
3871 svn_fs_fs__next_key(cur_node_id, &len, node_id);
3873 SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
3875 *node_id_p = apr_pstrcat(pool, "_", cur_node_id, NULL);
3877 return SVN_NO_ERROR;
3880 svn_error_t *
3881 svn_fs_fs__create_node(const svn_fs_id_t **id_p,
3882 svn_fs_t *fs,
3883 node_revision_t *noderev,
3884 const char *copy_id,
3885 const char *txn_id,
3886 apr_pool_t *pool)
3888 const char *node_id;
3889 const svn_fs_id_t *id;
3891 /* Get a new node-id for this node. */
3892 SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
3894 id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
3896 noderev->id = id;
3898 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
3900 *id_p = id;
3902 return SVN_NO_ERROR;
3905 svn_error_t *
3906 svn_fs_fs__purge_txn(svn_fs_t *fs,
3907 const char *txn_id,
3908 apr_pool_t *pool)
3910 fs_fs_data_t *ffd = fs->fsap_data;
3912 /* Remove the shared transaction object associated with this transaction. */
3913 SVN_ERR(purge_shared_txn(fs, txn_id, pool));
3914 /* Remove the directory associated with this transaction. */
3915 SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
3916 NULL, NULL, pool));
3917 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
3919 /* Delete protorev and its lock, which aren't in the txn
3920 directory. It's OK if they don't exist (for example, if this
3921 is post-commit and the proto-rev has been moved into
3922 place). */
3923 svn_error_t *err = svn_io_remove_file(path_txn_proto_rev(fs, txn_id,
3924 pool), pool);
3925 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3927 svn_error_clear(err);
3928 err = NULL;
3930 if (err)
3931 return err;
3933 err = svn_io_remove_file(path_txn_proto_rev_lock(fs, txn_id, pool),
3934 pool);
3935 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3937 svn_error_clear(err);
3938 err = NULL;
3940 if (err)
3941 return err;
3943 return SVN_NO_ERROR;
3947 svn_error_t *
3948 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
3949 apr_pool_t *pool)
3951 fs_fs_data_t *ffd;
3953 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
3955 /* Clean out the directory cache. */
3956 ffd = txn->fs->fsap_data;
3957 memset(&ffd->dir_cache_id, 0,
3958 sizeof(svn_fs_id_t *) * NUM_DIR_CACHE_ENTRIES);
3960 /* Now, purge the transaction. */
3961 SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
3962 _("Transaction cleanup failed"));
3964 return SVN_NO_ERROR;
3968 static const char *
3969 unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
3970 apr_pool_t *pool)
3972 return apr_psprintf(pool, "%s %s",
3973 (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
3974 svn_fs_fs__id_unparse(id, pool)->data);
3977 /* Given a hash ENTRIES of dirent structions, return a hash in
3978 *STR_ENTRIES_P, that has svn_string_t as the values in the format
3979 specified by the fs_fs directory contents file. Perform
3980 allocations in POOL. */
3981 static svn_error_t *
3982 unparse_dir_entries(apr_hash_t **str_entries_p,
3983 apr_hash_t *entries,
3984 apr_pool_t *pool)
3986 apr_hash_index_t *hi;
3988 *str_entries_p = apr_hash_make(pool);
3990 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
3992 const void *key;
3993 apr_ssize_t klen;
3994 void *val;
3995 svn_fs_dirent_t *dirent;
3996 const char *new_val;
3998 apr_hash_this(hi, &key, &klen, &val);
3999 dirent = val;
4000 new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
4001 apr_hash_set(*str_entries_p, key, klen,
4002 svn_string_create(new_val, pool));
4005 return SVN_NO_ERROR;
4009 svn_error_t *
4010 svn_fs_fs__set_entry(svn_fs_t *fs,
4011 const char *txn_id,
4012 node_revision_t *parent_noderev,
4013 const char *name,
4014 const svn_fs_id_t *id,
4015 svn_node_kind_t kind,
4016 apr_pool_t *pool)
4018 representation_t *rep = parent_noderev->data_rep;
4019 const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
4020 apr_file_t *file;
4021 svn_stream_t *out;
4023 if (!rep || !rep->txn_id)
4026 apr_hash_t *entries;
4027 apr_pool_t *subpool = svn_pool_create(pool);
4029 /* Before we can modify the directory, we need to dump its old
4030 contents into a mutable representation file. */
4031 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
4032 subpool));
4033 SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
4034 SVN_ERR(svn_io_file_open(&file, filename,
4035 APR_WRITE | APR_CREATE | APR_BUFFERED,
4036 APR_OS_DEFAULT, pool));
4037 out = svn_stream_from_aprfile(file, pool);
4038 SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
4040 svn_pool_destroy(subpool);
4043 /* Mark the node-rev's data rep as mutable. */
4044 rep = apr_pcalloc(pool, sizeof(*rep));
4045 rep->revision = SVN_INVALID_REVNUM;
4046 rep->txn_id = txn_id;
4047 parent_noderev->data_rep = rep;
4048 SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
4049 parent_noderev, FALSE, pool));
4051 else
4053 /* The directory rep is already mutable, so just open it for append. */
4054 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
4055 APR_OS_DEFAULT, pool));
4056 out = svn_stream_from_aprfile(file, pool);
4059 /* Append an incremental hash entry for the entry change. */
4060 if (id)
4062 const char *val = unparse_dir_entry(kind, id, pool);
4064 SVN_ERR(svn_stream_printf(out, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
4065 "V %" APR_SIZE_T_FMT "\n%s\n",
4066 strlen(name), name,
4067 strlen(val), val));
4069 else
4071 SVN_ERR(svn_stream_printf(out, pool, "D %" APR_SIZE_T_FMT "\n%s\n",
4072 strlen(name), name));
4075 SVN_ERR(svn_io_file_close(file, pool));
4076 return SVN_NO_ERROR;
4079 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
4080 string COPYFROM, into the file specified by FILE. All temporary
4081 allocations are in POOL. */
4082 static svn_error_t *
4083 write_change_entry(apr_file_t *file,
4084 const char *path,
4085 svn_fs_path_change_t *change,
4086 const char *copyfrom,
4087 apr_pool_t *pool)
4089 const char *idstr, *buf;
4090 const char *change_string = NULL;
4092 switch (change->change_kind)
4094 case svn_fs_path_change_modify:
4095 change_string = ACTION_MODIFY;
4096 break;
4097 case svn_fs_path_change_add:
4098 change_string = ACTION_ADD;
4099 break;
4100 case svn_fs_path_change_delete:
4101 change_string = ACTION_DELETE;
4102 break;
4103 case svn_fs_path_change_replace:
4104 change_string = ACTION_REPLACE;
4105 break;
4106 case svn_fs_path_change_reset:
4107 change_string = ACTION_RESET;
4108 break;
4109 default:
4110 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4111 _("Invalid change type"));
4114 if (change->node_rev_id)
4115 idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
4116 else
4117 idstr = ACTION_RESET;
4119 buf = apr_psprintf(pool, "%s %s %s %s %s\n",
4120 idstr, change_string,
4121 change->text_mod ? FLAG_TRUE : FLAG_FALSE,
4122 change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
4123 path);
4125 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
4127 if (copyfrom)
4129 SVN_ERR(svn_io_file_write_full(file, copyfrom, strlen(copyfrom),
4130 NULL, pool));
4133 SVN_ERR(svn_io_file_write_full(file, "\n", 1, NULL, pool));
4135 return SVN_NO_ERROR;
4138 svn_error_t *
4139 svn_fs_fs__add_change(svn_fs_t *fs,
4140 const char *txn_id,
4141 const char *path,
4142 const svn_fs_id_t *id,
4143 svn_fs_path_change_kind_t change_kind,
4144 svn_boolean_t text_mod,
4145 svn_boolean_t prop_mod,
4146 svn_revnum_t copyfrom_rev,
4147 const char *copyfrom_path,
4148 apr_pool_t *pool)
4150 apr_file_t *file;
4151 const char *copyfrom;
4152 svn_fs_path_change_t *change = apr_pcalloc(pool, sizeof(*change));
4154 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
4155 APR_APPEND | APR_WRITE | APR_CREATE
4156 | APR_BUFFERED, APR_OS_DEFAULT, pool));
4158 if (copyfrom_rev != SVN_INVALID_REVNUM)
4159 copyfrom = apr_psprintf(pool, "%ld %s", copyfrom_rev, copyfrom_path);
4160 else
4161 copyfrom = "";
4163 change->node_rev_id = id;
4164 change->change_kind = change_kind;
4165 change->text_mod = text_mod;
4166 change->prop_mod = prop_mod;
4168 SVN_ERR(write_change_entry(file, path, change, copyfrom, pool));
4170 SVN_ERR(svn_io_file_close(file, pool));
4172 return SVN_NO_ERROR;
4175 /* This baton is used by the representation writing streams. It keeps
4176 track of the checksum information as well as the total size of the
4177 representation so far. */
4178 struct rep_write_baton
4180 /* The FS we are writing to. */
4181 svn_fs_t *fs;
4183 /* Actual file to which we are writing. */
4184 svn_stream_t *rep_stream;
4186 /* A stream from the delta combiner. Data written here gets
4187 deltified, then eventually written to rep_stream. */
4188 svn_stream_t *delta_stream;
4190 /* Where is this representation header stored. */
4191 apr_off_t rep_offset;
4193 /* Start of the actual data. */
4194 apr_off_t delta_start;
4196 /* How many bytes have been written to this rep already. */
4197 svn_filesize_t rep_size;
4199 /* The node revision for which we're writing out info. */
4200 node_revision_t *noderev;
4202 /* Actual output file. */
4203 apr_file_t *file;
4204 /* Lock 'cookie' used to unlock the output file once we've finished
4205 writing to it. */
4206 void *lockcookie;
4208 struct apr_md5_ctx_t md5_context;
4210 apr_pool_t *pool;
4212 apr_pool_t *parent_pool;
4215 /* Handler for the write method of the representation writable stream.
4216 BATON is a rep_write_baton, DATA is the data to write, and *LEN is
4217 the length of this data. */
4218 static svn_error_t *
4219 rep_write_contents(void *baton,
4220 const char *data,
4221 apr_size_t *len)
4223 struct rep_write_baton *b = baton;
4225 apr_md5_update(&b->md5_context, data, *len);
4226 b->rep_size += *len;
4228 /* If we are writing a delta, use that stream. */
4229 if (b->delta_stream)
4231 SVN_ERR(svn_stream_write(b->delta_stream, data, len));
4233 else
4235 SVN_ERR(svn_stream_write(b->rep_stream, data, len));
4238 return SVN_NO_ERROR;
4241 /* Given a node-revision NODEREV in filesystem FS, return the
4242 representation in *REP to use as the base for a text representation
4243 delta. Perform temporary allocations in *POOL. */
4244 static svn_error_t *
4245 choose_delta_base(representation_t **rep,
4246 svn_fs_t *fs,
4247 node_revision_t *noderev,
4248 apr_pool_t *pool)
4250 int count;
4251 node_revision_t *base;
4253 /* If we have no predecessors, then use the empty stream as a
4254 base. */
4255 if (! noderev->predecessor_count)
4257 *rep = NULL;
4258 return SVN_NO_ERROR;
4261 /* Flip the rightmost '1' bit of the predecessor count to determine
4262 which file rev (counting from 0) we want to use. (To see why
4263 count & (count - 1) unsets the rightmost set bit, think about how
4264 you decrement a binary number.) */
4265 count = noderev->predecessor_count;
4266 count = count & (count - 1);
4268 /* Walk back a number of predecessors equal to the difference
4269 between count and the original predecessor count. (For example,
4270 if noderev has ten predecessors and we want the eighth file rev,
4271 walk back two predecessors.) */
4272 base = noderev;
4273 while ((count++) < noderev->predecessor_count)
4274 SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
4275 base->predecessor_id, pool));
4277 *rep = base->data_rep;
4279 return SVN_NO_ERROR;
4282 /* Get a rep_write_baton and store it in *WB_P for the representation
4283 indicated by NODEREV in filesystem FS. Perform allocations in
4284 POOL. Only appropriate for file contents, not for props or
4285 directory contents. */
4286 static svn_error_t *
4287 rep_write_get_baton(struct rep_write_baton **wb_p,
4288 svn_fs_t *fs,
4289 node_revision_t *noderev,
4290 apr_pool_t *pool)
4292 struct rep_write_baton *b;
4293 apr_file_t *file;
4294 representation_t *base_rep;
4295 svn_stream_t *source;
4296 const char *header;
4297 svn_txdelta_window_handler_t wh;
4298 void *whb;
4299 fs_fs_data_t *ffd = fs->fsap_data;
4301 b = apr_pcalloc(pool, sizeof(*b));
4303 apr_md5_init(&(b->md5_context));
4305 b->fs = fs;
4306 b->parent_pool = pool;
4307 b->pool = svn_pool_create(pool);
4308 b->rep_size = 0;
4309 b->noderev = noderev;
4311 /* Open the prototype rev file and seek to its end. */
4312 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
4313 fs, svn_fs_fs__id_txn_id(noderev->id),
4314 b->pool));
4316 b->file = file;
4317 b->rep_stream = svn_stream_from_aprfile(file, b->pool);
4319 SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
4321 /* Get the base for this delta. */
4322 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, b->pool));
4323 SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
4325 /* Write out the rep header. */
4326 if (base_rep)
4328 header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
4329 SVN_FILESIZE_T_FMT "\n",
4330 base_rep->revision, base_rep->offset,
4331 base_rep->size);
4333 else
4335 header = REP_DELTA "\n";
4337 SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
4338 b->pool));
4340 /* Now determine the offset of the actual svndiff data. */
4341 SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
4343 /* Prepare to write the svndiff data. */
4344 if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT)
4345 svn_txdelta_to_svndiff2(&wh, &whb, b->rep_stream, 1, pool);
4346 else
4347 svn_txdelta_to_svndiff2(&wh, &whb, b->rep_stream, 0, pool);
4349 b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
4351 *wb_p = b;
4353 return SVN_NO_ERROR;
4356 /* Close handler for the representation write stream. BATON is a
4357 rep_write_baton. Writes out a new node-rev that correctly
4358 references the representation we just finished writing. */
4359 static svn_error_t *
4360 rep_write_contents_close(void *baton)
4362 struct rep_write_baton *b = baton;
4363 representation_t *rep;
4364 apr_off_t offset;
4366 rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
4367 rep->offset = b->rep_offset;
4369 /* Close our delta stream so the last bits of svndiff are written
4370 out. */
4371 if (b->delta_stream)
4372 SVN_ERR(svn_stream_close(b->delta_stream));
4374 /* Determine the length of the svndiff data. */
4375 SVN_ERR(get_file_offset(&offset, b->file, b->pool));
4376 rep->size = offset - b->delta_start;
4378 /* Fill in the rest of the representation field. */
4379 rep->expanded_size = b->rep_size;
4380 rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
4381 rep->revision = SVN_INVALID_REVNUM;
4383 /* Finalize the MD5 checksum. */
4384 apr_md5_final(rep->checksum, &b->md5_context);
4386 /* Write out our cosmetic end marker. */
4387 SVN_ERR(svn_stream_printf(b->rep_stream, b->pool, "ENDREP\n"));
4389 b->noderev->data_rep = rep;
4391 /* Write out the new node-rev information. */
4392 SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
4393 b->pool));
4395 SVN_ERR(svn_io_file_close(b->file, b->pool));
4396 SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
4397 svn_pool_destroy(b->pool);
4399 return SVN_NO_ERROR;
4402 /* Store a writable stream in *CONTENTS_P that will receive all data
4403 written and store it as the file data representation referenced by
4404 NODEREV in filesystem FS. Perform temporary allocations in
4405 POOL. Only appropriate for file data, not props or directory
4406 contents. */
4407 static svn_error_t *
4408 set_representation(svn_stream_t **contents_p,
4409 svn_fs_t *fs,
4410 node_revision_t *noderev,
4411 apr_pool_t *pool)
4413 struct rep_write_baton *wb;
4415 if (! svn_fs_fs__id_txn_id(noderev->id))
4416 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4417 _("Attempted to write to non-transaction"));
4419 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
4421 *contents_p = svn_stream_create(wb, pool);
4422 svn_stream_set_write(*contents_p, rep_write_contents);
4423 svn_stream_set_close(*contents_p, rep_write_contents_close);
4425 return SVN_NO_ERROR;
4428 svn_error_t *
4429 svn_fs_fs__set_contents(svn_stream_t **stream,
4430 svn_fs_t *fs,
4431 node_revision_t *noderev,
4432 apr_pool_t *pool)
4434 if (noderev->kind != svn_node_file)
4435 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
4436 _("Can't set text contents of a directory"));
4438 return set_representation(stream, fs, noderev, pool);
4441 svn_error_t *
4442 svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
4443 svn_fs_t *fs,
4444 const svn_fs_id_t *old_idp,
4445 node_revision_t *new_noderev,
4446 const char *copy_id,
4447 const char *txn_id,
4448 apr_pool_t *pool)
4450 const svn_fs_id_t *id;
4452 if (! copy_id)
4453 copy_id = svn_fs_fs__id_copy_id(old_idp);
4454 id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
4455 txn_id, pool);
4457 new_noderev->id = id;
4459 if (! new_noderev->copyroot_path)
4461 new_noderev->copyroot_path = apr_pstrdup(pool,
4462 new_noderev->created_path);
4463 new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
4466 SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
4467 pool));
4469 *new_id_p = id;
4471 return SVN_NO_ERROR;
4474 svn_error_t *
4475 svn_fs_fs__set_proplist(svn_fs_t *fs,
4476 node_revision_t *noderev,
4477 apr_hash_t *proplist,
4478 apr_pool_t *pool)
4480 const char *filename = path_txn_node_props(fs, noderev->id, pool);
4481 apr_file_t *file;
4482 svn_stream_t *out;
4484 /* Dump the property list to the mutable property file. */
4485 SVN_ERR(svn_io_file_open(&file, filename,
4486 APR_WRITE | APR_CREATE | APR_TRUNCATE
4487 | APR_BUFFERED, APR_OS_DEFAULT, pool));
4488 out = svn_stream_from_aprfile(file, pool);
4489 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
4490 SVN_ERR(svn_io_file_close(file, pool));
4492 /* Mark the node-rev's prop rep as mutable, if not already done. */
4493 if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
4495 noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
4496 noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
4497 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
4500 return SVN_NO_ERROR;
4503 /* Read the 'current' file for filesystem FS and store the next
4504 available node id in *NODE_ID, and the next available copy id in
4505 *COPY_ID. Allocations are performed from POOL. */
4506 static svn_error_t *
4507 get_next_revision_ids(const char **node_id,
4508 const char **copy_id,
4509 svn_fs_t *fs,
4510 apr_pool_t *pool)
4512 char *buf;
4513 char *str, *last_str;
4515 SVN_ERR(read_current(svn_fs_fs__path_current(fs, pool), &buf, pool));
4517 str = apr_strtok(buf, " ", &last_str);
4518 if (! str)
4519 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4520 _("Corrupt current file"));
4522 str = apr_strtok(NULL, " ", &last_str);
4523 if (! str)
4524 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4525 _("Corrupt current file"));
4527 *node_id = apr_pstrdup(pool, str);
4529 str = apr_strtok(NULL, " ", &last_str);
4530 if (! str)
4531 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4532 _("Corrupt current file"));
4534 *copy_id = apr_pstrdup(pool, str);
4536 return SVN_NO_ERROR;
4539 /* This baton is used by the stream created for write_hash_rep. */
4540 struct write_hash_baton
4542 svn_stream_t *stream;
4544 apr_size_t size;
4546 struct apr_md5_ctx_t md5_context;
4549 /* The handler for the write_hash_rep stream. BATON is a
4550 write_hash_baton, DATA has the data to write and *LEN is the number
4551 of bytes to write. */
4552 static svn_error_t *
4553 write_hash_handler(void *baton,
4554 const char *data,
4555 apr_size_t *len)
4557 struct write_hash_baton *whb = baton;
4559 apr_md5_update(&whb->md5_context, data, *len);
4561 SVN_ERR(svn_stream_write(whb->stream, data, len));
4562 whb->size += *len;
4564 return SVN_NO_ERROR;
4567 /* Write out the hash HASH as a text representation to file FILE. In
4568 the process, record the total size of the dump in *SIZE, and the
4569 md5 digest in CHECKSUM. Perform temporary allocations in POOL. */
4570 static svn_error_t *
4571 write_hash_rep(svn_filesize_t *size,
4572 unsigned char checksum[APR_MD5_DIGESTSIZE],
4573 apr_file_t *file,
4574 apr_hash_t *hash,
4575 apr_pool_t *pool)
4577 svn_stream_t *stream;
4578 struct write_hash_baton *whb;
4580 whb = apr_pcalloc(pool, sizeof(*whb));
4582 whb->stream = svn_stream_from_aprfile(file, pool);
4583 whb->size = 0;
4584 apr_md5_init(&(whb->md5_context));
4586 stream = svn_stream_create(whb, pool);
4587 svn_stream_set_write(stream, write_hash_handler);
4589 SVN_ERR(svn_stream_printf(whb->stream, pool, "PLAIN\n"));
4591 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
4593 /* Store the results. */
4594 apr_md5_final(checksum, &whb->md5_context);
4595 *size = whb->size;
4597 SVN_ERR(svn_stream_printf(whb->stream, pool, "ENDREP\n"));
4599 return SVN_NO_ERROR;
4602 /* Copy a node-revision specified by id ID in fileystem FS from a
4603 transaction into the permanent rev-file FILE. Return the offset of
4604 the new node-revision in *OFFSET. If this is a directory, all
4605 children are copied as well. START_NODE_ID and START_COPY_ID are
4606 the first available node and copy ids for this filesystem.
4607 Temporary allocations are from POOL. */
4608 static svn_error_t *
4609 write_final_rev(const svn_fs_id_t **new_id_p,
4610 apr_file_t *file,
4611 svn_revnum_t rev,
4612 svn_fs_t *fs,
4613 const svn_fs_id_t *id,
4614 const char *start_node_id,
4615 const char *start_copy_id,
4616 apr_hash_t *node_origins,
4617 apr_pool_t *pool)
4619 node_revision_t *noderev;
4620 apr_off_t my_offset;
4621 char my_node_id[MAX_KEY_SIZE + 2];
4622 char my_copy_id[MAX_KEY_SIZE + 2];
4623 const svn_fs_id_t *new_id;
4624 const char *node_id, *copy_id;
4625 svn_boolean_t node_id_is_new = FALSE;
4627 *new_id_p = NULL;
4629 /* Check to see if this is a transaction node. */
4630 if (! svn_fs_fs__id_txn_id(id))
4631 return SVN_NO_ERROR;
4633 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
4635 if (noderev->kind == svn_node_dir)
4637 apr_pool_t *subpool;
4638 apr_hash_t *entries, *str_entries;
4639 svn_fs_dirent_t *dirent;
4640 void *val;
4641 apr_hash_index_t *hi;
4643 /* This is a directory. Write out all the children first. */
4644 subpool = svn_pool_create(pool);
4646 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
4648 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
4650 svn_pool_clear(subpool);
4651 apr_hash_this(hi, NULL, NULL, &val);
4652 dirent = val;
4653 SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
4654 start_node_id, start_copy_id,
4655 node_origins, subpool));
4656 if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
4657 dirent->id = svn_fs_fs__id_copy(new_id, pool);
4659 svn_pool_destroy(subpool);
4661 if (noderev->data_rep && noderev->data_rep->txn_id)
4663 /* Write out the contents of this directory as a text rep. */
4664 SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
4666 noderev->data_rep->txn_id = NULL;
4667 noderev->data_rep->revision = rev;
4668 SVN_ERR(get_file_offset(&noderev->data_rep->offset, file, pool));
4669 SVN_ERR(write_hash_rep(&noderev->data_rep->size,
4670 noderev->data_rep->checksum, file,
4671 str_entries, pool));
4672 noderev->data_rep->expanded_size = noderev->data_rep->size;
4675 else
4677 /* This is a file. We should make sure the data rep, if it
4678 exists in a "this" state, gets rewritten to our new revision
4679 num. */
4681 if (noderev->data_rep && noderev->data_rep->txn_id)
4683 noderev->data_rep->txn_id = NULL;
4684 noderev->data_rep->revision = rev;
4688 /* Fix up the property reps. */
4689 if (noderev->prop_rep && noderev->prop_rep->txn_id)
4691 apr_hash_t *proplist;
4693 SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
4694 SVN_ERR(get_file_offset(&noderev->prop_rep->offset, file, pool));
4695 SVN_ERR(write_hash_rep(&noderev->prop_rep->size,
4696 noderev->prop_rep->checksum, file,
4697 proplist, pool));
4699 noderev->prop_rep->txn_id = NULL;
4700 noderev->prop_rep->revision = rev;
4704 /* Convert our temporary ID into a permanent revision one. */
4705 SVN_ERR(get_file_offset(&my_offset, file, pool));
4707 node_id = svn_fs_fs__id_node_id(noderev->id);
4708 if (*node_id == '_')
4710 node_id_is_new = TRUE;
4711 svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id);
4713 else
4714 strcpy(my_node_id, node_id);
4716 copy_id = svn_fs_fs__id_copy_id(noderev->id);
4717 if (*copy_id == '_')
4718 svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id);
4719 else
4720 strcpy(my_copy_id, copy_id);
4722 if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
4723 noderev->copyroot_rev = rev;
4725 new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
4726 pool);
4728 noderev->id = new_id;
4730 if (node_id_is_new)
4732 apr_pool_t *hash_pool = apr_hash_pool_get(node_origins);
4733 const char *key = apr_pstrdup(hash_pool, my_node_id);
4734 const svn_fs_id_t *val = svn_fs_fs__id_copy(new_id, hash_pool);
4736 apr_hash_set(node_origins, key, APR_HASH_KEY_STRING, val);
4739 /* Write out our new node-revision. */
4740 SVN_ERR(write_noderev_txn(file, noderev, pool));
4742 /* Return our ID that references the revision file. */
4743 *new_id_p = noderev->id;
4745 return SVN_NO_ERROR;
4748 /* Write the changed path info from transaction TXN_ID in filesystem
4749 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
4750 in the file of the beginning of this information. Perform
4751 temporary allocations in POOL. */
4752 static svn_error_t *
4753 write_final_changed_path_info(apr_off_t *offset_p,
4754 apr_file_t *file,
4755 svn_fs_t *fs,
4756 const char *txn_id,
4757 apr_pool_t *pool)
4759 const char *copyfrom;
4760 apr_hash_t *changed_paths, *copyfrom_cache = apr_hash_make(pool);
4761 apr_off_t offset;
4762 apr_hash_index_t *hi;
4763 apr_pool_t *iterpool = svn_pool_create(pool);
4765 SVN_ERR(get_file_offset(&offset, file, pool));
4767 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id,
4768 copyfrom_cache, pool));
4770 /* Iterate through the changed paths one at a time, and convert the
4771 temporary node-id into a permanent one for each change entry. */
4772 for (hi = apr_hash_first(pool, changed_paths); hi; hi = apr_hash_next(hi))
4774 node_revision_t *noderev;
4775 const svn_fs_id_t *id;
4776 svn_fs_path_change_t *change;
4777 const void *key;
4778 void *val;
4780 svn_pool_clear(iterpool);
4782 apr_hash_this(hi, &key, NULL, &val);
4783 change = val;
4785 id = change->node_rev_id;
4787 /* If this was a delete of a mutable node, then it is OK to
4788 leave the change entry pointing to the non-existent temporary
4789 node, since it will never be used. */
4790 if ((change->change_kind != svn_fs_path_change_delete) &&
4791 (! svn_fs_fs__id_txn_id(id)))
4793 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
4795 /* noderev has the permanent node-id at this point, so we just
4796 substitute it for the temporary one. */
4797 change->node_rev_id = noderev->id;
4800 /* Find the cached copyfrom information. */
4801 copyfrom = apr_hash_get(copyfrom_cache, key, APR_HASH_KEY_STRING);
4803 /* Write out the new entry into the final rev-file. */
4804 SVN_ERR(write_change_entry(file, key, change, copyfrom, iterpool));
4807 svn_pool_destroy(iterpool);
4809 *offset_p = offset;
4811 return SVN_NO_ERROR;
4815 svn_error_t *
4816 svn_fs_fs__dup_perms(const char *filename,
4817 const char *perms_reference,
4818 apr_pool_t *pool)
4820 #ifndef WIN32
4821 apr_status_t status;
4822 apr_finfo_t finfo;
4823 const char *filename_apr, *perms_reference_apr;
4825 SVN_ERR(svn_path_cstring_from_utf8(&filename_apr, filename, pool));
4826 SVN_ERR(svn_path_cstring_from_utf8(&perms_reference_apr, perms_reference,
4827 pool));
4829 status = apr_stat(&finfo, perms_reference_apr, APR_FINFO_PROT, pool);
4830 if (status)
4831 return svn_error_wrap_apr(status, _("Can't stat '%s'"),
4832 svn_path_local_style(perms_reference, pool));
4833 status = apr_file_perms_set(filename_apr, finfo.protection);
4834 if (status)
4835 return svn_error_wrap_apr(status, _("Can't chmod '%s'"),
4836 svn_path_local_style(filename, pool));
4837 #endif
4838 return SVN_NO_ERROR;
4842 svn_error_t *
4843 svn_fs_fs__move_into_place(const char *old_filename,
4844 const char *new_filename,
4845 const char *perms_reference,
4846 apr_pool_t *pool)
4848 svn_error_t *err;
4850 SVN_ERR(svn_fs_fs__dup_perms(old_filename, perms_reference, pool));
4852 /* Move the file into place. */
4853 err = svn_io_file_rename(old_filename, new_filename, pool);
4854 if (err && APR_STATUS_IS_EXDEV(err->apr_err))
4856 apr_file_t *file;
4858 /* Can't rename across devices; fall back to copying. */
4859 svn_error_clear(err);
4860 err = SVN_NO_ERROR;
4861 SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
4863 /* Flush the target of the copy to disk. */
4864 SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
4865 APR_OS_DEFAULT, pool));
4866 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4867 SVN_ERR(svn_io_file_close(file, pool));
4869 if (err)
4870 return err;
4872 #ifdef __linux__
4874 /* Linux has the unusual feature that fsync() on a file is not
4875 enough to ensure that a file's directory entries have been
4876 flushed to disk; you have to fsync the directory as well.
4877 On other operating systems, we'd only be asking for trouble
4878 by trying to open and fsync a directory. */
4879 const char *dirname;
4880 apr_file_t *file;
4882 dirname = svn_path_dirname(new_filename, pool);
4883 SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
4884 pool));
4885 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4886 SVN_ERR(svn_io_file_close(file, pool));
4888 #endif
4890 return SVN_NO_ERROR;
4893 /* Atomically update the current file to hold the specifed REV, NEXT_NODE_ID,
4894 and NEXT_COPY_ID. Perform temporary allocations in POOL. */
4895 static svn_error_t *
4896 write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
4897 const char *next_copy_id, apr_pool_t *pool)
4899 char *buf;
4900 const char *tmp_name, *name;
4901 apr_file_t *file;
4903 /* Now we can just write out this line. */
4904 buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
4906 name = svn_fs_fs__path_current(fs, pool);
4907 SVN_ERR(svn_io_open_unique_file2(&file, &tmp_name, name, ".tmp",
4908 svn_io_file_del_none, pool));
4910 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
4912 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4914 SVN_ERR(svn_io_file_close(file, pool));
4916 SVN_ERR(svn_fs_fs__move_into_place(tmp_name, name, name, pool));
4918 return SVN_NO_ERROR;
4921 /* Update the current file to hold the correct next node and copy_ids
4922 from transaction TXN_ID in filesystem FS. The current revision is
4923 set to REV. Perform temporary allocations in POOL. */
4924 static svn_error_t *
4925 write_final_current(svn_fs_t *fs,
4926 const char *txn_id,
4927 svn_revnum_t rev,
4928 const char *start_node_id,
4929 const char *start_copy_id,
4930 apr_pool_t *pool)
4932 const char *txn_node_id, *txn_copy_id;
4933 char new_node_id[MAX_KEY_SIZE + 2];
4934 char new_copy_id[MAX_KEY_SIZE + 2];
4936 /* To find the next available ids, we add the id that used to be in
4937 the current file, to the next ids from the transaction file. */
4938 SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
4940 svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
4941 svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
4943 return write_current(fs, rev, new_node_id, new_copy_id, pool);
4946 /* Verify that the user registed with FS has all the locks necessary to
4947 permit all the changes associate with TXN_NAME.
4948 The FS write lock is assumed to be held by the caller. */
4949 static svn_error_t *
4950 verify_locks(svn_fs_t *fs,
4951 const char *txn_name,
4952 apr_pool_t *pool)
4954 apr_pool_t *subpool = svn_pool_create(pool);
4955 apr_hash_t *changes;
4956 apr_hash_index_t *hi;
4957 apr_array_header_t *changed_paths;
4958 svn_stringbuf_t *last_recursed = NULL;
4959 int i;
4961 /* Fetch the changes for this transaction. */
4962 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, NULL, pool));
4964 /* Make an array of the changed paths, and sort them depth-first-ily. */
4965 changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
4966 sizeof(const char *));
4967 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
4969 const void *key;
4970 apr_hash_this(hi, &key, NULL, NULL);
4971 APR_ARRAY_PUSH(changed_paths, const char *) = key;
4973 qsort(changed_paths->elts, changed_paths->nelts,
4974 changed_paths->elt_size, svn_sort_compare_paths);
4976 /* Now, traverse the array of changed paths, verify locks. Note
4977 that if we need to do a recursive verification a path, we'll skip
4978 over children of that path when we get to them. */
4979 for (i = 0; i < changed_paths->nelts; i++)
4981 const char *path;
4982 svn_fs_path_change_t *change;
4983 svn_boolean_t recurse = TRUE;
4985 svn_pool_clear(subpool);
4986 path = APR_ARRAY_IDX(changed_paths, i, const char *);
4988 /* If this path has already been verified as part of a recursive
4989 check of one of its parents, no need to do it again. */
4990 if (last_recursed
4991 && svn_path_is_child(last_recursed->data, path, subpool))
4992 continue;
4994 /* Fetch the change associated with our path. */
4995 change = apr_hash_get(changes, path, APR_HASH_KEY_STRING);
4997 /* What does it mean to succeed at lock verification for a given
4998 path? For an existing file or directory getting modified
4999 (text, props), it means we hold the lock on the file or
5000 directory. For paths being added or removed, we need to hold
5001 the locks for that path and any children of that path.
5003 WHEW! We have no reliable way to determine the node kind
5004 of deleted items, but fortunately we are going to do a
5005 recursive check on deleted paths regardless of their kind. */
5006 if (change->change_kind == svn_fs_path_change_modify)
5007 recurse = FALSE;
5008 SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
5009 subpool));
5011 /* If we just did a recursive check, remember the path we
5012 checked (so children can be skipped). */
5013 if (recurse)
5015 if (! last_recursed)
5016 last_recursed = svn_stringbuf_create(path, pool);
5017 else
5018 svn_stringbuf_set(last_recursed, path);
5021 svn_pool_destroy(subpool);
5022 return SVN_NO_ERROR;
5025 /* Baton used for commit_body below. */
5026 struct commit_baton {
5027 svn_revnum_t *new_rev_p;
5028 svn_fs_t *fs;
5029 svn_fs_txn_t *txn;
5030 apr_hash_t *node_origins;
5033 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
5034 This implements the svn_fs_fs__with_write_lock() 'body' callback
5035 type. BATON is a 'struct commit_baton *'. */
5036 static svn_error_t *
5037 commit_body(void *baton, apr_pool_t *pool)
5039 struct commit_baton *cb = baton;
5040 fs_fs_data_t *ffd = cb->fs->fsap_data;
5041 const char *old_rev_filename, *rev_filename, *proto_filename;
5042 const char *revprop_filename, *final_revprop;
5043 const svn_fs_id_t *root_id, *new_root_id;
5044 const char *start_node_id, *start_copy_id;
5045 svn_revnum_t old_rev, new_rev;
5046 apr_file_t *proto_file;
5047 void *proto_file_lockcookie;
5048 apr_off_t changed_path_offset;
5049 char *buf;
5050 apr_hash_t *txnprops;
5051 svn_string_t date;
5052 apr_hash_t *target_mergeinfo;
5054 /* Get the current youngest revision. */
5055 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
5057 /* Check to make sure this transaction is based off the most recent
5058 revision. */
5059 if (cb->txn->base_rev != old_rev)
5060 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
5061 _("Transaction out of date"));
5063 /* Locks may have been added (or stolen) between the calling of
5064 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
5065 to re-examine every changed-path in the txn and re-verify all
5066 discovered locks. */
5067 SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
5069 /* Get the next node_id and copy_id to use. */
5070 SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
5071 pool));
5073 /* We are going to be one better than this puny old revision. */
5074 new_rev = old_rev + 1;
5076 /* Get a write handle on the proto revision file. */
5077 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
5078 cb->fs, cb->txn->id, pool));
5080 /* Write out all the node-revisions and directory contents. */
5081 root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
5082 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
5083 start_node_id, start_copy_id, cb->node_origins,
5084 pool));
5086 /* Write the changed-path information. */
5087 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
5088 cb->fs, cb->txn->id, pool));
5090 /* Write the final line. */
5091 buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
5092 svn_fs_fs__id_offset(new_root_id),
5093 changed_path_offset);
5094 SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
5095 pool));
5096 SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
5097 SVN_ERR(svn_io_file_close(proto_file, pool));
5099 /* We don't unlock the prototype revision file immediately to avoid a
5100 race with another caller writing to the prototype revision file
5101 before we commit it. */
5103 /* Remove any temporary txn props representing 'flags'. */
5104 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
5105 if (txnprops)
5107 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
5108 svn_prop_t prop;
5109 prop.value = NULL;
5111 if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_OOD,
5112 APR_HASH_KEY_STRING))
5114 prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
5115 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
5118 if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS,
5119 APR_HASH_KEY_STRING))
5121 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
5122 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
5125 if (! apr_is_empty_array(props))
5126 SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, props, pool));
5129 SVN_ERR(get_txn_mergeinfo(&target_mergeinfo, cb->txn->fs, cb->txn->id, pool));
5131 /* Create the shard for the rev and revprop file, if we're sharding and
5132 this is the first revision of a new shard. We don't care if this
5133 fails because the shard already existed for some reason. */
5134 if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
5136 svn_error_t *err;
5137 err = svn_io_dir_make(path_rev_shard(cb->fs, new_rev, pool),
5138 APR_OS_DEFAULT, pool);
5139 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5140 svn_error_clear(err);
5141 else
5142 SVN_ERR(err);
5143 err = svn_io_dir_make(path_revprops_shard(cb->fs, new_rev, pool),
5144 APR_OS_DEFAULT, pool);
5145 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5146 svn_error_clear(err);
5147 else
5148 SVN_ERR(err);
5151 /* Move the finished rev file into place. */
5152 old_rev_filename = svn_fs_fs__path_rev(cb->fs, old_rev, pool);
5153 rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
5154 proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
5155 SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
5156 old_rev_filename, pool));
5158 /* Now that we've moved the prototype revision file out of the way,
5159 we can unlock it (since further attempts to write to the file
5160 will fail as it no longer exists). We must do this so that we can
5161 remove the transaction directory later. */
5162 SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
5164 /* Update commit time to ensure that svn:date revprops remain ordered. */
5165 date.data = svn_time_to_cstring(apr_time_now(), pool);
5166 date.len = strlen(date.data);
5168 SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
5169 &date, pool));
5171 /* Move the revprops file into place. */
5172 revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
5173 final_revprop = path_revprops(cb->fs, new_rev, pool);
5174 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
5175 old_rev_filename, pool));
5177 /* Update the 'current' file. */
5178 SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
5179 start_copy_id, pool));
5180 ffd->youngest_rev_cache = new_rev;
5182 /* Remove this transaction directory. */
5183 SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
5185 *cb->new_rev_p = new_rev;
5187 return SVN_NO_ERROR;
5190 svn_error_t *
5191 svn_fs_fs__commit(svn_revnum_t *new_rev_p,
5192 svn_fs_t *fs,
5193 svn_fs_txn_t *txn,
5194 apr_pool_t *pool)
5196 struct commit_baton cb;
5197 apr_hash_t *node_origins = apr_hash_make(pool);
5199 cb.new_rev_p = new_rev_p;
5200 cb.fs = fs;
5201 cb.txn = txn;
5202 cb.node_origins = node_origins;
5203 SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
5205 /* Now that we're no longer locked, we can update the node-origins
5206 cache without blocking writers. */
5207 if (apr_hash_count(node_origins) > 0)
5208 SVN_ERR(svn_fs_fs__set_node_origins(fs, node_origins, pool));
5210 return SVN_NO_ERROR;
5213 svn_error_t *
5214 svn_fs_fs__reserve_copy_id(const char **copy_id_p,
5215 svn_fs_t *fs,
5216 const char *txn_id,
5217 apr_pool_t *pool)
5219 const char *cur_node_id, *cur_copy_id;
5220 char *copy_id;
5221 apr_size_t len;
5223 /* First read in the current next-ids file. */
5224 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
5226 copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
5228 len = strlen(cur_copy_id);
5229 svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
5231 SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
5233 *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, NULL);
5235 return SVN_NO_ERROR;
5238 /* Write out the zeroth revision for filesystem FS. */
5239 static svn_error_t *
5240 write_revision_zero(svn_fs_t *fs)
5242 apr_hash_t *proplist;
5243 svn_string_t date;
5245 /* Write out a rev file for revision 0. */
5246 SVN_ERR(svn_io_file_create(svn_fs_fs__path_rev(fs, 0, fs->pool),
5247 "PLAIN\nEND\nENDREP\n"
5248 "id: 0.0.r0/17\n"
5249 "type: dir\n"
5250 "count: 0\n"
5251 "text: 0 0 4 4 "
5252 "2d2977d1c96f487abe4a1e202dd03b4e\n"
5253 "cpath: /\n"
5254 "\n\n17 107\n", fs->pool));
5256 /* Set a date on revision 0. */
5257 date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
5258 date.len = strlen(date.data);
5259 proplist = apr_hash_make(fs->pool);
5260 apr_hash_set(proplist, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING, &date);
5261 return svn_fs_fs__set_revision_proplist(fs, 0, proplist, fs->pool);
5264 svn_error_t *
5265 svn_fs_fs__create(svn_fs_t *fs,
5266 const char *path,
5267 apr_pool_t *pool)
5269 int format = SVN_FS_FS__FORMAT_NUMBER;
5270 fs_fs_data_t *ffd = fs->fsap_data;
5272 fs->path = apr_pstrdup(pool, path);
5273 /* See if we had an explicitly requested pre-1.4- or pre-1.5-compatible. */
5274 if (fs->config)
5276 if (apr_hash_get(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE,
5277 APR_HASH_KEY_STRING))
5278 format = 1;
5279 else if (apr_hash_get(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE,
5280 APR_HASH_KEY_STRING))
5281 format = 2;
5283 ffd->format = format;
5285 /* Override the default linear layout if this is a new-enough format. */
5286 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
5287 ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
5289 if (ffd->max_files_per_dir)
5291 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool),
5292 pool));
5293 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
5294 pool));
5296 else
5298 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_REVS_DIR,
5299 pool),
5300 pool));
5301 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path,
5302 PATH_REVPROPS_DIR,
5303 pool),
5304 pool));
5306 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_TXNS_DIR,
5307 pool),
5308 pool));
5310 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
5311 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_TXN_PROTOS_DIR,
5312 pool),
5313 pool));
5315 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool), "0 1 1\n",
5316 pool));
5317 SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
5318 SVN_ERR(svn_fs_fs__set_uuid(fs, svn_uuid_generate(pool), pool));
5320 SVN_ERR(write_revision_zero(fs));
5322 /* Create the txn-current file if the repository supports
5323 the transaction sequence file. */
5324 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
5326 SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
5327 "0\n", pool));
5328 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
5329 "", pool));
5332 /* This filesystem is ready. Stamp it with a format number. */
5333 SVN_ERR(write_format(path_format(fs, pool),
5334 ffd->format, ffd->max_files_per_dir, pool));
5336 ffd->youngest_rev_cache = 0;
5337 return SVN_NO_ERROR;
5340 /* Part of the recovery procedure. Return the largest revision *REV in
5341 filesystem FS. Use POOL for temporary allocation. */
5342 static svn_error_t *
5343 recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
5345 /* Discovering the largest revision in the filesystem would be an
5346 expensive operation if we did a readdir() or searched linearly,
5347 so we'll do a form of binary search. left is a revision that we
5348 know exists, right a revision that we know does not exist. */
5349 apr_pool_t *iterpool;
5350 svn_revnum_t left, right = 1;
5352 iterpool = svn_pool_create(pool);
5353 /* Keep doubling right, until we find a revision that doesn't exist. */
5354 while (1)
5356 svn_node_kind_t kind;
5357 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs, right, iterpool),
5358 &kind, iterpool));
5359 svn_pool_clear(iterpool);
5361 if (kind == svn_node_none)
5362 break;
5364 right <<= 1;
5367 left = right >> 1;
5369 /* We know that left exists and right doesn't. Do a normal bsearch to find
5370 the last revision. */
5371 while (left + 1 < right)
5373 svn_revnum_t probe = left + ((right - left) / 2);
5374 svn_node_kind_t kind;
5376 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs, probe, iterpool),
5377 &kind, iterpool));
5378 svn_pool_clear(iterpool);
5380 if (kind == svn_node_none)
5381 right = probe;
5382 else
5383 left = probe;
5386 svn_pool_destroy(iterpool);
5388 /* left is now the largest revision that exists. */
5389 *rev = left;
5390 return SVN_NO_ERROR;
5393 /* A baton for reading a fixed amount from an open file. For
5394 recover_find_max_ids() below. */
5395 struct recover_read_from_file_baton
5397 apr_file_t *file;
5398 apr_pool_t *pool;
5399 apr_size_t remaining;
5402 /* A stream read handler used by recover_find_max_ids() below.
5403 Read and return at most BATON->REMAINING bytes from the stream,
5404 returning nothing after that to indicate EOF. */
5405 static svn_error_t *
5406 read_handler_recover(void *baton, char *buffer, apr_size_t *len)
5408 struct recover_read_from_file_baton *b = baton;
5409 apr_size_t bytes_to_read = *len;
5411 if (b->remaining == 0)
5413 /* Return a successful read of zero bytes to signal EOF. */
5414 *len = 0;
5415 return SVN_NO_ERROR;
5418 if (bytes_to_read > b->remaining)
5419 bytes_to_read = b->remaining;
5420 b->remaining -= bytes_to_read;
5422 return svn_io_file_read_full(b->file, buffer, bytes_to_read, len, b->pool);
5425 /* Part of the recovery procedure. Read the directory noderev at offset
5426 OFFSET of file REV_FILE (the revision file of revision REV of
5427 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
5428 and copy-id of that node, if greater than the current value stored
5429 in either. Recurse into any child directories that were modified in
5430 this revision.
5432 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
5434 Perform temporary allocation in POOL. */
5435 static svn_error_t *
5436 recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
5437 apr_file_t *rev_file, apr_off_t offset,
5438 char *max_node_id, char *max_copy_id,
5439 apr_pool_t *pool)
5441 apr_hash_t *headers;
5442 char *value;
5443 node_revision_t noderev;
5444 struct rep_args *ra;
5445 struct recover_read_from_file_baton baton;
5446 svn_stream_t *stream;
5447 apr_hash_t *entries;
5448 apr_hash_index_t *hi;
5449 apr_pool_t *iterpool;
5451 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
5452 SVN_ERR(read_header_block(&headers, rev_file, pool));
5454 /* We're going to populate a skeletal noderev - just the id and data_rep. */
5455 value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
5456 noderev.id = svn_fs_fs__id_parse(value, strlen(value), pool);
5458 /* Check that this is a directory. It should be. */
5459 value = apr_hash_get(headers, HEADER_TYPE, APR_HASH_KEY_STRING);
5460 if (value == NULL || strcmp(value, KIND_DIR) != 0)
5461 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5462 _("Recovery encountered a non-directory node"));
5464 /* Get the data location. No data location indicates an empty directory. */
5465 value = apr_hash_get(headers, HEADER_TEXT, APR_HASH_KEY_STRING);
5466 if (!value)
5467 return SVN_NO_ERROR;
5468 SVN_ERR(read_rep_offsets(&noderev.data_rep, value, NULL, FALSE, pool));
5470 /* If the directory's data representation wasn't changed in this revision,
5471 we've already scanned the directory's contents for noderevs, so we don't
5472 need to again. This will occur if a property is changed on a directory
5473 without changing the directory's contents. */
5474 if (noderev.data_rep->revision != rev)
5475 return SVN_NO_ERROR;
5477 /* We could use get_dir_contents(), but this is much cheaper. It does
5478 rely on directory entries being stored as PLAIN reps, though. */
5479 offset = noderev.data_rep->offset;
5480 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
5481 SVN_ERR(read_rep_line(&ra, rev_file, pool));
5482 if (ra->is_delta)
5483 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5484 _("Recovery encountered a deltified directory "
5485 "representation"));
5487 /* Now create a stream that's allowed to read only as much data as is
5488 stored in the representation. */
5489 baton.file = rev_file;
5490 baton.pool = pool;
5491 baton.remaining = noderev.data_rep->expanded_size;
5492 stream = svn_stream_create(&baton, pool);
5493 svn_stream_set_read(stream, read_handler_recover);
5495 /* Now read the entries from that stream. */
5496 entries = apr_hash_make(pool);
5497 SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
5498 SVN_ERR(svn_stream_close(stream));
5500 /* Now check each of the entries in our directory to find new node and
5501 copy ids, and recurse into new subdirectories. */
5502 iterpool = svn_pool_create(pool);
5503 for (hi = apr_hash_first(NULL, entries); hi; hi = apr_hash_next(hi))
5505 void *val;
5506 char *str_val;
5507 char *str, *last_str;
5508 svn_node_kind_t kind;
5509 svn_fs_id_t *id;
5510 const char *node_id, *copy_id;
5511 apr_off_t child_dir_offset;
5513 svn_pool_clear(iterpool);
5515 apr_hash_this(hi, NULL, NULL, &val);
5516 str_val = apr_pstrdup(iterpool, *((char **)val));
5518 str = apr_strtok(str_val, " ", &last_str);
5519 if (str == NULL)
5520 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5521 _("Directory entry corrupt"));
5523 if (strcmp(str, KIND_FILE) == 0)
5524 kind = svn_node_file;
5525 else if (strcmp(str, KIND_DIR) == 0)
5526 kind = svn_node_dir;
5527 else
5529 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5530 _("Directory entry corrupt"));
5533 str = apr_strtok(NULL, " ", &last_str);
5534 if (str == NULL)
5535 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5536 _("Directory entry corrupt"));
5538 id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
5540 if (svn_fs_fs__id_rev(id) != rev)
5542 /* If the node wasn't modified in this revision, we've already
5543 checked the node and copy id. */
5544 continue;
5547 node_id = svn_fs_fs__id_node_id(id);
5548 copy_id = svn_fs_fs__id_copy_id(id);
5550 if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
5551 strcpy(max_node_id, node_id);
5552 if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
5553 strcpy(max_copy_id, copy_id);
5555 if (kind == svn_node_file)
5556 continue;
5558 child_dir_offset = svn_fs_fs__id_offset(id);
5559 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
5560 max_node_id, max_copy_id, iterpool));
5562 svn_pool_destroy(iterpool);
5564 return SVN_NO_ERROR;
5567 /* Baton used for recover_body below. */
5568 struct recover_baton {
5569 svn_fs_t *fs;
5570 svn_cancel_func_t cancel_func;
5571 void *cancel_baton;
5574 /* The work-horse for svn_fs_fs__recover, called with the FS
5575 write lock. This implements the svn_fs_fs__with_write_lock()
5576 'body' callback type. BATON is a 'struct recover_baton *'. */
5577 static svn_error_t *
5578 recover_body(void *baton, apr_pool_t *pool)
5580 struct recover_baton *b = baton;
5581 svn_fs_t *fs = b->fs;
5582 svn_revnum_t rev, max_rev;
5583 apr_pool_t *iterpool;
5584 char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
5585 char next_node_id[MAX_KEY_SIZE], next_copy_id[MAX_KEY_SIZE];
5586 apr_size_t len;
5588 /* First, we need to know the largest revision in the filesystem. */
5589 SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
5591 /* Next we need to find the maximum node id and copy id in use across the
5592 filesystem. Unfortunately, the only way we can get this information
5593 is to scan all the noderevs of all the revisions and keep track as
5594 we go along. */
5595 iterpool = svn_pool_create(pool);
5596 for (rev = 0; rev <= max_rev; rev++)
5598 apr_file_t *rev_file;
5599 apr_off_t root_offset;
5601 svn_pool_clear(iterpool);
5603 if (b->cancel_func)
5604 SVN_ERR(b->cancel_func(b->cancel_baton));
5606 SVN_ERR(svn_io_file_open(&rev_file,
5607 svn_fs_fs__path_rev(fs, rev, iterpool),
5608 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
5609 iterpool));
5610 SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, iterpool));
5611 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
5612 max_node_id, max_copy_id, iterpool));
5614 svn_pool_destroy(iterpool);
5616 /* Now that we finally have the maximum revision, node-id and copy-id, we
5617 can bump the two ids to get the next of each, and store them all in a
5618 new current file. */
5619 len = strlen(max_node_id);
5620 svn_fs_fs__next_key(max_node_id, &len, next_node_id);
5621 len = strlen(max_copy_id);
5622 svn_fs_fs__next_key(max_copy_id, &len, next_copy_id);
5624 SVN_ERR(write_current(fs, max_rev, next_node_id, next_copy_id, pool));
5626 return SVN_NO_ERROR;
5629 /* This implements the fs_library_vtable_t.recover() API. */
5630 svn_error_t *
5631 svn_fs_fs__recover(svn_fs_t *fs,
5632 svn_cancel_func_t cancel_func, void *cancel_baton,
5633 apr_pool_t *pool)
5635 struct recover_baton b;
5637 /* We have no way to take out an exclusive lock in FSFS, so we're
5638 restricted as to the types of recovery we can do. Luckily,
5639 we just want to recreate the current file, and we can do that just
5640 by blocking other writers. */
5641 b.fs = fs;
5642 b.cancel_func = cancel_func;
5643 b.cancel_baton = cancel_baton;
5644 return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
5647 svn_error_t *
5648 svn_fs_fs__get_uuid(svn_fs_t *fs,
5649 const char **uuid_p,
5650 apr_pool_t *pool)
5652 fs_fs_data_t *ffd = fs->fsap_data;
5654 *uuid_p = apr_pstrdup(pool, ffd->uuid);
5655 return SVN_NO_ERROR;
5658 svn_error_t *
5659 svn_fs_fs__set_uuid(svn_fs_t *fs,
5660 const char *uuid,
5661 apr_pool_t *pool)
5663 apr_file_t *uuid_file;
5664 const char *tmp_path;
5665 const char *uuid_path = path_uuid(fs, pool);
5666 fs_fs_data_t *ffd = fs->fsap_data;
5668 SVN_ERR(svn_io_open_unique_file2(&uuid_file, &tmp_path, uuid_path,
5669 ".tmp", svn_io_file_del_none, pool));
5671 if (! uuid)
5672 uuid = svn_uuid_generate(pool);
5674 SVN_ERR(svn_io_file_write_full(uuid_file, uuid, strlen(uuid), NULL,
5675 pool));
5676 SVN_ERR(svn_io_file_write_full(uuid_file, "\n", 1, NULL, pool));
5678 SVN_ERR(svn_io_file_close(uuid_file, pool));
5680 /* We use the permissions of the 'current' file, because the 'uuid'
5681 file does not exist during repository creation. */
5682 SVN_ERR(svn_fs_fs__move_into_place(tmp_path, uuid_path,
5683 svn_fs_fs__path_current(fs, pool), pool));
5685 ffd->uuid = apr_pstrdup(fs->pool, uuid);
5687 return SVN_NO_ERROR;
5690 /** Node origin lazy cache. */
5692 /* If directory PATH does not exist, create it and give it the same
5693 permissions as FS->path.*/
5694 svn_error_t *
5695 svn_fs_fs__ensure_dir_exists(const char *path,
5696 svn_fs_t *fs,
5697 apr_pool_t *pool)
5699 svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
5700 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5702 svn_error_clear(err);
5703 return SVN_NO_ERROR;
5705 SVN_ERR(err);
5707 /* We successfully created a new directory. Dup the permissions
5708 from FS->path. */
5709 SVN_ERR(svn_fs_fs__dup_perms(path, fs->path, pool));
5711 return SVN_NO_ERROR;
5714 svn_error_t *
5715 svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
5716 svn_fs_t *fs,
5717 const char *node_id,
5718 apr_pool_t *pool)
5720 apr_file_t *fd;
5721 svn_stringbuf_t *origin_stringbuf;
5722 svn_error_t *err;
5724 *origin_id = NULL;
5725 err = svn_io_file_open(&fd, path_node_origin(fs, node_id, pool),
5726 APR_READ, APR_OS_DEFAULT, pool);
5727 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
5729 svn_error_clear(err);
5730 return SVN_NO_ERROR;
5732 SVN_ERR(err);
5734 SVN_ERR(svn_stringbuf_from_aprfile(&origin_stringbuf, fd, pool));
5736 *origin_id = svn_fs_fs__id_parse(origin_stringbuf->data,
5737 origin_stringbuf->len, pool);
5739 SVN_ERR(svn_io_file_close(fd, pool));
5741 return SVN_NO_ERROR;
5744 /* Helper for svn_fs_fs__set_node_origin[s]. Exactly like
5745 svn_fs_fs__set_node_origin, except that it throws an error if the
5746 file can't be written. */
5747 static svn_error_t *
5748 set_node_origin(svn_fs_t *fs,
5749 const char *node_id,
5750 const svn_fs_id_t *node_rev_id,
5751 apr_pool_t *pool)
5753 apr_file_t *file;
5754 svn_string_t *node_rev_id_string;
5756 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_join(fs->path,
5757 PATH_NODE_ORIGINS_DIR,
5758 pool),
5759 fs, pool));
5761 node_rev_id_string = svn_fs_fs__id_unparse(node_rev_id, pool);
5763 SVN_ERR(svn_io_file_open(&file, path_node_origin(fs, node_id, pool),
5764 APR_WRITE | APR_CREATE | APR_TRUNCATE
5765 | APR_BUFFERED, APR_OS_DEFAULT, pool));
5766 SVN_ERR(svn_io_file_write_full(file,
5767 node_rev_id_string->data,
5768 node_rev_id_string->len, NULL, pool));
5769 SVN_ERR(svn_io_file_close(file, pool));
5771 return SVN_NO_ERROR;
5775 svn_error_t *
5776 svn_fs_fs__set_node_origins(svn_fs_t *fs,
5777 apr_hash_t *node_origins,
5778 apr_pool_t *pool)
5780 apr_hash_index_t *hi;
5781 apr_pool_t *iterpool = svn_pool_create(pool);
5783 for (hi = apr_hash_first(pool, node_origins);
5784 hi != NULL;
5785 hi = apr_hash_next(hi))
5787 const void *key;
5788 void *val;
5789 const char *node_id;
5790 const svn_fs_id_t *node_rev_id;
5791 svn_error_t *err;
5793 svn_pool_clear(iterpool);
5795 apr_hash_this(hi, &key, NULL, &val);
5796 node_id = key;
5797 node_rev_id = val;
5799 err = set_node_origin(fs, node_id, node_rev_id, iterpool);
5800 if (err && APR_STATUS_IS_EACCES(err->apr_err))
5802 /* It's just a cache; stop trying if I can't write. */
5803 svn_error_clear(err);
5804 err = NULL;
5805 goto cleanup;
5807 SVN_ERR(err);
5810 cleanup:
5811 svn_pool_destroy(iterpool);
5812 return SVN_NO_ERROR;
5815 svn_error_t *
5816 svn_fs_fs__set_node_origin(svn_fs_t *fs,
5817 const char *node_id,
5818 const svn_fs_id_t *node_rev_id,
5819 apr_pool_t *pool)
5821 svn_error_t *err = set_node_origin(fs, node_id, node_rev_id, pool);
5822 if (err && APR_STATUS_IS_EACCES(err->apr_err))
5824 /* It's just a cache; stop trying if I can't write. */
5825 svn_error_clear(err);
5826 err = NULL;
5828 return err;
5832 svn_error_t *
5833 svn_fs_fs__list_transactions(apr_array_header_t **names_p,
5834 svn_fs_t *fs,
5835 apr_pool_t *pool)
5837 const char *txn_dir;
5838 apr_hash_t *dirents;
5839 apr_hash_index_t *hi;
5840 apr_array_header_t *names;
5841 apr_size_t ext_len = strlen(PATH_EXT_TXN);
5843 names = apr_array_make(pool, 1, sizeof(const char *));
5845 /* Get the transactions directory. */
5846 txn_dir = svn_path_join(fs->path, PATH_TXNS_DIR, pool);
5848 /* Now find a listing of this directory. */
5849 SVN_ERR(svn_io_get_dirents2(&dirents, txn_dir, pool));
5851 /* Loop through all the entries and return anything that ends with '.txn'. */
5852 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
5854 const void *key;
5855 const char *name, *id;
5856 apr_ssize_t klen;
5858 apr_hash_this(hi, &key, &klen, NULL);
5859 name = key;
5861 /* The name must end with ".txn" to be considered a transaction. */
5862 if ((apr_size_t) klen <= ext_len
5863 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
5864 continue;
5866 /* Truncate the ".txn" extension and store the ID. */
5867 id = apr_pstrndup(pool, name, strlen(name) - ext_len);
5868 APR_ARRAY_PUSH(names, const char *) = id;
5871 *names_p = names;
5873 return SVN_NO_ERROR;
5876 svn_error_t *
5877 svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
5878 svn_fs_t *fs,
5879 const char *name,
5880 apr_pool_t *pool)
5882 svn_fs_txn_t *txn;
5883 svn_node_kind_t kind;
5884 transaction_t *local_txn;
5886 /* First check to see if the directory exists. */
5887 SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
5889 /* Did we find it? */
5890 if (kind != svn_node_dir)
5891 return svn_error_create(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
5892 _("No such transaction"));
5894 txn = apr_pcalloc(pool, sizeof(*txn));
5896 /* Read in the root node of this transaction. */
5897 txn->id = apr_pstrdup(pool, name);
5898 txn->fs = fs;
5900 SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
5902 txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
5904 txn->vtable = &txn_vtable;
5905 *txn_p = txn;
5907 return SVN_NO_ERROR;
5910 svn_error_t *
5911 svn_fs_fs__txn_proplist(apr_hash_t **table_p,
5912 svn_fs_txn_t *txn,
5913 apr_pool_t *pool)
5915 apr_hash_t *proplist = apr_hash_make(pool);
5916 SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
5917 *table_p = proplist;
5919 return SVN_NO_ERROR;
5922 svn_error_t *
5923 svn_fs_fs__delete_node_revision(svn_fs_t *fs,
5924 const svn_fs_id_t *id,
5925 apr_pool_t *pool)
5927 node_revision_t *noderev;
5929 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
5931 /* Delete any mutable property representation. */
5932 if (noderev->prop_rep && noderev->prop_rep->txn_id)
5933 SVN_ERR(svn_io_remove_file(path_txn_node_props(fs, id, pool), pool));
5935 /* Delete any mutable data representation. */
5936 if (noderev->data_rep && noderev->data_rep->txn_id
5937 && noderev->kind == svn_node_dir)
5938 SVN_ERR(svn_io_remove_file(path_txn_node_children(fs, id, pool), pool));
5940 return svn_io_remove_file(path_txn_node_rev(fs, id, pool), pool);
5945 /*** Revisions ***/
5947 svn_error_t *
5948 svn_fs_fs__revision_prop(svn_string_t **value_p,
5949 svn_fs_t *fs,
5950 svn_revnum_t rev,
5951 const char *propname,
5952 apr_pool_t *pool)
5954 apr_hash_t *table;
5956 SVN_ERR(svn_fs__check_fs(fs, TRUE));
5957 SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
5959 *value_p = apr_hash_get(table, propname, APR_HASH_KEY_STRING);
5961 return SVN_NO_ERROR;
5965 /* Baton used for change_rev_prop_body below. */
5966 struct change_rev_prop_baton {
5967 svn_fs_t *fs;
5968 svn_revnum_t rev;
5969 const char *name;
5970 const svn_string_t *value;
5973 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
5974 write lock. This implements the svn_fs_fs__with_write_lock()
5975 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
5977 static svn_error_t *
5978 change_rev_prop_body(void *baton, apr_pool_t *pool)
5980 struct change_rev_prop_baton *cb = baton;
5981 apr_hash_t *table;
5983 SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
5985 apr_hash_set(table, cb->name, APR_HASH_KEY_STRING, cb->value);
5987 SVN_ERR(svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool));
5989 return SVN_NO_ERROR;
5992 svn_error_t *
5993 svn_fs_fs__change_rev_prop(svn_fs_t *fs,
5994 svn_revnum_t rev,
5995 const char *name,
5996 const svn_string_t *value,
5997 apr_pool_t *pool)
5999 struct change_rev_prop_baton cb;
6001 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6003 cb.fs = fs;
6004 cb.rev = rev;
6005 cb.name = name;
6006 cb.value = value;
6008 return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
6013 /*** Transactions ***/
6015 svn_error_t *
6016 svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
6017 const svn_fs_id_t **base_root_id_p,
6018 svn_fs_t *fs,
6019 const char *txn_name,
6020 apr_pool_t *pool)
6022 transaction_t *txn;
6023 SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
6024 *root_id_p = txn->root_id;
6025 *base_root_id_p = txn->base_id;
6026 return SVN_NO_ERROR;
6030 /* Generic transaction operations. */
6032 svn_error_t *
6033 svn_fs_fs__txn_prop(svn_string_t **value_p,
6034 svn_fs_txn_t *txn,
6035 const char *propname,
6036 apr_pool_t *pool)
6038 apr_hash_t *table;
6039 svn_fs_t *fs = txn->fs;
6041 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6042 SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
6044 *value_p = apr_hash_get(table, propname, APR_HASH_KEY_STRING);
6046 return SVN_NO_ERROR;
6049 svn_error_t *
6050 svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
6051 svn_fs_t *fs,
6052 svn_revnum_t rev,
6053 apr_uint32_t flags,
6054 apr_pool_t *pool)
6056 svn_string_t date;
6057 svn_prop_t prop;
6058 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
6060 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6062 SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
6064 /* Put a datestamp on the newly created txn, so we always know
6065 exactly how old it is. (This will help sysadmins identify
6066 long-abandoned txns that may need to be manually removed.) When
6067 a txn is promoted to a revision, this property will be
6068 automatically overwritten with a revision datestamp. */
6069 date.data = svn_time_to_cstring(apr_time_now(), pool);
6070 date.len = strlen(date.data);
6072 prop.name = SVN_PROP_REVISION_DATE;
6073 prop.value = &date;
6074 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6076 /* Set temporary txn props that represent the requested 'flags'
6077 behaviors. */
6078 if (flags & SVN_FS_TXN_CHECK_OOD)
6080 prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
6081 prop.value = svn_string_create("true", pool);
6082 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6085 if (flags & SVN_FS_TXN_CHECK_LOCKS)
6087 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
6088 prop.value = svn_string_create("true", pool);
6089 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6092 return svn_fs_fs__change_txn_props(*txn_p, props, pool);