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_util.h"
52 #include "../libsvn_fs/fs-loader.h"
54 #include "svn_private_config.h"
56 /* An arbitrary maximum path length, so clients can't run us out of memory
57 * by giving us arbitrarily large paths. */
58 #define FSFS_MAX_PATH_LEN 4096
60 /* The default maximum number of files per directory to store in the
61 rev and revprops directory. The number below is somewhat arbitrary,
62 and can be overriden by defining the macro while compiling; the
63 figure of 1000 is reasonable for VFAT filesystems, which are by far
64 the worst performers in this area. */
65 #ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
66 #define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
69 /* Following are defines that specify the textual elements of the
70 native filesystem directories and revision files. */
72 /* Headers used to describe node-revision in the revision file. */
73 #define HEADER_ID "id"
74 #define HEADER_TYPE "type"
75 #define HEADER_COUNT "count"
76 #define HEADER_PROPS "props"
77 #define HEADER_TEXT "text"
78 #define HEADER_CPATH "cpath"
79 #define HEADER_PRED "pred"
80 #define HEADER_COPYFROM "copyfrom"
81 #define HEADER_COPYROOT "copyroot"
82 #define HEADER_FRESHTXNRT "is-fresh-txn-root"
83 #define HEADER_MINFO_HERE "minfo-here"
84 #define HEADER_MINFO_CNT "minfo-cnt"
86 /* Kinds that a change can be. */
87 #define ACTION_MODIFY "modify"
88 #define ACTION_ADD "add"
89 #define ACTION_DELETE "delete"
90 #define ACTION_REPLACE "replace"
91 #define ACTION_RESET "reset"
93 /* True and False flags. */
94 #define FLAG_TRUE "true"
95 #define FLAG_FALSE "false"
97 /* Kinds that a node-rev can be. */
98 #define KIND_FILE "file"
99 #define KIND_DIR "dir"
101 /* Kinds of representation. */
102 #define REP_PLAIN "PLAIN"
103 #define REP_DELTA "DELTA"
107 To avoid opening and closing the rev-files all the time, it would
108 probably be advantageous to keep each rev-file open for the
109 lifetime of the transaction object. I'll leave that as a later
110 optimization for now.
112 I didn't keep track of pool lifetimes at all in this code. There
113 are likely some errors because of that.
117 /* The vtable associated with an open transaction object. */
118 static txn_vtable_t txn_vtable
= {
119 svn_fs_fs__commit_txn
,
120 svn_fs_fs__abort_txn
,
122 svn_fs_fs__txn_proplist
,
123 svn_fs_fs__change_txn_prop
,
125 svn_fs_fs__change_txn_props
128 /* Pathname helper functions */
131 path_format(svn_fs_t
*fs
, apr_pool_t
*pool
)
133 return svn_path_join(fs
->path
, PATH_FORMAT
, pool
);
136 static APR_INLINE
const char *
137 path_uuid(svn_fs_t
*fs
, apr_pool_t
*pool
)
139 return svn_path_join(fs
->path
, PATH_UUID
, pool
);
143 svn_fs_fs__path_current(svn_fs_t
*fs
, apr_pool_t
*pool
)
145 return svn_path_join(fs
->path
, PATH_CURRENT
, pool
);
148 static APR_INLINE
const char *
149 path_txn_current(svn_fs_t
*fs
, apr_pool_t
*pool
)
151 return svn_path_join(fs
->path
, PATH_TXN_CURRENT
, pool
);
154 static APR_INLINE
const char *
155 path_txn_current_lock(svn_fs_t
*fs
, apr_pool_t
*pool
)
157 return svn_path_join(fs
->path
, PATH_TXN_CURRENT_LOCK
, pool
);
160 static APR_INLINE
const char *
161 path_lock(svn_fs_t
*fs
, apr_pool_t
*pool
)
163 return svn_path_join(fs
->path
, PATH_LOCK_FILE
, pool
);
167 path_rev_shard(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
169 fs_fs_data_t
*ffd
= fs
->fsap_data
;
171 assert(ffd
->max_files_per_dir
);
172 return svn_path_join_many(pool
, fs
->path
, PATH_REVS_DIR
,
173 apr_psprintf(pool
, "%ld",
174 rev
/ ffd
->max_files_per_dir
),
179 svn_fs_fs__path_rev(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
181 fs_fs_data_t
*ffd
= fs
->fsap_data
;
183 if (ffd
->max_files_per_dir
)
185 return svn_path_join(path_rev_shard(fs
, rev
, pool
),
186 apr_psprintf(pool
, "%ld", rev
),
190 return svn_path_join_many(pool
, fs
->path
, PATH_REVS_DIR
,
191 apr_psprintf(pool
, "%ld", rev
), NULL
);
195 path_revprops_shard(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
197 fs_fs_data_t
*ffd
= fs
->fsap_data
;
199 assert(ffd
->max_files_per_dir
);
200 return svn_path_join_many(pool
, fs
->path
, PATH_REVPROPS_DIR
,
201 apr_psprintf(pool
, "%ld",
202 rev
/ ffd
->max_files_per_dir
),
207 path_revprops(svn_fs_t
*fs
, svn_revnum_t rev
, apr_pool_t
*pool
)
209 fs_fs_data_t
*ffd
= fs
->fsap_data
;
211 if (ffd
->max_files_per_dir
)
213 return svn_path_join(path_revprops_shard(fs
, rev
, pool
),
214 apr_psprintf(pool
, "%ld", rev
),
218 return svn_path_join_many(pool
, fs
->path
, PATH_REVPROPS_DIR
,
219 apr_psprintf(pool
, "%ld", rev
), NULL
);
222 static APR_INLINE
const char *
223 path_txn_dir(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
225 return svn_path_join_many(pool
, fs
->path
, PATH_TXNS_DIR
,
226 apr_pstrcat(pool
, txn_id
, PATH_EXT_TXN
, NULL
),
230 static APR_INLINE
const char *
231 path_txn_changes(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
233 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_CHANGES
, pool
);
236 static APR_INLINE
const char *
237 path_txn_props(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
239 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_TXN_PROPS
, pool
);
242 static APR_INLINE
const char *
243 path_txn_next_ids(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
245 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_NEXT_IDS
, pool
);
248 static APR_INLINE
const char *
249 path_txn_proto_rev(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
251 fs_fs_data_t
*ffd
= fs
->fsap_data
;
252 if (ffd
->format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
253 return svn_path_join_many(pool
, fs
->path
, PATH_TXN_PROTOS_DIR
,
254 apr_pstrcat(pool
, txn_id
, PATH_EXT_REV
, NULL
),
257 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_REV
, pool
);
260 static APR_INLINE
const char *
261 path_txn_proto_rev_lock(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
263 fs_fs_data_t
*ffd
= fs
->fsap_data
;
264 if (ffd
->format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
265 return svn_path_join_many(pool
, fs
->path
, PATH_TXN_PROTOS_DIR
,
266 apr_pstrcat(pool
, txn_id
, PATH_EXT_REV_LOCK
,
270 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_REV_LOCK
, pool
);
274 path_txn_node_rev(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
276 const char *txn_id
= svn_fs_fs__id_txn_id(id
);
277 const char *node_id
= svn_fs_fs__id_node_id(id
);
278 const char *copy_id
= svn_fs_fs__id_copy_id(id
);
279 const char *name
= apr_psprintf(pool
, PATH_PREFIX_NODE
"%s.%s",
282 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), name
, pool
);
285 static APR_INLINE
const char *
286 path_txn_node_props(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
288 return apr_pstrcat(pool
, path_txn_node_rev(fs
, id
, pool
), PATH_EXT_PROPS
,
292 static APR_INLINE
const char *
293 path_txn_node_children(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
295 return apr_pstrcat(pool
, path_txn_node_rev(fs
, id
, pool
),
296 PATH_EXT_CHILDREN
, NULL
);
299 static APR_INLINE
const char *
300 path_node_origin(svn_fs_t
*fs
, const char *node_id
, apr_pool_t
*pool
)
302 int len
= strlen(node_id
);
303 const char *node_id_minus_last_char
=
304 (len
== 1) ? "0" : apr_pstrmemdup(pool
, node_id
, len
- 1);
305 return svn_path_join_many(pool
, fs
->path
, PATH_NODE_ORIGINS_DIR
,
306 node_id_minus_last_char
, NULL
);
310 /* Functions for working with shared transaction data. */
312 /* Return the transaction object for transaction TXN_ID from the
313 transaction list of filesystem FS (which must already be locked via the
314 txn_list_lock mutex). If the transaction does not exist in the list,
315 then create a new transaction object and return it (if CREATE_NEW is
316 true) or return NULL (otherwise). */
317 static fs_fs_shared_txn_data_t
*
318 get_shared_txn(svn_fs_t
*fs
, const char *txn_id
, svn_boolean_t create_new
)
320 fs_fs_data_t
*ffd
= fs
->fsap_data
;
321 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
322 fs_fs_shared_txn_data_t
*txn
;
324 for (txn
= ffsd
->txns
; txn
; txn
= txn
->next
)
325 if (strcmp(txn
->txn_id
, txn_id
) == 0)
328 if (txn
|| !create_new
)
331 /* Use the transaction object from the (single-object) freelist,
332 if one is available, or otherwise create a new object. */
335 txn
= ffsd
->free_txn
;
336 ffsd
->free_txn
= NULL
;
340 apr_pool_t
*subpool
= svn_pool_create(ffsd
->common_pool
);
341 txn
= apr_palloc(subpool
, sizeof(*txn
));
345 assert(strlen(txn_id
) < sizeof(txn
->txn_id
));
346 strcpy(txn
->txn_id
, txn_id
);
347 txn
->being_written
= FALSE
;
349 /* Link this transaction into the head of the list. We will typically
350 be dealing with only one active transaction at a time, so it makes
351 sense for searches through the transaction list to look at the
352 newest transactions first. */
353 txn
->next
= ffsd
->txns
;
359 /* Free the transaction object for transaction TXN_ID, and remove it
360 from the transaction list of filesystem FS (which must already be
361 locked via the txn_list_lock mutex). Do nothing if the transaction
364 free_shared_txn(svn_fs_t
*fs
, const char *txn_id
)
366 fs_fs_data_t
*ffd
= fs
->fsap_data
;
367 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
368 fs_fs_shared_txn_data_t
*txn
, *prev
= NULL
;
370 for (txn
= ffsd
->txns
; txn
; prev
= txn
, txn
= txn
->next
)
371 if (strcmp(txn
->txn_id
, txn_id
) == 0)
378 prev
->next
= txn
->next
;
380 ffsd
->txns
= txn
->next
;
382 /* As we typically will be dealing with one transaction after another,
383 we will maintain a single-object free list so that we can hopefully
384 keep reusing the same transaction object. */
386 ffsd
->free_txn
= txn
;
388 svn_pool_destroy(txn
->pool
);
392 /* Obtain a lock on the transaction list of filesystem FS, call BODY
393 with FS, BATON, and POOL, and then unlock the transaction list.
394 Return what BODY returned. */
396 with_txnlist_lock(svn_fs_t
*fs
,
397 svn_error_t
*(*body
)(svn_fs_t
*fs
,
405 fs_fs_data_t
*ffd
= fs
->fsap_data
;
406 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
407 apr_status_t apr_err
;
409 apr_err
= apr_thread_mutex_lock(ffsd
->txn_list_lock
);
411 return svn_error_wrap_apr(apr_err
, _("Can't grab FSFS txn list mutex"));
414 err
= body(fs
, baton
, pool
);
417 apr_err
= apr_thread_mutex_unlock(ffsd
->txn_list_lock
);
419 return svn_error_wrap_apr(apr_err
, _("Can't ungrab FSFS txn list mutex"));
426 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
428 get_lock_on_filesystem(const char *lock_filename
,
431 svn_error_t
*err
= svn_io_file_lock2(lock_filename
, TRUE
, FALSE
, pool
);
433 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
435 /* No lock file? No big deal; these are just empty files
436 anyway. Create it and try again. */
437 svn_error_clear(err
);
440 SVN_ERR(svn_io_file_create(lock_filename
, "", pool
));
441 SVN_ERR(svn_io_file_lock2(lock_filename
, TRUE
, FALSE
, pool
));
447 /* Obtain a write lock on the file LOCK_FILENAME (protecting with
448 LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
449 BATON and that subpool, destroy the subpool (releasing the write
450 lock) and return what BODY returned. */
452 with_some_lock(svn_error_t
*(*body
)(void *baton
,
455 const char *lock_filename
,
457 apr_thread_mutex_t
*lock_mutex
,
461 apr_pool_t
*subpool
= svn_pool_create(pool
);
467 /* POSIX fcntl locks are per-process, so we need to serialize locks
468 within the process. */
469 status
= apr_thread_mutex_lock(lock_mutex
);
471 return svn_error_wrap_apr(status
,
472 _("Can't grab FSFS mutex for '%s'"),
476 err
= get_lock_on_filesystem(lock_filename
, subpool
);
479 err
= body(baton
, subpool
);
481 svn_pool_destroy(subpool
);
484 status
= apr_thread_mutex_unlock(lock_mutex
);
486 return svn_error_wrap_apr(status
,
487 _("Can't ungrab FSFS mutex for '%s'"),
495 svn_fs_fs__with_write_lock(svn_fs_t
*fs
,
496 svn_error_t
*(*body
)(void *baton
,
502 fs_fs_data_t
*ffd
= fs
->fsap_data
;
503 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
504 apr_thread_mutex_t
*mutex
= ffsd
->fs_write_lock
;
507 return with_some_lock(body
, baton
,
515 /* Run BODY (with BATON and POOL) while the txn-current file
518 with_txn_current_lock(svn_fs_t
*fs
,
519 svn_error_t
*(*body
)(void *baton
,
525 fs_fs_data_t
*ffd
= fs
->fsap_data
;
526 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
527 apr_thread_mutex_t
*mutex
= ffsd
->txn_current_lock
;
530 return with_some_lock(body
, baton
,
531 path_txn_current_lock(fs
, pool
),
538 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
540 struct unlock_proto_rev_baton
546 /* Callback used in the implementation of unlock_proto_rev(). */
548 unlock_proto_rev_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
550 const struct unlock_proto_rev_baton
*b
= baton
;
551 const char *txn_id
= b
->txn_id
;
552 apr_file_t
*lockfile
= b
->lockcookie
;
553 fs_fs_shared_txn_data_t
*txn
= get_shared_txn(fs
, txn_id
, FALSE
);
554 apr_status_t apr_err
;
557 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
558 _("Can't unlock unknown transaction '%s'"),
560 if (!txn
->being_written
)
561 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
562 _("Can't unlock nonlocked transaction '%s'"),
565 apr_err
= apr_file_unlock(lockfile
);
567 return svn_error_wrap_apr
569 _("Can't unlock prototype revision lockfile for transaction '%s'"),
571 apr_err
= apr_file_close(lockfile
);
573 return svn_error_wrap_apr
575 _("Can't close prototype revision lockfile for transaction '%s'"),
578 txn
->being_written
= FALSE
;
583 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
584 FS using cookie LOCKCOOKIE. The original prototype revision file must
585 have been closed _before_ calling this function.
587 Perform temporary allocations in POOL. */
589 unlock_proto_rev(svn_fs_t
*fs
, const char *txn_id
, void *lockcookie
,
592 struct unlock_proto_rev_baton b
;
595 b
.lockcookie
= lockcookie
;
596 return with_txnlist_lock(fs
, unlock_proto_rev_body
, &b
, pool
);
599 /* Same as unlock_proto_rev(), but requires that the transaction list
600 lock is already held. */
602 unlock_proto_rev_list_locked(svn_fs_t
*fs
, const char *txn_id
,
606 struct unlock_proto_rev_baton b
;
609 b
.lockcookie
= lockcookie
;
610 return unlock_proto_rev_body(fs
, &b
, pool
);
613 /* A structure used by get_writable_proto_rev() and
614 get_writable_proto_rev_body(), which see. */
615 struct get_writable_proto_rev_baton
622 /* Callback used in the implementation of get_writable_proto_rev(). */
624 get_writable_proto_rev_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
626 const struct get_writable_proto_rev_baton
*b
= baton
;
627 apr_file_t
**file
= b
->file
;
628 void **lockcookie
= b
->lockcookie
;
629 const char *txn_id
= b
->txn_id
;
631 fs_fs_shared_txn_data_t
*txn
= get_shared_txn(fs
, txn_id
, TRUE
);
633 /* First, ensure that no thread in this process (including this one)
634 is currently writing to this transaction's proto-rev file. */
635 if (txn
->being_written
)
636 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN
, NULL
,
637 _("Cannot write to the prototype revision file "
638 "of transaction '%s' because a previous "
639 "representation is currently being written by "
644 /* We know that no thread in this process is writing to the proto-rev
645 file, and by extension, that no thread in this process is holding a
646 lock on the prototype revision lock file. It is therefore safe
647 for us to attempt to lock this file, to see if any other process
648 is holding a lock. */
651 apr_file_t
*lockfile
;
652 apr_status_t apr_err
;
653 const char *lockfile_path
= path_txn_proto_rev_lock(fs
, txn_id
, pool
);
655 /* Open the proto-rev lockfile, creating it if necessary, as it may
656 not exist if the transaction dates from before the lockfiles were
659 ### We'd also like to use something like svn_io_file_lock2(), but
660 that forces us to create a subpool just to be able to unlock
661 the file, which seems a waste. */
662 SVN_ERR(svn_io_file_open(&lockfile
, lockfile_path
,
663 APR_WRITE
| APR_CREATE
, APR_OS_DEFAULT
, pool
));
665 apr_err
= apr_file_lock(lockfile
,
666 APR_FLOCK_EXCLUSIVE
| APR_FLOCK_NONBLOCK
);
669 svn_error_clear(svn_io_file_close(lockfile
, pool
));
671 if (APR_STATUS_IS_EAGAIN(apr_err
))
672 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN
, NULL
,
673 _("Cannot write to the prototype revision "
674 "file of transaction '%s' because a "
675 "previous representation is currently "
676 "being written by another process"),
679 return svn_error_wrap_apr(apr_err
,
680 _("Can't get exclusive lock on file '%s'"),
681 svn_path_local_style(lockfile_path
, pool
));
684 *lockcookie
= lockfile
;
687 /* We've successfully locked the transaction; mark it as such. */
688 txn
->being_written
= TRUE
;
691 /* Now open the prototype revision file and seek to the end. */
692 err
= svn_io_file_open(file
, path_txn_proto_rev(fs
, txn_id
, pool
),
693 APR_WRITE
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
695 /* You might expect that we could dispense with the following seek
696 and achieve the same thing by opening the file using APR_APPEND.
697 Unfortunately, APR's buffered file implementation unconditionally
698 places its initial file pointer at the start of the file (even for
699 files opened with APR_APPEND), so we need this seek to reconcile
700 the APR file pointer to the OS file pointer (since we need to be
701 able to read the current file position later). */
704 apr_off_t offset
= 0;
705 err
= svn_io_file_seek(*file
, APR_END
, &offset
, 0);
710 svn_error_clear(unlock_proto_rev_list_locked(fs
, txn_id
, *lockcookie
,
718 /* Get a handle to the prototype revision file for transaction TXN_ID in
719 filesystem FS, and lock it for writing. Return FILE, a file handle
720 positioned at the end of the file, and LOCKCOOKIE, a cookie that
721 should be passed to unlock_proto_rev() to unlock the file once FILE
724 If the prototype revision file is already locked, return error
725 SVN_ERR_FS_REP_BEING_WRITTEN.
727 Perform all allocations in POOL. */
729 get_writable_proto_rev(apr_file_t
**file
,
731 svn_fs_t
*fs
, const char *txn_id
,
734 struct get_writable_proto_rev_baton b
;
737 b
.lockcookie
= lockcookie
;
740 return with_txnlist_lock(fs
, get_writable_proto_rev_body
, &b
, pool
);
743 /* Callback used in the implementation of purge_shared_txn(). */
745 purge_shared_txn_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
747 const char *txn_id
= *(const char **)baton
;
749 free_shared_txn(fs
, txn_id
);
753 /* Purge the shared data for transaction TXN_ID in filesystem FS.
754 Perform all allocations in POOL. */
756 purge_shared_txn(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
758 return with_txnlist_lock(fs
, purge_shared_txn_body
, (char **) &txn_id
, pool
);
763 /* Fetch the current offset of FILE into *OFFSET_P. */
765 get_file_offset(apr_off_t
*offset_p
, apr_file_t
*file
, apr_pool_t
*pool
)
769 /* Note that, for buffered files, one (possibly surprising) side-effect
770 of this call is to flush any unwritten data to disk. */
772 SVN_ERR(svn_io_file_seek(file
, APR_CUR
, &offset
, pool
));
779 /* Check that BUF, a buffer of text from format file PATH, contains
780 only digits, raising error SVN_ERR_BAD_VERSION_FILE_FORMAT if not.
782 Uses POOL for temporary allocation. */
784 check_format_file_buffer_numeric(const char *buf
, const char *path
,
789 for (p
= buf
; *p
; p
++)
790 if (!apr_isdigit(*p
))
791 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
792 _("Format file '%s' contains an unexpected non-digit"),
793 svn_path_local_style(path
, pool
));
798 /* Read the format number and maximum number of files per directory
799 from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
802 *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
803 will be set to zero if a linear scheme should be used.
805 Use POOL for temporary allocation. */
807 read_format(int *pformat
, int *max_files_per_dir
,
808 const char *path
, apr_pool_t
*pool
)
815 err
= svn_io_file_open(&file
, path
, APR_READ
| APR_BUFFERED
,
816 APR_OS_DEFAULT
, pool
);
817 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
819 /* Treat an absent format file as format 1. Do not try to
820 create the format file on the fly, because the repository
821 might be read-only for us, or this might be a read-only
822 operation, and the spirit of FSFS is to make no changes
823 whatseover in read-only operations. See thread starting at
824 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
826 svn_error_clear(err
);
828 *max_files_per_dir
= 0;
834 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
835 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
837 /* Return a more useful error message. */
838 svn_error_clear(err
);
839 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
840 _("Can't read first line of format file '%s'"),
841 svn_path_local_style(path
, pool
));
845 /* Check that the first line contains only digits. */
846 SVN_ERR(check_format_file_buffer_numeric(buf
, path
, pool
));
847 *pformat
= atoi(buf
);
849 /* Set the default values for anything that can be set via an option. */
850 *max_files_per_dir
= 0;
852 /* Read any options. */
856 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
857 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
859 /* No more options; that's okay. */
860 svn_error_clear(err
);
865 if (*pformat
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
&&
866 strncmp(buf
, "layout ", 7) == 0)
868 if (strcmp(buf
+7, "linear") == 0)
870 *max_files_per_dir
= 0;
874 if (strncmp(buf
+7, "sharded ", 8) == 0)
876 /* Check that the argument is numeric. */
877 SVN_ERR(check_format_file_buffer_numeric(buf
+15, path
, pool
));
878 *max_files_per_dir
= atoi(buf
+15);
883 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
884 _("'%s' contains invalid filesystem format option '%s'"),
885 svn_path_local_style(path
, pool
), buf
);
888 SVN_ERR(svn_io_file_close(file
, pool
));
893 /* Write the format number and maximum number of files per directory
894 to a new format file in PATH, possibly expecting to overwrite a
895 previously existing file.
897 Use POOL for temporary allocation. */
899 write_format(const char *path
, int format
, int max_files_per_dir
,
900 svn_boolean_t overwrite
, apr_pool_t
*pool
)
902 const char *contents
;
904 assert (1 <= format
&& format
<= SVN_FS_FS__FORMAT_NUMBER
);
905 if (format
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
)
907 if (max_files_per_dir
)
908 contents
= apr_psprintf(pool
,
910 "layout sharded %d\n",
911 format
, max_files_per_dir
);
913 contents
= apr_psprintf(pool
,
920 contents
= apr_psprintf(pool
, "%d\n", format
);
923 /* svn_io_write_version_file() does a load of magic to allow it to
924 replace version files that already exist. We only need to do
925 that when we're allowed to overwrite an existing file. */
928 /* Create the file */
929 SVN_ERR(svn_io_file_create(path
, contents
, pool
));
933 apr_file_t
*format_file
;
934 const char *path_tmp
;
936 /* Create a temporary file to write the data to */
937 SVN_ERR(svn_io_open_unique_file2(&format_file
, &path_tmp
, path
, ".tmp",
938 svn_io_file_del_none
, pool
));
940 /* ...dump out our version number string... */
941 SVN_ERR(svn_io_file_write_full(format_file
, contents
,
942 strlen(contents
), NULL
, pool
));
944 /* ...and close the file. */
945 SVN_ERR(svn_io_file_close(format_file
, pool
));
948 /* make the destination writable, but only on Windows, because
949 Windows does not let us replace read-only files. */
950 SVN_ERR(svn_io_set_file_read_write(path
, TRUE
, pool
));
953 /* rename the temp file as the real destination */
954 SVN_ERR(svn_io_file_rename(path_tmp
, path
, pool
));
957 /* And set the perms to make it read only */
958 SVN_ERR(svn_io_set_file_read_only(path
, FALSE
, pool
));
963 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
964 number is not the same as a format number supported by this
967 check_format(int format
)
969 /* We support all formats from 1-current simultaneously */
970 if (1 <= format
&& format
<= SVN_FS_FS__FORMAT_NUMBER
)
973 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT
, NULL
,
974 _("Expected FS format between '1' and '%d'; found format '%d'"),
975 SVN_FS_FS__FORMAT_NUMBER
, format
);
979 svn_fs_fs__fs_supports_mergeinfo(svn_fs_t
*fs
)
981 fs_fs_data_t
*ffd
= fs
->fsap_data
;
982 return ffd
->format
>= SVN_FS_FS__MIN_MERGEINFO_FORMAT
;
986 get_youngest(svn_revnum_t
*youngest_p
, const char *fs_path
, apr_pool_t
*pool
);
989 svn_fs_fs__open(svn_fs_t
*fs
, const char *path
, apr_pool_t
*pool
)
991 fs_fs_data_t
*ffd
= fs
->fsap_data
;
992 apr_file_t
*uuid_file
;
993 int format
, max_files_per_dir
;
994 char buf
[APR_UUID_FORMATTED_LENGTH
+ 2];
997 fs
->path
= apr_pstrdup(fs
->pool
, path
);
999 /* Read the FS format number. */
1000 SVN_ERR(read_format(&format
, &max_files_per_dir
,
1001 path_format(fs
, pool
), pool
));
1003 /* Now we've got a format number no matter what. */
1004 ffd
->format
= format
;
1005 ffd
->max_files_per_dir
= max_files_per_dir
;
1006 SVN_ERR(check_format(format
));
1008 /* Read in and cache the repository uuid. */
1009 SVN_ERR(svn_io_file_open(&uuid_file
, path_uuid(fs
, pool
),
1010 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1012 limit
= sizeof(buf
);
1013 SVN_ERR(svn_io_read_length_line(uuid_file
, buf
, &limit
, pool
));
1014 ffd
->uuid
= apr_pstrdup(fs
->pool
, buf
);
1016 SVN_ERR(svn_io_file_close(uuid_file
, pool
));
1018 SVN_ERR(get_youngest(&(ffd
->youngest_rev_cache
), path
, pool
));
1020 return SVN_NO_ERROR
;
1023 /* Wrapper around svn_io_file_create which ignores EEXIST. */
1024 static svn_error_t
*
1025 create_file_ignore_eexist(const char *file
,
1026 const char *contents
,
1029 svn_error_t
*err
= svn_io_file_create(file
, contents
, pool
);
1030 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
1032 svn_error_clear(err
);
1038 static svn_error_t
*
1039 upgrade_body(void *baton
, apr_pool_t
*pool
)
1041 svn_fs_t
*fs
= baton
;
1042 int format
, max_files_per_dir
;
1043 const char *format_path
= path_format(fs
, pool
);
1045 /* Read the FS format number and max-files-per-dir setting. */
1046 SVN_ERR(read_format(&format
, &max_files_per_dir
, format_path
, pool
));
1048 /* If we're already up-to-date, there's nothing to be done here. */
1049 if (format
== SVN_FS_FS__FORMAT_NUMBER
)
1050 return SVN_NO_ERROR
;
1052 /* If our filesystem predates the existance of the 'txn-current
1053 file', make that file and its corresponding lock file. */
1054 if (format
< SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
1056 SVN_ERR(create_file_ignore_eexist(path_txn_current(fs
, pool
), "0\n",
1058 SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs
, pool
), "",
1062 /* If our filesystem predates the existance of the 'txn-protorevs'
1063 dir, make that directory. */
1064 if (format
< SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
1066 /* We don't use path_txn_proto_rev() here because it expects
1067 we've already bumped our format. */
1068 SVN_ERR(svn_io_make_dir_recursively
1069 (svn_path_join(fs
->path
, PATH_TXN_PROTOS_DIR
, pool
), pool
));
1072 /* Bump the format file. We pass 0 for the max_files_per_dir here
1073 so we don't have to fuss with sharding directories ourselves. */
1074 SVN_ERR(write_format(format_path
, SVN_FS_FS__FORMAT_NUMBER
, 0,
1077 return SVN_NO_ERROR
;
1082 svn_fs_fs__upgrade(svn_fs_t
*fs
, apr_pool_t
*pool
)
1084 return svn_fs_fs__with_write_lock(fs
, upgrade_body
, (void *)fs
, pool
);
1088 /* SVN_ERR-like macros for dealing with recoverable errors on mutable files
1090 * Revprops, current, and txn-current files are mutable; that is, they
1091 * change as part of normal fsfs operation, in constrat to revs files, or
1092 * the format file, which are written once at create (or upgrade) time.
1093 * When more than one host writes to the same repository, we will
1094 * sometimes see these recoverable errors when accesssing these files.
1096 * These errors all relate to NFS, and thus we only use this retry code if
1097 * ESTALE is defined.
1101 * In NFS v3 and under, the server doesn't track opened files. If you
1102 * unlink(2) or rename(2) a file held open by another process *on the
1103 * same host*, that host's kernel typically renames the file to
1104 * .nfsXXXX and automatically deletes that when it's no longer open,
1105 * but this behavior is not required.
1107 * For obvious reasons, this does not work *across hosts*. No one
1108 * knows about the opened file; not the server, and not the deleting
1109 * client. So the file vanishes, and the reader gets stale NFS file
1114 * Some client implementations (at least the 2.6.18.5 kernel that ships
1115 * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
1116 * even EIO errors when trying to read these files that have been renamed
1117 * over on some other host.
1121 * Wrap opens and reads of such files with RETRY_RECOVERABLE and
1122 * closes with IGNORE_RECOVERABLE. Call these macros within a loop of
1123 * RECOVERABLE_RETRY_COUNT iterations (though, realistically, the
1124 * second try will succeed). Make sure you put a break statement
1125 * after the close, at the end of your loop. Immediately after your
1126 * loop, return err if err.
1128 * You must initialize err to SVN_NO_ERROR and filehandle to NULL, as
1129 * these macros do not.
1132 #define RECOVERABLE_RETRY_COUNT 10
1135 #define RETRY_RECOVERABLE(err, filehandle, expr) \
1137 svn_error_clear(err); \
1141 apr_status_t _e = APR_TO_OS_ERROR(err->apr_err); \
1142 if ((_e == ESTALE) || (_e == EIO) || (_e == ENOENT)) { \
1143 if (NULL != filehandle) \
1144 (void)apr_file_close(filehandle); \
1150 #define IGNORE_RECOVERABLE(err, expr) \
1152 svn_error_clear(err); \
1156 apr_status_t _e = APR_TO_OS_ERROR(err->apr_err); \
1157 if ((_e != ESTALE) && (_e != EIO)) \
1162 #define RETRY_RECOVERABLE(err, filehandle, expr) SVN_ERR(expr)
1163 #define IGNORE_RECOVERABLE(err, expr) SVN_ERR(expr)
1166 /* Long enough to hold: "<svn_revnum_t> <node id> <copy id>\0"
1167 * 19 bytes for svn_revnum_t (room for 32 or 64 bit values)
1169 * + 26 bytes for each id (these are actually unbounded, so we just
1170 * have to pick something; 2^64 is 13 bytes in base-36)
1171 * + 1 terminating null
1173 #define CURRENT_BUF_LEN 48
1175 /* Read the 'current' file FNAME and store the contents in *BUF.
1176 Allocations are performed in POOL. */
1177 static svn_error_t
*
1178 read_current(const char *fname
, char **buf
, apr_pool_t
*pool
)
1180 apr_file_t
*revision_file
= NULL
;
1183 svn_error_t
*err
= SVN_NO_ERROR
;
1184 apr_pool_t
*iterpool
;
1186 *buf
= apr_palloc(pool
, CURRENT_BUF_LEN
);
1187 iterpool
= svn_pool_create(pool
);
1188 for (i
= 0; i
< RECOVERABLE_RETRY_COUNT
; i
++)
1190 svn_pool_clear(iterpool
);
1192 RETRY_RECOVERABLE(err
, revision_file
,
1193 svn_io_file_open(&revision_file
, fname
,
1194 APR_READ
| APR_BUFFERED
,
1195 APR_OS_DEFAULT
, iterpool
));
1197 len
= CURRENT_BUF_LEN
;
1198 RETRY_RECOVERABLE(err
, revision_file
,
1199 svn_io_read_length_line(revision_file
,
1200 *buf
, &len
, iterpool
));
1201 IGNORE_RECOVERABLE(err
, svn_io_file_close(revision_file
, iterpool
));
1205 svn_pool_destroy(iterpool
);
1210 /* Find the youngest revision in a repository at path FS_PATH and
1211 return it in *YOUNGEST_P. Perform temporary allocations in
1213 static svn_error_t
*
1214 get_youngest(svn_revnum_t
*youngest_p
,
1215 const char *fs_path
,
1220 SVN_ERR(read_current(svn_path_join(fs_path
, PATH_CURRENT
, pool
),
1223 *youngest_p
= SVN_STR_TO_REV(buf
);
1225 return SVN_NO_ERROR
;
1229 svn_fs_fs__hotcopy(const char *src_path
,
1230 const char *dst_path
,
1233 const char *src_subdir
, *dst_subdir
;
1234 svn_revnum_t youngest
, rev
;
1235 apr_pool_t
*iterpool
;
1236 svn_node_kind_t kind
;
1237 int format
, max_files_per_dir
;
1239 /* Check format to be sure we know how to hotcopy this FS. */
1240 SVN_ERR(read_format(&format
, &max_files_per_dir
,
1241 svn_path_join(src_path
, PATH_FORMAT
, pool
),
1243 SVN_ERR(check_format(format
));
1245 /* Copy the current file. */
1246 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_CURRENT
, pool
));
1248 /* Copy the uuid. */
1249 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_UUID
, pool
));
1251 /* Find the youngest revision from this current file. */
1252 SVN_ERR(get_youngest(&youngest
, dst_path
, pool
));
1254 /* Copy the necessary rev files. */
1255 src_subdir
= svn_path_join(src_path
, PATH_REVS_DIR
, pool
);
1256 dst_subdir
= svn_path_join(dst_path
, PATH_REVS_DIR
, pool
);
1258 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1260 iterpool
= svn_pool_create(pool
);
1261 for (rev
= 0; rev
<= youngest
; rev
++)
1263 const char *src_subdir_shard
= src_subdir
,
1264 *dst_subdir_shard
= dst_subdir
;
1266 if (max_files_per_dir
)
1268 const char *shard
= apr_psprintf(iterpool
, "%ld",
1269 rev
/ max_files_per_dir
);
1270 src_subdir_shard
= svn_path_join(src_subdir
, shard
, iterpool
);
1271 dst_subdir_shard
= svn_path_join(dst_subdir
, shard
, iterpool
);
1273 if (rev
% max_files_per_dir
== 0)
1274 SVN_ERR(svn_io_dir_make(dst_subdir_shard
, APR_OS_DEFAULT
,
1278 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard
, dst_subdir_shard
,
1279 apr_psprintf(iterpool
, "%ld", rev
),
1281 svn_pool_clear(iterpool
);
1284 /* Copy the necessary revprop files. */
1285 src_subdir
= svn_path_join(src_path
, PATH_REVPROPS_DIR
, pool
);
1286 dst_subdir
= svn_path_join(dst_path
, PATH_REVPROPS_DIR
, pool
);
1288 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1290 for (rev
= 0; rev
<= youngest
; rev
++)
1292 const char *src_subdir_shard
= src_subdir
,
1293 *dst_subdir_shard
= dst_subdir
;
1295 svn_pool_clear(iterpool
);
1297 if (max_files_per_dir
)
1299 const char *shard
= apr_psprintf(iterpool
, "%ld",
1300 rev
/ max_files_per_dir
);
1301 src_subdir_shard
= svn_path_join(src_subdir
, shard
, iterpool
);
1302 dst_subdir_shard
= svn_path_join(dst_subdir
, shard
, iterpool
);
1304 if (rev
% max_files_per_dir
== 0)
1305 SVN_ERR(svn_io_dir_make(dst_subdir_shard
, APR_OS_DEFAULT
,
1309 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard
, dst_subdir_shard
,
1310 apr_psprintf(iterpool
, "%ld", rev
),
1314 svn_pool_destroy(iterpool
);
1316 /* Make an empty transactions directory for now. Eventually some
1317 method of copying in progress transactions will need to be
1319 dst_subdir
= svn_path_join(dst_path
, PATH_TXNS_DIR
, pool
);
1320 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1321 if (format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
1323 dst_subdir
= svn_path_join(dst_path
, PATH_TXN_PROTOS_DIR
, pool
);
1324 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1327 /* Now copy the locks tree. */
1328 src_subdir
= svn_path_join(src_path
, PATH_LOCKS_DIR
, pool
);
1329 SVN_ERR(svn_io_check_path(src_subdir
, &kind
, pool
));
1330 if (kind
== svn_node_dir
)
1331 SVN_ERR(svn_io_copy_dir_recursively(src_subdir
, dst_path
,
1332 PATH_LOCKS_DIR
, TRUE
, NULL
,
1335 /* Now copy the node-origins cache tree. */
1336 src_subdir
= svn_path_join(src_path
, PATH_NODE_ORIGINS_DIR
, pool
);
1337 SVN_ERR(svn_io_check_path(src_subdir
, &kind
, pool
));
1338 if (kind
== svn_node_dir
)
1339 SVN_ERR(svn_io_copy_dir_recursively(src_subdir
, dst_path
,
1340 PATH_NODE_ORIGINS_DIR
, TRUE
, NULL
,
1343 /* Copy the txn-current file. */
1344 if (format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
1345 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_TXN_CURRENT
, pool
));
1347 /* Hotcopied FS is complete. Stamp it with a format file. */
1348 SVN_ERR(write_format(svn_path_join(dst_path
, PATH_FORMAT
, pool
),
1349 format
, max_files_per_dir
, FALSE
, pool
));
1351 return SVN_NO_ERROR
;
1355 svn_fs_fs__youngest_rev(svn_revnum_t
*youngest_p
,
1359 fs_fs_data_t
*ffd
= fs
->fsap_data
;
1361 SVN_ERR(get_youngest(youngest_p
, fs
->path
, pool
));
1362 ffd
->youngest_rev_cache
= *youngest_p
;
1364 return SVN_NO_ERROR
;
1367 /* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN
1368 * bytes plus the stuff around them. */
1369 #define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1
1371 /* Given a revision file FILE that has been pre-positioned at the
1372 beginning of a Node-Rev header block, read in that header block and
1373 store it in the apr_hash_t HEADERS. All allocations will be from
1375 static svn_error_t
* read_header_block(apr_hash_t
**headers
,
1379 *headers
= apr_hash_make(pool
);
1383 char header_str
[MAX_HEADERS_STR_LEN
];
1384 const char *name
, *value
;
1385 apr_size_t i
= 0, header_len
;
1387 char *local_name
, *local_value
;
1389 limit
= sizeof(header_str
);
1390 SVN_ERR(svn_io_read_length_line(file
, header_str
, &limit
, pool
));
1392 if (strlen(header_str
) == 0)
1393 break; /* end of header block */
1395 header_len
= strlen(header_str
);
1397 while (header_str
[i
] != ':')
1399 if (header_str
[i
] == '\0')
1400 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1401 _("Found malformed header in "
1406 /* Create a 'name' string and point to it. */
1407 header_str
[i
] = '\0';
1410 /* Skip over the NULL byte and the space following it. */
1414 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1415 _("Found malformed header in "
1418 value
= header_str
+ i
;
1420 local_name
= apr_pstrdup(pool
, name
);
1421 local_value
= apr_pstrdup(pool
, value
);
1423 apr_hash_set(*headers
, local_name
, APR_HASH_KEY_STRING
, local_value
);
1426 return SVN_NO_ERROR
;
1429 /* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
1430 than the current youngest revision or is simply not a valid
1431 revision number, else return success.
1433 FSFS is based around the concept that commits only take effect when
1434 the number in "current" is bumped. Thus if there happens to be a rev
1435 or revprops file installed for a revision higher than the one recorded
1436 in "current" (because a commit failed between installing the rev file
1437 and bumping "current", or because an administrator rolled back the
1438 repository by resetting "current" without deleting rev files, etc), it
1439 ought to be completely ignored. This function provides the check
1440 by which callers can make that decision. */
1441 static svn_error_t
*
1442 ensure_revision_exists(svn_fs_t
*fs
,
1446 fs_fs_data_t
*ffd
= fs
->fsap_data
;
1448 if (! SVN_IS_VALID_REVNUM(rev
))
1449 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1450 _("Invalid revision number '%ld'"), rev
);
1453 /* Did the revision exist the last time we checked the current
1455 if (rev
<= ffd
->youngest_rev_cache
)
1456 return SVN_NO_ERROR
;
1458 SVN_ERR(get_youngest(&(ffd
->youngest_rev_cache
), fs
->path
, pool
));
1461 if (rev
<= ffd
->youngest_rev_cache
)
1462 return SVN_NO_ERROR
;
1464 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1465 _("No such revision %ld"), rev
);
1468 /* Open the revision file for revision REV in filesystem FS and store
1469 the newly opened file in FILE. Seek to location OFFSET before
1470 returning. Perform temporary allocations in POOL. */
1471 static svn_error_t
*
1472 open_and_seek_revision(apr_file_t
**file
,
1478 apr_file_t
*rev_file
;
1480 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
1482 SVN_ERR(svn_io_file_open(&rev_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
1483 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1485 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1489 return SVN_NO_ERROR
;
1492 /* Open the representation for a node-revision in transaction TXN_ID
1493 in filesystem FS and store the newly opened file in FILE. Seek to
1494 location OFFSET before returning. Perform temporary allocations in
1495 POOL. Only appropriate for file contents, nor props or directory
1497 static svn_error_t
*
1498 open_and_seek_transaction(apr_file_t
**file
,
1501 representation_t
*rep
,
1504 apr_file_t
*rev_file
;
1507 SVN_ERR(svn_io_file_open(&rev_file
, path_txn_proto_rev(fs
, txn_id
, pool
),
1508 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1510 offset
= rep
->offset
;
1511 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1515 return SVN_NO_ERROR
;
1518 /* Given a node-id ID, and a representation REP in filesystem FS, open
1519 the correct file and seek to the correction location. Store this
1520 file in *FILE_P. Perform any allocations in POOL. */
1521 static svn_error_t
*
1522 open_and_seek_representation(apr_file_t
**file_p
,
1524 representation_t
*rep
,
1528 return open_and_seek_revision(file_p
, fs
, rep
->revision
, rep
->offset
,
1531 return open_and_seek_transaction(file_p
, fs
, rep
->txn_id
, rep
, pool
);
1534 /* Parse the description of a representation from STRING and store it
1535 into *REP_P. If the representation is mutable (the revision is
1536 given as -1), then use TXN_ID for the representation's txn_id
1537 field. If MUTABLE_REP_TRUNCATED is true, then this representation
1538 is for property or directory contents, and no information will be
1539 expected except the "-1" revision number for a mutable
1540 representation. Allocate *REP_P in POOL. */
1541 static svn_error_t
*
1542 read_rep_offsets(representation_t
**rep_p
,
1545 svn_boolean_t mutable_rep_truncated
,
1548 representation_t
*rep
;
1549 char *str
, *last_str
;
1552 rep
= apr_pcalloc(pool
, sizeof(*rep
));
1555 str
= apr_strtok(string
, " ", &last_str
);
1557 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1558 _("Malformed text rep offset line in node-rev"));
1561 rep
->revision
= SVN_STR_TO_REV(str
);
1562 if (rep
->revision
== SVN_INVALID_REVNUM
)
1564 rep
->txn_id
= txn_id
;
1565 if (mutable_rep_truncated
)
1566 return SVN_NO_ERROR
;
1569 str
= apr_strtok(NULL
, " ", &last_str
);
1571 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1572 _("Malformed text rep offset line in node-rev"));
1574 rep
->offset
= apr_atoi64(str
);
1576 str
= apr_strtok(NULL
, " ", &last_str
);
1578 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1579 _("Malformed text rep offset line in node-rev"));
1581 rep
->size
= apr_atoi64(str
);
1583 str
= apr_strtok(NULL
, " ", &last_str
);
1585 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1586 _("Malformed text rep offset line in node-rev"));
1588 rep
->expanded_size
= apr_atoi64(str
);
1590 /* Read in the MD5 hash. */
1591 str
= apr_strtok(NULL
, " ", &last_str
);
1592 if ((str
== NULL
) || (strlen(str
) != (APR_MD5_DIGESTSIZE
* 2)))
1593 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1594 _("Malformed text rep offset line in node-rev"));
1596 /* Parse the hex MD5 hash into digest form. */
1597 for (i
= 0; i
< APR_MD5_DIGESTSIZE
; i
++)
1599 if ((! isxdigit(str
[i
* 2])) || (! isxdigit(str
[i
* 2 + 1])))
1600 return svn_error_create
1601 (SVN_ERR_FS_CORRUPT
, NULL
,
1602 _("Malformed text rep offset line in node-rev"));
1604 str
[i
* 2] = tolower(str
[i
* 2]);
1605 rep
->checksum
[i
] = (str
[i
* 2] -
1606 ((str
[i
* 2] <= '9') ? '0' : ('a' - 10))) << 4;
1608 str
[i
* 2 + 1] = tolower(str
[i
* 2 + 1]);
1609 rep
->checksum
[i
] |= (str
[i
* 2 + 1] -
1610 ((str
[i
* 2 + 1] <= '9') ? '0' : ('a' - 10)));
1613 return SVN_NO_ERROR
;
1616 /* See svn_fs_fs__get_node_revision, which wraps this and adds another
1618 static svn_error_t
*
1619 get_node_revision_body(node_revision_t
**noderev_p
,
1621 const svn_fs_id_t
*id
,
1624 apr_file_t
*revision_file
;
1625 apr_hash_t
*headers
;
1626 node_revision_t
*noderev
;
1630 if (svn_fs_fs__id_txn_id(id
))
1632 /* This is a transaction node-rev. */
1633 err
= svn_io_file_open(&revision_file
, path_txn_node_rev(fs
, id
, pool
),
1634 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
1638 /* This is a revision node-rev. */
1639 err
= open_and_seek_revision(&revision_file
, fs
,
1640 svn_fs_fs__id_rev(id
),
1641 svn_fs_fs__id_offset(id
),
1647 if (APR_STATUS_IS_ENOENT(err
->apr_err
))
1649 svn_error_clear(err
);
1650 return svn_fs_fs__err_dangling_id(fs
, id
);
1656 SVN_ERR(read_header_block(&headers
, revision_file
, pool
) );
1658 noderev
= apr_pcalloc(pool
, sizeof(*noderev
));
1660 /* Read the node-rev id. */
1661 value
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
1663 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1664 _("Missing id field in node-rev"));
1666 SVN_ERR(svn_io_file_close(revision_file
, pool
));
1668 noderev
->id
= svn_fs_fs__id_parse(value
, strlen(value
), pool
);
1670 /* Read the type. */
1671 value
= apr_hash_get(headers
, HEADER_TYPE
, APR_HASH_KEY_STRING
);
1673 if ((value
== NULL
) ||
1674 (strcmp(value
, KIND_FILE
) != 0 && strcmp(value
, KIND_DIR
)))
1675 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1676 _("Missing kind field in node-rev"));
1678 noderev
->kind
= (strcmp(value
, KIND_FILE
) == 0) ? svn_node_file
1681 /* Read the 'count' field. */
1682 value
= apr_hash_get(headers
, HEADER_COUNT
, APR_HASH_KEY_STRING
);
1683 noderev
->predecessor_count
= (value
== NULL
) ? 0 : atoi(value
);
1685 /* Get the properties location. */
1686 value
= apr_hash_get(headers
, HEADER_PROPS
, APR_HASH_KEY_STRING
);
1689 SVN_ERR(read_rep_offsets(&noderev
->prop_rep
, value
,
1690 svn_fs_fs__id_txn_id(id
), TRUE
, pool
));
1693 /* Get the data location. */
1694 value
= apr_hash_get(headers
, HEADER_TEXT
, APR_HASH_KEY_STRING
);
1697 SVN_ERR(read_rep_offsets(&noderev
->data_rep
, value
,
1698 svn_fs_fs__id_txn_id(id
),
1699 (noderev
->kind
== svn_node_dir
), pool
));
1702 /* Get the created path. */
1703 value
= apr_hash_get(headers
, HEADER_CPATH
, APR_HASH_KEY_STRING
);
1706 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1707 _("Missing cpath in node-rev"));
1711 noderev
->created_path
= apr_pstrdup(pool
, value
);
1714 /* Get the predecessor ID. */
1715 value
= apr_hash_get(headers
, HEADER_PRED
, APR_HASH_KEY_STRING
);
1717 noderev
->predecessor_id
= svn_fs_fs__id_parse(value
, strlen(value
),
1720 /* Get the copyroot. */
1721 value
= apr_hash_get(headers
, HEADER_COPYROOT
, APR_HASH_KEY_STRING
);
1724 noderev
->copyroot_path
= apr_pstrdup(pool
, noderev
->created_path
);
1725 noderev
->copyroot_rev
= svn_fs_fs__id_rev(noderev
->id
);
1729 char *str
, *last_str
;
1731 str
= apr_strtok(value
, " ", &last_str
);
1733 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1734 _("Malformed copyroot line in node-rev"));
1736 noderev
->copyroot_rev
= atoi(str
);
1738 if (last_str
== NULL
)
1739 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1740 _("Malformed copyroot line in node-rev"));
1741 noderev
->copyroot_path
= apr_pstrdup(pool
, last_str
);
1744 /* Get the copyfrom. */
1745 value
= apr_hash_get(headers
, HEADER_COPYFROM
, APR_HASH_KEY_STRING
);
1748 noderev
->copyfrom_path
= NULL
;
1749 noderev
->copyfrom_rev
= SVN_INVALID_REVNUM
;
1753 char *str
, *last_str
;
1755 str
= apr_strtok(value
, " ", &last_str
);
1757 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1758 _("Malformed copyfrom line in node-rev"));
1760 noderev
->copyfrom_rev
= atoi(str
);
1762 if (last_str
== NULL
)
1763 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1764 _("Malformed copyfrom line in node-rev"));
1765 noderev
->copyfrom_path
= apr_pstrdup(pool
, last_str
);
1768 /* Get whether this is a fresh txn root. */
1769 value
= apr_hash_get(headers
, HEADER_FRESHTXNRT
, APR_HASH_KEY_STRING
);
1770 noderev
->is_fresh_txn_root
= (value
!= NULL
);
1772 /* Get the mergeinfo count. */
1773 value
= apr_hash_get(headers
, HEADER_MINFO_CNT
, APR_HASH_KEY_STRING
);
1774 noderev
->mergeinfo_count
= (value
== NULL
) ? 0 : apr_atoi64(value
);
1776 /* Get whether *this* node has mergeinfo. */
1777 value
= apr_hash_get(headers
, HEADER_MINFO_HERE
, APR_HASH_KEY_STRING
);
1778 noderev
->has_mergeinfo
= (value
!= NULL
);
1780 *noderev_p
= noderev
;
1782 return SVN_NO_ERROR
;
1786 svn_fs_fs__get_node_revision(node_revision_t
**noderev_p
,
1788 const svn_fs_id_t
*id
,
1791 svn_error_t
*err
= get_node_revision_body(noderev_p
, fs
, id
, pool
);
1792 if (err
&& err
->apr_err
== SVN_ERR_FS_CORRUPT
)
1794 svn_string_t
*id_string
= svn_fs_fs__id_unparse(id
, pool
);
1795 return svn_error_createf(SVN_ERR_FS_CORRUPT
, err
,
1796 "Corrupt node-revision '%s'",
1803 /* Return a formatted string that represents the location of
1804 representation REP. If MUTABLE_REP_TRUNCATED is given, the rep is
1805 for props or dir contents, and only a "-1" revision number will be
1806 given for a mutable rep. Perform the allocation from POOL. */
1808 representation_string(representation_t
*rep
,
1809 svn_boolean_t mutable_rep_truncated
, apr_pool_t
*pool
)
1811 if (rep
->txn_id
&& mutable_rep_truncated
)
1814 return apr_psprintf(pool
, "%ld %" APR_OFF_T_FMT
" %" SVN_FILESIZE_T_FMT
1815 " %" SVN_FILESIZE_T_FMT
" %s",
1816 rep
->revision
, rep
->offset
, rep
->size
,
1818 svn_md5_digest_to_cstring_display(rep
->checksum
,
1822 /* Write the node-revision NODEREV into the file FILE. Only write
1823 mergeinfo-related metadata if INCLUDE_MERGEINFO is true. Temporary
1824 allocations are from POOL. */
1825 static svn_error_t
*
1826 write_noderev_txn(apr_file_t
*file
,
1827 node_revision_t
*noderev
,
1828 svn_boolean_t include_mergeinfo
,
1831 svn_stream_t
*outfile
;
1833 outfile
= svn_stream_from_aprfile(file
, pool
);
1835 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_ID
": %s\n",
1836 svn_fs_fs__id_unparse(noderev
->id
,
1839 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_TYPE
": %s\n",
1840 (noderev
->kind
== svn_node_file
) ?
1841 KIND_FILE
: KIND_DIR
));
1843 if (noderev
->predecessor_id
)
1844 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_PRED
": %s\n",
1845 svn_fs_fs__id_unparse(noderev
->predecessor_id
,
1848 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COUNT
": %d\n",
1849 noderev
->predecessor_count
));
1851 if (noderev
->data_rep
)
1852 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_TEXT
": %s\n",
1853 representation_string(noderev
->data_rep
,
1858 if (noderev
->prop_rep
)
1859 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_PROPS
": %s\n",
1860 representation_string(noderev
->prop_rep
, TRUE
,
1863 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_CPATH
": %s\n",
1864 noderev
->created_path
));
1866 if (noderev
->copyfrom_path
)
1867 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COPYFROM
": %ld"
1869 noderev
->copyfrom_rev
,
1870 noderev
->copyfrom_path
));
1872 if ((noderev
->copyroot_rev
!= svn_fs_fs__id_rev(noderev
->id
)) ||
1873 (strcmp(noderev
->copyroot_path
, noderev
->created_path
) != 0))
1874 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COPYROOT
": %ld"
1876 noderev
->copyroot_rev
,
1877 noderev
->copyroot_path
));
1879 if (noderev
->is_fresh_txn_root
)
1880 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_FRESHTXNRT
": y\n"));
1882 if (include_mergeinfo
)
1884 if (noderev
->mergeinfo_count
> 0)
1885 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_MINFO_CNT
": %"
1886 APR_INT64_T_FMT
"\n",
1887 noderev
->mergeinfo_count
));
1889 if (noderev
->has_mergeinfo
)
1890 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_MINFO_HERE
": y\n"));
1893 SVN_ERR(svn_stream_printf(outfile
, pool
, "\n"));
1895 return SVN_NO_ERROR
;
1899 svn_fs_fs__put_node_revision(svn_fs_t
*fs
,
1900 const svn_fs_id_t
*id
,
1901 node_revision_t
*noderev
,
1902 svn_boolean_t fresh_txn_root
,
1905 apr_file_t
*noderev_file
;
1906 const char *txn_id
= svn_fs_fs__id_txn_id(id
);
1908 noderev
->is_fresh_txn_root
= fresh_txn_root
;
1911 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1912 _("Attempted to write to non-transaction"));
1914 SVN_ERR(svn_io_file_open(&noderev_file
, path_txn_node_rev(fs
, id
, pool
),
1915 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
1916 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1918 SVN_ERR(write_noderev_txn(noderev_file
, noderev
,
1919 svn_fs_fs__fs_supports_mergeinfo(fs
),
1922 SVN_ERR(svn_io_file_close(noderev_file
, pool
));
1924 return SVN_NO_ERROR
;
1928 /* This structure is used to hold the information associated with a
1932 svn_boolean_t is_delta
;
1933 svn_boolean_t is_delta_vs_empty
;
1935 svn_revnum_t base_revision
;
1936 apr_off_t base_offset
;
1937 apr_size_t base_length
;
1940 /* Read the next line from file FILE and parse it as a text
1941 representation entry. Return the parsed entry in *REP_ARGS_P.
1942 Perform all allocations in POOL. */
1943 static svn_error_t
*
1944 read_rep_line(struct rep_args
**rep_args_p
,
1950 struct rep_args
*rep_args
;
1951 char *str
, *last_str
;
1953 limit
= sizeof(buffer
);
1954 SVN_ERR(svn_io_read_length_line(file
, buffer
, &limit
, pool
));
1956 rep_args
= apr_pcalloc(pool
, sizeof(*rep_args
));
1957 rep_args
->is_delta
= FALSE
;
1959 if (strcmp(buffer
, REP_PLAIN
) == 0)
1961 *rep_args_p
= rep_args
;
1962 return SVN_NO_ERROR
;
1965 if (strcmp(buffer
, REP_DELTA
) == 0)
1967 /* This is a delta against the empty stream. */
1968 rep_args
->is_delta
= TRUE
;
1969 rep_args
->is_delta_vs_empty
= TRUE
;
1970 *rep_args_p
= rep_args
;
1971 return SVN_NO_ERROR
;
1974 rep_args
->is_delta
= TRUE
;
1975 rep_args
->is_delta_vs_empty
= FALSE
;
1977 /* We have hopefully a DELTA vs. a non-empty base revision. */
1978 str
= apr_strtok(buffer
, " ", &last_str
);
1979 if (! str
|| (strcmp(str
, REP_DELTA
) != 0)) goto err
;
1981 str
= apr_strtok(NULL
, " ", &last_str
);
1982 if (! str
) goto err
;
1983 rep_args
->base_revision
= atol(str
);
1985 str
= apr_strtok(NULL
, " ", &last_str
);
1986 if (! str
) goto err
;
1987 rep_args
->base_offset
= (apr_off_t
) apr_atoi64(str
);
1989 str
= apr_strtok(NULL
, " ", &last_str
);
1990 if (! str
) goto err
;
1991 rep_args
->base_length
= (apr_size_t
) apr_atoi64(str
);
1993 *rep_args_p
= rep_args
;
1994 return SVN_NO_ERROR
;
1997 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1998 _("Malformed representation header"));
2001 /* Given a revision file REV_FILE, find the Node-ID of the header
2002 located at OFFSET and store it in *ID_P. Allocate temporary
2003 variables from POOL. */
2004 static svn_error_t
*
2005 get_fs_id_at_offset(svn_fs_id_t
**id_p
,
2006 apr_file_t
*rev_file
,
2011 apr_hash_t
*headers
;
2012 const char *node_id_str
;
2014 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
2016 SVN_ERR(read_header_block(&headers
, rev_file
, pool
));
2018 node_id_str
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
2020 if (node_id_str
== NULL
)
2021 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2022 _("Missing node-id in node-rev"));
2024 id
= svn_fs_fs__id_parse(node_id_str
, strlen(node_id_str
), pool
);
2027 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2028 _("Corrupt node-id in node-rev"));
2032 return SVN_NO_ERROR
;
2036 /* Given an open revision file REV_FILE, locate the trailer that
2037 specifies the offset to the root node-id and to the changed path
2038 information. Store the root node offset in *ROOT_OFFSET and the
2039 changed path offset in *CHANGES_OFFSET. If either of these
2040 pointers is NULL, do nothing with it. Allocate temporary variables
2042 static svn_error_t
*
2043 get_root_changes_offset(apr_off_t
*root_offset
,
2044 apr_off_t
*changes_offset
,
2045 apr_file_t
*rev_file
,
2053 /* We will assume that the last line containing the two offsets
2054 will never be longer than 64 characters. */
2056 SVN_ERR(svn_io_file_seek(rev_file
, APR_END
, &offset
, pool
));
2058 offset
-= sizeof(buf
);
2059 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
2061 /* Read in this last block, from which we will identify the last line. */
2063 SVN_ERR(svn_io_file_read(rev_file
, buf
, &len
, pool
));
2065 /* This cast should be safe since the maximum amount read, 64, will
2066 never be bigger than the size of an int. */
2067 num_bytes
= (int) len
;
2069 /* The last byte should be a newline. */
2070 if (buf
[num_bytes
- 1] != '\n')
2072 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
2073 _("Revision file lacks trailing newline"));
2076 /* Look for the next previous newline. */
2077 for (i
= num_bytes
- 2; i
>= 0; i
--)
2079 if (buf
[i
] == '\n') break;
2084 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
2085 _("Final line in revision file longer than 64 "
2092 *root_offset
= apr_atoi64(&buf
[i
]);
2094 /* find the next space */
2095 for ( ; i
< (num_bytes
- 2) ; i
++)
2096 if (buf
[i
] == ' ') break;
2098 if (i
== (num_bytes
- 2))
2099 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2100 _("Final line in revision file missing space"));
2104 /* note that apr_atoi64() will stop reading as soon as it encounters
2105 the final newline. */
2107 *changes_offset
= apr_atoi64(&buf
[i
]);
2109 return SVN_NO_ERROR
;
2113 svn_fs_fs__rev_get_root(svn_fs_id_t
**root_id_p
,
2118 fs_fs_data_t
*ffd
= fs
->fsap_data
;
2119 apr_file_t
*revision_file
;
2120 apr_off_t root_offset
;
2121 svn_fs_id_t
*root_id
;
2123 const char *rev_str
= apr_psprintf(pool
, "%ld", rev
);
2124 svn_fs_id_t
*cached_id
;
2126 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
2128 /* Calculate an index into the revroot id cache */
2129 cached_id
= apr_hash_get(ffd
->rev_root_id_cache
,
2131 APR_HASH_KEY_STRING
);
2135 *root_id_p
= svn_fs_fs__id_copy(cached_id
, pool
);
2136 return SVN_NO_ERROR
;
2139 err
= svn_io_file_open(&revision_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
2140 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
2141 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
2143 svn_error_clear(err
);
2144 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
2145 _("No such revision %ld"), rev
);
2151 SVN_ERR(get_root_changes_offset(&root_offset
, NULL
, revision_file
, pool
));
2153 SVN_ERR(get_fs_id_at_offset(&root_id
, revision_file
, root_offset
, pool
));
2155 SVN_ERR(svn_io_file_close(revision_file
, pool
));
2157 /* Make sure our cache size doesn't grow without bounds. */
2158 if (apr_hash_count(ffd
->rev_root_id_cache
) >= NUM_RRI_CACHE_ENTRIES
)
2160 /* In order to only use one pool for the whole cache, we need to
2161 * completely wipe it to expire entries! */
2162 svn_pool_clear(ffd
->rev_root_id_cache_pool
);
2163 ffd
->rev_root_id_cache
= apr_hash_make(ffd
->rev_root_id_cache_pool
);
2166 /* Cache the answer, copying both the key and value into the cache's
2168 apr_hash_set(ffd
->rev_root_id_cache
,
2169 apr_pstrdup(ffd
->rev_root_id_cache_pool
, rev_str
),
2170 APR_HASH_KEY_STRING
,
2171 svn_fs_fs__id_copy(root_id
, ffd
->rev_root_id_cache_pool
));
2173 *root_id_p
= root_id
;
2175 return SVN_NO_ERROR
;
2179 svn_fs_fs__set_revision_proplist(svn_fs_t
*fs
,
2181 apr_hash_t
*proplist
,
2184 const char *final_path
= path_revprops(fs
, rev
, pool
);
2185 const char *tmp_path
;
2188 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
2190 SVN_ERR(svn_io_open_unique_file2
2191 (&f
, &tmp_path
, final_path
, ".tmp", svn_io_file_del_none
, pool
));
2192 SVN_ERR(svn_hash_write(proplist
, f
, pool
));
2193 SVN_ERR(svn_io_file_close(f
, pool
));
2194 /* We use the rev file of this revision as the perms reference,
2195 because when setting revprops for the first time, the revprop
2196 file won't exist and therefore can't serve as its own reference.
2197 (Whereas the rev file should already exist at this point.) */
2198 SVN_ERR(svn_fs_fs__move_into_place(tmp_path
, final_path
,
2199 svn_fs_fs__path_rev(fs
, rev
, pool
),
2202 return SVN_NO_ERROR
;
2206 svn_fs_fs__revision_proplist(apr_hash_t
**proplist_p
,
2211 apr_file_t
*revprop_file
= NULL
;
2212 apr_hash_t
*proplist
;
2213 svn_error_t
*err
= SVN_NO_ERROR
;
2215 apr_pool_t
*iterpool
;
2217 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
2219 proplist
= apr_hash_make(pool
);
2220 iterpool
= svn_pool_create(pool
);
2221 for (i
= 0; i
< RECOVERABLE_RETRY_COUNT
; i
++)
2223 svn_pool_clear(iterpool
);
2225 /* Clear err here rather than after finding a recoverable error so
2226 * we can return that error on the last iteration of the loop. */
2227 svn_error_clear(err
);
2228 err
= svn_io_file_open(&revprop_file
, path_revprops(fs
, rev
, iterpool
),
2229 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
2233 if (APR_STATUS_IS_ENOENT(err
->apr_err
))
2235 svn_error_clear(err
);
2236 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
2237 _("No such revision %ld"), rev
);
2240 else if (APR_TO_OS_ERROR(err
->apr_err
) == ESTALE
2241 || APR_TO_OS_ERROR(err
->apr_err
) == EIO
2242 || APR_TO_OS_ERROR(err
->apr_err
) == ENOENT
)
2248 SVN_ERR(svn_hash__clear(proplist
));
2249 RETRY_RECOVERABLE(err
, revprop_file
,
2250 svn_hash_read2(proplist
,
2251 svn_stream_from_aprfile(revprop_file
,
2253 SVN_HASH_TERMINATOR
, pool
));
2255 IGNORE_RECOVERABLE(err
, svn_io_file_close(revprop_file
, iterpool
));
2261 svn_pool_destroy(iterpool
);
2263 *proplist_p
= proplist
;
2265 return SVN_NO_ERROR
;
2268 /* Represents where in the current svndiff data block each
2269 representation is. */
2273 apr_off_t start
; /* The starting offset for the raw
2274 svndiff/plaintext data minus header. */
2275 apr_off_t off
; /* The current offset into the file. */
2276 apr_off_t end
; /* The end offset of the raw data. */
2277 int ver
; /* If a delta, what svndiff version? */
2281 /* See create_rep_state, which wraps this and adds another error. */
2282 static svn_error_t
*
2283 create_rep_state_body(struct rep_state
**rep_state
,
2284 struct rep_args
**rep_args
,
2285 representation_t
*rep
,
2289 struct rep_state
*rs
= apr_pcalloc(pool
, sizeof(*rs
));
2290 struct rep_args
*ra
;
2291 unsigned char buf
[4];
2293 SVN_ERR(open_and_seek_representation(&rs
->file
, fs
, rep
, pool
));
2294 SVN_ERR(read_rep_line(&ra
, rs
->file
, pool
));
2295 SVN_ERR(get_file_offset(&rs
->start
, rs
->file
, pool
));
2296 rs
->off
= rs
->start
;
2297 rs
->end
= rs
->start
+ rep
->size
;
2301 if (ra
->is_delta
== FALSE
)
2302 /* This is a plaintext, so just return the current rep_state. */
2303 return SVN_NO_ERROR
;
2305 /* We are dealing with a delta, find out what version. */
2306 SVN_ERR(svn_io_file_read_full(rs
->file
, buf
, sizeof(buf
), NULL
, pool
));
2307 if (! ((buf
[0] == 'S') && (buf
[1] == 'V') && (buf
[2] == 'N')))
2308 return svn_error_create
2309 (SVN_ERR_FS_CORRUPT
, NULL
,
2310 _("Malformed svndiff data in representation"));
2312 rs
->chunk_index
= 0;
2315 return SVN_NO_ERROR
;
2318 /* Read the rep args for REP in filesystem FS and create a rep_state
2319 for reading the representation. Return the rep_state in *REP_STATE
2320 and the rep args in *REP_ARGS, both allocated in POOL. */
2321 static svn_error_t
*
2322 create_rep_state(struct rep_state
**rep_state
,
2323 struct rep_args
**rep_args
,
2324 representation_t
*rep
,
2328 svn_error_t
*err
= create_rep_state_body(rep_state
, rep_args
, rep
, fs
, pool
);
2329 if (err
&& err
->apr_err
== SVN_ERR_FS_CORRUPT
)
2331 /* ### This always returns "-1" for transaction reps, because
2332 ### this particular bit of code doesn't know if the rep is
2333 ### stored in the protorev or in the mutable area (for props
2334 ### or dir contents). It is pretty rare for FSFS to *read*
2335 ### from the protorev file, though, so this is probably OK.
2336 ### And anyone going to debug corruption errors is probably
2337 ### going to jump straight to this comment anyway! */
2338 return svn_error_createf(SVN_ERR_FS_CORRUPT
, err
,
2339 "Corrupt representation '%s'",
2340 representation_string(rep
, TRUE
, pool
));
2345 /* Build an array of rep_state structures in *LIST giving the delta
2346 reps from first_rep to a plain-text or self-compressed rep. Set
2347 *SRC_STATE to the plain-text rep we find at the end of the chain,
2348 or to NULL if the final delta representation is self-compressed.
2349 The representation to start from is designated by filesystem FS, id
2350 ID, and representation REP. */
2351 static svn_error_t
*
2352 build_rep_list(apr_array_header_t
**list
,
2353 struct rep_state
**src_state
,
2355 representation_t
*first_rep
,
2358 representation_t rep
;
2359 struct rep_state
*rs
;
2360 struct rep_args
*rep_args
;
2362 *list
= apr_array_make(pool
, 1, sizeof(struct rep_state
*));
2367 SVN_ERR(create_rep_state(&rs
, &rep_args
, &rep
, fs
, pool
));
2368 if (rep_args
->is_delta
== FALSE
)
2370 /* This is a plaintext, so just return the current rep_state. */
2372 return SVN_NO_ERROR
;
2375 /* Push this rep onto the list. If it's self-compressed, we're done. */
2376 APR_ARRAY_PUSH(*list
, struct rep_state
*) = rs
;
2377 if (rep_args
->is_delta_vs_empty
)
2380 return SVN_NO_ERROR
;
2383 rep
.revision
= rep_args
->base_revision
;
2384 rep
.offset
= rep_args
->base_offset
;
2385 rep
.size
= rep_args
->base_length
;
2391 struct rep_read_baton
2393 /* The FS from which we're reading. */
2396 /* The state of all prior delta representations. */
2397 apr_array_header_t
*rs_list
;
2399 /* The plaintext state, if there is a plaintext. */
2400 struct rep_state
*src_state
;
2402 /* The index of the current delta chunk, if we are reading a delta. */
2405 /* The buffer where we store undeltified data. */
2410 /* An MD5 context for summing the data read in order to verify it. */
2411 struct apr_md5_ctx_t md5_context
;
2412 svn_boolean_t checksum_finalized
;
2414 /* The stored checksum of the representation we are reading, its
2415 length, and the amount we've read so far. Some of this
2416 information is redundant with rs_list and src_state, but it's
2417 convenient for the checksumming code to have it here. */
2418 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2422 /* Used for temporary allocations during the read. */
2425 /* Pool used to store file handles and other data that is persistant
2426 for the entire stream read. */
2427 apr_pool_t
*filehandle_pool
;
2430 /* Create a rep_read_baton structure for node revision NODEREV in
2431 filesystem FS and store it in *RB_P. Perform all allocations in
2432 POOL. If rep is mutable, it must be for file contents. */
2433 static svn_error_t
*
2434 rep_read_get_baton(struct rep_read_baton
**rb_p
,
2436 representation_t
*rep
,
2439 struct rep_read_baton
*b
;
2441 b
= apr_pcalloc(pool
, sizeof(*b
));
2445 apr_md5_init(&(b
->md5_context
));
2446 b
->checksum_finalized
= FALSE
;
2447 memcpy(b
->checksum
, rep
->checksum
, sizeof(b
->checksum
));
2448 b
->len
= rep
->expanded_size
;
2450 b
->pool
= svn_pool_create(pool
);
2451 b
->filehandle_pool
= svn_pool_create(pool
);
2453 SVN_ERR(build_rep_list(&b
->rs_list
, &b
->src_state
, fs
, rep
,
2454 b
->filehandle_pool
));
2456 /* Save our output baton. */
2459 return SVN_NO_ERROR
;
2462 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
2463 window into *NWIN. */
2464 static svn_error_t
*
2465 read_window(svn_txdelta_window_t
**nwin
, int this_chunk
, struct rep_state
*rs
,
2468 svn_stream_t
*stream
;
2470 assert(rs
->chunk_index
<= this_chunk
);
2472 /* Skip windows to reach the current chunk if we aren't there yet. */
2473 while (rs
->chunk_index
< this_chunk
)
2475 SVN_ERR(svn_txdelta_skip_svndiff_window(rs
->file
, rs
->ver
, pool
));
2477 SVN_ERR(get_file_offset(&rs
->off
, rs
->file
, pool
));
2478 if (rs
->off
>= rs
->end
)
2479 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2480 _("Reading one svndiff window read "
2481 "beyond the end of the "
2485 /* Read the next window. */
2486 stream
= svn_stream_from_aprfile(rs
->file
, pool
);
2487 SVN_ERR(svn_txdelta_read_svndiff_window(nwin
, stream
, rs
->ver
, pool
));
2489 SVN_ERR(get_file_offset(&rs
->off
, rs
->file
, pool
));
2491 if (rs
->off
> rs
->end
)
2492 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2493 _("Reading one svndiff window read beyond "
2494 "the end of the representation"));
2496 return SVN_NO_ERROR
;
2499 /* Get one delta window that is a result of combining all but the last deltas
2500 from the current desired representation identified in *RB, to its
2501 final base representation. Store the window in *RESULT. */
2502 static svn_error_t
*
2503 get_combined_window(svn_txdelta_window_t
**result
,
2504 struct rep_read_baton
*rb
)
2506 apr_pool_t
*pool
, *new_pool
;
2508 svn_txdelta_window_t
*window
, *nwin
;
2509 struct rep_state
*rs
;
2511 assert(rb
->rs_list
->nelts
>= 2);
2513 pool
= svn_pool_create(rb
->pool
);
2515 /* Read the next window from the original rep. */
2516 rs
= APR_ARRAY_IDX(rb
->rs_list
, 0, struct rep_state
*);
2517 SVN_ERR(read_window(&window
, rb
->chunk_index
, rs
, pool
));
2519 /* Combine in the windows from the other delta reps, if needed. */
2520 for (i
= 1; i
< rb
->rs_list
->nelts
- 1; i
++)
2522 if (window
->src_ops
== 0)
2525 rs
= APR_ARRAY_IDX(rb
->rs_list
, i
, struct rep_state
*);
2527 SVN_ERR(read_window(&nwin
, rb
->chunk_index
, rs
, pool
));
2529 /* Combine this window with the current one. Cycles pools so that we
2530 only need to hold three windows at a time. */
2531 new_pool
= svn_pool_create(rb
->pool
);
2532 window
= svn_txdelta_compose_windows(nwin
, window
, new_pool
);
2533 svn_pool_destroy(pool
);
2538 return SVN_NO_ERROR
;
2541 static svn_error_t
*
2542 rep_read_contents_close(void *baton
)
2544 struct rep_read_baton
*rb
= baton
;
2546 svn_pool_destroy(rb
->pool
);
2547 svn_pool_destroy(rb
->filehandle_pool
);
2549 return SVN_NO_ERROR
;
2552 /* Return the next *LEN bytes of the rep and store them in *BUF. */
2553 static svn_error_t
*
2554 get_contents(struct rep_read_baton
*rb
,
2558 apr_size_t copy_len
, remaining
= *len
, tlen
;
2559 char *sbuf
, *tbuf
, *cur
= buf
;
2560 struct rep_state
*rs
;
2561 svn_txdelta_window_t
*cwindow
, *lwindow
;
2563 /* Special case for when there are no delta reps, only a plain
2565 if (rb
->rs_list
->nelts
== 0)
2567 copy_len
= remaining
;
2569 if (((apr_off_t
) copy_len
) > rs
->end
- rs
->off
)
2570 copy_len
= (apr_size_t
) (rs
->end
- rs
->off
);
2571 SVN_ERR(svn_io_file_read_full(rs
->file
, cur
, copy_len
, NULL
,
2573 rs
->off
+= copy_len
;
2575 return SVN_NO_ERROR
;
2578 while (remaining
> 0)
2580 /* If we have buffered data from a previous chunk, use that. */
2583 /* Determine how much to copy from the buffer. */
2584 copy_len
= rb
->buf_len
- rb
->buf_pos
;
2585 if (copy_len
> remaining
)
2586 copy_len
= remaining
;
2588 /* Actually copy the data. */
2589 memcpy(cur
, rb
->buf
+ rb
->buf_pos
, copy_len
);
2590 rb
->buf_pos
+= copy_len
;
2592 remaining
-= copy_len
;
2594 /* If the buffer is all used up, clear it and empty the
2596 if (rb
->buf_pos
== rb
->buf_len
)
2598 svn_pool_clear(rb
->pool
);
2605 rs
= APR_ARRAY_IDX(rb
->rs_list
, 0, struct rep_state
*);
2606 if (rs
->off
== rs
->end
)
2609 /* Get more buffered data by evaluating a chunk. */
2610 if (rb
->rs_list
->nelts
> 1)
2611 SVN_ERR(get_combined_window(&cwindow
, rb
));
2614 if (!cwindow
|| cwindow
->src_ops
> 0)
2616 rs
= APR_ARRAY_IDX(rb
->rs_list
, rb
->rs_list
->nelts
- 1,
2617 struct rep_state
*);
2618 /* Read window from last representation in list. */
2619 /* We apply this window directly instead of combining it
2620 with the others. We do this because vdelta used to
2621 be used for deltas against the empty stream, which
2622 will trigger quadratic behaviour in the delta
2623 combiner. It's still likely that we'll find such
2624 deltas in an old repository; it may be worth
2625 considering whether or not this special case is still
2626 needed in the future, though. */
2627 SVN_ERR(read_window(&lwindow
, rb
->chunk_index
, rs
, rb
->pool
));
2629 if (lwindow
->src_ops
> 0)
2631 if (! rb
->src_state
)
2632 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2633 _("svndiff data requested "
2634 "non-existent source"));
2636 sbuf
= apr_palloc(rb
->pool
, lwindow
->sview_len
);
2637 if (! ((rs
->start
+ lwindow
->sview_offset
) < rs
->end
))
2638 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2639 _("svndiff requested position "
2640 "beyond end of stream"));
2641 if ((rs
->start
+ lwindow
->sview_offset
) != rs
->off
)
2643 rs
->off
= rs
->start
+ lwindow
->sview_offset
;
2644 SVN_ERR(svn_io_file_seek(rs
->file
, APR_SET
, &rs
->off
,
2647 SVN_ERR(svn_io_file_read_full(rs
->file
, sbuf
,
2650 rs
->off
+= lwindow
->sview_len
;
2655 /* Apply lwindow to source. */
2656 tlen
= lwindow
->tview_len
;
2657 tbuf
= apr_palloc(rb
->pool
, tlen
);
2658 svn_txdelta_apply_instructions(lwindow
, sbuf
, tbuf
,
2660 if (tlen
!= lwindow
->tview_len
)
2661 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2662 _("svndiff window length is "
2673 rb
->buf_len
= cwindow
->tview_len
;
2674 rb
->buf
= apr_palloc(rb
->pool
, rb
->buf_len
);
2675 svn_txdelta_apply_instructions(cwindow
, sbuf
, rb
->buf
,
2677 if (rb
->buf_len
!= cwindow
->tview_len
)
2678 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2679 _("svndiff window length is "
2684 rb
->buf_len
= lwindow
->tview_len
;
2694 return SVN_NO_ERROR
;
2697 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
2698 representation and store them in *BUF. Sum as we read and verify
2699 the MD5 sum at the end. */
2700 static svn_error_t
*
2701 rep_read_contents(void *baton
,
2705 struct rep_read_baton
*rb
= baton
;
2707 /* Get the next block of data. */
2708 SVN_ERR(get_contents(rb
, buf
, len
));
2710 /* Perform checksumming. We want to check the checksum as soon as
2711 the last byte of data is read, in case the caller never performs
2712 a short read, but we don't want to finalize the MD5 context
2714 if (!rb
->checksum_finalized
)
2716 apr_md5_update(&rb
->md5_context
, buf
, *len
);
2718 if (rb
->off
== rb
->len
)
2720 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2722 rb
->checksum_finalized
= TRUE
;
2723 apr_md5_final(checksum
, &rb
->md5_context
);
2724 if (! svn_md5_digests_match(checksum
, rb
->checksum
))
2725 return svn_error_createf
2726 (SVN_ERR_FS_CORRUPT
, NULL
,
2727 _("Checksum mismatch while reading representation:\n"
2730 svn_md5_digest_to_cstring_display(rb
->checksum
, rb
->pool
),
2731 svn_md5_digest_to_cstring_display(checksum
, rb
->pool
));
2734 return SVN_NO_ERROR
;
2737 /* Return a stream in *CONTENTS_P that will read the contents of a
2738 representation stored at the location given by REP. Appropriate
2739 for any kind of immutable representation, but only for file
2740 contents (not props or directory contents) in mutable
2743 If REP is NULL, the representation is assumed to be empty, and the
2744 empty stream is returned.
2746 static svn_error_t
*
2747 read_representation(svn_stream_t
**contents_p
,
2749 representation_t
*rep
,
2752 struct rep_read_baton
*rb
;
2756 *contents_p
= svn_stream_empty(pool
);
2760 SVN_ERR(rep_read_get_baton(&rb
, fs
, rep
, pool
));
2761 *contents_p
= svn_stream_create(rb
, pool
);
2762 svn_stream_set_read(*contents_p
, rep_read_contents
);
2763 svn_stream_set_close(*contents_p
, rep_read_contents_close
);
2766 return SVN_NO_ERROR
;
2770 svn_fs_fs__get_contents(svn_stream_t
**contents_p
,
2772 node_revision_t
*noderev
,
2775 return read_representation(contents_p
, fs
, noderev
->data_rep
, pool
);
2778 /* Baton used when reading delta windows. */
2779 struct delta_read_baton
2781 struct rep_state
*rs
;
2782 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2785 /* This implements the svn_txdelta_next_window_fn_t interface. */
2786 static svn_error_t
*
2787 delta_read_next_window(svn_txdelta_window_t
**window
, void *baton
,
2790 struct delta_read_baton
*drb
= baton
;
2792 if (drb
->rs
->off
== drb
->rs
->end
)
2795 return SVN_NO_ERROR
;
2798 SVN_ERR(read_window(window
, drb
->rs
->chunk_index
, drb
->rs
, pool
));
2800 return SVN_NO_ERROR
;
2803 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
2804 static const unsigned char *
2805 delta_read_md5_digest(void *baton
)
2807 struct delta_read_baton
*drb
= baton
;
2809 return drb
->checksum
;
2813 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t
**stream_p
,
2815 node_revision_t
*source
,
2816 node_revision_t
*target
,
2819 svn_stream_t
*source_stream
, *target_stream
;
2821 /* Try a shortcut: if the target is stored as a delta against the source,
2822 then just use that delta. */
2823 if (source
&& source
->data_rep
&& target
->data_rep
)
2825 struct rep_state
*rep_state
;
2826 struct rep_args
*rep_args
;
2828 /* Read target's base rep if any. */
2829 SVN_ERR(create_rep_state(&rep_state
, &rep_args
, target
->data_rep
,
2831 /* If that matches source, then use this delta as is. */
2832 if (rep_args
->is_delta
2833 && (rep_args
->is_delta_vs_empty
2834 || (rep_args
->base_revision
== source
->data_rep
->revision
2835 && rep_args
->base_offset
== source
->data_rep
->offset
)))
2837 /* Create the delta read baton. */
2838 struct delta_read_baton
*drb
= apr_pcalloc(pool
, sizeof(*drb
));
2839 drb
->rs
= rep_state
;
2840 memcpy(drb
->checksum
, target
->data_rep
->checksum
,
2841 sizeof(drb
->checksum
));
2842 *stream_p
= svn_txdelta_stream_create(drb
, delta_read_next_window
,
2843 delta_read_md5_digest
, pool
);
2844 return SVN_NO_ERROR
;
2847 SVN_ERR(svn_io_file_close(rep_state
->file
, pool
));
2850 /* Read both fulltexts and construct a delta. */
2852 SVN_ERR(read_representation(&source_stream
, fs
, source
->data_rep
, pool
));
2854 source_stream
= svn_stream_empty(pool
);
2855 SVN_ERR(read_representation(&target_stream
, fs
, target
->data_rep
, pool
));
2856 svn_txdelta(stream_p
, source_stream
, target_stream
, pool
);
2858 return SVN_NO_ERROR
;
2862 /* Fetch the contents of a directory into ENTRIES. Values are stored
2863 as filename to string mappings; further conversion is necessary to
2864 convert them into svn_fs_dirent_t values. */
2865 static svn_error_t
*
2866 get_dir_contents(apr_hash_t
*entries
,
2868 node_revision_t
*noderev
,
2871 svn_stream_t
*contents
;
2873 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
2875 apr_file_t
*dir_file
;
2876 const char *filename
= path_txn_node_children(fs
, noderev
->id
, pool
);
2878 /* The representation is mutable. Read the old directory
2879 contents from the mutable children file, followed by the
2880 changes we've made in this transaction. */
2881 SVN_ERR(svn_io_file_open(&dir_file
, filename
, APR_READ
| APR_BUFFERED
,
2882 APR_OS_DEFAULT
, pool
));
2883 contents
= svn_stream_from_aprfile(dir_file
, pool
);
2884 SVN_ERR(svn_hash_read2(entries
, contents
, SVN_HASH_TERMINATOR
, pool
));
2885 SVN_ERR(svn_hash_read_incremental(entries
, contents
, NULL
, pool
));
2886 SVN_ERR(svn_io_file_close(dir_file
, pool
));
2888 else if (noderev
->data_rep
)
2890 /* The representation is immutable. Read it normally. */
2891 SVN_ERR(read_representation(&contents
, fs
, noderev
->data_rep
, pool
));
2892 SVN_ERR(svn_hash_read2(entries
, contents
, SVN_HASH_TERMINATOR
, pool
));
2893 SVN_ERR(svn_stream_close(contents
));
2896 return SVN_NO_ERROR
;
2899 /* Return a copy of the directory hash ENTRIES in POOL. */
2901 copy_dir_entries(apr_hash_t
*entries
,
2904 apr_hash_t
*new_entries
= apr_hash_make(pool
);
2905 apr_hash_index_t
*hi
;
2907 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
2910 svn_fs_dirent_t
*dirent
, *new_dirent
;
2912 apr_hash_this(hi
, NULL
, NULL
, &val
);
2914 new_dirent
= apr_palloc(pool
, sizeof(*new_dirent
));
2915 new_dirent
->name
= apr_pstrdup(pool
, dirent
->name
);
2916 new_dirent
->kind
= dirent
->kind
;
2917 new_dirent
->id
= svn_fs_fs__id_copy(dirent
->id
, pool
);
2918 apr_hash_set(new_entries
, new_dirent
->name
, APR_HASH_KEY_STRING
,
2926 svn_fs_fs__rep_contents_dir(apr_hash_t
**entries_p
,
2928 node_revision_t
*noderev
,
2931 fs_fs_data_t
*ffd
= fs
->fsap_data
;
2932 apr_hash_t
*unparsed_entries
, *parsed_entries
;
2933 apr_hash_index_t
*hi
;
2936 /* Calculate an index into the dir entries cache. This should be
2937 completely ignored if this is a mutable noderev. */
2938 hid
= DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev
->id
));
2940 /* If we have this directory cached, return it. */
2941 if (! svn_fs_fs__id_txn_id(noderev
->id
) &&
2942 ffd
->dir_cache_id
[hid
] && svn_fs_fs__id_eq(ffd
->dir_cache_id
[hid
],
2945 *entries_p
= copy_dir_entries(ffd
->dir_cache
[hid
], pool
);
2946 return SVN_NO_ERROR
;
2949 /* Read in the directory hash. */
2950 unparsed_entries
= apr_hash_make(pool
);
2951 SVN_ERR(get_dir_contents(unparsed_entries
, fs
, noderev
, pool
));
2953 parsed_entries
= apr_hash_make(pool
);
2955 /* Translate the string dir entries into real entries. */
2956 for (hi
= apr_hash_first(pool
, unparsed_entries
); hi
; hi
= apr_hash_next(hi
))
2961 char *str
, *last_str
;
2962 svn_fs_dirent_t
*dirent
= apr_pcalloc(pool
, sizeof(*dirent
));
2964 apr_hash_this(hi
, &key
, NULL
, &val
);
2965 str_val
= apr_pstrdup(pool
, *((char **)val
));
2966 dirent
->name
= apr_pstrdup(pool
, key
);
2968 str
= apr_strtok(str_val
, " ", &last_str
);
2970 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2971 _("Directory entry corrupt"));
2973 if (strcmp(str
, KIND_FILE
) == 0)
2975 dirent
->kind
= svn_node_file
;
2977 else if (strcmp(str
, KIND_DIR
) == 0)
2979 dirent
->kind
= svn_node_dir
;
2983 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2984 _("Directory entry corrupt"));
2987 str
= apr_strtok(NULL
, " ", &last_str
);
2989 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2990 _("Directory entry corrupt"));
2992 dirent
->id
= svn_fs_fs__id_parse(str
, strlen(str
), pool
);
2994 apr_hash_set(parsed_entries
, dirent
->name
, APR_HASH_KEY_STRING
, dirent
);
2997 /* If this is an immutable directory, let's cache the contents. */
2998 if (! svn_fs_fs__id_txn_id(noderev
->id
))
3000 /* Start by NULLing the ID field, so that we never leave the
3001 cache in an illegal state. */
3002 ffd
->dir_cache_id
[hid
] = NULL
;
3004 if (ffd
->dir_cache_pool
[hid
])
3005 svn_pool_clear(ffd
->dir_cache_pool
[hid
]);
3007 ffd
->dir_cache_pool
[hid
] = svn_pool_create(fs
->pool
);
3009 ffd
->dir_cache
[hid
] = copy_dir_entries(parsed_entries
,
3010 ffd
->dir_cache_pool
[hid
]);
3011 ffd
->dir_cache_id
[hid
] = svn_fs_fs__id_copy(noderev
->id
,
3012 ffd
->dir_cache_pool
[hid
]);
3015 *entries_p
= parsed_entries
;
3016 return SVN_NO_ERROR
;
3020 svn_fs_fs__get_proplist(apr_hash_t
**proplist_p
,
3022 node_revision_t
*noderev
,
3025 apr_hash_t
*proplist
;
3026 svn_stream_t
*stream
;
3028 proplist
= apr_hash_make(pool
);
3030 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
3032 apr_file_t
*props_file
;
3033 const char *filename
= path_txn_node_props(fs
, noderev
->id
, pool
);
3035 SVN_ERR(svn_io_file_open(&props_file
, filename
,
3036 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
3038 stream
= svn_stream_from_aprfile(props_file
, pool
);
3039 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
, pool
));
3040 SVN_ERR(svn_io_file_close(props_file
, pool
));
3042 else if (noderev
->prop_rep
)
3044 SVN_ERR(read_representation(&stream
, fs
, noderev
->prop_rep
, pool
));
3045 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
, pool
));
3046 SVN_ERR(svn_stream_close(stream
));
3049 *proplist_p
= proplist
;
3051 return SVN_NO_ERROR
;
3055 svn_fs_fs__file_length(svn_filesize_t
*length
,
3056 node_revision_t
*noderev
,
3059 if (noderev
->data_rep
)
3060 *length
= noderev
->data_rep
->expanded_size
;
3064 return SVN_NO_ERROR
;
3068 svn_fs_fs__noderev_same_rep_key(representation_t
*a
,
3069 representation_t
*b
)
3080 if (a
->offset
!= b
->offset
)
3083 if (a
->revision
!= b
->revision
)
3090 svn_fs_fs__file_checksum(unsigned char digest
[],
3091 node_revision_t
*noderev
,
3094 if (noderev
->data_rep
)
3095 memcpy(digest
, noderev
->data_rep
->checksum
, APR_MD5_DIGESTSIZE
);
3097 memset(digest
, 0, APR_MD5_DIGESTSIZE
);
3099 return SVN_NO_ERROR
;
3103 svn_fs_fs__rep_copy(representation_t
*rep
,
3106 representation_t
*rep_new
;
3111 rep_new
= apr_pcalloc(pool
, sizeof(*rep_new
));
3113 memcpy(rep_new
, rep
, sizeof(*rep_new
));
3118 /* Merge the internal-use-only CHANGE into a hash of public-FS
3119 svn_fs_path_change_t CHANGES, collapsing multiple changes into a
3120 single summarical (is that real word?) change per path. Also keep
3121 the COPYFROM_HASH up to date with new adds and replaces. */
3122 static svn_error_t
*
3123 fold_change(apr_hash_t
*changes
,
3124 const change_t
*change
,
3125 apr_hash_t
*copyfrom_hash
)
3127 apr_pool_t
*pool
= apr_hash_pool_get(changes
);
3128 apr_pool_t
*copyfrom_pool
= apr_hash_pool_get(copyfrom_hash
);
3129 svn_fs_path_change_t
*old_change
, *new_change
;
3130 const char *path
, *copyfrom_string
, *copyfrom_path
= NULL
;
3132 if ((old_change
= apr_hash_get(changes
, change
->path
, APR_HASH_KEY_STRING
)))
3134 /* This path already exists in the hash, so we have to merge
3135 this change into the already existing one. */
3137 /* Get the existing copyfrom entry for this path. */
3138 copyfrom_string
= apr_hash_get(copyfrom_hash
, change
->path
,
3139 APR_HASH_KEY_STRING
);
3141 /* If this entry existed in the copyfrom hash, we don't need to
3143 if (copyfrom_string
)
3144 copyfrom_path
= change
->path
;
3146 /* Since the path already exists in the hash, we don't have to
3147 dup the allocation for the path itself. */
3148 path
= change
->path
;
3149 /* Sanity check: only allow NULL node revision ID in the
3151 if ((! change
->noderev_id
) && (change
->kind
!= svn_fs_path_change_reset
))
3152 return svn_error_create
3153 (SVN_ERR_FS_CORRUPT
, NULL
,
3154 _("Missing required node revision ID"));
3156 /* Sanity check: we should be talking about the same node
3157 revision ID as our last change except where the last change
3159 if (change
->noderev_id
3160 && (! svn_fs_fs__id_eq(old_change
->node_rev_id
, change
->noderev_id
))
3161 && (old_change
->change_kind
!= svn_fs_path_change_delete
))
3162 return svn_error_create
3163 (SVN_ERR_FS_CORRUPT
, NULL
,
3164 _("Invalid change ordering: new node revision ID "
3167 /* Sanity check: an add, replacement, or reset must be the first
3168 thing to follow a deletion. */
3169 if ((old_change
->change_kind
== svn_fs_path_change_delete
)
3170 && (! ((change
->kind
== svn_fs_path_change_replace
)
3171 || (change
->kind
== svn_fs_path_change_reset
)
3172 || (change
->kind
== svn_fs_path_change_add
))))
3173 return svn_error_create
3174 (SVN_ERR_FS_CORRUPT
, NULL
,
3175 _("Invalid change ordering: non-add change on deleted path"));
3177 /* Now, merge that change in. */
3178 switch (change
->kind
)
3180 case svn_fs_path_change_reset
:
3181 /* A reset here will simply remove the path change from the
3184 copyfrom_string
= NULL
;
3187 case svn_fs_path_change_delete
:
3188 if (old_change
->change_kind
== svn_fs_path_change_add
)
3190 /* If the path was introduced in this transaction via an
3191 add, and we are deleting it, just remove the path
3197 /* A deletion overrules all previous changes. */
3198 old_change
->change_kind
= svn_fs_path_change_delete
;
3199 old_change
->text_mod
= change
->text_mod
;
3200 old_change
->prop_mod
= change
->prop_mod
;
3202 copyfrom_string
= NULL
;
3205 case svn_fs_path_change_add
:
3206 case svn_fs_path_change_replace
:
3207 /* An add at this point must be following a previous delete,
3208 so treat it just like a replace. */
3209 old_change
->change_kind
= svn_fs_path_change_replace
;
3210 old_change
->node_rev_id
= svn_fs_fs__id_copy(change
->noderev_id
,
3212 old_change
->text_mod
= change
->text_mod
;
3213 old_change
->prop_mod
= change
->prop_mod
;
3214 if (change
->copyfrom_rev
== SVN_INVALID_REVNUM
)
3215 copyfrom_string
= apr_pstrdup(copyfrom_pool
, "");
3218 copyfrom_string
= apr_psprintf(copyfrom_pool
,
3220 change
->copyfrom_rev
,
3221 change
->copyfrom_path
);
3225 case svn_fs_path_change_modify
:
3227 if (change
->text_mod
)
3228 old_change
->text_mod
= TRUE
;
3229 if (change
->prop_mod
)
3230 old_change
->prop_mod
= TRUE
;
3234 /* Point our new_change to our (possibly modified) old_change. */
3235 new_change
= old_change
;
3239 /* This change is new to the hash, so make a new public change
3240 structure from the internal one (in the hash's pool), and dup
3241 the path into the hash's pool, too. */
3242 new_change
= apr_pcalloc(pool
, sizeof(*new_change
));
3243 new_change
->node_rev_id
= svn_fs_fs__id_copy(change
->noderev_id
, pool
);
3244 new_change
->change_kind
= change
->kind
;
3245 new_change
->text_mod
= change
->text_mod
;
3246 new_change
->prop_mod
= change
->prop_mod
;
3247 if (change
->copyfrom_rev
!= SVN_INVALID_REVNUM
)
3249 copyfrom_string
= apr_psprintf(copyfrom_pool
, "%ld %s",
3250 change
->copyfrom_rev
,
3251 change
->copyfrom_path
);
3254 copyfrom_string
= apr_pstrdup(copyfrom_pool
, "");
3255 path
= apr_pstrdup(pool
, change
->path
);
3258 /* Add (or update) this path. */
3259 apr_hash_set(changes
, path
, APR_HASH_KEY_STRING
, new_change
);
3261 /* If copyfrom_path is non-NULL, the key is already present in the
3262 hash, so we don't need to duplicate it in the copyfrom pool. */
3263 if (! copyfrom_path
)
3265 /* If copyfrom_string is NULL, the hash entry will be deleted,
3266 so we don't need to duplicate the key in the copyfrom
3268 copyfrom_path
= copyfrom_string
? apr_pstrdup(copyfrom_pool
, path
)
3272 apr_hash_set(copyfrom_hash
, copyfrom_path
, APR_HASH_KEY_STRING
,
3275 return SVN_NO_ERROR
;
3278 /* The 256 is an arbitrary size large enough to hold the node id and the
3280 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
3282 /* Read the next entry in the changes record from file FILE and store
3283 the resulting change in *CHANGE_P. If there is no next record,
3284 store NULL there. Perform all allocations from POOL. */
3285 static svn_error_t
*
3286 read_change(change_t
**change_p
,
3290 char buf
[MAX_CHANGE_LINE_LEN
];
3291 apr_size_t len
= sizeof(buf
);
3293 char *str
, *last_str
;
3296 /* Default return value. */
3299 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
3301 /* Check for a blank line. */
3302 if (err
|| (len
== 0))
3304 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
3306 svn_error_clear(err
);
3307 return SVN_NO_ERROR
;
3309 if ((len
== 0) && (! err
))
3310 return SVN_NO_ERROR
;
3314 change
= apr_pcalloc(pool
, sizeof(*change
));
3316 /* Get the node-id of the change. */
3317 str
= apr_strtok(buf
, " ", &last_str
);
3319 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3320 _("Invalid changes line in rev-file"));
3322 change
->noderev_id
= svn_fs_fs__id_parse(str
, strlen(str
), pool
);
3324 /* Get the change type. */
3325 str
= apr_strtok(NULL
, " ", &last_str
);
3327 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3328 _("Invalid changes line in rev-file"));
3330 if (strcmp(str
, ACTION_MODIFY
) == 0)
3332 change
->kind
= svn_fs_path_change_modify
;
3334 else if (strcmp(str
, ACTION_ADD
) == 0)
3336 change
->kind
= svn_fs_path_change_add
;
3338 else if (strcmp(str
, ACTION_DELETE
) == 0)
3340 change
->kind
= svn_fs_path_change_delete
;
3342 else if (strcmp(str
, ACTION_REPLACE
) == 0)
3344 change
->kind
= svn_fs_path_change_replace
;
3346 else if (strcmp(str
, ACTION_RESET
) == 0)
3348 change
->kind
= svn_fs_path_change_reset
;
3352 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3353 _("Invalid change kind in rev file"));
3356 /* Get the text-mod flag. */
3357 str
= apr_strtok(NULL
, " ", &last_str
);
3359 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3360 _("Invalid changes line in rev-file"));
3362 if (strcmp(str
, FLAG_TRUE
) == 0)
3364 change
->text_mod
= TRUE
;
3366 else if (strcmp(str
, FLAG_FALSE
) == 0)
3368 change
->text_mod
= FALSE
;
3372 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3373 _("Invalid text-mod flag in rev-file"));
3376 /* Get the prop-mod flag. */
3377 str
= apr_strtok(NULL
, " ", &last_str
);
3379 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3380 _("Invalid changes line in rev-file"));
3382 if (strcmp(str
, FLAG_TRUE
) == 0)
3384 change
->prop_mod
= TRUE
;
3386 else if (strcmp(str
, FLAG_FALSE
) == 0)
3388 change
->prop_mod
= FALSE
;
3392 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3393 _("Invalid prop-mod flag in rev-file"));
3396 /* Get the changed path. */
3397 change
->path
= apr_pstrdup(pool
, last_str
);
3400 /* Read the next line, the copyfrom line. */
3402 SVN_ERR(svn_io_read_length_line(file
, buf
, &len
, pool
));
3406 change
->copyfrom_rev
= SVN_INVALID_REVNUM
;
3407 change
->copyfrom_path
= NULL
;
3411 str
= apr_strtok(buf
, " ", &last_str
);
3413 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3414 _("Invalid changes line in rev-file"));
3415 change
->copyfrom_rev
= atol(str
);
3418 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3419 _("Invalid changes line in rev-file"));
3421 change
->copyfrom_path
= apr_pstrdup(pool
, last_str
);
3426 return SVN_NO_ERROR
;
3429 /* Fetch all the changed path entries from FILE and store then in
3430 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
3431 *data. Store a hash of paths to copyfrom revisions/paths in
3432 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
3433 the changed-path entries have already been folded (by
3434 write_final_changed_path_info) and may be out of order, so we shouldn't
3435 remove children of replaced or deleted directories. Do all
3436 allocations in POOL. */
3437 static svn_error_t
*
3438 fetch_all_changes(apr_hash_t
*changed_paths
,
3439 apr_hash_t
*copyfrom_hash
,
3441 svn_boolean_t prefolded
,
3445 apr_pool_t
*iterpool
= svn_pool_create(pool
);
3446 apr_hash_t
*my_hash
;
3448 /* If we are passed a NULL copyfrom hash, manufacture one for the
3449 duration of this call. */
3450 my_hash
= copyfrom_hash
? copyfrom_hash
: apr_hash_make(pool
);
3452 /* Read in the changes one by one, folding them into our local hash
3455 SVN_ERR(read_change(&change
, file
, iterpool
));
3459 SVN_ERR(fold_change(changed_paths
, change
, my_hash
));
3461 /* Now, if our change was a deletion or replacement, we have to
3462 blow away any changes thus far on paths that are (or, were)
3463 children of this path.
3464 ### i won't bother with another iteration pool here -- at
3465 most we talking about a few extra dups of paths into what
3466 is already a temporary subpool.
3469 if (((change
->kind
== svn_fs_path_change_delete
)
3470 || (change
->kind
== svn_fs_path_change_replace
))
3473 apr_hash_index_t
*hi
;
3475 for (hi
= apr_hash_first(iterpool
, changed_paths
);
3477 hi
= apr_hash_next(hi
))
3479 /* KEY is the path. */
3480 const void *hashkey
;
3482 apr_hash_this(hi
, &hashkey
, &klen
, NULL
);
3484 /* If we come across our own path, ignore it. */
3485 if (strcmp(change
->path
, hashkey
) == 0)
3488 /* If we come across a child of our path, remove it. */
3489 if (svn_path_is_child(change
->path
, hashkey
, iterpool
))
3490 apr_hash_set(changed_paths
, hashkey
, klen
, NULL
);
3494 /* Clear the per-iteration subpool. */
3495 svn_pool_clear(iterpool
);
3497 SVN_ERR(read_change(&change
, file
, iterpool
));
3500 /* Destroy the per-iteration subpool. */
3501 svn_pool_destroy(iterpool
);
3503 return SVN_NO_ERROR
;
3507 svn_fs_fs__txn_changes_fetch(apr_hash_t
**changed_paths_p
,
3510 apr_hash_t
*copyfrom_cache
,
3514 apr_hash_t
*changed_paths
= apr_hash_make(pool
);
3516 SVN_ERR(svn_io_file_open(&file
, path_txn_changes(fs
, txn_id
, pool
),
3517 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3519 SVN_ERR(fetch_all_changes(changed_paths
, copyfrom_cache
, file
, FALSE
,
3522 SVN_ERR(svn_io_file_close(file
, pool
));
3524 *changed_paths_p
= changed_paths
;
3526 return SVN_NO_ERROR
;
3530 svn_fs_fs__paths_changed(apr_hash_t
**changed_paths_p
,
3533 apr_hash_t
*copyfrom_cache
,
3536 apr_off_t changes_offset
;
3537 apr_hash_t
*changed_paths
;
3538 apr_file_t
*revision_file
;
3540 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
3542 SVN_ERR(svn_io_file_open(&revision_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
3543 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3545 SVN_ERR(get_root_changes_offset(NULL
, &changes_offset
, revision_file
,
3548 SVN_ERR(svn_io_file_seek(revision_file
, APR_SET
, &changes_offset
, pool
));
3550 changed_paths
= apr_hash_make(pool
);
3552 SVN_ERR(fetch_all_changes(changed_paths
, copyfrom_cache
, revision_file
,
3555 /* Close the revision file. */
3556 SVN_ERR(svn_io_file_close(revision_file
, pool
));
3558 *changed_paths_p
= changed_paths
;
3560 return SVN_NO_ERROR
;
3563 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
3564 the filesystem FS. This is only used to create the root of a transaction.
3565 Allocations are from POOL. */
3566 static svn_error_t
*
3567 create_new_txn_noderev_from_rev(svn_fs_t
*fs
,
3572 node_revision_t
*noderev
;
3573 const char *node_id
, *copy_id
;
3575 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, src
, pool
));
3577 if (svn_fs_fs__id_txn_id(noderev
->id
))
3578 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3579 _("Copying from transactions not allowed"));
3581 noderev
->predecessor_id
= noderev
->id
;
3582 noderev
->predecessor_count
++;
3583 noderev
->copyfrom_path
= NULL
;
3584 noderev
->copyfrom_rev
= SVN_INVALID_REVNUM
;
3586 /* For the transaction root, the copyroot never changes. */
3588 node_id
= svn_fs_fs__id_node_id(noderev
->id
);
3589 copy_id
= svn_fs_fs__id_copy_id(noderev
->id
);
3590 noderev
->id
= svn_fs_fs__id_txn_create(node_id
, copy_id
, txn_id
, pool
);
3592 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, TRUE
, pool
));
3594 return SVN_NO_ERROR
;
3597 /* A structure used by get_and_increment_txn_key_body(). */
3598 struct get_and_increment_txn_key_baton
{
3604 /* Callback used in the implementation of create_txn_dir(). This gets
3605 the current base 36 value in PATH_TXN_CURRENT and increments it.
3606 It returns the original value by the baton. */
3607 static svn_error_t
*
3608 get_and_increment_txn_key_body(void *baton
, apr_pool_t
*pool
)
3610 struct get_and_increment_txn_key_baton
*cb
= baton
;
3611 const char *txn_current_filename
= path_txn_current(cb
->fs
, pool
);
3612 apr_file_t
*txn_current_file
= NULL
;
3613 const char *tmp_filename
;
3614 char next_txn_id
[MAX_KEY_SIZE
+3];
3615 svn_error_t
*err
= SVN_NO_ERROR
;
3616 apr_pool_t
*iterpool
;
3620 cb
->txn_id
= apr_palloc(cb
->pool
, MAX_KEY_SIZE
);
3622 iterpool
= svn_pool_create(pool
);
3623 for (i
= 0; i
< RECOVERABLE_RETRY_COUNT
; ++i
)
3625 svn_pool_clear(iterpool
);
3627 RETRY_RECOVERABLE(err
, txn_current_file
,
3628 svn_io_file_open(&txn_current_file
,
3629 txn_current_filename
,
3630 APR_READ
| APR_BUFFERED
,
3631 APR_OS_DEFAULT
, iterpool
));
3633 RETRY_RECOVERABLE(err
, txn_current_file
,
3634 svn_io_read_length_line(txn_current_file
,
3638 IGNORE_RECOVERABLE(err
, svn_io_file_close(txn_current_file
,
3646 svn_pool_destroy(iterpool
);
3648 /* Increment the key and add a trailing \n to the string so the
3649 txn-current file has a newline in it. */
3650 svn_fs_fs__next_key(cb
->txn_id
, &len
, next_txn_id
);
3651 next_txn_id
[len
] = '\n';
3653 next_txn_id
[len
] = '\0';
3655 SVN_ERR(svn_io_open_unique_file2(&txn_current_file
, &tmp_filename
,
3656 txn_current_filename
, ".tmp",
3657 svn_io_file_del_none
, pool
));
3659 SVN_ERR(svn_io_file_write_full(txn_current_file
,
3665 SVN_ERR(svn_io_file_flush_to_disk(txn_current_file
, pool
));
3667 SVN_ERR(svn_io_file_close(txn_current_file
, pool
));
3669 SVN_ERR(svn_fs_fs__move_into_place(tmp_filename
, txn_current_filename
,
3670 txn_current_filename
, pool
));
3675 /* Create a unique directory for a transaction in FS based on revision
3676 REV. Return the ID for this transaction in *ID_P. Use a sequence
3677 value in the transaction ID to prevent reuse of transaction IDs. */
3678 static svn_error_t
*
3679 create_txn_dir(const char **id_p
, svn_fs_t
*fs
, svn_revnum_t rev
,
3682 struct get_and_increment_txn_key_baton cb
;
3683 const char *txn_dir
;
3685 /* Get the current transaction sequence value, which is a base-36
3686 number, from the txn-current file, and write an
3687 incremented value back out to the file. Place the revision
3688 number the transaction is based off into the transaction id. */
3691 SVN_ERR(with_txn_current_lock(fs
,
3692 get_and_increment_txn_key_body
,
3695 *id_p
= apr_psprintf(pool
, "%ld-%s", rev
, cb
.txn_id
);
3697 txn_dir
= svn_path_join_many(pool
,
3700 apr_pstrcat(pool
, *id_p
, PATH_EXT_TXN
, NULL
),
3703 SVN_ERR(svn_io_dir_make(txn_dir
, APR_OS_DEFAULT
, pool
));
3705 return SVN_NO_ERROR
;
3708 /* Create a unique directory for a transaction in FS based on revision
3709 REV. Return the ID for this transaction in *ID_P. This
3710 implementation is used in svn 1.4 and earlier repositories and is
3711 kept in 1.5 and greater to support the --pre-1.4-compatible and
3712 --pre-1.5-compatible repository creation options. Reused
3713 transaction IDs are possible with this implementation. */
3714 static svn_error_t
*
3715 create_txn_dir_pre_1_5(const char **id_p
, svn_fs_t
*fs
, svn_revnum_t rev
,
3719 apr_pool_t
*subpool
;
3720 const char *unique_path
, *prefix
;
3722 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
3723 prefix
= svn_path_join_many(pool
, fs
->path
, PATH_TXNS_DIR
,
3724 apr_psprintf(pool
, "%ld", rev
), NULL
);
3726 subpool
= svn_pool_create(pool
);
3727 for (i
= 1; i
<= 99999; i
++)
3731 svn_pool_clear(subpool
);
3732 unique_path
= apr_psprintf(subpool
, "%s-%u" PATH_EXT_TXN
, prefix
, i
);
3733 err
= svn_io_dir_make(unique_path
, APR_OS_DEFAULT
, subpool
);
3736 /* We succeeded. Return the basename minus the ".txn" extension. */
3737 const char *name
= svn_path_basename(unique_path
, subpool
);
3738 *id_p
= apr_pstrndup(pool
, name
,
3739 strlen(name
) - strlen(PATH_EXT_TXN
));
3740 svn_pool_destroy(subpool
);
3741 return SVN_NO_ERROR
;
3743 if (! APR_STATUS_IS_EEXIST(err
->apr_err
))
3745 svn_error_clear(err
);
3748 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED
,
3750 _("Unable to create transaction directory "
3751 "in '%s' for revision %ld"),
3756 svn_fs_fs__create_txn(svn_fs_txn_t
**txn_p
,
3761 fs_fs_data_t
*ffd
= fs
->fsap_data
;
3763 svn_fs_id_t
*root_id
;
3765 txn
= apr_pcalloc(pool
, sizeof(*txn
));
3767 /* Get the txn_id. */
3768 if (ffd
->format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
3769 SVN_ERR(create_txn_dir(&txn
->id
, fs
, rev
, pool
));
3771 SVN_ERR(create_txn_dir_pre_1_5(&txn
->id
, fs
, rev
, pool
));
3774 txn
->base_rev
= rev
;
3776 txn
->vtable
= &txn_vtable
;
3779 /* Create a new root node for this transaction. */
3780 SVN_ERR(svn_fs_fs__rev_get_root(&root_id
, fs
, rev
, pool
));
3781 SVN_ERR(create_new_txn_noderev_from_rev(fs
, txn
->id
, root_id
, pool
));
3783 /* Create an empty rev file. */
3784 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs
, txn
->id
, pool
), "",
3787 /* Create an empty rev-lock file. */
3788 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs
, txn
->id
, pool
), "",
3791 /* Create an empty changes file. */
3792 SVN_ERR(svn_io_file_create(path_txn_changes(fs
, txn
->id
, pool
), "",
3795 /* Create the next-ids file. */
3796 SVN_ERR(svn_io_file_create(path_txn_next_ids(fs
, txn
->id
, pool
), "0 0\n",
3799 return SVN_NO_ERROR
;
3802 /* Store the property list for transaction TXN_ID in PROPLIST.
3803 Perform temporary allocations in POOL. */
3804 static svn_error_t
*
3805 get_txn_proplist(apr_hash_t
*proplist
,
3810 apr_file_t
*txn_prop_file
;
3812 /* Open the transaction properties file. */
3813 SVN_ERR(svn_io_file_open(&txn_prop_file
, path_txn_props(fs
, txn_id
, pool
),
3814 APR_READ
| APR_BUFFERED
,
3815 APR_OS_DEFAULT
, pool
));
3817 /* Read in the property list. */
3818 SVN_ERR(svn_hash_read2(proplist
,
3819 svn_stream_from_aprfile(txn_prop_file
, pool
),
3820 SVN_HASH_TERMINATOR
, pool
));
3822 SVN_ERR(svn_io_file_close(txn_prop_file
, pool
));
3824 return SVN_NO_ERROR
;
3828 svn_fs_fs__change_txn_prop(svn_fs_txn_t
*txn
,
3830 const svn_string_t
*value
,
3833 apr_array_header_t
*props
= apr_array_make(pool
, 1, sizeof(svn_prop_t
));
3838 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
3840 return svn_fs_fs__change_txn_props(txn
, props
, pool
);
3844 svn_fs_fs__change_txn_props(svn_fs_txn_t
*txn
,
3845 apr_array_header_t
*props
,
3848 apr_file_t
*txn_prop_file
;
3849 apr_hash_t
*txn_prop
= apr_hash_make(pool
);
3853 err
= get_txn_proplist(txn_prop
, txn
->fs
, txn
->id
, pool
);
3854 /* Here - and here only - we need to deal with the possibility that the
3855 transaction property file doesn't yet exist. The rest of the
3856 implementation assumes that the file exists, but we're called to set the
3857 initial transaction properties as the transaction is being created. */
3858 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
)))
3859 svn_error_clear(err
);
3863 for (i
= 0; i
< props
->nelts
; i
++)
3865 svn_prop_t
*prop
= &APR_ARRAY_IDX(props
, i
, svn_prop_t
);
3867 apr_hash_set(txn_prop
, prop
->name
, APR_HASH_KEY_STRING
, prop
->value
);
3870 /* Create a new version of the file and write out the new props. */
3871 /* Open the transaction properties file. */
3872 SVN_ERR(svn_io_file_open(&txn_prop_file
,
3873 path_txn_props(txn
->fs
, txn
->id
, pool
),
3874 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
3875 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3877 SVN_ERR(svn_hash_write(txn_prop
, txn_prop_file
, pool
));
3879 SVN_ERR(svn_io_file_close(txn_prop_file
, pool
));
3881 return SVN_NO_ERROR
;
3885 svn_fs_fs__get_txn(transaction_t
**txn_p
,
3891 node_revision_t
*noderev
;
3892 svn_fs_id_t
*root_id
;
3894 txn
= apr_pcalloc(pool
, sizeof(*txn
));
3895 txn
->proplist
= apr_hash_make(pool
);
3897 SVN_ERR(get_txn_proplist(txn
->proplist
, fs
, txn_id
, pool
));
3898 root_id
= svn_fs_fs__id_txn_create("0", "0", txn_id
, pool
);
3900 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, root_id
, pool
));
3902 txn
->root_id
= svn_fs_fs__id_copy(noderev
->id
, pool
);
3903 txn
->base_id
= svn_fs_fs__id_copy(noderev
->predecessor_id
, pool
);
3908 return SVN_NO_ERROR
;
3911 /* Write out the currently available next node_id NODE_ID and copy_id
3912 COPY_ID for transaction TXN_ID in filesystem FS. Perform temporary
3913 allocations in POOL. */
3914 static svn_error_t
*
3915 write_next_ids(svn_fs_t
*fs
,
3917 const char *node_id
,
3918 const char *copy_id
,
3922 svn_stream_t
*out_stream
;
3924 SVN_ERR(svn_io_file_open(&file
, path_txn_next_ids(fs
, txn_id
, pool
),
3925 APR_WRITE
| APR_TRUNCATE
,
3926 APR_OS_DEFAULT
, pool
));
3928 out_stream
= svn_stream_from_aprfile(file
, pool
);
3930 SVN_ERR(svn_stream_printf(out_stream
, pool
, "%s %s\n", node_id
, copy_id
));
3932 SVN_ERR(svn_stream_close(out_stream
));
3933 SVN_ERR(svn_io_file_close(file
, pool
));
3935 return SVN_NO_ERROR
;
3938 /* Find out what the next unique node-id and copy-id are for
3939 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
3940 and *COPY_ID. Perform all allocations in POOL. */
3941 static svn_error_t
*
3942 read_next_ids(const char **node_id
,
3943 const char **copy_id
,
3949 char buf
[MAX_KEY_SIZE
*2+3];
3951 char *str
, *last_str
;
3953 SVN_ERR(svn_io_file_open(&file
, path_txn_next_ids(fs
, txn_id
, pool
),
3954 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3956 limit
= sizeof(buf
);
3957 SVN_ERR(svn_io_read_length_line(file
, buf
, &limit
, pool
));
3959 SVN_ERR(svn_io_file_close(file
, pool
));
3961 /* Parse this into two separate strings. */
3963 str
= apr_strtok(buf
, " ", &last_str
);
3965 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3966 _("next-id file corrupt"));
3968 *node_id
= apr_pstrdup(pool
, str
);
3970 str
= apr_strtok(NULL
, " ", &last_str
);
3972 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3973 _("next-id file corrupt"));
3975 *copy_id
= apr_pstrdup(pool
, str
);
3977 return SVN_NO_ERROR
;
3980 /* Get a new and unique to this transaction node-id for transaction
3981 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
3982 Perform all allocations in POOL. */
3983 static svn_error_t
*
3984 get_new_txn_node_id(const char **node_id_p
,
3989 const char *cur_node_id
, *cur_copy_id
;
3993 /* First read in the current next-ids file. */
3994 SVN_ERR(read_next_ids(&cur_node_id
, &cur_copy_id
, fs
, txn_id
, pool
));
3996 node_id
= apr_pcalloc(pool
, strlen(cur_node_id
) + 2);
3998 len
= strlen(cur_node_id
);
3999 svn_fs_fs__next_key(cur_node_id
, &len
, node_id
);
4001 SVN_ERR(write_next_ids(fs
, txn_id
, node_id
, cur_copy_id
, pool
));
4003 *node_id_p
= apr_pstrcat(pool
, "_", cur_node_id
, NULL
);
4005 return SVN_NO_ERROR
;
4009 svn_fs_fs__create_node(const svn_fs_id_t
**id_p
,
4011 node_revision_t
*noderev
,
4012 const char *copy_id
,
4016 const char *node_id
;
4017 const svn_fs_id_t
*id
;
4019 /* Get a new node-id for this node. */
4020 SVN_ERR(get_new_txn_node_id(&node_id
, fs
, txn_id
, pool
));
4022 id
= svn_fs_fs__id_txn_create(node_id
, copy_id
, txn_id
, pool
);
4026 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, FALSE
, pool
));
4030 return SVN_NO_ERROR
;
4034 svn_fs_fs__purge_txn(svn_fs_t
*fs
,
4038 fs_fs_data_t
*ffd
= fs
->fsap_data
;
4040 /* Remove the shared transaction object associated with this transaction. */
4041 SVN_ERR(purge_shared_txn(fs
, txn_id
, pool
));
4042 /* Remove the directory associated with this transaction. */
4043 SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs
, txn_id
, pool
), FALSE
,
4045 if (ffd
->format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
4047 /* Delete protorev and its lock, which aren't in the txn
4048 directory. It's OK if they don't exist (for example, if this
4049 is post-commit and the proto-rev has been moved into
4051 svn_error_t
*err
= svn_io_remove_file(path_txn_proto_rev(fs
, txn_id
,
4053 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
4055 svn_error_clear(err
);
4061 err
= svn_io_remove_file(path_txn_proto_rev_lock(fs
, txn_id
, pool
),
4063 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
4065 svn_error_clear(err
);
4071 return SVN_NO_ERROR
;
4076 svn_fs_fs__abort_txn(svn_fs_txn_t
*txn
,
4081 SVN_ERR(svn_fs__check_fs(txn
->fs
, TRUE
));
4083 /* Clean out the directory cache. */
4084 ffd
= txn
->fs
->fsap_data
;
4085 memset(&ffd
->dir_cache_id
, 0,
4086 sizeof(svn_fs_id_t
*) * NUM_DIR_CACHE_ENTRIES
);
4088 /* Now, purge the transaction. */
4089 SVN_ERR_W(svn_fs_fs__purge_txn(txn
->fs
, txn
->id
, pool
),
4090 _("Transaction cleanup failed"));
4092 return SVN_NO_ERROR
;
4097 unparse_dir_entry(svn_node_kind_t kind
, const svn_fs_id_t
*id
,
4100 return apr_psprintf(pool
, "%s %s",
4101 (kind
== svn_node_file
) ? KIND_FILE
: KIND_DIR
,
4102 svn_fs_fs__id_unparse(id
, pool
)->data
);
4105 /* Given a hash ENTRIES of dirent structions, return a hash in
4106 *STR_ENTRIES_P, that has svn_string_t as the values in the format
4107 specified by the fs_fs directory contents file. Perform
4108 allocations in POOL. */
4109 static svn_error_t
*
4110 unparse_dir_entries(apr_hash_t
**str_entries_p
,
4111 apr_hash_t
*entries
,
4114 apr_hash_index_t
*hi
;
4116 *str_entries_p
= apr_hash_make(pool
);
4118 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
4123 svn_fs_dirent_t
*dirent
;
4124 const char *new_val
;
4126 apr_hash_this(hi
, &key
, &klen
, &val
);
4128 new_val
= unparse_dir_entry(dirent
->kind
, dirent
->id
, pool
);
4129 apr_hash_set(*str_entries_p
, key
, klen
,
4130 svn_string_create(new_val
, pool
));
4133 return SVN_NO_ERROR
;
4138 svn_fs_fs__set_entry(svn_fs_t
*fs
,
4140 node_revision_t
*parent_noderev
,
4142 const svn_fs_id_t
*id
,
4143 svn_node_kind_t kind
,
4146 representation_t
*rep
= parent_noderev
->data_rep
;
4147 const char *filename
= path_txn_node_children(fs
, parent_noderev
->id
, pool
);
4151 if (!rep
|| !rep
->txn_id
)
4154 apr_hash_t
*entries
;
4155 apr_pool_t
*subpool
= svn_pool_create(pool
);
4157 /* Before we can modify the directory, we need to dump its old
4158 contents into a mutable representation file. */
4159 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries
, fs
, parent_noderev
,
4161 SVN_ERR(unparse_dir_entries(&entries
, entries
, subpool
));
4162 SVN_ERR(svn_io_file_open(&file
, filename
,
4163 APR_WRITE
| APR_CREATE
| APR_BUFFERED
,
4164 APR_OS_DEFAULT
, pool
));
4165 out
= svn_stream_from_aprfile(file
, pool
);
4166 SVN_ERR(svn_hash_write2(entries
, out
, SVN_HASH_TERMINATOR
, subpool
));
4168 svn_pool_destroy(subpool
);
4171 /* Mark the node-rev's data rep as mutable. */
4172 rep
= apr_pcalloc(pool
, sizeof(*rep
));
4173 rep
->revision
= SVN_INVALID_REVNUM
;
4174 rep
->txn_id
= txn_id
;
4175 parent_noderev
->data_rep
= rep
;
4176 SVN_ERR(svn_fs_fs__put_node_revision(fs
, parent_noderev
->id
,
4177 parent_noderev
, FALSE
, pool
));
4181 /* The directory rep is already mutable, so just open it for append. */
4182 SVN_ERR(svn_io_file_open(&file
, filename
, APR_WRITE
| APR_APPEND
,
4183 APR_OS_DEFAULT
, pool
));
4184 out
= svn_stream_from_aprfile(file
, pool
);
4187 /* Append an incremental hash entry for the entry change. */
4190 const char *val
= unparse_dir_entry(kind
, id
, pool
);
4192 SVN_ERR(svn_stream_printf(out
, pool
, "K %" APR_SIZE_T_FMT
"\n%s\n"
4193 "V %" APR_SIZE_T_FMT
"\n%s\n",
4199 SVN_ERR(svn_stream_printf(out
, pool
, "D %" APR_SIZE_T_FMT
"\n%s\n",
4200 strlen(name
), name
));
4203 SVN_ERR(svn_io_file_close(file
, pool
));
4204 return SVN_NO_ERROR
;
4207 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
4208 string COPYFROM, into the file specified by FILE. All temporary
4209 allocations are in POOL. */
4210 static svn_error_t
*
4211 write_change_entry(apr_file_t
*file
,
4213 svn_fs_path_change_t
*change
,
4214 const char *copyfrom
,
4217 const char *idstr
, *buf
;
4218 const char *change_string
= NULL
;
4220 switch (change
->change_kind
)
4222 case svn_fs_path_change_modify
:
4223 change_string
= ACTION_MODIFY
;
4225 case svn_fs_path_change_add
:
4226 change_string
= ACTION_ADD
;
4228 case svn_fs_path_change_delete
:
4229 change_string
= ACTION_DELETE
;
4231 case svn_fs_path_change_replace
:
4232 change_string
= ACTION_REPLACE
;
4234 case svn_fs_path_change_reset
:
4235 change_string
= ACTION_RESET
;
4238 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4239 _("Invalid change type"));
4242 if (change
->node_rev_id
)
4243 idstr
= svn_fs_fs__id_unparse(change
->node_rev_id
, pool
)->data
;
4245 idstr
= ACTION_RESET
;
4247 buf
= apr_psprintf(pool
, "%s %s %s %s %s\n",
4248 idstr
, change_string
,
4249 change
->text_mod
? FLAG_TRUE
: FLAG_FALSE
,
4250 change
->prop_mod
? FLAG_TRUE
: FLAG_FALSE
,
4253 SVN_ERR(svn_io_file_write_full(file
, buf
, strlen(buf
), NULL
, pool
));
4257 SVN_ERR(svn_io_file_write_full(file
, copyfrom
, strlen(copyfrom
),
4261 SVN_ERR(svn_io_file_write_full(file
, "\n", 1, NULL
, pool
));
4263 return SVN_NO_ERROR
;
4267 svn_fs_fs__add_change(svn_fs_t
*fs
,
4270 const svn_fs_id_t
*id
,
4271 svn_fs_path_change_kind_t change_kind
,
4272 svn_boolean_t text_mod
,
4273 svn_boolean_t prop_mod
,
4274 svn_revnum_t copyfrom_rev
,
4275 const char *copyfrom_path
,
4279 const char *copyfrom
;
4280 svn_fs_path_change_t
*change
= apr_pcalloc(pool
, sizeof(*change
));
4282 SVN_ERR(svn_io_file_open(&file
, path_txn_changes(fs
, txn_id
, pool
),
4283 APR_APPEND
| APR_WRITE
| APR_CREATE
4284 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
4286 if (copyfrom_rev
!= SVN_INVALID_REVNUM
)
4287 copyfrom
= apr_psprintf(pool
, "%ld %s", copyfrom_rev
, copyfrom_path
);
4291 change
->node_rev_id
= id
;
4292 change
->change_kind
= change_kind
;
4293 change
->text_mod
= text_mod
;
4294 change
->prop_mod
= prop_mod
;
4296 SVN_ERR(write_change_entry(file
, path
, change
, copyfrom
, pool
));
4298 SVN_ERR(svn_io_file_close(file
, pool
));
4300 return SVN_NO_ERROR
;
4303 /* This baton is used by the representation writing streams. It keeps
4304 track of the checksum information as well as the total size of the
4305 representation so far. */
4306 struct rep_write_baton
4308 /* The FS we are writing to. */
4311 /* Actual file to which we are writing. */
4312 svn_stream_t
*rep_stream
;
4314 /* A stream from the delta combiner. Data written here gets
4315 deltified, then eventually written to rep_stream. */
4316 svn_stream_t
*delta_stream
;
4318 /* Where is this representation header stored. */
4319 apr_off_t rep_offset
;
4321 /* Start of the actual data. */
4322 apr_off_t delta_start
;
4324 /* How many bytes have been written to this rep already. */
4325 svn_filesize_t rep_size
;
4327 /* The node revision for which we're writing out info. */
4328 node_revision_t
*noderev
;
4330 /* Actual output file. */
4332 /* Lock 'cookie' used to unlock the output file once we've finished
4336 struct apr_md5_ctx_t md5_context
;
4340 apr_pool_t
*parent_pool
;
4343 /* Handler for the write method of the representation writable stream.
4344 BATON is a rep_write_baton, DATA is the data to write, and *LEN is
4345 the length of this data. */
4346 static svn_error_t
*
4347 rep_write_contents(void *baton
,
4351 struct rep_write_baton
*b
= baton
;
4353 apr_md5_update(&b
->md5_context
, data
, *len
);
4354 b
->rep_size
+= *len
;
4356 /* If we are writing a delta, use that stream. */
4357 if (b
->delta_stream
)
4359 SVN_ERR(svn_stream_write(b
->delta_stream
, data
, len
));
4363 SVN_ERR(svn_stream_write(b
->rep_stream
, data
, len
));
4366 return SVN_NO_ERROR
;
4369 /* Given a node-revision NODEREV in filesystem FS, return the
4370 representation in *REP to use as the base for a text representation
4371 delta. Perform temporary allocations in *POOL. */
4372 static svn_error_t
*
4373 choose_delta_base(representation_t
**rep
,
4375 node_revision_t
*noderev
,
4379 node_revision_t
*base
;
4381 /* If we have no predecessors, then use the empty stream as a
4383 if (! noderev
->predecessor_count
)
4386 return SVN_NO_ERROR
;
4389 /* Flip the rightmost '1' bit of the predecessor count to determine
4390 which file rev (counting from 0) we want to use. (To see why
4391 count & (count - 1) unsets the rightmost set bit, think about how
4392 you decrement a binary number.) */
4393 count
= noderev
->predecessor_count
;
4394 count
= count
& (count
- 1);
4396 /* Walk back a number of predecessors equal to the difference
4397 between count and the original predecessor count. (For example,
4398 if noderev has ten predecessors and we want the eighth file rev,
4399 walk back two predecessors.) */
4401 while ((count
++) < noderev
->predecessor_count
)
4402 SVN_ERR(svn_fs_fs__get_node_revision(&base
, fs
,
4403 base
->predecessor_id
, pool
));
4405 *rep
= base
->data_rep
;
4407 return SVN_NO_ERROR
;
4410 /* Get a rep_write_baton and store it in *WB_P for the representation
4411 indicated by NODEREV in filesystem FS. Perform allocations in
4412 POOL. Only appropriate for file contents, not for props or
4413 directory contents. */
4414 static svn_error_t
*
4415 rep_write_get_baton(struct rep_write_baton
**wb_p
,
4417 node_revision_t
*noderev
,
4420 struct rep_write_baton
*b
;
4422 representation_t
*base_rep
;
4423 svn_stream_t
*source
;
4425 svn_txdelta_window_handler_t wh
;
4427 fs_fs_data_t
*ffd
= fs
->fsap_data
;
4429 b
= apr_pcalloc(pool
, sizeof(*b
));
4431 apr_md5_init(&(b
->md5_context
));
4434 b
->parent_pool
= pool
;
4435 b
->pool
= svn_pool_create(pool
);
4437 b
->noderev
= noderev
;
4439 /* Open the prototype rev file and seek to its end. */
4440 SVN_ERR(get_writable_proto_rev(&file
, &b
->lockcookie
,
4441 fs
, svn_fs_fs__id_txn_id(noderev
->id
),
4445 b
->rep_stream
= svn_stream_from_aprfile(file
, b
->pool
);
4447 SVN_ERR(get_file_offset(&b
->rep_offset
, file
, b
->pool
));
4449 /* Get the base for this delta. */
4450 SVN_ERR(choose_delta_base(&base_rep
, fs
, noderev
, b
->pool
));
4451 SVN_ERR(read_representation(&source
, fs
, base_rep
, b
->pool
));
4453 /* Write out the rep header. */
4456 header
= apr_psprintf(b
->pool
, REP_DELTA
" %ld %" APR_OFF_T_FMT
" %"
4457 SVN_FILESIZE_T_FMT
"\n",
4458 base_rep
->revision
, base_rep
->offset
,
4463 header
= REP_DELTA
"\n";
4465 SVN_ERR(svn_io_file_write_full(file
, header
, strlen(header
), NULL
,
4468 /* Now determine the offset of the actual svndiff data. */
4469 SVN_ERR(get_file_offset(&b
->delta_start
, file
, b
->pool
));
4471 /* Prepare to write the svndiff data. */
4472 if (ffd
->format
>= SVN_FS_FS__MIN_SVNDIFF1_FORMAT
)
4473 svn_txdelta_to_svndiff2(&wh
, &whb
, b
->rep_stream
, 1, pool
);
4475 svn_txdelta_to_svndiff2(&wh
, &whb
, b
->rep_stream
, 0, pool
);
4477 b
->delta_stream
= svn_txdelta_target_push(wh
, whb
, source
, b
->pool
);
4481 return SVN_NO_ERROR
;
4484 /* Close handler for the representation write stream. BATON is a
4485 rep_write_baton. Writes out a new node-rev that correctly
4486 references the representation we just finished writing. */
4487 static svn_error_t
*
4488 rep_write_contents_close(void *baton
)
4490 struct rep_write_baton
*b
= baton
;
4491 representation_t
*rep
;
4494 rep
= apr_pcalloc(b
->parent_pool
, sizeof(*rep
));
4495 rep
->offset
= b
->rep_offset
;
4497 /* Close our delta stream so the last bits of svndiff are written
4499 if (b
->delta_stream
)
4500 SVN_ERR(svn_stream_close(b
->delta_stream
));
4502 /* Determine the length of the svndiff data. */
4503 SVN_ERR(get_file_offset(&offset
, b
->file
, b
->pool
));
4504 rep
->size
= offset
- b
->delta_start
;
4506 /* Fill in the rest of the representation field. */
4507 rep
->expanded_size
= b
->rep_size
;
4508 rep
->txn_id
= svn_fs_fs__id_txn_id(b
->noderev
->id
);
4509 rep
->revision
= SVN_INVALID_REVNUM
;
4511 /* Finalize the MD5 checksum. */
4512 apr_md5_final(rep
->checksum
, &b
->md5_context
);
4514 /* Write out our cosmetic end marker. */
4515 SVN_ERR(svn_stream_printf(b
->rep_stream
, b
->pool
, "ENDREP\n"));
4517 b
->noderev
->data_rep
= rep
;
4519 /* Write out the new node-rev information. */
4520 SVN_ERR(svn_fs_fs__put_node_revision(b
->fs
, b
->noderev
->id
, b
->noderev
, FALSE
,
4523 SVN_ERR(svn_io_file_close(b
->file
, b
->pool
));
4524 SVN_ERR(unlock_proto_rev(b
->fs
, rep
->txn_id
, b
->lockcookie
, b
->pool
));
4525 svn_pool_destroy(b
->pool
);
4527 return SVN_NO_ERROR
;
4530 /* Store a writable stream in *CONTENTS_P that will receive all data
4531 written and store it as the file data representation referenced by
4532 NODEREV in filesystem FS. Perform temporary allocations in
4533 POOL. Only appropriate for file data, not props or directory
4535 static svn_error_t
*
4536 set_representation(svn_stream_t
**contents_p
,
4538 node_revision_t
*noderev
,
4541 struct rep_write_baton
*wb
;
4543 if (! svn_fs_fs__id_txn_id(noderev
->id
))
4544 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4545 _("Attempted to write to non-transaction"));
4547 SVN_ERR(rep_write_get_baton(&wb
, fs
, noderev
, pool
));
4549 *contents_p
= svn_stream_create(wb
, pool
);
4550 svn_stream_set_write(*contents_p
, rep_write_contents
);
4551 svn_stream_set_close(*contents_p
, rep_write_contents_close
);
4553 return SVN_NO_ERROR
;
4557 svn_fs_fs__set_contents(svn_stream_t
**stream
,
4559 node_revision_t
*noderev
,
4562 if (noderev
->kind
!= svn_node_file
)
4563 return svn_error_create(SVN_ERR_FS_NOT_FILE
, NULL
,
4564 _("Can't set text contents of a directory"));
4566 return set_representation(stream
, fs
, noderev
, pool
);
4570 svn_fs_fs__create_successor(const svn_fs_id_t
**new_id_p
,
4572 const svn_fs_id_t
*old_idp
,
4573 node_revision_t
*new_noderev
,
4574 const char *copy_id
,
4578 const svn_fs_id_t
*id
;
4581 copy_id
= svn_fs_fs__id_copy_id(old_idp
);
4582 id
= svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp
), copy_id
,
4585 new_noderev
->id
= id
;
4587 if (! new_noderev
->copyroot_path
)
4589 new_noderev
->copyroot_path
= apr_pstrdup(pool
,
4590 new_noderev
->created_path
);
4591 new_noderev
->copyroot_rev
= svn_fs_fs__id_rev(new_noderev
->id
);
4594 SVN_ERR(svn_fs_fs__put_node_revision(fs
, new_noderev
->id
, new_noderev
, FALSE
,
4599 return SVN_NO_ERROR
;
4603 svn_fs_fs__set_proplist(svn_fs_t
*fs
,
4604 node_revision_t
*noderev
,
4605 apr_hash_t
*proplist
,
4608 const char *filename
= path_txn_node_props(fs
, noderev
->id
, pool
);
4612 /* Dump the property list to the mutable property file. */
4613 SVN_ERR(svn_io_file_open(&file
, filename
,
4614 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
4615 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
4616 out
= svn_stream_from_aprfile(file
, pool
);
4617 SVN_ERR(svn_hash_write2(proplist
, out
, SVN_HASH_TERMINATOR
, pool
));
4618 SVN_ERR(svn_io_file_close(file
, pool
));
4620 /* Mark the node-rev's prop rep as mutable, if not already done. */
4621 if (!noderev
->prop_rep
|| !noderev
->prop_rep
->txn_id
)
4623 noderev
->prop_rep
= apr_pcalloc(pool
, sizeof(*noderev
->prop_rep
));
4624 noderev
->prop_rep
->txn_id
= svn_fs_fs__id_txn_id(noderev
->id
);
4625 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, FALSE
, pool
));
4628 return SVN_NO_ERROR
;
4631 /* Read the 'current' file for filesystem FS and store the next
4632 available node id in *NODE_ID, and the next available copy id in
4633 *COPY_ID. Allocations are performed from POOL. */
4634 static svn_error_t
*
4635 get_next_revision_ids(const char **node_id
,
4636 const char **copy_id
,
4641 char *str
, *last_str
;
4643 SVN_ERR(read_current(svn_fs_fs__path_current(fs
, pool
), &buf
, pool
));
4645 str
= apr_strtok(buf
, " ", &last_str
);
4647 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4648 _("Corrupt current file"));
4650 str
= apr_strtok(NULL
, " ", &last_str
);
4652 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4653 _("Corrupt current file"));
4655 *node_id
= apr_pstrdup(pool
, str
);
4657 str
= apr_strtok(NULL
, " ", &last_str
);
4659 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4660 _("Corrupt current file"));
4662 *copy_id
= apr_pstrdup(pool
, str
);
4664 return SVN_NO_ERROR
;
4667 /* This baton is used by the stream created for write_hash_rep. */
4668 struct write_hash_baton
4670 svn_stream_t
*stream
;
4674 struct apr_md5_ctx_t md5_context
;
4677 /* The handler for the write_hash_rep stream. BATON is a
4678 write_hash_baton, DATA has the data to write and *LEN is the number
4679 of bytes to write. */
4680 static svn_error_t
*
4681 write_hash_handler(void *baton
,
4685 struct write_hash_baton
*whb
= baton
;
4687 apr_md5_update(&whb
->md5_context
, data
, *len
);
4689 SVN_ERR(svn_stream_write(whb
->stream
, data
, len
));
4692 return SVN_NO_ERROR
;
4695 /* Write out the hash HASH as a text representation to file FILE. In
4696 the process, record the total size of the dump in *SIZE, and the
4697 md5 digest in CHECKSUM. Perform temporary allocations in POOL. */
4698 static svn_error_t
*
4699 write_hash_rep(svn_filesize_t
*size
,
4700 unsigned char checksum
[APR_MD5_DIGESTSIZE
],
4705 svn_stream_t
*stream
;
4706 struct write_hash_baton
*whb
;
4708 whb
= apr_pcalloc(pool
, sizeof(*whb
));
4710 whb
->stream
= svn_stream_from_aprfile(file
, pool
);
4712 apr_md5_init(&(whb
->md5_context
));
4714 stream
= svn_stream_create(whb
, pool
);
4715 svn_stream_set_write(stream
, write_hash_handler
);
4717 SVN_ERR(svn_stream_printf(whb
->stream
, pool
, "PLAIN\n"));
4719 SVN_ERR(svn_hash_write2(hash
, stream
, SVN_HASH_TERMINATOR
, pool
));
4721 /* Store the results. */
4722 apr_md5_final(checksum
, &whb
->md5_context
);
4725 SVN_ERR(svn_stream_printf(whb
->stream
, pool
, "ENDREP\n"));
4727 return SVN_NO_ERROR
;
4730 /* Copy a node-revision specified by id ID in fileystem FS from a
4731 transaction into the permanent rev-file FILE. Return the offset of
4732 the new node-revision in *OFFSET. If this is a directory, all
4733 children are copied as well. START_NODE_ID and START_COPY_ID are
4734 the first available node and copy ids for this filesystem, for older
4735 FS formats. Temporary allocations are from POOL. */
4736 static svn_error_t
*
4737 write_final_rev(const svn_fs_id_t
**new_id_p
,
4741 const svn_fs_id_t
*id
,
4742 const char *start_node_id
,
4743 const char *start_copy_id
,
4746 node_revision_t
*noderev
;
4747 apr_off_t my_offset
;
4748 char my_node_id_buf
[MAX_KEY_SIZE
+ 2];
4749 char my_copy_id_buf
[MAX_KEY_SIZE
+ 2];
4750 const svn_fs_id_t
*new_id
;
4751 const char *node_id
, *copy_id
, *my_node_id
, *my_copy_id
;
4752 fs_fs_data_t
*ffd
= fs
->fsap_data
;
4756 /* Check to see if this is a transaction node. */
4757 if (! svn_fs_fs__id_txn_id(id
))
4758 return SVN_NO_ERROR
;
4760 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, pool
));
4762 if (noderev
->kind
== svn_node_dir
)
4764 apr_pool_t
*subpool
;
4765 apr_hash_t
*entries
, *str_entries
;
4766 svn_fs_dirent_t
*dirent
;
4768 apr_hash_index_t
*hi
;
4770 /* This is a directory. Write out all the children first. */
4771 subpool
= svn_pool_create(pool
);
4773 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries
, fs
, noderev
, pool
));
4775 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
4777 svn_pool_clear(subpool
);
4778 apr_hash_this(hi
, NULL
, NULL
, &val
);
4780 SVN_ERR(write_final_rev(&new_id
, file
, rev
, fs
, dirent
->id
,
4781 start_node_id
, start_copy_id
,
4783 if (new_id
&& (svn_fs_fs__id_rev(new_id
) == rev
))
4784 dirent
->id
= svn_fs_fs__id_copy(new_id
, pool
);
4786 svn_pool_destroy(subpool
);
4788 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
4790 /* Write out the contents of this directory as a text rep. */
4791 SVN_ERR(unparse_dir_entries(&str_entries
, entries
, pool
));
4793 noderev
->data_rep
->txn_id
= NULL
;
4794 noderev
->data_rep
->revision
= rev
;
4795 SVN_ERR(get_file_offset(&noderev
->data_rep
->offset
, file
, pool
));
4796 SVN_ERR(write_hash_rep(&noderev
->data_rep
->size
,
4797 noderev
->data_rep
->checksum
, file
,
4798 str_entries
, pool
));
4799 noderev
->data_rep
->expanded_size
= noderev
->data_rep
->size
;
4804 /* This is a file. We should make sure the data rep, if it
4805 exists in a "this" state, gets rewritten to our new revision
4808 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
4810 noderev
->data_rep
->txn_id
= NULL
;
4811 noderev
->data_rep
->revision
= rev
;
4815 /* Fix up the property reps. */
4816 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
4818 apr_hash_t
*proplist
;
4820 SVN_ERR(svn_fs_fs__get_proplist(&proplist
, fs
, noderev
, pool
));
4821 SVN_ERR(get_file_offset(&noderev
->prop_rep
->offset
, file
, pool
));
4822 SVN_ERR(write_hash_rep(&noderev
->prop_rep
->size
,
4823 noderev
->prop_rep
->checksum
, file
,
4826 noderev
->prop_rep
->txn_id
= NULL
;
4827 noderev
->prop_rep
->revision
= rev
;
4831 /* Convert our temporary ID into a permanent revision one. */
4832 SVN_ERR(get_file_offset(&my_offset
, file
, pool
));
4834 node_id
= svn_fs_fs__id_node_id(noderev
->id
);
4835 if (*node_id
== '_')
4837 if (ffd
->format
>= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
)
4838 my_node_id
= apr_psprintf(pool
, "%s-%ld", node_id
+ 1, rev
);
4841 svn_fs_fs__add_keys(start_node_id
, node_id
+ 1, my_node_id_buf
);
4842 my_node_id
= my_node_id_buf
;
4846 my_node_id
= node_id
;
4848 copy_id
= svn_fs_fs__id_copy_id(noderev
->id
);
4849 if (*copy_id
== '_')
4851 if (ffd
->format
>= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
)
4852 my_copy_id
= apr_psprintf(pool
, "%s-%ld", copy_id
+ 1, rev
);
4855 svn_fs_fs__add_keys(start_copy_id
, copy_id
+ 1, my_copy_id_buf
);
4856 my_copy_id
= my_copy_id_buf
;
4860 my_copy_id
= copy_id
;
4862 if (noderev
->copyroot_rev
== SVN_INVALID_REVNUM
)
4863 noderev
->copyroot_rev
= rev
;
4865 new_id
= svn_fs_fs__id_rev_create(my_node_id
, my_copy_id
, rev
, my_offset
,
4868 noderev
->id
= new_id
;
4870 /* Write out our new node-revision. */
4871 SVN_ERR(write_noderev_txn(file
, noderev
,
4872 svn_fs_fs__fs_supports_mergeinfo(fs
),
4875 /* Return our ID that references the revision file. */
4876 *new_id_p
= noderev
->id
;
4878 return SVN_NO_ERROR
;
4881 /* Write the changed path info from transaction TXN_ID in filesystem
4882 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
4883 in the file of the beginning of this information. Perform
4884 temporary allocations in POOL. */
4885 static svn_error_t
*
4886 write_final_changed_path_info(apr_off_t
*offset_p
,
4892 const char *copyfrom
;
4893 apr_hash_t
*changed_paths
, *copyfrom_cache
= apr_hash_make(pool
);
4895 apr_hash_index_t
*hi
;
4896 apr_pool_t
*iterpool
= svn_pool_create(pool
);
4898 SVN_ERR(get_file_offset(&offset
, file
, pool
));
4900 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths
, fs
, txn_id
,
4901 copyfrom_cache
, pool
));
4903 /* Iterate through the changed paths one at a time, and convert the
4904 temporary node-id into a permanent one for each change entry. */
4905 for (hi
= apr_hash_first(pool
, changed_paths
); hi
; hi
= apr_hash_next(hi
))
4907 node_revision_t
*noderev
;
4908 const svn_fs_id_t
*id
;
4909 svn_fs_path_change_t
*change
;
4913 svn_pool_clear(iterpool
);
4915 apr_hash_this(hi
, &key
, NULL
, &val
);
4918 id
= change
->node_rev_id
;
4920 /* If this was a delete of a mutable node, then it is OK to
4921 leave the change entry pointing to the non-existent temporary
4922 node, since it will never be used. */
4923 if ((change
->change_kind
!= svn_fs_path_change_delete
) &&
4924 (! svn_fs_fs__id_txn_id(id
)))
4926 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, iterpool
));
4928 /* noderev has the permanent node-id at this point, so we just
4929 substitute it for the temporary one. */
4930 change
->node_rev_id
= noderev
->id
;
4933 /* Find the cached copyfrom information. */
4934 copyfrom
= apr_hash_get(copyfrom_cache
, key
, APR_HASH_KEY_STRING
);
4936 /* Write out the new entry into the final rev-file. */
4937 SVN_ERR(write_change_entry(file
, key
, change
, copyfrom
, iterpool
));
4940 svn_pool_destroy(iterpool
);
4944 return SVN_NO_ERROR
;
4949 svn_fs_fs__dup_perms(const char *filename
,
4950 const char *perms_reference
,
4954 apr_status_t status
;
4956 const char *filename_apr
, *perms_reference_apr
;
4958 SVN_ERR(svn_path_cstring_from_utf8(&filename_apr
, filename
, pool
));
4959 SVN_ERR(svn_path_cstring_from_utf8(&perms_reference_apr
, perms_reference
,
4962 status
= apr_stat(&finfo
, perms_reference_apr
, APR_FINFO_PROT
, pool
);
4964 return svn_error_wrap_apr(status
, _("Can't stat '%s'"),
4965 svn_path_local_style(perms_reference
, pool
));
4966 status
= apr_file_perms_set(filename_apr
, finfo
.protection
);
4968 return svn_error_wrap_apr(status
, _("Can't chmod '%s'"),
4969 svn_path_local_style(filename
, pool
));
4971 return SVN_NO_ERROR
;
4976 svn_fs_fs__move_into_place(const char *old_filename
,
4977 const char *new_filename
,
4978 const char *perms_reference
,
4983 SVN_ERR(svn_fs_fs__dup_perms(old_filename
, perms_reference
, pool
));
4985 /* Move the file into place. */
4986 err
= svn_io_file_rename(old_filename
, new_filename
, pool
);
4987 if (err
&& APR_STATUS_IS_EXDEV(err
->apr_err
))
4991 /* Can't rename across devices; fall back to copying. */
4992 svn_error_clear(err
);
4994 SVN_ERR(svn_io_copy_file(old_filename
, new_filename
, TRUE
, pool
));
4996 /* Flush the target of the copy to disk. */
4997 SVN_ERR(svn_io_file_open(&file
, new_filename
, APR_READ
,
4998 APR_OS_DEFAULT
, pool
));
4999 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
5000 SVN_ERR(svn_io_file_close(file
, pool
));
5007 /* Linux has the unusual feature that fsync() on a file is not
5008 enough to ensure that a file's directory entries have been
5009 flushed to disk; you have to fsync the directory as well.
5010 On other operating systems, we'd only be asking for trouble
5011 by trying to open and fsync a directory. */
5012 const char *dirname
;
5015 dirname
= svn_path_dirname(new_filename
, pool
);
5016 SVN_ERR(svn_io_file_open(&file
, dirname
, APR_READ
, APR_OS_DEFAULT
,
5018 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
5019 SVN_ERR(svn_io_file_close(file
, pool
));
5023 return SVN_NO_ERROR
;
5026 /* Atomically update the current file to hold the specifed REV,
5027 NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are
5028 ignored and may be NULL if the FS format does not use them.)
5029 Perform temporary allocations in POOL. */
5030 static svn_error_t
*
5031 write_current(svn_fs_t
*fs
, svn_revnum_t rev
, const char *next_node_id
,
5032 const char *next_copy_id
, apr_pool_t
*pool
)
5035 const char *tmp_name
, *name
;
5037 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5039 /* Now we can just write out this line. */
5040 if (ffd
->format
>= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
)
5041 buf
= apr_psprintf(pool
, "%ld\n", rev
);
5043 buf
= apr_psprintf(pool
, "%ld %s %s\n", rev
, next_node_id
, next_copy_id
);
5045 name
= svn_fs_fs__path_current(fs
, pool
);
5046 SVN_ERR(svn_io_open_unique_file2(&file
, &tmp_name
, name
, ".tmp",
5047 svn_io_file_del_none
, pool
));
5049 SVN_ERR(svn_io_file_write_full(file
, buf
, strlen(buf
), NULL
, pool
));
5051 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
5053 SVN_ERR(svn_io_file_close(file
, pool
));
5055 SVN_ERR(svn_fs_fs__move_into_place(tmp_name
, name
, name
, pool
));
5057 return SVN_NO_ERROR
;
5060 /* Update the current file to hold the correct next node and copy_ids
5061 from transaction TXN_ID in filesystem FS. The current revision is
5062 set to REV. Perform temporary allocations in POOL. */
5063 static svn_error_t
*
5064 write_final_current(svn_fs_t
*fs
,
5067 const char *start_node_id
,
5068 const char *start_copy_id
,
5071 const char *txn_node_id
, *txn_copy_id
;
5072 char new_node_id
[MAX_KEY_SIZE
+ 2];
5073 char new_copy_id
[MAX_KEY_SIZE
+ 2];
5074 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5076 if (ffd
->format
>= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
)
5077 return write_current(fs
, rev
, NULL
, NULL
, pool
);
5079 /* To find the next available ids, we add the id that used to be in
5080 the current file, to the next ids from the transaction file. */
5081 SVN_ERR(read_next_ids(&txn_node_id
, &txn_copy_id
, fs
, txn_id
, pool
));
5083 svn_fs_fs__add_keys(start_node_id
, txn_node_id
, new_node_id
);
5084 svn_fs_fs__add_keys(start_copy_id
, txn_copy_id
, new_copy_id
);
5086 return write_current(fs
, rev
, new_node_id
, new_copy_id
, pool
);
5089 /* Verify that the user registed with FS has all the locks necessary to
5090 permit all the changes associate with TXN_NAME.
5091 The FS write lock is assumed to be held by the caller. */
5092 static svn_error_t
*
5093 verify_locks(svn_fs_t
*fs
,
5094 const char *txn_name
,
5097 apr_pool_t
*subpool
= svn_pool_create(pool
);
5098 apr_hash_t
*changes
;
5099 apr_hash_index_t
*hi
;
5100 apr_array_header_t
*changed_paths
;
5101 svn_stringbuf_t
*last_recursed
= NULL
;
5104 /* Fetch the changes for this transaction. */
5105 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes
, fs
, txn_name
, NULL
, pool
));
5107 /* Make an array of the changed paths, and sort them depth-first-ily. */
5108 changed_paths
= apr_array_make(pool
, apr_hash_count(changes
) + 1,
5109 sizeof(const char *));
5110 for (hi
= apr_hash_first(pool
, changes
); hi
; hi
= apr_hash_next(hi
))
5113 apr_hash_this(hi
, &key
, NULL
, NULL
);
5114 APR_ARRAY_PUSH(changed_paths
, const char *) = key
;
5116 qsort(changed_paths
->elts
, changed_paths
->nelts
,
5117 changed_paths
->elt_size
, svn_sort_compare_paths
);
5119 /* Now, traverse the array of changed paths, verify locks. Note
5120 that if we need to do a recursive verification a path, we'll skip
5121 over children of that path when we get to them. */
5122 for (i
= 0; i
< changed_paths
->nelts
; i
++)
5125 svn_fs_path_change_t
*change
;
5126 svn_boolean_t recurse
= TRUE
;
5128 svn_pool_clear(subpool
);
5129 path
= APR_ARRAY_IDX(changed_paths
, i
, const char *);
5131 /* If this path has already been verified as part of a recursive
5132 check of one of its parents, no need to do it again. */
5134 && svn_path_is_child(last_recursed
->data
, path
, subpool
))
5137 /* Fetch the change associated with our path. */
5138 change
= apr_hash_get(changes
, path
, APR_HASH_KEY_STRING
);
5140 /* What does it mean to succeed at lock verification for a given
5141 path? For an existing file or directory getting modified
5142 (text, props), it means we hold the lock on the file or
5143 directory. For paths being added or removed, we need to hold
5144 the locks for that path and any children of that path.
5146 WHEW! We have no reliable way to determine the node kind
5147 of deleted items, but fortunately we are going to do a
5148 recursive check on deleted paths regardless of their kind. */
5149 if (change
->change_kind
== svn_fs_path_change_modify
)
5151 SVN_ERR(svn_fs_fs__allow_locked_operation(path
, fs
, recurse
, TRUE
,
5154 /* If we just did a recursive check, remember the path we
5155 checked (so children can be skipped). */
5158 if (! last_recursed
)
5159 last_recursed
= svn_stringbuf_create(path
, pool
);
5161 svn_stringbuf_set(last_recursed
, path
);
5164 svn_pool_destroy(subpool
);
5165 return SVN_NO_ERROR
;
5168 /* Baton used for commit_body below. */
5169 struct commit_baton
{
5170 svn_revnum_t
*new_rev_p
;
5175 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
5176 This implements the svn_fs_fs__with_write_lock() 'body' callback
5177 type. BATON is a 'struct commit_baton *'. */
5178 static svn_error_t
*
5179 commit_body(void *baton
, apr_pool_t
*pool
)
5181 struct commit_baton
*cb
= baton
;
5182 fs_fs_data_t
*ffd
= cb
->fs
->fsap_data
;
5183 const char *old_rev_filename
, *rev_filename
, *proto_filename
;
5184 const char *revprop_filename
, *final_revprop
;
5185 const svn_fs_id_t
*root_id
, *new_root_id
;
5186 const char *start_node_id
= NULL
, *start_copy_id
= NULL
;
5187 svn_revnum_t old_rev
, new_rev
;
5188 apr_file_t
*proto_file
;
5189 void *proto_file_lockcookie
;
5190 apr_off_t changed_path_offset
;
5192 apr_hash_t
*txnprops
;
5195 /* Get the current youngest revision. */
5196 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev
, cb
->fs
, pool
));
5198 /* Check to make sure this transaction is based off the most recent
5200 if (cb
->txn
->base_rev
!= old_rev
)
5201 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE
, NULL
,
5202 _("Transaction out of date"));
5204 /* Locks may have been added (or stolen) between the calling of
5205 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
5206 to re-examine every changed-path in the txn and re-verify all
5207 discovered locks. */
5208 SVN_ERR(verify_locks(cb
->fs
, cb
->txn
->id
, pool
));
5210 /* Get the next node_id and copy_id to use. */
5211 if (ffd
->format
< SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
)
5212 SVN_ERR(get_next_revision_ids(&start_node_id
, &start_copy_id
, cb
->fs
,
5215 /* We are going to be one better than this puny old revision. */
5216 new_rev
= old_rev
+ 1;
5218 /* Get a write handle on the proto revision file. */
5219 SVN_ERR(get_writable_proto_rev(&proto_file
, &proto_file_lockcookie
,
5220 cb
->fs
, cb
->txn
->id
, pool
));
5222 /* Write out all the node-revisions and directory contents. */
5223 root_id
= svn_fs_fs__id_txn_create("0", "0", cb
->txn
->id
, pool
);
5224 SVN_ERR(write_final_rev(&new_root_id
, proto_file
, new_rev
, cb
->fs
, root_id
,
5225 start_node_id
, start_copy_id
,
5228 /* Write the changed-path information. */
5229 SVN_ERR(write_final_changed_path_info(&changed_path_offset
, proto_file
,
5230 cb
->fs
, cb
->txn
->id
, pool
));
5232 /* Write the final line. */
5233 buf
= apr_psprintf(pool
, "\n%" APR_OFF_T_FMT
" %" APR_OFF_T_FMT
"\n",
5234 svn_fs_fs__id_offset(new_root_id
),
5235 changed_path_offset
);
5236 SVN_ERR(svn_io_file_write_full(proto_file
, buf
, strlen(buf
), NULL
,
5238 SVN_ERR(svn_io_file_flush_to_disk(proto_file
, pool
));
5239 SVN_ERR(svn_io_file_close(proto_file
, pool
));
5241 /* We don't unlock the prototype revision file immediately to avoid a
5242 race with another caller writing to the prototype revision file
5243 before we commit it. */
5245 /* Remove any temporary txn props representing 'flags'. */
5246 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops
, cb
->txn
, pool
));
5249 apr_array_header_t
*props
= apr_array_make(pool
, 3, sizeof(svn_prop_t
));
5253 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_OOD
,
5254 APR_HASH_KEY_STRING
))
5256 prop
.name
= SVN_FS__PROP_TXN_CHECK_OOD
;
5257 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5260 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_LOCKS
,
5261 APR_HASH_KEY_STRING
))
5263 prop
.name
= SVN_FS__PROP_TXN_CHECK_LOCKS
;
5264 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5267 if (! apr_is_empty_array(props
))
5268 SVN_ERR(svn_fs_fs__change_txn_props(cb
->txn
, props
, pool
));
5271 /* Create the shard for the rev and revprop file, if we're sharding and
5272 this is the first revision of a new shard. We don't care if this
5273 fails because the shard already existed for some reason. */
5274 if (ffd
->max_files_per_dir
&& new_rev
% ffd
->max_files_per_dir
== 0)
5277 err
= svn_io_dir_make(path_rev_shard(cb
->fs
, new_rev
, pool
),
5278 APR_OS_DEFAULT
, pool
);
5279 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5280 svn_error_clear(err
);
5283 err
= svn_io_dir_make(path_revprops_shard(cb
->fs
, new_rev
, pool
),
5284 APR_OS_DEFAULT
, pool
);
5285 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5286 svn_error_clear(err
);
5291 /* Move the finished rev file into place. */
5292 old_rev_filename
= svn_fs_fs__path_rev(cb
->fs
, old_rev
, pool
);
5293 rev_filename
= svn_fs_fs__path_rev(cb
->fs
, new_rev
, pool
);
5294 proto_filename
= path_txn_proto_rev(cb
->fs
, cb
->txn
->id
, pool
);
5295 SVN_ERR(svn_fs_fs__move_into_place(proto_filename
, rev_filename
,
5296 old_rev_filename
, pool
));
5298 /* Now that we've moved the prototype revision file out of the way,
5299 we can unlock it (since further attempts to write to the file
5300 will fail as it no longer exists). We must do this so that we can
5301 remove the transaction directory later. */
5302 SVN_ERR(unlock_proto_rev(cb
->fs
, cb
->txn
->id
, proto_file_lockcookie
, pool
));
5304 /* Update commit time to ensure that svn:date revprops remain ordered. */
5305 date
.data
= svn_time_to_cstring(apr_time_now(), pool
);
5306 date
.len
= strlen(date
.data
);
5308 SVN_ERR(svn_fs_fs__change_txn_prop(cb
->txn
, SVN_PROP_REVISION_DATE
,
5311 /* Move the revprops file into place. */
5312 revprop_filename
= path_txn_props(cb
->fs
, cb
->txn
->id
, pool
);
5313 final_revprop
= path_revprops(cb
->fs
, new_rev
, pool
);
5314 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename
, final_revprop
,
5315 old_rev_filename
, pool
));
5317 /* Update the 'current' file. */
5318 SVN_ERR(write_final_current(cb
->fs
, cb
->txn
->id
, new_rev
, start_node_id
,
5319 start_copy_id
, pool
));
5320 ffd
->youngest_rev_cache
= new_rev
;
5322 /* Remove this transaction directory. */
5323 SVN_ERR(svn_fs_fs__purge_txn(cb
->fs
, cb
->txn
->id
, pool
));
5325 *cb
->new_rev_p
= new_rev
;
5327 return SVN_NO_ERROR
;
5331 svn_fs_fs__commit(svn_revnum_t
*new_rev_p
,
5336 struct commit_baton cb
;
5338 cb
.new_rev_p
= new_rev_p
;
5341 return svn_fs_fs__with_write_lock(fs
, commit_body
, &cb
, pool
);
5345 svn_fs_fs__reserve_copy_id(const char **copy_id_p
,
5350 const char *cur_node_id
, *cur_copy_id
;
5354 /* First read in the current next-ids file. */
5355 SVN_ERR(read_next_ids(&cur_node_id
, &cur_copy_id
, fs
, txn_id
, pool
));
5357 copy_id
= apr_pcalloc(pool
, strlen(cur_copy_id
) + 2);
5359 len
= strlen(cur_copy_id
);
5360 svn_fs_fs__next_key(cur_copy_id
, &len
, copy_id
);
5362 SVN_ERR(write_next_ids(fs
, txn_id
, cur_node_id
, copy_id
, pool
));
5364 *copy_id_p
= apr_pstrcat(pool
, "_", cur_copy_id
, NULL
);
5366 return SVN_NO_ERROR
;
5369 /* Write out the zeroth revision for filesystem FS. */
5370 static svn_error_t
*
5371 write_revision_zero(svn_fs_t
*fs
)
5373 apr_hash_t
*proplist
;
5376 /* Write out a rev file for revision 0. */
5377 SVN_ERR(svn_io_file_create(svn_fs_fs__path_rev(fs
, 0, fs
->pool
),
5378 "PLAIN\nEND\nENDREP\n"
5383 "2d2977d1c96f487abe4a1e202dd03b4e\n"
5385 "\n\n17 107\n", fs
->pool
));
5387 /* Set a date on revision 0. */
5388 date
.data
= svn_time_to_cstring(apr_time_now(), fs
->pool
);
5389 date
.len
= strlen(date
.data
);
5390 proplist
= apr_hash_make(fs
->pool
);
5391 apr_hash_set(proplist
, SVN_PROP_REVISION_DATE
, APR_HASH_KEY_STRING
, &date
);
5392 return svn_fs_fs__set_revision_proplist(fs
, 0, proplist
, fs
->pool
);
5396 svn_fs_fs__create(svn_fs_t
*fs
,
5400 int format
= SVN_FS_FS__FORMAT_NUMBER
;
5401 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5403 fs
->path
= apr_pstrdup(pool
, path
);
5404 /* See if we had an explicitly requested pre-1.4- or pre-1.5-compatible. */
5407 if (apr_hash_get(fs
->config
, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE
,
5408 APR_HASH_KEY_STRING
))
5410 else if (apr_hash_get(fs
->config
, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE
,
5411 APR_HASH_KEY_STRING
))
5414 ffd
->format
= format
;
5416 /* Override the default linear layout if this is a new-enough format. */
5417 if (format
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
)
5418 ffd
->max_files_per_dir
= SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
;
5420 if (ffd
->max_files_per_dir
)
5422 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs
, 0, pool
),
5424 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs
, 0, pool
),
5429 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_REVS_DIR
,
5432 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
,
5437 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_TXNS_DIR
,
5441 if (format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
5442 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_TXN_PROTOS_DIR
,
5446 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs
, pool
),
5447 (format
>= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
5448 ? "0\n" : "0 1 1\n"),
5450 SVN_ERR(svn_io_file_create(path_lock(fs
, pool
), "", pool
));
5451 SVN_ERR(svn_fs_fs__set_uuid(fs
, svn_uuid_generate(pool
), pool
));
5453 SVN_ERR(write_revision_zero(fs
));
5455 /* Create the txn-current file if the repository supports
5456 the transaction sequence file. */
5457 if (format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
5459 SVN_ERR(svn_io_file_create(path_txn_current(fs
, pool
),
5461 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs
, pool
),
5465 /* This filesystem is ready. Stamp it with a format number. */
5466 SVN_ERR(write_format(path_format(fs
, pool
),
5467 ffd
->format
, ffd
->max_files_per_dir
, FALSE
, pool
));
5469 ffd
->youngest_rev_cache
= 0;
5470 return SVN_NO_ERROR
;
5473 /* Part of the recovery procedure. Return the largest revision *REV in
5474 filesystem FS. Use POOL for temporary allocation. */
5475 static svn_error_t
*
5476 recover_get_largest_revision(svn_fs_t
*fs
, svn_revnum_t
*rev
, apr_pool_t
*pool
)
5478 /* Discovering the largest revision in the filesystem would be an
5479 expensive operation if we did a readdir() or searched linearly,
5480 so we'll do a form of binary search. left is a revision that we
5481 know exists, right a revision that we know does not exist. */
5482 apr_pool_t
*iterpool
;
5483 svn_revnum_t left
, right
= 1;
5485 iterpool
= svn_pool_create(pool
);
5486 /* Keep doubling right, until we find a revision that doesn't exist. */
5489 svn_node_kind_t kind
;
5490 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs
, right
, iterpool
),
5492 svn_pool_clear(iterpool
);
5494 if (kind
== svn_node_none
)
5502 /* We know that left exists and right doesn't. Do a normal bsearch to find
5503 the last revision. */
5504 while (left
+ 1 < right
)
5506 svn_revnum_t probe
= left
+ ((right
- left
) / 2);
5507 svn_node_kind_t kind
;
5509 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs
, probe
, iterpool
),
5511 svn_pool_clear(iterpool
);
5513 if (kind
== svn_node_none
)
5519 svn_pool_destroy(iterpool
);
5521 /* left is now the largest revision that exists. */
5523 return SVN_NO_ERROR
;
5526 /* A baton for reading a fixed amount from an open file. For
5527 recover_find_max_ids() below. */
5528 struct recover_read_from_file_baton
5532 apr_size_t remaining
;
5535 /* A stream read handler used by recover_find_max_ids() below.
5536 Read and return at most BATON->REMAINING bytes from the stream,
5537 returning nothing after that to indicate EOF. */
5538 static svn_error_t
*
5539 read_handler_recover(void *baton
, char *buffer
, apr_size_t
*len
)
5541 struct recover_read_from_file_baton
*b
= baton
;
5542 apr_size_t bytes_to_read
= *len
;
5544 if (b
->remaining
== 0)
5546 /* Return a successful read of zero bytes to signal EOF. */
5548 return SVN_NO_ERROR
;
5551 if (bytes_to_read
> b
->remaining
)
5552 bytes_to_read
= b
->remaining
;
5553 b
->remaining
-= bytes_to_read
;
5555 return svn_io_file_read_full(b
->file
, buffer
, bytes_to_read
, len
, b
->pool
);
5558 /* Part of the recovery procedure. Read the directory noderev at offset
5559 OFFSET of file REV_FILE (the revision file of revision REV of
5560 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
5561 and copy-id of that node, if greater than the current value stored
5562 in either. Recurse into any child directories that were modified in
5565 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
5567 Perform temporary allocation in POOL. */
5568 static svn_error_t
*
5569 recover_find_max_ids(svn_fs_t
*fs
, svn_revnum_t rev
,
5570 apr_file_t
*rev_file
, apr_off_t offset
,
5571 char *max_node_id
, char *max_copy_id
,
5574 apr_hash_t
*headers
;
5576 node_revision_t noderev
;
5577 struct rep_args
*ra
;
5578 struct recover_read_from_file_baton baton
;
5579 svn_stream_t
*stream
;
5580 apr_hash_t
*entries
;
5581 apr_hash_index_t
*hi
;
5582 apr_pool_t
*iterpool
;
5584 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
5585 SVN_ERR(read_header_block(&headers
, rev_file
, pool
));
5587 /* We're going to populate a skeletal noderev - just the id and data_rep. */
5588 value
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
5589 noderev
.id
= svn_fs_fs__id_parse(value
, strlen(value
), pool
);
5591 /* Check that this is a directory. It should be. */
5592 value
= apr_hash_get(headers
, HEADER_TYPE
, APR_HASH_KEY_STRING
);
5593 if (value
== NULL
|| strcmp(value
, KIND_DIR
) != 0)
5594 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5595 _("Recovery encountered a non-directory node"));
5597 /* Get the data location. No data location indicates an empty directory. */
5598 value
= apr_hash_get(headers
, HEADER_TEXT
, APR_HASH_KEY_STRING
);
5600 return SVN_NO_ERROR
;
5601 SVN_ERR(read_rep_offsets(&noderev
.data_rep
, value
, NULL
, FALSE
, pool
));
5603 /* If the directory's data representation wasn't changed in this revision,
5604 we've already scanned the directory's contents for noderevs, so we don't
5605 need to again. This will occur if a property is changed on a directory
5606 without changing the directory's contents. */
5607 if (noderev
.data_rep
->revision
!= rev
)
5608 return SVN_NO_ERROR
;
5610 /* We could use get_dir_contents(), but this is much cheaper. It does
5611 rely on directory entries being stored as PLAIN reps, though. */
5612 offset
= noderev
.data_rep
->offset
;
5613 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
5614 SVN_ERR(read_rep_line(&ra
, rev_file
, pool
));
5616 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5617 _("Recovery encountered a deltified directory "
5620 /* Now create a stream that's allowed to read only as much data as is
5621 stored in the representation. */
5622 baton
.file
= rev_file
;
5624 baton
.remaining
= noderev
.data_rep
->expanded_size
;
5625 stream
= svn_stream_create(&baton
, pool
);
5626 svn_stream_set_read(stream
, read_handler_recover
);
5628 /* Now read the entries from that stream. */
5629 entries
= apr_hash_make(pool
);
5630 SVN_ERR(svn_hash_read2(entries
, stream
, SVN_HASH_TERMINATOR
, pool
));
5631 SVN_ERR(svn_stream_close(stream
));
5633 /* Now check each of the entries in our directory to find new node and
5634 copy ids, and recurse into new subdirectories. */
5635 iterpool
= svn_pool_create(pool
);
5636 for (hi
= apr_hash_first(NULL
, entries
); hi
; hi
= apr_hash_next(hi
))
5640 char *str
, *last_str
;
5641 svn_node_kind_t kind
;
5643 const char *node_id
, *copy_id
;
5644 apr_off_t child_dir_offset
;
5646 svn_pool_clear(iterpool
);
5648 apr_hash_this(hi
, NULL
, NULL
, &val
);
5649 str_val
= apr_pstrdup(iterpool
, *((char **)val
));
5651 str
= apr_strtok(str_val
, " ", &last_str
);
5653 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5654 _("Directory entry corrupt"));
5656 if (strcmp(str
, KIND_FILE
) == 0)
5657 kind
= svn_node_file
;
5658 else if (strcmp(str
, KIND_DIR
) == 0)
5659 kind
= svn_node_dir
;
5662 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5663 _("Directory entry corrupt"));
5666 str
= apr_strtok(NULL
, " ", &last_str
);
5668 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5669 _("Directory entry corrupt"));
5671 id
= svn_fs_fs__id_parse(str
, strlen(str
), iterpool
);
5673 if (svn_fs_fs__id_rev(id
) != rev
)
5675 /* If the node wasn't modified in this revision, we've already
5676 checked the node and copy id. */
5680 node_id
= svn_fs_fs__id_node_id(id
);
5681 copy_id
= svn_fs_fs__id_copy_id(id
);
5683 if (svn_fs_fs__key_compare(node_id
, max_node_id
) > 0)
5684 strcpy(max_node_id
, node_id
);
5685 if (svn_fs_fs__key_compare(copy_id
, max_copy_id
) > 0)
5686 strcpy(max_copy_id
, copy_id
);
5688 if (kind
== svn_node_file
)
5691 child_dir_offset
= svn_fs_fs__id_offset(id
);
5692 SVN_ERR(recover_find_max_ids(fs
, rev
, rev_file
, child_dir_offset
,
5693 max_node_id
, max_copy_id
, iterpool
));
5695 svn_pool_destroy(iterpool
);
5697 return SVN_NO_ERROR
;
5700 /* Baton used for recover_body below. */
5701 struct recover_baton
{
5703 svn_cancel_func_t cancel_func
;
5707 /* The work-horse for svn_fs_fs__recover, called with the FS
5708 write lock. This implements the svn_fs_fs__with_write_lock()
5709 'body' callback type. BATON is a 'struct recover_baton *'. */
5710 static svn_error_t
*
5711 recover_body(void *baton
, apr_pool_t
*pool
)
5713 struct recover_baton
*b
= baton
;
5714 svn_fs_t
*fs
= b
->fs
;
5715 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5716 svn_revnum_t max_rev
;
5717 char next_node_id_buf
[MAX_KEY_SIZE
], next_copy_id_buf
[MAX_KEY_SIZE
];
5718 char *next_node_id
= NULL
, *next_copy_id
= NULL
;
5719 svn_revnum_t youngest_rev
;
5720 svn_node_kind_t youngest_revprops_kind
;
5722 /* First, we need to know the largest revision in the filesystem. */
5723 SVN_ERR(recover_get_largest_revision(fs
, &max_rev
, pool
));
5725 /* Get the expected youngest revision */
5726 SVN_ERR(get_youngest(&youngest_rev
, fs
->path
, pool
));
5730 Since the revprops file is written after the revs file, the true
5731 maximum available revision is the youngest one for which both are
5732 present. That's probably the same as the max_rev we just found,
5733 but if it's not, we could, in theory, repeatedly decrement
5734 max_rev until we find a revision that has both a revs and
5735 revprops file, then write db/current with that.
5737 But we choose not to. If a repository is so corrupt that it's
5738 missing at least one revprops file, we shouldn't assume that the
5739 youngest revision for which both the revs and revprops files are
5740 present is healthy. In other words, we're willing to recover
5741 from a missing or out-of-date db/current file, because db/current
5742 is truly redundant -- it's basically a cache so we don't have to
5743 find max_rev each time, albeit a cache with unusual semantics,
5744 since it also officially defines when a revision goes live. But
5745 if we're missing more than the cache, it's time to back out and
5746 let the admin reconstruct things by hand: correctness at that
5747 point may depend on external things like checking a commit email
5748 list, looking in particular working copies, etc.
5750 This policy matches well with a typical naive backup scenario.
5751 Say you're rsyncing your FSFS repository nightly to the same
5752 location. Once revs and revprops are written, you've got the
5753 maximum rev; if the backup should bomb before db/current is
5754 written, then db/current could stay arbitrarily out-of-date, but
5755 we can still recover. It's a small window, but we might as well
5758 /* Even if db/current were missing, it would be created with 0 by
5759 get_youngest(), so this conditional remains valid. */
5760 if (youngest_rev
> max_rev
)
5761 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
5762 _("Expected current rev to be <= %ld "
5763 "but found %ld"), max_rev
, youngest_rev
);
5765 /* We only need to search for maximum IDs for old FS formats which
5766 se global ID counters. */
5767 if (ffd
->format
< SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
)
5769 /* Next we need to find the maximum node id and copy id in use across the
5770 filesystem. Unfortunately, the only way we can get this information
5771 is to scan all the noderevs of all the revisions and keep track as
5774 apr_pool_t
*iterpool
= svn_pool_create(pool
);
5775 char max_node_id
[MAX_KEY_SIZE
] = "0", max_copy_id
[MAX_KEY_SIZE
] = "0";
5778 for (rev
= 0; rev
<= max_rev
; rev
++)
5780 apr_file_t
*rev_file
;
5781 apr_off_t root_offset
;
5783 svn_pool_clear(iterpool
);
5786 SVN_ERR(b
->cancel_func(b
->cancel_baton
));
5788 SVN_ERR(svn_io_file_open(&rev_file
,
5789 svn_fs_fs__path_rev(fs
, rev
, iterpool
),
5790 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
5792 SVN_ERR(get_root_changes_offset(&root_offset
, NULL
, rev_file
,
5794 SVN_ERR(recover_find_max_ids(fs
, rev
, rev_file
, root_offset
,
5795 max_node_id
, max_copy_id
, iterpool
));
5797 svn_pool_destroy(iterpool
);
5799 /* Now that we finally have the maximum revision, node-id and copy-id, we
5800 can bump the two ids to get the next of each. */
5801 len
= strlen(max_node_id
);
5802 svn_fs_fs__next_key(max_node_id
, &len
, next_node_id_buf
);
5803 next_node_id
= next_node_id_buf
;
5804 len
= strlen(max_copy_id
);
5805 svn_fs_fs__next_key(max_copy_id
, &len
, next_copy_id_buf
);
5806 next_copy_id
= next_copy_id_buf
;
5809 /* Before setting current, verify that there is a revprops file
5810 for the youngest revision. (Issue #2992) */
5811 SVN_ERR(svn_io_check_path(path_revprops(fs
, max_rev
, pool
),
5812 &youngest_revprops_kind
, pool
));
5813 if (youngest_revprops_kind
== svn_node_none
)
5814 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
5815 _("Revision %ld has a revs file but no "
5818 else if (youngest_revprops_kind
!= svn_node_file
)
5819 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
5820 _("Revision %ld has a non-file where its "
5821 "revprops file should be"),
5824 /* Now store the discovered youngest revision, and the next IDs if
5825 relevant, in a new current file. */
5826 SVN_ERR(write_current(fs
, max_rev
, next_node_id
, next_copy_id
, pool
));
5828 return SVN_NO_ERROR
;
5831 /* This implements the fs_library_vtable_t.recover() API. */
5833 svn_fs_fs__recover(svn_fs_t
*fs
,
5834 svn_cancel_func_t cancel_func
, void *cancel_baton
,
5837 struct recover_baton b
;
5839 /* We have no way to take out an exclusive lock in FSFS, so we're
5840 restricted as to the types of recovery we can do. Luckily,
5841 we just want to recreate the current file, and we can do that just
5842 by blocking other writers. */
5844 b
.cancel_func
= cancel_func
;
5845 b
.cancel_baton
= cancel_baton
;
5846 return svn_fs_fs__with_write_lock(fs
, recover_body
, &b
, pool
);
5850 svn_fs_fs__get_uuid(svn_fs_t
*fs
,
5851 const char **uuid_p
,
5854 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5856 *uuid_p
= apr_pstrdup(pool
, ffd
->uuid
);
5857 return SVN_NO_ERROR
;
5861 svn_fs_fs__set_uuid(svn_fs_t
*fs
,
5865 apr_file_t
*uuid_file
;
5866 const char *tmp_path
;
5867 const char *uuid_path
= path_uuid(fs
, pool
);
5868 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5870 SVN_ERR(svn_io_open_unique_file2(&uuid_file
, &tmp_path
, uuid_path
,
5871 ".tmp", svn_io_file_del_none
, pool
));
5874 uuid
= svn_uuid_generate(pool
);
5876 SVN_ERR(svn_io_file_write_full(uuid_file
, uuid
, strlen(uuid
), NULL
,
5878 SVN_ERR(svn_io_file_write_full(uuid_file
, "\n", 1, NULL
, pool
));
5880 SVN_ERR(svn_io_file_close(uuid_file
, pool
));
5882 /* We use the permissions of the 'current' file, because the 'uuid'
5883 file does not exist during repository creation. */
5884 SVN_ERR(svn_fs_fs__move_into_place(tmp_path
, uuid_path
,
5885 svn_fs_fs__path_current(fs
, pool
), pool
));
5887 ffd
->uuid
= apr_pstrdup(fs
->pool
, uuid
);
5889 return SVN_NO_ERROR
;
5892 /** Node origin lazy cache. */
5894 /* If directory PATH does not exist, create it and give it the same
5895 permissions as FS->path.*/
5897 svn_fs_fs__ensure_dir_exists(const char *path
,
5901 svn_error_t
*err
= svn_io_dir_make(path
, APR_OS_DEFAULT
, pool
);
5902 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5904 svn_error_clear(err
);
5905 return SVN_NO_ERROR
;
5909 /* We successfully created a new directory. Dup the permissions
5911 SVN_ERR(svn_fs_fs__dup_perms(path
, fs
->path
, pool
));
5913 return SVN_NO_ERROR
;
5916 /* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
5917 'svn_string_t *' node revision IDs. Use POOL for allocations. */
5918 static svn_error_t
*
5919 get_node_origins_from_file(svn_fs_t
*fs
,
5920 apr_hash_t
**node_origins
,
5921 const char *node_origins_file
,
5926 svn_stream_t
*stream
;
5928 *node_origins
= NULL
;
5929 err
= svn_io_file_open(&fd
, node_origins_file
,
5930 APR_READ
, APR_OS_DEFAULT
, pool
);
5931 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
5933 svn_error_clear(err
);
5934 return SVN_NO_ERROR
;
5938 stream
= svn_stream_from_aprfile2(fd
, FALSE
, pool
);
5939 *node_origins
= apr_hash_make(pool
);
5940 SVN_ERR(svn_hash_read2(*node_origins
, stream
, SVN_HASH_TERMINATOR
, pool
));
5941 return svn_stream_close(stream
);
5945 svn_fs_fs__get_node_origin(const svn_fs_id_t
**origin_id
,
5947 const char *node_id
,
5950 apr_hash_t
*node_origins
;
5953 SVN_ERR(get_node_origins_from_file(fs
, &node_origins
,
5954 path_node_origin(fs
, node_id
, pool
),
5958 svn_string_t
*origin_id_str
=
5959 apr_hash_get(node_origins
, node_id
, APR_HASH_KEY_STRING
);
5961 *origin_id
= svn_fs_fs__id_parse(origin_id_str
->data
,
5962 origin_id_str
->len
, pool
);
5964 return SVN_NO_ERROR
;
5968 /* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID
5969 pair and adds it to the NODE_ORIGINS_PATH file. */
5970 static svn_error_t
*
5971 set_node_origins_for_file(svn_fs_t
*fs
,
5972 const char *node_origins_path
,
5973 const char *node_id
,
5974 svn_string_t
*node_rev_id
,
5978 const char *path_tmp
;
5979 svn_stream_t
*stream
;
5980 apr_hash_t
*origins_hash
;
5981 svn_string_t
*old_node_rev_id
;
5983 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_join(fs
->path
,
5984 PATH_NODE_ORIGINS_DIR
,
5988 /* Read the previously existing origins (if any), and merge our
5990 SVN_ERR(get_node_origins_from_file(fs
, &origins_hash
,
5991 node_origins_path
, pool
));
5993 origins_hash
= apr_hash_make(pool
);
5995 old_node_rev_id
= apr_hash_get(origins_hash
, node_id
, APR_HASH_KEY_STRING
);
5997 if (old_node_rev_id
&& !svn_string_compare(node_rev_id
, old_node_rev_id
))
5998 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
5999 _("Node origin for '%s' exists with a different "
6000 "value (%s) than what we were about to store "
6002 node_id
, old_node_rev_id
->data
, node_rev_id
->data
);
6004 apr_hash_set(origins_hash
, node_id
, APR_HASH_KEY_STRING
, node_rev_id
);
6006 /* Sure, there's a race condition here. Two processes could be
6007 trying to add different cache elements to the same file at the
6008 same time, and the entries added by the first one to write will
6009 be lost. But this is just a cache of reconstructible data, so
6010 we'll accept this problem in return for not having to deal with
6011 locking overhead. */
6013 /* Create a temporary file, write out our hash, and close the file. */
6014 SVN_ERR(svn_io_open_unique_file2(&fd
, &path_tmp
, node_origins_path
, ".tmp",
6015 svn_io_file_del_none
, pool
));
6016 stream
= svn_stream_from_aprfile2(fd
, FALSE
, pool
);
6017 SVN_ERR(svn_hash_write2(origins_hash
, stream
, SVN_HASH_TERMINATOR
, pool
));
6018 SVN_ERR(svn_stream_close(stream
));
6020 /* Rename the temp file as the real destination */
6021 SVN_ERR(svn_io_file_rename(path_tmp
, node_origins_path
, pool
));
6023 return SVN_NO_ERROR
;
6028 svn_fs_fs__set_node_origin(svn_fs_t
*fs
,
6029 const char *node_id
,
6030 const svn_fs_id_t
*node_rev_id
,
6034 const char *filename
= path_node_origin(fs
, node_id
, pool
);
6036 err
= set_node_origins_for_file(fs
, filename
,
6038 svn_fs_fs__id_unparse(node_rev_id
, pool
),
6040 if (err
&& APR_STATUS_IS_EACCES(err
->apr_err
))
6042 /* It's just a cache; stop trying if I can't write. */
6043 svn_error_clear(err
);
6051 svn_fs_fs__list_transactions(apr_array_header_t
**names_p
,
6055 const char *txn_dir
;
6056 apr_hash_t
*dirents
;
6057 apr_hash_index_t
*hi
;
6058 apr_array_header_t
*names
;
6059 apr_size_t ext_len
= strlen(PATH_EXT_TXN
);
6061 names
= apr_array_make(pool
, 1, sizeof(const char *));
6063 /* Get the transactions directory. */
6064 txn_dir
= svn_path_join(fs
->path
, PATH_TXNS_DIR
, pool
);
6066 /* Now find a listing of this directory. */
6067 SVN_ERR(svn_io_get_dirents2(&dirents
, txn_dir
, pool
));
6069 /* Loop through all the entries and return anything that ends with '.txn'. */
6070 for (hi
= apr_hash_first(pool
, dirents
); hi
; hi
= apr_hash_next(hi
))
6073 const char *name
, *id
;
6076 apr_hash_this(hi
, &key
, &klen
, NULL
);
6079 /* The name must end with ".txn" to be considered a transaction. */
6080 if ((apr_size_t
) klen
<= ext_len
6081 || (strcmp(name
+ klen
- ext_len
, PATH_EXT_TXN
)) != 0)
6084 /* Truncate the ".txn" extension and store the ID. */
6085 id
= apr_pstrndup(pool
, name
, strlen(name
) - ext_len
);
6086 APR_ARRAY_PUSH(names
, const char *) = id
;
6091 return SVN_NO_ERROR
;
6095 svn_fs_fs__open_txn(svn_fs_txn_t
**txn_p
,
6101 svn_node_kind_t kind
;
6102 transaction_t
*local_txn
;
6104 /* First check to see if the directory exists. */
6105 SVN_ERR(svn_io_check_path(path_txn_dir(fs
, name
, pool
), &kind
, pool
));
6107 /* Did we find it? */
6108 if (kind
!= svn_node_dir
)
6109 return svn_error_create(SVN_ERR_FS_NO_SUCH_TRANSACTION
, NULL
,
6110 _("No such transaction"));
6112 txn
= apr_pcalloc(pool
, sizeof(*txn
));
6114 /* Read in the root node of this transaction. */
6115 txn
->id
= apr_pstrdup(pool
, name
);
6118 SVN_ERR(svn_fs_fs__get_txn(&local_txn
, fs
, name
, pool
));
6120 txn
->base_rev
= svn_fs_fs__id_rev(local_txn
->base_id
);
6122 txn
->vtable
= &txn_vtable
;
6125 return SVN_NO_ERROR
;
6129 svn_fs_fs__txn_proplist(apr_hash_t
**table_p
,
6133 apr_hash_t
*proplist
= apr_hash_make(pool
);
6134 SVN_ERR(get_txn_proplist(proplist
, txn
->fs
, txn
->id
, pool
));
6135 *table_p
= proplist
;
6137 return SVN_NO_ERROR
;
6141 svn_fs_fs__delete_node_revision(svn_fs_t
*fs
,
6142 const svn_fs_id_t
*id
,
6145 node_revision_t
*noderev
;
6147 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, pool
));
6149 /* Delete any mutable property representation. */
6150 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
6151 SVN_ERR(svn_io_remove_file(path_txn_node_props(fs
, id
, pool
), pool
));
6153 /* Delete any mutable data representation. */
6154 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
6155 && noderev
->kind
== svn_node_dir
)
6156 SVN_ERR(svn_io_remove_file(path_txn_node_children(fs
, id
, pool
), pool
));
6158 return svn_io_remove_file(path_txn_node_rev(fs
, id
, pool
), pool
);
6166 svn_fs_fs__revision_prop(svn_string_t
**value_p
,
6169 const char *propname
,
6174 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
6175 SVN_ERR(svn_fs_fs__revision_proplist(&table
, fs
, rev
, pool
));
6177 *value_p
= apr_hash_get(table
, propname
, APR_HASH_KEY_STRING
);
6179 return SVN_NO_ERROR
;
6183 /* Baton used for change_rev_prop_body below. */
6184 struct change_rev_prop_baton
{
6188 const svn_string_t
*value
;
6191 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
6192 write lock. This implements the svn_fs_fs__with_write_lock()
6193 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
6195 static svn_error_t
*
6196 change_rev_prop_body(void *baton
, apr_pool_t
*pool
)
6198 struct change_rev_prop_baton
*cb
= baton
;
6201 SVN_ERR(svn_fs_fs__revision_proplist(&table
, cb
->fs
, cb
->rev
, pool
));
6203 apr_hash_set(table
, cb
->name
, APR_HASH_KEY_STRING
, cb
->value
);
6205 SVN_ERR(svn_fs_fs__set_revision_proplist(cb
->fs
, cb
->rev
, table
, pool
));
6207 return SVN_NO_ERROR
;
6211 svn_fs_fs__change_rev_prop(svn_fs_t
*fs
,
6214 const svn_string_t
*value
,
6217 struct change_rev_prop_baton cb
;
6219 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
6226 return svn_fs_fs__with_write_lock(fs
, change_rev_prop_body
, &cb
, pool
);
6231 /*** Transactions ***/
6234 svn_fs_fs__get_txn_ids(const svn_fs_id_t
**root_id_p
,
6235 const svn_fs_id_t
**base_root_id_p
,
6237 const char *txn_name
,
6241 SVN_ERR(svn_fs_fs__get_txn(&txn
, fs
, txn_name
, pool
));
6242 *root_id_p
= txn
->root_id
;
6243 *base_root_id_p
= txn
->base_id
;
6244 return SVN_NO_ERROR
;
6248 /* Generic transaction operations. */
6251 svn_fs_fs__txn_prop(svn_string_t
**value_p
,
6253 const char *propname
,
6257 svn_fs_t
*fs
= txn
->fs
;
6259 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
6260 SVN_ERR(svn_fs_fs__txn_proplist(&table
, txn
, pool
));
6262 *value_p
= apr_hash_get(table
, propname
, APR_HASH_KEY_STRING
);
6264 return SVN_NO_ERROR
;
6268 svn_fs_fs__begin_txn(svn_fs_txn_t
**txn_p
,
6276 apr_array_header_t
*props
= apr_array_make(pool
, 3, sizeof(svn_prop_t
));
6278 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
6280 SVN_ERR(svn_fs_fs__create_txn(txn_p
, fs
, rev
, pool
));
6282 /* Put a datestamp on the newly created txn, so we always know
6283 exactly how old it is. (This will help sysadmins identify
6284 long-abandoned txns that may need to be manually removed.) When
6285 a txn is promoted to a revision, this property will be
6286 automatically overwritten with a revision datestamp. */
6287 date
.data
= svn_time_to_cstring(apr_time_now(), pool
);
6288 date
.len
= strlen(date
.data
);
6290 prop
.name
= SVN_PROP_REVISION_DATE
;
6292 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
6294 /* Set temporary txn props that represent the requested 'flags'
6296 if (flags
& SVN_FS_TXN_CHECK_OOD
)
6298 prop
.name
= SVN_FS__PROP_TXN_CHECK_OOD
;
6299 prop
.value
= svn_string_create("true", pool
);
6300 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
6303 if (flags
& SVN_FS_TXN_CHECK_LOCKS
)
6305 prop
.name
= SVN_FS__PROP_TXN_CHECK_LOCKS
;
6306 prop
.value
= svn_string_create("true", pool
);
6307 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
6310 return svn_fs_fs__change_txn_props(*txn_p
, props
, pool
);