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_mergeinfo(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
245 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_TXN_MERGEINFO
,
249 static APR_INLINE
const char *
250 path_txn_next_ids(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
252 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_NEXT_IDS
, pool
);
255 static APR_INLINE
const char *
256 path_txn_proto_rev(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
258 fs_fs_data_t
*ffd
= fs
->fsap_data
;
259 if (ffd
->format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
260 return svn_path_join_many(pool
, fs
->path
, PATH_TXN_PROTOS_DIR
,
261 apr_pstrcat(pool
, txn_id
, PATH_EXT_REV
, NULL
),
264 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_REV
, pool
);
267 static APR_INLINE
const char *
268 path_txn_proto_rev_lock(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
270 fs_fs_data_t
*ffd
= fs
->fsap_data
;
271 if (ffd
->format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
272 return svn_path_join_many(pool
, fs
->path
, PATH_TXN_PROTOS_DIR
,
273 apr_pstrcat(pool
, txn_id
, PATH_EXT_REV_LOCK
,
277 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), PATH_REV_LOCK
, pool
);
281 path_txn_node_rev(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
283 const char *txn_id
= svn_fs_fs__id_txn_id(id
);
284 const char *node_id
= svn_fs_fs__id_node_id(id
);
285 const char *copy_id
= svn_fs_fs__id_copy_id(id
);
286 const char *name
= apr_psprintf(pool
, PATH_PREFIX_NODE
"%s.%s",
289 return svn_path_join(path_txn_dir(fs
, txn_id
, pool
), name
, pool
);
292 static APR_INLINE
const char *
293 path_txn_node_props(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
295 return apr_pstrcat(pool
, path_txn_node_rev(fs
, id
, pool
), PATH_EXT_PROPS
,
299 static APR_INLINE
const char *
300 path_txn_node_children(svn_fs_t
*fs
, const svn_fs_id_t
*id
, apr_pool_t
*pool
)
302 return apr_pstrcat(pool
, path_txn_node_rev(fs
, id
, pool
),
303 PATH_EXT_CHILDREN
, NULL
);
306 static APR_INLINE
const char *
307 path_node_origin(svn_fs_t
*fs
, const char *node_id
, apr_pool_t
*pool
)
309 return svn_path_join_many(pool
, fs
->path
, PATH_NODE_ORIGINS_DIR
,
314 /* Functions for working with shared transaction data. */
316 /* Return the transaction object for transaction TXN_ID from the
317 transaction list of filesystem FS (which must already be locked via the
318 txn_list_lock mutex). If the transaction does not exist in the list,
319 then create a new transaction object and return it (if CREATE_NEW is
320 true) or return NULL (otherwise). */
321 static fs_fs_shared_txn_data_t
*
322 get_shared_txn(svn_fs_t
*fs
, const char *txn_id
, svn_boolean_t create_new
)
324 fs_fs_data_t
*ffd
= fs
->fsap_data
;
325 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
326 fs_fs_shared_txn_data_t
*txn
;
328 for (txn
= ffsd
->txns
; txn
; txn
= txn
->next
)
329 if (strcmp(txn
->txn_id
, txn_id
) == 0)
332 if (txn
|| !create_new
)
335 /* Use the transaction object from the (single-object) freelist,
336 if one is available, or otherwise create a new object. */
339 txn
= ffsd
->free_txn
;
340 ffsd
->free_txn
= NULL
;
344 apr_pool_t
*subpool
= svn_pool_create(ffsd
->common_pool
);
345 txn
= apr_palloc(subpool
, sizeof(*txn
));
349 assert(strlen(txn_id
) < sizeof(txn
->txn_id
));
350 strcpy(txn
->txn_id
, txn_id
);
351 txn
->being_written
= FALSE
;
353 /* Link this transaction into the head of the list. We will typically
354 be dealing with only one active transaction at a time, so it makes
355 sense for searches through the transaction list to look at the
356 newest transactions first. */
357 txn
->next
= ffsd
->txns
;
363 /* Free the transaction object for transaction TXN_ID, and remove it
364 from the transaction list of filesystem FS (which must already be
365 locked via the txn_list_lock mutex). Do nothing if the transaction
368 free_shared_txn(svn_fs_t
*fs
, const char *txn_id
)
370 fs_fs_data_t
*ffd
= fs
->fsap_data
;
371 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
372 fs_fs_shared_txn_data_t
*txn
, *prev
= NULL
;
374 for (txn
= ffsd
->txns
; txn
; prev
= txn
, txn
= txn
->next
)
375 if (strcmp(txn
->txn_id
, txn_id
) == 0)
382 prev
->next
= txn
->next
;
384 ffsd
->txns
= txn
->next
;
386 /* As we typically will be dealing with one transaction after another,
387 we will maintain a single-object free list so that we can hopefully
388 keep reusing the same transaction object. */
390 ffsd
->free_txn
= txn
;
392 svn_pool_destroy(txn
->pool
);
396 /* Obtain a lock on the transaction list of filesystem FS, call BODY
397 with FS, BATON, and POOL, and then unlock the transaction list.
398 Return what BODY returned. */
400 with_txnlist_lock(svn_fs_t
*fs
,
401 svn_error_t
*(*body
)(svn_fs_t
*fs
,
409 fs_fs_data_t
*ffd
= fs
->fsap_data
;
410 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
411 apr_status_t apr_err
;
413 apr_err
= apr_thread_mutex_lock(ffsd
->txn_list_lock
);
415 return svn_error_wrap_apr(apr_err
, _("Can't grab FSFS txn list mutex"));
418 err
= body(fs
, baton
, pool
);
421 apr_err
= apr_thread_mutex_unlock(ffsd
->txn_list_lock
);
423 return svn_error_wrap_apr(apr_err
, _("Can't ungrab FSFS txn list mutex"));
430 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
432 get_lock_on_filesystem(const char *lock_filename
,
435 svn_error_t
*err
= svn_io_file_lock2(lock_filename
, TRUE
, FALSE
, pool
);
437 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
439 /* No lock file? No big deal; these are just empty files
440 anyway. Create it and try again. */
441 svn_error_clear(err
);
444 SVN_ERR(svn_io_file_create(lock_filename
, "", pool
));
445 SVN_ERR(svn_io_file_lock2(lock_filename
, TRUE
, FALSE
, pool
));
451 /* Obtain a write lock on the file LOCK_FILENAME (protecting with
452 LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
453 BATON and that subpool, destroy the subpool (releasing the write
454 lock) and return what BODY returned. */
456 with_some_lock(svn_error_t
*(*body
)(void *baton
,
459 const char *lock_filename
,
461 apr_thread_mutex_t
*lock_mutex
,
465 apr_pool_t
*subpool
= svn_pool_create(pool
);
471 /* POSIX fcntl locks are per-process, so we need to serialize locks
472 within the process. */
473 status
= apr_thread_mutex_lock(lock_mutex
);
475 return svn_error_wrap_apr(status
,
476 _("Can't grab FSFS mutex for '%s'"),
480 err
= get_lock_on_filesystem(lock_filename
, subpool
);
483 err
= body(baton
, subpool
);
485 svn_pool_destroy(subpool
);
488 status
= apr_thread_mutex_unlock(lock_mutex
);
490 return svn_error_wrap_apr(status
,
491 _("Can't ungrab FSFS mutex for '%s'"),
499 svn_fs_fs__with_write_lock(svn_fs_t
*fs
,
500 svn_error_t
*(*body
)(void *baton
,
506 fs_fs_data_t
*ffd
= fs
->fsap_data
;
507 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
508 apr_thread_mutex_t
*mutex
= ffsd
->fs_write_lock
;
511 return with_some_lock(body
, baton
,
519 /* Run BODY (with BATON and POOL) while the txn-current file
522 with_txn_current_lock(svn_fs_t
*fs
,
523 svn_error_t
*(*body
)(void *baton
,
529 fs_fs_data_t
*ffd
= fs
->fsap_data
;
530 fs_fs_shared_data_t
*ffsd
= ffd
->shared
;
531 apr_thread_mutex_t
*mutex
= ffsd
->txn_current_lock
;
534 return with_some_lock(body
, baton
,
535 path_txn_current_lock(fs
, pool
),
542 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
544 struct unlock_proto_rev_baton
550 /* Callback used in the implementation of unlock_proto_rev(). */
552 unlock_proto_rev_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
554 const struct unlock_proto_rev_baton
*b
= baton
;
555 const char *txn_id
= b
->txn_id
;
556 apr_file_t
*lockfile
= b
->lockcookie
;
557 fs_fs_shared_txn_data_t
*txn
= get_shared_txn(fs
, txn_id
, FALSE
);
558 apr_status_t apr_err
;
561 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
562 _("Can't unlock unknown transaction '%s'"),
564 if (!txn
->being_written
)
565 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
566 _("Can't unlock nonlocked transaction '%s'"),
569 apr_err
= apr_file_unlock(lockfile
);
571 return svn_error_wrap_apr
573 _("Can't unlock prototype revision lockfile for transaction '%s'"),
575 apr_err
= apr_file_close(lockfile
);
577 return svn_error_wrap_apr
579 _("Can't close prototype revision lockfile for transaction '%s'"),
582 txn
->being_written
= FALSE
;
587 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
588 FS using cookie LOCKCOOKIE. The original prototype revision file must
589 have been closed _before_ calling this function.
591 Perform temporary allocations in POOL. */
593 unlock_proto_rev(svn_fs_t
*fs
, const char *txn_id
, void *lockcookie
,
596 struct unlock_proto_rev_baton b
;
599 b
.lockcookie
= lockcookie
;
600 return with_txnlist_lock(fs
, unlock_proto_rev_body
, &b
, pool
);
603 /* Same as unlock_proto_rev(), but requires that the transaction list
604 lock is already held. */
606 unlock_proto_rev_list_locked(svn_fs_t
*fs
, const char *txn_id
,
610 struct unlock_proto_rev_baton b
;
613 b
.lockcookie
= lockcookie
;
614 return unlock_proto_rev_body(fs
, &b
, pool
);
617 /* A structure used by get_writable_proto_rev() and
618 get_writable_proto_rev_body(), which see. */
619 struct get_writable_proto_rev_baton
626 /* Callback used in the implementation of get_writable_proto_rev(). */
628 get_writable_proto_rev_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
630 const struct get_writable_proto_rev_baton
*b
= baton
;
631 apr_file_t
**file
= b
->file
;
632 void **lockcookie
= b
->lockcookie
;
633 const char *txn_id
= b
->txn_id
;
635 fs_fs_shared_txn_data_t
*txn
= get_shared_txn(fs
, txn_id
, TRUE
);
637 /* First, ensure that no thread in this process (including this one)
638 is currently writing to this transaction's proto-rev file. */
639 if (txn
->being_written
)
640 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN
, NULL
,
641 _("Cannot write to the prototype revision file "
642 "of transaction '%s' because a previous "
643 "representation is currently being written by "
648 /* We know that no thread in this process is writing to the proto-rev
649 file, and by extension, that no thread in this process is holding a
650 lock on the prototype revision lock file. It is therefore safe
651 for us to attempt to lock this file, to see if any other process
652 is holding a lock. */
655 apr_file_t
*lockfile
;
656 apr_status_t apr_err
;
657 const char *lockfile_path
= path_txn_proto_rev_lock(fs
, txn_id
, pool
);
659 /* Open the proto-rev lockfile, creating it if necessary, as it may
660 not exist if the transaction dates from before the lockfiles were
663 ### We'd also like to use something like svn_io_file_lock2(), but
664 that forces us to create a subpool just to be able to unlock
665 the file, which seems a waste. */
666 SVN_ERR(svn_io_file_open(&lockfile
, lockfile_path
,
667 APR_WRITE
| APR_CREATE
, APR_OS_DEFAULT
, pool
));
669 apr_err
= apr_file_lock(lockfile
,
670 APR_FLOCK_EXCLUSIVE
| APR_FLOCK_NONBLOCK
);
673 svn_error_clear(svn_io_file_close(lockfile
, pool
));
675 if (APR_STATUS_IS_EAGAIN(apr_err
))
676 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN
, NULL
,
677 _("Cannot write to the prototype revision "
678 "file of transaction '%s' because a "
679 "previous representation is currently "
680 "being written by another process"),
683 return svn_error_wrap_apr(apr_err
,
684 _("Can't get exclusive lock on file '%s'"),
685 svn_path_local_style(lockfile_path
, pool
));
688 *lockcookie
= lockfile
;
691 /* We've successfully locked the transaction; mark it as such. */
692 txn
->being_written
= TRUE
;
695 /* Now open the prototype revision file and seek to the end. */
696 err
= svn_io_file_open(file
, path_txn_proto_rev(fs
, txn_id
, pool
),
697 APR_WRITE
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
699 /* You might expect that we could dispense with the following seek
700 and achieve the same thing by opening the file using APR_APPEND.
701 Unfortunately, APR's buffered file implementation unconditionally
702 places its initial file pointer at the start of the file (even for
703 files opened with APR_APPEND), so we need this seek to reconcile
704 the APR file pointer to the OS file pointer (since we need to be
705 able to read the current file position later). */
708 apr_off_t offset
= 0;
709 err
= svn_io_file_seek(*file
, APR_END
, &offset
, 0);
714 svn_error_clear(unlock_proto_rev_list_locked(fs
, txn_id
, *lockcookie
,
722 /* Get a handle to the prototype revision file for transaction TXN_ID in
723 filesystem FS, and lock it for writing. Return FILE, a file handle
724 positioned at the end of the file, and LOCKCOOKIE, a cookie that
725 should be passed to unlock_proto_rev() to unlock the file once FILE
728 If the prototype revision file is already locked, return error
729 SVN_ERR_FS_REP_BEING_WRITTEN.
731 Perform all allocations in POOL. */
733 get_writable_proto_rev(apr_file_t
**file
,
735 svn_fs_t
*fs
, const char *txn_id
,
738 struct get_writable_proto_rev_baton b
;
741 b
.lockcookie
= lockcookie
;
744 return with_txnlist_lock(fs
, get_writable_proto_rev_body
, &b
, pool
);
747 /* Callback used in the implementation of purge_shared_txn(). */
749 purge_shared_txn_body(svn_fs_t
*fs
, const void *baton
, apr_pool_t
*pool
)
751 const char *txn_id
= *(const char **)baton
;
753 free_shared_txn(fs
, txn_id
);
757 /* Purge the shared data for transaction TXN_ID in filesystem FS.
758 Perform all allocations in POOL. */
760 purge_shared_txn(svn_fs_t
*fs
, const char *txn_id
, apr_pool_t
*pool
)
762 return with_txnlist_lock(fs
, purge_shared_txn_body
, (char **) &txn_id
, pool
);
767 /* Fetch the current offset of FILE into *OFFSET_P. */
769 get_file_offset(apr_off_t
*offset_p
, apr_file_t
*file
, apr_pool_t
*pool
)
773 /* Note that, for buffered files, one (possibly surprising) side-effect
774 of this call is to flush any unwritten data to disk. */
776 SVN_ERR(svn_io_file_seek(file
, APR_CUR
, &offset
, pool
));
783 /* Check that BUF, a buffer of text from format file PATH, contains
784 only digits, raising error SVN_ERR_BAD_VERSION_FILE_FORMAT if not.
786 Uses POOL for temporary allocation. */
788 check_format_file_buffer_numeric(const char *buf
, const char *path
,
793 for (p
= buf
; *p
; p
++)
794 if (!apr_isdigit(*p
))
795 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
796 _("Format file '%s' contains an unexpected non-digit"),
797 svn_path_local_style(path
, pool
));
802 /* Read the format number and maximum number of files per directory
803 from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
806 *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
807 will be set to zero if a linear scheme should be used.
809 Use POOL for temporary allocation. */
811 read_format(int *pformat
, int *max_files_per_dir
,
812 const char *path
, apr_pool_t
*pool
)
819 err
= svn_io_file_open(&file
, path
, APR_READ
| APR_BUFFERED
,
820 APR_OS_DEFAULT
, pool
);
821 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
823 /* Treat an absent format file as format 1. Do not try to
824 create the format file on the fly, because the repository
825 might be read-only for us, or this might be a read-only
826 operation, and the spirit of FSFS is to make no changes
827 whatseover in read-only operations. See thread starting at
828 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
830 svn_error_clear(err
);
832 *max_files_per_dir
= 0;
838 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
839 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
841 /* Return a more useful error message. */
842 svn_error_clear(err
);
843 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
844 _("Can't read first line of format file '%s'"),
845 svn_path_local_style(path
, pool
));
849 /* Check that the first line contains only digits. */
850 SVN_ERR(check_format_file_buffer_numeric(buf
, path
, pool
));
851 *pformat
= atoi(buf
);
853 /* Set the default values for anything that can be set via an option. */
854 *max_files_per_dir
= 0;
856 /* Read any options. */
860 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
861 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
863 /* No more options; that's okay. */
864 svn_error_clear(err
);
869 if (*pformat
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
&&
870 strncmp(buf
, "layout ", 7) == 0)
872 if (strcmp(buf
+7, "linear") == 0)
874 *max_files_per_dir
= 0;
878 if (strncmp(buf
+7, "sharded ", 8) == 0)
880 /* Check that the argument is numeric. */
881 SVN_ERR(check_format_file_buffer_numeric(buf
+15, path
, pool
));
882 *max_files_per_dir
= atoi(buf
+15);
887 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT
, NULL
,
888 _("'%s' contains invalid filesystem format option '%s'"),
889 svn_path_local_style(path
, pool
), buf
);
892 SVN_ERR(svn_io_file_close(file
, pool
));
897 /* Write the format number and maximum number of files per directory
898 to a new format file in PATH.
900 Use POOL for temporary allocation. */
902 write_format(const char *path
, int format
, int max_files_per_dir
,
905 /* svn_io_write_version_file() does a load of magic to allow it to
906 replace version files that already exist. Luckily, we never need to
908 const char *contents
;
910 assert (1 <= format
&& format
<= SVN_FS_FS__FORMAT_NUMBER
);
911 if (format
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
)
913 if (max_files_per_dir
)
914 contents
= apr_psprintf(pool
,
916 "layout sharded %d\n",
917 format
, max_files_per_dir
);
919 contents
= apr_psprintf(pool
,
926 contents
= apr_psprintf(pool
, "%d\n", format
);
929 /* Create the file */
930 SVN_ERR(svn_io_file_create(path
, contents
, pool
));
931 /* And set the perms to make it read only */
932 SVN_ERR(svn_io_set_file_read_only(path
, FALSE
, pool
));
938 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
939 number is not the same as a format number supported by this
942 check_format(int format
)
944 /* We support all formats from 1-current simultaneously */
945 if (1 <= format
&& format
<= SVN_FS_FS__FORMAT_NUMBER
)
948 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT
, NULL
,
949 _("Expected FS format between '1' and '%d'; found format '%d'"),
950 SVN_FS_FS__FORMAT_NUMBER
, format
);
954 get_youngest(svn_revnum_t
*youngest_p
, const char *fs_path
, apr_pool_t
*pool
);
957 svn_fs_fs__open(svn_fs_t
*fs
, const char *path
, apr_pool_t
*pool
)
959 fs_fs_data_t
*ffd
= fs
->fsap_data
;
960 apr_file_t
*uuid_file
;
961 int format
, max_files_per_dir
;
962 char buf
[APR_UUID_FORMATTED_LENGTH
+ 2];
965 fs
->path
= apr_pstrdup(fs
->pool
, path
);
967 /* Read the FS format number. */
968 SVN_ERR(read_format(&format
, &max_files_per_dir
,
969 path_format(fs
, pool
), pool
));
971 /* Now we've got a format number no matter what. */
972 ffd
->format
= format
;
973 ffd
->max_files_per_dir
= max_files_per_dir
;
974 SVN_ERR(check_format(format
));
976 /* Read in and cache the repository uuid. */
977 SVN_ERR(svn_io_file_open(&uuid_file
, path_uuid(fs
, pool
),
978 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
981 SVN_ERR(svn_io_read_length_line(uuid_file
, buf
, &limit
, pool
));
982 ffd
->uuid
= apr_pstrdup(fs
->pool
, buf
);
984 SVN_ERR(svn_io_file_close(uuid_file
, pool
));
986 SVN_ERR(get_youngest(&(ffd
->youngest_rev_cache
), path
, pool
));
991 /* SVN_ERR-like macros for dealing with ESTALE
993 * In NFS v3 and under, the server doesn't track opened files. If you
994 * unlink(2) or rename(2) a file held open by another process *on the
995 * same host*, that host's kernel typically renames the file to
996 * .nfsXXXX and automatically deletes that when it's no longer open,
997 * but this behavior is not required.
999 * For obvious reasons, this does not work *across hosts*. No one
1000 * knows about the opened file; not the server, and not the deleting
1001 * client. So the file vanishes, and the reader gets stale NFS file
1002 * handle. We have this problem with revprops files, current, and
1005 * Wrap opens and reads of such files with SVN_RETRY_ESTALE and closes
1006 * with SVN_IGNORE_ESTALE. Call these macros within a loop of
1007 * SVN_ESTALE_RETRY_COUNT iterations (though, realistically, the
1008 * second try will succeed). Make sure you put a break statement
1009 * after the close, at the end of your loop. Immediately after your
1010 * loop, return err if err.
1012 * You must initialize err to SVN_NO_ERROR, as these macros do not.
1015 #define SVN_ESTALE_RETRY_COUNT 10
1018 #define SVN_RETRY_ESTALE(err, expr) \
1020 /* Clear err here (svn_error_clear can safely be passed
1021 * SVN_NO_ERROR) rather than after finding ESTALE so we can return
1022 * the ESTALE error on the last iteration of the loop. */ \
1023 svn_error_clear(err); \
1027 if (APR_TO_OS_ERROR(err->apr_err) == ESTALE) \
1032 #define SVN_IGNORE_ESTALE(err, expr) \
1034 svn_error_clear(err); \
1038 if (APR_TO_OS_ERROR(err->apr_err) != ESTALE) \
1043 #define SVN_RETRY_ESTALE(err, expr) SVN_ERR(expr)
1044 #define SVN_IGNORE_ESTALE(err, expr) SVN_ERR(expr)
1047 /* Long enough to hold: "<svn_revnum_t> <node id> <copy id>\0"
1048 * 19 bytes for svn_revnum_t (room for 32 or 64 bit values)
1050 * + 26 bytes for each id (these are actually unbounded, so we just
1051 * have to pick something; 2^64 is 13 bytes in base-36)
1052 * + 1 terminating null
1054 #define CURRENT_BUF_LEN 48
1056 /* Read the 'current' file FNAME and store the contents in *BUF.
1057 Allocations are performed in POOL. */
1058 static svn_error_t
*
1059 read_current(const char *fname
, char **buf
, apr_pool_t
*pool
)
1061 apr_file_t
*revision_file
;
1064 svn_error_t
*err
= SVN_NO_ERROR
;
1065 apr_pool_t
*iterpool
;
1067 *buf
= apr_palloc(pool
, CURRENT_BUF_LEN
);
1068 iterpool
= svn_pool_create(pool
);
1069 for (i
= 0; i
< SVN_ESTALE_RETRY_COUNT
; i
++)
1071 svn_pool_clear(iterpool
);
1073 SVN_RETRY_ESTALE(err
, svn_io_file_open(&revision_file
, fname
,
1074 APR_READ
| APR_BUFFERED
,
1075 APR_OS_DEFAULT
, iterpool
));
1077 len
= CURRENT_BUF_LEN
;
1078 SVN_RETRY_ESTALE(err
, svn_io_read_length_line(revision_file
,
1079 *buf
, &len
, iterpool
));
1080 SVN_IGNORE_ESTALE(err
, svn_io_file_close(revision_file
, iterpool
));
1084 svn_pool_destroy(iterpool
);
1089 /* Find the youngest revision in a repository at path FS_PATH and
1090 return it in *YOUNGEST_P. Perform temporary allocations in
1092 static svn_error_t
*
1093 get_youngest(svn_revnum_t
*youngest_p
,
1094 const char *fs_path
,
1099 SVN_ERR(read_current(svn_path_join(fs_path
, PATH_CURRENT
, pool
),
1102 *youngest_p
= SVN_STR_TO_REV(buf
);
1104 return SVN_NO_ERROR
;
1108 svn_fs_fs__hotcopy(const char *src_path
,
1109 const char *dst_path
,
1112 const char *src_subdir
, *dst_subdir
;
1113 svn_revnum_t youngest
, rev
;
1114 apr_pool_t
*iterpool
;
1115 svn_node_kind_t kind
;
1116 int format
, max_files_per_dir
;
1118 /* Check format to be sure we know how to hotcopy this FS. */
1119 SVN_ERR(read_format(&format
, &max_files_per_dir
,
1120 svn_path_join(src_path
, PATH_FORMAT
, pool
),
1122 SVN_ERR(check_format(format
));
1124 /* Copy the current file. */
1125 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_CURRENT
, pool
));
1127 /* Copy the uuid. */
1128 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_UUID
, pool
));
1130 /* Find the youngest revision from this current file. */
1131 SVN_ERR(get_youngest(&youngest
, dst_path
, pool
));
1133 /* Copy the necessary rev files. */
1134 src_subdir
= svn_path_join(src_path
, PATH_REVS_DIR
, pool
);
1135 dst_subdir
= svn_path_join(dst_path
, PATH_REVS_DIR
, pool
);
1137 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1139 iterpool
= svn_pool_create(pool
);
1140 for (rev
= 0; rev
<= youngest
; rev
++)
1142 const char *src_subdir_shard
= src_subdir
,
1143 *dst_subdir_shard
= dst_subdir
;
1145 if (max_files_per_dir
)
1147 const char *shard
= apr_psprintf(iterpool
, "%ld",
1148 rev
/ max_files_per_dir
);
1149 src_subdir_shard
= svn_path_join(src_subdir
, shard
, iterpool
);
1150 dst_subdir_shard
= svn_path_join(dst_subdir
, shard
, iterpool
);
1152 if (rev
% max_files_per_dir
== 0)
1153 SVN_ERR(svn_io_dir_make(dst_subdir_shard
, APR_OS_DEFAULT
,
1157 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard
, dst_subdir_shard
,
1158 apr_psprintf(iterpool
, "%ld", rev
),
1160 svn_pool_clear(iterpool
);
1163 /* Copy the necessary revprop files. */
1164 src_subdir
= svn_path_join(src_path
, PATH_REVPROPS_DIR
, pool
);
1165 dst_subdir
= svn_path_join(dst_path
, PATH_REVPROPS_DIR
, pool
);
1167 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1169 for (rev
= 0; rev
<= youngest
; rev
++)
1171 const char *src_subdir_shard
= src_subdir
,
1172 *dst_subdir_shard
= dst_subdir
;
1174 svn_pool_clear(iterpool
);
1176 if (max_files_per_dir
)
1178 const char *shard
= apr_psprintf(iterpool
, "%ld",
1179 rev
/ max_files_per_dir
);
1180 src_subdir_shard
= svn_path_join(src_subdir
, shard
, iterpool
);
1181 dst_subdir_shard
= svn_path_join(dst_subdir
, shard
, iterpool
);
1183 if (rev
% max_files_per_dir
== 0)
1184 SVN_ERR(svn_io_dir_make(dst_subdir_shard
, APR_OS_DEFAULT
,
1188 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard
, dst_subdir_shard
,
1189 apr_psprintf(iterpool
, "%ld", rev
),
1193 svn_pool_destroy(iterpool
);
1195 /* Make an empty transactions directory for now. Eventually some
1196 method of copying in progress transactions will need to be
1198 dst_subdir
= svn_path_join(dst_path
, PATH_TXNS_DIR
, pool
);
1199 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1200 if (format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
1202 dst_subdir
= svn_path_join(dst_path
, PATH_TXN_PROTOS_DIR
, pool
);
1203 SVN_ERR(svn_io_make_dir_recursively(dst_subdir
, pool
));
1206 /* Now copy the locks tree. */
1207 src_subdir
= svn_path_join(src_path
, PATH_LOCKS_DIR
, pool
);
1208 SVN_ERR(svn_io_check_path(src_subdir
, &kind
, pool
));
1209 if (kind
== svn_node_dir
)
1210 SVN_ERR(svn_io_copy_dir_recursively(src_subdir
, dst_path
,
1211 PATH_LOCKS_DIR
, TRUE
, NULL
,
1214 /* Now copy the node-origins cache tree. */
1215 src_subdir
= svn_path_join(src_path
, PATH_NODE_ORIGINS_DIR
, pool
);
1216 SVN_ERR(svn_io_check_path(src_subdir
, &kind
, pool
));
1217 if (kind
== svn_node_dir
)
1218 SVN_ERR(svn_io_copy_dir_recursively(src_subdir
, dst_path
,
1219 PATH_NODE_ORIGINS_DIR
, TRUE
, NULL
,
1222 /* Copy the txn-current file. */
1223 if (format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
1224 SVN_ERR(svn_io_dir_file_copy(src_path
, dst_path
, PATH_TXN_CURRENT
, pool
));
1226 /* Hotcopied FS is complete. Stamp it with a format file. */
1227 SVN_ERR(write_format(svn_path_join(dst_path
, PATH_FORMAT
, pool
),
1228 format
, max_files_per_dir
, pool
));
1230 return SVN_NO_ERROR
;
1234 svn_fs_fs__youngest_rev(svn_revnum_t
*youngest_p
,
1238 fs_fs_data_t
*ffd
= fs
->fsap_data
;
1240 SVN_ERR(get_youngest(youngest_p
, fs
->path
, pool
));
1241 ffd
->youngest_rev_cache
= *youngest_p
;
1243 return SVN_NO_ERROR
;
1246 /* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN
1247 * bytes plus the stuff around them. */
1248 #define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1
1250 /* Given a revision file FILE that has been pre-positioned at the
1251 beginning of a Node-Rev header block, read in that header block and
1252 store it in the apr_hash_t HEADERS. All allocations will be from
1254 static svn_error_t
* read_header_block(apr_hash_t
**headers
,
1258 *headers
= apr_hash_make(pool
);
1262 char header_str
[MAX_HEADERS_STR_LEN
];
1263 const char *name
, *value
;
1264 apr_size_t i
= 0, header_len
;
1266 char *local_name
, *local_value
;
1268 limit
= sizeof(header_str
);
1269 SVN_ERR(svn_io_read_length_line(file
, header_str
, &limit
, pool
));
1271 if (strlen(header_str
) == 0)
1272 break; /* end of header block */
1274 header_len
= strlen(header_str
);
1276 while (header_str
[i
] != ':')
1278 if (header_str
[i
] == '\0')
1279 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1280 _("Found malformed header in "
1285 /* Create a 'name' string and point to it. */
1286 header_str
[i
] = '\0';
1289 /* Skip over the NULL byte and the space following it. */
1293 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1294 _("Found malformed header in "
1297 value
= header_str
+ i
;
1299 local_name
= apr_pstrdup(pool
, name
);
1300 local_value
= apr_pstrdup(pool
, value
);
1302 apr_hash_set(*headers
, local_name
, APR_HASH_KEY_STRING
, local_value
);
1305 return SVN_NO_ERROR
;
1308 /* Throw an error if the given revision is newer than the current
1309 youngest revision. */
1310 static svn_error_t
*
1311 ensure_revision_exists(svn_fs_t
*fs
,
1315 fs_fs_data_t
*ffd
= fs
->fsap_data
;
1317 if (! SVN_IS_VALID_REVNUM(rev
))
1318 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1319 _("Invalid revision number '%ld'"), rev
);
1322 /* Did the revision exist the last time we checked the current
1324 if (rev
<= ffd
->youngest_rev_cache
)
1325 return SVN_NO_ERROR
;
1327 SVN_ERR(get_youngest(&(ffd
->youngest_rev_cache
), fs
->path
, pool
));
1330 if (rev
<= ffd
->youngest_rev_cache
)
1331 return SVN_NO_ERROR
;
1333 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1334 _("No such revision %ld"), rev
);
1337 /* Open the revision file for revision REV in filesystem FS and store
1338 the newly opened file in FILE. Seek to location OFFSET before
1339 returning. Perform temporary allocations in POOL. */
1340 static svn_error_t
*
1341 open_and_seek_revision(apr_file_t
**file
,
1347 apr_file_t
*rev_file
;
1349 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
1351 SVN_ERR(svn_io_file_open(&rev_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
1352 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1354 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1358 return SVN_NO_ERROR
;
1361 /* Open the representation for a node-revision in transaction TXN_ID
1362 in filesystem FS and store the newly opened file in FILE. Seek to
1363 location OFFSET before returning. Perform temporary allocations in
1364 POOL. Only appropriate for file contents, nor props or directory
1366 static svn_error_t
*
1367 open_and_seek_transaction(apr_file_t
**file
,
1370 representation_t
*rep
,
1373 apr_file_t
*rev_file
;
1376 SVN_ERR(svn_io_file_open(&rev_file
, path_txn_proto_rev(fs
, txn_id
, pool
),
1377 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1379 offset
= rep
->offset
;
1380 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1384 return SVN_NO_ERROR
;
1387 /* Given a node-id ID, and a representation REP in filesystem FS, open
1388 the correct file and seek to the correction location. Store this
1389 file in *FILE_P. Perform any allocations in POOL. */
1390 static svn_error_t
*
1391 open_and_seek_representation(apr_file_t
**file_p
,
1393 representation_t
*rep
,
1397 return open_and_seek_revision(file_p
, fs
, rep
->revision
, rep
->offset
,
1400 return open_and_seek_transaction(file_p
, fs
, rep
->txn_id
, rep
, pool
);
1403 /* Parse the description of a representation from STRING and store it
1404 into *REP_P. If the representation is mutable (the revision is
1405 given as -1), then use TXN_ID for the representation's txn_id
1406 field. If MUTABLE_REP_TRUNCATED is true, then this representation
1407 is for property or directory contents, and no information will be
1408 expected except the "-1" revision number for a mutable
1409 representation. Allocate *REP_P in POOL. */
1410 static svn_error_t
*
1411 read_rep_offsets(representation_t
**rep_p
,
1414 svn_boolean_t mutable_rep_truncated
,
1417 representation_t
*rep
;
1418 char *str
, *last_str
;
1421 rep
= apr_pcalloc(pool
, sizeof(*rep
));
1424 str
= apr_strtok(string
, " ", &last_str
);
1426 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1427 _("Malformed text rep offset line in node-rev"));
1430 rep
->revision
= SVN_STR_TO_REV(str
);
1431 if (rep
->revision
== SVN_INVALID_REVNUM
)
1433 rep
->txn_id
= txn_id
;
1434 if (mutable_rep_truncated
)
1435 return SVN_NO_ERROR
;
1438 str
= apr_strtok(NULL
, " ", &last_str
);
1440 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1441 _("Malformed text rep offset line in node-rev"));
1443 rep
->offset
= apr_atoi64(str
);
1445 str
= apr_strtok(NULL
, " ", &last_str
);
1447 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1448 _("Malformed text rep offset line in node-rev"));
1450 rep
->size
= apr_atoi64(str
);
1452 str
= apr_strtok(NULL
, " ", &last_str
);
1454 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1455 _("Malformed text rep offset line in node-rev"));
1457 rep
->expanded_size
= apr_atoi64(str
);
1459 /* Read in the MD5 hash. */
1460 str
= apr_strtok(NULL
, " ", &last_str
);
1461 if ((str
== NULL
) || (strlen(str
) != (APR_MD5_DIGESTSIZE
* 2)))
1462 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1463 _("Malformed text rep offset line in node-rev"));
1465 /* Parse the hex MD5 hash into digest form. */
1466 for (i
= 0; i
< APR_MD5_DIGESTSIZE
; i
++)
1468 if ((! isxdigit(str
[i
* 2])) || (! isxdigit(str
[i
* 2 + 1])))
1469 return svn_error_create
1470 (SVN_ERR_FS_CORRUPT
, NULL
,
1471 _("Malformed text rep offset line in node-rev"));
1473 str
[i
* 2] = tolower(str
[i
* 2]);
1474 rep
->checksum
[i
] = (str
[i
* 2] -
1475 ((str
[i
* 2] <= '9') ? '0' : ('a' - 10))) << 4;
1477 str
[i
* 2 + 1] = tolower(str
[i
* 2 + 1]);
1478 rep
->checksum
[i
] |= (str
[i
* 2 + 1] -
1479 ((str
[i
* 2 + 1] <= '9') ? '0' : ('a' - 10)));
1482 return SVN_NO_ERROR
;
1486 svn_fs_fs__get_node_revision(node_revision_t
**noderev_p
,
1488 const svn_fs_id_t
*id
,
1491 apr_file_t
*revision_file
;
1492 apr_hash_t
*headers
;
1493 node_revision_t
*noderev
;
1497 if (svn_fs_fs__id_txn_id(id
))
1499 /* This is a transaction node-rev. */
1500 err
= svn_io_file_open(&revision_file
, path_txn_node_rev(fs
, id
, pool
),
1501 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
1505 /* This is a revision node-rev. */
1506 err
= open_and_seek_revision(&revision_file
, fs
,
1507 svn_fs_fs__id_rev(id
),
1508 svn_fs_fs__id_offset(id
),
1514 if (APR_STATUS_IS_ENOENT(err
->apr_err
))
1516 svn_error_clear(err
);
1517 return svn_fs_fs__err_dangling_id(fs
, id
);
1523 SVN_ERR(read_header_block(&headers
, revision_file
, pool
) );
1525 noderev
= apr_pcalloc(pool
, sizeof(*noderev
));
1527 /* Read the node-rev id. */
1528 value
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
1530 SVN_ERR(svn_io_file_close(revision_file
, pool
));
1532 noderev
->id
= svn_fs_fs__id_parse(value
, strlen(value
), pool
);
1534 /* Read the type. */
1535 value
= apr_hash_get(headers
, HEADER_TYPE
, APR_HASH_KEY_STRING
);
1537 if ((value
== NULL
) ||
1538 (strcmp(value
, KIND_FILE
) != 0 && strcmp(value
, KIND_DIR
)))
1539 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1540 _("Missing kind field in node-rev"));
1542 noderev
->kind
= (strcmp(value
, KIND_FILE
) == 0) ? svn_node_file
1545 /* Read the 'count' field. */
1546 value
= apr_hash_get(headers
, HEADER_COUNT
, APR_HASH_KEY_STRING
);
1547 noderev
->predecessor_count
= (value
== NULL
) ? 0 : atoi(value
);
1549 /* Get the properties location. */
1550 value
= apr_hash_get(headers
, HEADER_PROPS
, APR_HASH_KEY_STRING
);
1553 SVN_ERR(read_rep_offsets(&noderev
->prop_rep
, value
,
1554 svn_fs_fs__id_txn_id(id
), TRUE
, pool
));
1557 /* Get the data location. */
1558 value
= apr_hash_get(headers
, HEADER_TEXT
, APR_HASH_KEY_STRING
);
1561 SVN_ERR(read_rep_offsets(&noderev
->data_rep
, value
,
1562 svn_fs_fs__id_txn_id(id
),
1563 (noderev
->kind
== svn_node_dir
), pool
));
1566 /* Get the created path. */
1567 value
= apr_hash_get(headers
, HEADER_CPATH
, APR_HASH_KEY_STRING
);
1570 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1571 _("Missing cpath in node-rev"));
1575 noderev
->created_path
= apr_pstrdup(pool
, value
);
1578 /* Get the predecessor ID. */
1579 value
= apr_hash_get(headers
, HEADER_PRED
, APR_HASH_KEY_STRING
);
1581 noderev
->predecessor_id
= svn_fs_fs__id_parse(value
, strlen(value
),
1584 /* Get the copyroot. */
1585 value
= apr_hash_get(headers
, HEADER_COPYROOT
, APR_HASH_KEY_STRING
);
1588 noderev
->copyroot_path
= apr_pstrdup(pool
, noderev
->created_path
);
1589 noderev
->copyroot_rev
= svn_fs_fs__id_rev(noderev
->id
);
1593 char *str
, *last_str
;
1595 str
= apr_strtok(value
, " ", &last_str
);
1597 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1598 _("Malformed copyroot line in node-rev"));
1600 noderev
->copyroot_rev
= atoi(str
);
1602 if (last_str
== NULL
)
1603 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1604 _("Malformed copyroot line in node-rev"));
1605 noderev
->copyroot_path
= apr_pstrdup(pool
, last_str
);
1608 /* Get the copyfrom. */
1609 value
= apr_hash_get(headers
, HEADER_COPYFROM
, APR_HASH_KEY_STRING
);
1612 noderev
->copyfrom_path
= NULL
;
1613 noderev
->copyfrom_rev
= SVN_INVALID_REVNUM
;
1617 char *str
, *last_str
;
1619 str
= apr_strtok(value
, " ", &last_str
);
1621 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1622 _("Malformed copyfrom line in node-rev"));
1624 noderev
->copyfrom_rev
= atoi(str
);
1626 if (last_str
== NULL
)
1627 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1628 _("Malformed copyfrom line in node-rev"));
1629 noderev
->copyfrom_path
= apr_pstrdup(pool
, last_str
);
1632 /* Get whether this is a fresh txn root. */
1633 value
= apr_hash_get(headers
, HEADER_FRESHTXNRT
, APR_HASH_KEY_STRING
);
1634 noderev
->is_fresh_txn_root
= (value
!= NULL
);
1636 /* Get the mergeinfo count. */
1637 value
= apr_hash_get(headers
, HEADER_MINFO_CNT
, APR_HASH_KEY_STRING
);
1638 noderev
->mergeinfo_count
= (value
== NULL
) ? 0 : apr_atoi64(value
);
1640 /* Get whether *this* node has mergeinfo. */
1641 value
= apr_hash_get(headers
, HEADER_MINFO_HERE
, APR_HASH_KEY_STRING
);
1642 noderev
->has_mergeinfo
= (value
!= NULL
);
1644 *noderev_p
= noderev
;
1646 return SVN_NO_ERROR
;
1649 /* Return a formatted string that represents the location of
1650 representation REP. If MUTABLE_REP_TRUNCATED is given, the rep is
1651 for props or dir contents, and only a "-1" revision number will be
1652 given for a mutable rep. Perform the allocation from POOL. */
1654 representation_string(representation_t
*rep
,
1655 svn_boolean_t mutable_rep_truncated
, apr_pool_t
*pool
)
1657 if (rep
->txn_id
&& mutable_rep_truncated
)
1660 return apr_psprintf(pool
, "%ld %" APR_OFF_T_FMT
" %" SVN_FILESIZE_T_FMT
1661 " %" SVN_FILESIZE_T_FMT
" %s",
1662 rep
->revision
, rep
->offset
, rep
->size
,
1664 svn_md5_digest_to_cstring_display(rep
->checksum
,
1668 /* Write the node-revision NODEREV into the file FILE. Temporary
1669 allocations are from POOL. */
1670 static svn_error_t
*
1671 write_noderev_txn(apr_file_t
*file
,
1672 node_revision_t
*noderev
,
1675 svn_stream_t
*outfile
;
1677 outfile
= svn_stream_from_aprfile(file
, pool
);
1679 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_ID
": %s\n",
1680 svn_fs_fs__id_unparse(noderev
->id
,
1683 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_TYPE
": %s\n",
1684 (noderev
->kind
== svn_node_file
) ?
1685 KIND_FILE
: KIND_DIR
));
1687 if (noderev
->predecessor_id
)
1688 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_PRED
": %s\n",
1689 svn_fs_fs__id_unparse(noderev
->predecessor_id
,
1692 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COUNT
": %d\n",
1693 noderev
->predecessor_count
));
1695 if (noderev
->data_rep
)
1696 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_TEXT
": %s\n",
1697 representation_string(noderev
->data_rep
,
1702 if (noderev
->prop_rep
)
1703 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_PROPS
": %s\n",
1704 representation_string(noderev
->prop_rep
, TRUE
,
1707 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_CPATH
": %s\n",
1708 noderev
->created_path
));
1710 if (noderev
->copyfrom_path
)
1711 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COPYFROM
": %ld"
1713 noderev
->copyfrom_rev
,
1714 noderev
->copyfrom_path
));
1716 if ((noderev
->copyroot_rev
!= svn_fs_fs__id_rev(noderev
->id
)) ||
1717 (strcmp(noderev
->copyroot_path
, noderev
->created_path
) != 0))
1718 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_COPYROOT
": %ld"
1720 noderev
->copyroot_rev
,
1721 noderev
->copyroot_path
));
1723 if (noderev
->is_fresh_txn_root
)
1724 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_FRESHTXNRT
": y\n"));
1726 if (noderev
->mergeinfo_count
> 0)
1727 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_MINFO_CNT
": %"
1728 APR_INT64_T_FMT
"\n",
1729 noderev
->mergeinfo_count
));
1731 if (noderev
->has_mergeinfo
)
1732 SVN_ERR(svn_stream_printf(outfile
, pool
, HEADER_MINFO_HERE
": y\n"));
1734 SVN_ERR(svn_stream_printf(outfile
, pool
, "\n"));
1736 return SVN_NO_ERROR
;
1740 svn_fs_fs__put_node_revision(svn_fs_t
*fs
,
1741 const svn_fs_id_t
*id
,
1742 node_revision_t
*noderev
,
1743 svn_boolean_t fresh_txn_root
,
1746 apr_file_t
*noderev_file
;
1747 const char *txn_id
= svn_fs_fs__id_txn_id(id
);
1749 noderev
->is_fresh_txn_root
= fresh_txn_root
;
1752 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1753 _("Attempted to write to non-transaction"));
1755 SVN_ERR(svn_io_file_open(&noderev_file
, path_txn_node_rev(fs
, id
, pool
),
1756 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
1757 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
1759 SVN_ERR(write_noderev_txn(noderev_file
, noderev
, pool
));
1761 SVN_ERR(svn_io_file_close(noderev_file
, pool
));
1763 return SVN_NO_ERROR
;
1767 /* This structure is used to hold the information associated with a
1771 svn_boolean_t is_delta
;
1772 svn_boolean_t is_delta_vs_empty
;
1774 svn_revnum_t base_revision
;
1775 apr_off_t base_offset
;
1776 apr_size_t base_length
;
1779 /* Read the next line from file FILE and parse it as a text
1780 representation entry. Return the parsed entry in *REP_ARGS_P.
1781 Perform all allocations in POOL. */
1782 static svn_error_t
*
1783 read_rep_line(struct rep_args
**rep_args_p
,
1789 struct rep_args
*rep_args
;
1790 char *str
, *last_str
;
1792 limit
= sizeof(buffer
);
1793 SVN_ERR(svn_io_read_length_line(file
, buffer
, &limit
, pool
));
1795 rep_args
= apr_pcalloc(pool
, sizeof(*rep_args
));
1796 rep_args
->is_delta
= FALSE
;
1798 if (strcmp(buffer
, REP_PLAIN
) == 0)
1800 *rep_args_p
= rep_args
;
1801 return SVN_NO_ERROR
;
1804 if (strcmp(buffer
, REP_DELTA
) == 0)
1806 /* This is a delta against the empty stream. */
1807 rep_args
->is_delta
= TRUE
;
1808 rep_args
->is_delta_vs_empty
= TRUE
;
1809 *rep_args_p
= rep_args
;
1810 return SVN_NO_ERROR
;
1813 rep_args
->is_delta
= TRUE
;
1814 rep_args
->is_delta_vs_empty
= FALSE
;
1816 /* We have hopefully a DELTA vs. a non-empty base revision. */
1817 str
= apr_strtok(buffer
, " ", &last_str
);
1818 if (! str
|| (strcmp(str
, REP_DELTA
) != 0)) goto err
;
1820 str
= apr_strtok(NULL
, " ", &last_str
);
1821 if (! str
) goto err
;
1822 rep_args
->base_revision
= atol(str
);
1824 str
= apr_strtok(NULL
, " ", &last_str
);
1825 if (! str
) goto err
;
1826 rep_args
->base_offset
= (apr_off_t
) apr_atoi64(str
);
1828 str
= apr_strtok(NULL
, " ", &last_str
);
1829 if (! str
) goto err
;
1830 rep_args
->base_length
= (apr_size_t
) apr_atoi64(str
);
1832 *rep_args_p
= rep_args
;
1833 return SVN_NO_ERROR
;
1836 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1837 _("Malformed representation header"));
1840 /* Given a revision file REV_FILE, find the Node-ID of the header
1841 located at OFFSET and store it in *ID_P. Allocate temporary
1842 variables from POOL. */
1843 static svn_error_t
*
1844 get_fs_id_at_offset(svn_fs_id_t
**id_p
,
1845 apr_file_t
*rev_file
,
1850 apr_hash_t
*headers
;
1851 const char *node_id_str
;
1853 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1855 SVN_ERR(read_header_block(&headers
, rev_file
, pool
));
1857 node_id_str
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
1859 if (node_id_str
== NULL
)
1860 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1861 _("Missing node-id in node-rev"));
1863 id
= svn_fs_fs__id_parse(node_id_str
, strlen(node_id_str
), pool
);
1866 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1867 _("Corrupt node-id in node-rev"));
1871 return SVN_NO_ERROR
;
1875 /* Given an open revision file REV_FILE, locate the trailer that
1876 specifies the offset to the root node-id and to the changed path
1877 information. Store the root node offset in *ROOT_OFFSET and the
1878 changed path offset in *CHANGES_OFFSET. If either of these
1879 pointers is NULL, do nothing with it. Allocate temporary variables
1881 static svn_error_t
*
1882 get_root_changes_offset(apr_off_t
*root_offset
,
1883 apr_off_t
*changes_offset
,
1884 apr_file_t
*rev_file
,
1892 /* We will assume that the last line containing the two offsets
1893 will never be longer than 64 characters. */
1895 SVN_ERR(svn_io_file_seek(rev_file
, APR_END
, &offset
, pool
));
1897 offset
-= sizeof(buf
);
1898 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
1900 /* Read in this last block, from which we will identify the last line. */
1902 SVN_ERR(svn_io_file_read(rev_file
, buf
, &len
, pool
));
1904 /* This cast should be safe since the maximum amount read, 64, will
1905 never be bigger than the size of an int. */
1906 num_bytes
= (int) len
;
1908 /* The last byte should be a newline. */
1909 if (buf
[num_bytes
- 1] != '\n')
1911 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
1912 _("Revision file lacks trailing newline"));
1915 /* Look for the next previous newline. */
1916 for (i
= num_bytes
- 2; i
>= 0; i
--)
1918 if (buf
[i
] == '\n') break;
1923 return svn_error_createf(SVN_ERR_FS_CORRUPT
, NULL
,
1924 _("Final line in revision file longer than 64 "
1931 *root_offset
= apr_atoi64(&buf
[i
]);
1933 /* find the next space */
1934 for ( ; i
< (num_bytes
- 2) ; i
++)
1935 if (buf
[i
] == ' ') break;
1937 if (i
== (num_bytes
- 2))
1938 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
1939 _("Final line in revision file missing space"));
1943 /* note that apr_atoi64() will stop reading as soon as it encounters
1944 the final newline. */
1946 *changes_offset
= apr_atoi64(&buf
[i
]);
1948 return SVN_NO_ERROR
;
1952 svn_fs_fs__rev_get_root(svn_fs_id_t
**root_id_p
,
1957 fs_fs_data_t
*ffd
= fs
->fsap_data
;
1958 apr_file_t
*revision_file
;
1959 apr_off_t root_offset
;
1960 svn_fs_id_t
*root_id
;
1962 const char *rev_str
= apr_psprintf(ffd
->rev_root_id_cache_pool
, "%ld", rev
);
1963 svn_fs_id_t
*cached_id
;
1965 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
1967 /* Calculate an index into the revroot id cache */
1968 cached_id
= apr_hash_get(ffd
->rev_root_id_cache
,
1970 APR_HASH_KEY_STRING
);
1974 *root_id_p
= svn_fs_fs__id_copy(cached_id
, pool
);
1975 return SVN_NO_ERROR
;
1978 err
= svn_io_file_open(&revision_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
1979 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
);
1980 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
1982 svn_error_clear(err
);
1983 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
1984 _("No such revision %ld"), rev
);
1990 SVN_ERR(get_root_changes_offset(&root_offset
, NULL
, revision_file
, pool
));
1992 SVN_ERR(get_fs_id_at_offset(&root_id
, revision_file
, root_offset
, pool
));
1994 SVN_ERR(svn_io_file_close(revision_file
, pool
));
1997 if (apr_hash_count(ffd
->rev_root_id_cache
) >= NUM_RRI_CACHE_ENTRIES
)
1999 /* In order to only use one pool for the whole cache, we need to
2000 * completely wipe it to expire entries! */
2001 svn_pool_clear(ffd
->rev_root_id_cache_pool
);
2002 ffd
->rev_root_id_cache
= apr_hash_make(ffd
->rev_root_id_cache_pool
);
2004 apr_hash_set(ffd
->rev_root_id_cache
, rev_str
, APR_HASH_KEY_STRING
,
2005 svn_fs_fs__id_copy(root_id
, ffd
->rev_root_id_cache_pool
));
2007 *root_id_p
= root_id
;
2009 return SVN_NO_ERROR
;
2013 svn_fs_fs__set_revision_proplist(svn_fs_t
*fs
,
2015 apr_hash_t
*proplist
,
2018 const char *final_path
= path_revprops(fs
, rev
, pool
);
2019 const char *tmp_path
;
2022 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
2024 SVN_ERR(svn_io_open_unique_file2
2025 (&f
, &tmp_path
, final_path
, ".tmp", svn_io_file_del_none
, pool
));
2026 SVN_ERR(svn_hash_write(proplist
, f
, pool
));
2027 SVN_ERR(svn_io_file_close(f
, pool
));
2028 /* We use the rev file of this revision as the perms reference,
2029 because when setting revprops for the first time, the revprop
2030 file won't exist and therefore can't serve as its own reference.
2031 (Whereas the rev file should already exist at this point.) */
2032 SVN_ERR(svn_fs_fs__move_into_place(tmp_path
, final_path
,
2033 svn_fs_fs__path_rev(fs
, rev
, pool
),
2036 return SVN_NO_ERROR
;
2040 svn_fs_fs__revision_proplist(apr_hash_t
**proplist_p
,
2045 apr_file_t
*revprop_file
;
2046 apr_hash_t
*proplist
;
2047 svn_error_t
*err
= SVN_NO_ERROR
;
2049 apr_pool_t
*iterpool
;
2051 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
2053 proplist
= apr_hash_make(pool
);
2054 iterpool
= svn_pool_create(pool
);
2055 for (i
= 0; i
< SVN_ESTALE_RETRY_COUNT
; i
++)
2057 svn_pool_clear(iterpool
);
2059 /* Clear err here (svn_error_clear can safely be passed
2060 * SVN_NO_ERROR) rather than after finding ESTALE so we can
2061 * return the ESTALE error on the last iteration of the loop. */
2062 svn_error_clear(err
);
2063 err
= svn_io_file_open(&revprop_file
, path_revprops(fs
, rev
, iterpool
),
2064 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
2068 if (APR_STATUS_IS_ENOENT(err
->apr_err
))
2070 svn_error_clear(err
);
2071 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
2072 _("No such revision %ld"), rev
);
2075 else if (APR_TO_OS_ERROR(err
->apr_err
) == ESTALE
)
2081 SVN_ERR(svn_hash__clear(proplist
));
2082 SVN_RETRY_ESTALE(err
,
2083 svn_hash_read2(proplist
,
2084 svn_stream_from_aprfile(revprop_file
,
2086 SVN_HASH_TERMINATOR
, pool
));
2088 SVN_IGNORE_ESTALE(err
, svn_io_file_close(revprop_file
, iterpool
));
2094 svn_pool_destroy(iterpool
);
2096 *proplist_p
= proplist
;
2098 return SVN_NO_ERROR
;
2101 /* Represents where in the current svndiff data block each
2102 representation is. */
2106 apr_off_t start
; /* The starting offset for the raw
2107 svndiff/plaintext data minus header. */
2108 apr_off_t off
; /* The current offset into the file. */
2109 apr_off_t end
; /* The end offset of the raw data. */
2110 int ver
; /* If a delta, what svndiff version? */
2114 /* Read the rep args for REP in filesystem FS and create a rep_state
2115 for reading the representation. Return the rep_state in *REP_STATE
2116 and the rep args in *REP_ARGS, both allocated in POOL. */
2117 static svn_error_t
*
2118 create_rep_state(struct rep_state
**rep_state
,
2119 struct rep_args
**rep_args
,
2120 representation_t
*rep
,
2124 struct rep_state
*rs
= apr_pcalloc(pool
, sizeof(*rs
));
2125 struct rep_args
*ra
;
2126 unsigned char buf
[4];
2128 SVN_ERR(open_and_seek_representation(&rs
->file
, fs
, rep
, pool
));
2129 SVN_ERR(read_rep_line(&ra
, rs
->file
, pool
));
2130 SVN_ERR(get_file_offset(&rs
->start
, rs
->file
, pool
));
2131 rs
->off
= rs
->start
;
2132 rs
->end
= rs
->start
+ rep
->size
;
2136 if (ra
->is_delta
== FALSE
)
2137 /* This is a plaintext, so just return the current rep_state. */
2138 return SVN_NO_ERROR
;
2140 /* We are dealing with a delta, find out what version. */
2141 SVN_ERR(svn_io_file_read_full(rs
->file
, buf
, sizeof(buf
), NULL
, pool
));
2142 if (! ((buf
[0] == 'S') && (buf
[1] == 'V') && (buf
[2] == 'N')))
2143 return svn_error_create
2144 (SVN_ERR_FS_CORRUPT
, NULL
,
2145 _("Malformed svndiff data in representation"));
2147 rs
->chunk_index
= 0;
2150 return SVN_NO_ERROR
;
2153 /* Build an array of rep_state structures in *LIST giving the delta
2154 reps from first_rep to a plain-text or self-compressed rep. Set
2155 *SRC_STATE to the plain-text rep we find at the end of the chain,
2156 or to NULL if the final delta representation is self-compressed.
2157 The representation to start from is designated by filesystem FS, id
2158 ID, and representation REP. */
2159 static svn_error_t
*
2160 build_rep_list(apr_array_header_t
**list
,
2161 struct rep_state
**src_state
,
2163 representation_t
*first_rep
,
2166 representation_t rep
;
2167 struct rep_state
*rs
;
2168 struct rep_args
*rep_args
;
2170 *list
= apr_array_make(pool
, 1, sizeof(struct rep_state
*));
2175 SVN_ERR(create_rep_state(&rs
, &rep_args
, &rep
, fs
, pool
));
2176 if (rep_args
->is_delta
== FALSE
)
2178 /* This is a plaintext, so just return the current rep_state. */
2180 return SVN_NO_ERROR
;
2183 /* Push this rep onto the list. If it's self-compressed, we're done. */
2184 APR_ARRAY_PUSH(*list
, struct rep_state
*) = rs
;
2185 if (rep_args
->is_delta_vs_empty
)
2188 return SVN_NO_ERROR
;
2191 rep
.revision
= rep_args
->base_revision
;
2192 rep
.offset
= rep_args
->base_offset
;
2193 rep
.size
= rep_args
->base_length
;
2199 struct rep_read_baton
2201 /* The FS from which we're reading. */
2204 /* The state of all prior delta representations. */
2205 apr_array_header_t
*rs_list
;
2207 /* The plaintext state, if there is a plaintext. */
2208 struct rep_state
*src_state
;
2210 /* The index of the current delta chunk, if we are reading a delta. */
2213 /* The buffer where we store undeltified data. */
2218 /* An MD5 context for summing the data read in order to verify it. */
2219 struct apr_md5_ctx_t md5_context
;
2220 svn_boolean_t checksum_finalized
;
2222 /* The stored checksum of the representation we are reading, its
2223 length, and the amount we've read so far. Some of this
2224 information is redundant with rs_list and src_state, but it's
2225 convenient for the checksumming code to have it here. */
2226 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2230 /* Used for temporary allocations during the read. */
2233 /* Pool used to store file handles and other data that is persistant
2234 for the entire stream read. */
2235 apr_pool_t
*filehandle_pool
;
2238 /* Create a rep_read_baton structure for node revision NODEREV in
2239 filesystem FS and store it in *RB_P. Perform all allocations in
2240 POOL. If rep is mutable, it must be for file contents. */
2241 static svn_error_t
*
2242 rep_read_get_baton(struct rep_read_baton
**rb_p
,
2244 representation_t
*rep
,
2247 struct rep_read_baton
*b
;
2249 b
= apr_pcalloc(pool
, sizeof(*b
));
2253 apr_md5_init(&(b
->md5_context
));
2254 b
->checksum_finalized
= FALSE
;
2255 memcpy(b
->checksum
, rep
->checksum
, sizeof(b
->checksum
));
2256 b
->len
= rep
->expanded_size
;
2258 b
->pool
= svn_pool_create(pool
);
2259 b
->filehandle_pool
= svn_pool_create(pool
);
2261 SVN_ERR(build_rep_list(&b
->rs_list
, &b
->src_state
, fs
, rep
,
2262 b
->filehandle_pool
));
2264 /* Save our output baton. */
2267 return SVN_NO_ERROR
;
2270 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
2271 window into *NWIN. */
2272 static svn_error_t
*
2273 read_window(svn_txdelta_window_t
**nwin
, int this_chunk
, struct rep_state
*rs
,
2276 svn_stream_t
*stream
;
2278 assert(rs
->chunk_index
<= this_chunk
);
2280 /* Skip windows to reach the current chunk if we aren't there yet. */
2281 while (rs
->chunk_index
< this_chunk
)
2283 SVN_ERR(svn_txdelta_skip_svndiff_window(rs
->file
, rs
->ver
, pool
));
2285 SVN_ERR(get_file_offset(&rs
->off
, rs
->file
, pool
));
2286 if (rs
->off
>= rs
->end
)
2287 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2288 _("Reading one svndiff window read "
2289 "beyond the end of the "
2293 /* Read the next window. */
2294 stream
= svn_stream_from_aprfile(rs
->file
, pool
);
2295 SVN_ERR(svn_txdelta_read_svndiff_window(nwin
, stream
, rs
->ver
, pool
));
2297 SVN_ERR(get_file_offset(&rs
->off
, rs
->file
, pool
));
2299 if (rs
->off
> rs
->end
)
2300 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2301 _("Reading one svndiff window read beyond "
2302 "the end of the representation"));
2304 return SVN_NO_ERROR
;
2307 /* Get one delta window that is a result of combining all but the last deltas
2308 from the current desired representation identified in *RB, to its
2309 final base representation. Store the window in *RESULT. */
2310 static svn_error_t
*
2311 get_combined_window(svn_txdelta_window_t
**result
,
2312 struct rep_read_baton
*rb
)
2314 apr_pool_t
*pool
, *new_pool
;
2316 svn_txdelta_window_t
*window
, *nwin
;
2317 struct rep_state
*rs
;
2319 assert(rb
->rs_list
->nelts
>= 2);
2321 pool
= svn_pool_create(rb
->pool
);
2323 /* Read the next window from the original rep. */
2324 rs
= APR_ARRAY_IDX(rb
->rs_list
, 0, struct rep_state
*);
2325 SVN_ERR(read_window(&window
, rb
->chunk_index
, rs
, pool
));
2327 /* Combine in the windows from the other delta reps, if needed. */
2328 for (i
= 1; i
< rb
->rs_list
->nelts
- 1; i
++)
2330 if (window
->src_ops
== 0)
2333 rs
= APR_ARRAY_IDX(rb
->rs_list
, i
, struct rep_state
*);
2335 SVN_ERR(read_window(&nwin
, rb
->chunk_index
, rs
, pool
));
2337 /* Combine this window with the current one. Cycles pools so that we
2338 only need to hold three windows at a time. */
2339 new_pool
= svn_pool_create(rb
->pool
);
2340 window
= svn_txdelta_compose_windows(nwin
, window
, new_pool
);
2341 svn_pool_destroy(pool
);
2346 return SVN_NO_ERROR
;
2349 static svn_error_t
*
2350 rep_read_contents_close(void *baton
)
2352 struct rep_read_baton
*rb
= baton
;
2354 svn_pool_destroy(rb
->pool
);
2355 svn_pool_destroy(rb
->filehandle_pool
);
2357 return SVN_NO_ERROR
;
2360 /* Return the next *LEN bytes of the rep and store them in *BUF. */
2361 static svn_error_t
*
2362 get_contents(struct rep_read_baton
*rb
,
2366 apr_size_t copy_len
, remaining
= *len
, tlen
;
2367 char *sbuf
, *tbuf
, *cur
= buf
;
2368 struct rep_state
*rs
;
2369 svn_txdelta_window_t
*cwindow
, *lwindow
;
2371 /* Special case for when there are no delta reps, only a plain
2373 if (rb
->rs_list
->nelts
== 0)
2375 copy_len
= remaining
;
2377 if (((apr_off_t
) copy_len
) > rs
->end
- rs
->off
)
2378 copy_len
= (apr_size_t
) (rs
->end
- rs
->off
);
2379 SVN_ERR(svn_io_file_read_full(rs
->file
, cur
, copy_len
, NULL
,
2381 rs
->off
+= copy_len
;
2383 return SVN_NO_ERROR
;
2386 while (remaining
> 0)
2388 /* If we have buffered data from a previous chunk, use that. */
2391 /* Determine how much to copy from the buffer. */
2392 copy_len
= rb
->buf_len
- rb
->buf_pos
;
2393 if (copy_len
> remaining
)
2394 copy_len
= remaining
;
2396 /* Actually copy the data. */
2397 memcpy(cur
, rb
->buf
+ rb
->buf_pos
, copy_len
);
2398 rb
->buf_pos
+= copy_len
;
2400 remaining
-= copy_len
;
2402 /* If the buffer is all used up, clear it and empty the
2404 if (rb
->buf_pos
== rb
->buf_len
)
2406 svn_pool_clear(rb
->pool
);
2413 rs
= APR_ARRAY_IDX(rb
->rs_list
, 0, struct rep_state
*);
2414 if (rs
->off
== rs
->end
)
2417 /* Get more buffered data by evaluating a chunk. */
2418 if (rb
->rs_list
->nelts
> 1)
2419 SVN_ERR(get_combined_window(&cwindow
, rb
));
2422 if (!cwindow
|| cwindow
->src_ops
> 0)
2424 rs
= APR_ARRAY_IDX(rb
->rs_list
, rb
->rs_list
->nelts
- 1,
2425 struct rep_state
*);
2426 /* Read window from last representation in list. */
2427 /* We apply this window directly instead of combining it with the
2428 others. We do this because vdelta is used for deltas against
2429 the empty stream, which will trigger quadratic behaviour in
2430 the delta combiner. */
2431 SVN_ERR(read_window(&lwindow
, rb
->chunk_index
, rs
, rb
->pool
));
2433 if (lwindow
->src_ops
> 0)
2435 if (! rb
->src_state
)
2436 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2437 _("svndiff data requested "
2438 "non-existent source"));
2440 sbuf
= apr_palloc(rb
->pool
, lwindow
->sview_len
);
2441 if (! ((rs
->start
+ lwindow
->sview_offset
) < rs
->end
))
2442 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2443 _("svndiff requested position "
2444 "beyond end of stream"));
2445 if ((rs
->start
+ lwindow
->sview_offset
) != rs
->off
)
2447 rs
->off
= rs
->start
+ lwindow
->sview_offset
;
2448 SVN_ERR(svn_io_file_seek(rs
->file
, APR_SET
, &rs
->off
,
2451 SVN_ERR(svn_io_file_read_full(rs
->file
, sbuf
,
2454 rs
->off
+= lwindow
->sview_len
;
2459 /* Apply lwindow to source. */
2460 tlen
= lwindow
->tview_len
;
2461 tbuf
= apr_palloc(rb
->pool
, tlen
);
2462 svn_txdelta_apply_instructions(lwindow
, sbuf
, tbuf
,
2464 if (tlen
!= lwindow
->tview_len
)
2465 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2466 _("svndiff window length is "
2477 rb
->buf_len
= cwindow
->tview_len
;
2478 rb
->buf
= apr_palloc(rb
->pool
, rb
->buf_len
);
2479 svn_txdelta_apply_instructions(cwindow
, sbuf
, rb
->buf
,
2481 if (rb
->buf_len
!= cwindow
->tview_len
)
2482 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2483 _("svndiff window length is "
2488 rb
->buf_len
= lwindow
->tview_len
;
2498 return SVN_NO_ERROR
;
2501 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
2502 representation and store them in *BUF. Sum as we read and verify
2503 the MD5 sum at the end. */
2504 static svn_error_t
*
2505 rep_read_contents(void *baton
,
2509 struct rep_read_baton
*rb
= baton
;
2511 /* Get the next block of data. */
2512 SVN_ERR(get_contents(rb
, buf
, len
));
2514 /* Perform checksumming. We want to check the checksum as soon as
2515 the last byte of data is read, in case the caller never performs
2516 a short read, but we don't want to finalize the MD5 context
2518 if (!rb
->checksum_finalized
)
2520 apr_md5_update(&rb
->md5_context
, buf
, *len
);
2522 if (rb
->off
== rb
->len
)
2524 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2526 rb
->checksum_finalized
= TRUE
;
2527 apr_md5_final(checksum
, &rb
->md5_context
);
2528 if (! svn_md5_digests_match(checksum
, rb
->checksum
))
2529 return svn_error_createf
2530 (SVN_ERR_FS_CORRUPT
, NULL
,
2531 _("Checksum mismatch while reading representation:\n"
2534 svn_md5_digest_to_cstring_display(rb
->checksum
, rb
->pool
),
2535 svn_md5_digest_to_cstring_display(checksum
, rb
->pool
));
2538 return SVN_NO_ERROR
;
2541 /* Return a stream in *CONTENTS_P that will read the contents of a
2542 representation stored at the location given by REP. Appropriate
2543 for any kind of immutable representation, but only for file
2544 contents (not props or directory contents) in mutable
2547 If REP is NULL, the representation is assumed to be empty, and the
2548 empty stream is returned.
2550 static svn_error_t
*
2551 read_representation(svn_stream_t
**contents_p
,
2553 representation_t
*rep
,
2556 struct rep_read_baton
*rb
;
2560 *contents_p
= svn_stream_empty(pool
);
2564 SVN_ERR(rep_read_get_baton(&rb
, fs
, rep
, pool
));
2565 *contents_p
= svn_stream_create(rb
, pool
);
2566 svn_stream_set_read(*contents_p
, rep_read_contents
);
2567 svn_stream_set_close(*contents_p
, rep_read_contents_close
);
2570 return SVN_NO_ERROR
;
2574 svn_fs_fs__get_contents(svn_stream_t
**contents_p
,
2576 node_revision_t
*noderev
,
2579 return read_representation(contents_p
, fs
, noderev
->data_rep
, pool
);
2582 /* Baton used when reading delta windows. */
2583 struct delta_read_baton
2585 struct rep_state
*rs
;
2586 unsigned char checksum
[APR_MD5_DIGESTSIZE
];
2589 /* This implements the svn_txdelta_next_window_fn_t interface. */
2590 static svn_error_t
*
2591 delta_read_next_window(svn_txdelta_window_t
**window
, void *baton
,
2594 struct delta_read_baton
*drb
= baton
;
2596 if (drb
->rs
->off
== drb
->rs
->end
)
2599 return SVN_NO_ERROR
;
2602 SVN_ERR(read_window(window
, drb
->rs
->chunk_index
, drb
->rs
, pool
));
2604 return SVN_NO_ERROR
;
2607 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
2608 static const unsigned char *
2609 delta_read_md5_digest(void *baton
)
2611 struct delta_read_baton
*drb
= baton
;
2613 return drb
->checksum
;
2617 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t
**stream_p
,
2619 node_revision_t
*source
,
2620 node_revision_t
*target
,
2623 svn_stream_t
*source_stream
, *target_stream
;
2625 /* Try a shortcut: if the target is stored as a delta against the source,
2626 then just use that delta. */
2627 if (source
&& source
->data_rep
&& target
->data_rep
)
2629 struct rep_state
*rep_state
;
2630 struct rep_args
*rep_args
;
2632 /* Read target's base rep if any. */
2633 SVN_ERR(create_rep_state(&rep_state
, &rep_args
, target
->data_rep
,
2635 /* If that matches source, then use this delta as is. */
2636 if (rep_args
->is_delta
2637 && (rep_args
->is_delta_vs_empty
2638 || (rep_args
->base_revision
== source
->data_rep
->revision
2639 && rep_args
->base_offset
== source
->data_rep
->offset
)))
2641 /* Create the delta read baton. */
2642 struct delta_read_baton
*drb
= apr_pcalloc(pool
, sizeof(*drb
));
2643 drb
->rs
= rep_state
;
2644 memcpy(drb
->checksum
, target
->data_rep
->checksum
,
2645 sizeof(drb
->checksum
));
2646 *stream_p
= svn_txdelta_stream_create(drb
, delta_read_next_window
,
2647 delta_read_md5_digest
, pool
);
2648 return SVN_NO_ERROR
;
2651 SVN_ERR(svn_io_file_close(rep_state
->file
, pool
));
2654 /* Read both fulltexts and construct a delta. */
2656 SVN_ERR(read_representation(&source_stream
, fs
, source
->data_rep
, pool
));
2658 source_stream
= svn_stream_empty(pool
);
2659 SVN_ERR(read_representation(&target_stream
, fs
, target
->data_rep
, pool
));
2660 svn_txdelta(stream_p
, source_stream
, target_stream
, pool
);
2662 return SVN_NO_ERROR
;
2666 /* Fetch the contents of a directory into ENTRIES. Values are stored
2667 as filename to string mappings; further conversion is necessary to
2668 convert them into svn_fs_dirent_t values. */
2669 static svn_error_t
*
2670 get_dir_contents(apr_hash_t
*entries
,
2672 node_revision_t
*noderev
,
2675 svn_stream_t
*contents
;
2677 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
2679 apr_file_t
*dir_file
;
2680 const char *filename
= path_txn_node_children(fs
, noderev
->id
, pool
);
2682 /* The representation is mutable. Read the old directory
2683 contents from the mutable children file, followed by the
2684 changes we've made in this transaction. */
2685 SVN_ERR(svn_io_file_open(&dir_file
, filename
, APR_READ
| APR_BUFFERED
,
2686 APR_OS_DEFAULT
, pool
));
2687 contents
= svn_stream_from_aprfile(dir_file
, pool
);
2688 SVN_ERR(svn_hash_read2(entries
, contents
, SVN_HASH_TERMINATOR
, pool
));
2689 SVN_ERR(svn_hash_read_incremental(entries
, contents
, NULL
, pool
));
2690 SVN_ERR(svn_io_file_close(dir_file
, pool
));
2692 else if (noderev
->data_rep
)
2694 /* The representation is immutable. Read it normally. */
2695 SVN_ERR(read_representation(&contents
, fs
, noderev
->data_rep
, pool
));
2696 SVN_ERR(svn_hash_read2(entries
, contents
, SVN_HASH_TERMINATOR
, pool
));
2697 SVN_ERR(svn_stream_close(contents
));
2700 return SVN_NO_ERROR
;
2703 /* Return a copy of the directory hash ENTRIES in POOL. */
2705 copy_dir_entries(apr_hash_t
*entries
,
2708 apr_hash_t
*new_entries
= apr_hash_make(pool
);
2709 apr_hash_index_t
*hi
;
2711 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
2714 svn_fs_dirent_t
*dirent
, *new_dirent
;
2716 apr_hash_this(hi
, NULL
, NULL
, &val
);
2718 new_dirent
= apr_palloc(pool
, sizeof(*new_dirent
));
2719 new_dirent
->name
= apr_pstrdup(pool
, dirent
->name
);
2720 new_dirent
->kind
= dirent
->kind
;
2721 new_dirent
->id
= svn_fs_fs__id_copy(dirent
->id
, pool
);
2722 apr_hash_set(new_entries
, new_dirent
->name
, APR_HASH_KEY_STRING
,
2730 svn_fs_fs__rep_contents_dir(apr_hash_t
**entries_p
,
2732 node_revision_t
*noderev
,
2735 fs_fs_data_t
*ffd
= fs
->fsap_data
;
2736 apr_hash_t
*unparsed_entries
, *parsed_entries
;
2737 apr_hash_index_t
*hi
;
2740 /* Calculate an index into the dir entries cache. This should be
2741 completely ignored if this is a mutable noderev. */
2742 hid
= DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev
->id
));
2744 /* If we have this directory cached, return it. */
2745 if (! svn_fs_fs__id_txn_id(noderev
->id
) &&
2746 ffd
->dir_cache_id
[hid
] && svn_fs_fs__id_eq(ffd
->dir_cache_id
[hid
],
2749 *entries_p
= copy_dir_entries(ffd
->dir_cache
[hid
], pool
);
2750 return SVN_NO_ERROR
;
2753 /* Read in the directory hash. */
2754 unparsed_entries
= apr_hash_make(pool
);
2755 SVN_ERR(get_dir_contents(unparsed_entries
, fs
, noderev
, pool
));
2757 parsed_entries
= apr_hash_make(pool
);
2759 /* Translate the string dir entries into real entries. */
2760 for (hi
= apr_hash_first(pool
, unparsed_entries
); hi
; hi
= apr_hash_next(hi
))
2765 char *str
, *last_str
;
2766 svn_fs_dirent_t
*dirent
= apr_pcalloc(pool
, sizeof(*dirent
));
2768 apr_hash_this(hi
, &key
, NULL
, &val
);
2769 str_val
= apr_pstrdup(pool
, *((char **)val
));
2770 dirent
->name
= apr_pstrdup(pool
, key
);
2772 str
= apr_strtok(str_val
, " ", &last_str
);
2774 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2775 _("Directory entry corrupt"));
2777 if (strcmp(str
, KIND_FILE
) == 0)
2779 dirent
->kind
= svn_node_file
;
2781 else if (strcmp(str
, KIND_DIR
) == 0)
2783 dirent
->kind
= svn_node_dir
;
2787 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2788 _("Directory entry corrupt"));
2791 str
= apr_strtok(NULL
, " ", &last_str
);
2793 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
2794 _("Directory entry corrupt"));
2796 dirent
->id
= svn_fs_fs__id_parse(str
, strlen(str
), pool
);
2798 apr_hash_set(parsed_entries
, dirent
->name
, APR_HASH_KEY_STRING
, dirent
);
2801 /* If this is an immutable directory, let's cache the contents. */
2802 if (! svn_fs_fs__id_txn_id(noderev
->id
))
2804 /* Start by NULLing the ID field, so that we never leave the
2805 cache in an illegal state. */
2806 ffd
->dir_cache_id
[hid
] = NULL
;
2808 if (ffd
->dir_cache_pool
[hid
])
2809 svn_pool_clear(ffd
->dir_cache_pool
[hid
]);
2811 ffd
->dir_cache_pool
[hid
] = svn_pool_create(fs
->pool
);
2813 ffd
->dir_cache
[hid
] = copy_dir_entries(parsed_entries
,
2814 ffd
->dir_cache_pool
[hid
]);
2815 ffd
->dir_cache_id
[hid
] = svn_fs_fs__id_copy(noderev
->id
,
2816 ffd
->dir_cache_pool
[hid
]);
2819 *entries_p
= parsed_entries
;
2820 return SVN_NO_ERROR
;
2824 svn_fs_fs__get_proplist(apr_hash_t
**proplist_p
,
2826 node_revision_t
*noderev
,
2829 apr_hash_t
*proplist
;
2830 svn_stream_t
*stream
;
2832 proplist
= apr_hash_make(pool
);
2834 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
2836 apr_file_t
*props_file
;
2837 const char *filename
= path_txn_node_props(fs
, noderev
->id
, pool
);
2839 SVN_ERR(svn_io_file_open(&props_file
, filename
,
2840 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
2842 stream
= svn_stream_from_aprfile(props_file
, pool
);
2843 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
, pool
));
2844 SVN_ERR(svn_io_file_close(props_file
, pool
));
2846 else if (noderev
->prop_rep
)
2848 SVN_ERR(read_representation(&stream
, fs
, noderev
->prop_rep
, pool
));
2849 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
, pool
));
2850 SVN_ERR(svn_stream_close(stream
));
2853 *proplist_p
= proplist
;
2855 return SVN_NO_ERROR
;
2859 svn_fs_fs__file_length(svn_filesize_t
*length
,
2860 node_revision_t
*noderev
,
2863 if (noderev
->data_rep
)
2864 *length
= noderev
->data_rep
->expanded_size
;
2868 return SVN_NO_ERROR
;
2872 svn_fs_fs__noderev_same_rep_key(representation_t
*a
,
2873 representation_t
*b
)
2884 if (a
->offset
!= b
->offset
)
2887 if (a
->revision
!= b
->revision
)
2894 svn_fs_fs__file_checksum(unsigned char digest
[],
2895 node_revision_t
*noderev
,
2898 if (noderev
->data_rep
)
2899 memcpy(digest
, noderev
->data_rep
->checksum
, APR_MD5_DIGESTSIZE
);
2901 memset(digest
, 0, APR_MD5_DIGESTSIZE
);
2903 return SVN_NO_ERROR
;
2907 svn_fs_fs__rep_copy(representation_t
*rep
,
2910 representation_t
*rep_new
;
2915 rep_new
= apr_pcalloc(pool
, sizeof(*rep_new
));
2917 memcpy(rep_new
, rep
, sizeof(*rep_new
));
2922 /* Merge the internal-use-only CHANGE into a hash of public-FS
2923 svn_fs_path_change_t CHANGES, collapsing multiple changes into a
2924 single summarical (is that real word?) change per path. Also keep
2925 the COPYFROM_HASH up to date with new adds and replaces. */
2926 static svn_error_t
*
2927 fold_change(apr_hash_t
*changes
,
2928 const change_t
*change
,
2929 apr_hash_t
*copyfrom_hash
)
2931 apr_pool_t
*pool
= apr_hash_pool_get(changes
);
2932 apr_pool_t
*copyfrom_pool
= apr_hash_pool_get(copyfrom_hash
);
2933 svn_fs_path_change_t
*old_change
, *new_change
;
2934 const char *path
, *copyfrom_string
, *copyfrom_path
= NULL
;
2936 if ((old_change
= apr_hash_get(changes
, change
->path
, APR_HASH_KEY_STRING
)))
2938 /* This path already exists in the hash, so we have to merge
2939 this change into the already existing one. */
2941 /* Get the existing copyfrom entry for this path. */
2942 copyfrom_string
= apr_hash_get(copyfrom_hash
, change
->path
,
2943 APR_HASH_KEY_STRING
);
2945 /* If this entry existed in the copyfrom hash, we don't need to
2947 if (copyfrom_string
)
2948 copyfrom_path
= change
->path
;
2950 /* Since the path already exists in the hash, we don't have to
2951 dup the allocation for the path itself. */
2952 path
= change
->path
;
2953 /* Sanity check: only allow NULL node revision ID in the
2955 if ((! change
->noderev_id
) && (change
->kind
!= svn_fs_path_change_reset
))
2956 return svn_error_create
2957 (SVN_ERR_FS_CORRUPT
, NULL
,
2958 _("Missing required node revision ID"));
2960 /* Sanity check: we should be talking about the same node
2961 revision ID as our last change except where the last change
2963 if (change
->noderev_id
2964 && (! svn_fs_fs__id_eq(old_change
->node_rev_id
, change
->noderev_id
))
2965 && (old_change
->change_kind
!= svn_fs_path_change_delete
))
2966 return svn_error_create
2967 (SVN_ERR_FS_CORRUPT
, NULL
,
2968 _("Invalid change ordering: new node revision ID "
2971 /* Sanity check: an add, replacement, or reset must be the first
2972 thing to follow a deletion. */
2973 if ((old_change
->change_kind
== svn_fs_path_change_delete
)
2974 && (! ((change
->kind
== svn_fs_path_change_replace
)
2975 || (change
->kind
== svn_fs_path_change_reset
)
2976 || (change
->kind
== svn_fs_path_change_add
))))
2977 return svn_error_create
2978 (SVN_ERR_FS_CORRUPT
, NULL
,
2979 _("Invalid change ordering: non-add change on deleted path"));
2981 /* Now, merge that change in. */
2982 switch (change
->kind
)
2984 case svn_fs_path_change_reset
:
2985 /* A reset here will simply remove the path change from the
2988 copyfrom_string
= NULL
;
2991 case svn_fs_path_change_delete
:
2992 if (old_change
->change_kind
== svn_fs_path_change_add
)
2994 /* If the path was introduced in this transaction via an
2995 add, and we are deleting it, just remove the path
3001 /* A deletion overrules all previous changes. */
3002 old_change
->change_kind
= svn_fs_path_change_delete
;
3003 old_change
->text_mod
= change
->text_mod
;
3004 old_change
->prop_mod
= change
->prop_mod
;
3006 copyfrom_string
= NULL
;
3009 case svn_fs_path_change_add
:
3010 case svn_fs_path_change_replace
:
3011 /* An add at this point must be following a previous delete,
3012 so treat it just like a replace. */
3013 old_change
->change_kind
= svn_fs_path_change_replace
;
3014 old_change
->node_rev_id
= svn_fs_fs__id_copy(change
->noderev_id
,
3016 old_change
->text_mod
= change
->text_mod
;
3017 old_change
->prop_mod
= change
->prop_mod
;
3018 if (change
->copyfrom_rev
== SVN_INVALID_REVNUM
)
3019 copyfrom_string
= apr_pstrdup(copyfrom_pool
, "");
3022 copyfrom_string
= apr_psprintf(copyfrom_pool
,
3024 change
->copyfrom_rev
,
3025 change
->copyfrom_path
);
3029 case svn_fs_path_change_modify
:
3031 if (change
->text_mod
)
3032 old_change
->text_mod
= TRUE
;
3033 if (change
->prop_mod
)
3034 old_change
->prop_mod
= TRUE
;
3038 /* Point our new_change to our (possibly modified) old_change. */
3039 new_change
= old_change
;
3043 /* This change is new to the hash, so make a new public change
3044 structure from the internal one (in the hash's pool), and dup
3045 the path into the hash's pool, too. */
3046 new_change
= apr_pcalloc(pool
, sizeof(*new_change
));
3047 new_change
->node_rev_id
= svn_fs_fs__id_copy(change
->noderev_id
, pool
);
3048 new_change
->change_kind
= change
->kind
;
3049 new_change
->text_mod
= change
->text_mod
;
3050 new_change
->prop_mod
= change
->prop_mod
;
3051 if (change
->copyfrom_rev
!= SVN_INVALID_REVNUM
)
3053 copyfrom_string
= apr_psprintf(copyfrom_pool
, "%ld %s",
3054 change
->copyfrom_rev
,
3055 change
->copyfrom_path
);
3058 copyfrom_string
= apr_pstrdup(copyfrom_pool
, "");
3059 path
= apr_pstrdup(pool
, change
->path
);
3062 /* Add (or update) this path. */
3063 apr_hash_set(changes
, path
, APR_HASH_KEY_STRING
, new_change
);
3065 /* If copyfrom_path is non-NULL, the key is already present in the
3066 hash, so we don't need to duplicate it in the copyfrom pool. */
3067 if (! copyfrom_path
)
3069 /* If copyfrom_string is NULL, the hash entry will be deleted,
3070 so we don't need to duplicate the key in the copyfrom
3072 copyfrom_path
= copyfrom_string
? apr_pstrdup(copyfrom_pool
, path
)
3076 apr_hash_set(copyfrom_hash
, copyfrom_path
, APR_HASH_KEY_STRING
,
3079 return SVN_NO_ERROR
;
3082 /* The 256 is an arbitrary size large enough to hold the node id and the
3084 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
3086 /* Read the next entry in the changes record from file FILE and store
3087 the resulting change in *CHANGE_P. If there is no next record,
3088 store NULL there. Perform all allocations from POOL. */
3089 static svn_error_t
*
3090 read_change(change_t
**change_p
,
3094 char buf
[MAX_CHANGE_LINE_LEN
];
3095 apr_size_t len
= sizeof(buf
);
3097 char *str
, *last_str
;
3100 /* Default return value. */
3103 err
= svn_io_read_length_line(file
, buf
, &len
, pool
);
3105 /* Check for a blank line. */
3106 if (err
|| (len
== 0))
3108 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
))
3110 svn_error_clear(err
);
3111 return SVN_NO_ERROR
;
3113 if ((len
== 0) && (! err
))
3114 return SVN_NO_ERROR
;
3118 change
= apr_pcalloc(pool
, sizeof(*change
));
3120 /* Get the node-id of the change. */
3121 str
= apr_strtok(buf
, " ", &last_str
);
3123 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3124 _("Invalid changes line in rev-file"));
3126 change
->noderev_id
= svn_fs_fs__id_parse(str
, strlen(str
), pool
);
3128 /* Get the change type. */
3129 str
= apr_strtok(NULL
, " ", &last_str
);
3131 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3132 _("Invalid changes line in rev-file"));
3134 if (strcmp(str
, ACTION_MODIFY
) == 0)
3136 change
->kind
= svn_fs_path_change_modify
;
3138 else if (strcmp(str
, ACTION_ADD
) == 0)
3140 change
->kind
= svn_fs_path_change_add
;
3142 else if (strcmp(str
, ACTION_DELETE
) == 0)
3144 change
->kind
= svn_fs_path_change_delete
;
3146 else if (strcmp(str
, ACTION_REPLACE
) == 0)
3148 change
->kind
= svn_fs_path_change_replace
;
3150 else if (strcmp(str
, ACTION_RESET
) == 0)
3152 change
->kind
= svn_fs_path_change_reset
;
3156 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3157 _("Invalid change kind in rev file"));
3160 /* Get the text-mod flag. */
3161 str
= apr_strtok(NULL
, " ", &last_str
);
3163 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3164 _("Invalid changes line in rev-file"));
3166 if (strcmp(str
, FLAG_TRUE
) == 0)
3168 change
->text_mod
= TRUE
;
3170 else if (strcmp(str
, FLAG_FALSE
) == 0)
3172 change
->text_mod
= FALSE
;
3176 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3177 _("Invalid text-mod flag in rev-file"));
3180 /* Get the prop-mod flag. */
3181 str
= apr_strtok(NULL
, " ", &last_str
);
3183 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3184 _("Invalid changes line in rev-file"));
3186 if (strcmp(str
, FLAG_TRUE
) == 0)
3188 change
->prop_mod
= TRUE
;
3190 else if (strcmp(str
, FLAG_FALSE
) == 0)
3192 change
->prop_mod
= FALSE
;
3196 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3197 _("Invalid prop-mod flag in rev-file"));
3200 /* Get the changed path. */
3201 change
->path
= apr_pstrdup(pool
, last_str
);
3204 /* Read the next line, the copyfrom line. */
3206 SVN_ERR(svn_io_read_length_line(file
, buf
, &len
, pool
));
3210 change
->copyfrom_rev
= SVN_INVALID_REVNUM
;
3211 change
->copyfrom_path
= NULL
;
3215 str
= apr_strtok(buf
, " ", &last_str
);
3217 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3218 _("Invalid changes line in rev-file"));
3219 change
->copyfrom_rev
= atol(str
);
3222 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3223 _("Invalid changes line in rev-file"));
3225 change
->copyfrom_path
= apr_pstrdup(pool
, last_str
);
3230 return SVN_NO_ERROR
;
3233 /* Fetch all the changed path entries from FILE and store then in
3234 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
3235 *data. Store a hash of paths to copyfrom revisions/paths in
3236 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
3237 the changed-path entries have already been folded (by
3238 write_final_changed_path_info) and may be out of order, so we shouldn't
3239 remove children of replaced or deleted directories. Do all
3240 allocations in POOL. */
3241 static svn_error_t
*
3242 fetch_all_changes(apr_hash_t
*changed_paths
,
3243 apr_hash_t
*copyfrom_hash
,
3245 svn_boolean_t prefolded
,
3249 apr_pool_t
*iterpool
= svn_pool_create(pool
);
3250 apr_hash_t
*my_hash
;
3252 /* If we are passed a NULL copyfrom hash, manufacture one for the
3253 duration of this call. */
3254 my_hash
= copyfrom_hash
? copyfrom_hash
: apr_hash_make(pool
);
3256 /* Read in the changes one by one, folding them into our local hash
3259 SVN_ERR(read_change(&change
, file
, iterpool
));
3263 SVN_ERR(fold_change(changed_paths
, change
, my_hash
));
3265 /* Now, if our change was a deletion or replacement, we have to
3266 blow away any changes thus far on paths that are (or, were)
3267 children of this path.
3268 ### i won't bother with another iteration pool here -- at
3269 most we talking about a few extra dups of paths into what
3270 is already a temporary subpool.
3273 if (((change
->kind
== svn_fs_path_change_delete
)
3274 || (change
->kind
== svn_fs_path_change_replace
))
3277 apr_hash_index_t
*hi
;
3279 for (hi
= apr_hash_first(iterpool
, changed_paths
);
3281 hi
= apr_hash_next(hi
))
3283 /* KEY is the path. */
3284 const void *hashkey
;
3286 apr_hash_this(hi
, &hashkey
, &klen
, NULL
);
3288 /* If we come across our own path, ignore it. */
3289 if (strcmp(change
->path
, hashkey
) == 0)
3292 /* If we come across a child of our path, remove it. */
3293 if (svn_path_is_child(change
->path
, hashkey
, iterpool
))
3294 apr_hash_set(changed_paths
, hashkey
, klen
, NULL
);
3298 /* Clear the per-iteration subpool. */
3299 svn_pool_clear(iterpool
);
3301 SVN_ERR(read_change(&change
, file
, iterpool
));
3304 /* Destroy the per-iteration subpool. */
3305 svn_pool_destroy(iterpool
);
3307 return SVN_NO_ERROR
;
3311 svn_fs_fs__txn_changes_fetch(apr_hash_t
**changed_paths_p
,
3314 apr_hash_t
*copyfrom_cache
,
3318 apr_hash_t
*changed_paths
= apr_hash_make(pool
);
3320 SVN_ERR(svn_io_file_open(&file
, path_txn_changes(fs
, txn_id
, pool
),
3321 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3323 SVN_ERR(fetch_all_changes(changed_paths
, copyfrom_cache
, file
, FALSE
,
3326 SVN_ERR(svn_io_file_close(file
, pool
));
3328 *changed_paths_p
= changed_paths
;
3330 return SVN_NO_ERROR
;
3334 svn_fs_fs__paths_changed(apr_hash_t
**changed_paths_p
,
3337 apr_hash_t
*copyfrom_cache
,
3340 apr_off_t changes_offset
;
3341 apr_hash_t
*changed_paths
;
3342 apr_file_t
*revision_file
;
3344 SVN_ERR(ensure_revision_exists(fs
, rev
, pool
));
3346 SVN_ERR(svn_io_file_open(&revision_file
, svn_fs_fs__path_rev(fs
, rev
, pool
),
3347 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3349 SVN_ERR(get_root_changes_offset(NULL
, &changes_offset
, revision_file
,
3352 SVN_ERR(svn_io_file_seek(revision_file
, APR_SET
, &changes_offset
, pool
));
3354 changed_paths
= apr_hash_make(pool
);
3356 SVN_ERR(fetch_all_changes(changed_paths
, copyfrom_cache
, revision_file
,
3359 /* Close the revision file. */
3360 SVN_ERR(svn_io_file_close(revision_file
, pool
));
3362 *changed_paths_p
= changed_paths
;
3364 return SVN_NO_ERROR
;
3367 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
3368 the filesystem FS. This is only used to create the root of a transaction.
3369 Allocations are from POOL. */
3370 static svn_error_t
*
3371 create_new_txn_noderev_from_rev(svn_fs_t
*fs
,
3376 node_revision_t
*noderev
;
3377 const char *node_id
, *copy_id
;
3379 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, src
, pool
));
3381 if (svn_fs_fs__id_txn_id(noderev
->id
))
3382 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3383 _("Copying from transactions not allowed"));
3385 noderev
->predecessor_id
= noderev
->id
;
3386 noderev
->predecessor_count
++;
3387 noderev
->copyfrom_path
= NULL
;
3388 noderev
->copyfrom_rev
= SVN_INVALID_REVNUM
;
3390 /* For the transaction root, the copyroot never changes. */
3392 node_id
= svn_fs_fs__id_node_id(noderev
->id
);
3393 copy_id
= svn_fs_fs__id_copy_id(noderev
->id
);
3394 noderev
->id
= svn_fs_fs__id_txn_create(node_id
, copy_id
, txn_id
, pool
);
3396 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, TRUE
, pool
));
3398 return SVN_NO_ERROR
;
3401 /* A structure used by get_and_increment_txn_key_body(). */
3402 struct get_and_increment_txn_key_baton
{
3408 /* Callback used in the implementation of create_txn_dir(). This gets
3409 the current base 36 value in PATH_TXN_CURRENT and increments it.
3410 It returns the original value by the baton. */
3411 static svn_error_t
*
3412 get_and_increment_txn_key_body(void *baton
, apr_pool_t
*pool
)
3414 struct get_and_increment_txn_key_baton
*cb
= baton
;
3415 const char *txn_current_filename
= path_txn_current(cb
->fs
, pool
);
3416 apr_file_t
*txn_current_file
;
3417 const char *tmp_filename
;
3418 char next_txn_id
[MAX_KEY_SIZE
+3];
3419 svn_error_t
*err
= SVN_NO_ERROR
;
3420 apr_pool_t
*iterpool
;
3424 cb
->txn_id
= apr_palloc(cb
->pool
, MAX_KEY_SIZE
);
3426 iterpool
= svn_pool_create(pool
);
3427 for (i
= 0; i
< SVN_ESTALE_RETRY_COUNT
; ++i
)
3429 svn_pool_clear(iterpool
);
3431 SVN_RETRY_ESTALE(err
, svn_io_file_open(&txn_current_file
,
3432 txn_current_filename
,
3433 APR_READ
| APR_BUFFERED
,
3434 APR_OS_DEFAULT
, iterpool
));
3436 SVN_RETRY_ESTALE(err
, svn_io_read_length_line(txn_current_file
,
3440 SVN_IGNORE_ESTALE(err
, svn_io_file_close(txn_current_file
, iterpool
));
3447 svn_pool_destroy(iterpool
);
3449 /* Increment the key and add a trailing \n to the string so the
3450 txn-current file has a newline in it. */
3451 svn_fs_fs__next_key(cb
->txn_id
, &len
, next_txn_id
);
3452 next_txn_id
[len
] = '\n';
3454 next_txn_id
[len
] = '\0';
3456 SVN_ERR(svn_io_open_unique_file2(&txn_current_file
, &tmp_filename
,
3457 txn_current_filename
, ".tmp",
3458 svn_io_file_del_none
, pool
));
3460 SVN_ERR(svn_io_file_write_full(txn_current_file
,
3466 SVN_ERR(svn_io_file_flush_to_disk(txn_current_file
, pool
));
3468 SVN_ERR(svn_io_file_close(txn_current_file
, pool
));
3470 SVN_ERR(svn_fs_fs__move_into_place(tmp_filename
, txn_current_filename
,
3471 txn_current_filename
, pool
));
3476 /* Create a unique directory for a transaction in FS based on revision
3477 REV. Return the ID for this transaction in *ID_P. Use a sequence
3478 value in the transaction ID to prevent reuse of transaction IDs. */
3479 static svn_error_t
*
3480 create_txn_dir(const char **id_p
, svn_fs_t
*fs
, svn_revnum_t rev
,
3483 struct get_and_increment_txn_key_baton cb
;
3484 const char *txn_dir
;
3486 /* Get the current transaction sequence value, which is a base-36
3487 number, from the txn-current file, and write an
3488 incremented value back out to the file. Place the revision
3489 number the transaction is based off into the transaction id. */
3492 SVN_ERR(with_txn_current_lock(fs
,
3493 get_and_increment_txn_key_body
,
3496 *id_p
= apr_psprintf(pool
, "%ld-%s", rev
, cb
.txn_id
);
3498 txn_dir
= svn_path_join_many(pool
,
3501 apr_pstrcat(pool
, *id_p
, PATH_EXT_TXN
, NULL
),
3504 SVN_ERR(svn_io_dir_make(txn_dir
, APR_OS_DEFAULT
, pool
));
3506 return SVN_NO_ERROR
;
3509 /* Create a unique directory for a transaction in FS based on revision
3510 REV. Return the ID for this transaction in *ID_P. This
3511 implementation is used in svn 1.4 and earlier repositories and is
3512 kept in 1.5 and greater to support the --pre-1.4-compatible and
3513 --pre-1.5-compatible repository creation options. Reused
3514 transaction IDs are possible with this implementation. */
3515 static svn_error_t
*
3516 create_txn_dir_pre_1_5(const char **id_p
, svn_fs_t
*fs
, svn_revnum_t rev
,
3520 apr_pool_t
*subpool
;
3521 const char *unique_path
, *prefix
;
3523 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
3524 prefix
= svn_path_join_many(pool
, fs
->path
, PATH_TXNS_DIR
,
3525 apr_psprintf(pool
, "%ld", rev
), NULL
);
3527 subpool
= svn_pool_create(pool
);
3528 for (i
= 1; i
<= 99999; i
++)
3532 svn_pool_clear(subpool
);
3533 unique_path
= apr_psprintf(subpool
, "%s-%u" PATH_EXT_TXN
, prefix
, i
);
3534 err
= svn_io_dir_make(unique_path
, APR_OS_DEFAULT
, subpool
);
3537 /* We succeeded. Return the basename minus the ".txn" extension. */
3538 const char *name
= svn_path_basename(unique_path
, subpool
);
3539 *id_p
= apr_pstrndup(pool
, name
,
3540 strlen(name
) - strlen(PATH_EXT_TXN
));
3541 svn_pool_destroy(subpool
);
3542 return SVN_NO_ERROR
;
3544 if (! APR_STATUS_IS_EEXIST(err
->apr_err
))
3546 svn_error_clear(err
);
3549 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED
,
3551 _("Unable to create transaction directory "
3552 "in '%s' for revision %ld"),
3557 svn_fs_fs__create_txn(svn_fs_txn_t
**txn_p
,
3562 fs_fs_data_t
*ffd
= fs
->fsap_data
;
3564 svn_fs_id_t
*root_id
;
3566 txn
= apr_pcalloc(pool
, sizeof(*txn
));
3568 /* Get the txn_id. */
3569 if (ffd
->format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
3570 SVN_ERR(create_txn_dir(&txn
->id
, fs
, rev
, pool
));
3572 SVN_ERR(create_txn_dir_pre_1_5(&txn
->id
, fs
, rev
, pool
));
3575 txn
->base_rev
= rev
;
3577 txn
->vtable
= &txn_vtable
;
3580 /* Create a new root node for this transaction. */
3581 SVN_ERR(svn_fs_fs__rev_get_root(&root_id
, fs
, rev
, pool
));
3582 SVN_ERR(create_new_txn_noderev_from_rev(fs
, txn
->id
, root_id
, pool
));
3584 /* Create an empty rev file. */
3585 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs
, txn
->id
, pool
), "",
3588 /* Create an empty rev-lock file. */
3589 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs
, txn
->id
, pool
), "",
3592 /* Create an empty changes file. */
3593 SVN_ERR(svn_io_file_create(path_txn_changes(fs
, txn
->id
, pool
), "",
3596 /* Create the next-ids file. */
3597 SVN_ERR(svn_io_file_create(path_txn_next_ids(fs
, txn
->id
, pool
), "0 0\n",
3600 return SVN_NO_ERROR
;
3603 /* Store the property list for transaction TXN_ID in PROPLIST.
3604 Perform temporary allocations in POOL. */
3605 static svn_error_t
*
3606 get_txn_proplist(apr_hash_t
*proplist
,
3611 apr_file_t
*txn_prop_file
;
3613 /* Open the transaction properties file. */
3614 SVN_ERR(svn_io_file_open(&txn_prop_file
, path_txn_props(fs
, txn_id
, pool
),
3615 APR_READ
| APR_BUFFERED
,
3616 APR_OS_DEFAULT
, pool
));
3618 /* Read in the property list. */
3619 SVN_ERR(svn_hash_read2(proplist
,
3620 svn_stream_from_aprfile(txn_prop_file
, pool
),
3621 SVN_HASH_TERMINATOR
, pool
));
3623 SVN_ERR(svn_io_file_close(txn_prop_file
, pool
));
3625 return SVN_NO_ERROR
;
3629 svn_fs_fs__change_txn_prop(svn_fs_txn_t
*txn
,
3631 const svn_string_t
*value
,
3634 apr_array_header_t
*props
= apr_array_make(pool
, 1, sizeof(svn_prop_t
));
3639 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
3641 return svn_fs_fs__change_txn_props(txn
, props
, pool
);
3645 svn_fs_fs__change_txn_props(svn_fs_txn_t
*txn
,
3646 apr_array_header_t
*props
,
3649 apr_file_t
*txn_prop_file
;
3650 apr_hash_t
*txn_prop
= apr_hash_make(pool
);
3654 err
= get_txn_proplist(txn_prop
, txn
->fs
, txn
->id
, pool
);
3655 /* Here - and here only - we need to deal with the possibility that the
3656 transaction property file doesn't yet exist. The rest of the
3657 implementation assumes that the file exists, but we're called to set the
3658 initial transaction properties as the transaction is being created. */
3659 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
)))
3660 svn_error_clear(err
);
3664 for (i
= 0; i
< props
->nelts
; i
++)
3666 svn_prop_t
*prop
= &APR_ARRAY_IDX(props
, i
, svn_prop_t
);
3668 apr_hash_set(txn_prop
, prop
->name
, APR_HASH_KEY_STRING
, prop
->value
);
3671 /* Create a new version of the file and write out the new props. */
3672 /* Open the transaction properties file. */
3673 SVN_ERR(svn_io_file_open(&txn_prop_file
,
3674 path_txn_props(txn
->fs
, txn
->id
, pool
),
3675 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
3676 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3678 SVN_ERR(svn_hash_write(txn_prop
, txn_prop_file
, pool
));
3680 SVN_ERR(svn_io_file_close(txn_prop_file
, pool
));
3682 return SVN_NO_ERROR
;
3685 /* Store the mergeinfo list for transaction TXN_ID into new hash
3686 *MINFO. Perform allocation of *MINFO and temporary allocations in
3687 *POOL. Sets *MINFO to NULL if no mergeinfo has changed. */
3689 static svn_error_t
*
3690 get_txn_mergeinfo(apr_hash_t
**minfo
,
3695 apr_file_t
*txn_minfo_file
;
3698 /* Open the transaction mergeinfo file. */
3699 svn_error_t
*err
= svn_io_file_open(&txn_minfo_file
,
3700 path_txn_mergeinfo(fs
, txn_id
, pool
),
3701 APR_READ
| APR_BUFFERED
,
3702 APR_OS_DEFAULT
, pool
);
3703 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
3705 svn_error_clear(err
);
3707 return SVN_NO_ERROR
;
3711 result
= apr_hash_make(pool
);
3712 /* Read in the property list. */
3713 SVN_ERR(svn_hash_read2(result
,
3714 svn_stream_from_aprfile(txn_minfo_file
, pool
),
3715 SVN_HASH_TERMINATOR
, pool
));
3717 SVN_ERR(svn_io_file_close(txn_minfo_file
, pool
));
3721 return SVN_NO_ERROR
;
3725 /* Change mergeinfo for path NAME in TXN to VALUE. */
3728 svn_fs_fs__change_txn_mergeinfo(svn_fs_txn_t
*txn
,
3730 const svn_string_t
*value
,
3733 apr_file_t
*txn_minfo_file
;
3734 apr_hash_t
*txn_minfo
;
3736 SVN_ERR(get_txn_mergeinfo(&txn_minfo
, txn
->fs
, txn
->id
, pool
));
3737 if (txn_minfo
== NULL
) /* doesn't exist yet */
3738 txn_minfo
= apr_hash_make(pool
);
3740 apr_hash_set(txn_minfo
, name
, APR_HASH_KEY_STRING
, value
);
3742 /* Create a new version of the file and write out the new minfos. */
3743 /* Open the transaction minfoerties file. */
3744 SVN_ERR(svn_io_file_open(&txn_minfo_file
,
3745 path_txn_mergeinfo(txn
->fs
, txn
->id
, pool
),
3746 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
3747 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3749 SVN_ERR(svn_hash_write(txn_minfo
, txn_minfo_file
, pool
));
3751 SVN_ERR(svn_io_file_close(txn_minfo_file
, pool
));
3753 return SVN_NO_ERROR
;
3757 svn_fs_fs__get_txn(transaction_t
**txn_p
,
3763 node_revision_t
*noderev
;
3764 svn_fs_id_t
*root_id
;
3766 txn
= apr_pcalloc(pool
, sizeof(*txn
));
3767 txn
->proplist
= apr_hash_make(pool
);
3769 SVN_ERR(get_txn_proplist(txn
->proplist
, fs
, txn_id
, pool
));
3770 root_id
= svn_fs_fs__id_txn_create("0", "0", txn_id
, pool
);
3772 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, root_id
, pool
));
3774 txn
->root_id
= svn_fs_fs__id_copy(noderev
->id
, pool
);
3775 txn
->base_id
= svn_fs_fs__id_copy(noderev
->predecessor_id
, pool
);
3780 return SVN_NO_ERROR
;
3783 /* Write out the currently available next node_id NODE_ID and copy_id
3784 COPY_ID for transaction TXN_ID in filesystem FS. Perform temporary
3785 allocations in POOL. */
3786 static svn_error_t
*
3787 write_next_ids(svn_fs_t
*fs
,
3789 const char *node_id
,
3790 const char *copy_id
,
3794 svn_stream_t
*out_stream
;
3796 SVN_ERR(svn_io_file_open(&file
, path_txn_next_ids(fs
, txn_id
, pool
),
3797 APR_WRITE
| APR_TRUNCATE
,
3798 APR_OS_DEFAULT
, pool
));
3800 out_stream
= svn_stream_from_aprfile(file
, pool
);
3802 SVN_ERR(svn_stream_printf(out_stream
, pool
, "%s %s\n", node_id
, copy_id
));
3804 SVN_ERR(svn_stream_close(out_stream
));
3805 SVN_ERR(svn_io_file_close(file
, pool
));
3807 return SVN_NO_ERROR
;
3810 /* Find out what the next unique node-id and copy-id are for
3811 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
3812 and *COPY_ID. Perform all allocations in POOL. */
3813 static svn_error_t
*
3814 read_next_ids(const char **node_id
,
3815 const char **copy_id
,
3821 char buf
[MAX_KEY_SIZE
*2+3];
3823 char *str
, *last_str
;
3825 SVN_ERR(svn_io_file_open(&file
, path_txn_next_ids(fs
, txn_id
, pool
),
3826 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
3828 limit
= sizeof(buf
);
3829 SVN_ERR(svn_io_read_length_line(file
, buf
, &limit
, pool
));
3831 SVN_ERR(svn_io_file_close(file
, pool
));
3833 /* Parse this into two separate strings. */
3835 str
= apr_strtok(buf
, " ", &last_str
);
3837 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3838 _("next-id file corrupt"));
3840 *node_id
= apr_pstrdup(pool
, str
);
3842 str
= apr_strtok(NULL
, " ", &last_str
);
3844 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
3845 _("next-id file corrupt"));
3847 *copy_id
= apr_pstrdup(pool
, str
);
3849 return SVN_NO_ERROR
;
3852 /* Get a new and unique to this transaction node-id for transaction
3853 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
3854 Perform all allocations in POOL. */
3855 static svn_error_t
*
3856 get_new_txn_node_id(const char **node_id_p
,
3861 const char *cur_node_id
, *cur_copy_id
;
3865 /* First read in the current next-ids file. */
3866 SVN_ERR(read_next_ids(&cur_node_id
, &cur_copy_id
, fs
, txn_id
, pool
));
3868 node_id
= apr_pcalloc(pool
, strlen(cur_node_id
) + 2);
3870 len
= strlen(cur_node_id
);
3871 svn_fs_fs__next_key(cur_node_id
, &len
, node_id
);
3873 SVN_ERR(write_next_ids(fs
, txn_id
, node_id
, cur_copy_id
, pool
));
3875 *node_id_p
= apr_pstrcat(pool
, "_", cur_node_id
, NULL
);
3877 return SVN_NO_ERROR
;
3881 svn_fs_fs__create_node(const svn_fs_id_t
**id_p
,
3883 node_revision_t
*noderev
,
3884 const char *copy_id
,
3888 const char *node_id
;
3889 const svn_fs_id_t
*id
;
3891 /* Get a new node-id for this node. */
3892 SVN_ERR(get_new_txn_node_id(&node_id
, fs
, txn_id
, pool
));
3894 id
= svn_fs_fs__id_txn_create(node_id
, copy_id
, txn_id
, pool
);
3898 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, FALSE
, pool
));
3902 return SVN_NO_ERROR
;
3906 svn_fs_fs__purge_txn(svn_fs_t
*fs
,
3910 fs_fs_data_t
*ffd
= fs
->fsap_data
;
3912 /* Remove the shared transaction object associated with this transaction. */
3913 SVN_ERR(purge_shared_txn(fs
, txn_id
, pool
));
3914 /* Remove the directory associated with this transaction. */
3915 SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs
, txn_id
, pool
), FALSE
,
3917 if (ffd
->format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
3919 /* Delete protorev and its lock, which aren't in the txn
3920 directory. It's OK if they don't exist (for example, if this
3921 is post-commit and the proto-rev has been moved into
3923 svn_error_t
*err
= svn_io_remove_file(path_txn_proto_rev(fs
, txn_id
,
3925 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
3927 svn_error_clear(err
);
3933 err
= svn_io_remove_file(path_txn_proto_rev_lock(fs
, txn_id
, pool
),
3935 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
3937 svn_error_clear(err
);
3943 return SVN_NO_ERROR
;
3948 svn_fs_fs__abort_txn(svn_fs_txn_t
*txn
,
3953 SVN_ERR(svn_fs__check_fs(txn
->fs
, TRUE
));
3955 /* Clean out the directory cache. */
3956 ffd
= txn
->fs
->fsap_data
;
3957 memset(&ffd
->dir_cache_id
, 0,
3958 sizeof(svn_fs_id_t
*) * NUM_DIR_CACHE_ENTRIES
);
3960 /* Now, purge the transaction. */
3961 SVN_ERR_W(svn_fs_fs__purge_txn(txn
->fs
, txn
->id
, pool
),
3962 _("Transaction cleanup failed"));
3964 return SVN_NO_ERROR
;
3969 unparse_dir_entry(svn_node_kind_t kind
, const svn_fs_id_t
*id
,
3972 return apr_psprintf(pool
, "%s %s",
3973 (kind
== svn_node_file
) ? KIND_FILE
: KIND_DIR
,
3974 svn_fs_fs__id_unparse(id
, pool
)->data
);
3977 /* Given a hash ENTRIES of dirent structions, return a hash in
3978 *STR_ENTRIES_P, that has svn_string_t as the values in the format
3979 specified by the fs_fs directory contents file. Perform
3980 allocations in POOL. */
3981 static svn_error_t
*
3982 unparse_dir_entries(apr_hash_t
**str_entries_p
,
3983 apr_hash_t
*entries
,
3986 apr_hash_index_t
*hi
;
3988 *str_entries_p
= apr_hash_make(pool
);
3990 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
3995 svn_fs_dirent_t
*dirent
;
3996 const char *new_val
;
3998 apr_hash_this(hi
, &key
, &klen
, &val
);
4000 new_val
= unparse_dir_entry(dirent
->kind
, dirent
->id
, pool
);
4001 apr_hash_set(*str_entries_p
, key
, klen
,
4002 svn_string_create(new_val
, pool
));
4005 return SVN_NO_ERROR
;
4010 svn_fs_fs__set_entry(svn_fs_t
*fs
,
4012 node_revision_t
*parent_noderev
,
4014 const svn_fs_id_t
*id
,
4015 svn_node_kind_t kind
,
4018 representation_t
*rep
= parent_noderev
->data_rep
;
4019 const char *filename
= path_txn_node_children(fs
, parent_noderev
->id
, pool
);
4023 if (!rep
|| !rep
->txn_id
)
4026 apr_hash_t
*entries
;
4027 apr_pool_t
*subpool
= svn_pool_create(pool
);
4029 /* Before we can modify the directory, we need to dump its old
4030 contents into a mutable representation file. */
4031 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries
, fs
, parent_noderev
,
4033 SVN_ERR(unparse_dir_entries(&entries
, entries
, subpool
));
4034 SVN_ERR(svn_io_file_open(&file
, filename
,
4035 APR_WRITE
| APR_CREATE
| APR_BUFFERED
,
4036 APR_OS_DEFAULT
, pool
));
4037 out
= svn_stream_from_aprfile(file
, pool
);
4038 SVN_ERR(svn_hash_write2(entries
, out
, SVN_HASH_TERMINATOR
, subpool
));
4040 svn_pool_destroy(subpool
);
4043 /* Mark the node-rev's data rep as mutable. */
4044 rep
= apr_pcalloc(pool
, sizeof(*rep
));
4045 rep
->revision
= SVN_INVALID_REVNUM
;
4046 rep
->txn_id
= txn_id
;
4047 parent_noderev
->data_rep
= rep
;
4048 SVN_ERR(svn_fs_fs__put_node_revision(fs
, parent_noderev
->id
,
4049 parent_noderev
, FALSE
, pool
));
4053 /* The directory rep is already mutable, so just open it for append. */
4054 SVN_ERR(svn_io_file_open(&file
, filename
, APR_WRITE
| APR_APPEND
,
4055 APR_OS_DEFAULT
, pool
));
4056 out
= svn_stream_from_aprfile(file
, pool
);
4059 /* Append an incremental hash entry for the entry change. */
4062 const char *val
= unparse_dir_entry(kind
, id
, pool
);
4064 SVN_ERR(svn_stream_printf(out
, pool
, "K %" APR_SIZE_T_FMT
"\n%s\n"
4065 "V %" APR_SIZE_T_FMT
"\n%s\n",
4071 SVN_ERR(svn_stream_printf(out
, pool
, "D %" APR_SIZE_T_FMT
"\n%s\n",
4072 strlen(name
), name
));
4075 SVN_ERR(svn_io_file_close(file
, pool
));
4076 return SVN_NO_ERROR
;
4079 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
4080 string COPYFROM, into the file specified by FILE. All temporary
4081 allocations are in POOL. */
4082 static svn_error_t
*
4083 write_change_entry(apr_file_t
*file
,
4085 svn_fs_path_change_t
*change
,
4086 const char *copyfrom
,
4089 const char *idstr
, *buf
;
4090 const char *change_string
= NULL
;
4092 switch (change
->change_kind
)
4094 case svn_fs_path_change_modify
:
4095 change_string
= ACTION_MODIFY
;
4097 case svn_fs_path_change_add
:
4098 change_string
= ACTION_ADD
;
4100 case svn_fs_path_change_delete
:
4101 change_string
= ACTION_DELETE
;
4103 case svn_fs_path_change_replace
:
4104 change_string
= ACTION_REPLACE
;
4106 case svn_fs_path_change_reset
:
4107 change_string
= ACTION_RESET
;
4110 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4111 _("Invalid change type"));
4114 if (change
->node_rev_id
)
4115 idstr
= svn_fs_fs__id_unparse(change
->node_rev_id
, pool
)->data
;
4117 idstr
= ACTION_RESET
;
4119 buf
= apr_psprintf(pool
, "%s %s %s %s %s\n",
4120 idstr
, change_string
,
4121 change
->text_mod
? FLAG_TRUE
: FLAG_FALSE
,
4122 change
->prop_mod
? FLAG_TRUE
: FLAG_FALSE
,
4125 SVN_ERR(svn_io_file_write_full(file
, buf
, strlen(buf
), NULL
, pool
));
4129 SVN_ERR(svn_io_file_write_full(file
, copyfrom
, strlen(copyfrom
),
4133 SVN_ERR(svn_io_file_write_full(file
, "\n", 1, NULL
, pool
));
4135 return SVN_NO_ERROR
;
4139 svn_fs_fs__add_change(svn_fs_t
*fs
,
4142 const svn_fs_id_t
*id
,
4143 svn_fs_path_change_kind_t change_kind
,
4144 svn_boolean_t text_mod
,
4145 svn_boolean_t prop_mod
,
4146 svn_revnum_t copyfrom_rev
,
4147 const char *copyfrom_path
,
4151 const char *copyfrom
;
4152 svn_fs_path_change_t
*change
= apr_pcalloc(pool
, sizeof(*change
));
4154 SVN_ERR(svn_io_file_open(&file
, path_txn_changes(fs
, txn_id
, pool
),
4155 APR_APPEND
| APR_WRITE
| APR_CREATE
4156 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
4158 if (copyfrom_rev
!= SVN_INVALID_REVNUM
)
4159 copyfrom
= apr_psprintf(pool
, "%ld %s", copyfrom_rev
, copyfrom_path
);
4163 change
->node_rev_id
= id
;
4164 change
->change_kind
= change_kind
;
4165 change
->text_mod
= text_mod
;
4166 change
->prop_mod
= prop_mod
;
4168 SVN_ERR(write_change_entry(file
, path
, change
, copyfrom
, pool
));
4170 SVN_ERR(svn_io_file_close(file
, pool
));
4172 return SVN_NO_ERROR
;
4175 /* This baton is used by the representation writing streams. It keeps
4176 track of the checksum information as well as the total size of the
4177 representation so far. */
4178 struct rep_write_baton
4180 /* The FS we are writing to. */
4183 /* Actual file to which we are writing. */
4184 svn_stream_t
*rep_stream
;
4186 /* A stream from the delta combiner. Data written here gets
4187 deltified, then eventually written to rep_stream. */
4188 svn_stream_t
*delta_stream
;
4190 /* Where is this representation header stored. */
4191 apr_off_t rep_offset
;
4193 /* Start of the actual data. */
4194 apr_off_t delta_start
;
4196 /* How many bytes have been written to this rep already. */
4197 svn_filesize_t rep_size
;
4199 /* The node revision for which we're writing out info. */
4200 node_revision_t
*noderev
;
4202 /* Actual output file. */
4204 /* Lock 'cookie' used to unlock the output file once we've finished
4208 struct apr_md5_ctx_t md5_context
;
4212 apr_pool_t
*parent_pool
;
4215 /* Handler for the write method of the representation writable stream.
4216 BATON is a rep_write_baton, DATA is the data to write, and *LEN is
4217 the length of this data. */
4218 static svn_error_t
*
4219 rep_write_contents(void *baton
,
4223 struct rep_write_baton
*b
= baton
;
4225 apr_md5_update(&b
->md5_context
, data
, *len
);
4226 b
->rep_size
+= *len
;
4228 /* If we are writing a delta, use that stream. */
4229 if (b
->delta_stream
)
4231 SVN_ERR(svn_stream_write(b
->delta_stream
, data
, len
));
4235 SVN_ERR(svn_stream_write(b
->rep_stream
, data
, len
));
4238 return SVN_NO_ERROR
;
4241 /* Given a node-revision NODEREV in filesystem FS, return the
4242 representation in *REP to use as the base for a text representation
4243 delta. Perform temporary allocations in *POOL. */
4244 static svn_error_t
*
4245 choose_delta_base(representation_t
**rep
,
4247 node_revision_t
*noderev
,
4251 node_revision_t
*base
;
4253 /* If we have no predecessors, then use the empty stream as a
4255 if (! noderev
->predecessor_count
)
4258 return SVN_NO_ERROR
;
4261 /* Flip the rightmost '1' bit of the predecessor count to determine
4262 which file rev (counting from 0) we want to use. (To see why
4263 count & (count - 1) unsets the rightmost set bit, think about how
4264 you decrement a binary number.) */
4265 count
= noderev
->predecessor_count
;
4266 count
= count
& (count
- 1);
4268 /* Walk back a number of predecessors equal to the difference
4269 between count and the original predecessor count. (For example,
4270 if noderev has ten predecessors and we want the eighth file rev,
4271 walk back two predecessors.) */
4273 while ((count
++) < noderev
->predecessor_count
)
4274 SVN_ERR(svn_fs_fs__get_node_revision(&base
, fs
,
4275 base
->predecessor_id
, pool
));
4277 *rep
= base
->data_rep
;
4279 return SVN_NO_ERROR
;
4282 /* Get a rep_write_baton and store it in *WB_P for the representation
4283 indicated by NODEREV in filesystem FS. Perform allocations in
4284 POOL. Only appropriate for file contents, not for props or
4285 directory contents. */
4286 static svn_error_t
*
4287 rep_write_get_baton(struct rep_write_baton
**wb_p
,
4289 node_revision_t
*noderev
,
4292 struct rep_write_baton
*b
;
4294 representation_t
*base_rep
;
4295 svn_stream_t
*source
;
4297 svn_txdelta_window_handler_t wh
;
4299 fs_fs_data_t
*ffd
= fs
->fsap_data
;
4301 b
= apr_pcalloc(pool
, sizeof(*b
));
4303 apr_md5_init(&(b
->md5_context
));
4306 b
->parent_pool
= pool
;
4307 b
->pool
= svn_pool_create(pool
);
4309 b
->noderev
= noderev
;
4311 /* Open the prototype rev file and seek to its end. */
4312 SVN_ERR(get_writable_proto_rev(&file
, &b
->lockcookie
,
4313 fs
, svn_fs_fs__id_txn_id(noderev
->id
),
4317 b
->rep_stream
= svn_stream_from_aprfile(file
, b
->pool
);
4319 SVN_ERR(get_file_offset(&b
->rep_offset
, file
, b
->pool
));
4321 /* Get the base for this delta. */
4322 SVN_ERR(choose_delta_base(&base_rep
, fs
, noderev
, b
->pool
));
4323 SVN_ERR(read_representation(&source
, fs
, base_rep
, b
->pool
));
4325 /* Write out the rep header. */
4328 header
= apr_psprintf(b
->pool
, REP_DELTA
" %ld %" APR_OFF_T_FMT
" %"
4329 SVN_FILESIZE_T_FMT
"\n",
4330 base_rep
->revision
, base_rep
->offset
,
4335 header
= REP_DELTA
"\n";
4337 SVN_ERR(svn_io_file_write_full(file
, header
, strlen(header
), NULL
,
4340 /* Now determine the offset of the actual svndiff data. */
4341 SVN_ERR(get_file_offset(&b
->delta_start
, file
, b
->pool
));
4343 /* Prepare to write the svndiff data. */
4344 if (ffd
->format
>= SVN_FS_FS__MIN_SVNDIFF1_FORMAT
)
4345 svn_txdelta_to_svndiff2(&wh
, &whb
, b
->rep_stream
, 1, pool
);
4347 svn_txdelta_to_svndiff2(&wh
, &whb
, b
->rep_stream
, 0, pool
);
4349 b
->delta_stream
= svn_txdelta_target_push(wh
, whb
, source
, b
->pool
);
4353 return SVN_NO_ERROR
;
4356 /* Close handler for the representation write stream. BATON is a
4357 rep_write_baton. Writes out a new node-rev that correctly
4358 references the representation we just finished writing. */
4359 static svn_error_t
*
4360 rep_write_contents_close(void *baton
)
4362 struct rep_write_baton
*b
= baton
;
4363 representation_t
*rep
;
4366 rep
= apr_pcalloc(b
->parent_pool
, sizeof(*rep
));
4367 rep
->offset
= b
->rep_offset
;
4369 /* Close our delta stream so the last bits of svndiff are written
4371 if (b
->delta_stream
)
4372 SVN_ERR(svn_stream_close(b
->delta_stream
));
4374 /* Determine the length of the svndiff data. */
4375 SVN_ERR(get_file_offset(&offset
, b
->file
, b
->pool
));
4376 rep
->size
= offset
- b
->delta_start
;
4378 /* Fill in the rest of the representation field. */
4379 rep
->expanded_size
= b
->rep_size
;
4380 rep
->txn_id
= svn_fs_fs__id_txn_id(b
->noderev
->id
);
4381 rep
->revision
= SVN_INVALID_REVNUM
;
4383 /* Finalize the MD5 checksum. */
4384 apr_md5_final(rep
->checksum
, &b
->md5_context
);
4386 /* Write out our cosmetic end marker. */
4387 SVN_ERR(svn_stream_printf(b
->rep_stream
, b
->pool
, "ENDREP\n"));
4389 b
->noderev
->data_rep
= rep
;
4391 /* Write out the new node-rev information. */
4392 SVN_ERR(svn_fs_fs__put_node_revision(b
->fs
, b
->noderev
->id
, b
->noderev
, FALSE
,
4395 SVN_ERR(svn_io_file_close(b
->file
, b
->pool
));
4396 SVN_ERR(unlock_proto_rev(b
->fs
, rep
->txn_id
, b
->lockcookie
, b
->pool
));
4397 svn_pool_destroy(b
->pool
);
4399 return SVN_NO_ERROR
;
4402 /* Store a writable stream in *CONTENTS_P that will receive all data
4403 written and store it as the file data representation referenced by
4404 NODEREV in filesystem FS. Perform temporary allocations in
4405 POOL. Only appropriate for file data, not props or directory
4407 static svn_error_t
*
4408 set_representation(svn_stream_t
**contents_p
,
4410 node_revision_t
*noderev
,
4413 struct rep_write_baton
*wb
;
4415 if (! svn_fs_fs__id_txn_id(noderev
->id
))
4416 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4417 _("Attempted to write to non-transaction"));
4419 SVN_ERR(rep_write_get_baton(&wb
, fs
, noderev
, pool
));
4421 *contents_p
= svn_stream_create(wb
, pool
);
4422 svn_stream_set_write(*contents_p
, rep_write_contents
);
4423 svn_stream_set_close(*contents_p
, rep_write_contents_close
);
4425 return SVN_NO_ERROR
;
4429 svn_fs_fs__set_contents(svn_stream_t
**stream
,
4431 node_revision_t
*noderev
,
4434 if (noderev
->kind
!= svn_node_file
)
4435 return svn_error_create(SVN_ERR_FS_NOT_FILE
, NULL
,
4436 _("Can't set text contents of a directory"));
4438 return set_representation(stream
, fs
, noderev
, pool
);
4442 svn_fs_fs__create_successor(const svn_fs_id_t
**new_id_p
,
4444 const svn_fs_id_t
*old_idp
,
4445 node_revision_t
*new_noderev
,
4446 const char *copy_id
,
4450 const svn_fs_id_t
*id
;
4453 copy_id
= svn_fs_fs__id_copy_id(old_idp
);
4454 id
= svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp
), copy_id
,
4457 new_noderev
->id
= id
;
4459 if (! new_noderev
->copyroot_path
)
4461 new_noderev
->copyroot_path
= apr_pstrdup(pool
,
4462 new_noderev
->created_path
);
4463 new_noderev
->copyroot_rev
= svn_fs_fs__id_rev(new_noderev
->id
);
4466 SVN_ERR(svn_fs_fs__put_node_revision(fs
, new_noderev
->id
, new_noderev
, FALSE
,
4471 return SVN_NO_ERROR
;
4475 svn_fs_fs__set_proplist(svn_fs_t
*fs
,
4476 node_revision_t
*noderev
,
4477 apr_hash_t
*proplist
,
4480 const char *filename
= path_txn_node_props(fs
, noderev
->id
, pool
);
4484 /* Dump the property list to the mutable property file. */
4485 SVN_ERR(svn_io_file_open(&file
, filename
,
4486 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
4487 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
4488 out
= svn_stream_from_aprfile(file
, pool
);
4489 SVN_ERR(svn_hash_write2(proplist
, out
, SVN_HASH_TERMINATOR
, pool
));
4490 SVN_ERR(svn_io_file_close(file
, pool
));
4492 /* Mark the node-rev's prop rep as mutable, if not already done. */
4493 if (!noderev
->prop_rep
|| !noderev
->prop_rep
->txn_id
)
4495 noderev
->prop_rep
= apr_pcalloc(pool
, sizeof(*noderev
->prop_rep
));
4496 noderev
->prop_rep
->txn_id
= svn_fs_fs__id_txn_id(noderev
->id
);
4497 SVN_ERR(svn_fs_fs__put_node_revision(fs
, noderev
->id
, noderev
, FALSE
, pool
));
4500 return SVN_NO_ERROR
;
4503 /* Read the 'current' file for filesystem FS and store the next
4504 available node id in *NODE_ID, and the next available copy id in
4505 *COPY_ID. Allocations are performed from POOL. */
4506 static svn_error_t
*
4507 get_next_revision_ids(const char **node_id
,
4508 const char **copy_id
,
4513 char *str
, *last_str
;
4515 SVN_ERR(read_current(svn_fs_fs__path_current(fs
, pool
), &buf
, pool
));
4517 str
= apr_strtok(buf
, " ", &last_str
);
4519 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4520 _("Corrupt current file"));
4522 str
= apr_strtok(NULL
, " ", &last_str
);
4524 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4525 _("Corrupt current file"));
4527 *node_id
= apr_pstrdup(pool
, str
);
4529 str
= apr_strtok(NULL
, " ", &last_str
);
4531 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
4532 _("Corrupt current file"));
4534 *copy_id
= apr_pstrdup(pool
, str
);
4536 return SVN_NO_ERROR
;
4539 /* This baton is used by the stream created for write_hash_rep. */
4540 struct write_hash_baton
4542 svn_stream_t
*stream
;
4546 struct apr_md5_ctx_t md5_context
;
4549 /* The handler for the write_hash_rep stream. BATON is a
4550 write_hash_baton, DATA has the data to write and *LEN is the number
4551 of bytes to write. */
4552 static svn_error_t
*
4553 write_hash_handler(void *baton
,
4557 struct write_hash_baton
*whb
= baton
;
4559 apr_md5_update(&whb
->md5_context
, data
, *len
);
4561 SVN_ERR(svn_stream_write(whb
->stream
, data
, len
));
4564 return SVN_NO_ERROR
;
4567 /* Write out the hash HASH as a text representation to file FILE. In
4568 the process, record the total size of the dump in *SIZE, and the
4569 md5 digest in CHECKSUM. Perform temporary allocations in POOL. */
4570 static svn_error_t
*
4571 write_hash_rep(svn_filesize_t
*size
,
4572 unsigned char checksum
[APR_MD5_DIGESTSIZE
],
4577 svn_stream_t
*stream
;
4578 struct write_hash_baton
*whb
;
4580 whb
= apr_pcalloc(pool
, sizeof(*whb
));
4582 whb
->stream
= svn_stream_from_aprfile(file
, pool
);
4584 apr_md5_init(&(whb
->md5_context
));
4586 stream
= svn_stream_create(whb
, pool
);
4587 svn_stream_set_write(stream
, write_hash_handler
);
4589 SVN_ERR(svn_stream_printf(whb
->stream
, pool
, "PLAIN\n"));
4591 SVN_ERR(svn_hash_write2(hash
, stream
, SVN_HASH_TERMINATOR
, pool
));
4593 /* Store the results. */
4594 apr_md5_final(checksum
, &whb
->md5_context
);
4597 SVN_ERR(svn_stream_printf(whb
->stream
, pool
, "ENDREP\n"));
4599 return SVN_NO_ERROR
;
4602 /* Copy a node-revision specified by id ID in fileystem FS from a
4603 transaction into the permanent rev-file FILE. Return the offset of
4604 the new node-revision in *OFFSET. If this is a directory, all
4605 children are copied as well. START_NODE_ID and START_COPY_ID are
4606 the first available node and copy ids for this filesystem.
4607 Temporary allocations are from POOL. */
4608 static svn_error_t
*
4609 write_final_rev(const svn_fs_id_t
**new_id_p
,
4613 const svn_fs_id_t
*id
,
4614 const char *start_node_id
,
4615 const char *start_copy_id
,
4616 apr_hash_t
*node_origins
,
4619 node_revision_t
*noderev
;
4620 apr_off_t my_offset
;
4621 char my_node_id
[MAX_KEY_SIZE
+ 2];
4622 char my_copy_id
[MAX_KEY_SIZE
+ 2];
4623 const svn_fs_id_t
*new_id
;
4624 const char *node_id
, *copy_id
;
4625 svn_boolean_t node_id_is_new
= FALSE
;
4629 /* Check to see if this is a transaction node. */
4630 if (! svn_fs_fs__id_txn_id(id
))
4631 return SVN_NO_ERROR
;
4633 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, pool
));
4635 if (noderev
->kind
== svn_node_dir
)
4637 apr_pool_t
*subpool
;
4638 apr_hash_t
*entries
, *str_entries
;
4639 svn_fs_dirent_t
*dirent
;
4641 apr_hash_index_t
*hi
;
4643 /* This is a directory. Write out all the children first. */
4644 subpool
= svn_pool_create(pool
);
4646 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries
, fs
, noderev
, pool
));
4648 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
4650 svn_pool_clear(subpool
);
4651 apr_hash_this(hi
, NULL
, NULL
, &val
);
4653 SVN_ERR(write_final_rev(&new_id
, file
, rev
, fs
, dirent
->id
,
4654 start_node_id
, start_copy_id
,
4655 node_origins
, subpool
));
4656 if (new_id
&& (svn_fs_fs__id_rev(new_id
) == rev
))
4657 dirent
->id
= svn_fs_fs__id_copy(new_id
, pool
);
4659 svn_pool_destroy(subpool
);
4661 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
4663 /* Write out the contents of this directory as a text rep. */
4664 SVN_ERR(unparse_dir_entries(&str_entries
, entries
, pool
));
4666 noderev
->data_rep
->txn_id
= NULL
;
4667 noderev
->data_rep
->revision
= rev
;
4668 SVN_ERR(get_file_offset(&noderev
->data_rep
->offset
, file
, pool
));
4669 SVN_ERR(write_hash_rep(&noderev
->data_rep
->size
,
4670 noderev
->data_rep
->checksum
, file
,
4671 str_entries
, pool
));
4672 noderev
->data_rep
->expanded_size
= noderev
->data_rep
->size
;
4677 /* This is a file. We should make sure the data rep, if it
4678 exists in a "this" state, gets rewritten to our new revision
4681 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
)
4683 noderev
->data_rep
->txn_id
= NULL
;
4684 noderev
->data_rep
->revision
= rev
;
4688 /* Fix up the property reps. */
4689 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
4691 apr_hash_t
*proplist
;
4693 SVN_ERR(svn_fs_fs__get_proplist(&proplist
, fs
, noderev
, pool
));
4694 SVN_ERR(get_file_offset(&noderev
->prop_rep
->offset
, file
, pool
));
4695 SVN_ERR(write_hash_rep(&noderev
->prop_rep
->size
,
4696 noderev
->prop_rep
->checksum
, file
,
4699 noderev
->prop_rep
->txn_id
= NULL
;
4700 noderev
->prop_rep
->revision
= rev
;
4704 /* Convert our temporary ID into a permanent revision one. */
4705 SVN_ERR(get_file_offset(&my_offset
, file
, pool
));
4707 node_id
= svn_fs_fs__id_node_id(noderev
->id
);
4708 if (*node_id
== '_')
4710 node_id_is_new
= TRUE
;
4711 svn_fs_fs__add_keys(start_node_id
, node_id
+ 1, my_node_id
);
4714 strcpy(my_node_id
, node_id
);
4716 copy_id
= svn_fs_fs__id_copy_id(noderev
->id
);
4717 if (*copy_id
== '_')
4718 svn_fs_fs__add_keys(start_copy_id
, copy_id
+ 1, my_copy_id
);
4720 strcpy(my_copy_id
, copy_id
);
4722 if (noderev
->copyroot_rev
== SVN_INVALID_REVNUM
)
4723 noderev
->copyroot_rev
= rev
;
4725 new_id
= svn_fs_fs__id_rev_create(my_node_id
, my_copy_id
, rev
, my_offset
,
4728 noderev
->id
= new_id
;
4732 apr_pool_t
*hash_pool
= apr_hash_pool_get(node_origins
);
4733 const char *key
= apr_pstrdup(hash_pool
, my_node_id
);
4734 const svn_fs_id_t
*val
= svn_fs_fs__id_copy(new_id
, hash_pool
);
4736 apr_hash_set(node_origins
, key
, APR_HASH_KEY_STRING
, val
);
4739 /* Write out our new node-revision. */
4740 SVN_ERR(write_noderev_txn(file
, noderev
, pool
));
4742 /* Return our ID that references the revision file. */
4743 *new_id_p
= noderev
->id
;
4745 return SVN_NO_ERROR
;
4748 /* Write the changed path info from transaction TXN_ID in filesystem
4749 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
4750 in the file of the beginning of this information. Perform
4751 temporary allocations in POOL. */
4752 static svn_error_t
*
4753 write_final_changed_path_info(apr_off_t
*offset_p
,
4759 const char *copyfrom
;
4760 apr_hash_t
*changed_paths
, *copyfrom_cache
= apr_hash_make(pool
);
4762 apr_hash_index_t
*hi
;
4763 apr_pool_t
*iterpool
= svn_pool_create(pool
);
4765 SVN_ERR(get_file_offset(&offset
, file
, pool
));
4767 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths
, fs
, txn_id
,
4768 copyfrom_cache
, pool
));
4770 /* Iterate through the changed paths one at a time, and convert the
4771 temporary node-id into a permanent one for each change entry. */
4772 for (hi
= apr_hash_first(pool
, changed_paths
); hi
; hi
= apr_hash_next(hi
))
4774 node_revision_t
*noderev
;
4775 const svn_fs_id_t
*id
;
4776 svn_fs_path_change_t
*change
;
4780 svn_pool_clear(iterpool
);
4782 apr_hash_this(hi
, &key
, NULL
, &val
);
4785 id
= change
->node_rev_id
;
4787 /* If this was a delete of a mutable node, then it is OK to
4788 leave the change entry pointing to the non-existent temporary
4789 node, since it will never be used. */
4790 if ((change
->change_kind
!= svn_fs_path_change_delete
) &&
4791 (! svn_fs_fs__id_txn_id(id
)))
4793 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, iterpool
));
4795 /* noderev has the permanent node-id at this point, so we just
4796 substitute it for the temporary one. */
4797 change
->node_rev_id
= noderev
->id
;
4800 /* Find the cached copyfrom information. */
4801 copyfrom
= apr_hash_get(copyfrom_cache
, key
, APR_HASH_KEY_STRING
);
4803 /* Write out the new entry into the final rev-file. */
4804 SVN_ERR(write_change_entry(file
, key
, change
, copyfrom
, iterpool
));
4807 svn_pool_destroy(iterpool
);
4811 return SVN_NO_ERROR
;
4816 svn_fs_fs__dup_perms(const char *filename
,
4817 const char *perms_reference
,
4821 apr_status_t status
;
4823 const char *filename_apr
, *perms_reference_apr
;
4825 SVN_ERR(svn_path_cstring_from_utf8(&filename_apr
, filename
, pool
));
4826 SVN_ERR(svn_path_cstring_from_utf8(&perms_reference_apr
, perms_reference
,
4829 status
= apr_stat(&finfo
, perms_reference_apr
, APR_FINFO_PROT
, pool
);
4831 return svn_error_wrap_apr(status
, _("Can't stat '%s'"),
4832 svn_path_local_style(perms_reference
, pool
));
4833 status
= apr_file_perms_set(filename_apr
, finfo
.protection
);
4835 return svn_error_wrap_apr(status
, _("Can't chmod '%s'"),
4836 svn_path_local_style(filename
, pool
));
4838 return SVN_NO_ERROR
;
4843 svn_fs_fs__move_into_place(const char *old_filename
,
4844 const char *new_filename
,
4845 const char *perms_reference
,
4850 SVN_ERR(svn_fs_fs__dup_perms(old_filename
, perms_reference
, pool
));
4852 /* Move the file into place. */
4853 err
= svn_io_file_rename(old_filename
, new_filename
, pool
);
4854 if (err
&& APR_STATUS_IS_EXDEV(err
->apr_err
))
4858 /* Can't rename across devices; fall back to copying. */
4859 svn_error_clear(err
);
4861 SVN_ERR(svn_io_copy_file(old_filename
, new_filename
, TRUE
, pool
));
4863 /* Flush the target of the copy to disk. */
4864 SVN_ERR(svn_io_file_open(&file
, new_filename
, APR_READ
,
4865 APR_OS_DEFAULT
, pool
));
4866 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
4867 SVN_ERR(svn_io_file_close(file
, pool
));
4874 /* Linux has the unusual feature that fsync() on a file is not
4875 enough to ensure that a file's directory entries have been
4876 flushed to disk; you have to fsync the directory as well.
4877 On other operating systems, we'd only be asking for trouble
4878 by trying to open and fsync a directory. */
4879 const char *dirname
;
4882 dirname
= svn_path_dirname(new_filename
, pool
);
4883 SVN_ERR(svn_io_file_open(&file
, dirname
, APR_READ
, APR_OS_DEFAULT
,
4885 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
4886 SVN_ERR(svn_io_file_close(file
, pool
));
4890 return SVN_NO_ERROR
;
4893 /* Atomically update the current file to hold the specifed REV, NEXT_NODE_ID,
4894 and NEXT_COPY_ID. Perform temporary allocations in POOL. */
4895 static svn_error_t
*
4896 write_current(svn_fs_t
*fs
, svn_revnum_t rev
, const char *next_node_id
,
4897 const char *next_copy_id
, apr_pool_t
*pool
)
4900 const char *tmp_name
, *name
;
4903 /* Now we can just write out this line. */
4904 buf
= apr_psprintf(pool
, "%ld %s %s\n", rev
, next_node_id
, next_copy_id
);
4906 name
= svn_fs_fs__path_current(fs
, pool
);
4907 SVN_ERR(svn_io_open_unique_file2(&file
, &tmp_name
, name
, ".tmp",
4908 svn_io_file_del_none
, pool
));
4910 SVN_ERR(svn_io_file_write_full(file
, buf
, strlen(buf
), NULL
, pool
));
4912 SVN_ERR(svn_io_file_flush_to_disk(file
, pool
));
4914 SVN_ERR(svn_io_file_close(file
, pool
));
4916 SVN_ERR(svn_fs_fs__move_into_place(tmp_name
, name
, name
, pool
));
4918 return SVN_NO_ERROR
;
4921 /* Update the current file to hold the correct next node and copy_ids
4922 from transaction TXN_ID in filesystem FS. The current revision is
4923 set to REV. Perform temporary allocations in POOL. */
4924 static svn_error_t
*
4925 write_final_current(svn_fs_t
*fs
,
4928 const char *start_node_id
,
4929 const char *start_copy_id
,
4932 const char *txn_node_id
, *txn_copy_id
;
4933 char new_node_id
[MAX_KEY_SIZE
+ 2];
4934 char new_copy_id
[MAX_KEY_SIZE
+ 2];
4936 /* To find the next available ids, we add the id that used to be in
4937 the current file, to the next ids from the transaction file. */
4938 SVN_ERR(read_next_ids(&txn_node_id
, &txn_copy_id
, fs
, txn_id
, pool
));
4940 svn_fs_fs__add_keys(start_node_id
, txn_node_id
, new_node_id
);
4941 svn_fs_fs__add_keys(start_copy_id
, txn_copy_id
, new_copy_id
);
4943 return write_current(fs
, rev
, new_node_id
, new_copy_id
, pool
);
4946 /* Verify that the user registed with FS has all the locks necessary to
4947 permit all the changes associate with TXN_NAME.
4948 The FS write lock is assumed to be held by the caller. */
4949 static svn_error_t
*
4950 verify_locks(svn_fs_t
*fs
,
4951 const char *txn_name
,
4954 apr_pool_t
*subpool
= svn_pool_create(pool
);
4955 apr_hash_t
*changes
;
4956 apr_hash_index_t
*hi
;
4957 apr_array_header_t
*changed_paths
;
4958 svn_stringbuf_t
*last_recursed
= NULL
;
4961 /* Fetch the changes for this transaction. */
4962 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes
, fs
, txn_name
, NULL
, pool
));
4964 /* Make an array of the changed paths, and sort them depth-first-ily. */
4965 changed_paths
= apr_array_make(pool
, apr_hash_count(changes
) + 1,
4966 sizeof(const char *));
4967 for (hi
= apr_hash_first(pool
, changes
); hi
; hi
= apr_hash_next(hi
))
4970 apr_hash_this(hi
, &key
, NULL
, NULL
);
4971 APR_ARRAY_PUSH(changed_paths
, const char *) = key
;
4973 qsort(changed_paths
->elts
, changed_paths
->nelts
,
4974 changed_paths
->elt_size
, svn_sort_compare_paths
);
4976 /* Now, traverse the array of changed paths, verify locks. Note
4977 that if we need to do a recursive verification a path, we'll skip
4978 over children of that path when we get to them. */
4979 for (i
= 0; i
< changed_paths
->nelts
; i
++)
4982 svn_fs_path_change_t
*change
;
4983 svn_boolean_t recurse
= TRUE
;
4985 svn_pool_clear(subpool
);
4986 path
= APR_ARRAY_IDX(changed_paths
, i
, const char *);
4988 /* If this path has already been verified as part of a recursive
4989 check of one of its parents, no need to do it again. */
4991 && svn_path_is_child(last_recursed
->data
, path
, subpool
))
4994 /* Fetch the change associated with our path. */
4995 change
= apr_hash_get(changes
, path
, APR_HASH_KEY_STRING
);
4997 /* What does it mean to succeed at lock verification for a given
4998 path? For an existing file or directory getting modified
4999 (text, props), it means we hold the lock on the file or
5000 directory. For paths being added or removed, we need to hold
5001 the locks for that path and any children of that path.
5003 WHEW! We have no reliable way to determine the node kind
5004 of deleted items, but fortunately we are going to do a
5005 recursive check on deleted paths regardless of their kind. */
5006 if (change
->change_kind
== svn_fs_path_change_modify
)
5008 SVN_ERR(svn_fs_fs__allow_locked_operation(path
, fs
, recurse
, TRUE
,
5011 /* If we just did a recursive check, remember the path we
5012 checked (so children can be skipped). */
5015 if (! last_recursed
)
5016 last_recursed
= svn_stringbuf_create(path
, pool
);
5018 svn_stringbuf_set(last_recursed
, path
);
5021 svn_pool_destroy(subpool
);
5022 return SVN_NO_ERROR
;
5025 /* Baton used for commit_body below. */
5026 struct commit_baton
{
5027 svn_revnum_t
*new_rev_p
;
5030 apr_hash_t
*node_origins
;
5033 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
5034 This implements the svn_fs_fs__with_write_lock() 'body' callback
5035 type. BATON is a 'struct commit_baton *'. */
5036 static svn_error_t
*
5037 commit_body(void *baton
, apr_pool_t
*pool
)
5039 struct commit_baton
*cb
= baton
;
5040 fs_fs_data_t
*ffd
= cb
->fs
->fsap_data
;
5041 const char *old_rev_filename
, *rev_filename
, *proto_filename
;
5042 const char *revprop_filename
, *final_revprop
;
5043 const svn_fs_id_t
*root_id
, *new_root_id
;
5044 const char *start_node_id
, *start_copy_id
;
5045 svn_revnum_t old_rev
, new_rev
;
5046 apr_file_t
*proto_file
;
5047 void *proto_file_lockcookie
;
5048 apr_off_t changed_path_offset
;
5050 apr_hash_t
*txnprops
;
5052 apr_hash_t
*target_mergeinfo
;
5054 /* Get the current youngest revision. */
5055 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev
, cb
->fs
, pool
));
5057 /* Check to make sure this transaction is based off the most recent
5059 if (cb
->txn
->base_rev
!= old_rev
)
5060 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE
, NULL
,
5061 _("Transaction out of date"));
5063 /* Locks may have been added (or stolen) between the calling of
5064 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
5065 to re-examine every changed-path in the txn and re-verify all
5066 discovered locks. */
5067 SVN_ERR(verify_locks(cb
->fs
, cb
->txn
->id
, pool
));
5069 /* Get the next node_id and copy_id to use. */
5070 SVN_ERR(get_next_revision_ids(&start_node_id
, &start_copy_id
, cb
->fs
,
5073 /* We are going to be one better than this puny old revision. */
5074 new_rev
= old_rev
+ 1;
5076 /* Get a write handle on the proto revision file. */
5077 SVN_ERR(get_writable_proto_rev(&proto_file
, &proto_file_lockcookie
,
5078 cb
->fs
, cb
->txn
->id
, pool
));
5080 /* Write out all the node-revisions and directory contents. */
5081 root_id
= svn_fs_fs__id_txn_create("0", "0", cb
->txn
->id
, pool
);
5082 SVN_ERR(write_final_rev(&new_root_id
, proto_file
, new_rev
, cb
->fs
, root_id
,
5083 start_node_id
, start_copy_id
, cb
->node_origins
,
5086 /* Write the changed-path information. */
5087 SVN_ERR(write_final_changed_path_info(&changed_path_offset
, proto_file
,
5088 cb
->fs
, cb
->txn
->id
, pool
));
5090 /* Write the final line. */
5091 buf
= apr_psprintf(pool
, "\n%" APR_OFF_T_FMT
" %" APR_OFF_T_FMT
"\n",
5092 svn_fs_fs__id_offset(new_root_id
),
5093 changed_path_offset
);
5094 SVN_ERR(svn_io_file_write_full(proto_file
, buf
, strlen(buf
), NULL
,
5096 SVN_ERR(svn_io_file_flush_to_disk(proto_file
, pool
));
5097 SVN_ERR(svn_io_file_close(proto_file
, pool
));
5099 /* We don't unlock the prototype revision file immediately to avoid a
5100 race with another caller writing to the prototype revision file
5101 before we commit it. */
5103 /* Remove any temporary txn props representing 'flags'. */
5104 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops
, cb
->txn
, pool
));
5107 apr_array_header_t
*props
= apr_array_make(pool
, 3, sizeof(svn_prop_t
));
5111 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_OOD
,
5112 APR_HASH_KEY_STRING
))
5114 prop
.name
= SVN_FS__PROP_TXN_CHECK_OOD
;
5115 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5118 if (apr_hash_get(txnprops
, SVN_FS__PROP_TXN_CHECK_LOCKS
,
5119 APR_HASH_KEY_STRING
))
5121 prop
.name
= SVN_FS__PROP_TXN_CHECK_LOCKS
;
5122 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
5125 if (! apr_is_empty_array(props
))
5126 SVN_ERR(svn_fs_fs__change_txn_props(cb
->txn
, props
, pool
));
5129 SVN_ERR(get_txn_mergeinfo(&target_mergeinfo
, cb
->txn
->fs
, cb
->txn
->id
, pool
));
5131 /* Create the shard for the rev and revprop file, if we're sharding and
5132 this is the first revision of a new shard. We don't care if this
5133 fails because the shard already existed for some reason. */
5134 if (ffd
->max_files_per_dir
&& new_rev
% ffd
->max_files_per_dir
== 0)
5137 err
= svn_io_dir_make(path_rev_shard(cb
->fs
, new_rev
, pool
),
5138 APR_OS_DEFAULT
, pool
);
5139 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5140 svn_error_clear(err
);
5143 err
= svn_io_dir_make(path_revprops_shard(cb
->fs
, new_rev
, pool
),
5144 APR_OS_DEFAULT
, pool
);
5145 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5146 svn_error_clear(err
);
5151 /* Move the finished rev file into place. */
5152 old_rev_filename
= svn_fs_fs__path_rev(cb
->fs
, old_rev
, pool
);
5153 rev_filename
= svn_fs_fs__path_rev(cb
->fs
, new_rev
, pool
);
5154 proto_filename
= path_txn_proto_rev(cb
->fs
, cb
->txn
->id
, pool
);
5155 SVN_ERR(svn_fs_fs__move_into_place(proto_filename
, rev_filename
,
5156 old_rev_filename
, pool
));
5158 /* Now that we've moved the prototype revision file out of the way,
5159 we can unlock it (since further attempts to write to the file
5160 will fail as it no longer exists). We must do this so that we can
5161 remove the transaction directory later. */
5162 SVN_ERR(unlock_proto_rev(cb
->fs
, cb
->txn
->id
, proto_file_lockcookie
, pool
));
5164 /* Update commit time to ensure that svn:date revprops remain ordered. */
5165 date
.data
= svn_time_to_cstring(apr_time_now(), pool
);
5166 date
.len
= strlen(date
.data
);
5168 SVN_ERR(svn_fs_fs__change_txn_prop(cb
->txn
, SVN_PROP_REVISION_DATE
,
5171 /* Move the revprops file into place. */
5172 revprop_filename
= path_txn_props(cb
->fs
, cb
->txn
->id
, pool
);
5173 final_revprop
= path_revprops(cb
->fs
, new_rev
, pool
);
5174 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename
, final_revprop
,
5175 old_rev_filename
, pool
));
5177 /* Update the 'current' file. */
5178 SVN_ERR(write_final_current(cb
->fs
, cb
->txn
->id
, new_rev
, start_node_id
,
5179 start_copy_id
, pool
));
5180 ffd
->youngest_rev_cache
= new_rev
;
5182 /* Remove this transaction directory. */
5183 SVN_ERR(svn_fs_fs__purge_txn(cb
->fs
, cb
->txn
->id
, pool
));
5185 *cb
->new_rev_p
= new_rev
;
5187 return SVN_NO_ERROR
;
5191 svn_fs_fs__commit(svn_revnum_t
*new_rev_p
,
5196 struct commit_baton cb
;
5197 apr_hash_t
*node_origins
= apr_hash_make(pool
);
5199 cb
.new_rev_p
= new_rev_p
;
5202 cb
.node_origins
= node_origins
;
5203 SVN_ERR(svn_fs_fs__with_write_lock(fs
, commit_body
, &cb
, pool
));
5205 /* Now that we're no longer locked, we can update the node-origins
5206 cache without blocking writers. */
5207 if (apr_hash_count(node_origins
) > 0)
5208 SVN_ERR(svn_fs_fs__set_node_origins(fs
, node_origins
, pool
));
5210 return SVN_NO_ERROR
;
5214 svn_fs_fs__reserve_copy_id(const char **copy_id_p
,
5219 const char *cur_node_id
, *cur_copy_id
;
5223 /* First read in the current next-ids file. */
5224 SVN_ERR(read_next_ids(&cur_node_id
, &cur_copy_id
, fs
, txn_id
, pool
));
5226 copy_id
= apr_pcalloc(pool
, strlen(cur_copy_id
) + 2);
5228 len
= strlen(cur_copy_id
);
5229 svn_fs_fs__next_key(cur_copy_id
, &len
, copy_id
);
5231 SVN_ERR(write_next_ids(fs
, txn_id
, cur_node_id
, copy_id
, pool
));
5233 *copy_id_p
= apr_pstrcat(pool
, "_", cur_copy_id
, NULL
);
5235 return SVN_NO_ERROR
;
5238 /* Write out the zeroth revision for filesystem FS. */
5239 static svn_error_t
*
5240 write_revision_zero(svn_fs_t
*fs
)
5242 apr_hash_t
*proplist
;
5245 /* Write out a rev file for revision 0. */
5246 SVN_ERR(svn_io_file_create(svn_fs_fs__path_rev(fs
, 0, fs
->pool
),
5247 "PLAIN\nEND\nENDREP\n"
5252 "2d2977d1c96f487abe4a1e202dd03b4e\n"
5254 "\n\n17 107\n", fs
->pool
));
5256 /* Set a date on revision 0. */
5257 date
.data
= svn_time_to_cstring(apr_time_now(), fs
->pool
);
5258 date
.len
= strlen(date
.data
);
5259 proplist
= apr_hash_make(fs
->pool
);
5260 apr_hash_set(proplist
, SVN_PROP_REVISION_DATE
, APR_HASH_KEY_STRING
, &date
);
5261 return svn_fs_fs__set_revision_proplist(fs
, 0, proplist
, fs
->pool
);
5265 svn_fs_fs__create(svn_fs_t
*fs
,
5269 int format
= SVN_FS_FS__FORMAT_NUMBER
;
5270 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5272 fs
->path
= apr_pstrdup(pool
, path
);
5273 /* See if we had an explicitly requested pre-1.4- or pre-1.5-compatible. */
5276 if (apr_hash_get(fs
->config
, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE
,
5277 APR_HASH_KEY_STRING
))
5279 else if (apr_hash_get(fs
->config
, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE
,
5280 APR_HASH_KEY_STRING
))
5283 ffd
->format
= format
;
5285 /* Override the default linear layout if this is a new-enough format. */
5286 if (format
>= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT
)
5287 ffd
->max_files_per_dir
= SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
;
5289 if (ffd
->max_files_per_dir
)
5291 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs
, 0, pool
),
5293 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs
, 0, pool
),
5298 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_REVS_DIR
,
5301 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
,
5306 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_TXNS_DIR
,
5310 if (format
>= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT
)
5311 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path
, PATH_TXN_PROTOS_DIR
,
5315 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs
, pool
), "0 1 1\n",
5317 SVN_ERR(svn_io_file_create(path_lock(fs
, pool
), "", pool
));
5318 SVN_ERR(svn_fs_fs__set_uuid(fs
, svn_uuid_generate(pool
), pool
));
5320 SVN_ERR(write_revision_zero(fs
));
5322 /* Create the txn-current file if the repository supports
5323 the transaction sequence file. */
5324 if (format
>= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT
)
5326 SVN_ERR(svn_io_file_create(path_txn_current(fs
, pool
),
5328 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs
, pool
),
5332 /* This filesystem is ready. Stamp it with a format number. */
5333 SVN_ERR(write_format(path_format(fs
, pool
),
5334 ffd
->format
, ffd
->max_files_per_dir
, pool
));
5336 ffd
->youngest_rev_cache
= 0;
5337 return SVN_NO_ERROR
;
5340 /* Part of the recovery procedure. Return the largest revision *REV in
5341 filesystem FS. Use POOL for temporary allocation. */
5342 static svn_error_t
*
5343 recover_get_largest_revision(svn_fs_t
*fs
, svn_revnum_t
*rev
, apr_pool_t
*pool
)
5345 /* Discovering the largest revision in the filesystem would be an
5346 expensive operation if we did a readdir() or searched linearly,
5347 so we'll do a form of binary search. left is a revision that we
5348 know exists, right a revision that we know does not exist. */
5349 apr_pool_t
*iterpool
;
5350 svn_revnum_t left
, right
= 1;
5352 iterpool
= svn_pool_create(pool
);
5353 /* Keep doubling right, until we find a revision that doesn't exist. */
5356 svn_node_kind_t kind
;
5357 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs
, right
, iterpool
),
5359 svn_pool_clear(iterpool
);
5361 if (kind
== svn_node_none
)
5369 /* We know that left exists and right doesn't. Do a normal bsearch to find
5370 the last revision. */
5371 while (left
+ 1 < right
)
5373 svn_revnum_t probe
= left
+ ((right
- left
) / 2);
5374 svn_node_kind_t kind
;
5376 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs
, probe
, iterpool
),
5378 svn_pool_clear(iterpool
);
5380 if (kind
== svn_node_none
)
5386 svn_pool_destroy(iterpool
);
5388 /* left is now the largest revision that exists. */
5390 return SVN_NO_ERROR
;
5393 /* A baton for reading a fixed amount from an open file. For
5394 recover_find_max_ids() below. */
5395 struct recover_read_from_file_baton
5399 apr_size_t remaining
;
5402 /* A stream read handler used by recover_find_max_ids() below.
5403 Read and return at most BATON->REMAINING bytes from the stream,
5404 returning nothing after that to indicate EOF. */
5405 static svn_error_t
*
5406 read_handler_recover(void *baton
, char *buffer
, apr_size_t
*len
)
5408 struct recover_read_from_file_baton
*b
= baton
;
5409 apr_size_t bytes_to_read
= *len
;
5411 if (b
->remaining
== 0)
5413 /* Return a successful read of zero bytes to signal EOF. */
5415 return SVN_NO_ERROR
;
5418 if (bytes_to_read
> b
->remaining
)
5419 bytes_to_read
= b
->remaining
;
5420 b
->remaining
-= bytes_to_read
;
5422 return svn_io_file_read_full(b
->file
, buffer
, bytes_to_read
, len
, b
->pool
);
5425 /* Part of the recovery procedure. Read the directory noderev at offset
5426 OFFSET of file REV_FILE (the revision file of revision REV of
5427 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
5428 and copy-id of that node, if greater than the current value stored
5429 in either. Recurse into any child directories that were modified in
5432 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
5434 Perform temporary allocation in POOL. */
5435 static svn_error_t
*
5436 recover_find_max_ids(svn_fs_t
*fs
, svn_revnum_t rev
,
5437 apr_file_t
*rev_file
, apr_off_t offset
,
5438 char *max_node_id
, char *max_copy_id
,
5441 apr_hash_t
*headers
;
5443 node_revision_t noderev
;
5444 struct rep_args
*ra
;
5445 struct recover_read_from_file_baton baton
;
5446 svn_stream_t
*stream
;
5447 apr_hash_t
*entries
;
5448 apr_hash_index_t
*hi
;
5449 apr_pool_t
*iterpool
;
5451 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
5452 SVN_ERR(read_header_block(&headers
, rev_file
, pool
));
5454 /* We're going to populate a skeletal noderev - just the id and data_rep. */
5455 value
= apr_hash_get(headers
, HEADER_ID
, APR_HASH_KEY_STRING
);
5456 noderev
.id
= svn_fs_fs__id_parse(value
, strlen(value
), pool
);
5458 /* Check that this is a directory. It should be. */
5459 value
= apr_hash_get(headers
, HEADER_TYPE
, APR_HASH_KEY_STRING
);
5460 if (value
== NULL
|| strcmp(value
, KIND_DIR
) != 0)
5461 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5462 _("Recovery encountered a non-directory node"));
5464 /* Get the data location. No data location indicates an empty directory. */
5465 value
= apr_hash_get(headers
, HEADER_TEXT
, APR_HASH_KEY_STRING
);
5467 return SVN_NO_ERROR
;
5468 SVN_ERR(read_rep_offsets(&noderev
.data_rep
, value
, NULL
, FALSE
, pool
));
5470 /* If the directory's data representation wasn't changed in this revision,
5471 we've already scanned the directory's contents for noderevs, so we don't
5472 need to again. This will occur if a property is changed on a directory
5473 without changing the directory's contents. */
5474 if (noderev
.data_rep
->revision
!= rev
)
5475 return SVN_NO_ERROR
;
5477 /* We could use get_dir_contents(), but this is much cheaper. It does
5478 rely on directory entries being stored as PLAIN reps, though. */
5479 offset
= noderev
.data_rep
->offset
;
5480 SVN_ERR(svn_io_file_seek(rev_file
, APR_SET
, &offset
, pool
));
5481 SVN_ERR(read_rep_line(&ra
, rev_file
, pool
));
5483 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5484 _("Recovery encountered a deltified directory "
5487 /* Now create a stream that's allowed to read only as much data as is
5488 stored in the representation. */
5489 baton
.file
= rev_file
;
5491 baton
.remaining
= noderev
.data_rep
->expanded_size
;
5492 stream
= svn_stream_create(&baton
, pool
);
5493 svn_stream_set_read(stream
, read_handler_recover
);
5495 /* Now read the entries from that stream. */
5496 entries
= apr_hash_make(pool
);
5497 SVN_ERR(svn_hash_read2(entries
, stream
, SVN_HASH_TERMINATOR
, pool
));
5498 SVN_ERR(svn_stream_close(stream
));
5500 /* Now check each of the entries in our directory to find new node and
5501 copy ids, and recurse into new subdirectories. */
5502 iterpool
= svn_pool_create(pool
);
5503 for (hi
= apr_hash_first(NULL
, entries
); hi
; hi
= apr_hash_next(hi
))
5507 char *str
, *last_str
;
5508 svn_node_kind_t kind
;
5510 const char *node_id
, *copy_id
;
5511 apr_off_t child_dir_offset
;
5513 svn_pool_clear(iterpool
);
5515 apr_hash_this(hi
, NULL
, NULL
, &val
);
5516 str_val
= apr_pstrdup(iterpool
, *((char **)val
));
5518 str
= apr_strtok(str_val
, " ", &last_str
);
5520 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5521 _("Directory entry corrupt"));
5523 if (strcmp(str
, KIND_FILE
) == 0)
5524 kind
= svn_node_file
;
5525 else if (strcmp(str
, KIND_DIR
) == 0)
5526 kind
= svn_node_dir
;
5529 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5530 _("Directory entry corrupt"));
5533 str
= apr_strtok(NULL
, " ", &last_str
);
5535 return svn_error_create(SVN_ERR_FS_CORRUPT
, NULL
,
5536 _("Directory entry corrupt"));
5538 id
= svn_fs_fs__id_parse(str
, strlen(str
), iterpool
);
5540 if (svn_fs_fs__id_rev(id
) != rev
)
5542 /* If the node wasn't modified in this revision, we've already
5543 checked the node and copy id. */
5547 node_id
= svn_fs_fs__id_node_id(id
);
5548 copy_id
= svn_fs_fs__id_copy_id(id
);
5550 if (svn_fs_fs__key_compare(node_id
, max_node_id
) > 0)
5551 strcpy(max_node_id
, node_id
);
5552 if (svn_fs_fs__key_compare(copy_id
, max_copy_id
) > 0)
5553 strcpy(max_copy_id
, copy_id
);
5555 if (kind
== svn_node_file
)
5558 child_dir_offset
= svn_fs_fs__id_offset(id
);
5559 SVN_ERR(recover_find_max_ids(fs
, rev
, rev_file
, child_dir_offset
,
5560 max_node_id
, max_copy_id
, iterpool
));
5562 svn_pool_destroy(iterpool
);
5564 return SVN_NO_ERROR
;
5567 /* Baton used for recover_body below. */
5568 struct recover_baton
{
5570 svn_cancel_func_t cancel_func
;
5574 /* The work-horse for svn_fs_fs__recover, called with the FS
5575 write lock. This implements the svn_fs_fs__with_write_lock()
5576 'body' callback type. BATON is a 'struct recover_baton *'. */
5577 static svn_error_t
*
5578 recover_body(void *baton
, apr_pool_t
*pool
)
5580 struct recover_baton
*b
= baton
;
5581 svn_fs_t
*fs
= b
->fs
;
5582 svn_revnum_t rev
, max_rev
;
5583 apr_pool_t
*iterpool
;
5584 char max_node_id
[MAX_KEY_SIZE
] = "0", max_copy_id
[MAX_KEY_SIZE
] = "0";
5585 char next_node_id
[MAX_KEY_SIZE
], next_copy_id
[MAX_KEY_SIZE
];
5588 /* First, we need to know the largest revision in the filesystem. */
5589 SVN_ERR(recover_get_largest_revision(fs
, &max_rev
, pool
));
5591 /* Next we need to find the maximum node id and copy id in use across the
5592 filesystem. Unfortunately, the only way we can get this information
5593 is to scan all the noderevs of all the revisions and keep track as
5595 iterpool
= svn_pool_create(pool
);
5596 for (rev
= 0; rev
<= max_rev
; rev
++)
5598 apr_file_t
*rev_file
;
5599 apr_off_t root_offset
;
5601 svn_pool_clear(iterpool
);
5604 SVN_ERR(b
->cancel_func(b
->cancel_baton
));
5606 SVN_ERR(svn_io_file_open(&rev_file
,
5607 svn_fs_fs__path_rev(fs
, rev
, iterpool
),
5608 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
5610 SVN_ERR(get_root_changes_offset(&root_offset
, NULL
, rev_file
, iterpool
));
5611 SVN_ERR(recover_find_max_ids(fs
, rev
, rev_file
, root_offset
,
5612 max_node_id
, max_copy_id
, iterpool
));
5614 svn_pool_destroy(iterpool
);
5616 /* Now that we finally have the maximum revision, node-id and copy-id, we
5617 can bump the two ids to get the next of each, and store them all in a
5618 new current file. */
5619 len
= strlen(max_node_id
);
5620 svn_fs_fs__next_key(max_node_id
, &len
, next_node_id
);
5621 len
= strlen(max_copy_id
);
5622 svn_fs_fs__next_key(max_copy_id
, &len
, next_copy_id
);
5624 SVN_ERR(write_current(fs
, max_rev
, next_node_id
, next_copy_id
, pool
));
5626 return SVN_NO_ERROR
;
5629 /* This implements the fs_library_vtable_t.recover() API. */
5631 svn_fs_fs__recover(svn_fs_t
*fs
,
5632 svn_cancel_func_t cancel_func
, void *cancel_baton
,
5635 struct recover_baton b
;
5637 /* We have no way to take out an exclusive lock in FSFS, so we're
5638 restricted as to the types of recovery we can do. Luckily,
5639 we just want to recreate the current file, and we can do that just
5640 by blocking other writers. */
5642 b
.cancel_func
= cancel_func
;
5643 b
.cancel_baton
= cancel_baton
;
5644 return svn_fs_fs__with_write_lock(fs
, recover_body
, &b
, pool
);
5648 svn_fs_fs__get_uuid(svn_fs_t
*fs
,
5649 const char **uuid_p
,
5652 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5654 *uuid_p
= apr_pstrdup(pool
, ffd
->uuid
);
5655 return SVN_NO_ERROR
;
5659 svn_fs_fs__set_uuid(svn_fs_t
*fs
,
5663 apr_file_t
*uuid_file
;
5664 const char *tmp_path
;
5665 const char *uuid_path
= path_uuid(fs
, pool
);
5666 fs_fs_data_t
*ffd
= fs
->fsap_data
;
5668 SVN_ERR(svn_io_open_unique_file2(&uuid_file
, &tmp_path
, uuid_path
,
5669 ".tmp", svn_io_file_del_none
, pool
));
5672 uuid
= svn_uuid_generate(pool
);
5674 SVN_ERR(svn_io_file_write_full(uuid_file
, uuid
, strlen(uuid
), NULL
,
5676 SVN_ERR(svn_io_file_write_full(uuid_file
, "\n", 1, NULL
, pool
));
5678 SVN_ERR(svn_io_file_close(uuid_file
, pool
));
5680 /* We use the permissions of the 'current' file, because the 'uuid'
5681 file does not exist during repository creation. */
5682 SVN_ERR(svn_fs_fs__move_into_place(tmp_path
, uuid_path
,
5683 svn_fs_fs__path_current(fs
, pool
), pool
));
5685 ffd
->uuid
= apr_pstrdup(fs
->pool
, uuid
);
5687 return SVN_NO_ERROR
;
5690 /** Node origin lazy cache. */
5692 /* If directory PATH does not exist, create it and give it the same
5693 permissions as FS->path.*/
5695 svn_fs_fs__ensure_dir_exists(const char *path
,
5699 svn_error_t
*err
= svn_io_dir_make(path
, APR_OS_DEFAULT
, pool
);
5700 if (err
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
5702 svn_error_clear(err
);
5703 return SVN_NO_ERROR
;
5707 /* We successfully created a new directory. Dup the permissions
5709 SVN_ERR(svn_fs_fs__dup_perms(path
, fs
->path
, pool
));
5711 return SVN_NO_ERROR
;
5715 svn_fs_fs__get_node_origin(const svn_fs_id_t
**origin_id
,
5717 const char *node_id
,
5721 svn_stringbuf_t
*origin_stringbuf
;
5725 err
= svn_io_file_open(&fd
, path_node_origin(fs
, node_id
, pool
),
5726 APR_READ
, APR_OS_DEFAULT
, pool
);
5727 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
5729 svn_error_clear(err
);
5730 return SVN_NO_ERROR
;
5734 SVN_ERR(svn_stringbuf_from_aprfile(&origin_stringbuf
, fd
, pool
));
5736 *origin_id
= svn_fs_fs__id_parse(origin_stringbuf
->data
,
5737 origin_stringbuf
->len
, pool
);
5739 SVN_ERR(svn_io_file_close(fd
, pool
));
5741 return SVN_NO_ERROR
;
5744 /* Helper for svn_fs_fs__set_node_origin[s]. Exactly like
5745 svn_fs_fs__set_node_origin, except that it throws an error if the
5746 file can't be written. */
5747 static svn_error_t
*
5748 set_node_origin(svn_fs_t
*fs
,
5749 const char *node_id
,
5750 const svn_fs_id_t
*node_rev_id
,
5754 svn_string_t
*node_rev_id_string
;
5756 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_join(fs
->path
,
5757 PATH_NODE_ORIGINS_DIR
,
5761 node_rev_id_string
= svn_fs_fs__id_unparse(node_rev_id
, pool
);
5763 SVN_ERR(svn_io_file_open(&file
, path_node_origin(fs
, node_id
, pool
),
5764 APR_WRITE
| APR_CREATE
| APR_TRUNCATE
5765 | APR_BUFFERED
, APR_OS_DEFAULT
, pool
));
5766 SVN_ERR(svn_io_file_write_full(file
,
5767 node_rev_id_string
->data
,
5768 node_rev_id_string
->len
, NULL
, pool
));
5769 SVN_ERR(svn_io_file_close(file
, pool
));
5771 return SVN_NO_ERROR
;
5776 svn_fs_fs__set_node_origins(svn_fs_t
*fs
,
5777 apr_hash_t
*node_origins
,
5780 apr_hash_index_t
*hi
;
5781 apr_pool_t
*iterpool
= svn_pool_create(pool
);
5783 for (hi
= apr_hash_first(pool
, node_origins
);
5785 hi
= apr_hash_next(hi
))
5789 const char *node_id
;
5790 const svn_fs_id_t
*node_rev_id
;
5793 svn_pool_clear(iterpool
);
5795 apr_hash_this(hi
, &key
, NULL
, &val
);
5799 err
= set_node_origin(fs
, node_id
, node_rev_id
, iterpool
);
5800 if (err
&& APR_STATUS_IS_EACCES(err
->apr_err
))
5802 /* It's just a cache; stop trying if I can't write. */
5803 svn_error_clear(err
);
5811 svn_pool_destroy(iterpool
);
5812 return SVN_NO_ERROR
;
5816 svn_fs_fs__set_node_origin(svn_fs_t
*fs
,
5817 const char *node_id
,
5818 const svn_fs_id_t
*node_rev_id
,
5821 svn_error_t
*err
= set_node_origin(fs
, node_id
, node_rev_id
, pool
);
5822 if (err
&& APR_STATUS_IS_EACCES(err
->apr_err
))
5824 /* It's just a cache; stop trying if I can't write. */
5825 svn_error_clear(err
);
5833 svn_fs_fs__list_transactions(apr_array_header_t
**names_p
,
5837 const char *txn_dir
;
5838 apr_hash_t
*dirents
;
5839 apr_hash_index_t
*hi
;
5840 apr_array_header_t
*names
;
5841 apr_size_t ext_len
= strlen(PATH_EXT_TXN
);
5843 names
= apr_array_make(pool
, 1, sizeof(const char *));
5845 /* Get the transactions directory. */
5846 txn_dir
= svn_path_join(fs
->path
, PATH_TXNS_DIR
, pool
);
5848 /* Now find a listing of this directory. */
5849 SVN_ERR(svn_io_get_dirents2(&dirents
, txn_dir
, pool
));
5851 /* Loop through all the entries and return anything that ends with '.txn'. */
5852 for (hi
= apr_hash_first(pool
, dirents
); hi
; hi
= apr_hash_next(hi
))
5855 const char *name
, *id
;
5858 apr_hash_this(hi
, &key
, &klen
, NULL
);
5861 /* The name must end with ".txn" to be considered a transaction. */
5862 if ((apr_size_t
) klen
<= ext_len
5863 || (strcmp(name
+ klen
- ext_len
, PATH_EXT_TXN
)) != 0)
5866 /* Truncate the ".txn" extension and store the ID. */
5867 id
= apr_pstrndup(pool
, name
, strlen(name
) - ext_len
);
5868 APR_ARRAY_PUSH(names
, const char *) = id
;
5873 return SVN_NO_ERROR
;
5877 svn_fs_fs__open_txn(svn_fs_txn_t
**txn_p
,
5883 svn_node_kind_t kind
;
5884 transaction_t
*local_txn
;
5886 /* First check to see if the directory exists. */
5887 SVN_ERR(svn_io_check_path(path_txn_dir(fs
, name
, pool
), &kind
, pool
));
5889 /* Did we find it? */
5890 if (kind
!= svn_node_dir
)
5891 return svn_error_create(SVN_ERR_FS_NO_SUCH_TRANSACTION
, NULL
,
5892 _("No such transaction"));
5894 txn
= apr_pcalloc(pool
, sizeof(*txn
));
5896 /* Read in the root node of this transaction. */
5897 txn
->id
= apr_pstrdup(pool
, name
);
5900 SVN_ERR(svn_fs_fs__get_txn(&local_txn
, fs
, name
, pool
));
5902 txn
->base_rev
= svn_fs_fs__id_rev(local_txn
->base_id
);
5904 txn
->vtable
= &txn_vtable
;
5907 return SVN_NO_ERROR
;
5911 svn_fs_fs__txn_proplist(apr_hash_t
**table_p
,
5915 apr_hash_t
*proplist
= apr_hash_make(pool
);
5916 SVN_ERR(get_txn_proplist(proplist
, txn
->fs
, txn
->id
, pool
));
5917 *table_p
= proplist
;
5919 return SVN_NO_ERROR
;
5923 svn_fs_fs__delete_node_revision(svn_fs_t
*fs
,
5924 const svn_fs_id_t
*id
,
5927 node_revision_t
*noderev
;
5929 SVN_ERR(svn_fs_fs__get_node_revision(&noderev
, fs
, id
, pool
));
5931 /* Delete any mutable property representation. */
5932 if (noderev
->prop_rep
&& noderev
->prop_rep
->txn_id
)
5933 SVN_ERR(svn_io_remove_file(path_txn_node_props(fs
, id
, pool
), pool
));
5935 /* Delete any mutable data representation. */
5936 if (noderev
->data_rep
&& noderev
->data_rep
->txn_id
5937 && noderev
->kind
== svn_node_dir
)
5938 SVN_ERR(svn_io_remove_file(path_txn_node_children(fs
, id
, pool
), pool
));
5940 return svn_io_remove_file(path_txn_node_rev(fs
, id
, pool
), pool
);
5948 svn_fs_fs__revision_prop(svn_string_t
**value_p
,
5951 const char *propname
,
5956 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
5957 SVN_ERR(svn_fs_fs__revision_proplist(&table
, fs
, rev
, pool
));
5959 *value_p
= apr_hash_get(table
, propname
, APR_HASH_KEY_STRING
);
5961 return SVN_NO_ERROR
;
5965 /* Baton used for change_rev_prop_body below. */
5966 struct change_rev_prop_baton
{
5970 const svn_string_t
*value
;
5973 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
5974 write lock. This implements the svn_fs_fs__with_write_lock()
5975 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
5977 static svn_error_t
*
5978 change_rev_prop_body(void *baton
, apr_pool_t
*pool
)
5980 struct change_rev_prop_baton
*cb
= baton
;
5983 SVN_ERR(svn_fs_fs__revision_proplist(&table
, cb
->fs
, cb
->rev
, pool
));
5985 apr_hash_set(table
, cb
->name
, APR_HASH_KEY_STRING
, cb
->value
);
5987 SVN_ERR(svn_fs_fs__set_revision_proplist(cb
->fs
, cb
->rev
, table
, pool
));
5989 return SVN_NO_ERROR
;
5993 svn_fs_fs__change_rev_prop(svn_fs_t
*fs
,
5996 const svn_string_t
*value
,
5999 struct change_rev_prop_baton cb
;
6001 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
6008 return svn_fs_fs__with_write_lock(fs
, change_rev_prop_body
, &cb
, pool
);
6013 /*** Transactions ***/
6016 svn_fs_fs__get_txn_ids(const svn_fs_id_t
**root_id_p
,
6017 const svn_fs_id_t
**base_root_id_p
,
6019 const char *txn_name
,
6023 SVN_ERR(svn_fs_fs__get_txn(&txn
, fs
, txn_name
, pool
));
6024 *root_id_p
= txn
->root_id
;
6025 *base_root_id_p
= txn
->base_id
;
6026 return SVN_NO_ERROR
;
6030 /* Generic transaction operations. */
6033 svn_fs_fs__txn_prop(svn_string_t
**value_p
,
6035 const char *propname
,
6039 svn_fs_t
*fs
= txn
->fs
;
6041 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
6042 SVN_ERR(svn_fs_fs__txn_proplist(&table
, txn
, pool
));
6044 *value_p
= apr_hash_get(table
, propname
, APR_HASH_KEY_STRING
);
6046 return SVN_NO_ERROR
;
6050 svn_fs_fs__begin_txn(svn_fs_txn_t
**txn_p
,
6058 apr_array_header_t
*props
= apr_array_make(pool
, 3, sizeof(svn_prop_t
));
6060 SVN_ERR(svn_fs__check_fs(fs
, TRUE
));
6062 SVN_ERR(svn_fs_fs__create_txn(txn_p
, fs
, rev
, pool
));
6064 /* Put a datestamp on the newly created txn, so we always know
6065 exactly how old it is. (This will help sysadmins identify
6066 long-abandoned txns that may need to be manually removed.) When
6067 a txn is promoted to a revision, this property will be
6068 automatically overwritten with a revision datestamp. */
6069 date
.data
= svn_time_to_cstring(apr_time_now(), pool
);
6070 date
.len
= strlen(date
.data
);
6072 prop
.name
= SVN_PROP_REVISION_DATE
;
6074 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
6076 /* Set temporary txn props that represent the requested 'flags'
6078 if (flags
& SVN_FS_TXN_CHECK_OOD
)
6080 prop
.name
= SVN_FS__PROP_TXN_CHECK_OOD
;
6081 prop
.value
= svn_string_create("true", pool
);
6082 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
6085 if (flags
& SVN_FS_TXN_CHECK_LOCKS
)
6087 prop
.name
= SVN_FS__PROP_TXN_CHECK_LOCKS
;
6088 prop
.value
= svn_string_create("true", pool
);
6089 APR_ARRAY_PUSH(props
, svn_prop_t
) = prop
;
6092 return svn_fs_fs__change_txn_props(*txn_p
, props
, pool
);