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 * ====================================================================
25 #include <apr_general.h>
26 #include <apr_pools.h>
27 #include <apr_file_io.h>
31 #include <apr_thread_mutex.h>
33 #include "svn_pools.h"
38 #include "svn_props.h"
39 #include "svn_sorts.h"
41 #include "svn_mergeinfo.h"
51 #include "private/svn_fs_sqlite.h"
52 #include "private/svn_fs_mergeinfo.h"
53 #include "private/svn_fs_node_origins.h"
54 #include "private/svn_fs_util.h"
55 #include "../libsvn_fs/fs-loader.h"
57 #include "svn_private_config.h"
59 /* An arbitrary maximum path length, so clients can't run us out of memory
60 * by giving us arbitrarily large paths. */
61 #define FSFS_MAX_PATH_LEN 4096
63 /* The default maximum number of files per directory to store in the
64 rev and revprops directory. The number below is somewhat arbitrary,
65 and can be overriden by defining the macro while compiling; the
66 figure of 1000 is reasonable for VFAT filesystems, which are by far
67 the worst performers in this area. */
68 #ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
69 #define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
72 /* Following are defines that specify the textual elements of the
73 native filesystem directories and revision files. */
75 /* Headers used to describe node-revision in the revision file. */
76 #define HEADER_ID "id"
77 #define HEADER_TYPE "type"
78 #define HEADER_COUNT "count"
79 #define HEADER_PROPS "props"
80 #define HEADER_TEXT "text"
81 #define HEADER_CPATH "cpath"
82 #define HEADER_PRED "pred"
83 #define HEADER_COPYFROM "copyfrom"
84 #define HEADER_COPYROOT "copyroot"
85 #define HEADER_FRESHTXNRT "is-fresh-txn-root"
87 /* Kinds that a change can be. */
88 #define ACTION_MODIFY "modify"
89 #define ACTION_ADD "add"
90 #define ACTION_DELETE "delete"
91 #define ACTION_REPLACE "replace"
92 #define ACTION_RESET "reset"
94 /* True and False flags. */
95 #define FLAG_TRUE "true"
96 #define FLAG_FALSE "false"
98 /* Kinds that a node-rev can be. */
99 #define KIND_FILE "file"
100 #define KIND_DIR "dir"
102 /* Kinds of representation. */
103 #define REP_PLAIN "PLAIN"
104 #define REP_DELTA "DELTA"
108 To avoid opening and closing the rev-files all the time, it would
109 probably be advantageous to keep each rev-file open for the
110 lifetime of the transaction object. I'll leave that as a later
111 optimization for now.
113 I didn't keep track of pool lifetimes at all in this code. There
114 are likely some errors because of that.
118 /* The vtable associated with an open transaction object. */
119 static txn_vtable_t txn_vtable
= {
120 svn_fs_fs__commit_txn
,
121 svn_fs_fs__abort_txn
,
123 svn_fs_fs__txn_proplist
,
124 svn_fs_fs__change_txn_prop
,
126 svn_fs_fs__change_txn_props
129 /* Pathname helper functions */
132 path_format(svn_fs_t
*fs
, apr_pool_t
*pool
)
134 return svn_path_join(fs
->path
, PATH_FORMAT
, pool
);
137 static APR_INLINE
const char *
138 path_uuid(svn_fs_t
*fs
, apr_pool_t
*pool
)
140 return svn_path_join(fs
->path
, PATH_UUID
, pool
);
144 svn_fs_fs__path_current(svn_fs_t
*fs
, apr_pool_t
*pool
)
146 return svn_path_join(fs
->path
, PATH_CURRENT
, pool
);
149 static APR_INLINE
const char *
150 path_txn_current(svn_fs_t
*fs
, apr_pool_t
*pool
)
152 return svn_path_join(fs
->path
, PATH_TXN_CURRENT
, pool
);
155 static APR_INLINE
const char *
156 path_txn_current_lock(svn_fs_t
*fs
, apr_pool_t
*pool
)
158 return svn_path_join(fs
->path
, PATH_TXN_CURRENT_LOCK
, pool
);
161 static APR_INLINE
const char *
162 path_lock(svn_fs_t
*fs
, apr_pool_t
*pool
)
164 return svn_path_join(fs
->path
, PATH_LOCK_FILE
, pool
);
168 path_rev_shard(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
170 fs_fs_data_t
*ffd
= fs
->fsap_data
;
172 assert(ffd
->max_files_per_dir
);
173 return svn_path_join_many(pool
, fs
->path
, PATH_REVS_DIR
,
174 apr_psprintf(pool
, "%ld",
175 rev
/ ffd
->max_files_per_dir
),
180 svn_fs_fs__path_rev(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
182 fs_fs_data_t
*ffd
= fs
->fsap_data
;
184 if (ffd
->max_files_per_dir
)
186 return svn_path_join(path_rev_shard(fs
, rev
, pool
),
187 apr_psprintf(pool
, "%ld", rev
),
191 return svn_path_join_many(pool
, fs
->path
, PATH_REVS_DIR
,
192 apr_psprintf(pool
, "%ld", rev
), NULL
);
196 path_revprops_shard(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
198 fs_fs_data_t
*ffd
= fs
->fsap_data
;
200 assert(ffd
->max_files_per_dir
);
201 return svn_path_join_many(pool
, fs
->path
, PATH_REVPROPS_DIR
,
202 apr_psprintf(pool
, "%ld",
203 rev
/ ffd
->max_files_per_dir
),
208 path_revprops(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
210 fs_fs_data_t
*ffd
= fs
->fsap_data
;
212 if (ffd
->max_files_per_dir
)
214 return svn_path_join(path_revprops_shard(fs
, rev
, pool
),
215 apr_psprintf(pool
, "%ld", rev
),
219 return svn_path_join_many(pool
, fs
->path
, PATH_REVPROPS_DIR
,
220 apr_psprintf(pool
, "%ld", rev
), NULL
);
223 static APR_INLINE
const char *
224 path_txn_dir(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
226 return svn_path_join_many(pool
, fs
->path
, PATH_TXNS_DIR
,
227 apr_pstrcat(pool
, txn_id
, PATH_EXT_TXN
, NULL
),
231 static APR_INLINE
const char *
232 path_txn_changes(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
234 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_CHANGES
, pool
);
237 static APR_INLINE
const char *
238 path_txn_props(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
240 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_TXN_PROPS
, pool
);
243 static APR_INLINE
const char *
244 path_txn_mergeinfo(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
246 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_TXN_MERGEINFO
,
250 static APR_INLINE
const char *
251 path_txn_next_ids(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
253 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_NEXT_IDS
, pool
);
256 static APR_INLINE
const char *
257 path_txn_proto_rev(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
259 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_REV
, pool
);
262 static APR_INLINE
const char *
263 path_txn_proto_rev_lock(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
265 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_REV_LOCK
, pool
);
269 path_txn_node_rev(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
271 const char *txn_id
= svn_fs_fs__id_txn_id(id
);
272 const char *node_id
= svn_fs_fs__id_node_id(id
);
273 const char *copy_id
= svn_fs_fs__id_copy_id(id
);
274 const char *name
= apr_psprintf(pool
, PATH_PREFIX_NODE
"%s.%s",
277 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), name
, pool
);
280 static APR_INLINE
const char *
281 path_txn_node_props(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
283 return apr_pstrcat(pool
, path_txn_node_rev(fs
, id
, pool
), PATH_EXT_PROPS
,
287 static APR_INLINE
const char *
288 path_txn_node_children(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
290 return apr_pstrcat(pool
, path_txn_node_rev(fs
, id
, pool
),
291 PATH_EXT_CHILDREN
, NULL
);
296 /* Functions for working with shared transaction data. */
298 /* Return the transaction object for transaction TXN_ID from the
299 transaction list of filesystem FS (which must already be locked via the
300 txn_list_lock mutex). If the transaction does not exist in the list,
301 then create a new transaction object and return it (if CREATE_NEW is
302 true) or return NULL (otherwise). */
303 static fs_fs_shared_txn_data_t
*
304 get_shared_txn(svn_fs_t
*fs
, const char *txn_id
, svn_boolean_t create_new
)
306 fs_fs_data_t
*ffd
= fs
->fsap_data
;
307 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
308 fs_fs_shared_txn_data_t
*txn
;
310 for (txn
= ffsd
->txns
; txn
; txn
= txn
->next
)
311 if (strcmp(txn
->txn_id
, txn_id
) == 0)
314 if (txn
|| !create_new
)
317 /* Use the transaction object from the (single-object) freelist,
318 if one is available, or otherwise create a new object. */
321 txn
= ffsd
->free_txn
;
322 ffsd
->free_txn
= NULL
;
326 apr_pool_t
*subpool
= svn_pool_create(ffsd
->common_pool
);
327 txn
= apr_palloc(subpool
, sizeof(*txn
));
331 assert(strlen(txn_id
) < sizeof(txn
->txn_id
));
332 strcpy(txn
->txn_id
, txn_id
);
333 txn
->being_written
= FALSE
;
335 /* Link this transaction into the head of the list. We will typically
336 be dealing with only one active transaction at a time, so it makes
337 sense for searches through the transaction list to look at the
338 newest transactions first. */
339 txn
->next
= ffsd
->txns
;
345 /* Free the transaction object for transaction TXN_ID, and remove it
346 from the transaction list of filesystem FS (which must already be
347 locked via the txn_list_lock mutex). Do nothing if the transaction
350 free_shared_txn(svn_fs_t
*fs
, const char *txn_id
)
352 fs_fs_data_t
*ffd
= fs
->fsap_data
;
353 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
354 fs_fs_shared_txn_data_t
*txn
, *prev
= NULL
;
356 for (txn
= ffsd
->txns
; txn
; prev
= txn
, txn
= txn
->next
)
357 if (strcmp(txn
->txn_id
, txn_id
) == 0)
364 prev
->next
= txn
->next
;
366 ffsd
->txns
= txn
->next
;
368 /* As we typically will be dealing with one transaction after another,
369 we will maintain a single-object free list so that we can hopefully
370 keep reusing the same transaction object. */
372 ffsd
->free_txn
= txn
;
374 svn_pool_destroy(txn
->pool
);
378 /* Obtain a lock on the transaction list of filesystem FS, call BODY
379 with FS, BATON, and POOL, and then unlock the transaction list.
380 Return what BODY returned. */
382 with_txnlist_lock(svn_fs_t
*fs
,
383 svn_error_t
*(*body
)(svn_fs_t
*fs
,
391 fs_fs_data_t
*ffd
= fs
->fsap_data
;
392 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
393 apr_status_t apr_err
;
395 apr_err
= apr_thread_mutex_lock(ffsd
->txn_list_lock
);
397 return svn_error_wrap_apr(apr_err
, _("Can't grab FSFS txn list mutex"));
400 err
= body(fs
, baton
, pool
);
403 apr_err
= apr_thread_mutex_unlock(ffsd
->txn_list_lock
);
405 return svn_error_wrap_apr(apr_err
, _("Can't ungrab FSFS txn list mutex"));
412 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
414 get_lock_on_filesystem(const char *lock_filename
,
417 svn_error_t
*err
= svn_io_file_lock2(lock_filename
, TRUE
, FALSE
, pool
);
419 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
421 /* No lock file? No big deal; these are just empty files
422 anyway. Create it and try again. */
423 svn_error_clear(err
);
426 SVN_ERR(svn_io_file_create(lock_filename
, "", pool
));
427 SVN_ERR(svn_io_file_lock2(lock_filename
, TRUE
, FALSE
, pool
));
433 /* Obtain a write lock on the file LOCK_FILENAME (protecting with
434 LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
435 BATON and that subpool, destroy the subpool (releasing the write
436 lock) and return what BODY returned. */
438 with_some_lock(svn_error_t
*(*body
)(void *baton
,
441 const char *lock_filename
,
443 apr_thread_mutex_t
*lock_mutex
,
447 apr_pool_t
*subpool
= svn_pool_create(pool
);
453 /* POSIX fcntl locks are per-process, so we need to serialize locks
454 within the process. */
455 status
= apr_thread_mutex_lock(lock_mutex
);
457 return svn_error_wrap_apr(status
,
458 _("Can't grab FSFS mutex for '%s'"),
462 err
= get_lock_on_filesystem(lock_filename
, subpool
);
465 err
= body(baton
, subpool
);
467 svn_pool_destroy(subpool
);
470 status
= apr_thread_mutex_unlock(lock_mutex
);
472 return svn_error_wrap_apr(status
,
473 _("Can't ungrab FSFS mutex for '%s'"),
481 svn_fs_fs__with_write_lock(svn_fs_t
*fs
,
482 svn_error_t
*(*body
)(void *baton
,
488 fs_fs_data_t
*ffd
= fs
->fsap_data
;
489 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
490 apr_thread_mutex_t
*mutex
= ffsd
->fs_write_lock
;
493 return with_some_lock(body
, baton
,
501 /* Run BODY (with BATON and POOL) while the transaction-current file
504 with_txn_current_lock(svn_fs_t
*fs
,
505 svn_error_t
*(*body
)(void *baton
,
511 fs_fs_data_t
*ffd
= fs
->fsap_data
;
512 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
513 apr_thread_mutex_t
*mutex
= ffsd
->txn_current_lock
;
516 return with_some_lock(body
, baton
,
517 path_txn_current_lock(fs
, pool
),
524 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
526 struct unlock_proto_rev_baton
532 /* Callback used in the implementation of unlock_proto_rev(). */
534 unlock_proto_rev_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
536 const struct unlock_proto_rev_baton
*b
= baton
;
537 const char *txn_id
= b
->txn_id
;
538 apr_file_t
*lockfile
= b
->lockcookie
;
539 fs_fs_shared_txn_data_t
*txn
= get_shared_txn(fs
, txn_id
, FALSE
);
540 apr_status_t apr_err
;
543 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
544 _("Can't unlock unknown transaction '%s'"),
546 if (!txn
->being_written
)
547 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
548 _("Can't unlock nonlocked transaction '%s'"),
551 apr_err
= apr_file_unlock(lockfile
);
553 return svn_error_wrap_apr
555 _("Can't unlock prototype revision lockfile for transaction '%s'"),
557 apr_err
= apr_file_close(lockfile
);
559 return svn_error_wrap_apr
561 _("Can't close prototype revision lockfile for transaction '%s'"),
564 txn
->being_written
= FALSE
;
569 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
570 FS using cookie LOCKCOOKIE. The original prototype revision file must
571 have been closed _before_ calling this function.
573 Perform temporary allocations in POOL. */
575 unlock_proto_rev(svn_fs_t
*fs
, const char *txn_id
, void *lockcookie
,
578 struct unlock_proto_rev_baton b
;
581 b
.lockcookie
= lockcookie
;
582 return with_txnlist_lock(fs
, unlock_proto_rev_body
, &b
, pool
);
585 /* Same as unlock_proto_rev(), but requires that the transaction list
586 lock is already held. */
588 unlock_proto_rev_list_locked(svn_fs_t
*fs
, const char *txn_id
,
592 struct unlock_proto_rev_baton b
;
595 b
.lockcookie
= lockcookie
;
596 return unlock_proto_rev_body(fs
, &b
, pool
);
599 /* A structure used by get_writable_proto_rev() and
600 get_writable_proto_rev_body(), which see. */
601 struct get_writable_proto_rev_baton
608 /* Callback used in the implementation of get_writable_proto_rev(). */
610 get_writable_proto_rev_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
612 const struct get_writable_proto_rev_baton
*b
= baton
;
613 apr_file_t
**file
= b
->file
;
614 void **lockcookie
= b
->lockcookie
;
615 const char *txn_id
= b
->txn_id
;
617 fs_fs_shared_txn_data_t
*txn
= get_shared_txn(fs
, txn_id
, TRUE
);
619 /* First, ensure that no thread in this process (including this one)
620 is currently writing to this transaction's proto-rev file. */
621 if (txn
->being_written
)
622 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN
, NULL
,
623 _("Cannot write to the prototype revision file "
624 "of transaction '%s' because a previous "
625 "representation is currently being written by "
630 /* We know that no thread in this process is writing to the proto-rev
631 file, and by extension, that no thread in this process is holding a
632 lock on the prototype revision lock file. It is therefore safe
633 for us to attempt to lock this file, to see if any other process
634 is holding a lock. */
637 apr_file_t
*lockfile
;
638 apr_status_t apr_err
;
639 const char *lockfile_path
= path_txn_proto_rev_lock(fs
, txn_id
, pool
);
641 /* Open the proto-rev lockfile, creating it if necessary, as it may
642 not exist if the transaction dates from before the lockfiles were
645 ### We'd also like to use something like svn_io_file_lock2(), but
646 that forces us to create a subpool just to be able to unlock
647 the file, which seems a waste. */
648 SVN_ERR(svn_io_file_open(&lockfile
, lockfile_path
,
649 APR_WRITE
| APR_CREATE
, APR_OS_DEFAULT
, pool
));
651 apr_err
= apr_file_lock(lockfile
,
652 APR_FLOCK_EXCLUSIVE
| APR_FLOCK_NONBLOCK
);
655 svn_error_clear(svn_io_file_close(lockfile
, pool
));
657 if (APR_STATUS_IS_EAGAIN(apr_err
))
658 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN
, NULL
,
659 _("Cannot write to the prototype revision "
660 "file of transaction '%s' because a "
661 "previous representation is currently "
662 "being written by another process"),
665 return svn_error_wrap_apr(apr_err
,
666 _("Can't get exclusive lock on file '%s'"),
667 svn_path_local_style(lockfile_path
, pool
));
670 *lockcookie
= lockfile
;
673 /* We've successfully locked the transaction; mark it as such. */
674 txn
->being_written
= TRUE
;
677 /* Now open the prototype revision file and seek to the end. */
678 err
= svn_io_file_open(file
, path_txn_proto_rev(fs
, txn_id
, pool
),
679 APR_WRITE
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
681 /* You might expect that we could dispense with the following seek
682 and achieve the same thing by opening the file using APR_APPEND.
683 Unfortunately, APR's buffered file implementation unconditionally
684 places its initial file pointer at the start of the file (even for
685 files opened with APR_APPEND), so we need this seek to reconcile
686 the APR file pointer to the OS file pointer (since we need to be
687 able to read the current file position later). */
690 apr_off_t offset
= 0;
691 err
= svn_io_file_seek(*file
, APR_END
, &offset
, 0);
696 svn_error_clear(unlock_proto_rev_list_locked(fs
, txn_id
, *lockcookie
,
704 /* Get a handle to the prototype revision file for transaction TXN_ID in
705 filesystem FS, and lock it for writing. Return FILE, a file handle
706 positioned at the end of the file, and LOCKCOOKIE, a cookie that
707 should be passed to unlock_proto_rev() to unlock the file once FILE
710 If the prototype revision file is already locked, return error
711 SVN_ERR_FS_REP_BEING_WRITTEN.
713 Perform all allocations in POOL. */
715 get_writable_proto_rev(apr_file_t
**file
,
717 svn_fs_t
*fs
, const char *txn_id
,
720 struct get_writable_proto_rev_baton b
;
723 b
.lockcookie
= lockcookie
;
726 return with_txnlist_lock(fs
, get_writable_proto_rev_body
, &b
, pool
);
729 /* Callback used in the implementation of purge_shared_txn(). */
731 purge_shared_txn_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
733 const char *txn_id
= *(const char **)baton
;
735 free_shared_txn(fs
, txn_id
);
739 /* Purge the shared data for transaction TXN_ID in filesystem FS.
740 Perform all allocations in POOL. */
742 purge_shared_txn(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
744 return with_txnlist_lock(fs
, purge_shared_txn_body
, (char **) &txn_id
, pool
);
749 /* Fetch the current offset of FILE into *OFFSET_P. */
751 get_file_offset(apr_off_t
*offset_p
, apr_file_t
*file
, apr_pool_t
*pool
)
755 /* Note that, for buffered files, one (possibly surprising) side-effect
756 of this call is to flush any unwritten data to disk. */
758 SVN_ERR(svn_io_file_seek(file
, APR_CUR
, &offset
, pool
));
765 /* Check that BUF, a buffer of text from format file PATH, contains
766 only digits, raising error SVN_ERR_BAD_VERSION_FILE_FORMAT if not.
768 Uses POOL for temporary allocation. */
770 check_format_file_buffer_numeric(const char *buf
, const char *path
,
775 for (p
= buf
; *p
; p
++)
776 if (!apr_isdigit(*p
))
777 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
778 _("Format file '%s' contains an unexpected non-digit"),
779 svn_path_local_style(path
, pool
));
784 /* Read the format number and maximum number of files per directory
785 from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
788 *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
789 will be set to zero if a linear scheme should be used.
791 Use POOL for temporary allocation. */
793 read_format(int *pformat
, int *max_files_per_dir
,
794 const char *path
, apr_pool_t
*pool
)
801 err
= svn_io_file_open(&file
, path
, APR_READ
| APR_BUFFERED
,
802 APR_OS_DEFAULT
, pool
);
803 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
805 /* Treat an absent format file as format 1. Do not try to
806 create the format file on the fly, because the repository
807 might be read-only for us, or this might be a read-only
808 operation, and the spirit of FSFS is to make no changes
809 whatseover in read-only operations. See thread starting at
810 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
812 svn_error_clear(err
);
814 *max_files_per_dir
= 0;
820 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
821 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
823 /* Return a more useful error message. */
824 svn_error_clear(err
);
825 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
826 _("Can't read first line of format file '%s'"),
827 svn_path_local_style(path
, pool
));
831 /* Check that the first line contains only digits. */
832 SVN_ERR(check_format_file_buffer_numeric(buf
, path
, pool
));
833 *pformat
= atoi(buf
);
835 /* Set the default values for anything that can be set via an option. */
836 *max_files_per_dir
= 0;
838 /* Read any options. */
842 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
843 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
845 /* No more options; that's okay. */
846 svn_error_clear(err
);
851 if (*pformat
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
&&
852 strncmp(buf
, "layout ", 7) == 0)
854 if (strcmp(buf
+7, "linear") == 0)
856 *max_files_per_dir
= 0;
860 if (strncmp(buf
+7, "sharded ", 8) == 0)
862 /* Check that the argument is numeric. */
863 SVN_ERR(check_format_file_buffer_numeric(buf
+15, path
, pool
));
864 *max_files_per_dir
= atoi(buf
+15);
869 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
870 _("'%s' contains invalid filesystem format option '%s'"),
871 svn_path_local_style(path
, pool
), buf
);
874 SVN_ERR(svn_io_file_close(file
, pool
));
879 /* Write the format number and maximum number of files per directory
880 to a new format file in PATH.
882 Use POOL for temporary allocation. */
884 write_format(const char *path
, int format
, int max_files_per_dir
,
887 /* svn_io_write_version_file() does a load of magic to allow it to
888 replace version files that already exist. Luckily, we never need to
890 const char *contents
;
892 assert (1 <= format
&& format
<= SVN_FS_FS__FORMAT_NUMBER
);
893 if (format
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
)
895 if (max_files_per_dir
)
896 contents
= apr_psprintf(pool
,
898 "layout sharded %d\n",
899 format
, max_files_per_dir
);
901 contents
= apr_psprintf(pool
,
908 contents
= apr_psprintf(pool
, "%d\n", format
);
911 /* Create the file */
912 SVN_ERR(svn_io_file_create(path
, contents
, pool
));
913 /* And set the perms to make it read only */
914 SVN_ERR(svn_io_set_file_read_only(path
, FALSE
, pool
));
920 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
921 number is not the same as a format number supported by this
924 check_format(int format
)
926 /* We support all formats from 1-current simultaneously */
927 if (1 <= format
&& format
<= SVN_FS_FS__FORMAT_NUMBER
)
930 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT
, NULL
,
931 _("Expected FS format between '1' and '%d'; found format '%d'"),
932 SVN_FS_FS__FORMAT_NUMBER
, format
);
936 svn_fs_fs__open(svn_fs_t
*fs
, const char *path
, apr_pool_t
*pool
)
938 fs_fs_data_t
*ffd
= fs
->fsap_data
;
939 apr_file_t
*uuid_file
;
940 int format
, max_files_per_dir
;
941 char buf
[APR_UUID_FORMATTED_LENGTH
+ 2];
944 fs
->path
= apr_pstrdup(fs
->pool
, path
);
946 /* Read the FS format number. */
947 SVN_ERR(read_format(&format
, &max_files_per_dir
,
948 path_format(fs
, pool
), pool
));
950 /* Now we've got a format number no matter what. */
951 ffd
->format
= format
;
952 ffd
->max_files_per_dir
= max_files_per_dir
;
953 SVN_ERR(check_format(format
));
955 /* Read in and cache the repository uuid. */
956 SVN_ERR(svn_io_file_open(&uuid_file
, path_uuid(fs
, pool
),
957 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
960 SVN_ERR(svn_io_read_length_line(uuid_file
, buf
, &limit
, pool
));
961 ffd
->uuid
= apr_pstrdup(fs
->pool
, buf
);
963 SVN_ERR(svn_io_file_close(uuid_file
, pool
));
968 /* SVN_ERR-like macros for dealing with ESTALE
970 * In NFS v3 and under, the server doesn't track opened files. If you
971 * unlink(2) or rename(2) a file held open by another process *on the
972 * same host*, that host's kernel typically renames the file to
973 * .nfsXXXX and automatically deletes that when it's no longer open,
974 * but this behavior is not required.
976 * For obvious reasons, this does not work *across hosts*. No one
977 * knows about the opened file; not the server, and not the deleting
978 * client. So the file vanishes, and the reader gets stale NFS file
979 * handle. We have this problem with revprops files, current, and
980 * transaction-current.
982 * Wrap opens and reads of such files with SVN_RETRY_ESTALE and closes
983 * with SVN_IGNORE_ESTALE. Call these macros within a loop of
984 * SVN_ESTALE_RETRY_COUNT iterations (though, realistically, the
985 * second try will succeed). Make sure you put a break statement
986 * after the close, at the end of your loop. Immediately after your
987 * loop, return err if err.
989 * You must initialize err to SVN_NO_ERROR, as these macros do not.
992 #define SVN_ESTALE_RETRY_COUNT 10
995 #define SVN_RETRY_ESTALE(err, expr) \
997 /* Clear err here (svn_error_clear can safely be passed
998 * SVN_NO_ERROR) rather than after finding ESTALE so we can return
999 * the ESTALE error on the last iteration of the loop. */ \
1000 svn_error_clear(err); \
1004 if (APR_TO_OS_ERROR(err->apr_err) == ESTALE) \
1009 #define SVN_IGNORE_ESTALE(err, expr) \
1011 svn_error_clear(err); \
1015 if (APR_TO_OS_ERROR(err->apr_err) != ESTALE) \
1020 #define SVN_RETRY_ESTALE(err, expr) SVN_ERR(expr)
1021 #define SVN_IGNORE_ESTALE(err, expr) SVN_ERR(expr)
1024 /* Long enough to hold: "<svn_revnum_t> <node id> <copy id>\0"
1025 * 19 bytes for svn_revnum_t (room for 32 or 64 bit values)
1027 * + 26 bytes for each id (these are actually unbounded, so we just
1028 * have to pick something; 2^64 is 13 bytes in base-36)
1029 * + 1 terminating null
1031 #define CURRENT_BUF_LEN 48
1033 /* Read the 'current' file FNAME and store the contents in *BUF.
1034 Allocations are performed in POOL. */
1035 static svn_error_t
*
1036 read_current(const char *fname
, char **buf
, apr_pool_t
*pool
)
1038 apr_file_t
*revision_file
;
1041 svn_error_t
*err
= SVN_NO_ERROR
;
1042 apr_pool_t
*iterpool
;
1044 *buf
= apr_palloc(pool
, CURRENT_BUF_LEN
);
1045 iterpool
= svn_pool_create(pool
);
1046 for (i
= 0; i
< SVN_ESTALE_RETRY_COUNT
; i
++)
1048 svn_pool_clear(iterpool
);
1050 SVN_RETRY_ESTALE(err
, svn_io_file_open(&revision_file
, fname
,
1051 APR_READ
| APR_BUFFERED
,
1052 APR_OS_DEFAULT
, iterpool
));
1054 len
= CURRENT_BUF_LEN
;
1055 SVN_RETRY_ESTALE(err
, svn_io_read_length_line(revision_file
,
1056 *buf
, &len
, iterpool
));
1057 SVN_IGNORE_ESTALE(err
, svn_io_file_close(revision_file
, iterpool
));
1061 svn_pool_destroy(iterpool
);
1066 /* Find the youngest revision in a repository at path FS_PATH and
1067 return it in *YOUNGEST_P. Perform temporary allocations in
1069 static svn_error_t
*
1070 get_youngest(svn_revnum_t
*youngest_p
,
1071 const char *fs_path
,
1076 SVN_ERR(read_current(svn_path_join(fs_path
, PATH_CURRENT
, pool
),
1079 *youngest_p
= SVN_STR_TO_REV(buf
);
1081 return SVN_NO_ERROR
;
1085 svn_fs_fs__hotcopy(const char *src_path
,
1086 const char *dst_path
,
1089 const char *src_subdir
, *dst_subdir
;
1090 svn_revnum_t youngest
, rev
;
1091 apr_pool_t
*iterpool
;
1092 svn_node_kind_t kind
;
1093 int format
, max_files_per_dir
;
1095 /* Check format to be sure we know how to hotcopy this FS. */
1096 SVN_ERR(read_format(&format
, &max_files_per_dir
,
1097 svn_path_join(src_path
, PATH_FORMAT
, pool
),
1099 SVN_ERR(check_format(format
));
1101 /* Copy the current file. */
1102 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_CURRENT
, pool
));
1104 /* Copy the uuid. */
1105 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_UUID
, pool
));
1107 /* Copy the merge tracking info. */
1108 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, SVN_FS__SQLITE_DB_NAME
,
1111 /* Find the youngest revision from this current file. */
1112 SVN_ERR(get_youngest(&youngest
, dst_path
, pool
));
1114 /* Copy the necessary rev files. */
1115 src_subdir
= svn_path_join(src_path
, PATH_REVS_DIR
, pool
);
1116 dst_subdir
= svn_path_join(dst_path
, PATH_REVS_DIR
, pool
);
1118 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1120 iterpool
= svn_pool_create(pool
);
1121 for (rev
= 0; rev
<= youngest
; rev
++)
1123 const char *src_subdir_shard
= src_subdir
,
1124 *dst_subdir_shard
= dst_subdir
;
1126 if (max_files_per_dir
)
1128 const char *shard
= apr_psprintf(iterpool
, "%ld",
1129 rev
/ max_files_per_dir
);
1130 src_subdir_shard
= svn_path_join(src_subdir
, shard
, iterpool
);
1131 dst_subdir_shard
= svn_path_join(dst_subdir
, shard
, iterpool
);
1133 if (rev
% max_files_per_dir
== 0)
1134 SVN_ERR(svn_io_dir_make(dst_subdir_shard
, APR_OS_DEFAULT
,
1138 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard
, dst_subdir_shard
,
1139 apr_psprintf(iterpool
, "%ld", rev
),
1141 svn_pool_clear(iterpool
);
1144 /* Copy the necessary revprop files. */
1145 src_subdir
= svn_path_join(src_path
, PATH_REVPROPS_DIR
, pool
);
1146 dst_subdir
= svn_path_join(dst_path
, PATH_REVPROPS_DIR
, pool
);
1148 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1150 for (rev
= 0; rev
<= youngest
; rev
++)
1152 const char *src_subdir_shard
= src_subdir
,
1153 *dst_subdir_shard
= dst_subdir
;
1155 svn_pool_clear(iterpool
);
1157 if (max_files_per_dir
)
1159 const char *shard
= apr_psprintf(iterpool
, "%ld",
1160 rev
/ max_files_per_dir
);
1161 src_subdir_shard
= svn_path_join(src_subdir
, shard
, iterpool
);
1162 dst_subdir_shard
= svn_path_join(dst_subdir
, shard
, iterpool
);
1164 if (rev
% max_files_per_dir
== 0)
1165 SVN_ERR(svn_io_dir_make(dst_subdir_shard
, APR_OS_DEFAULT
,
1169 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard
, dst_subdir_shard
,
1170 apr_psprintf(iterpool
, "%ld", rev
),
1174 svn_pool_destroy(iterpool
);
1176 /* Make an empty transactions directory for now. Eventually some
1177 method of copying in progress transactions will need to be
1179 dst_subdir
= svn_path_join(dst_path
, PATH_TXNS_DIR
, pool
);
1180 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1182 /* Now copy the locks tree. */
1183 src_subdir
= svn_path_join(src_path
, PATH_LOCKS_DIR
, pool
);
1184 SVN_ERR(svn_io_check_path(src_subdir
, &kind
, pool
));
1185 if (kind
== svn_node_dir
)
1186 SVN_ERR(svn_io_copy_dir_recursively(src_subdir
, dst_path
,
1187 PATH_LOCKS_DIR
, TRUE
, NULL
,
1190 /* Copy the transaction-current file. */
1191 if (format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
1192 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_TXN_CURRENT
, pool
));
1194 /* Hotcopied FS is complete. Stamp it with a format file. */
1195 SVN_ERR(write_format(svn_path_join(dst_path
, PATH_FORMAT
, pool
),
1196 format
, max_files_per_dir
, pool
));
1198 return SVN_NO_ERROR
;
1202 svn_fs_fs__youngest_rev(svn_revnum_t
*youngest_p
,
1206 SVN_ERR(get_youngest(youngest_p
, fs
->path
, pool
));
1208 return SVN_NO_ERROR
;
1211 /* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN
1212 * bytes plus the stuff around them. */
1213 #define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1
1215 /* Given a revision file FILE that has been pre-positioned at the
1216 beginning of a Node-Rev header block, read in that header block and
1217 store it in the apr_hash_t HEADERS. All allocations will be from
1219 static svn_error_t
* read_header_block(apr_hash_t
**headers
,
1223 *headers
= apr_hash_make(pool
);
1227 char header_str
[MAX_HEADERS_STR_LEN
];
1228 const char *name
, *value
;
1229 apr_size_t i
= 0, header_len
;
1231 char *local_name
, *local_value
;
1233 limit
= sizeof(header_str
);
1234 SVN_ERR(svn_io_read_length_line(file
, header_str
, &limit
, pool
));
1236 if (strlen(header_str
) == 0)
1237 break; /* end of header block */
1239 header_len
= strlen(header_str
);
1241 while (header_str
[i
] != ':')
1243 if (header_str
[i
] == '\0')
1244 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1245 _("Found malformed header in "
1250 /* Create a 'name' string and point to it. */
1251 header_str
[i
] = '\0';
1254 /* Skip over the NULL byte and the space following it. */
1258 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1259 _("Found malformed header in "
1262 value
= header_str
+ i
;
1264 local_name
= apr_pstrdup(pool
, name
);
1265 local_value
= apr_pstrdup(pool
, value
);
1267 apr_hash_set(*headers
, local_name
, APR_HASH_KEY_STRING
, local_value
);
1270 return SVN_NO_ERROR
;
1273 /* Open the revision file for revision REV in filesystem FS and store
1274 the newly opened file in FILE. Seek to location OFFSET before
1275 returning. Perform temporary allocations in POOL. */
1276 static svn_error_t
*
1277 open_and_seek_revision(apr_file_t
**file
,
1283 apr_file_t
*rev_file
;
1285 SVN_ERR(svn_io_file_open(&rev_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
1286 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1288 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1292 return SVN_NO_ERROR
;
1295 /* Open the representation for a node-revision in transaction TXN_ID
1296 in filesystem FS and store the newly opened file in FILE. Seek to
1297 location OFFSET before returning. Perform temporary allocations in
1298 POOL. Only appropriate for file contents, nor props or directory
1300 static svn_error_t
*
1301 open_and_seek_transaction(apr_file_t
**file
,
1304 representation_t
*rep
,
1307 apr_file_t
*rev_file
;
1310 SVN_ERR(svn_io_file_open(&rev_file
, path_txn_proto_rev(fs
, txn_id
, pool
),
1311 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1313 offset
= rep
->offset
;
1314 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1318 return SVN_NO_ERROR
;
1321 /* Given a node-id ID, and a representation REP in filesystem FS, open
1322 the correct file and seek to the correction location. Store this
1323 file in *FILE_P. Perform any allocations in POOL. */
1324 static svn_error_t
*
1325 open_and_seek_representation(apr_file_t
**file_p
,
1327 representation_t
*rep
,
1331 return open_and_seek_revision(file_p
, fs
, rep
->revision
, rep
->offset
,
1334 return open_and_seek_transaction(file_p
, fs
, rep
->txn_id
, rep
, pool
);
1337 /* Parse the description of a representation from STRING and store it
1338 into *REP_P. If the representation is mutable (the revision is
1339 given as -1), then use TXN_ID for the representation's txn_id
1340 field. If MUTABLE_REP_TRUNCATED is true, then this representation
1341 is for property or directory contents, and no information will be
1342 expected except the "-1" revision number for a mutable
1343 representation. Allocate *REP_P in POOL. */
1344 static svn_error_t
*
1345 read_rep_offsets(representation_t
**rep_p
,
1348 svn_boolean_t mutable_rep_truncated
,
1351 representation_t
*rep
;
1352 char *str
, *last_str
;
1355 rep
= apr_pcalloc(pool
, sizeof(*rep
));
1358 str
= apr_strtok(string
, " ", &last_str
);
1360 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1361 _("Malformed text rep offset line in node-rev"));
1364 rep
->revision
= SVN_STR_TO_REV(str
);
1365 if (rep
->revision
== SVN_INVALID_REVNUM
)
1367 rep
->txn_id
= txn_id
;
1368 if (mutable_rep_truncated
)
1369 return SVN_NO_ERROR
;
1372 str
= apr_strtok(NULL
, " ", &last_str
);
1374 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1375 _("Malformed text rep offset line in node-rev"));
1377 rep
->offset
= apr_atoi64(str
);
1379 str
= apr_strtok(NULL
, " ", &last_str
);
1381 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1382 _("Malformed text rep offset line in node-rev"));
1384 rep
->size
= apr_atoi64(str
);
1386 str
= apr_strtok(NULL
, " ", &last_str
);
1388 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1389 _("Malformed text rep offset line in node-rev"));
1391 rep
->expanded_size
= apr_atoi64(str
);
1393 /* Read in the MD5 hash. */
1394 str
= apr_strtok(NULL
, " ", &last_str
);
1395 if ((str
== NULL
) || (strlen(str
) != (APR_MD5_DIGESTSIZE
* 2)))
1396 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1397 _("Malformed text rep offset line in node-rev"));
1399 /* Parse the hex MD5 hash into digest form. */
1400 for (i
= 0; i
< APR_MD5_DIGESTSIZE
; i
++)
1402 if ((! isxdigit(str
[i
* 2])) || (! isxdigit(str
[i
* 2 + 1])))
1403 return svn_error_create
1404 (SVN_ERR_FS_CORRUPT
, NULL
,
1405 _("Malformed text rep offset line in node-rev"));
1407 str
[i
* 2] = tolower(str
[i
* 2]);
1408 rep
->checksum
[i
] = (str
[i
* 2] -
1409 ((str
[i
* 2] <= '9') ? '0' : ('a' - 10))) << 4;
1411 str
[i
* 2 + 1] = tolower(str
[i
* 2 + 1]);
1412 rep
->checksum
[i
] |= (str
[i
* 2 + 1] -
1413 ((str
[i
* 2 + 1] <= '9') ? '0' : ('a' - 10)));
1416 return SVN_NO_ERROR
;
1420 svn_fs_fs__get_node_revision(node_revision_t
**noderev_p
,
1422 const svn_fs_id_t
*id
,
1425 apr_file_t
*revision_file
;
1426 apr_hash_t
*headers
;
1427 node_revision_t
*noderev
;
1431 if (svn_fs_fs__id_txn_id(id
))
1433 /* This is a transaction node-rev. */
1434 err
= svn_io_file_open(&revision_file
, path_txn_node_rev(fs
, id
, pool
),
1435 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
1439 /* This is a revision node-rev. */
1440 err
= open_and_seek_revision(&revision_file
, fs
,
1441 svn_fs_fs__id_rev(id
),
1442 svn_fs_fs__id_offset(id
),
1448 if (APR_STATUS_IS_ENOENT(err
->apr_err
))
1450 svn_error_clear(err
);
1451 return svn_fs_fs__err_dangling_id(fs
, id
);
1457 SVN_ERR(read_header_block(&headers
, revision_file
, pool
) );
1459 noderev
= apr_pcalloc(pool
, sizeof(*noderev
));
1461 /* Read the node-rev id. */
1462 value
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
1464 SVN_ERR(svn_io_file_close(revision_file
, pool
));
1466 noderev
->id
= svn_fs_fs__id_parse(value
, strlen(value
), pool
);
1468 /* Read the type. */
1469 value
= apr_hash_get(headers
, HEADER_TYPE
, APR_HASH_KEY_STRING
);
1471 if ((value
== NULL
) ||
1472 (strcmp(value
, KIND_FILE
) != 0 && strcmp(value
, KIND_DIR
)))
1473 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1474 _("Missing kind field in node-rev"));
1476 noderev
->kind
= (strcmp(value
, KIND_FILE
) == 0) ? svn_node_file
1479 /* Read the 'count' field. */
1480 value
= apr_hash_get(headers
, HEADER_COUNT
, APR_HASH_KEY_STRING
);
1481 noderev
->predecessor_count
= (value
== NULL
) ? 0 : atoi(value
);
1483 /* Get the properties location. */
1484 value
= apr_hash_get(headers
, HEADER_PROPS
, APR_HASH_KEY_STRING
);
1487 SVN_ERR(read_rep_offsets(&noderev
->prop_rep
, value
,
1488 svn_fs_fs__id_txn_id(id
), TRUE
, pool
));
1491 /* Get the data location. */
1492 value
= apr_hash_get(headers
, HEADER_TEXT
, APR_HASH_KEY_STRING
);
1495 SVN_ERR(read_rep_offsets(&noderev
->data_rep
, value
,
1496 svn_fs_fs__id_txn_id(id
),
1497 (noderev
->kind
== svn_node_dir
), pool
));
1500 /* Get the created path. */
1501 value
= apr_hash_get(headers
, HEADER_CPATH
, APR_HASH_KEY_STRING
);
1504 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1505 _("Missing cpath in node-rev"));
1509 noderev
->created_path
= apr_pstrdup(pool
, value
);
1512 /* Get the predecessor ID. */
1513 value
= apr_hash_get(headers
, HEADER_PRED
, APR_HASH_KEY_STRING
);
1515 noderev
->predecessor_id
= svn_fs_fs__id_parse(value
, strlen(value
),
1518 /* Get the copyroot. */
1519 value
= apr_hash_get(headers
, HEADER_COPYROOT
, APR_HASH_KEY_STRING
);
1522 noderev
->copyroot_path
= apr_pstrdup(pool
, noderev
->created_path
);
1523 noderev
->copyroot_rev
= svn_fs_fs__id_rev(noderev
->id
);
1527 char *str
, *last_str
;
1529 str
= apr_strtok(value
, " ", &last_str
);
1531 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1532 _("Malformed copyroot line in node-rev"));
1534 noderev
->copyroot_rev
= atoi(str
);
1536 if (last_str
== NULL
)
1537 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1538 _("Malformed copyroot line in node-rev"));
1539 noderev
->copyroot_path
= apr_pstrdup(pool
, last_str
);
1542 /* Get the copyfrom. */
1543 value
= apr_hash_get(headers
, HEADER_COPYFROM
, APR_HASH_KEY_STRING
);
1546 noderev
->copyfrom_path
= NULL
;
1547 noderev
->copyfrom_rev
= SVN_INVALID_REVNUM
;
1551 char *str
, *last_str
;
1553 str
= apr_strtok(value
, " ", &last_str
);
1555 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1556 _("Malformed copyfrom line in node-rev"));
1558 noderev
->copyfrom_rev
= atoi(str
);
1560 if (last_str
== NULL
)
1561 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1562 _("Malformed copyfrom line in node-rev"));
1563 noderev
->copyfrom_path
= apr_pstrdup(pool
, last_str
);
1566 /* Get whether this is a fresh txn root. */
1567 value
= apr_hash_get(headers
, HEADER_FRESHTXNRT
, APR_HASH_KEY_STRING
);
1568 noderev
->is_fresh_txn_root
= (value
!= NULL
);
1570 *noderev_p
= noderev
;
1572 return SVN_NO_ERROR
;
1575 /* Return a formatted string that represents the location of
1576 representation REP. If MUTABLE_REP_TRUNCATED is given, the rep is
1577 for props or dir contents, and only a "-1" revision number will be
1578 given for a mutable rep. Perform the allocation from POOL. */
1580 representation_string(representation_t
*rep
,
1581 svn_boolean_t mutable_rep_truncated
, apr_pool_t
*pool
)
1583 if (rep
->txn_id
&& mutable_rep_truncated
)
1586 return apr_psprintf(pool
, "%ld %" APR_OFF_T_FMT
" %" SVN_FILESIZE_T_FMT
1587 " %" SVN_FILESIZE_T_FMT
" %s",
1588 rep
->revision
, rep
->offset
, rep
->size
,
1590 svn_md5_digest_to_cstring_display(rep
->checksum
,
1594 /* Write the node-revision NODEREV into the file FILE. Temporary
1595 allocations are from POOL. */
1596 static svn_error_t
*
1597 write_noderev_txn(apr_file_t
*file
,
1598 node_revision_t
*noderev
,
1601 svn_stream_t
*outfile
;
1603 outfile
= svn_stream_from_aprfile(file
, pool
);
1605 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_ID
": %s\n",
1606 svn_fs_fs__id_unparse(noderev
->id
,
1609 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_TYPE
": %s\n",
1610 (noderev
->kind
== svn_node_file
) ?
1611 KIND_FILE
: KIND_DIR
));
1613 if (noderev
->predecessor_id
)
1614 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_PRED
": %s\n",
1615 svn_fs_fs__id_unparse(noderev
->predecessor_id
,
1618 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COUNT
": %d\n",
1619 noderev
->predecessor_count
));
1621 if (noderev
->data_rep
)
1622 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_TEXT
": %s\n",
1623 representation_string(noderev
->data_rep
,
1628 if (noderev
->prop_rep
)
1629 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_PROPS
": %s\n",
1630 representation_string(noderev
->prop_rep
, TRUE
,
1633 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_CPATH
": %s\n",
1634 noderev
->created_path
));
1636 if (noderev
->copyfrom_path
)
1637 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COPYFROM
": %ld"
1639 noderev
->copyfrom_rev
,
1640 noderev
->copyfrom_path
));
1642 if ((noderev
->copyroot_rev
!= svn_fs_fs__id_rev(noderev
->id
)) ||
1643 (strcmp(noderev
->copyroot_path
, noderev
->created_path
) != 0))
1644 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COPYROOT
": %ld"
1646 noderev
->copyroot_rev
,
1647 noderev
->copyroot_path
));
1649 if (noderev
->is_fresh_txn_root
)
1650 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_FRESHTXNRT
": y\n"));
1652 SVN_ERR(svn_stream_printf(outfile
, pool
, "\n"));
1654 return SVN_NO_ERROR
;
1658 svn_fs_fs__put_node_revision(svn_fs_t
*fs
,
1659 const svn_fs_id_t
*id
,
1660 node_revision_t
*noderev
,
1661 svn_boolean_t fresh_txn_root
,
1664 apr_file_t
*noderev_file
;
1665 const char *txn_id
= svn_fs_fs__id_txn_id(id
);
1667 noderev
->is_fresh_txn_root
= fresh_txn_root
;
1670 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1671 _("Attempted to write to non-transaction"));
1673 SVN_ERR(svn_io_file_open(&noderev_file
, path_txn_node_rev(fs
, id
, pool
),
1674 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
1675 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1677 SVN_ERR(write_noderev_txn(noderev_file
, noderev
, pool
));
1679 SVN_ERR(svn_io_file_close(noderev_file
, pool
));
1681 return SVN_NO_ERROR
;
1685 /* This structure is used to hold the information associated with a
1689 svn_boolean_t is_delta
;
1690 svn_boolean_t is_delta_vs_empty
;
1692 svn_revnum_t base_revision
;
1693 apr_off_t base_offset
;
1694 apr_size_t base_length
;
1697 /* Read the next line from file FILE and parse it as a text
1698 representation entry. Return the parsed entry in *REP_ARGS_P.
1699 Perform all allocations in POOL. */
1700 static svn_error_t
*
1701 read_rep_line(struct rep_args
**rep_args_p
,
1707 struct rep_args
*rep_args
;
1708 char *str
, *last_str
;
1710 limit
= sizeof(buffer
);
1711 SVN_ERR(svn_io_read_length_line(file
, buffer
, &limit
, pool
));
1713 rep_args
= apr_pcalloc(pool
, sizeof(*rep_args
));
1714 rep_args
->is_delta
= FALSE
;
1716 if (strcmp(buffer
, REP_PLAIN
) == 0)
1718 *rep_args_p
= rep_args
;
1719 return SVN_NO_ERROR
;
1722 if (strcmp(buffer
, REP_DELTA
) == 0)
1724 /* This is a delta against the empty stream. */
1725 rep_args
->is_delta
= TRUE
;
1726 rep_args
->is_delta_vs_empty
= TRUE
;
1727 *rep_args_p
= rep_args
;
1728 return SVN_NO_ERROR
;
1731 rep_args
->is_delta
= TRUE
;
1732 rep_args
->is_delta_vs_empty
= FALSE
;
1734 /* We have hopefully a DELTA vs. a non-empty base revision. */
1735 str
= apr_strtok(buffer
, " ", &last_str
);
1736 if (! str
|| (strcmp(str
, REP_DELTA
) != 0)) goto err
;
1738 str
= apr_strtok(NULL
, " ", &last_str
);
1739 if (! str
) goto err
;
1740 rep_args
->base_revision
= atol(str
);
1742 str
= apr_strtok(NULL
, " ", &last_str
);
1743 if (! str
) goto err
;
1744 rep_args
->base_offset
= (apr_off_t
) apr_atoi64(str
);
1746 str
= apr_strtok(NULL
, " ", &last_str
);
1747 if (! str
) goto err
;
1748 rep_args
->base_length
= (apr_size_t
) apr_atoi64(str
);
1750 *rep_args_p
= rep_args
;
1751 return SVN_NO_ERROR
;
1754 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1755 _("Malformed representation header"));
1758 /* Given a revision file REV_FILE, find the Node-ID of the header
1759 located at OFFSET and store it in *ID_P. Allocate temporary
1760 variables from POOL. */
1761 static svn_error_t
*
1762 get_fs_id_at_offset(svn_fs_id_t
**id_p
,
1763 apr_file_t
*rev_file
,
1768 apr_hash_t
*headers
;
1769 const char *node_id_str
;
1771 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1773 SVN_ERR(read_header_block(&headers
, rev_file
, pool
));
1775 node_id_str
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
1777 if (node_id_str
== NULL
)
1778 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1779 _("Missing node-id in node-rev"));
1781 id
= svn_fs_fs__id_parse(node_id_str
, strlen(node_id_str
), pool
);
1784 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1785 _("Corrupt node-id in node-rev"));
1789 return SVN_NO_ERROR
;
1793 /* Given an open revision file REV_FILE, locate the trailer that
1794 specifies the offset to the root node-id and to the changed path
1795 information. Store the root node offset in *ROOT_OFFSET and the
1796 changed path offset in *CHANGES_OFFSET. If either of these
1797 pointers is NULL, do nothing with it. Allocate temporary variables
1799 static svn_error_t
*
1800 get_root_changes_offset(apr_off_t
*root_offset
,
1801 apr_off_t
*changes_offset
,
1802 apr_file_t
*rev_file
,
1810 /* We will assume that the last line containing the two offsets
1811 will never be longer than 64 characters. */
1813 SVN_ERR(svn_io_file_seek(rev_file
, APR_END
, &offset
, pool
));
1815 offset
-= sizeof(buf
);
1816 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1818 /* Read in this last block, from which we will identify the last line. */
1820 SVN_ERR(svn_io_file_read(rev_file
, buf
, &len
, pool
));
1822 /* This cast should be safe since the maximum amount read, 64, will
1823 never be bigger than the size of an int. */
1824 num_bytes
= (int) len
;
1826 /* The last byte should be a newline. */
1827 if (buf
[num_bytes
- 1] != '\n')
1829 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
1830 _("Revision file lacks trailing newline"));
1833 /* Look for the next previous newline. */
1834 for (i
= num_bytes
- 2; i
>= 0; i
--)
1836 if (buf
[i
] == '\n') break;
1841 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
1842 _("Final line in revision file longer than 64 "
1849 *root_offset
= apr_atoi64(&buf
[i
]);
1851 /* find the next space */
1852 for ( ; i
< (num_bytes
- 2) ; i
++)
1853 if (buf
[i
] == ' ') break;
1855 if (i
== (num_bytes
- 2))
1856 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1857 _("Final line in revision file missing space"));
1861 /* note that apr_atoi64() will stop reading as soon as it encounters
1862 the final newline. */
1864 *changes_offset
= apr_atoi64(&buf
[i
]);
1866 return SVN_NO_ERROR
;
1870 svn_fs_fs__rev_get_root(svn_fs_id_t
**root_id_p
,
1875 fs_fs_data_t
*ffd
= fs
->fsap_data
;
1876 apr_file_t
*revision_file
;
1877 apr_off_t root_offset
;
1878 svn_fs_id_t
*root_id
;
1880 const char *rev_str
= apr_psprintf(ffd
->rev_root_id_cache_pool
, "%ld", rev
);
1881 svn_fs_id_t
*cached_id
;
1883 /* Calculate an index into the revroot id cache */
1884 cached_id
= apr_hash_get(ffd
->rev_root_id_cache
,
1886 APR_HASH_KEY_STRING
);
1890 *root_id_p
= svn_fs_fs__id_copy(cached_id
, pool
);
1891 return SVN_NO_ERROR
;
1894 err
= svn_io_file_open(&revision_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
1895 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
1896 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
1898 svn_error_clear(err
);
1899 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1900 _("No such revision %ld"), rev
);
1906 SVN_ERR(get_root_changes_offset(&root_offset
, NULL
, revision_file
, pool
));
1908 SVN_ERR(get_fs_id_at_offset(&root_id
, revision_file
, root_offset
, pool
));
1910 SVN_ERR(svn_io_file_close(revision_file
, pool
));
1913 if (apr_hash_count(ffd
->rev_root_id_cache
) >= NUM_RRI_CACHE_ENTRIES
)
1915 /* In order to only use one pool for the whole cache, we need to
1916 * completely wipe it to expire entries! */
1917 svn_pool_clear(ffd
->rev_root_id_cache_pool
);
1918 ffd
->rev_root_id_cache
= apr_hash_make(ffd
->rev_root_id_cache_pool
);
1920 apr_hash_set(ffd
->rev_root_id_cache
, rev_str
, APR_HASH_KEY_STRING
,
1921 svn_fs_fs__id_copy(root_id
, ffd
->rev_root_id_cache_pool
));
1923 *root_id_p
= root_id
;
1925 return SVN_NO_ERROR
;
1929 svn_fs_fs__set_revision_proplist(svn_fs_t
*fs
,
1931 apr_hash_t
*proplist
,
1934 const char *final_path
= path_revprops(fs
, rev
, pool
);
1935 const char *tmp_path
;
1938 SVN_ERR(svn_io_open_unique_file2
1939 (&f
, &tmp_path
, final_path
, ".tmp", svn_io_file_del_none
, pool
));
1940 SVN_ERR(svn_hash_write(proplist
, f
, pool
));
1941 SVN_ERR(svn_io_file_close(f
, pool
));
1942 /* We use the rev file of this revision as the perms reference,
1943 because when setting revprops for the first time, the revprop
1944 file won't exist and therefore can't serve as its own reference.
1945 (Whereas the rev file should already exist at this point.) */
1946 SVN_ERR(svn_fs_fs__move_into_place(tmp_path
, final_path
,
1947 svn_fs_fs__path_rev(fs
, rev
, pool
),
1950 return SVN_NO_ERROR
;
1954 svn_fs_fs__revision_proplist(apr_hash_t
**proplist_p
,
1959 apr_file_t
*revprop_file
;
1960 apr_hash_t
*proplist
;
1961 svn_error_t
*err
= SVN_NO_ERROR
;
1963 apr_pool_t
*iterpool
;
1965 proplist
= apr_hash_make(pool
);
1966 iterpool
= svn_pool_create(pool
);
1967 for (i
= 0; i
< SVN_ESTALE_RETRY_COUNT
; i
++)
1969 svn_pool_clear(iterpool
);
1971 /* Clear err here (svn_error_clear can safely be passed
1972 * SVN_NO_ERROR) rather than after finding ESTALE so we can
1973 * return the ESTALE error on the last iteration of the loop. */
1974 svn_error_clear(err
);
1975 err
= svn_io_file_open(&revprop_file
, path_revprops(fs
, rev
, iterpool
),
1976 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
1980 if (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
);
1987 else if (APR_TO_OS_ERROR(err
->apr_err
) == ESTALE
)
1993 SVN_ERR(svn_hash__clear(proplist
));
1994 SVN_RETRY_ESTALE(err
,
1995 svn_hash_read2(proplist
,
1996 svn_stream_from_aprfile(revprop_file
,
1998 SVN_HASH_TERMINATOR
, pool
));
2000 SVN_IGNORE_ESTALE(err
, svn_io_file_close(revprop_file
, iterpool
));
2006 svn_pool_destroy(iterpool
);
2008 *proplist_p
= proplist
;
2010 return SVN_NO_ERROR
;
2013 /* Represents where in the current svndiff data block each
2014 representation is. */
2018 apr_off_t start
; /* The starting offset for the raw
2019 svndiff/plaintext data minus header. */
2020 apr_off_t off
; /* The current offset into the file. */
2021 apr_off_t end
; /* The end offset of the raw data. */
2022 int ver
; /* If a delta, what svndiff version? */
2026 /* Read the rep args for REP in filesystem FS and create a rep_state
2027 for reading the representation. Return the rep_state in *REP_STATE
2028 and the rep args in *REP_ARGS, both allocated in POOL. */
2029 static svn_error_t
*
2030 create_rep_state(struct rep_state
**rep_state
,
2031 struct rep_args
**rep_args
,
2032 representation_t
*rep
,
2036 struct rep_state
*rs
= apr_pcalloc(pool
, sizeof(*rs
));
2037 struct rep_args
*ra
;
2038 unsigned char buf
[4];
2040 SVN_ERR(open_and_seek_representation(&rs
->file
, fs
, rep
, pool
));
2041 SVN_ERR(read_rep_line(&ra
, rs
->file
, pool
));
2042 SVN_ERR(get_file_offset(&rs
->start
, rs
->file
, pool
));
2043 rs
->off
= rs
->start
;
2044 rs
->end
= rs
->start
+ rep
->size
;
2048 if (ra
->is_delta
== FALSE
)
2049 /* This is a plaintext, so just return the current rep_state. */
2050 return SVN_NO_ERROR
;
2052 /* We are dealing with a delta, find out what version. */
2053 SVN_ERR(svn_io_file_read_full(rs
->file
, buf
, sizeof(buf
), NULL
, pool
));
2054 if (! ((buf
[0] == 'S') && (buf
[1] == 'V') && (buf
[2] == 'N')))
2055 return svn_error_create
2056 (SVN_ERR_FS_CORRUPT
, NULL
,
2057 _("Malformed svndiff data in representation"));
2059 rs
->chunk_index
= 0;
2062 return SVN_NO_ERROR
;
2065 /* Build an array of rep_state structures in *LIST giving the delta
2066 reps from first_rep to a plain-text or self-compressed rep. Set
2067 *SRC_STATE to the plain-text rep we find at the end of the chain,
2068 or to NULL if the final delta representation is self-compressed.
2069 The representation to start from is designated by filesystem FS, id
2070 ID, and representation REP. */
2071 static svn_error_t
*
2072 build_rep_list(apr_array_header_t
**list
,
2073 struct rep_state
**src_state
,
2075 representation_t
*first_rep
,
2078 representation_t rep
;
2079 struct rep_state
*rs
;
2080 struct rep_args
*rep_args
;
2082 *list
= apr_array_make(pool
, 1, sizeof(struct rep_state
*));
2087 SVN_ERR(create_rep_state(&rs
, &rep_args
, &rep
, fs
, pool
));
2088 if (rep_args
->is_delta
== FALSE
)
2090 /* This is a plaintext, so just return the current rep_state. */
2092 return SVN_NO_ERROR
;
2095 /* Push this rep onto the list. If it's self-compressed, we're done. */
2096 APR_ARRAY_PUSH(*list
, struct rep_state
*) = rs
;
2097 if (rep_args
->is_delta_vs_empty
)
2100 return SVN_NO_ERROR
;
2103 rep
.revision
= rep_args
->base_revision
;
2104 rep
.offset
= rep_args
->base_offset
;
2105 rep
.size
= rep_args
->base_length
;
2111 struct rep_read_baton
2113 /* The FS from which we're reading. */
2116 /* The state of all prior delta representations. */
2117 apr_array_header_t
*rs_list
;
2119 /* The plaintext state, if there is a plaintext. */
2120 struct rep_state
*src_state
;
2122 /* The index of the current delta chunk, if we are reading a delta. */
2125 /* The buffer where we store undeltified data. */
2130 /* An MD5 context for summing the data read in order to verify it. */
2131 struct apr_md5_ctx_t md5_context
;
2132 svn_boolean_t checksum_finalized
;
2134 /* The stored checksum of the representation we are reading, its
2135 length, and the amount we've read so far. Some of this
2136 information is redundant with rs_list and src_state, but it's
2137 convenient for the checksumming code to have it here. */
2138 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2142 /* Used for temporary allocations during the read. */
2145 /* Pool used to store file handles and other data that is persistant
2146 for the entire stream read. */
2147 apr_pool_t
*filehandle_pool
;
2150 /* Create a rep_read_baton structure for node revision NODEREV in
2151 filesystem FS and store it in *RB_P. Perform all allocations in
2152 POOL. If rep is mutable, it must be for file contents. */
2153 static svn_error_t
*
2154 rep_read_get_baton(struct rep_read_baton
**rb_p
,
2156 representation_t
*rep
,
2159 struct rep_read_baton
*b
;
2161 b
= apr_pcalloc(pool
, sizeof(*b
));
2165 apr_md5_init(&(b
->md5_context
));
2166 b
->checksum_finalized
= FALSE
;
2167 memcpy(b
->checksum
, rep
->checksum
, sizeof(b
->checksum
));
2168 b
->len
= rep
->expanded_size
;
2170 b
->pool
= svn_pool_create(pool
);
2171 b
->filehandle_pool
= svn_pool_create(pool
);
2173 SVN_ERR(build_rep_list(&b
->rs_list
, &b
->src_state
, fs
, rep
,
2174 b
->filehandle_pool
));
2176 /* Save our output baton. */
2179 return SVN_NO_ERROR
;
2182 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
2183 window into *NWIN. */
2184 static svn_error_t
*
2185 read_window(svn_txdelta_window_t
**nwin
, int this_chunk
, struct rep_state
*rs
,
2188 svn_stream_t
*stream
;
2190 assert(rs
->chunk_index
<= this_chunk
);
2192 /* Skip windows to reach the current chunk if we aren't there yet. */
2193 while (rs
->chunk_index
< this_chunk
)
2195 SVN_ERR(svn_txdelta_skip_svndiff_window(rs
->file
, rs
->ver
, pool
));
2197 SVN_ERR(get_file_offset(&rs
->off
, rs
->file
, pool
));
2198 if (rs
->off
>= rs
->end
)
2199 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2200 _("Reading one svndiff window read "
2201 "beyond the end of the "
2205 /* Read the next window. */
2206 stream
= svn_stream_from_aprfile(rs
->file
, pool
);
2207 SVN_ERR(svn_txdelta_read_svndiff_window(nwin
, stream
, rs
->ver
, pool
));
2209 SVN_ERR(get_file_offset(&rs
->off
, rs
->file
, pool
));
2211 if (rs
->off
> rs
->end
)
2212 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2213 _("Reading one svndiff window read beyond "
2214 "the end of the representation"));
2216 return SVN_NO_ERROR
;
2219 /* Get one delta window that is a result of combining all but the last deltas
2220 from the current desired representation identified in *RB, to its
2221 final base representation. Store the window in *RESULT. */
2222 static svn_error_t
*
2223 get_combined_window(svn_txdelta_window_t
**result
,
2224 struct rep_read_baton
*rb
)
2226 apr_pool_t
*pool
, *new_pool
;
2228 svn_txdelta_window_t
*window
, *nwin
;
2229 struct rep_state
*rs
;
2231 assert(rb
->rs_list
->nelts
>= 2);
2233 pool
= svn_pool_create(rb
->pool
);
2235 /* Read the next window from the original rep. */
2236 rs
= APR_ARRAY_IDX(rb
->rs_list
, 0, struct rep_state
*);
2237 SVN_ERR(read_window(&window
, rb
->chunk_index
, rs
, pool
));
2239 /* Combine in the windows from the other delta reps, if needed. */
2240 for (i
= 1; i
< rb
->rs_list
->nelts
- 1; i
++)
2242 if (window
->src_ops
== 0)
2245 rs
= APR_ARRAY_IDX(rb
->rs_list
, i
, struct rep_state
*);
2247 SVN_ERR(read_window(&nwin
, rb
->chunk_index
, rs
, pool
));
2249 /* Combine this window with the current one. Cycles pools so that we
2250 only need to hold three windows at a time. */
2251 new_pool
= svn_pool_create(rb
->pool
);
2252 window
= svn_txdelta_compose_windows(nwin
, window
, new_pool
);
2253 svn_pool_destroy(pool
);
2258 return SVN_NO_ERROR
;
2261 static svn_error_t
*
2262 rep_read_contents_close(void *baton
)
2264 struct rep_read_baton
*rb
= baton
;
2266 svn_pool_destroy(rb
->pool
);
2267 svn_pool_destroy(rb
->filehandle_pool
);
2269 return SVN_NO_ERROR
;
2272 /* Return the next *LEN bytes of the rep and store them in *BUF. */
2273 static svn_error_t
*
2274 get_contents(struct rep_read_baton
*rb
,
2278 apr_size_t copy_len
, remaining
= *len
, tlen
;
2279 char *sbuf
, *tbuf
, *cur
= buf
;
2280 struct rep_state
*rs
;
2281 svn_txdelta_window_t
*cwindow
, *lwindow
;
2283 /* Special case for when there are no delta reps, only a plain
2285 if (rb
->rs_list
->nelts
== 0)
2287 copy_len
= remaining
;
2289 if (((apr_off_t
) copy_len
) > rs
->end
- rs
->off
)
2290 copy_len
= (apr_size_t
) (rs
->end
- rs
->off
);
2291 SVN_ERR(svn_io_file_read_full(rs
->file
, cur
, copy_len
, NULL
,
2293 rs
->off
+= copy_len
;
2295 return SVN_NO_ERROR
;
2298 while (remaining
> 0)
2300 /* If we have buffered data from a previous chunk, use that. */
2303 /* Determine how much to copy from the buffer. */
2304 copy_len
= rb
->buf_len
- rb
->buf_pos
;
2305 if (copy_len
> remaining
)
2306 copy_len
= remaining
;
2308 /* Actually copy the data. */
2309 memcpy(cur
, rb
->buf
+ rb
->buf_pos
, copy_len
);
2310 rb
->buf_pos
+= copy_len
;
2312 remaining
-= copy_len
;
2314 /* If the buffer is all used up, clear it and empty the
2316 if (rb
->buf_pos
== rb
->buf_len
)
2318 svn_pool_clear(rb
->pool
);
2325 rs
= APR_ARRAY_IDX(rb
->rs_list
, 0, struct rep_state
*);
2326 if (rs
->off
== rs
->end
)
2329 /* Get more buffered data by evaluating a chunk. */
2330 if (rb
->rs_list
->nelts
> 1)
2331 SVN_ERR(get_combined_window(&cwindow
, rb
));
2334 if (!cwindow
|| cwindow
->src_ops
> 0)
2336 rs
= APR_ARRAY_IDX(rb
->rs_list
, rb
->rs_list
->nelts
- 1,
2337 struct rep_state
*);
2338 /* Read window from last representation in list. */
2339 /* We apply this window directly instead of combining it with the
2340 others. We do this because vdelta is used for deltas against
2341 the empty stream, which will trigger quadratic behaviour in
2342 the delta combiner. */
2343 SVN_ERR(read_window(&lwindow
, rb
->chunk_index
, rs
, rb
->pool
));
2345 if (lwindow
->src_ops
> 0)
2347 if (! rb
->src_state
)
2348 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2349 _("svndiff data requested "
2350 "non-existent source"));
2352 sbuf
= apr_palloc(rb
->pool
, lwindow
->sview_len
);
2353 if (! ((rs
->start
+ lwindow
->sview_offset
) < rs
->end
))
2354 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2355 _("svndiff requested position "
2356 "beyond end of stream"));
2357 if ((rs
->start
+ lwindow
->sview_offset
) != rs
->off
)
2359 rs
->off
= rs
->start
+ lwindow
->sview_offset
;
2360 SVN_ERR(svn_io_file_seek(rs
->file
, APR_SET
, &rs
->off
,
2363 SVN_ERR(svn_io_file_read_full(rs
->file
, sbuf
,
2366 rs
->off
+= lwindow
->sview_len
;
2371 /* Apply lwindow to source. */
2372 tlen
= lwindow
->tview_len
;
2373 tbuf
= apr_palloc(rb
->pool
, tlen
);
2374 svn_txdelta_apply_instructions(lwindow
, sbuf
, tbuf
,
2376 if (tlen
!= lwindow
->tview_len
)
2377 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2378 _("svndiff window length is "
2389 rb
->buf_len
= cwindow
->tview_len
;
2390 rb
->buf
= apr_palloc(rb
->pool
, rb
->buf_len
);
2391 svn_txdelta_apply_instructions(cwindow
, sbuf
, rb
->buf
,
2393 if (rb
->buf_len
!= cwindow
->tview_len
)
2394 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2395 _("svndiff window length is "
2400 rb
->buf_len
= lwindow
->tview_len
;
2410 return SVN_NO_ERROR
;
2413 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
2414 representation and store them in *BUF. Sum as we read and verify
2415 the MD5 sum at the end. */
2416 static svn_error_t
*
2417 rep_read_contents(void *baton
,
2421 struct rep_read_baton
*rb
= baton
;
2423 /* Get the next block of data. */
2424 SVN_ERR(get_contents(rb
, buf
, len
));
2426 /* Perform checksumming. We want to check the checksum as soon as
2427 the last byte of data is read, in case the caller never performs
2428 a short read, but we don't want to finalize the MD5 context
2430 if (!rb
->checksum_finalized
)
2432 apr_md5_update(&rb
->md5_context
, buf
, *len
);
2434 if (rb
->off
== rb
->len
)
2436 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2438 rb
->checksum_finalized
= TRUE
;
2439 apr_md5_final(checksum
, &rb
->md5_context
);
2440 if (! svn_md5_digests_match(checksum
, rb
->checksum
))
2441 return svn_error_createf
2442 (SVN_ERR_FS_CORRUPT
, NULL
,
2443 _("Checksum mismatch while reading representation:\n"
2446 svn_md5_digest_to_cstring_display(rb
->checksum
, rb
->pool
),
2447 svn_md5_digest_to_cstring_display(checksum
, rb
->pool
));
2450 return SVN_NO_ERROR
;
2453 /* Return a stream in *CONTENTS_P that will read the contents of a
2454 representation stored at the location given by REP. Appropriate
2455 for any kind of immutable representation, but only for file
2456 contents (not props or directory contents) in mutable
2459 If REP is NULL, the representation is assumed to be empty, and the
2460 empty stream is returned.
2462 static svn_error_t
*
2463 read_representation(svn_stream_t
**contents_p
,
2465 representation_t
*rep
,
2468 struct rep_read_baton
*rb
;
2472 *contents_p
= svn_stream_empty(pool
);
2476 SVN_ERR(rep_read_get_baton(&rb
, fs
, rep
, pool
));
2477 *contents_p
= svn_stream_create(rb
, pool
);
2478 svn_stream_set_read(*contents_p
, rep_read_contents
);
2479 svn_stream_set_close(*contents_p
, rep_read_contents_close
);
2482 return SVN_NO_ERROR
;
2486 svn_fs_fs__get_contents(svn_stream_t
**contents_p
,
2488 node_revision_t
*noderev
,
2491 return read_representation(contents_p
, fs
, noderev
->data_rep
, pool
);
2494 /* Baton used when reading delta windows. */
2495 struct delta_read_baton
2497 struct rep_state
*rs
;
2498 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2501 /* This implements the svn_txdelta_next_window_fn_t interface. */
2502 static svn_error_t
*
2503 delta_read_next_window(svn_txdelta_window_t
**window
, void *baton
,
2506 struct delta_read_baton
*drb
= baton
;
2508 if (drb
->rs
->off
== drb
->rs
->end
)
2511 return SVN_NO_ERROR
;
2514 SVN_ERR(read_window(window
, drb
->rs
->chunk_index
, drb
->rs
, pool
));
2516 return SVN_NO_ERROR
;
2519 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
2520 static const unsigned char *
2521 delta_read_md5_digest(void *baton
)
2523 struct delta_read_baton
*drb
= baton
;
2525 return drb
->checksum
;
2529 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t
**stream_p
,
2531 node_revision_t
*source
,
2532 node_revision_t
*target
,
2535 svn_stream_t
*source_stream
, *target_stream
;
2537 /* Try a shortcut: if the target is stored as a delta against the source,
2538 then just use that delta. */
2539 if (source
&& source
->data_rep
&& target
->data_rep
)
2541 struct rep_state
*rep_state
;
2542 struct rep_args
*rep_args
;
2544 /* Read target's base rep if any. */
2545 SVN_ERR(create_rep_state(&rep_state
, &rep_args
, target
->data_rep
,
2547 /* If that matches source, then use this delta as is. */
2548 if (rep_args
->is_delta
2549 && (rep_args
->is_delta_vs_empty
2550 || (rep_args
->base_revision
== source
->data_rep
->revision
2551 && rep_args
->base_offset
== source
->data_rep
->offset
)))
2553 /* Create the delta read baton. */
2554 struct delta_read_baton
*drb
= apr_pcalloc(pool
, sizeof(*drb
));
2555 drb
->rs
= rep_state
;
2556 memcpy(drb
->checksum
, target
->data_rep
->checksum
,
2557 sizeof(drb
->checksum
));
2558 *stream_p
= svn_txdelta_stream_create(drb
, delta_read_next_window
,
2559 delta_read_md5_digest
, pool
);
2560 return SVN_NO_ERROR
;
2563 SVN_ERR(svn_io_file_close(rep_state
->file
, pool
));
2566 /* Read both fulltexts and construct a delta. */
2568 SVN_ERR(read_representation(&source_stream
, fs
, source
->data_rep
, pool
));
2570 source_stream
= svn_stream_empty(pool
);
2571 SVN_ERR(read_representation(&target_stream
, fs
, target
->data_rep
, pool
));
2572 svn_txdelta(stream_p
, source_stream
, target_stream
, pool
);
2574 return SVN_NO_ERROR
;
2578 /* Fetch the contents of a directory into ENTRIES. Values are stored
2579 as filename to string mappings; further conversion is necessary to
2580 convert them into svn_fs_dirent_t values. */
2581 static svn_error_t
*
2582 get_dir_contents(apr_hash_t
*entries
,
2584 node_revision_t
*noderev
,
2587 svn_stream_t
*contents
;
2589 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
2591 apr_file_t
*dir_file
;
2592 const char *filename
= path_txn_node_children(fs
, noderev
->id
, pool
);
2594 /* The representation is mutable. Read the old directory
2595 contents from the mutable children file, followed by the
2596 changes we've made in this transaction. */
2597 SVN_ERR(svn_io_file_open(&dir_file
, filename
, APR_READ
| APR_BUFFERED
,
2598 APR_OS_DEFAULT
, pool
));
2599 contents
= svn_stream_from_aprfile(dir_file
, pool
);
2600 SVN_ERR(svn_hash_read2(entries
, contents
, SVN_HASH_TERMINATOR
, pool
));
2601 SVN_ERR(svn_hash_read_incremental(entries
, contents
, NULL
, pool
));
2602 SVN_ERR(svn_io_file_close(dir_file
, pool
));
2604 else if (noderev
->data_rep
)
2606 /* The representation is immutable. Read it normally. */
2607 SVN_ERR(read_representation(&contents
, fs
, noderev
->data_rep
, pool
));
2608 SVN_ERR(svn_hash_read2(entries
, contents
, SVN_HASH_TERMINATOR
, pool
));
2609 SVN_ERR(svn_stream_close(contents
));
2612 return SVN_NO_ERROR
;
2616 svn_fs_fs__rep_contents_dir(apr_hash_t
**entries_p
,
2618 node_revision_t
*noderev
,
2621 fs_fs_data_t
*ffd
= fs
->fsap_data
;
2622 apr_hash_t
*entries
;
2623 apr_hash_index_t
*hi
;
2626 /* Calculate an index into the dir entries cache */
2627 hid
= DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev
->id
));
2629 /* If we have this directory cached, return it. */
2630 if (! svn_fs_fs__id_txn_id(noderev
->id
) &&
2631 ffd
->dir_cache_id
[hid
] && svn_fs_fs__id_eq(ffd
->dir_cache_id
[hid
],
2634 *entries_p
= ffd
->dir_cache
[hid
];
2635 return SVN_NO_ERROR
;
2638 /* Read in the directory hash. */
2639 entries
= apr_hash_make(pool
);
2640 SVN_ERR(get_dir_contents(entries
, fs
, noderev
, pool
));
2642 /* Prepare to cache this directory. */
2643 ffd
->dir_cache_id
[hid
] = NULL
;
2644 if (ffd
->dir_cache_pool
[hid
])
2645 svn_pool_clear(ffd
->dir_cache_pool
[hid
]);
2647 ffd
->dir_cache_pool
[hid
] = svn_pool_create(fs
->pool
);
2648 ffd
->dir_cache
[hid
] = apr_hash_make(ffd
->dir_cache_pool
[hid
]);
2650 /* Translate the string dir entries into real entries in the dir cache. */
2651 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
2656 char *str
, *last_str
;
2657 svn_fs_dirent_t
*dirent
= apr_pcalloc(ffd
->dir_cache_pool
[hid
],
2660 apr_hash_this(hi
, &key
, NULL
, &val
);
2661 str_val
= apr_pstrdup(pool
, *((char **)val
));
2662 dirent
->name
= apr_pstrdup(ffd
->dir_cache_pool
[hid
], key
);
2664 str
= apr_strtok(str_val
, " ", &last_str
);
2666 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2667 _("Directory entry corrupt"));
2669 if (strcmp(str
, KIND_FILE
) == 0)
2671 dirent
->kind
= svn_node_file
;
2673 else if (strcmp(str
, KIND_DIR
) == 0)
2675 dirent
->kind
= svn_node_dir
;
2679 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2680 _("Directory entry corrupt"));
2683 str
= apr_strtok(NULL
, " ", &last_str
);
2685 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2686 _("Directory entry corrupt"));
2688 dirent
->id
= svn_fs_fs__id_parse(str
, strlen(str
),
2689 ffd
->dir_cache_pool
[hid
]);
2691 apr_hash_set(ffd
->dir_cache
[hid
], dirent
->name
, APR_HASH_KEY_STRING
, dirent
);
2694 /* Mark which directory we've cached and return it. */
2695 ffd
->dir_cache_id
[hid
] = svn_fs_fs__id_copy(noderev
->id
, ffd
->dir_cache_pool
[hid
]);
2696 *entries_p
= ffd
->dir_cache
[hid
];
2697 return SVN_NO_ERROR
;
2701 svn_fs_fs__copy_dir_entries(apr_hash_t
*entries
,
2704 apr_hash_t
*new_entries
= apr_hash_make(pool
);
2705 apr_hash_index_t
*hi
;
2707 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
2710 svn_fs_dirent_t
*dirent
, *new_dirent
;
2712 apr_hash_this(hi
, NULL
, NULL
, &val
);
2714 new_dirent
= apr_palloc(pool
, sizeof(*new_dirent
));
2715 new_dirent
->name
= apr_pstrdup(pool
, dirent
->name
);
2716 new_dirent
->kind
= dirent
->kind
;
2717 new_dirent
->id
= svn_fs_fs__id_copy(dirent
->id
, pool
);
2718 apr_hash_set(new_entries
, new_dirent
->name
, APR_HASH_KEY_STRING
,
2725 svn_fs_fs__get_proplist(apr_hash_t
**proplist_p
,
2727 node_revision_t
*noderev
,
2730 apr_hash_t
*proplist
;
2731 svn_stream_t
*stream
;
2733 proplist
= apr_hash_make(pool
);
2735 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
2737 apr_file_t
*props_file
;
2738 const char *filename
= path_txn_node_props(fs
, noderev
->id
, pool
);
2740 SVN_ERR(svn_io_file_open(&props_file
, filename
,
2741 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
2743 stream
= svn_stream_from_aprfile(props_file
, pool
);
2744 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
, pool
));
2745 SVN_ERR(svn_io_file_close(props_file
, pool
));
2747 else if (noderev
->prop_rep
)
2749 SVN_ERR(read_representation(&stream
, fs
, noderev
->prop_rep
, pool
));
2750 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
, pool
));
2751 SVN_ERR(svn_stream_close(stream
));
2754 *proplist_p
= proplist
;
2756 return SVN_NO_ERROR
;
2760 svn_fs_fs__file_length(svn_filesize_t
*length
,
2761 node_revision_t
*noderev
,
2764 if (noderev
->data_rep
)
2765 *length
= noderev
->data_rep
->expanded_size
;
2769 return SVN_NO_ERROR
;
2773 svn_fs_fs__noderev_same_rep_key(representation_t
*a
,
2774 representation_t
*b
)
2785 if (a
->offset
!= b
->offset
)
2788 if (a
->revision
!= b
->revision
)
2795 svn_fs_fs__file_checksum(unsigned char digest
[],
2796 node_revision_t
*noderev
,
2799 if (noderev
->data_rep
)
2800 memcpy(digest
, noderev
->data_rep
->checksum
, APR_MD5_DIGESTSIZE
);
2802 memset(digest
, 0, APR_MD5_DIGESTSIZE
);
2804 return SVN_NO_ERROR
;
2808 svn_fs_fs__rep_copy(representation_t
*rep
,
2811 representation_t
*rep_new
;
2816 rep_new
= apr_pcalloc(pool
, sizeof(*rep_new
));
2818 memcpy(rep_new
, rep
, sizeof(*rep_new
));
2823 /* Merge the internal-use-only CHANGE into a hash of public-FS
2824 svn_fs_path_change_t CHANGES, collapsing multiple changes into a
2825 single summarical (is that real word?) change per path. Also keep
2826 the COPYFROM_HASH up to date with new adds and replaces. */
2827 static svn_error_t
*
2828 fold_change(apr_hash_t
*changes
,
2829 const change_t
*change
,
2830 apr_hash_t
*copyfrom_hash
)
2832 apr_pool_t
*pool
= apr_hash_pool_get(changes
);
2833 apr_pool_t
*copyfrom_pool
= apr_hash_pool_get(copyfrom_hash
);
2834 svn_fs_path_change_t
*old_change
, *new_change
;
2835 const char *path
, *copyfrom_string
, *copyfrom_path
= NULL
;
2837 if ((old_change
= apr_hash_get(changes
, change
->path
, APR_HASH_KEY_STRING
)))
2839 /* This path already exists in the hash, so we have to merge
2840 this change into the already existing one. */
2842 /* Get the existing copyfrom entry for this path. */
2843 copyfrom_string
= apr_hash_get(copyfrom_hash
, change
->path
,
2844 APR_HASH_KEY_STRING
);
2846 /* If this entry existed in the copyfrom hash, we don't need to
2848 if (copyfrom_string
)
2849 copyfrom_path
= change
->path
;
2851 /* Since the path already exists in the hash, we don't have to
2852 dup the allocation for the path itself. */
2853 path
= change
->path
;
2854 /* Sanity check: only allow NULL node revision ID in the
2856 if ((! change
->noderev_id
) && (change
->kind
!= svn_fs_path_change_reset
))
2857 return svn_error_create
2858 (SVN_ERR_FS_CORRUPT
, NULL
,
2859 _("Missing required node revision ID"));
2861 /* Sanity check: we should be talking about the same node
2862 revision ID as our last change except where the last change
2864 if (change
->noderev_id
2865 && (! svn_fs_fs__id_eq(old_change
->node_rev_id
, change
->noderev_id
))
2866 && (old_change
->change_kind
!= svn_fs_path_change_delete
))
2867 return svn_error_create
2868 (SVN_ERR_FS_CORRUPT
, NULL
,
2869 _("Invalid change ordering: new node revision ID "
2872 /* Sanity check: an add, replacement, or reset must be the first
2873 thing to follow a deletion. */
2874 if ((old_change
->change_kind
== svn_fs_path_change_delete
)
2875 && (! ((change
->kind
== svn_fs_path_change_replace
)
2876 || (change
->kind
== svn_fs_path_change_reset
)
2877 || (change
->kind
== svn_fs_path_change_add
))))
2878 return svn_error_create
2879 (SVN_ERR_FS_CORRUPT
, NULL
,
2880 _("Invalid change ordering: non-add change on deleted path"));
2882 /* Now, merge that change in. */
2883 switch (change
->kind
)
2885 case svn_fs_path_change_reset
:
2886 /* A reset here will simply remove the path change from the
2889 copyfrom_string
= NULL
;
2892 case svn_fs_path_change_delete
:
2893 if (old_change
->change_kind
== svn_fs_path_change_add
)
2895 /* If the path was introduced in this transaction via an
2896 add, and we are deleting it, just remove the path
2902 /* A deletion overrules all previous changes. */
2903 old_change
->change_kind
= svn_fs_path_change_delete
;
2904 old_change
->text_mod
= change
->text_mod
;
2905 old_change
->prop_mod
= change
->prop_mod
;
2907 copyfrom_string
= NULL
;
2910 case svn_fs_path_change_add
:
2911 case svn_fs_path_change_replace
:
2912 /* An add at this point must be following a previous delete,
2913 so treat it just like a replace. */
2914 old_change
->change_kind
= svn_fs_path_change_replace
;
2915 old_change
->node_rev_id
= svn_fs_fs__id_copy(change
->noderev_id
,
2917 old_change
->text_mod
= change
->text_mod
;
2918 old_change
->prop_mod
= change
->prop_mod
;
2919 if (change
->copyfrom_rev
== SVN_INVALID_REVNUM
)
2920 copyfrom_string
= apr_pstrdup(copyfrom_pool
, "");
2923 copyfrom_string
= apr_psprintf(copyfrom_pool
,
2925 change
->copyfrom_rev
,
2926 change
->copyfrom_path
);
2930 case svn_fs_path_change_modify
:
2932 if (change
->text_mod
)
2933 old_change
->text_mod
= TRUE
;
2934 if (change
->prop_mod
)
2935 old_change
->prop_mod
= TRUE
;
2939 /* Point our new_change to our (possibly modified) old_change. */
2940 new_change
= old_change
;
2944 /* This change is new to the hash, so make a new public change
2945 structure from the internal one (in the hash's pool), and dup
2946 the path into the hash's pool, too. */
2947 new_change
= apr_pcalloc(pool
, sizeof(*new_change
));
2948 new_change
->node_rev_id
= svn_fs_fs__id_copy(change
->noderev_id
, pool
);
2949 new_change
->change_kind
= change
->kind
;
2950 new_change
->text_mod
= change
->text_mod
;
2951 new_change
->prop_mod
= change
->prop_mod
;
2952 if (change
->copyfrom_rev
!= SVN_INVALID_REVNUM
)
2954 copyfrom_string
= apr_psprintf(copyfrom_pool
, "%ld %s",
2955 change
->copyfrom_rev
,
2956 change
->copyfrom_path
);
2959 copyfrom_string
= apr_pstrdup(copyfrom_pool
, "");
2960 path
= apr_pstrdup(pool
, change
->path
);
2963 /* Add (or update) this path. */
2964 apr_hash_set(changes
, path
, APR_HASH_KEY_STRING
, new_change
);
2966 /* If copyfrom_path is non-NULL, the key is already present in the
2967 hash, so we don't need to duplicate it in the copyfrom pool. */
2968 if (! copyfrom_path
)
2970 /* If copyfrom_string is NULL, the hash entry will be deleted,
2971 so we don't need to duplicate the key in the copyfrom
2973 copyfrom_path
= copyfrom_string
? apr_pstrdup(copyfrom_pool
, path
)
2977 apr_hash_set(copyfrom_hash
, copyfrom_path
, APR_HASH_KEY_STRING
,
2980 return SVN_NO_ERROR
;
2983 /* The 256 is an arbitrary size large enough to hold the node id and the
2985 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
2987 /* Read the next entry in the changes record from file FILE and store
2988 the resulting change in *CHANGE_P. If there is no next record,
2989 store NULL there. Perform all allocations from POOL. */
2990 static svn_error_t
*
2991 read_change(change_t
**change_p
,
2995 char buf
[MAX_CHANGE_LINE_LEN
];
2996 apr_size_t len
= sizeof(buf
);
2998 char *str
, *last_str
;
3001 /* Default return value. */
3004 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
3006 /* Check for a blank line. */
3007 if (err
|| (len
== 0))
3009 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
3011 svn_error_clear(err
);
3012 return SVN_NO_ERROR
;
3014 if ((len
== 0) && (! err
))
3015 return SVN_NO_ERROR
;
3019 change
= apr_pcalloc(pool
, sizeof(*change
));
3021 /* Get the node-id of the change. */
3022 str
= apr_strtok(buf
, " ", &last_str
);
3024 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3025 _("Invalid changes line in rev-file"));
3027 change
->noderev_id
= svn_fs_fs__id_parse(str
, strlen(str
), pool
);
3029 /* Get the change type. */
3030 str
= apr_strtok(NULL
, " ", &last_str
);
3032 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3033 _("Invalid changes line in rev-file"));
3035 if (strcmp(str
, ACTION_MODIFY
) == 0)
3037 change
->kind
= svn_fs_path_change_modify
;
3039 else if (strcmp(str
, ACTION_ADD
) == 0)
3041 change
->kind
= svn_fs_path_change_add
;
3043 else if (strcmp(str
, ACTION_DELETE
) == 0)
3045 change
->kind
= svn_fs_path_change_delete
;
3047 else if (strcmp(str
, ACTION_REPLACE
) == 0)
3049 change
->kind
= svn_fs_path_change_replace
;
3051 else if (strcmp(str
, ACTION_RESET
) == 0)
3053 change
->kind
= svn_fs_path_change_reset
;
3057 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3058 _("Invalid change kind in rev file"));
3061 /* Get the text-mod flag. */
3062 str
= apr_strtok(NULL
, " ", &last_str
);
3064 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3065 _("Invalid changes line in rev-file"));
3067 if (strcmp(str
, FLAG_TRUE
) == 0)
3069 change
->text_mod
= TRUE
;
3071 else if (strcmp(str
, FLAG_FALSE
) == 0)
3073 change
->text_mod
= FALSE
;
3077 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3078 _("Invalid text-mod flag in rev-file"));
3081 /* Get the prop-mod flag. */
3082 str
= apr_strtok(NULL
, " ", &last_str
);
3084 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3085 _("Invalid changes line in rev-file"));
3087 if (strcmp(str
, FLAG_TRUE
) == 0)
3089 change
->prop_mod
= TRUE
;
3091 else if (strcmp(str
, FLAG_FALSE
) == 0)
3093 change
->prop_mod
= FALSE
;
3097 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3098 _("Invalid prop-mod flag in rev-file"));
3101 /* Get the changed path. */
3102 change
->path
= apr_pstrdup(pool
, last_str
);
3105 /* Read the next line, the copyfrom line. */
3107 SVN_ERR(svn_io_read_length_line(file
, buf
, &len
, pool
));
3111 change
->copyfrom_rev
= SVN_INVALID_REVNUM
;
3112 change
->copyfrom_path
= NULL
;
3116 str
= apr_strtok(buf
, " ", &last_str
);
3118 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3119 _("Invalid changes line in rev-file"));
3120 change
->copyfrom_rev
= atol(str
);
3123 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3124 _("Invalid changes line in rev-file"));
3126 change
->copyfrom_path
= apr_pstrdup(pool
, last_str
);
3131 return SVN_NO_ERROR
;
3134 /* Fetch all the changed path entries from FILE and store then in
3135 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
3136 *data. Store a hash of paths to copyfrom revisions/paths in
3137 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
3138 the changed-path entries have already been folded (by
3139 write_final_changed_path_info) and may be out of order, so we shouldn't
3140 remove children of replaced or deleted directories. Do all
3141 allocations in POOL. */
3142 static svn_error_t
*
3143 fetch_all_changes(apr_hash_t
*changed_paths
,
3144 apr_hash_t
*copyfrom_hash
,
3146 svn_boolean_t prefolded
,
3150 apr_pool_t
*iterpool
= svn_pool_create(pool
);
3151 apr_hash_t
*my_hash
;
3153 /* If we are passed a NULL copyfrom hash, manufacture one for the
3154 duration of this call. */
3155 my_hash
= copyfrom_hash
? copyfrom_hash
: apr_hash_make(pool
);
3157 /* Read in the changes one by one, folding them into our local hash
3160 SVN_ERR(read_change(&change
, file
, iterpool
));
3164 SVN_ERR(fold_change(changed_paths
, change
, my_hash
));
3166 /* Now, if our change was a deletion or replacement, we have to
3167 blow away any changes thus far on paths that are (or, were)
3168 children of this path.
3169 ### i won't bother with another iteration pool here -- at
3170 most we talking about a few extra dups of paths into what
3171 is already a temporary subpool.
3174 if (((change
->kind
== svn_fs_path_change_delete
)
3175 || (change
->kind
== svn_fs_path_change_replace
))
3178 apr_hash_index_t
*hi
;
3180 for (hi
= apr_hash_first(iterpool
, changed_paths
);
3182 hi
= apr_hash_next(hi
))
3184 /* KEY is the path. */
3185 const void *hashkey
;
3187 apr_hash_this(hi
, &hashkey
, &klen
, NULL
);
3189 /* If we come across our own path, ignore it. */
3190 if (strcmp(change
->path
, hashkey
) == 0)
3193 /* If we come across a child of our path, remove it. */
3194 if (svn_path_is_child(change
->path
, hashkey
, iterpool
))
3195 apr_hash_set(changed_paths
, hashkey
, klen
, NULL
);
3199 /* Clear the per-iteration subpool. */
3200 svn_pool_clear(iterpool
);
3202 SVN_ERR(read_change(&change
, file
, iterpool
));
3205 /* Destroy the per-iteration subpool. */
3206 svn_pool_destroy(iterpool
);
3208 return SVN_NO_ERROR
;
3212 svn_fs_fs__txn_changes_fetch(apr_hash_t
**changed_paths_p
,
3215 apr_hash_t
*copyfrom_cache
,
3219 apr_hash_t
*changed_paths
= apr_hash_make(pool
);
3221 SVN_ERR(svn_io_file_open(&file
, path_txn_changes(fs
, txn_id
, pool
),
3222 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3224 SVN_ERR(fetch_all_changes(changed_paths
, copyfrom_cache
, file
, FALSE
,
3227 SVN_ERR(svn_io_file_close(file
, pool
));
3229 *changed_paths_p
= changed_paths
;
3231 return SVN_NO_ERROR
;
3235 svn_fs_fs__paths_changed(apr_hash_t
**changed_paths_p
,
3238 apr_hash_t
*copyfrom_cache
,
3241 apr_off_t changes_offset
;
3242 apr_hash_t
*changed_paths
;
3243 apr_file_t
*revision_file
;
3245 SVN_ERR(svn_io_file_open(&revision_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
3246 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3248 SVN_ERR(get_root_changes_offset(NULL
, &changes_offset
, revision_file
,
3251 SVN_ERR(svn_io_file_seek(revision_file
, APR_SET
, &changes_offset
, pool
));
3253 changed_paths
= apr_hash_make(pool
);
3255 SVN_ERR(fetch_all_changes(changed_paths
, copyfrom_cache
, revision_file
,
3258 /* Close the revision file. */
3259 SVN_ERR(svn_io_file_close(revision_file
, pool
));
3261 *changed_paths_p
= changed_paths
;
3263 return SVN_NO_ERROR
;
3266 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
3267 the filesystem FS. This is only used to create the root of a transaction.
3268 Allocations are from POOL. */
3269 static svn_error_t
*
3270 create_new_txn_noderev_from_rev(svn_fs_t
*fs
,
3275 node_revision_t
*noderev
;
3276 const char *node_id
, *copy_id
;
3278 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, src
, pool
));
3280 if (svn_fs_fs__id_txn_id(noderev
->id
))
3281 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3282 _("Copying from transactions not allowed"));
3284 noderev
->predecessor_id
= noderev
->id
;
3285 noderev
->predecessor_count
++;
3286 noderev
->copyfrom_path
= NULL
;
3287 noderev
->copyfrom_rev
= SVN_INVALID_REVNUM
;
3289 /* For the transaction root, the copyroot never changes. */
3291 node_id
= svn_fs_fs__id_node_id(noderev
->id
);
3292 copy_id
= svn_fs_fs__id_copy_id(noderev
->id
);
3293 noderev
->id
= svn_fs_fs__id_txn_create(node_id
, copy_id
, txn_id
, pool
);
3295 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, TRUE
, pool
));
3297 return SVN_NO_ERROR
;
3300 /* A structure used by get_and_increment_txn_key_body(). */
3301 struct get_and_increment_txn_key_baton
{
3307 /* Callback used in the implementation of create_txn_dir(). This gets
3308 the current base 36 value in PATH_TXN_CURRENT and increments it.
3309 It returns the original value by the baton. */
3310 static svn_error_t
*
3311 get_and_increment_txn_key_body(void *baton
, apr_pool_t
*pool
)
3313 struct get_and_increment_txn_key_baton
*cb
= baton
;
3314 const char *txn_current_filename
= path_txn_current(cb
->fs
, pool
);
3315 apr_file_t
*txn_current_file
;
3316 const char *tmp_filename
;
3317 char next_txn_id
[MAX_KEY_SIZE
+3];
3318 svn_error_t
*err
= SVN_NO_ERROR
;
3319 apr_pool_t
*iterpool
;
3323 cb
->txn_id
= apr_palloc(cb
->pool
, MAX_KEY_SIZE
);
3325 iterpool
= svn_pool_create(pool
);
3326 for (i
= 0; i
< SVN_ESTALE_RETRY_COUNT
; ++i
)
3328 svn_pool_clear(iterpool
);
3330 SVN_RETRY_ESTALE(err
, svn_io_file_open(&txn_current_file
,
3331 txn_current_filename
,
3332 APR_READ
| APR_BUFFERED
,
3333 APR_OS_DEFAULT
, iterpool
));
3335 SVN_RETRY_ESTALE(err
, svn_io_read_length_line(txn_current_file
,
3339 SVN_IGNORE_ESTALE(err
, svn_io_file_close(txn_current_file
, iterpool
));
3346 svn_pool_destroy(iterpool
);
3348 /* Increment the key and add a trailing \n to the string so the
3349 transaction-current file has a newline in it. */
3350 svn_fs_fs__next_key(cb
->txn_id
, &len
, next_txn_id
);
3351 next_txn_id
[len
] = '\n';
3353 next_txn_id
[len
] = '\0';
3355 SVN_ERR(svn_io_open_unique_file2(&txn_current_file
, &tmp_filename
,
3356 txn_current_filename
, ".tmp",
3357 svn_io_file_del_none
, pool
));
3359 SVN_ERR(svn_io_file_write_full(txn_current_file
,
3365 SVN_ERR(svn_io_file_flush_to_disk(txn_current_file
, pool
));
3367 SVN_ERR(svn_io_file_close(txn_current_file
, pool
));
3369 SVN_ERR(svn_fs_fs__move_into_place(tmp_filename
, txn_current_filename
,
3370 txn_current_filename
, pool
));
3375 /* Create a unique directory for a transaction in FS based on revision
3376 REV. Return the ID for this transaction in *ID_P. Use a sequence
3377 value in the transaction ID to prevent reuse of transaction IDs. */
3378 static svn_error_t
*
3379 create_txn_dir(const char **id_p
, svn_fs_t
*fs
, svn_revnum_t rev
,
3382 struct get_and_increment_txn_key_baton cb
;
3383 const char *txn_dir
;
3385 /* Get the current transaction sequence value, which is a base-36
3386 number, from the transaction-current file, and write an
3387 incremented value back out to the file. Place the revision
3388 number the transaction is based off into the transaction id. */
3391 SVN_ERR(with_txn_current_lock(fs
,
3392 get_and_increment_txn_key_body
,
3395 *id_p
= apr_psprintf(pool
, "%ld-%s", rev
, cb
.txn_id
);
3397 txn_dir
= svn_path_join_many(pool
,
3400 apr_pstrcat(pool
, *id_p
, PATH_EXT_TXN
, NULL
),
3403 SVN_ERR(svn_io_dir_make(txn_dir
, APR_OS_DEFAULT
, pool
));
3405 return SVN_NO_ERROR
;
3408 /* Create a unique directory for a transaction in FS based on revision
3409 REV. Return the ID for this transaction in *ID_P. This
3410 implementation is used in svn 1.4 and earlier repositories and is
3411 kept in 1.5 and greater to support the --pre-1.4-compatible and
3412 --pre-1.5-compatible repository creation options. Reused
3413 transaction IDs are possible with this implementation. */
3414 static svn_error_t
*
3415 create_txn_dir_pre_1_5(const char **id_p
, svn_fs_t
*fs
, svn_revnum_t rev
,
3419 apr_pool_t
*subpool
;
3420 const char *unique_path
, *prefix
;
3422 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
3423 prefix
= svn_path_join_many(pool
, fs
->path
, PATH_TXNS_DIR
,
3424 apr_psprintf(pool
, "%ld", rev
), NULL
);
3426 subpool
= svn_pool_create(pool
);
3427 for (i
= 1; i
<= 99999; i
++)
3431 svn_pool_clear(subpool
);
3432 unique_path
= apr_psprintf(subpool
, "%s-%u" PATH_EXT_TXN
, prefix
, i
);
3433 err
= svn_io_dir_make(unique_path
, APR_OS_DEFAULT
, subpool
);
3436 /* We succeeded. Return the basename minus the ".txn" extension. */
3437 const char *name
= svn_path_basename(unique_path
, subpool
);
3438 *id_p
= apr_pstrndup(pool
, name
,
3439 strlen(name
) - strlen(PATH_EXT_TXN
));
3440 svn_pool_destroy(subpool
);
3441 return SVN_NO_ERROR
;
3443 if (! APR_STATUS_IS_EEXIST(err
->apr_err
))
3445 svn_error_clear(err
);
3448 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED
,
3450 _("Unable to create transaction directory "
3451 "in '%s' for revision %ld"),
3456 svn_fs_fs__create_txn(svn_fs_txn_t
**txn_p
,
3461 fs_fs_data_t
*ffd
= fs
->fsap_data
;
3463 svn_fs_id_t
*root_id
;
3465 txn
= apr_pcalloc(pool
, sizeof(*txn
));
3467 /* Get the txn_id. */
3468 if (ffd
->format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
3469 SVN_ERR(create_txn_dir(&txn
->id
, fs
, rev
, pool
));
3471 SVN_ERR(create_txn_dir_pre_1_5(&txn
->id
, fs
, rev
, pool
));
3474 txn
->base_rev
= rev
;
3476 txn
->vtable
= &txn_vtable
;
3479 /* Create a new root node for this transaction. */
3480 SVN_ERR(svn_fs_fs__rev_get_root(&root_id
, fs
, rev
, pool
));
3481 SVN_ERR(create_new_txn_noderev_from_rev(fs
, txn
->id
, root_id
, pool
));
3483 /* Create an empty rev file. */
3484 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs
, txn
->id
, pool
), "",
3487 /* Create an empty rev-lock file. */
3488 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs
, txn
->id
, pool
), "",
3491 /* Create an empty changes file. */
3492 SVN_ERR(svn_io_file_create(path_txn_changes(fs
, txn
->id
, pool
), "",
3495 /* Create the next-ids file. */
3496 SVN_ERR(svn_io_file_create(path_txn_next_ids(fs
, txn
->id
, pool
), "0 0\n",
3499 return SVN_NO_ERROR
;
3502 /* Store the property list for transaction TXN_ID in PROPLIST.
3503 Perform temporary allocations in POOL. */
3504 static svn_error_t
*
3505 get_txn_proplist(apr_hash_t
*proplist
,
3510 apr_file_t
*txn_prop_file
;
3512 /* Open the transaction properties file. */
3513 SVN_ERR(svn_io_file_open(&txn_prop_file
, path_txn_props(fs
, txn_id
, pool
),
3514 APR_READ
| APR_BUFFERED
,
3515 APR_OS_DEFAULT
, pool
));
3517 /* Read in the property list. */
3518 SVN_ERR(svn_hash_read2(proplist
,
3519 svn_stream_from_aprfile(txn_prop_file
, pool
),
3520 SVN_HASH_TERMINATOR
, pool
));
3522 SVN_ERR(svn_io_file_close(txn_prop_file
, pool
));
3524 return SVN_NO_ERROR
;
3528 svn_fs_fs__change_txn_prop(svn_fs_txn_t
*txn
,
3530 const svn_string_t
*value
,
3533 apr_array_header_t
*props
= apr_array_make(pool
, 1, sizeof(svn_prop_t
));
3538 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
3540 return svn_fs_fs__change_txn_props(txn
, props
, pool
);
3544 svn_fs_fs__change_txn_props(svn_fs_txn_t
*txn
,
3545 apr_array_header_t
*props
,
3548 apr_file_t
*txn_prop_file
;
3549 apr_hash_t
*txn_prop
= apr_hash_make(pool
);
3553 err
= get_txn_proplist(txn_prop
, txn
->fs
, txn
->id
, pool
);
3554 /* Here - and here only - we need to deal with the possibility that the
3555 transaction property file doesn't yet exist. The rest of the
3556 implementation assumes that the file exists, but we're called to set the
3557 initial transaction properties as the transaction is being created. */
3558 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
)))
3559 svn_error_clear(err
);
3563 for (i
= 0; i
< props
->nelts
; i
++)
3565 svn_prop_t
*prop
= &APR_ARRAY_IDX(props
, i
, svn_prop_t
);
3567 apr_hash_set(txn_prop
, prop
->name
, APR_HASH_KEY_STRING
, prop
->value
);
3570 /* Create a new version of the file and write out the new props. */
3571 /* Open the transaction properties file. */
3572 SVN_ERR(svn_io_file_open(&txn_prop_file
,
3573 path_txn_props(txn
->fs
, txn
->id
, pool
),
3574 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
3575 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3577 SVN_ERR(svn_hash_write(txn_prop
, txn_prop_file
, pool
));
3579 SVN_ERR(svn_io_file_close(txn_prop_file
, pool
));
3581 return SVN_NO_ERROR
;
3584 /* Store the mergeinfo list for transaction TXN_ID in MINFO.
3585 Perform temporary allocations in POOL. */
3587 static svn_error_t
*
3588 get_txn_mergeinfo(apr_hash_t
*minfo
,
3593 apr_file_t
*txn_minfo_file
;
3595 /* Open the transaction mergeinfo file. */
3596 SVN_ERR(svn_io_file_open(&txn_minfo_file
,
3597 path_txn_mergeinfo(fs
, txn_id
, pool
),
3598 APR_READ
| APR_BUFFERED
,
3599 APR_OS_DEFAULT
, pool
));
3601 /* Read in the property list. */
3602 SVN_ERR(svn_hash_read2(minfo
,
3603 svn_stream_from_aprfile(txn_minfo_file
, pool
),
3604 SVN_HASH_TERMINATOR
, pool
));
3606 SVN_ERR(svn_io_file_close(txn_minfo_file
, pool
));
3608 return SVN_NO_ERROR
;
3611 /* Change mergeinfo for path NAME in TXN to VALUE. */
3614 svn_fs_fs__change_txn_mergeinfo(svn_fs_txn_t
*txn
,
3616 const svn_string_t
*value
,
3619 apr_file_t
*txn_minfo_file
;
3620 apr_hash_t
*txn_minfo
= apr_hash_make(pool
);
3623 err
= get_txn_mergeinfo(txn_minfo
, txn
->fs
, txn
->id
, pool
);
3624 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
))) /* doesn't exist yet */
3625 svn_error_clear(err
);
3629 apr_hash_set(txn_minfo
, name
, APR_HASH_KEY_STRING
, value
);
3631 /* Create a new version of the file and write out the new minfos. */
3632 /* Open the transaction minfoerties file. */
3633 SVN_ERR(svn_io_file_open(&txn_minfo_file
,
3634 path_txn_mergeinfo(txn
->fs
, txn
->id
, pool
),
3635 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
3636 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3638 SVN_ERR(svn_hash_write(txn_minfo
, txn_minfo_file
, pool
));
3640 SVN_ERR(svn_io_file_close(txn_minfo_file
, pool
));
3642 return SVN_NO_ERROR
;
3646 svn_fs_fs__get_txn(transaction_t
**txn_p
,
3652 node_revision_t
*noderev
;
3653 svn_fs_id_t
*root_id
;
3655 txn
= apr_pcalloc(pool
, sizeof(*txn
));
3656 txn
->proplist
= apr_hash_make(pool
);
3658 SVN_ERR(get_txn_proplist(txn
->proplist
, fs
, txn_id
, pool
));
3659 root_id
= svn_fs_fs__id_txn_create("0", "0", txn_id
, pool
);
3661 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, root_id
, pool
));
3663 txn
->root_id
= svn_fs_fs__id_copy(noderev
->id
, pool
);
3664 txn
->base_id
= svn_fs_fs__id_copy(noderev
->predecessor_id
, pool
);
3669 return SVN_NO_ERROR
;
3672 /* Write out the currently available next node_id NODE_ID and copy_id
3673 COPY_ID for transaction TXN_ID in filesystem FS. Perform temporary
3674 allocations in POOL. */
3675 static svn_error_t
*
3676 write_next_ids(svn_fs_t
*fs
,
3678 const char *node_id
,
3679 const char *copy_id
,
3683 svn_stream_t
*out_stream
;
3685 SVN_ERR(svn_io_file_open(&file
, path_txn_next_ids(fs
, txn_id
, pool
),
3686 APR_WRITE
| APR_TRUNCATE
,
3687 APR_OS_DEFAULT
, pool
));
3689 out_stream
= svn_stream_from_aprfile(file
, pool
);
3691 SVN_ERR(svn_stream_printf(out_stream
, pool
, "%s %s\n", node_id
, copy_id
));
3693 SVN_ERR(svn_stream_close(out_stream
));
3694 SVN_ERR(svn_io_file_close(file
, pool
));
3696 return SVN_NO_ERROR
;
3699 /* Find out what the next unique node-id and copy-id are for
3700 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
3701 and *COPY_ID. Perform all allocations in POOL. */
3702 static svn_error_t
*
3703 read_next_ids(const char **node_id
,
3704 const char **copy_id
,
3710 char buf
[MAX_KEY_SIZE
*2+3];
3712 char *str
, *last_str
;
3714 SVN_ERR(svn_io_file_open(&file
, path_txn_next_ids(fs
, txn_id
, pool
),
3715 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3717 limit
= sizeof(buf
);
3718 SVN_ERR(svn_io_read_length_line(file
, buf
, &limit
, pool
));
3720 SVN_ERR(svn_io_file_close(file
, pool
));
3722 /* Parse this into two separate strings. */
3724 str
= apr_strtok(buf
, " ", &last_str
);
3726 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3727 _("next-id file corrupt"));
3729 *node_id
= apr_pstrdup(pool
, str
);
3731 str
= apr_strtok(NULL
, " ", &last_str
);
3733 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3734 _("next-id file corrupt"));
3736 *copy_id
= apr_pstrdup(pool
, str
);
3738 return SVN_NO_ERROR
;
3741 /* Get a new and unique to this transaction node-id for transaction
3742 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
3743 Perform all allocations in POOL. */
3744 static svn_error_t
*
3745 get_new_txn_node_id(const char **node_id_p
,
3750 const char *cur_node_id
, *cur_copy_id
;
3754 /* First read in the current next-ids file. */
3755 SVN_ERR(read_next_ids(&cur_node_id
, &cur_copy_id
, fs
, txn_id
, pool
));
3757 node_id
= apr_pcalloc(pool
, strlen(cur_node_id
) + 2);
3759 len
= strlen(cur_node_id
);
3760 svn_fs_fs__next_key(cur_node_id
, &len
, node_id
);
3762 SVN_ERR(write_next_ids(fs
, txn_id
, node_id
, cur_copy_id
, pool
));
3764 *node_id_p
= apr_pstrcat(pool
, "_", cur_node_id
, NULL
);
3766 return SVN_NO_ERROR
;
3770 svn_fs_fs__create_node(const svn_fs_id_t
**id_p
,
3772 node_revision_t
*noderev
,
3773 const char *copy_id
,
3777 const char *node_id
;
3778 const svn_fs_id_t
*id
;
3780 /* Get a new node-id for this node. */
3781 SVN_ERR(get_new_txn_node_id(&node_id
, fs
, txn_id
, pool
));
3783 id
= svn_fs_fs__id_txn_create(node_id
, copy_id
, txn_id
, pool
);
3787 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, FALSE
, pool
));
3791 return SVN_NO_ERROR
;
3795 svn_fs_fs__purge_txn(svn_fs_t
*fs
,
3799 /* Remove the shared transaction object associated with this transaction. */
3800 SVN_ERR(purge_shared_txn(fs
, txn_id
, pool
));
3801 /* Remove the directory associated with this transaction. */
3802 return svn_io_remove_dir2(path_txn_dir(fs
, txn_id
, pool
), FALSE
,
3808 svn_fs_fs__abort_txn(svn_fs_txn_t
*txn
,
3813 SVN_ERR(svn_fs__check_fs(txn
->fs
));
3815 /* Clean out the directory cache. */
3816 ffd
= txn
->fs
->fsap_data
;
3817 memset(&ffd
->dir_cache_id
, 0,
3818 sizeof(svn_fs_id_t
*) * NUM_DIR_CACHE_ENTRIES
);
3820 /* Now, purge the transaction. */
3821 SVN_ERR_W(svn_fs_fs__purge_txn(txn
->fs
, txn
->id
, pool
),
3822 _("Transaction cleanup failed"));
3824 return SVN_NO_ERROR
;
3829 unparse_dir_entry(svn_node_kind_t kind
, const svn_fs_id_t
*id
,
3832 return apr_psprintf(pool
, "%s %s",
3833 (kind
== svn_node_file
) ? KIND_FILE
: KIND_DIR
,
3834 svn_fs_fs__id_unparse(id
, pool
)->data
);
3837 /* Given a hash ENTRIES of dirent structions, return a hash in
3838 *STR_ENTRIES_P, that has svn_string_t as the values in the format
3839 specified by the fs_fs directory contents file. Perform
3840 allocations in POOL. */
3841 static svn_error_t
*
3842 unparse_dir_entries(apr_hash_t
**str_entries_p
,
3843 apr_hash_t
*entries
,
3846 apr_hash_index_t
*hi
;
3848 *str_entries_p
= apr_hash_make(pool
);
3850 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
3855 svn_fs_dirent_t
*dirent
;
3856 const char *new_val
;
3858 apr_hash_this(hi
, &key
, &klen
, &val
);
3860 new_val
= unparse_dir_entry(dirent
->kind
, dirent
->id
, pool
);
3861 apr_hash_set(*str_entries_p
, key
, klen
,
3862 svn_string_create(new_val
, pool
));
3865 return SVN_NO_ERROR
;
3870 svn_fs_fs__set_entry(svn_fs_t
*fs
,
3872 node_revision_t
*parent_noderev
,
3874 const svn_fs_id_t
*id
,
3875 svn_node_kind_t kind
,
3878 fs_fs_data_t
*ffd
= fs
->fsap_data
;
3879 representation_t
*rep
= parent_noderev
->data_rep
;
3880 const char *filename
= path_txn_node_children(fs
, parent_noderev
->id
, pool
);
3883 svn_boolean_t have_cached
;
3886 if (!rep
|| !rep
->txn_id
)
3888 apr_hash_t
*entries
;
3890 /* Before we can modify the directory, we need to dump its old
3891 contents into a mutable representation file. */
3892 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries
, fs
, parent_noderev
,
3894 SVN_ERR(unparse_dir_entries(&entries
, entries
, pool
));
3895 SVN_ERR(svn_io_file_open(&file
, filename
,
3896 APR_WRITE
| APR_CREATE
| APR_BUFFERED
,
3897 APR_OS_DEFAULT
, pool
));
3898 out
= svn_stream_from_aprfile(file
, pool
);
3899 SVN_ERR(svn_hash_write2(entries
, out
, SVN_HASH_TERMINATOR
, pool
));
3901 /* Mark the node-rev's data rep as mutable. */
3902 rep
= apr_pcalloc(pool
, sizeof(*rep
));
3903 rep
->revision
= SVN_INVALID_REVNUM
;
3904 rep
->txn_id
= txn_id
;
3905 parent_noderev
->data_rep
= rep
;
3906 SVN_ERR(svn_fs_fs__put_node_revision(fs
, parent_noderev
->id
,
3907 parent_noderev
, FALSE
, pool
));
3911 /* The directory rep is already mutable, so just open it for append. */
3912 SVN_ERR(svn_io_file_open(&file
, filename
, APR_WRITE
| APR_APPEND
,
3913 APR_OS_DEFAULT
, pool
));
3914 out
= svn_stream_from_aprfile(file
, pool
);
3917 /* Calculate an index into the dir entries cache. */
3918 hid
= DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(parent_noderev
->id
));
3920 /* Make a note if we have this directory cached. */
3921 have_cached
= (ffd
->dir_cache_id
[hid
]
3922 && svn_fs_fs__id_eq(ffd
->dir_cache_id
[hid
], parent_noderev
->id
));
3924 /* Append an incremental hash entry for the entry change, and update
3925 the cached directory if necessary. */
3928 const char *val
= unparse_dir_entry(kind
, id
, pool
);
3930 SVN_ERR(svn_stream_printf(out
, pool
, "K %" APR_SIZE_T_FMT
"\n%s\n"
3931 "V %" APR_SIZE_T_FMT
"\n%s\n",
3936 svn_fs_dirent_t
*dirent
;
3938 dirent
= apr_palloc(ffd
->dir_cache_pool
[hid
], sizeof(*dirent
));
3939 dirent
->name
= apr_pstrdup(ffd
->dir_cache_pool
[hid
], name
);
3940 dirent
->kind
= kind
;
3941 dirent
->id
= svn_fs_fs__id_copy(id
, ffd
->dir_cache_pool
[hid
]);
3942 apr_hash_set(ffd
->dir_cache
[hid
], dirent
->name
, APR_HASH_KEY_STRING
,
3948 SVN_ERR(svn_stream_printf(out
, pool
, "D %" APR_SIZE_T_FMT
"\n%s\n",
3949 strlen(name
), name
));
3951 apr_hash_set(ffd
->dir_cache
[hid
], name
, APR_HASH_KEY_STRING
, NULL
);
3954 SVN_ERR(svn_io_file_close(file
, pool
));
3955 return SVN_NO_ERROR
;
3958 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
3959 string COPYFROM, into the file specified by FILE. All temporary
3960 allocations are in POOL. */
3961 static svn_error_t
*
3962 write_change_entry(apr_file_t
*file
,
3964 svn_fs_path_change_t
*change
,
3965 const char *copyfrom
,
3968 const char *idstr
, *buf
;
3969 const char *change_string
= NULL
;
3971 switch (change
->change_kind
)
3973 case svn_fs_path_change_modify
:
3974 change_string
= ACTION_MODIFY
;
3976 case svn_fs_path_change_add
:
3977 change_string
= ACTION_ADD
;
3979 case svn_fs_path_change_delete
:
3980 change_string
= ACTION_DELETE
;
3982 case svn_fs_path_change_replace
:
3983 change_string
= ACTION_REPLACE
;
3985 case svn_fs_path_change_reset
:
3986 change_string
= ACTION_RESET
;
3989 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3990 _("Invalid change type"));
3993 if (change
->node_rev_id
)
3994 idstr
= svn_fs_fs__id_unparse(change
->node_rev_id
, pool
)->data
;
3996 idstr
= ACTION_RESET
;
3998 buf
= apr_psprintf(pool
, "%s %s %s %s %s\n",
3999 idstr
, change_string
,
4000 change
->text_mod
? FLAG_TRUE
: FLAG_FALSE
,
4001 change
->prop_mod
? FLAG_TRUE
: FLAG_FALSE
,
4004 SVN_ERR(svn_io_file_write_full(file
, buf
, strlen(buf
), NULL
, pool
));
4008 SVN_ERR(svn_io_file_write_full(file
, copyfrom
, strlen(copyfrom
),
4012 SVN_ERR(svn_io_file_write_full(file
, "\n", 1, NULL
, pool
));
4014 return SVN_NO_ERROR
;
4018 svn_fs_fs__add_change(svn_fs_t
*fs
,
4021 const svn_fs_id_t
*id
,
4022 svn_fs_path_change_kind_t change_kind
,
4023 svn_boolean_t text_mod
,
4024 svn_boolean_t prop_mod
,
4025 svn_revnum_t copyfrom_rev
,
4026 const char *copyfrom_path
,
4030 const char *copyfrom
;
4031 svn_fs_path_change_t
*change
= apr_pcalloc(pool
, sizeof(*change
));
4033 SVN_ERR(svn_io_file_open(&file
, path_txn_changes(fs
, txn_id
, pool
),
4034 APR_APPEND
| APR_WRITE
| APR_CREATE
4035 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
4037 if (copyfrom_rev
!= SVN_INVALID_REVNUM
)
4038 copyfrom
= apr_psprintf(pool
, "%ld %s", copyfrom_rev
, copyfrom_path
);
4042 change
->node_rev_id
= id
;
4043 change
->change_kind
= change_kind
;
4044 change
->text_mod
= text_mod
;
4045 change
->prop_mod
= prop_mod
;
4047 SVN_ERR(write_change_entry(file
, path
, change
, copyfrom
, pool
));
4049 SVN_ERR(svn_io_file_close(file
, pool
));
4051 return SVN_NO_ERROR
;
4054 /* This baton is used by the representation writing streams. It keeps
4055 track of the checksum information as well as the total size of the
4056 representation so far. */
4057 struct rep_write_baton
4059 /* The FS we are writing to. */
4062 /* Actual file to which we are writing. */
4063 svn_stream_t
*rep_stream
;
4065 /* A stream from the delta combiner. Data written here gets
4066 deltified, then eventually written to rep_stream. */
4067 svn_stream_t
*delta_stream
;
4069 /* Where is this representation header stored. */
4070 apr_off_t rep_offset
;
4072 /* Start of the actual data. */
4073 apr_off_t delta_start
;
4075 /* How many bytes have been written to this rep already. */
4076 svn_filesize_t rep_size
;
4078 /* The node revision for which we're writing out info. */
4079 node_revision_t
*noderev
;
4081 /* Actual output file. */
4083 /* Lock 'cookie' used to unlock the output file once we've finished
4087 struct apr_md5_ctx_t md5_context
;
4091 apr_pool_t
*parent_pool
;
4094 /* Handler for the write method of the representation writable stream.
4095 BATON is a rep_write_baton, DATA is the data to write, and *LEN is
4096 the length of this data. */
4097 static svn_error_t
*
4098 rep_write_contents(void *baton
,
4102 struct rep_write_baton
*b
= baton
;
4104 apr_md5_update(&b
->md5_context
, data
, *len
);
4105 b
->rep_size
+= *len
;
4107 /* If we are writing a delta, use that stream. */
4108 if (b
->delta_stream
)
4110 SVN_ERR(svn_stream_write(b
->delta_stream
, data
, len
));
4114 SVN_ERR(svn_stream_write(b
->rep_stream
, data
, len
));
4117 return SVN_NO_ERROR
;
4120 /* Given a node-revision NODEREV in filesystem FS, return the
4121 representation in *REP to use as the base for a text representation
4122 delta. Perform temporary allocations in *POOL. */
4123 static svn_error_t
*
4124 choose_delta_base(representation_t
**rep
,
4126 node_revision_t
*noderev
,
4130 node_revision_t
*base
;
4132 /* If we have no predecessors, then use the empty stream as a
4134 if (! noderev
->predecessor_count
)
4137 return SVN_NO_ERROR
;
4140 /* Flip the rightmost '1' bit of the predecessor count to determine
4141 which file rev (counting from 0) we want to use. (To see why
4142 count & (count - 1) unsets the rightmost set bit, think about how
4143 you decrement a binary number.) */
4144 count
= noderev
->predecessor_count
;
4145 count
= count
& (count
- 1);
4147 /* Walk back a number of predecessors equal to the difference
4148 between count and the original predecessor count. (For example,
4149 if noderev has ten predecessors and we want the eighth file rev,
4150 walk back two predecessors.) */
4152 while ((count
++) < noderev
->predecessor_count
)
4153 SVN_ERR(svn_fs_fs__get_node_revision(&base
, fs
,
4154 base
->predecessor_id
, pool
));
4156 *rep
= base
->data_rep
;
4158 return SVN_NO_ERROR
;
4161 /* Get a rep_write_baton and store it in *WB_P for the representation
4162 indicated by NODEREV in filesystem FS. Perform allocations in
4163 POOL. Only appropriate for file contents, not for props or
4164 directory contents. */
4165 static svn_error_t
*
4166 rep_write_get_baton(struct rep_write_baton
**wb_p
,
4168 node_revision_t
*noderev
,
4171 struct rep_write_baton
*b
;
4173 representation_t
*base_rep
;
4174 svn_stream_t
*source
;
4176 svn_txdelta_window_handler_t wh
;
4178 fs_fs_data_t
*ffd
= fs
->fsap_data
;
4180 b
= apr_pcalloc(pool
, sizeof(*b
));
4182 apr_md5_init(&(b
->md5_context
));
4185 b
->parent_pool
= pool
;
4186 b
->pool
= svn_pool_create(pool
);
4188 b
->noderev
= noderev
;
4190 /* Open the prototype rev file and seek to its end. */
4191 SVN_ERR(get_writable_proto_rev(&file
, &b
->lockcookie
,
4192 fs
, svn_fs_fs__id_txn_id(noderev
->id
),
4196 b
->rep_stream
= svn_stream_from_aprfile(file
, b
->pool
);
4198 SVN_ERR(get_file_offset(&b
->rep_offset
, file
, b
->pool
));
4200 /* Get the base for this delta. */
4201 SVN_ERR(choose_delta_base(&base_rep
, fs
, noderev
, b
->pool
));
4202 SVN_ERR(read_representation(&source
, fs
, base_rep
, b
->pool
));
4204 /* Write out the rep header. */
4207 header
= apr_psprintf(b
->pool
, REP_DELTA
" %ld %" APR_OFF_T_FMT
" %"
4208 SVN_FILESIZE_T_FMT
"\n",
4209 base_rep
->revision
, base_rep
->offset
,
4214 header
= REP_DELTA
"\n";
4216 SVN_ERR(svn_io_file_write_full(file
, header
, strlen(header
), NULL
,
4219 /* Now determine the offset of the actual svndiff data. */
4220 SVN_ERR(get_file_offset(&b
->delta_start
, file
, b
->pool
));
4222 /* Prepare to write the svndiff data. */
4223 if (ffd
->format
>= SVN_FS_FS__MIN_SVNDIFF1_FORMAT
)
4224 svn_txdelta_to_svndiff2(&wh
, &whb
, b
->rep_stream
, 1, pool
);
4226 svn_txdelta_to_svndiff2(&wh
, &whb
, b
->rep_stream
, 0, pool
);
4228 b
->delta_stream
= svn_txdelta_target_push(wh
, whb
, source
, b
->pool
);
4232 return SVN_NO_ERROR
;
4235 /* Close handler for the representation write stream. BATON is a
4236 rep_write_baton. Writes out a new node-rev that correctly
4237 references the representation we just finished writing. */
4238 static svn_error_t
*
4239 rep_write_contents_close(void *baton
)
4241 struct rep_write_baton
*b
= baton
;
4242 representation_t
*rep
;
4245 rep
= apr_pcalloc(b
->parent_pool
, sizeof(*rep
));
4246 rep
->offset
= b
->rep_offset
;
4248 /* Close our delta stream so the last bits of svndiff are written
4250 if (b
->delta_stream
)
4251 SVN_ERR(svn_stream_close(b
->delta_stream
));
4253 /* Determine the length of the svndiff data. */
4254 SVN_ERR(get_file_offset(&offset
, b
->file
, b
->pool
));
4255 rep
->size
= offset
- b
->delta_start
;
4257 /* Fill in the rest of the representation field. */
4258 rep
->expanded_size
= b
->rep_size
;
4259 rep
->txn_id
= svn_fs_fs__id_txn_id(b
->noderev
->id
);
4260 rep
->revision
= SVN_INVALID_REVNUM
;
4262 /* Finalize the MD5 checksum. */
4263 apr_md5_final(rep
->checksum
, &b
->md5_context
);
4265 /* Write out our cosmetic end marker. */
4266 SVN_ERR(svn_stream_printf(b
->rep_stream
, b
->pool
, "ENDREP\n"));
4268 b
->noderev
->data_rep
= rep
;
4270 /* Write out the new node-rev information. */
4271 SVN_ERR(svn_fs_fs__put_node_revision(b
->fs
, b
->noderev
->id
, b
->noderev
, FALSE
,
4274 SVN_ERR(svn_io_file_close(b
->file
, b
->pool
));
4275 SVN_ERR(unlock_proto_rev(b
->fs
, rep
->txn_id
, b
->lockcookie
, b
->pool
));
4276 svn_pool_destroy(b
->pool
);
4278 return SVN_NO_ERROR
;
4281 /* Store a writable stream in *CONTENTS_P that will receive all data
4282 written and store it as the file data representation referenced by
4283 NODEREV in filesystem FS. Perform temporary allocations in
4284 POOL. Only appropriate for file data, not props or directory
4286 static svn_error_t
*
4287 set_representation(svn_stream_t
**contents_p
,
4289 node_revision_t
*noderev
,
4292 struct rep_write_baton
*wb
;
4294 if (! svn_fs_fs__id_txn_id(noderev
->id
))
4295 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4296 _("Attempted to write to non-transaction"));
4298 SVN_ERR(rep_write_get_baton(&wb
, fs
, noderev
, pool
));
4300 *contents_p
= svn_stream_create(wb
, pool
);
4301 svn_stream_set_write(*contents_p
, rep_write_contents
);
4302 svn_stream_set_close(*contents_p
, rep_write_contents_close
);
4304 return SVN_NO_ERROR
;
4308 svn_fs_fs__set_contents(svn_stream_t
**stream
,
4310 node_revision_t
*noderev
,
4313 if (noderev
->kind
!= svn_node_file
)
4314 return svn_error_create(SVN_ERR_FS_NOT_FILE
, NULL
,
4315 _("Can't set text contents of a directory"));
4317 return set_representation(stream
, fs
, noderev
, pool
);
4321 svn_fs_fs__create_successor(const svn_fs_id_t
**new_id_p
,
4323 const svn_fs_id_t
*old_idp
,
4324 node_revision_t
*new_noderev
,
4325 const char *copy_id
,
4329 const svn_fs_id_t
*id
;
4332 copy_id
= svn_fs_fs__id_copy_id(old_idp
);
4333 id
= svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp
), copy_id
,
4336 new_noderev
->id
= id
;
4338 if (! new_noderev
->copyroot_path
)
4340 new_noderev
->copyroot_path
= apr_pstrdup(pool
,
4341 new_noderev
->created_path
);
4342 new_noderev
->copyroot_rev
= svn_fs_fs__id_rev(new_noderev
->id
);
4345 SVN_ERR(svn_fs_fs__put_node_revision(fs
, new_noderev
->id
, new_noderev
, FALSE
,
4350 return SVN_NO_ERROR
;
4354 svn_fs_fs__set_proplist(svn_fs_t
*fs
,
4355 node_revision_t
*noderev
,
4356 apr_hash_t
*proplist
,
4359 const char *filename
= path_txn_node_props(fs
, noderev
->id
, pool
);
4363 /* Dump the property list to the mutable property file. */
4364 SVN_ERR(svn_io_file_open(&file
, filename
,
4365 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
4366 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
4367 out
= svn_stream_from_aprfile(file
, pool
);
4368 SVN_ERR(svn_hash_write2(proplist
, out
, SVN_HASH_TERMINATOR
, pool
));
4369 SVN_ERR(svn_io_file_close(file
, pool
));
4371 /* Mark the node-rev's prop rep as mutable, if not already done. */
4372 if (!noderev
->prop_rep
|| !noderev
->prop_rep
->txn_id
)
4374 noderev
->prop_rep
= apr_pcalloc(pool
, sizeof(*noderev
->prop_rep
));
4375 noderev
->prop_rep
->txn_id
= svn_fs_fs__id_txn_id(noderev
->id
);
4376 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, FALSE
, pool
));
4379 return SVN_NO_ERROR
;
4382 /* Read the 'current' file for filesystem FS and store the next
4383 available node id in *NODE_ID, and the next available copy id in
4384 *COPY_ID. Allocations are performed from POOL. */
4385 static svn_error_t
*
4386 get_next_revision_ids(const char **node_id
,
4387 const char **copy_id
,
4392 char *str
, *last_str
;
4394 SVN_ERR(read_current(svn_fs_fs__path_current(fs
, pool
), &buf
, pool
));
4396 str
= apr_strtok(buf
, " ", &last_str
);
4398 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4399 _("Corrupt current file"));
4401 str
= apr_strtok(NULL
, " ", &last_str
);
4403 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4404 _("Corrupt current file"));
4406 *node_id
= apr_pstrdup(pool
, str
);
4408 str
= apr_strtok(NULL
, " ", &last_str
);
4410 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4411 _("Corrupt current file"));
4413 *copy_id
= apr_pstrdup(pool
, str
);
4415 return SVN_NO_ERROR
;
4418 /* This baton is used by the stream created for write_hash_rep. */
4419 struct write_hash_baton
4421 svn_stream_t
*stream
;
4425 struct apr_md5_ctx_t md5_context
;
4428 /* The handler for the write_hash_rep stream. BATON is a
4429 write_hash_baton, DATA has the data to write and *LEN is the number
4430 of bytes to write. */
4431 static svn_error_t
*
4432 write_hash_handler(void *baton
,
4436 struct write_hash_baton
*whb
= baton
;
4438 apr_md5_update(&whb
->md5_context
, data
, *len
);
4440 SVN_ERR(svn_stream_write(whb
->stream
, data
, len
));
4443 return SVN_NO_ERROR
;
4446 /* Write out the hash HASH as a text representation to file FILE. In
4447 the process, record the total size of the dump in *SIZE, and the
4448 md5 digest in CHECKSUM. Perform temporary allocations in POOL. */
4449 static svn_error_t
*
4450 write_hash_rep(svn_filesize_t
*size
,
4451 unsigned char checksum
[APR_MD5_DIGESTSIZE
],
4456 svn_stream_t
*stream
;
4457 struct write_hash_baton
*whb
;
4459 whb
= apr_pcalloc(pool
, sizeof(*whb
));
4461 whb
->stream
= svn_stream_from_aprfile(file
, pool
);
4463 apr_md5_init(&(whb
->md5_context
));
4465 stream
= svn_stream_create(whb
, pool
);
4466 svn_stream_set_write(stream
, write_hash_handler
);
4468 SVN_ERR(svn_stream_printf(whb
->stream
, pool
, "PLAIN\n"));
4470 SVN_ERR(svn_hash_write2(hash
, stream
, SVN_HASH_TERMINATOR
, pool
));
4472 /* Store the results. */
4473 apr_md5_final(checksum
, &whb
->md5_context
);
4476 SVN_ERR(svn_stream_printf(whb
->stream
, pool
, "ENDREP\n"));
4478 return SVN_NO_ERROR
;
4481 /* Copy a node-revision specified by id ID in fileystem FS from a
4482 transaction into the permanent rev-file FILE. Return the offset of
4483 the new node-revision in *OFFSET. If this is a directory, all
4484 children are copied as well. START_NODE_ID and START_COPY_ID are
4485 the first available node and copy ids for this filesystem.
4486 Temporary allocations are from POOL. */
4487 static svn_error_t
*
4488 write_final_rev(const svn_fs_id_t
**new_id_p
,
4492 const svn_fs_id_t
*id
,
4493 const char *start_node_id
,
4494 const char *start_copy_id
,
4495 apr_hash_t
*node_origins
,
4498 node_revision_t
*noderev
;
4499 apr_off_t my_offset
;
4500 char my_node_id
[MAX_KEY_SIZE
+ 2];
4501 char my_copy_id
[MAX_KEY_SIZE
+ 2];
4502 const svn_fs_id_t
*new_id
;
4503 const char *node_id
, *copy_id
;
4504 svn_boolean_t node_id_is_new
= FALSE
;
4508 /* Check to see if this is a transaction node. */
4509 if (! svn_fs_fs__id_txn_id(id
))
4510 return SVN_NO_ERROR
;
4512 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, pool
));
4514 if (noderev
->kind
== svn_node_dir
)
4516 apr_pool_t
*subpool
;
4517 apr_hash_t
*entries
, *str_entries
;
4518 svn_fs_dirent_t
*dirent
;
4520 apr_hash_index_t
*hi
;
4522 /* This is a directory. Write out all the children first. */
4523 subpool
= svn_pool_create(pool
);
4525 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries
, fs
, noderev
, pool
));
4526 entries
= svn_fs_fs__copy_dir_entries(entries
, pool
);
4528 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
4530 svn_pool_clear(subpool
);
4531 apr_hash_this(hi
, NULL
, NULL
, &val
);
4533 SVN_ERR(write_final_rev(&new_id
, file
, rev
, fs
, dirent
->id
,
4534 start_node_id
, start_copy_id
,
4535 node_origins
, subpool
));
4536 if (new_id
&& (svn_fs_fs__id_rev(new_id
) == rev
))
4537 dirent
->id
= svn_fs_fs__id_copy(new_id
, pool
);
4539 svn_pool_destroy(subpool
);
4541 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
4543 /* Write out the contents of this directory as a text rep. */
4544 SVN_ERR(unparse_dir_entries(&str_entries
, entries
, pool
));
4546 noderev
->data_rep
->txn_id
= NULL
;
4547 noderev
->data_rep
->revision
= rev
;
4548 SVN_ERR(get_file_offset(&noderev
->data_rep
->offset
, file
, pool
));
4549 SVN_ERR(write_hash_rep(&noderev
->data_rep
->size
,
4550 noderev
->data_rep
->checksum
, file
,
4551 str_entries
, pool
));
4552 noderev
->data_rep
->expanded_size
= noderev
->data_rep
->size
;
4557 /* This is a file. We should make sure the data rep, if it
4558 exists in a "this" state, gets rewritten to our new revision
4561 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
4563 noderev
->data_rep
->txn_id
= NULL
;
4564 noderev
->data_rep
->revision
= rev
;
4568 /* Fix up the property reps. */
4569 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
4571 apr_hash_t
*proplist
;
4573 SVN_ERR(svn_fs_fs__get_proplist(&proplist
, fs
, noderev
, pool
));
4574 SVN_ERR(get_file_offset(&noderev
->prop_rep
->offset
, file
, pool
));
4575 SVN_ERR(write_hash_rep(&noderev
->prop_rep
->size
,
4576 noderev
->prop_rep
->checksum
, file
,
4579 noderev
->prop_rep
->txn_id
= NULL
;
4580 noderev
->prop_rep
->revision
= rev
;
4584 /* Convert our temporary ID into a permanent revision one. */
4585 SVN_ERR(get_file_offset(&my_offset
, file
, pool
));
4587 node_id
= svn_fs_fs__id_node_id(noderev
->id
);
4588 if (*node_id
== '_')
4590 node_id_is_new
= TRUE
;
4591 svn_fs_fs__add_keys(start_node_id
, node_id
+ 1, my_node_id
);
4594 strcpy(my_node_id
, node_id
);
4596 copy_id
= svn_fs_fs__id_copy_id(noderev
->id
);
4597 if (*copy_id
== '_')
4598 svn_fs_fs__add_keys(start_copy_id
, copy_id
+ 1, my_copy_id
);
4600 strcpy(my_copy_id
, copy_id
);
4602 if (noderev
->copyroot_rev
== SVN_INVALID_REVNUM
)
4603 noderev
->copyroot_rev
= rev
;
4605 new_id
= svn_fs_fs__id_rev_create(my_node_id
, my_copy_id
, rev
, my_offset
,
4608 noderev
->id
= new_id
;
4612 apr_pool_t
*hash_pool
= apr_hash_pool_get(node_origins
);
4613 const char *key
= apr_pstrdup(hash_pool
, my_node_id
);
4614 const svn_fs_id_t
*val
= svn_fs_fs__id_copy(new_id
, hash_pool
);
4616 apr_hash_set(node_origins
, key
, APR_HASH_KEY_STRING
, val
);
4619 /* Write out our new node-revision. */
4620 SVN_ERR(write_noderev_txn(file
, noderev
, pool
));
4622 /* Return our ID that references the revision file. */
4623 *new_id_p
= noderev
->id
;
4625 return SVN_NO_ERROR
;
4628 /* Write the changed path info from transaction TXN_ID in filesystem
4629 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
4630 in the file of the beginning of this information. Perform
4631 temporary allocations in POOL. */
4632 static svn_error_t
*
4633 write_final_changed_path_info(apr_off_t
*offset_p
,
4639 const char *copyfrom
;
4640 apr_hash_t
*changed_paths
, *copyfrom_cache
= apr_hash_make(pool
);
4642 apr_hash_index_t
*hi
;
4643 apr_pool_t
*iterpool
= svn_pool_create(pool
);
4645 SVN_ERR(get_file_offset(&offset
, file
, pool
));
4647 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths
, fs
, txn_id
,
4648 copyfrom_cache
, pool
));
4650 /* Iterate through the changed paths one at a time, and convert the
4651 temporary node-id into a permanent one for each change entry. */
4652 for (hi
= apr_hash_first(pool
, changed_paths
); hi
; hi
= apr_hash_next(hi
))
4654 node_revision_t
*noderev
;
4655 const svn_fs_id_t
*id
;
4656 svn_fs_path_change_t
*change
;
4660 svn_pool_clear(iterpool
);
4662 apr_hash_this(hi
, &key
, NULL
, &val
);
4665 id
= change
->node_rev_id
;
4667 /* If this was a delete of a mutable node, then it is OK to
4668 leave the change entry pointing to the non-existent temporary
4669 node, since it will never be used. */
4670 if ((change
->change_kind
!= svn_fs_path_change_delete
) &&
4671 (! svn_fs_fs__id_txn_id(id
)))
4673 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, iterpool
));
4675 /* noderev has the permanent node-id at this point, so we just
4676 substitute it for the temporary one. */
4677 change
->node_rev_id
= noderev
->id
;
4680 /* Find the cached copyfrom information. */
4681 copyfrom
= apr_hash_get(copyfrom_cache
, key
, APR_HASH_KEY_STRING
);
4683 /* Write out the new entry into the final rev-file. */
4684 SVN_ERR(write_change_entry(file
, key
, change
, copyfrom
, iterpool
));
4687 svn_pool_destroy(iterpool
);
4691 return SVN_NO_ERROR
;
4696 svn_fs_fs__dup_perms(const char *filename
,
4697 const char *perms_reference
,
4701 apr_status_t status
;
4703 const char *filename_apr
, *perms_reference_apr
;
4705 SVN_ERR(svn_path_cstring_from_utf8(&filename_apr
, filename
, pool
));
4706 SVN_ERR(svn_path_cstring_from_utf8(&perms_reference_apr
, perms_reference
,
4709 status
= apr_stat(&finfo
, perms_reference_apr
, APR_FINFO_PROT
, pool
);
4711 return svn_error_wrap_apr(status
, _("Can't stat '%s'"),
4712 svn_path_local_style(perms_reference
, pool
));
4713 status
= apr_file_perms_set(filename_apr
, finfo
.protection
);
4715 return svn_error_wrap_apr(status
, _("Can't chmod '%s'"),
4716 svn_path_local_style(filename
, pool
));
4718 return SVN_NO_ERROR
;
4723 svn_fs_fs__move_into_place(const char *old_filename
,
4724 const char *new_filename
,
4725 const char *perms_reference
,
4730 SVN_ERR(svn_fs_fs__dup_perms(old_filename
, perms_reference
, pool
));
4732 /* Move the file into place. */
4733 err
= svn_io_file_rename(old_filename
, new_filename
, pool
);
4734 if (err
&& APR_STATUS_IS_EXDEV(err
->apr_err
))
4738 /* Can't rename across devices; fall back to copying. */
4739 svn_error_clear(err
);
4740 SVN_ERR(svn_io_copy_file(old_filename
, new_filename
, TRUE
, pool
));
4742 /* Flush the target of the copy to disk. */
4743 SVN_ERR(svn_io_file_open(&file
, new_filename
, APR_READ
,
4744 APR_OS_DEFAULT
, pool
));
4745 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
4746 SVN_ERR(svn_io_file_close(file
, pool
));
4751 /* Linux has the unusual feature that fsync() on a file is not
4752 enough to ensure that a file's directory entries have been
4753 flushed to disk; you have to fsync the directory as well.
4754 On other operating systems, we'd only be asking for trouble
4755 by trying to open and fsync a directory. */
4756 const char *dirname
;
4759 dirname
= svn_path_dirname(new_filename
, pool
);
4760 SVN_ERR(svn_io_file_open(&file
, dirname
, APR_READ
, APR_OS_DEFAULT
,
4762 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
4763 SVN_ERR(svn_io_file_close(file
, pool
));
4770 /* Atomically update the current file to hold the specifed REV, NEXT_NODE_ID,
4771 and NEXT_COPY_ID. Perform temporary allocations in POOL. */
4772 static svn_error_t
*
4773 write_current(svn_fs_t
*fs
, svn_revnum_t rev
, const char *next_node_id
,
4774 const char *next_copy_id
, apr_pool_t
*pool
)
4777 const char *tmp_name
, *name
;
4780 /* Now we can just write out this line. */
4781 buf
= apr_psprintf(pool
, "%ld %s %s\n", rev
, next_node_id
, next_copy_id
);
4783 name
= svn_fs_fs__path_current(fs
, pool
);
4784 SVN_ERR(svn_io_open_unique_file2(&file
, &tmp_name
, name
, ".tmp",
4785 svn_io_file_del_none
, pool
));
4787 SVN_ERR(svn_io_file_write_full(file
, buf
, strlen(buf
), NULL
, pool
));
4789 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
4791 SVN_ERR(svn_io_file_close(file
, pool
));
4793 SVN_ERR(svn_fs_fs__move_into_place(tmp_name
, name
, name
, pool
));
4795 return SVN_NO_ERROR
;
4798 /* Update the current file to hold the correct next node and copy_ids
4799 from transaction TXN_ID in filesystem FS. The current revision is
4800 set to REV. Perform temporary allocations in POOL. */
4801 static svn_error_t
*
4802 write_final_current(svn_fs_t
*fs
,
4805 const char *start_node_id
,
4806 const char *start_copy_id
,
4809 const char *txn_node_id
, *txn_copy_id
;
4810 char new_node_id
[MAX_KEY_SIZE
+ 2];
4811 char new_copy_id
[MAX_KEY_SIZE
+ 2];
4813 /* To find the next available ids, we add the id that used to be in
4814 the current file, to the next ids from the transaction file. */
4815 SVN_ERR(read_next_ids(&txn_node_id
, &txn_copy_id
, fs
, txn_id
, pool
));
4817 svn_fs_fs__add_keys(start_node_id
, txn_node_id
, new_node_id
);
4818 svn_fs_fs__add_keys(start_copy_id
, txn_copy_id
, new_copy_id
);
4820 return write_current(fs
, rev
, new_node_id
, new_copy_id
, pool
);
4823 /* Verify that the user registed with FS has all the locks necessary to
4824 permit all the changes associate with TXN_NAME.
4825 The FS write lock is assumed to be held by the caller. */
4826 static svn_error_t
*
4827 verify_locks(svn_fs_t
*fs
,
4828 const char *txn_name
,
4831 apr_pool_t
*subpool
= svn_pool_create(pool
);
4832 apr_hash_t
*changes
;
4833 apr_hash_index_t
*hi
;
4834 apr_array_header_t
*changed_paths
;
4835 svn_stringbuf_t
*last_recursed
= NULL
;
4838 /* Fetch the changes for this transaction. */
4839 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes
, fs
, txn_name
, NULL
, pool
));
4841 /* Make an array of the changed paths, and sort them depth-first-ily. */
4842 changed_paths
= apr_array_make(pool
, apr_hash_count(changes
) + 1,
4843 sizeof(const char *));
4844 for (hi
= apr_hash_first(pool
, changes
); hi
; hi
= apr_hash_next(hi
))
4847 apr_hash_this(hi
, &key
, NULL
, NULL
);
4848 APR_ARRAY_PUSH(changed_paths
, const char *) = key
;
4850 qsort(changed_paths
->elts
, changed_paths
->nelts
,
4851 changed_paths
->elt_size
, svn_sort_compare_paths
);
4853 /* Now, traverse the array of changed paths, verify locks. Note
4854 that if we need to do a recursive verification a path, we'll skip
4855 over children of that path when we get to them. */
4856 for (i
= 0; i
< changed_paths
->nelts
; i
++)
4859 svn_fs_path_change_t
*change
;
4860 svn_boolean_t recurse
= TRUE
;
4862 svn_pool_clear(subpool
);
4863 path
= APR_ARRAY_IDX(changed_paths
, i
, const char *);
4865 /* If this path has already been verified as part of a recursive
4866 check of one of its parents, no need to do it again. */
4868 && svn_path_is_child(last_recursed
->data
, path
, subpool
))
4871 /* Fetch the change associated with our path. */
4872 change
= apr_hash_get(changes
, path
, APR_HASH_KEY_STRING
);
4874 /* What does it mean to succeed at lock verification for a given
4875 path? For an existing file or directory getting modified
4876 (text, props), it means we hold the lock on the file or
4877 directory. For paths being added or removed, we need to hold
4878 the locks for that path and any children of that path.
4880 WHEW! We have no reliable way to determine the node kind
4881 of deleted items, but fortunately we are going to do a
4882 recursive check on deleted paths regardless of their kind. */
4883 if (change
->change_kind
== svn_fs_path_change_modify
)
4885 SVN_ERR(svn_fs_fs__allow_locked_operation(path
, fs
, recurse
, TRUE
,
4888 /* If we just did a recursive check, remember the path we
4889 checked (so children can be skipped). */
4892 if (! last_recursed
)
4893 last_recursed
= svn_stringbuf_create(path
, pool
);
4895 svn_stringbuf_set(last_recursed
, path
);
4898 svn_pool_destroy(subpool
);
4899 return SVN_NO_ERROR
;
4902 /* Baton used for commit_body below. */
4903 struct commit_baton
{
4904 svn_revnum_t
*new_rev_p
;
4907 apr_hash_t
*node_origins
;
4910 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
4911 This implements the svn_fs_fs__with_write_lock() 'body' callback
4912 type. BATON is a 'struct commit_baton *'. */
4913 static svn_error_t
*
4914 commit_body(void *baton
, apr_pool_t
*pool
)
4916 struct commit_baton
*cb
= baton
;
4917 fs_fs_data_t
*ffd
= cb
->fs
->fsap_data
;
4918 const char *old_rev_filename
, *rev_filename
, *proto_filename
;
4919 const char *revprop_filename
, *final_revprop
;
4920 const svn_fs_id_t
*root_id
, *new_root_id
;
4921 const char *start_node_id
, *start_copy_id
;
4922 svn_revnum_t old_rev
, new_rev
;
4923 apr_file_t
*proto_file
;
4924 void *proto_file_lockcookie
;
4925 apr_off_t changed_path_offset
;
4927 apr_hash_t
*txnprops
;
4929 apr_hash_t
*target_mergeinfo
= NULL
;
4931 /* Get the current youngest revision. */
4932 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev
, cb
->fs
, pool
));
4934 /* Check to make sure this transaction is based off the most recent
4936 if (cb
->txn
->base_rev
!= old_rev
)
4937 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE
, NULL
,
4938 _("Transaction out of date"));
4940 /* Locks may have been added (or stolen) between the calling of
4941 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
4942 to re-examine every changed-path in the txn and re-verify all
4943 discovered locks. */
4944 SVN_ERR(verify_locks(cb
->fs
, cb
->txn
->id
, pool
));
4946 /* Get the next node_id and copy_id to use. */
4947 SVN_ERR(get_next_revision_ids(&start_node_id
, &start_copy_id
, cb
->fs
,
4950 /* We are going to be one better than this puny old revision. */
4951 new_rev
= old_rev
+ 1;
4953 /* Get a write handle on the proto revision file. */
4954 SVN_ERR(get_writable_proto_rev(&proto_file
, &proto_file_lockcookie
,
4955 cb
->fs
, cb
->txn
->id
, pool
));
4957 /* Write out all the node-revisions and directory contents. */
4958 root_id
= svn_fs_fs__id_txn_create("0", "0", cb
->txn
->id
, pool
);
4959 SVN_ERR(write_final_rev(&new_root_id
, proto_file
, new_rev
, cb
->fs
, root_id
,
4960 start_node_id
, start_copy_id
, cb
->node_origins
,
4963 /* Write the changed-path information. */
4964 SVN_ERR(write_final_changed_path_info(&changed_path_offset
, proto_file
,
4965 cb
->fs
, cb
->txn
->id
, pool
));
4967 /* Write the final line. */
4968 buf
= apr_psprintf(pool
, "\n%" APR_OFF_T_FMT
" %" APR_OFF_T_FMT
"\n",
4969 svn_fs_fs__id_offset(new_root_id
),
4970 changed_path_offset
);
4971 SVN_ERR(svn_io_file_write_full(proto_file
, buf
, strlen(buf
), NULL
,
4973 SVN_ERR(svn_io_file_flush_to_disk(proto_file
, pool
));
4974 SVN_ERR(svn_io_file_close(proto_file
, pool
));
4976 /* We don't unlock the prototype revision file immediately to avoid a
4977 race with another caller writing to the prototype revision file
4978 before we commit it. */
4980 /* Remove any temporary txn props representing 'flags'. */
4981 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops
, cb
->txn
, pool
));
4984 apr_array_header_t
*props
= apr_array_make(pool
, 3, sizeof(svn_prop_t
));
4988 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_OOD
,
4989 APR_HASH_KEY_STRING
))
4991 prop
.name
= SVN_FS__PROP_TXN_CHECK_OOD
;
4992 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
4995 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_LOCKS
,
4996 APR_HASH_KEY_STRING
))
4998 prop
.name
= SVN_FS__PROP_TXN_CHECK_LOCKS
;
4999 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5002 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CONTAINS_MERGEINFO
,
5003 APR_HASH_KEY_STRING
))
5005 target_mergeinfo
= apr_hash_make(pool
);
5006 SVN_ERR(get_txn_mergeinfo(target_mergeinfo
, cb
->txn
->fs
, cb
->txn
->id
,
5008 prop
.name
= SVN_FS__PROP_TXN_CONTAINS_MERGEINFO
;
5009 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5012 if (! apr_is_empty_array(props
))
5013 SVN_ERR(svn_fs_fs__change_txn_props(cb
->txn
, props
, pool
));
5016 /* Create the shard for the rev and revprop file, if we're sharding and
5017 this is the first revision of a new shard. We don't care if this
5018 fails because the shard already existed for some reason. */
5019 if (ffd
->max_files_per_dir
&& new_rev
% ffd
->max_files_per_dir
== 0)
5022 err
= svn_io_dir_make(path_rev_shard(cb
->fs
, new_rev
, pool
),
5023 APR_OS_DEFAULT
, pool
);
5024 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5025 svn_error_clear(err
);
5028 err
= svn_io_dir_make(path_revprops_shard(cb
->fs
, new_rev
, pool
),
5029 APR_OS_DEFAULT
, pool
);
5030 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5031 svn_error_clear(err
);
5036 /* Move the finished rev file into place. */
5037 old_rev_filename
= svn_fs_fs__path_rev(cb
->fs
, old_rev
, pool
);
5038 rev_filename
= svn_fs_fs__path_rev(cb
->fs
, new_rev
, pool
);
5039 proto_filename
= path_txn_proto_rev(cb
->fs
, cb
->txn
->id
, pool
);
5040 SVN_ERR(svn_fs_fs__move_into_place(proto_filename
, rev_filename
,
5041 old_rev_filename
, pool
));
5043 /* Now that we've moved the prototype revision file out of the way,
5044 we can unlock it (since further attempts to write to the file
5045 will fail as it no longer exists). We must do this so that we can
5046 remove the transaction directory later. */
5047 SVN_ERR(unlock_proto_rev(cb
->fs
, cb
->txn
->id
, proto_file_lockcookie
, pool
));
5049 /* Update commit time to ensure that svn:date revprops remain ordered. */
5050 date
.data
= svn_time_to_cstring(apr_time_now(), pool
);
5051 date
.len
= strlen(date
.data
);
5053 SVN_ERR(svn_fs_fs__change_txn_prop(cb
->txn
, SVN_PROP_REVISION_DATE
,
5056 /* Move the revprops file into place. */
5057 revprop_filename
= path_txn_props(cb
->fs
, cb
->txn
->id
, pool
);
5058 final_revprop
= path_revprops(cb
->fs
, new_rev
, pool
);
5059 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename
, final_revprop
,
5060 old_rev_filename
, pool
));
5062 /* Update the merge tracking information index. */
5063 SVN_ERR(svn_fs_mergeinfo__update_index(cb
->txn
, new_rev
, target_mergeinfo
,
5066 /* Update the 'current' file. */
5067 SVN_ERR(write_final_current(cb
->fs
, cb
->txn
->id
, new_rev
, start_node_id
,
5068 start_copy_id
, pool
));
5070 /* Remove this transaction directory. */
5071 SVN_ERR(svn_fs_fs__purge_txn(cb
->fs
, cb
->txn
->id
, pool
));
5073 *cb
->new_rev_p
= new_rev
;
5075 return SVN_NO_ERROR
;
5079 svn_fs_fs__commit(svn_revnum_t
*new_rev_p
,
5084 struct commit_baton cb
;
5085 apr_hash_t
*node_origins
= apr_hash_make(pool
);
5087 cb
.new_rev_p
= new_rev_p
;
5090 cb
.node_origins
= node_origins
;
5091 SVN_ERR(svn_fs_fs__with_write_lock(fs
, commit_body
, &cb
, pool
));
5093 /* Now that we're no longer locked, we can update the node-origins
5094 cache without blocking writers. */
5095 if (apr_hash_count(node_origins
) > 0)
5096 SVN_ERR(svn_fs__set_node_origins(fs
, node_origins
, pool
));
5098 return SVN_NO_ERROR
;
5102 svn_fs_fs__reserve_copy_id(const char **copy_id_p
,
5107 const char *cur_node_id
, *cur_copy_id
;
5111 /* First read in the current next-ids file. */
5112 SVN_ERR(read_next_ids(&cur_node_id
, &cur_copy_id
, fs
, txn_id
, pool
));
5114 copy_id
= apr_pcalloc(pool
, strlen(cur_copy_id
) + 2);
5116 len
= strlen(cur_copy_id
);
5117 svn_fs_fs__next_key(cur_copy_id
, &len
, copy_id
);
5119 SVN_ERR(write_next_ids(fs
, txn_id
, cur_node_id
, copy_id
, pool
));
5121 *copy_id_p
= apr_pstrcat(pool
, "_", cur_copy_id
, NULL
);
5123 return SVN_NO_ERROR
;
5126 /* Write out the zeroth revision for filesystem FS. */
5127 static svn_error_t
*
5128 write_revision_zero(svn_fs_t
*fs
)
5130 apr_hash_t
*proplist
;
5133 /* Write out a rev file for revision 0. */
5134 SVN_ERR(svn_io_file_create(svn_fs_fs__path_rev(fs
, 0, fs
->pool
),
5135 "PLAIN\nEND\nENDREP\n"
5140 "2d2977d1c96f487abe4a1e202dd03b4e\n"
5142 "\n\n17 107\n", fs
->pool
));
5144 /* Set a date on revision 0. */
5145 date
.data
= svn_time_to_cstring(apr_time_now(), fs
->pool
);
5146 date
.len
= strlen(date
.data
);
5147 proplist
= apr_hash_make(fs
->pool
);
5148 apr_hash_set(proplist
, SVN_PROP_REVISION_DATE
, APR_HASH_KEY_STRING
, &date
);
5149 return svn_fs_fs__set_revision_proplist(fs
, 0, proplist
, fs
->pool
);
5153 svn_fs_fs__create(svn_fs_t
*fs
,
5157 int format
= SVN_FS_FS__FORMAT_NUMBER
;
5158 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5160 fs
->path
= apr_pstrdup(pool
, path
);
5161 /* See if we had an explicitly requested pre-1.4- or pre-1.5-compatible. */
5164 if (apr_hash_get(fs
->config
, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE
,
5165 APR_HASH_KEY_STRING
))
5167 else if (apr_hash_get(fs
->config
, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE
,
5168 APR_HASH_KEY_STRING
))
5171 ffd
->format
= format
;
5173 /* Override the default linear layout if this is a new-enough format. */
5174 if (format
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
)
5175 ffd
->max_files_per_dir
= SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
;
5177 if (ffd
->max_files_per_dir
)
5179 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs
, 0, pool
),
5181 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs
, 0, pool
),
5186 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_REVS_DIR
,
5189 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
,
5194 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_TXNS_DIR
,
5198 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs
, pool
), "0 1 1\n",
5200 SVN_ERR(svn_io_file_create(path_lock(fs
, pool
), "", pool
));
5201 SVN_ERR(svn_fs_fs__set_uuid(fs
, svn_uuid_generate(pool
), pool
));
5203 SVN_ERR(write_revision_zero(fs
));
5205 /* Create the transaction-current file if the repository supports
5206 the transaction sequence file. */
5207 if (format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
5209 SVN_ERR(svn_io_file_create(path_txn_current(fs
, pool
),
5211 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs
, pool
),
5215 /* This filesystem is ready. Stamp it with a format number. */
5216 SVN_ERR(write_format(path_format(fs
, pool
),
5217 ffd
->format
, ffd
->max_files_per_dir
, pool
));
5219 /* ### this should be before the format file */
5220 SVN_ERR(svn_fs__sqlite_create_index(path
, pool
));
5221 return SVN_NO_ERROR
;
5224 /* Part of the recovery procedure. Return the largest revision *REV in
5225 filesystem FS. Use POOL for temporary allocation. */
5226 static svn_error_t
*
5227 recover_get_largest_revision(svn_fs_t
*fs
, svn_revnum_t
*rev
, apr_pool_t
*pool
)
5229 /* Discovering the largest revision in the filesystem would be an
5230 expensive operation if we did a readdir() or searched linearly,
5231 so we'll do a form of binary search. left is a revision that we
5232 know exists, right a revision that we know does not exist. */
5233 apr_pool_t
*iterpool
;
5234 svn_revnum_t left
, right
= 1;
5236 iterpool
= svn_pool_create(pool
);
5237 /* Keep doubling right, until we find a revision that doesn't exist. */
5240 svn_node_kind_t kind
;
5241 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs
, right
, iterpool
),
5243 svn_pool_clear(iterpool
);
5245 if (kind
== svn_node_none
)
5253 /* We know that left exists and right doesn't. Do a normal bsearch to find
5254 the last revision. */
5255 while (left
+ 1 < right
)
5257 svn_revnum_t probe
= left
+ ((right
- left
) / 2);
5258 svn_node_kind_t kind
;
5260 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs
, probe
, iterpool
),
5262 svn_pool_clear(iterpool
);
5264 if (kind
== svn_node_none
)
5270 svn_pool_destroy(iterpool
);
5272 /* left is now the largest revision that exists. */
5274 return SVN_NO_ERROR
;
5277 /* A baton for reading a fixed amount from an open file. For
5278 recover_find_max_ids() below. */
5279 struct recover_read_from_file_baton
5283 apr_size_t remaining
;
5286 /* A stream read handler used by recover_find_max_ids() below.
5287 Read and return at most BATON->REMAINING bytes from the stream,
5288 returning nothing after that to indicate EOF. */
5289 static svn_error_t
*
5290 read_handler_recover(void *baton
, char *buffer
, apr_size_t
*len
)
5292 struct recover_read_from_file_baton
*b
= baton
;
5293 apr_size_t bytes_to_read
= *len
;
5295 if (b
->remaining
== 0)
5297 /* Return a successful read of zero bytes to signal EOF. */
5299 return SVN_NO_ERROR
;
5302 if (bytes_to_read
> b
->remaining
)
5303 bytes_to_read
= b
->remaining
;
5304 b
->remaining
-= bytes_to_read
;
5306 return svn_io_file_read_full(b
->file
, buffer
, bytes_to_read
, len
, b
->pool
);
5309 /* Part of the recovery procedure. Read the directory noderev at offset
5310 OFFSET of file REV_FILE (the revision file of revision REV of
5311 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
5312 and copy-id of that node, if greater than the current value stored
5313 in either. Recurse into any child directories that were modified in
5316 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
5318 Perform temporary allocation in POOL. */
5319 static svn_error_t
*
5320 recover_find_max_ids(svn_fs_t
*fs
, svn_revnum_t rev
,
5321 apr_file_t
*rev_file
, apr_off_t offset
,
5322 char *max_node_id
, char *max_copy_id
,
5325 apr_hash_t
*headers
;
5327 node_revision_t noderev
;
5328 struct rep_args
*ra
;
5329 struct recover_read_from_file_baton baton
;
5330 svn_stream_t
*stream
;
5331 apr_hash_t
*entries
;
5332 apr_hash_index_t
*hi
;
5333 apr_pool_t
*iterpool
;
5335 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
5336 SVN_ERR(read_header_block(&headers
, rev_file
, pool
));
5338 /* We're going to populate a skeletal noderev - just the id and data_rep. */
5339 value
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
5340 noderev
.id
= svn_fs_fs__id_parse(value
, strlen(value
), pool
);
5342 /* Check that this is a directory. It should be. */
5343 value
= apr_hash_get(headers
, HEADER_TYPE
, APR_HASH_KEY_STRING
);
5344 if (value
== NULL
|| strcmp(value
, KIND_DIR
) != 0)
5345 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5346 _("Recovery encountered a non-directory node"));
5348 /* Get the data location. No data location indicates an empty directory. */
5349 value
= apr_hash_get(headers
, HEADER_TEXT
, APR_HASH_KEY_STRING
);
5351 return SVN_NO_ERROR
;
5352 SVN_ERR(read_rep_offsets(&noderev
.data_rep
, value
, NULL
, FALSE
, pool
));
5354 /* If the directory's data representation wasn't changed in this revision,
5355 we've already scanned the directory's contents for noderevs, so we don't
5356 need to again. This will occur if a property is changed on a directory
5357 without changing the directory's contents. */
5358 if (noderev
.data_rep
->revision
!= rev
)
5359 return SVN_NO_ERROR
;
5361 /* We could use get_dir_contents(), but this is much cheaper. It does
5362 rely on directory entries being stored as PLAIN reps, though. */
5363 offset
= noderev
.data_rep
->offset
;
5364 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
5365 SVN_ERR(read_rep_line(&ra
, rev_file
, pool
));
5367 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5368 _("Recovery encountered a deltified directory "
5371 /* Now create a stream that's allowed to read only as much data as is
5372 stored in the representation. */
5373 baton
.file
= rev_file
;
5375 baton
.remaining
= noderev
.data_rep
->expanded_size
;
5376 stream
= svn_stream_create(&baton
, pool
);
5377 svn_stream_set_read(stream
, read_handler_recover
);
5379 /* Now read the entries from that stream. */
5380 entries
= apr_hash_make(pool
);
5381 SVN_ERR(svn_hash_read2(entries
, stream
, SVN_HASH_TERMINATOR
, pool
));
5382 SVN_ERR(svn_stream_close(stream
));
5384 /* Now check each of the entries in our directory to find new node and
5385 copy ids, and recurse into new subdirectories. */
5386 iterpool
= svn_pool_create(pool
);
5387 for (hi
= apr_hash_first(NULL
, entries
); hi
; hi
= apr_hash_next(hi
))
5391 char *str
, *last_str
;
5392 svn_node_kind_t kind
;
5394 const char *node_id
, *copy_id
;
5395 apr_off_t child_dir_offset
;
5397 svn_pool_clear(iterpool
);
5399 apr_hash_this(hi
, NULL
, NULL
, &val
);
5400 str_val
= apr_pstrdup(iterpool
, *((char **)val
));
5402 str
= apr_strtok(str_val
, " ", &last_str
);
5404 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5405 _("Directory entry corrupt"));
5407 if (strcmp(str
, KIND_FILE
) == 0)
5408 kind
= svn_node_file
;
5409 else if (strcmp(str
, KIND_DIR
) == 0)
5410 kind
= svn_node_dir
;
5413 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5414 _("Directory entry corrupt"));
5417 str
= apr_strtok(NULL
, " ", &last_str
);
5419 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5420 _("Directory entry corrupt"));
5422 id
= svn_fs_fs__id_parse(str
, strlen(str
), iterpool
);
5424 if (svn_fs_fs__id_rev(id
) != rev
)
5426 /* If the node wasn't modified in this revision, we've already
5427 checked the node and copy id. */
5431 node_id
= svn_fs_fs__id_node_id(id
);
5432 copy_id
= svn_fs_fs__id_copy_id(id
);
5434 if (svn_fs_fs__key_compare(node_id
, max_node_id
) > 0)
5435 strcpy(max_node_id
, node_id
);
5436 if (svn_fs_fs__key_compare(copy_id
, max_copy_id
) > 0)
5437 strcpy(max_copy_id
, copy_id
);
5439 if (kind
== svn_node_file
)
5442 child_dir_offset
= svn_fs_fs__id_offset(id
);
5443 SVN_ERR(recover_find_max_ids(fs
, rev
, rev_file
, child_dir_offset
,
5444 max_node_id
, max_copy_id
, iterpool
));
5446 svn_pool_destroy(iterpool
);
5448 return SVN_NO_ERROR
;
5451 /* Baton used for recover_body below. */
5452 struct recover_baton
{
5454 svn_cancel_func_t cancel_func
;
5458 /* The work-horse for svn_fs_fs__recover, called with the FS
5459 write lock. This implements the svn_fs_fs__with_write_lock()
5460 'body' callback type. BATON is a 'struct recover_baton *'. */
5461 static svn_error_t
*
5462 recover_body(void *baton
, apr_pool_t
*pool
)
5464 struct recover_baton
*b
= baton
;
5465 svn_fs_t
*fs
= b
->fs
;
5466 svn_revnum_t rev
, max_rev
;
5467 apr_pool_t
*iterpool
;
5468 char max_node_id
[MAX_KEY_SIZE
] = "0", max_copy_id
[MAX_KEY_SIZE
] = "0";
5469 char next_node_id
[MAX_KEY_SIZE
], next_copy_id
[MAX_KEY_SIZE
];
5472 /* First, we need to know the largest revision in the filesystem. */
5473 SVN_ERR(recover_get_largest_revision(fs
, &max_rev
, pool
));
5475 /* Next we need to find the maximum node id and copy id in use across the
5476 filesystem. Unfortunately, the only way we can get this information
5477 is to scan all the noderevs of all the revisions and keep track as
5479 iterpool
= svn_pool_create(pool
);
5480 for (rev
= 0; rev
<= max_rev
; rev
++)
5482 apr_file_t
*rev_file
;
5483 apr_off_t root_offset
;
5485 svn_pool_clear(iterpool
);
5488 SVN_ERR(b
->cancel_func(b
->cancel_baton
));
5490 SVN_ERR(svn_io_file_open(&rev_file
,
5491 svn_fs_fs__path_rev(fs
, rev
, iterpool
),
5492 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
5494 SVN_ERR(get_root_changes_offset(&root_offset
, NULL
, rev_file
, iterpool
));
5495 SVN_ERR(recover_find_max_ids(fs
, rev
, rev_file
, root_offset
,
5496 max_node_id
, max_copy_id
, iterpool
));
5498 svn_pool_destroy(iterpool
);
5500 /* Now that we finally have the maximum revision, node-id and copy-id, we
5501 can bump the two ids to get the next of each, and store them all in a
5502 new current file. */
5503 len
= strlen(max_node_id
);
5504 svn_fs_fs__next_key(max_node_id
, &len
, next_node_id
);
5505 len
= strlen(max_copy_id
);
5506 svn_fs_fs__next_key(max_copy_id
, &len
, next_copy_id
);
5508 SVN_ERR(write_current(fs
, max_rev
, next_node_id
, next_copy_id
, pool
));
5510 return SVN_NO_ERROR
;
5513 /* This implements the fs_library_vtable_t.recover() API. */
5515 svn_fs_fs__recover(svn_fs_t
*fs
,
5516 svn_cancel_func_t cancel_func
, void *cancel_baton
,
5519 struct recover_baton b
;
5521 /* We have no way to take out an exclusive lock in FSFS, so we're
5522 restricted as to the types of recovery we can do. Luckily,
5523 we just want to recreate the current file, and we can do that just
5524 by blocking other writers. */
5526 b
.cancel_func
= cancel_func
;
5527 b
.cancel_baton
= cancel_baton
;
5528 return svn_fs_fs__with_write_lock(fs
, recover_body
, &b
, pool
);
5532 svn_fs_fs__get_uuid(svn_fs_t
*fs
,
5533 const char **uuid_p
,
5536 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5538 *uuid_p
= apr_pstrdup(pool
, ffd
->uuid
);
5539 return SVN_NO_ERROR
;
5543 svn_fs_fs__set_uuid(svn_fs_t
*fs
,
5547 apr_file_t
*uuid_file
;
5548 const char *tmp_path
;
5549 const char *uuid_path
= path_uuid(fs
, pool
);
5550 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5552 SVN_ERR(svn_io_open_unique_file2(&uuid_file
, &tmp_path
, uuid_path
,
5553 ".tmp", svn_io_file_del_none
, pool
));
5556 uuid
= svn_uuid_generate(pool
);
5558 SVN_ERR(svn_io_file_write_full(uuid_file
, uuid
, strlen(uuid
), NULL
,
5560 SVN_ERR(svn_io_file_write_full(uuid_file
, "\n", 1, NULL
, pool
));
5562 SVN_ERR(svn_io_file_close(uuid_file
, pool
));
5564 /* We use the permissions of the 'current' file, because the 'uuid'
5565 file does not exist during repository creation. */
5566 SVN_ERR(svn_fs_fs__move_into_place(tmp_path
, uuid_path
,
5567 svn_fs_fs__path_current(fs
, pool
), pool
));
5569 ffd
->uuid
= apr_pstrdup(fs
->pool
, uuid
);
5571 return SVN_NO_ERROR
;
5575 svn_fs_fs__list_transactions(apr_array_header_t
**names_p
,
5579 const char *txn_dir
;
5580 apr_hash_t
*dirents
;
5581 apr_hash_index_t
*hi
;
5582 apr_array_header_t
*names
;
5583 apr_size_t ext_len
= strlen(PATH_EXT_TXN
);
5585 names
= apr_array_make(pool
, 1, sizeof(const char *));
5587 /* Get the transactions directory. */
5588 txn_dir
= svn_path_join(fs
->path
, PATH_TXNS_DIR
, pool
);
5590 /* Now find a listing of this directory. */
5591 SVN_ERR(svn_io_get_dirents2(&dirents
, txn_dir
, pool
));
5593 /* Loop through all the entries and return anything that ends with '.txn'. */
5594 for (hi
= apr_hash_first(pool
, dirents
); hi
; hi
= apr_hash_next(hi
))
5597 const char *name
, *id
;
5600 apr_hash_this(hi
, &key
, &klen
, NULL
);
5603 /* The name must end with ".txn" to be considered a transaction. */
5604 if ((apr_size_t
) klen
<= ext_len
5605 || (strcmp(name
+ klen
- ext_len
, PATH_EXT_TXN
)) != 0)
5608 /* Truncate the ".txn" extension and store the ID. */
5609 id
= apr_pstrndup(pool
, name
, strlen(name
) - ext_len
);
5610 APR_ARRAY_PUSH(names
, const char *) = id
;
5615 return SVN_NO_ERROR
;
5619 svn_fs_fs__open_txn(svn_fs_txn_t
**txn_p
,
5625 svn_node_kind_t kind
;
5626 transaction_t
*local_txn
;
5628 /* First check to see if the directory exists. */
5629 SVN_ERR(svn_io_check_path(path_txn_dir(fs
, name
, pool
), &kind
, pool
));
5631 /* Did we find it? */
5632 if (kind
!= svn_node_dir
)
5633 return svn_error_create(SVN_ERR_FS_NO_SUCH_TRANSACTION
, NULL
,
5634 _("No such transaction"));
5636 txn
= apr_pcalloc(pool
, sizeof(*txn
));
5638 /* Read in the root node of this transaction. */
5639 txn
->id
= apr_pstrdup(pool
, name
);
5642 SVN_ERR(svn_fs_fs__get_txn(&local_txn
, fs
, name
, pool
));
5644 txn
->base_rev
= svn_fs_fs__id_rev(local_txn
->base_id
);
5646 txn
->vtable
= &txn_vtable
;
5649 return SVN_NO_ERROR
;
5653 svn_fs_fs__txn_proplist(apr_hash_t
**table_p
,
5657 apr_hash_t
*proplist
= apr_hash_make(pool
);
5658 SVN_ERR(get_txn_proplist(proplist
, txn
->fs
, txn
->id
, pool
));
5659 *table_p
= proplist
;
5661 return SVN_NO_ERROR
;
5665 svn_fs_fs__delete_node_revision(svn_fs_t
*fs
,
5666 const svn_fs_id_t
*id
,
5669 node_revision_t
*noderev
;
5671 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, pool
));
5673 /* Delete any mutable property representation. */
5674 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
5675 SVN_ERR(svn_io_remove_file(path_txn_node_props(fs
, id
, pool
), pool
));
5677 /* Delete any mutable data representation. */
5678 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
5679 && noderev
->kind
== svn_node_dir
)
5680 SVN_ERR(svn_io_remove_file(path_txn_node_children(fs
, id
, pool
), pool
));
5682 return svn_io_remove_file(path_txn_node_rev(fs
, id
, pool
), pool
);
5690 svn_fs_fs__revision_prop(svn_string_t
**value_p
,
5693 const char *propname
,
5698 SVN_ERR(svn_fs__check_fs(fs
));
5699 SVN_ERR(svn_fs_fs__revision_proplist(&table
, fs
, rev
, pool
));
5701 *value_p
= apr_hash_get(table
, propname
, APR_HASH_KEY_STRING
);
5703 return SVN_NO_ERROR
;
5707 /* Baton used for change_rev_prop_body below. */
5708 struct change_rev_prop_baton
{
5712 const svn_string_t
*value
;
5715 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
5716 write lock. This implements the svn_fs_fs__with_write_lock()
5717 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
5719 static svn_error_t
*
5720 change_rev_prop_body(void *baton
, apr_pool_t
*pool
)
5722 struct change_rev_prop_baton
*cb
= baton
;
5725 SVN_ERR(svn_fs_fs__revision_proplist(&table
, cb
->fs
, cb
->rev
, pool
));
5727 apr_hash_set(table
, cb
->name
, APR_HASH_KEY_STRING
, cb
->value
);
5729 SVN_ERR(svn_fs_fs__set_revision_proplist(cb
->fs
, cb
->rev
, table
, pool
));
5731 return SVN_NO_ERROR
;
5735 svn_fs_fs__change_rev_prop(svn_fs_t
*fs
,
5738 const svn_string_t
*value
,
5741 struct change_rev_prop_baton cb
;
5743 SVN_ERR(svn_fs__check_fs(fs
));
5750 return svn_fs_fs__with_write_lock(fs
, change_rev_prop_body
, &cb
, pool
);
5755 /*** Transactions ***/
5758 svn_fs_fs__get_txn_ids(const svn_fs_id_t
**root_id_p
,
5759 const svn_fs_id_t
**base_root_id_p
,
5761 const char *txn_name
,
5765 SVN_ERR(svn_fs_fs__get_txn(&txn
, fs
, txn_name
, pool
));
5766 *root_id_p
= txn
->root_id
;
5767 *base_root_id_p
= txn
->base_id
;
5768 return SVN_NO_ERROR
;
5772 /* Generic transaction operations. */
5775 svn_fs_fs__txn_prop(svn_string_t
**value_p
,
5777 const char *propname
,
5781 svn_fs_t
*fs
= txn
->fs
;
5783 SVN_ERR(svn_fs__check_fs(fs
));
5784 SVN_ERR(svn_fs_fs__txn_proplist(&table
, txn
, pool
));
5786 *value_p
= apr_hash_get(table
, propname
, APR_HASH_KEY_STRING
);
5788 return SVN_NO_ERROR
;
5792 svn_fs_fs__begin_txn(svn_fs_txn_t
**txn_p
,
5800 apr_array_header_t
*props
= apr_array_make(pool
, 3, sizeof(svn_prop_t
));
5802 SVN_ERR(svn_fs__check_fs(fs
));
5804 SVN_ERR(svn_fs_fs__create_txn(txn_p
, fs
, rev
, pool
));
5806 /* Put a datestamp on the newly created txn, so we always know
5807 exactly how old it is. (This will help sysadmins identify
5808 long-abandoned txns that may need to be manually removed.) When
5809 a txn is promoted to a revision, this property will be
5810 automatically overwritten with a revision datestamp. */
5811 date
.data
= svn_time_to_cstring(apr_time_now(), pool
);
5812 date
.len
= strlen(date
.data
);
5814 prop
.name
= SVN_PROP_REVISION_DATE
;
5816 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5818 /* Set temporary txn props that represent the requested 'flags'
5820 if (flags
& SVN_FS_TXN_CHECK_OOD
)
5822 prop
.name
= SVN_FS__PROP_TXN_CHECK_OOD
;
5823 prop
.value
= svn_string_create("true", pool
);
5824 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5827 if (flags
& SVN_FS_TXN_CHECK_LOCKS
)
5829 prop
.name
= SVN_FS__PROP_TXN_CHECK_LOCKS
;
5830 prop
.value
= svn_string_create("true", pool
);
5831 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5834 return svn_fs_fs__change_txn_props(*txn_p
, props
, pool
);