Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_fs_fs / fs_fs.c
blob540e86e06ec95758a9e03b28bf23dca8e5f91945
1 /* fs_fs.c --- filesystem operations specific to fs_fs
3 * ====================================================================
4 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <assert.h>
23 #include <errno.h>
25 #include <apr_general.h>
26 #include <apr_pools.h>
27 #include <apr_file_io.h>
28 #include <apr_uuid.h>
29 #include <apr_lib.h>
30 #include <apr_md5.h>
31 #include <apr_thread_mutex.h>
33 #include "svn_pools.h"
34 #include "svn_fs.h"
35 #include "svn_path.h"
36 #include "svn_hash.h"
37 #include "svn_md5.h"
38 #include "svn_props.h"
39 #include "svn_sorts.h"
40 #include "svn_time.h"
41 #include "svn_mergeinfo.h"
43 #include "fs.h"
44 #include "err.h"
45 #include "tree.h"
46 #include "lock.h"
47 #include "key-gen.h"
48 #include "fs_fs.h"
49 #include "id.h"
51 #include "private/svn_fs_util.h"
52 #include "../libsvn_fs/fs-loader.h"
54 #include "svn_private_config.h"
56 /* An arbitrary maximum path length, so clients can't run us out of memory
57 * by giving us arbitrarily large paths. */
58 #define FSFS_MAX_PATH_LEN 4096
60 /* The default maximum number of files per directory to store in the
61 rev and revprops directory. The number below is somewhat arbitrary,
62 and can be overriden by defining the macro while compiling; the
63 figure of 1000 is reasonable for VFAT filesystems, which are by far
64 the worst performers in this area. */
65 #ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
66 #define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
67 #endif
69 /* Following are defines that specify the textual elements of the
70 native filesystem directories and revision files. */
72 /* Headers used to describe node-revision in the revision file. */
73 #define HEADER_ID "id"
74 #define HEADER_TYPE "type"
75 #define HEADER_COUNT "count"
76 #define HEADER_PROPS "props"
77 #define HEADER_TEXT "text"
78 #define HEADER_CPATH "cpath"
79 #define HEADER_PRED "pred"
80 #define HEADER_COPYFROM "copyfrom"
81 #define HEADER_COPYROOT "copyroot"
82 #define HEADER_FRESHTXNRT "is-fresh-txn-root"
83 #define HEADER_MINFO_HERE "minfo-here"
84 #define HEADER_MINFO_CNT "minfo-cnt"
86 /* Kinds that a change can be. */
87 #define ACTION_MODIFY "modify"
88 #define ACTION_ADD "add"
89 #define ACTION_DELETE "delete"
90 #define ACTION_REPLACE "replace"
91 #define ACTION_RESET "reset"
93 /* True and False flags. */
94 #define FLAG_TRUE "true"
95 #define FLAG_FALSE "false"
97 /* Kinds that a node-rev can be. */
98 #define KIND_FILE "file"
99 #define KIND_DIR "dir"
101 /* Kinds of representation. */
102 #define REP_PLAIN "PLAIN"
103 #define REP_DELTA "DELTA"
105 /* Notes:
107 To avoid opening and closing the rev-files all the time, it would
108 probably be advantageous to keep each rev-file open for the
109 lifetime of the transaction object. I'll leave that as a later
110 optimization for now.
112 I didn't keep track of pool lifetimes at all in this code. There
113 are likely some errors because of that.
117 /* The vtable associated with an open transaction object. */
118 static txn_vtable_t txn_vtable = {
119 svn_fs_fs__commit_txn,
120 svn_fs_fs__abort_txn,
121 svn_fs_fs__txn_prop,
122 svn_fs_fs__txn_proplist,
123 svn_fs_fs__change_txn_prop,
124 svn_fs_fs__txn_root,
125 svn_fs_fs__change_txn_props
128 /* Pathname helper functions */
130 static const char *
131 path_format(svn_fs_t *fs, apr_pool_t *pool)
133 return svn_path_join(fs->path, PATH_FORMAT, pool);
136 static APR_INLINE const char *
137 path_uuid(svn_fs_t *fs, apr_pool_t *pool)
139 return svn_path_join(fs->path, PATH_UUID, pool);
142 const char *
143 svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
145 return svn_path_join(fs->path, PATH_CURRENT, pool);
148 static APR_INLINE const char *
149 path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
151 return svn_path_join(fs->path, PATH_TXN_CURRENT, pool);
154 static APR_INLINE const char *
155 path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
157 return svn_path_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
160 static APR_INLINE const char *
161 path_lock(svn_fs_t *fs, apr_pool_t *pool)
163 return svn_path_join(fs->path, PATH_LOCK_FILE, pool);
166 static const char *
167 path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
169 fs_fs_data_t *ffd = fs->fsap_data;
171 assert(ffd->max_files_per_dir);
172 return svn_path_join_many(pool, fs->path, PATH_REVS_DIR,
173 apr_psprintf(pool, "%ld",
174 rev / ffd->max_files_per_dir),
175 NULL);
178 const char *
179 svn_fs_fs__path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
181 fs_fs_data_t *ffd = fs->fsap_data;
183 if (ffd->max_files_per_dir)
185 return svn_path_join(path_rev_shard(fs, rev, pool),
186 apr_psprintf(pool, "%ld", rev),
187 pool);
190 return svn_path_join_many(pool, fs->path, PATH_REVS_DIR,
191 apr_psprintf(pool, "%ld", rev), NULL);
194 static const char *
195 path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
197 fs_fs_data_t *ffd = fs->fsap_data;
199 assert(ffd->max_files_per_dir);
200 return svn_path_join_many(pool, fs->path, PATH_REVPROPS_DIR,
201 apr_psprintf(pool, "%ld",
202 rev / ffd->max_files_per_dir),
203 NULL);
206 static const char *
207 path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
209 fs_fs_data_t *ffd = fs->fsap_data;
211 if (ffd->max_files_per_dir)
213 return svn_path_join(path_revprops_shard(fs, rev, pool),
214 apr_psprintf(pool, "%ld", rev),
215 pool);
218 return svn_path_join_many(pool, fs->path, PATH_REVPROPS_DIR,
219 apr_psprintf(pool, "%ld", rev), NULL);
222 static APR_INLINE const char *
223 path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
225 return svn_path_join_many(pool, fs->path, PATH_TXNS_DIR,
226 apr_pstrcat(pool, txn_id, PATH_EXT_TXN, NULL),
227 NULL);
230 static APR_INLINE const char *
231 path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
233 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
236 static APR_INLINE const char *
237 path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
239 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
242 static APR_INLINE const char *
243 path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
245 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
248 static APR_INLINE const char *
249 path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
251 fs_fs_data_t *ffd = fs->fsap_data;
252 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
253 return svn_path_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
254 apr_pstrcat(pool, txn_id, PATH_EXT_REV, NULL),
255 NULL);
256 else
257 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
260 static APR_INLINE const char *
261 path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
263 fs_fs_data_t *ffd = fs->fsap_data;
264 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
265 return svn_path_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
266 apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
267 NULL),
268 NULL);
269 else
270 return svn_path_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK, pool);
273 static const char *
274 path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
276 const char *txn_id = svn_fs_fs__id_txn_id(id);
277 const char *node_id = svn_fs_fs__id_node_id(id);
278 const char *copy_id = svn_fs_fs__id_copy_id(id);
279 const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
280 node_id, copy_id);
282 return svn_path_join(path_txn_dir(fs, txn_id, pool), name, pool);
285 static APR_INLINE const char *
286 path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
288 return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
289 NULL);
292 static APR_INLINE const char *
293 path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
295 return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
296 PATH_EXT_CHILDREN, NULL);
299 static APR_INLINE const char *
300 path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
302 int len = strlen(node_id);
303 const char *node_id_minus_last_char =
304 (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
305 return svn_path_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
306 node_id_minus_last_char, NULL);
310 /* Functions for working with shared transaction data. */
312 /* Return the transaction object for transaction TXN_ID from the
313 transaction list of filesystem FS (which must already be locked via the
314 txn_list_lock mutex). If the transaction does not exist in the list,
315 then create a new transaction object and return it (if CREATE_NEW is
316 true) or return NULL (otherwise). */
317 static fs_fs_shared_txn_data_t *
318 get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
320 fs_fs_data_t *ffd = fs->fsap_data;
321 fs_fs_shared_data_t *ffsd = ffd->shared;
322 fs_fs_shared_txn_data_t *txn;
324 for (txn = ffsd->txns; txn; txn = txn->next)
325 if (strcmp(txn->txn_id, txn_id) == 0)
326 break;
328 if (txn || !create_new)
329 return txn;
331 /* Use the transaction object from the (single-object) freelist,
332 if one is available, or otherwise create a new object. */
333 if (ffsd->free_txn)
335 txn = ffsd->free_txn;
336 ffsd->free_txn = NULL;
338 else
340 apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
341 txn = apr_palloc(subpool, sizeof(*txn));
342 txn->pool = subpool;
345 assert(strlen(txn_id) < sizeof(txn->txn_id));
346 strcpy(txn->txn_id, txn_id);
347 txn->being_written = FALSE;
349 /* Link this transaction into the head of the list. We will typically
350 be dealing with only one active transaction at a time, so it makes
351 sense for searches through the transaction list to look at the
352 newest transactions first. */
353 txn->next = ffsd->txns;
354 ffsd->txns = txn;
356 return txn;
359 /* Free the transaction object for transaction TXN_ID, and remove it
360 from the transaction list of filesystem FS (which must already be
361 locked via the txn_list_lock mutex). Do nothing if the transaction
362 does not exist. */
363 static void
364 free_shared_txn(svn_fs_t *fs, const char *txn_id)
366 fs_fs_data_t *ffd = fs->fsap_data;
367 fs_fs_shared_data_t *ffsd = ffd->shared;
368 fs_fs_shared_txn_data_t *txn, *prev = NULL;
370 for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
371 if (strcmp(txn->txn_id, txn_id) == 0)
372 break;
374 if (!txn)
375 return;
377 if (prev)
378 prev->next = txn->next;
379 else
380 ffsd->txns = txn->next;
382 /* As we typically will be dealing with one transaction after another,
383 we will maintain a single-object free list so that we can hopefully
384 keep reusing the same transaction object. */
385 if (!ffsd->free_txn)
386 ffsd->free_txn = txn;
387 else
388 svn_pool_destroy(txn->pool);
392 /* Obtain a lock on the transaction list of filesystem FS, call BODY
393 with FS, BATON, and POOL, and then unlock the transaction list.
394 Return what BODY returned. */
395 static svn_error_t *
396 with_txnlist_lock(svn_fs_t *fs,
397 svn_error_t *(*body)(svn_fs_t *fs,
398 const void *baton,
399 apr_pool_t *pool),
400 void *baton,
401 apr_pool_t *pool)
403 svn_error_t *err;
404 #if APR_HAS_THREADS
405 fs_fs_data_t *ffd = fs->fsap_data;
406 fs_fs_shared_data_t *ffsd = ffd->shared;
407 apr_status_t apr_err;
409 apr_err = apr_thread_mutex_lock(ffsd->txn_list_lock);
410 if (apr_err)
411 return svn_error_wrap_apr(apr_err, _("Can't grab FSFS txn list mutex"));
412 #endif
414 err = body(fs, baton, pool);
416 #if APR_HAS_THREADS
417 apr_err = apr_thread_mutex_unlock(ffsd->txn_list_lock);
418 if (apr_err && !err)
419 return svn_error_wrap_apr(apr_err, _("Can't ungrab FSFS txn list mutex"));
420 #endif
422 return err;
426 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
427 static svn_error_t *
428 get_lock_on_filesystem(const char *lock_filename,
429 apr_pool_t *pool)
431 svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
433 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
435 /* No lock file? No big deal; these are just empty files
436 anyway. Create it and try again. */
437 svn_error_clear(err);
438 err = NULL;
440 SVN_ERR(svn_io_file_create(lock_filename, "", pool));
441 SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
444 return err;
447 /* Obtain a write lock on the file LOCK_FILENAME (protecting with
448 LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
449 BATON and that subpool, destroy the subpool (releasing the write
450 lock) and return what BODY returned. */
451 static svn_error_t *
452 with_some_lock(svn_error_t *(*body)(void *baton,
453 apr_pool_t *pool),
454 void *baton,
455 const char *lock_filename,
456 #if APR_HAS_THREADS
457 apr_thread_mutex_t *lock_mutex,
458 #endif
459 apr_pool_t *pool)
461 apr_pool_t *subpool = svn_pool_create(pool);
462 svn_error_t *err;
464 #if APR_HAS_THREADS
465 apr_status_t status;
467 /* POSIX fcntl locks are per-process, so we need to serialize locks
468 within the process. */
469 status = apr_thread_mutex_lock(lock_mutex);
470 if (status)
471 return svn_error_wrap_apr(status,
472 _("Can't grab FSFS mutex for '%s'"),
473 lock_filename);
474 #endif
476 err = get_lock_on_filesystem(lock_filename, subpool);
478 if (!err)
479 err = body(baton, subpool);
481 svn_pool_destroy(subpool);
483 #if APR_HAS_THREADS
484 status = apr_thread_mutex_unlock(lock_mutex);
485 if (status && !err)
486 return svn_error_wrap_apr(status,
487 _("Can't ungrab FSFS mutex for '%s'"),
488 lock_filename);
489 #endif
491 return err;
494 svn_error_t *
495 svn_fs_fs__with_write_lock(svn_fs_t *fs,
496 svn_error_t *(*body)(void *baton,
497 apr_pool_t *pool),
498 void *baton,
499 apr_pool_t *pool)
501 #if APR_HAS_THREADS
502 fs_fs_data_t *ffd = fs->fsap_data;
503 fs_fs_shared_data_t *ffsd = ffd->shared;
504 apr_thread_mutex_t *mutex = ffsd->fs_write_lock;
505 #endif
507 return with_some_lock(body, baton,
508 path_lock(fs, pool),
509 #if APR_HAS_THREADS
510 mutex,
511 #endif
512 pool);
515 /* Run BODY (with BATON and POOL) while the txn-current file
516 of FS is locked. */
517 static svn_error_t *
518 with_txn_current_lock(svn_fs_t *fs,
519 svn_error_t *(*body)(void *baton,
520 apr_pool_t *pool),
521 void *baton,
522 apr_pool_t *pool)
524 #if APR_HAS_THREADS
525 fs_fs_data_t *ffd = fs->fsap_data;
526 fs_fs_shared_data_t *ffsd = ffd->shared;
527 apr_thread_mutex_t *mutex = ffsd->txn_current_lock;
528 #endif
530 return with_some_lock(body, baton,
531 path_txn_current_lock(fs, pool),
532 #if APR_HAS_THREADS
533 mutex,
534 #endif
535 pool);
538 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
539 which see. */
540 struct unlock_proto_rev_baton
542 const char *txn_id;
543 void *lockcookie;
546 /* Callback used in the implementation of unlock_proto_rev(). */
547 static svn_error_t *
548 unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
550 const struct unlock_proto_rev_baton *b = baton;
551 const char *txn_id = b->txn_id;
552 apr_file_t *lockfile = b->lockcookie;
553 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
554 apr_status_t apr_err;
556 if (!txn)
557 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
558 _("Can't unlock unknown transaction '%s'"),
559 txn_id);
560 if (!txn->being_written)
561 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
562 _("Can't unlock nonlocked transaction '%s'"),
563 txn_id);
565 apr_err = apr_file_unlock(lockfile);
566 if (apr_err)
567 return svn_error_wrap_apr
568 (apr_err,
569 _("Can't unlock prototype revision lockfile for transaction '%s'"),
570 txn_id);
571 apr_err = apr_file_close(lockfile);
572 if (apr_err)
573 return svn_error_wrap_apr
574 (apr_err,
575 _("Can't close prototype revision lockfile for transaction '%s'"),
576 txn_id);
578 txn->being_written = FALSE;
580 return SVN_NO_ERROR;
583 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
584 FS using cookie LOCKCOOKIE. The original prototype revision file must
585 have been closed _before_ calling this function.
587 Perform temporary allocations in POOL. */
588 static svn_error_t *
589 unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
590 apr_pool_t *pool)
592 struct unlock_proto_rev_baton b;
594 b.txn_id = txn_id;
595 b.lockcookie = lockcookie;
596 return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
599 /* Same as unlock_proto_rev(), but requires that the transaction list
600 lock is already held. */
601 static svn_error_t *
602 unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
603 void *lockcookie,
604 apr_pool_t *pool)
606 struct unlock_proto_rev_baton b;
608 b.txn_id = txn_id;
609 b.lockcookie = lockcookie;
610 return unlock_proto_rev_body(fs, &b, pool);
613 /* A structure used by get_writable_proto_rev() and
614 get_writable_proto_rev_body(), which see. */
615 struct get_writable_proto_rev_baton
617 apr_file_t **file;
618 void **lockcookie;
619 const char *txn_id;
622 /* Callback used in the implementation of get_writable_proto_rev(). */
623 static svn_error_t *
624 get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
626 const struct get_writable_proto_rev_baton *b = baton;
627 apr_file_t **file = b->file;
628 void **lockcookie = b->lockcookie;
629 const char *txn_id = b->txn_id;
630 svn_error_t *err;
631 fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
633 /* First, ensure that no thread in this process (including this one)
634 is currently writing to this transaction's proto-rev file. */
635 if (txn->being_written)
636 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
637 _("Cannot write to the prototype revision file "
638 "of transaction '%s' because a previous "
639 "representation is currently being written by "
640 "this process"),
641 txn_id);
644 /* We know that no thread in this process is writing to the proto-rev
645 file, and by extension, that no thread in this process is holding a
646 lock on the prototype revision lock file. It is therefore safe
647 for us to attempt to lock this file, to see if any other process
648 is holding a lock. */
651 apr_file_t *lockfile;
652 apr_status_t apr_err;
653 const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
655 /* Open the proto-rev lockfile, creating it if necessary, as it may
656 not exist if the transaction dates from before the lockfiles were
657 introduced.
659 ### We'd also like to use something like svn_io_file_lock2(), but
660 that forces us to create a subpool just to be able to unlock
661 the file, which seems a waste. */
662 SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
663 APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
665 apr_err = apr_file_lock(lockfile,
666 APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
667 if (apr_err)
669 svn_error_clear(svn_io_file_close(lockfile, pool));
671 if (APR_STATUS_IS_EAGAIN(apr_err))
672 return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
673 _("Cannot write to the prototype revision "
674 "file of transaction '%s' because a "
675 "previous representation is currently "
676 "being written by another process"),
677 txn_id);
679 return svn_error_wrap_apr(apr_err,
680 _("Can't get exclusive lock on file '%s'"),
681 svn_path_local_style(lockfile_path, pool));
684 *lockcookie = lockfile;
687 /* We've successfully locked the transaction; mark it as such. */
688 txn->being_written = TRUE;
691 /* Now open the prototype revision file and seek to the end. */
692 err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
693 APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
695 /* You might expect that we could dispense with the following seek
696 and achieve the same thing by opening the file using APR_APPEND.
697 Unfortunately, APR's buffered file implementation unconditionally
698 places its initial file pointer at the start of the file (even for
699 files opened with APR_APPEND), so we need this seek to reconcile
700 the APR file pointer to the OS file pointer (since we need to be
701 able to read the current file position later). */
702 if (!err)
704 apr_off_t offset = 0;
705 err = svn_io_file_seek(*file, APR_END, &offset, 0);
708 if (err)
710 svn_error_clear(unlock_proto_rev_list_locked(fs, txn_id, *lockcookie,
711 pool));
712 *lockcookie = NULL;
715 return err;
718 /* Get a handle to the prototype revision file for transaction TXN_ID in
719 filesystem FS, and lock it for writing. Return FILE, a file handle
720 positioned at the end of the file, and LOCKCOOKIE, a cookie that
721 should be passed to unlock_proto_rev() to unlock the file once FILE
722 has been closed.
724 If the prototype revision file is already locked, return error
725 SVN_ERR_FS_REP_BEING_WRITTEN.
727 Perform all allocations in POOL. */
728 static svn_error_t *
729 get_writable_proto_rev(apr_file_t **file,
730 void **lockcookie,
731 svn_fs_t *fs, const char *txn_id,
732 apr_pool_t *pool)
734 struct get_writable_proto_rev_baton b;
736 b.file = file;
737 b.lockcookie = lockcookie;
738 b.txn_id = txn_id;
740 return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
743 /* Callback used in the implementation of purge_shared_txn(). */
744 static svn_error_t *
745 purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
747 const char *txn_id = *(const char **)baton;
749 free_shared_txn(fs, txn_id);
750 return SVN_NO_ERROR;
753 /* Purge the shared data for transaction TXN_ID in filesystem FS.
754 Perform all allocations in POOL. */
755 static svn_error_t *
756 purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
758 return with_txnlist_lock(fs, purge_shared_txn_body, (char **) &txn_id, pool);
763 /* Fetch the current offset of FILE into *OFFSET_P. */
764 static svn_error_t *
765 get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
767 apr_off_t offset;
769 /* Note that, for buffered files, one (possibly surprising) side-effect
770 of this call is to flush any unwritten data to disk. */
771 offset = 0;
772 SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
773 *offset_p = offset;
775 return SVN_NO_ERROR;
779 /* Check that BUF, a buffer of text from format file PATH, contains
780 only digits, raising error SVN_ERR_BAD_VERSION_FILE_FORMAT if not.
782 Uses POOL for temporary allocation. */
783 static svn_error_t *
784 check_format_file_buffer_numeric(const char *buf, const char *path,
785 apr_pool_t *pool)
787 const char *p;
789 for (p = buf; *p; p++)
790 if (!apr_isdigit(*p))
791 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
792 _("Format file '%s' contains an unexpected non-digit"),
793 svn_path_local_style(path, pool));
795 return SVN_NO_ERROR;
798 /* Read the format number and maximum number of files per directory
799 from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
800 respectively.
802 *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
803 will be set to zero if a linear scheme should be used.
805 Use POOL for temporary allocation. */
806 static svn_error_t *
807 read_format(int *pformat, int *max_files_per_dir,
808 const char *path, apr_pool_t *pool)
810 svn_error_t *err;
811 apr_file_t *file;
812 char buf[80];
813 apr_size_t len;
815 err = svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
816 APR_OS_DEFAULT, pool);
817 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
819 /* Treat an absent format file as format 1. Do not try to
820 create the format file on the fly, because the repository
821 might be read-only for us, or this might be a read-only
822 operation, and the spirit of FSFS is to make no changes
823 whatseover in read-only operations. See thread starting at
824 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
825 for more. */
826 svn_error_clear(err);
827 *pformat = 1;
828 *max_files_per_dir = 0;
830 return SVN_NO_ERROR;
833 len = sizeof(buf);
834 err = svn_io_read_length_line(file, buf, &len, pool);
835 if (err && APR_STATUS_IS_EOF(err->apr_err))
837 /* Return a more useful error message. */
838 svn_error_clear(err);
839 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
840 _("Can't read first line of format file '%s'"),
841 svn_path_local_style(path, pool));
843 SVN_ERR(err);
845 /* Check that the first line contains only digits. */
846 SVN_ERR(check_format_file_buffer_numeric(buf, path, pool));
847 *pformat = atoi(buf);
849 /* Set the default values for anything that can be set via an option. */
850 *max_files_per_dir = 0;
852 /* Read any options. */
853 while (1)
855 len = sizeof(buf);
856 err = svn_io_read_length_line(file, buf, &len, pool);
857 if (err && APR_STATUS_IS_EOF(err->apr_err))
859 /* No more options; that's okay. */
860 svn_error_clear(err);
861 break;
863 SVN_ERR(err);
865 if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
866 strncmp(buf, "layout ", 7) == 0)
868 if (strcmp(buf+7, "linear") == 0)
870 *max_files_per_dir = 0;
871 continue;
874 if (strncmp(buf+7, "sharded ", 8) == 0)
876 /* Check that the argument is numeric. */
877 SVN_ERR(check_format_file_buffer_numeric(buf+15, path, pool));
878 *max_files_per_dir = atoi(buf+15);
879 continue;
883 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
884 _("'%s' contains invalid filesystem format option '%s'"),
885 svn_path_local_style(path, pool), buf);
888 SVN_ERR(svn_io_file_close(file, pool));
890 return SVN_NO_ERROR;
893 /* Write the format number and maximum number of files per directory
894 to a new format file in PATH, possibly expecting to overwrite a
895 previously existing file.
897 Use POOL for temporary allocation. */
898 static svn_error_t *
899 write_format(const char *path, int format, int max_files_per_dir,
900 svn_boolean_t overwrite, apr_pool_t *pool)
902 const char *contents;
904 assert (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
905 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
907 if (max_files_per_dir)
908 contents = apr_psprintf(pool,
909 "%d\n"
910 "layout sharded %d\n",
911 format, max_files_per_dir);
912 else
913 contents = apr_psprintf(pool,
914 "%d\n"
915 "layout linear",
916 format);
918 else
920 contents = apr_psprintf(pool, "%d\n", format);
923 /* svn_io_write_version_file() does a load of magic to allow it to
924 replace version files that already exist. We only need to do
925 that when we're allowed to overwrite an existing file. */
926 if (! overwrite)
928 /* Create the file */
929 SVN_ERR(svn_io_file_create(path, contents, pool));
931 else
933 apr_file_t *format_file;
934 const char *path_tmp;
936 /* Create a temporary file to write the data to */
937 SVN_ERR(svn_io_open_unique_file2(&format_file, &path_tmp, path, ".tmp",
938 svn_io_file_del_none, pool));
940 /* ...dump out our version number string... */
941 SVN_ERR(svn_io_file_write_full(format_file, contents,
942 strlen(contents), NULL, pool));
944 /* ...and close the file. */
945 SVN_ERR(svn_io_file_close(format_file, pool));
947 #ifdef WIN32
948 /* make the destination writable, but only on Windows, because
949 Windows does not let us replace read-only files. */
950 SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
951 #endif /* WIN32 */
953 /* rename the temp file as the real destination */
954 SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
957 /* And set the perms to make it read only */
958 SVN_ERR(svn_io_set_file_read_only(path, FALSE, pool));
960 return SVN_NO_ERROR;
963 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
964 number is not the same as a format number supported by this
965 Subversion. */
966 static svn_error_t *
967 check_format(int format)
969 /* We support all formats from 1-current simultaneously */
970 if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
971 return SVN_NO_ERROR;
973 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
974 _("Expected FS format between '1' and '%d'; found format '%d'"),
975 SVN_FS_FS__FORMAT_NUMBER, format);
978 svn_boolean_t
979 svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
981 fs_fs_data_t *ffd = fs->fsap_data;
982 return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
985 static svn_error_t *
986 get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
988 svn_error_t *
989 svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
991 fs_fs_data_t *ffd = fs->fsap_data;
992 apr_file_t *uuid_file;
993 int format, max_files_per_dir;
994 char buf[APR_UUID_FORMATTED_LENGTH + 2];
995 apr_size_t limit;
997 fs->path = apr_pstrdup(fs->pool, path);
999 /* Read the FS format number. */
1000 SVN_ERR(read_format(&format, &max_files_per_dir,
1001 path_format(fs, pool), pool));
1003 /* Now we've got a format number no matter what. */
1004 ffd->format = format;
1005 ffd->max_files_per_dir = max_files_per_dir;
1006 SVN_ERR(check_format(format));
1008 /* Read in and cache the repository uuid. */
1009 SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
1010 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1012 limit = sizeof(buf);
1013 SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
1014 ffd->uuid = apr_pstrdup(fs->pool, buf);
1016 SVN_ERR(svn_io_file_close(uuid_file, pool));
1018 SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), path, pool));
1020 return SVN_NO_ERROR;
1024 static svn_error_t *
1025 upgrade_body(void *baton, apr_pool_t *pool)
1027 svn_fs_t *fs = baton;
1028 int format, max_files_per_dir;
1029 const char *format_path = path_format(fs, pool);
1031 /* Read the FS format number and max-files-per-dir setting. */
1032 SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
1034 /* If we're already up-to-date, there's nothing to be done here. */
1035 if (format == SVN_FS_FS__FORMAT_NUMBER)
1036 return SVN_NO_ERROR;
1038 /* If our filesystem predates the existance of the 'txn-current
1039 file', make that file and its corresponding lock file. */
1040 if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1042 SVN_ERR(svn_io_file_create(path_txn_current(fs, pool), "0\n", pool));
1043 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool), "", pool));
1046 /* If our filesystem predates the existance of the 'txn-protorevs'
1047 dir, make that directory. */
1048 if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1050 /* We don't use path_txn_proto_rev() here because it expects
1051 we've already bumped our format. */
1052 SVN_ERR(svn_io_make_dir_recursively
1053 (svn_path_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
1056 /* Bump the format file. We pass 0 for the max_files_per_dir here
1057 so we don't have to fuss with sharding directories ourselves. */
1058 SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER, 0,
1059 TRUE, pool));
1061 return SVN_NO_ERROR;
1065 svn_error_t *
1066 svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
1068 return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
1072 /* SVN_ERR-like macros for dealing with ESTALE
1074 * In NFS v3 and under, the server doesn't track opened files. If you
1075 * unlink(2) or rename(2) a file held open by another process *on the
1076 * same host*, that host's kernel typically renames the file to
1077 * .nfsXXXX and automatically deletes that when it's no longer open,
1078 * but this behavior is not required.
1080 * For obvious reasons, this does not work *across hosts*. No one
1081 * knows about the opened file; not the server, and not the deleting
1082 * client. So the file vanishes, and the reader gets stale NFS file
1083 * handle. We have this problem with revprops files, current, and
1084 * txn-current.
1086 * Wrap opens and reads of such files with SVN_RETRY_ESTALE and closes
1087 * with SVN_IGNORE_ESTALE. Call these macros within a loop of
1088 * SVN_ESTALE_RETRY_COUNT iterations (though, realistically, the
1089 * second try will succeed). Make sure you put a break statement
1090 * after the close, at the end of your loop. Immediately after your
1091 * loop, return err if err.
1093 * You must initialize err to SVN_NO_ERROR, as these macros do not.
1096 #define SVN_ESTALE_RETRY_COUNT 10
1098 #ifdef ESTALE
1099 #define SVN_RETRY_ESTALE(err, expr) \
1101 /* Clear err here (svn_error_clear can safely be passed
1102 * SVN_NO_ERROR) rather than after finding ESTALE so we can return
1103 * the ESTALE error on the last iteration of the loop. */ \
1104 svn_error_clear(err); \
1105 err = (expr); \
1106 if (err) \
1108 if (APR_TO_OS_ERROR(err->apr_err) == ESTALE) \
1109 continue; \
1110 return err; \
1113 #define SVN_IGNORE_ESTALE(err, expr) \
1115 svn_error_clear(err); \
1116 err = (expr); \
1117 if (err) \
1119 if (APR_TO_OS_ERROR(err->apr_err) != ESTALE) \
1120 return err; \
1123 #else
1124 #define SVN_RETRY_ESTALE(err, expr) SVN_ERR(expr)
1125 #define SVN_IGNORE_ESTALE(err, expr) SVN_ERR(expr)
1126 #endif
1128 /* Long enough to hold: "<svn_revnum_t> <node id> <copy id>\0"
1129 * 19 bytes for svn_revnum_t (room for 32 or 64 bit values)
1130 * + 2 spaces
1131 * + 26 bytes for each id (these are actually unbounded, so we just
1132 * have to pick something; 2^64 is 13 bytes in base-36)
1133 * + 1 terminating null
1135 #define CURRENT_BUF_LEN 48
1137 /* Read the 'current' file FNAME and store the contents in *BUF.
1138 Allocations are performed in POOL. */
1139 static svn_error_t *
1140 read_current(const char *fname, char **buf, apr_pool_t *pool)
1142 apr_file_t *revision_file;
1143 apr_size_t len;
1144 int i;
1145 svn_error_t *err = SVN_NO_ERROR;
1146 apr_pool_t *iterpool;
1148 *buf = apr_palloc(pool, CURRENT_BUF_LEN);
1149 iterpool = svn_pool_create(pool);
1150 for (i = 0; i < SVN_ESTALE_RETRY_COUNT; i++)
1152 svn_pool_clear(iterpool);
1154 SVN_RETRY_ESTALE(err, svn_io_file_open(&revision_file, fname,
1155 APR_READ | APR_BUFFERED,
1156 APR_OS_DEFAULT, iterpool));
1158 len = CURRENT_BUF_LEN;
1159 SVN_RETRY_ESTALE(err, svn_io_read_length_line(revision_file,
1160 *buf, &len, iterpool));
1161 SVN_IGNORE_ESTALE(err, svn_io_file_close(revision_file, iterpool));
1163 break;
1165 svn_pool_destroy(iterpool);
1167 return err;
1170 /* Find the youngest revision in a repository at path FS_PATH and
1171 return it in *YOUNGEST_P. Perform temporary allocations in
1172 POOL. */
1173 static svn_error_t *
1174 get_youngest(svn_revnum_t *youngest_p,
1175 const char *fs_path,
1176 apr_pool_t *pool)
1178 char *buf;
1180 SVN_ERR(read_current(svn_path_join(fs_path, PATH_CURRENT, pool),
1181 &buf, pool));
1183 *youngest_p = SVN_STR_TO_REV(buf);
1185 return SVN_NO_ERROR;
1188 svn_error_t *
1189 svn_fs_fs__hotcopy(const char *src_path,
1190 const char *dst_path,
1191 apr_pool_t *pool)
1193 const char *src_subdir, *dst_subdir;
1194 svn_revnum_t youngest, rev;
1195 apr_pool_t *iterpool;
1196 svn_node_kind_t kind;
1197 int format, max_files_per_dir;
1199 /* Check format to be sure we know how to hotcopy this FS. */
1200 SVN_ERR(read_format(&format, &max_files_per_dir,
1201 svn_path_join(src_path, PATH_FORMAT, pool),
1202 pool));
1203 SVN_ERR(check_format(format));
1205 /* Copy the current file. */
1206 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_CURRENT, pool));
1208 /* Copy the uuid. */
1209 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_UUID, pool));
1211 /* Find the youngest revision from this current file. */
1212 SVN_ERR(get_youngest(&youngest, dst_path, pool));
1214 /* Copy the necessary rev files. */
1215 src_subdir = svn_path_join(src_path, PATH_REVS_DIR, pool);
1216 dst_subdir = svn_path_join(dst_path, PATH_REVS_DIR, pool);
1218 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1220 iterpool = svn_pool_create(pool);
1221 for (rev = 0; rev <= youngest; rev++)
1223 const char *src_subdir_shard = src_subdir,
1224 *dst_subdir_shard = dst_subdir;
1226 if (max_files_per_dir)
1228 const char *shard = apr_psprintf(iterpool, "%ld",
1229 rev / max_files_per_dir);
1230 src_subdir_shard = svn_path_join(src_subdir, shard, iterpool);
1231 dst_subdir_shard = svn_path_join(dst_subdir, shard, iterpool);
1233 if (rev % max_files_per_dir == 0)
1234 SVN_ERR(svn_io_dir_make(dst_subdir_shard, APR_OS_DEFAULT,
1235 iterpool));
1238 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
1239 apr_psprintf(iterpool, "%ld", rev),
1240 iterpool));
1241 svn_pool_clear(iterpool);
1244 /* Copy the necessary revprop files. */
1245 src_subdir = svn_path_join(src_path, PATH_REVPROPS_DIR, pool);
1246 dst_subdir = svn_path_join(dst_path, PATH_REVPROPS_DIR, pool);
1248 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1250 for (rev = 0; rev <= youngest; rev++)
1252 const char *src_subdir_shard = src_subdir,
1253 *dst_subdir_shard = dst_subdir;
1255 svn_pool_clear(iterpool);
1257 if (max_files_per_dir)
1259 const char *shard = apr_psprintf(iterpool, "%ld",
1260 rev / max_files_per_dir);
1261 src_subdir_shard = svn_path_join(src_subdir, shard, iterpool);
1262 dst_subdir_shard = svn_path_join(dst_subdir, shard, iterpool);
1264 if (rev % max_files_per_dir == 0)
1265 SVN_ERR(svn_io_dir_make(dst_subdir_shard, APR_OS_DEFAULT,
1266 iterpool));
1269 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
1270 apr_psprintf(iterpool, "%ld", rev),
1271 iterpool));
1274 svn_pool_destroy(iterpool);
1276 /* Make an empty transactions directory for now. Eventually some
1277 method of copying in progress transactions will need to be
1278 developed.*/
1279 dst_subdir = svn_path_join(dst_path, PATH_TXNS_DIR, pool);
1280 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1281 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1283 dst_subdir = svn_path_join(dst_path, PATH_TXN_PROTOS_DIR, pool);
1284 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1287 /* Now copy the locks tree. */
1288 src_subdir = svn_path_join(src_path, PATH_LOCKS_DIR, pool);
1289 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
1290 if (kind == svn_node_dir)
1291 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_path,
1292 PATH_LOCKS_DIR, TRUE, NULL,
1293 NULL, pool));
1295 /* Now copy the node-origins cache tree. */
1296 src_subdir = svn_path_join(src_path, PATH_NODE_ORIGINS_DIR, pool);
1297 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
1298 if (kind == svn_node_dir)
1299 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_path,
1300 PATH_NODE_ORIGINS_DIR, TRUE, NULL,
1301 NULL, pool));
1303 /* Copy the txn-current file. */
1304 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1305 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_TXN_CURRENT, pool));
1307 /* Hotcopied FS is complete. Stamp it with a format file. */
1308 SVN_ERR(write_format(svn_path_join(dst_path, PATH_FORMAT, pool),
1309 format, max_files_per_dir, FALSE, pool));
1311 return SVN_NO_ERROR;
1314 svn_error_t *
1315 svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1316 svn_fs_t *fs,
1317 apr_pool_t *pool)
1319 fs_fs_data_t *ffd = fs->fsap_data;
1321 SVN_ERR(get_youngest(youngest_p, fs->path, pool));
1322 ffd->youngest_rev_cache = *youngest_p;
1324 return SVN_NO_ERROR;
1327 /* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN
1328 * bytes plus the stuff around them. */
1329 #define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1
1331 /* Given a revision file FILE that has been pre-positioned at the
1332 beginning of a Node-Rev header block, read in that header block and
1333 store it in the apr_hash_t HEADERS. All allocations will be from
1334 POOL. */
1335 static svn_error_t * read_header_block(apr_hash_t **headers,
1336 apr_file_t *file,
1337 apr_pool_t *pool)
1339 *headers = apr_hash_make(pool);
1341 while (1)
1343 char header_str[MAX_HEADERS_STR_LEN];
1344 const char *name, *value;
1345 apr_size_t i = 0, header_len;
1346 apr_size_t limit;
1347 char *local_name, *local_value;
1349 limit = sizeof(header_str);
1350 SVN_ERR(svn_io_read_length_line(file, header_str, &limit, pool));
1352 if (strlen(header_str) == 0)
1353 break; /* end of header block */
1355 header_len = strlen(header_str);
1357 while (header_str[i] != ':')
1359 if (header_str[i] == '\0')
1360 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1361 _("Found malformed header in "
1362 "revision file"));
1363 i++;
1366 /* Create a 'name' string and point to it. */
1367 header_str[i] = '\0';
1368 name = header_str;
1370 /* Skip over the NULL byte and the space following it. */
1371 i += 2;
1373 if (i > header_len)
1374 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1375 _("Found malformed header in "
1376 "revision file"));
1378 value = header_str + i;
1380 local_name = apr_pstrdup(pool, name);
1381 local_value = apr_pstrdup(pool, value);
1383 apr_hash_set(*headers, local_name, APR_HASH_KEY_STRING, local_value);
1386 return SVN_NO_ERROR;
1389 /* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
1390 than the current youngest revision or is simply not a valid
1391 revision number, else return success.
1393 FSFS is based around the concept that commits only take effect when
1394 the number in "current" is bumped. Thus if there happens to be a rev
1395 or revprops file installed for a revision higher than the one recorded
1396 in "current" (because a commit failed between installing the rev file
1397 and bumping "current", or because an administrator rolled back the
1398 repository by resetting "current" without deleting rev files, etc), it
1399 ought to be completely ignored. This function provides the check
1400 by which callers can make that decision. */
1401 static svn_error_t *
1402 ensure_revision_exists(svn_fs_t *fs,
1403 svn_revnum_t rev,
1404 apr_pool_t *pool)
1406 fs_fs_data_t *ffd = fs->fsap_data;
1408 if (! SVN_IS_VALID_REVNUM(rev))
1409 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1410 _("Invalid revision number '%ld'"), rev);
1413 /* Did the revision exist the last time we checked the current
1414 file? */
1415 if (rev <= ffd->youngest_rev_cache)
1416 return SVN_NO_ERROR;
1418 SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
1420 /* Check again. */
1421 if (rev <= ffd->youngest_rev_cache)
1422 return SVN_NO_ERROR;
1424 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1425 _("No such revision %ld"), rev);
1428 /* Open the revision file for revision REV in filesystem FS and store
1429 the newly opened file in FILE. Seek to location OFFSET before
1430 returning. Perform temporary allocations in POOL. */
1431 static svn_error_t *
1432 open_and_seek_revision(apr_file_t **file,
1433 svn_fs_t *fs,
1434 svn_revnum_t rev,
1435 apr_off_t offset,
1436 apr_pool_t *pool)
1438 apr_file_t *rev_file;
1440 SVN_ERR(ensure_revision_exists(fs, rev, pool));
1442 SVN_ERR(svn_io_file_open(&rev_file, svn_fs_fs__path_rev(fs, rev, pool),
1443 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1445 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1447 *file = rev_file;
1449 return SVN_NO_ERROR;
1452 /* Open the representation for a node-revision in transaction TXN_ID
1453 in filesystem FS and store the newly opened file in FILE. Seek to
1454 location OFFSET before returning. Perform temporary allocations in
1455 POOL. Only appropriate for file contents, nor props or directory
1456 contents. */
1457 static svn_error_t *
1458 open_and_seek_transaction(apr_file_t **file,
1459 svn_fs_t *fs,
1460 const char *txn_id,
1461 representation_t *rep,
1462 apr_pool_t *pool)
1464 apr_file_t *rev_file;
1465 apr_off_t offset;
1467 SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
1468 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1470 offset = rep->offset;
1471 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1473 *file = rev_file;
1475 return SVN_NO_ERROR;
1478 /* Given a node-id ID, and a representation REP in filesystem FS, open
1479 the correct file and seek to the correction location. Store this
1480 file in *FILE_P. Perform any allocations in POOL. */
1481 static svn_error_t *
1482 open_and_seek_representation(apr_file_t **file_p,
1483 svn_fs_t *fs,
1484 representation_t *rep,
1485 apr_pool_t *pool)
1487 if (! rep->txn_id)
1488 return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
1489 pool);
1490 else
1491 return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
1494 /* Parse the description of a representation from STRING and store it
1495 into *REP_P. If the representation is mutable (the revision is
1496 given as -1), then use TXN_ID for the representation's txn_id
1497 field. If MUTABLE_REP_TRUNCATED is true, then this representation
1498 is for property or directory contents, and no information will be
1499 expected except the "-1" revision number for a mutable
1500 representation. Allocate *REP_P in POOL. */
1501 static svn_error_t *
1502 read_rep_offsets(representation_t **rep_p,
1503 char *string,
1504 const char *txn_id,
1505 svn_boolean_t mutable_rep_truncated,
1506 apr_pool_t *pool)
1508 representation_t *rep;
1509 char *str, *last_str;
1510 int i;
1512 rep = apr_pcalloc(pool, sizeof(*rep));
1513 *rep_p = rep;
1515 str = apr_strtok(string, " ", &last_str);
1516 if (str == NULL)
1517 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1518 _("Malformed text rep offset line in node-rev"));
1521 rep->revision = SVN_STR_TO_REV(str);
1522 if (rep->revision == SVN_INVALID_REVNUM)
1524 rep->txn_id = txn_id;
1525 if (mutable_rep_truncated)
1526 return SVN_NO_ERROR;
1529 str = apr_strtok(NULL, " ", &last_str);
1530 if (str == NULL)
1531 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1532 _("Malformed text rep offset line in node-rev"));
1534 rep->offset = apr_atoi64(str);
1536 str = apr_strtok(NULL, " ", &last_str);
1537 if (str == NULL)
1538 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1539 _("Malformed text rep offset line in node-rev"));
1541 rep->size = apr_atoi64(str);
1543 str = apr_strtok(NULL, " ", &last_str);
1544 if (str == NULL)
1545 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1546 _("Malformed text rep offset line in node-rev"));
1548 rep->expanded_size = apr_atoi64(str);
1550 /* Read in the MD5 hash. */
1551 str = apr_strtok(NULL, " ", &last_str);
1552 if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
1553 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1554 _("Malformed text rep offset line in node-rev"));
1556 /* Parse the hex MD5 hash into digest form. */
1557 for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
1559 if ((! isxdigit(str[i * 2])) || (! isxdigit(str[i * 2 + 1])))
1560 return svn_error_create
1561 (SVN_ERR_FS_CORRUPT, NULL,
1562 _("Malformed text rep offset line in node-rev"));
1564 str[i * 2] = tolower(str[i * 2]);
1565 rep->checksum[i] = (str[i * 2] -
1566 ((str[i * 2] <= '9') ? '0' : ('a' - 10))) << 4;
1568 str[i * 2 + 1] = tolower(str[i * 2 + 1]);
1569 rep->checksum[i] |= (str[i * 2 + 1] -
1570 ((str[i * 2 + 1] <= '9') ? '0' : ('a' - 10)));
1573 return SVN_NO_ERROR;
1576 svn_error_t *
1577 svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
1578 svn_fs_t *fs,
1579 const svn_fs_id_t *id,
1580 apr_pool_t *pool)
1582 apr_file_t *revision_file;
1583 apr_hash_t *headers;
1584 node_revision_t *noderev;
1585 char *value;
1586 svn_error_t *err;
1588 if (svn_fs_fs__id_txn_id(id))
1590 /* This is a transaction node-rev. */
1591 err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
1592 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1594 else
1596 /* This is a revision node-rev. */
1597 err = open_and_seek_revision(&revision_file, fs,
1598 svn_fs_fs__id_rev(id),
1599 svn_fs_fs__id_offset(id),
1600 pool);
1603 if (err)
1605 if (APR_STATUS_IS_ENOENT(err->apr_err))
1607 svn_error_clear(err);
1608 return svn_fs_fs__err_dangling_id(fs, id);
1611 return err;
1614 SVN_ERR(read_header_block(&headers, revision_file, pool) );
1616 noderev = apr_pcalloc(pool, sizeof(*noderev));
1618 /* Read the node-rev id. */
1619 value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
1620 if (value == NULL)
1621 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1622 _("Missing id field in node-rev"));
1624 SVN_ERR(svn_io_file_close(revision_file, pool));
1626 noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
1628 /* Read the type. */
1629 value = apr_hash_get(headers, HEADER_TYPE, APR_HASH_KEY_STRING);
1631 if ((value == NULL) ||
1632 (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
1633 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1634 _("Missing kind field in node-rev"));
1636 noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
1637 : svn_node_dir;
1639 /* Read the 'count' field. */
1640 value = apr_hash_get(headers, HEADER_COUNT, APR_HASH_KEY_STRING);
1641 noderev->predecessor_count = (value == NULL) ? 0 : atoi(value);
1643 /* Get the properties location. */
1644 value = apr_hash_get(headers, HEADER_PROPS, APR_HASH_KEY_STRING);
1645 if (value)
1647 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
1648 svn_fs_fs__id_txn_id(id), TRUE, pool));
1651 /* Get the data location. */
1652 value = apr_hash_get(headers, HEADER_TEXT, APR_HASH_KEY_STRING);
1653 if (value)
1655 SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
1656 svn_fs_fs__id_txn_id(id),
1657 (noderev->kind == svn_node_dir), pool));
1660 /* Get the created path. */
1661 value = apr_hash_get(headers, HEADER_CPATH, APR_HASH_KEY_STRING);
1662 if (value == NULL)
1664 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1665 _("Missing cpath in node-rev"));
1667 else
1669 noderev->created_path = apr_pstrdup(pool, value);
1672 /* Get the predecessor ID. */
1673 value = apr_hash_get(headers, HEADER_PRED, APR_HASH_KEY_STRING);
1674 if (value)
1675 noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
1676 pool);
1678 /* Get the copyroot. */
1679 value = apr_hash_get(headers, HEADER_COPYROOT, APR_HASH_KEY_STRING);
1680 if (value == NULL)
1682 noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
1683 noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
1685 else
1687 char *str, *last_str;
1689 str = apr_strtok(value, " ", &last_str);
1690 if (str == NULL)
1691 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1692 _("Malformed copyroot line in node-rev"));
1694 noderev->copyroot_rev = atoi(str);
1696 if (last_str == NULL)
1697 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1698 _("Malformed copyroot line in node-rev"));
1699 noderev->copyroot_path = apr_pstrdup(pool, last_str);
1702 /* Get the copyfrom. */
1703 value = apr_hash_get(headers, HEADER_COPYFROM, APR_HASH_KEY_STRING);
1704 if (value == NULL)
1706 noderev->copyfrom_path = NULL;
1707 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1709 else
1711 char *str, *last_str;
1713 str = apr_strtok(value, " ", &last_str);
1714 if (str == NULL)
1715 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1716 _("Malformed copyfrom line in node-rev"));
1718 noderev->copyfrom_rev = atoi(str);
1720 if (last_str == NULL)
1721 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1722 _("Malformed copyfrom line in node-rev"));
1723 noderev->copyfrom_path = apr_pstrdup(pool, last_str);
1726 /* Get whether this is a fresh txn root. */
1727 value = apr_hash_get(headers, HEADER_FRESHTXNRT, APR_HASH_KEY_STRING);
1728 noderev->is_fresh_txn_root = (value != NULL);
1730 /* Get the mergeinfo count. */
1731 value = apr_hash_get(headers, HEADER_MINFO_CNT, APR_HASH_KEY_STRING);
1732 noderev->mergeinfo_count = (value == NULL) ? 0 : apr_atoi64(value);
1734 /* Get whether *this* node has mergeinfo. */
1735 value = apr_hash_get(headers, HEADER_MINFO_HERE, APR_HASH_KEY_STRING);
1736 noderev->has_mergeinfo = (value != NULL);
1738 *noderev_p = noderev;
1740 return SVN_NO_ERROR;
1743 /* Return a formatted string that represents the location of
1744 representation REP. If MUTABLE_REP_TRUNCATED is given, the rep is
1745 for props or dir contents, and only a "-1" revision number will be
1746 given for a mutable rep. Perform the allocation from POOL. */
1747 static const char *
1748 representation_string(representation_t *rep,
1749 svn_boolean_t mutable_rep_truncated, apr_pool_t *pool)
1751 if (rep->txn_id && mutable_rep_truncated)
1752 return "-1";
1753 else
1754 return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
1755 " %" SVN_FILESIZE_T_FMT " %s",
1756 rep->revision, rep->offset, rep->size,
1757 rep->expanded_size,
1758 svn_md5_digest_to_cstring_display(rep->checksum,
1759 pool));
1762 /* Write the node-revision NODEREV into the file FILE. Only write
1763 mergeinfo-related metadata if INCLUDE_MERGEINFO is true. Temporary
1764 allocations are from POOL. */
1765 static svn_error_t *
1766 write_noderev_txn(apr_file_t *file,
1767 node_revision_t *noderev,
1768 svn_boolean_t include_mergeinfo,
1769 apr_pool_t *pool)
1771 svn_stream_t *outfile;
1773 outfile = svn_stream_from_aprfile(file, pool);
1775 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
1776 svn_fs_fs__id_unparse(noderev->id,
1777 pool)->data));
1779 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
1780 (noderev->kind == svn_node_file) ?
1781 KIND_FILE : KIND_DIR));
1783 if (noderev->predecessor_id)
1784 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
1785 svn_fs_fs__id_unparse(noderev->predecessor_id,
1786 pool)->data));
1788 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
1789 noderev->predecessor_count));
1791 if (noderev->data_rep)
1792 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
1793 representation_string(noderev->data_rep,
1794 (noderev->kind
1795 == svn_node_dir),
1796 pool)));
1798 if (noderev->prop_rep)
1799 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
1800 representation_string(noderev->prop_rep, TRUE,
1801 pool)));
1803 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
1804 noderev->created_path));
1806 if (noderev->copyfrom_path)
1807 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
1808 " %s\n",
1809 noderev->copyfrom_rev,
1810 noderev->copyfrom_path));
1812 if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
1813 (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
1814 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
1815 " %s\n",
1816 noderev->copyroot_rev,
1817 noderev->copyroot_path));
1819 if (noderev->is_fresh_txn_root)
1820 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_FRESHTXNRT ": y\n"));
1822 if (include_mergeinfo)
1824 if (noderev->mergeinfo_count > 0)
1825 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
1826 APR_INT64_T_FMT "\n",
1827 noderev->mergeinfo_count));
1829 if (noderev->has_mergeinfo)
1830 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_HERE ": y\n"));
1833 SVN_ERR(svn_stream_printf(outfile, pool, "\n"));
1835 return SVN_NO_ERROR;
1838 svn_error_t *
1839 svn_fs_fs__put_node_revision(svn_fs_t *fs,
1840 const svn_fs_id_t *id,
1841 node_revision_t *noderev,
1842 svn_boolean_t fresh_txn_root,
1843 apr_pool_t *pool)
1845 apr_file_t *noderev_file;
1846 const char *txn_id = svn_fs_fs__id_txn_id(id);
1848 noderev->is_fresh_txn_root = fresh_txn_root;
1850 if (! txn_id)
1851 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1852 _("Attempted to write to non-transaction"));
1854 SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
1855 APR_WRITE | APR_CREATE | APR_TRUNCATE
1856 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1858 SVN_ERR(write_noderev_txn(noderev_file, noderev,
1859 svn_fs_fs__fs_supports_mergeinfo(fs),
1860 pool));
1862 SVN_ERR(svn_io_file_close(noderev_file, pool));
1864 return SVN_NO_ERROR;
1868 /* This structure is used to hold the information associated with a
1869 REP line. */
1870 struct rep_args
1872 svn_boolean_t is_delta;
1873 svn_boolean_t is_delta_vs_empty;
1875 svn_revnum_t base_revision;
1876 apr_off_t base_offset;
1877 apr_size_t base_length;
1880 /* Read the next line from file FILE and parse it as a text
1881 representation entry. Return the parsed entry in *REP_ARGS_P.
1882 Perform all allocations in POOL. */
1883 static svn_error_t *
1884 read_rep_line(struct rep_args **rep_args_p,
1885 apr_file_t *file,
1886 apr_pool_t *pool)
1888 char buffer[160];
1889 apr_size_t limit;
1890 struct rep_args *rep_args;
1891 char *str, *last_str;
1893 limit = sizeof(buffer);
1894 SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
1896 rep_args = apr_pcalloc(pool, sizeof(*rep_args));
1897 rep_args->is_delta = FALSE;
1899 if (strcmp(buffer, REP_PLAIN) == 0)
1901 *rep_args_p = rep_args;
1902 return SVN_NO_ERROR;
1905 if (strcmp(buffer, REP_DELTA) == 0)
1907 /* This is a delta against the empty stream. */
1908 rep_args->is_delta = TRUE;
1909 rep_args->is_delta_vs_empty = TRUE;
1910 *rep_args_p = rep_args;
1911 return SVN_NO_ERROR;
1914 rep_args->is_delta = TRUE;
1915 rep_args->is_delta_vs_empty = FALSE;
1917 /* We have hopefully a DELTA vs. a non-empty base revision. */
1918 str = apr_strtok(buffer, " ", &last_str);
1919 if (! str || (strcmp(str, REP_DELTA) != 0)) goto err;
1921 str = apr_strtok(NULL, " ", &last_str);
1922 if (! str) goto err;
1923 rep_args->base_revision = atol(str);
1925 str = apr_strtok(NULL, " ", &last_str);
1926 if (! str) goto err;
1927 rep_args->base_offset = (apr_off_t) apr_atoi64(str);
1929 str = apr_strtok(NULL, " ", &last_str);
1930 if (! str) goto err;
1931 rep_args->base_length = (apr_size_t) apr_atoi64(str);
1933 *rep_args_p = rep_args;
1934 return SVN_NO_ERROR;
1936 err:
1937 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1938 _("Malformed representation header"));
1941 /* Given a revision file REV_FILE, find the Node-ID of the header
1942 located at OFFSET and store it in *ID_P. Allocate temporary
1943 variables from POOL. */
1944 static svn_error_t *
1945 get_fs_id_at_offset(svn_fs_id_t **id_p,
1946 apr_file_t *rev_file,
1947 apr_off_t offset,
1948 apr_pool_t *pool)
1950 svn_fs_id_t *id;
1951 apr_hash_t *headers;
1952 const char *node_id_str;
1954 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1956 SVN_ERR(read_header_block(&headers, rev_file, pool));
1958 node_id_str = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
1960 if (node_id_str == NULL)
1961 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1962 _("Missing node-id in node-rev"));
1964 id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
1966 if (id == NULL)
1967 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1968 _("Corrupt node-id in node-rev"));
1970 *id_p = id;
1972 return SVN_NO_ERROR;
1976 /* Given an open revision file REV_FILE, locate the trailer that
1977 specifies the offset to the root node-id and to the changed path
1978 information. Store the root node offset in *ROOT_OFFSET and the
1979 changed path offset in *CHANGES_OFFSET. If either of these
1980 pointers is NULL, do nothing with it. Allocate temporary variables
1981 from POOL. */
1982 static svn_error_t *
1983 get_root_changes_offset(apr_off_t *root_offset,
1984 apr_off_t *changes_offset,
1985 apr_file_t *rev_file,
1986 apr_pool_t *pool)
1988 apr_off_t offset;
1989 char buf[64];
1990 int i, num_bytes;
1991 apr_size_t len;
1993 /* We will assume that the last line containing the two offsets
1994 will never be longer than 64 characters. */
1995 offset = 0;
1996 SVN_ERR(svn_io_file_seek(rev_file, APR_END, &offset, pool));
1998 offset -= sizeof(buf);
1999 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2001 /* Read in this last block, from which we will identify the last line. */
2002 len = sizeof(buf);
2003 SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
2005 /* This cast should be safe since the maximum amount read, 64, will
2006 never be bigger than the size of an int. */
2007 num_bytes = (int) len;
2009 /* The last byte should be a newline. */
2010 if (buf[num_bytes - 1] != '\n')
2012 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2013 _("Revision file lacks trailing newline"));
2016 /* Look for the next previous newline. */
2017 for (i = num_bytes - 2; i >= 0; i--)
2019 if (buf[i] == '\n') break;
2022 if (i < 0)
2024 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2025 _("Final line in revision file longer than 64 "
2026 "characters"));
2029 i++;
2031 if (root_offset)
2032 *root_offset = apr_atoi64(&buf[i]);
2034 /* find the next space */
2035 for ( ; i < (num_bytes - 2) ; i++)
2036 if (buf[i] == ' ') break;
2038 if (i == (num_bytes - 2))
2039 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2040 _("Final line in revision file missing space"));
2042 i++;
2044 /* note that apr_atoi64() will stop reading as soon as it encounters
2045 the final newline. */
2046 if (changes_offset)
2047 *changes_offset = apr_atoi64(&buf[i]);
2049 return SVN_NO_ERROR;
2052 svn_error_t *
2053 svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
2054 svn_fs_t *fs,
2055 svn_revnum_t rev,
2056 apr_pool_t *pool)
2058 fs_fs_data_t *ffd = fs->fsap_data;
2059 apr_file_t *revision_file;
2060 apr_off_t root_offset;
2061 svn_fs_id_t *root_id;
2062 svn_error_t *err;
2063 const char *rev_str = apr_psprintf(pool, "%ld", rev);
2064 svn_fs_id_t *cached_id;
2066 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2068 /* Calculate an index into the revroot id cache */
2069 cached_id = apr_hash_get(ffd->rev_root_id_cache,
2070 rev_str,
2071 APR_HASH_KEY_STRING);
2073 if (cached_id)
2075 *root_id_p = svn_fs_fs__id_copy(cached_id, pool);
2076 return SVN_NO_ERROR;
2079 err = svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool),
2080 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
2081 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
2083 svn_error_clear(err);
2084 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
2085 _("No such revision %ld"), rev);
2087 else if (err)
2088 return err;
2091 SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, pool));
2093 SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, root_offset, pool));
2095 SVN_ERR(svn_io_file_close(revision_file, pool));
2097 /* Make sure our cache size doesn't grow without bounds. */
2098 if (apr_hash_count(ffd->rev_root_id_cache) >= NUM_RRI_CACHE_ENTRIES)
2100 /* In order to only use one pool for the whole cache, we need to
2101 * completely wipe it to expire entries! */
2102 svn_pool_clear(ffd->rev_root_id_cache_pool);
2103 ffd->rev_root_id_cache = apr_hash_make(ffd->rev_root_id_cache_pool);
2106 /* Cache the answer, copying both the key and value into the cache's
2107 pool. */
2108 apr_hash_set(ffd->rev_root_id_cache,
2109 apr_pstrdup(ffd->rev_root_id_cache_pool, rev_str),
2110 APR_HASH_KEY_STRING,
2111 svn_fs_fs__id_copy(root_id, ffd->rev_root_id_cache_pool));
2113 *root_id_p = root_id;
2115 return SVN_NO_ERROR;
2118 svn_error_t *
2119 svn_fs_fs__set_revision_proplist(svn_fs_t *fs,
2120 svn_revnum_t rev,
2121 apr_hash_t *proplist,
2122 apr_pool_t *pool)
2124 const char *final_path = path_revprops(fs, rev, pool);
2125 const char *tmp_path;
2126 apr_file_t *f;
2128 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2130 SVN_ERR(svn_io_open_unique_file2
2131 (&f, &tmp_path, final_path, ".tmp", svn_io_file_del_none, pool));
2132 SVN_ERR(svn_hash_write(proplist, f, pool));
2133 SVN_ERR(svn_io_file_close(f, pool));
2134 /* We use the rev file of this revision as the perms reference,
2135 because when setting revprops for the first time, the revprop
2136 file won't exist and therefore can't serve as its own reference.
2137 (Whereas the rev file should already exist at this point.) */
2138 SVN_ERR(svn_fs_fs__move_into_place(tmp_path, final_path,
2139 svn_fs_fs__path_rev(fs, rev, pool),
2140 pool));
2142 return SVN_NO_ERROR;
2145 svn_error_t *
2146 svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
2147 svn_fs_t *fs,
2148 svn_revnum_t rev,
2149 apr_pool_t *pool)
2151 apr_file_t *revprop_file;
2152 apr_hash_t *proplist;
2153 svn_error_t *err = SVN_NO_ERROR;
2154 int i;
2155 apr_pool_t *iterpool;
2157 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2159 proplist = apr_hash_make(pool);
2160 iterpool = svn_pool_create(pool);
2161 for (i = 0; i < SVN_ESTALE_RETRY_COUNT; i++)
2163 svn_pool_clear(iterpool);
2165 /* Clear err here (svn_error_clear can safely be passed
2166 * SVN_NO_ERROR) rather than after finding ESTALE so we can
2167 * return the ESTALE error on the last iteration of the loop. */
2168 svn_error_clear(err);
2169 err = svn_io_file_open(&revprop_file, path_revprops(fs, rev, iterpool),
2170 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
2171 iterpool);
2172 if (err)
2174 if (APR_STATUS_IS_ENOENT(err->apr_err))
2176 svn_error_clear(err);
2177 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
2178 _("No such revision %ld"), rev);
2180 #ifdef ESTALE
2181 else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE)
2182 continue;
2183 #endif
2184 return err;
2187 SVN_ERR(svn_hash__clear(proplist));
2188 SVN_RETRY_ESTALE(err,
2189 svn_hash_read2(proplist,
2190 svn_stream_from_aprfile(revprop_file,
2191 iterpool),
2192 SVN_HASH_TERMINATOR, pool));
2194 SVN_IGNORE_ESTALE(err, svn_io_file_close(revprop_file, iterpool));
2196 break;
2198 if (err)
2199 return err;
2200 svn_pool_destroy(iterpool);
2202 *proplist_p = proplist;
2204 return SVN_NO_ERROR;
2207 /* Represents where in the current svndiff data block each
2208 representation is. */
2209 struct rep_state
2211 apr_file_t *file;
2212 apr_off_t start; /* The starting offset for the raw
2213 svndiff/plaintext data minus header. */
2214 apr_off_t off; /* The current offset into the file. */
2215 apr_off_t end; /* The end offset of the raw data. */
2216 int ver; /* If a delta, what svndiff version? */
2217 int chunk_index;
2220 /* Read the rep args for REP in filesystem FS and create a rep_state
2221 for reading the representation. Return the rep_state in *REP_STATE
2222 and the rep args in *REP_ARGS, both allocated in POOL. */
2223 static svn_error_t *
2224 create_rep_state(struct rep_state **rep_state,
2225 struct rep_args **rep_args,
2226 representation_t *rep,
2227 svn_fs_t *fs,
2228 apr_pool_t *pool)
2230 struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
2231 struct rep_args *ra;
2232 unsigned char buf[4];
2234 SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
2235 SVN_ERR(read_rep_line(&ra, rs->file, pool));
2236 SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
2237 rs->off = rs->start;
2238 rs->end = rs->start + rep->size;
2239 *rep_state = rs;
2240 *rep_args = ra;
2242 if (ra->is_delta == FALSE)
2243 /* This is a plaintext, so just return the current rep_state. */
2244 return SVN_NO_ERROR;
2246 /* We are dealing with a delta, find out what version. */
2247 SVN_ERR(svn_io_file_read_full(rs->file, buf, sizeof(buf), NULL, pool));
2248 if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
2249 return svn_error_create
2250 (SVN_ERR_FS_CORRUPT, NULL,
2251 _("Malformed svndiff data in representation"));
2252 rs->ver = buf[3];
2253 rs->chunk_index = 0;
2254 rs->off += 4;
2256 return SVN_NO_ERROR;
2259 /* Build an array of rep_state structures in *LIST giving the delta
2260 reps from first_rep to a plain-text or self-compressed rep. Set
2261 *SRC_STATE to the plain-text rep we find at the end of the chain,
2262 or to NULL if the final delta representation is self-compressed.
2263 The representation to start from is designated by filesystem FS, id
2264 ID, and representation REP. */
2265 static svn_error_t *
2266 build_rep_list(apr_array_header_t **list,
2267 struct rep_state **src_state,
2268 svn_fs_t *fs,
2269 representation_t *first_rep,
2270 apr_pool_t *pool)
2272 representation_t rep;
2273 struct rep_state *rs;
2274 struct rep_args *rep_args;
2276 *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
2277 rep = *first_rep;
2279 while (1)
2281 SVN_ERR(create_rep_state(&rs, &rep_args, &rep, fs, pool));
2282 if (rep_args->is_delta == FALSE)
2284 /* This is a plaintext, so just return the current rep_state. */
2285 *src_state = rs;
2286 return SVN_NO_ERROR;
2289 /* Push this rep onto the list. If it's self-compressed, we're done. */
2290 APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
2291 if (rep_args->is_delta_vs_empty)
2293 *src_state = NULL;
2294 return SVN_NO_ERROR;
2297 rep.revision = rep_args->base_revision;
2298 rep.offset = rep_args->base_offset;
2299 rep.size = rep_args->base_length;
2300 rep.txn_id = NULL;
2305 struct rep_read_baton
2307 /* The FS from which we're reading. */
2308 svn_fs_t *fs;
2310 /* The state of all prior delta representations. */
2311 apr_array_header_t *rs_list;
2313 /* The plaintext state, if there is a plaintext. */
2314 struct rep_state *src_state;
2316 /* The index of the current delta chunk, if we are reading a delta. */
2317 int chunk_index;
2319 /* The buffer where we store undeltified data. */
2320 char *buf;
2321 apr_size_t buf_pos;
2322 apr_size_t buf_len;
2324 /* An MD5 context for summing the data read in order to verify it. */
2325 struct apr_md5_ctx_t md5_context;
2326 svn_boolean_t checksum_finalized;
2328 /* The stored checksum of the representation we are reading, its
2329 length, and the amount we've read so far. Some of this
2330 information is redundant with rs_list and src_state, but it's
2331 convenient for the checksumming code to have it here. */
2332 unsigned char checksum[APR_MD5_DIGESTSIZE];
2333 svn_filesize_t len;
2334 svn_filesize_t off;
2336 /* Used for temporary allocations during the read. */
2337 apr_pool_t *pool;
2339 /* Pool used to store file handles and other data that is persistant
2340 for the entire stream read. */
2341 apr_pool_t *filehandle_pool;
2344 /* Create a rep_read_baton structure for node revision NODEREV in
2345 filesystem FS and store it in *RB_P. Perform all allocations in
2346 POOL. If rep is mutable, it must be for file contents. */
2347 static svn_error_t *
2348 rep_read_get_baton(struct rep_read_baton **rb_p,
2349 svn_fs_t *fs,
2350 representation_t *rep,
2351 apr_pool_t *pool)
2353 struct rep_read_baton *b;
2355 b = apr_pcalloc(pool, sizeof(*b));
2356 b->fs = fs;
2357 b->chunk_index = 0;
2358 b->buf = NULL;
2359 apr_md5_init(&(b->md5_context));
2360 b->checksum_finalized = FALSE;
2361 memcpy(b->checksum, rep->checksum, sizeof(b->checksum));
2362 b->len = rep->expanded_size;
2363 b->off = 0;
2364 b->pool = svn_pool_create(pool);
2365 b->filehandle_pool = svn_pool_create(pool);
2367 SVN_ERR(build_rep_list(&b->rs_list, &b->src_state, fs, rep,
2368 b->filehandle_pool));
2370 /* Save our output baton. */
2371 *rb_p = b;
2373 return SVN_NO_ERROR;
2376 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
2377 window into *NWIN. */
2378 static svn_error_t *
2379 read_window(svn_txdelta_window_t **nwin, int this_chunk, struct rep_state *rs,
2380 apr_pool_t *pool)
2382 svn_stream_t *stream;
2384 assert(rs->chunk_index <= this_chunk);
2386 /* Skip windows to reach the current chunk if we aren't there yet. */
2387 while (rs->chunk_index < this_chunk)
2389 SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
2390 rs->chunk_index++;
2391 SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
2392 if (rs->off >= rs->end)
2393 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2394 _("Reading one svndiff window read "
2395 "beyond the end of the "
2396 "representation"));
2399 /* Read the next window. */
2400 stream = svn_stream_from_aprfile(rs->file, pool);
2401 SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
2402 rs->chunk_index++;
2403 SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
2405 if (rs->off > rs->end)
2406 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2407 _("Reading one svndiff window read beyond "
2408 "the end of the representation"));
2410 return SVN_NO_ERROR;
2413 /* Get one delta window that is a result of combining all but the last deltas
2414 from the current desired representation identified in *RB, to its
2415 final base representation. Store the window in *RESULT. */
2416 static svn_error_t *
2417 get_combined_window(svn_txdelta_window_t **result,
2418 struct rep_read_baton *rb)
2420 apr_pool_t *pool, *new_pool;
2421 int i;
2422 svn_txdelta_window_t *window, *nwin;
2423 struct rep_state *rs;
2425 assert(rb->rs_list->nelts >= 2);
2427 pool = svn_pool_create(rb->pool);
2429 /* Read the next window from the original rep. */
2430 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
2431 SVN_ERR(read_window(&window, rb->chunk_index, rs, pool));
2433 /* Combine in the windows from the other delta reps, if needed. */
2434 for (i = 1; i < rb->rs_list->nelts - 1; i++)
2436 if (window->src_ops == 0)
2437 break;
2439 rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
2441 SVN_ERR(read_window(&nwin, rb->chunk_index, rs, pool));
2443 /* Combine this window with the current one. Cycles pools so that we
2444 only need to hold three windows at a time. */
2445 new_pool = svn_pool_create(rb->pool);
2446 window = svn_txdelta_compose_windows(nwin, window, new_pool);
2447 svn_pool_destroy(pool);
2448 pool = new_pool;
2451 *result = window;
2452 return SVN_NO_ERROR;
2455 static svn_error_t *
2456 rep_read_contents_close(void *baton)
2458 struct rep_read_baton *rb = baton;
2460 svn_pool_destroy(rb->pool);
2461 svn_pool_destroy(rb->filehandle_pool);
2463 return SVN_NO_ERROR;
2466 /* Return the next *LEN bytes of the rep and store them in *BUF. */
2467 static svn_error_t *
2468 get_contents(struct rep_read_baton *rb,
2469 char *buf,
2470 apr_size_t *len)
2472 apr_size_t copy_len, remaining = *len, tlen;
2473 char *sbuf, *tbuf, *cur = buf;
2474 struct rep_state *rs;
2475 svn_txdelta_window_t *cwindow, *lwindow;
2477 /* Special case for when there are no delta reps, only a plain
2478 text. */
2479 if (rb->rs_list->nelts == 0)
2481 copy_len = remaining;
2482 rs = rb->src_state;
2483 if (((apr_off_t) copy_len) > rs->end - rs->off)
2484 copy_len = (apr_size_t) (rs->end - rs->off);
2485 SVN_ERR(svn_io_file_read_full(rs->file, cur, copy_len, NULL,
2486 rb->pool));
2487 rs->off += copy_len;
2488 *len = copy_len;
2489 return SVN_NO_ERROR;
2492 while (remaining > 0)
2494 /* If we have buffered data from a previous chunk, use that. */
2495 if (rb->buf)
2497 /* Determine how much to copy from the buffer. */
2498 copy_len = rb->buf_len - rb->buf_pos;
2499 if (copy_len > remaining)
2500 copy_len = remaining;
2502 /* Actually copy the data. */
2503 memcpy(cur, rb->buf + rb->buf_pos, copy_len);
2504 rb->buf_pos += copy_len;
2505 cur += copy_len;
2506 remaining -= copy_len;
2508 /* If the buffer is all used up, clear it and empty the
2509 local pool. */
2510 if (rb->buf_pos == rb->buf_len)
2512 svn_pool_clear(rb->pool);
2513 rb->buf = NULL;
2516 else
2519 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
2520 if (rs->off == rs->end)
2521 break;
2523 /* Get more buffered data by evaluating a chunk. */
2524 if (rb->rs_list->nelts > 1)
2525 SVN_ERR(get_combined_window(&cwindow, rb));
2526 else
2527 cwindow = NULL;
2528 if (!cwindow || cwindow->src_ops > 0)
2530 rs = APR_ARRAY_IDX(rb->rs_list, rb->rs_list->nelts - 1,
2531 struct rep_state *);
2532 /* Read window from last representation in list. */
2533 /* We apply this window directly instead of combining it with the
2534 others. We do this because vdelta is used for deltas against
2535 the empty stream, which will trigger quadratic behaviour in
2536 the delta combiner. */
2537 SVN_ERR(read_window(&lwindow, rb->chunk_index, rs, rb->pool));
2539 if (lwindow->src_ops > 0)
2541 if (! rb->src_state)
2542 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2543 _("svndiff data requested "
2544 "non-existent source"));
2545 rs = rb->src_state;
2546 sbuf = apr_palloc(rb->pool, lwindow->sview_len);
2547 if (! ((rs->start + lwindow->sview_offset) < rs->end))
2548 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2549 _("svndiff requested position "
2550 "beyond end of stream"));
2551 if ((rs->start + lwindow->sview_offset) != rs->off)
2553 rs->off = rs->start + lwindow->sview_offset;
2554 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off,
2555 rb->pool));
2557 SVN_ERR(svn_io_file_read_full(rs->file, sbuf,
2558 lwindow->sview_len,
2559 NULL, rb->pool));
2560 rs->off += lwindow->sview_len;
2562 else
2563 sbuf = NULL;
2565 /* Apply lwindow to source. */
2566 tlen = lwindow->tview_len;
2567 tbuf = apr_palloc(rb->pool, tlen);
2568 svn_txdelta_apply_instructions(lwindow, sbuf, tbuf,
2569 &tlen);
2570 if (tlen != lwindow->tview_len)
2571 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2572 _("svndiff window length is "
2573 "corrupt"));
2574 sbuf = tbuf;
2576 else
2577 sbuf = NULL;
2579 rb->chunk_index++;
2581 if (cwindow)
2583 rb->buf_len = cwindow->tview_len;
2584 rb->buf = apr_palloc(rb->pool, rb->buf_len);
2585 svn_txdelta_apply_instructions(cwindow, sbuf, rb->buf,
2586 &rb->buf_len);
2587 if (rb->buf_len != cwindow->tview_len)
2588 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2589 _("svndiff window length is "
2590 "corrupt"));
2592 else
2594 rb->buf_len = lwindow->tview_len;
2595 rb->buf = sbuf;
2598 rb->buf_pos = 0;
2602 *len = cur - buf;
2604 return SVN_NO_ERROR;
2607 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
2608 representation and store them in *BUF. Sum as we read and verify
2609 the MD5 sum at the end. */
2610 static svn_error_t *
2611 rep_read_contents(void *baton,
2612 char *buf,
2613 apr_size_t *len)
2615 struct rep_read_baton *rb = baton;
2617 /* Get the next block of data. */
2618 SVN_ERR(get_contents(rb, buf, len));
2620 /* Perform checksumming. We want to check the checksum as soon as
2621 the last byte of data is read, in case the caller never performs
2622 a short read, but we don't want to finalize the MD5 context
2623 twice. */
2624 if (!rb->checksum_finalized)
2626 apr_md5_update(&rb->md5_context, buf, *len);
2627 rb->off += *len;
2628 if (rb->off == rb->len)
2630 unsigned char checksum[APR_MD5_DIGESTSIZE];
2632 rb->checksum_finalized = TRUE;
2633 apr_md5_final(checksum, &rb->md5_context);
2634 if (! svn_md5_digests_match(checksum, rb->checksum))
2635 return svn_error_createf
2636 (SVN_ERR_FS_CORRUPT, NULL,
2637 _("Checksum mismatch while reading representation:\n"
2638 " expected: %s\n"
2639 " actual: %s\n"),
2640 svn_md5_digest_to_cstring_display(rb->checksum, rb->pool),
2641 svn_md5_digest_to_cstring_display(checksum, rb->pool));
2644 return SVN_NO_ERROR;
2647 /* Return a stream in *CONTENTS_P that will read the contents of a
2648 representation stored at the location given by REP. Appropriate
2649 for any kind of immutable representation, but only for file
2650 contents (not props or directory contents) in mutable
2651 representations.
2653 If REP is NULL, the representation is assumed to be empty, and the
2654 empty stream is returned.
2656 static svn_error_t *
2657 read_representation(svn_stream_t **contents_p,
2658 svn_fs_t *fs,
2659 representation_t *rep,
2660 apr_pool_t *pool)
2662 struct rep_read_baton *rb;
2664 if (! rep)
2666 *contents_p = svn_stream_empty(pool);
2668 else
2670 SVN_ERR(rep_read_get_baton(&rb, fs, rep, pool));
2671 *contents_p = svn_stream_create(rb, pool);
2672 svn_stream_set_read(*contents_p, rep_read_contents);
2673 svn_stream_set_close(*contents_p, rep_read_contents_close);
2676 return SVN_NO_ERROR;
2679 svn_error_t *
2680 svn_fs_fs__get_contents(svn_stream_t **contents_p,
2681 svn_fs_t *fs,
2682 node_revision_t *noderev,
2683 apr_pool_t *pool)
2685 return read_representation(contents_p, fs, noderev->data_rep, pool);
2688 /* Baton used when reading delta windows. */
2689 struct delta_read_baton
2691 struct rep_state *rs;
2692 unsigned char checksum[APR_MD5_DIGESTSIZE];
2695 /* This implements the svn_txdelta_next_window_fn_t interface. */
2696 static svn_error_t *
2697 delta_read_next_window(svn_txdelta_window_t **window, void *baton,
2698 apr_pool_t *pool)
2700 struct delta_read_baton *drb = baton;
2702 if (drb->rs->off == drb->rs->end)
2704 *window = NULL;
2705 return SVN_NO_ERROR;
2708 SVN_ERR(read_window(window, drb->rs->chunk_index, drb->rs, pool));
2710 return SVN_NO_ERROR;
2713 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
2714 static const unsigned char *
2715 delta_read_md5_digest(void *baton)
2717 struct delta_read_baton *drb = baton;
2719 return drb->checksum;
2722 svn_error_t *
2723 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
2724 svn_fs_t *fs,
2725 node_revision_t *source,
2726 node_revision_t *target,
2727 apr_pool_t *pool)
2729 svn_stream_t *source_stream, *target_stream;
2731 /* Try a shortcut: if the target is stored as a delta against the source,
2732 then just use that delta. */
2733 if (source && source->data_rep && target->data_rep)
2735 struct rep_state *rep_state;
2736 struct rep_args *rep_args;
2738 /* Read target's base rep if any. */
2739 SVN_ERR(create_rep_state(&rep_state, &rep_args, target->data_rep,
2740 fs, pool));
2741 /* If that matches source, then use this delta as is. */
2742 if (rep_args->is_delta
2743 && (rep_args->is_delta_vs_empty
2744 || (rep_args->base_revision == source->data_rep->revision
2745 && rep_args->base_offset == source->data_rep->offset)))
2747 /* Create the delta read baton. */
2748 struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
2749 drb->rs = rep_state;
2750 memcpy(drb->checksum, target->data_rep->checksum,
2751 sizeof(drb->checksum));
2752 *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
2753 delta_read_md5_digest, pool);
2754 return SVN_NO_ERROR;
2756 else
2757 SVN_ERR(svn_io_file_close(rep_state->file, pool));
2760 /* Read both fulltexts and construct a delta. */
2761 if (source)
2762 SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
2763 else
2764 source_stream = svn_stream_empty(pool);
2765 SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
2766 svn_txdelta(stream_p, source_stream, target_stream, pool);
2768 return SVN_NO_ERROR;
2772 /* Fetch the contents of a directory into ENTRIES. Values are stored
2773 as filename to string mappings; further conversion is necessary to
2774 convert them into svn_fs_dirent_t values. */
2775 static svn_error_t *
2776 get_dir_contents(apr_hash_t *entries,
2777 svn_fs_t *fs,
2778 node_revision_t *noderev,
2779 apr_pool_t *pool)
2781 svn_stream_t *contents;
2783 if (noderev->data_rep && noderev->data_rep->txn_id)
2785 apr_file_t *dir_file;
2786 const char *filename = path_txn_node_children(fs, noderev->id, pool);
2788 /* The representation is mutable. Read the old directory
2789 contents from the mutable children file, followed by the
2790 changes we've made in this transaction. */
2791 SVN_ERR(svn_io_file_open(&dir_file, filename, APR_READ | APR_BUFFERED,
2792 APR_OS_DEFAULT, pool));
2793 contents = svn_stream_from_aprfile(dir_file, pool);
2794 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
2795 SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
2796 SVN_ERR(svn_io_file_close(dir_file, pool));
2798 else if (noderev->data_rep)
2800 /* The representation is immutable. Read it normally. */
2801 SVN_ERR(read_representation(&contents, fs, noderev->data_rep, pool));
2802 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
2803 SVN_ERR(svn_stream_close(contents));
2806 return SVN_NO_ERROR;
2809 /* Return a copy of the directory hash ENTRIES in POOL. */
2810 static apr_hash_t *
2811 copy_dir_entries(apr_hash_t *entries,
2812 apr_pool_t *pool)
2814 apr_hash_t *new_entries = apr_hash_make(pool);
2815 apr_hash_index_t *hi;
2817 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2819 void *val;
2820 svn_fs_dirent_t *dirent, *new_dirent;
2822 apr_hash_this(hi, NULL, NULL, &val);
2823 dirent = val;
2824 new_dirent = apr_palloc(pool, sizeof(*new_dirent));
2825 new_dirent->name = apr_pstrdup(pool, dirent->name);
2826 new_dirent->kind = dirent->kind;
2827 new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool);
2828 apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING,
2829 new_dirent);
2831 return new_entries;
2835 svn_error_t *
2836 svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
2837 svn_fs_t *fs,
2838 node_revision_t *noderev,
2839 apr_pool_t *pool)
2841 fs_fs_data_t *ffd = fs->fsap_data;
2842 apr_hash_t *unparsed_entries, *parsed_entries;
2843 apr_hash_index_t *hi;
2844 unsigned int hid;
2846 /* Calculate an index into the dir entries cache. This should be
2847 completely ignored if this is a mutable noderev. */
2848 hid = DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev->id));
2850 /* If we have this directory cached, return it. */
2851 if (! svn_fs_fs__id_txn_id(noderev->id) &&
2852 ffd->dir_cache_id[hid] && svn_fs_fs__id_eq(ffd->dir_cache_id[hid],
2853 noderev->id))
2855 *entries_p = copy_dir_entries(ffd->dir_cache[hid], pool);
2856 return SVN_NO_ERROR;
2859 /* Read in the directory hash. */
2860 unparsed_entries = apr_hash_make(pool);
2861 SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
2863 parsed_entries = apr_hash_make(pool);
2865 /* Translate the string dir entries into real entries. */
2866 for (hi = apr_hash_first(pool, unparsed_entries); hi; hi = apr_hash_next(hi))
2868 const void *key;
2869 void *val;
2870 char *str_val;
2871 char *str, *last_str;
2872 svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
2874 apr_hash_this(hi, &key, NULL, &val);
2875 str_val = apr_pstrdup(pool, *((char **)val));
2876 dirent->name = apr_pstrdup(pool, key);
2878 str = apr_strtok(str_val, " ", &last_str);
2879 if (str == NULL)
2880 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2881 _("Directory entry corrupt"));
2883 if (strcmp(str, KIND_FILE) == 0)
2885 dirent->kind = svn_node_file;
2887 else if (strcmp(str, KIND_DIR) == 0)
2889 dirent->kind = svn_node_dir;
2891 else
2893 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2894 _("Directory entry corrupt"));
2897 str = apr_strtok(NULL, " ", &last_str);
2898 if (str == NULL)
2899 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2900 _("Directory entry corrupt"));
2902 dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
2904 apr_hash_set(parsed_entries, dirent->name, APR_HASH_KEY_STRING, dirent);
2907 /* If this is an immutable directory, let's cache the contents. */
2908 if (! svn_fs_fs__id_txn_id(noderev->id))
2910 /* Start by NULLing the ID field, so that we never leave the
2911 cache in an illegal state. */
2912 ffd->dir_cache_id[hid] = NULL;
2914 if (ffd->dir_cache_pool[hid])
2915 svn_pool_clear(ffd->dir_cache_pool[hid]);
2916 else
2917 ffd->dir_cache_pool[hid] = svn_pool_create(fs->pool);
2919 ffd->dir_cache[hid] = copy_dir_entries(parsed_entries,
2920 ffd->dir_cache_pool[hid]);
2921 ffd->dir_cache_id[hid] = svn_fs_fs__id_copy(noderev->id,
2922 ffd->dir_cache_pool[hid]);
2925 *entries_p = parsed_entries;
2926 return SVN_NO_ERROR;
2929 svn_error_t *
2930 svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
2931 svn_fs_t *fs,
2932 node_revision_t *noderev,
2933 apr_pool_t *pool)
2935 apr_hash_t *proplist;
2936 svn_stream_t *stream;
2938 proplist = apr_hash_make(pool);
2940 if (noderev->prop_rep && noderev->prop_rep->txn_id)
2942 apr_file_t *props_file;
2943 const char *filename = path_txn_node_props(fs, noderev->id, pool);
2945 SVN_ERR(svn_io_file_open(&props_file, filename,
2946 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
2947 pool));
2948 stream = svn_stream_from_aprfile(props_file, pool);
2949 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
2950 SVN_ERR(svn_io_file_close(props_file, pool));
2952 else if (noderev->prop_rep)
2954 SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
2955 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
2956 SVN_ERR(svn_stream_close(stream));
2959 *proplist_p = proplist;
2961 return SVN_NO_ERROR;
2964 svn_error_t *
2965 svn_fs_fs__file_length(svn_filesize_t *length,
2966 node_revision_t *noderev,
2967 apr_pool_t *pool)
2969 if (noderev->data_rep)
2970 *length = noderev->data_rep->expanded_size;
2971 else
2972 *length = 0;
2974 return SVN_NO_ERROR;
2977 svn_boolean_t
2978 svn_fs_fs__noderev_same_rep_key(representation_t *a,
2979 representation_t *b)
2981 if (a == b)
2982 return TRUE;
2984 if (a && (! b))
2985 return FALSE;
2987 if (b && (! a))
2988 return FALSE;
2990 if (a->offset != b->offset)
2991 return FALSE;
2993 if (a->revision != b->revision)
2994 return FALSE;
2996 return TRUE;
2999 svn_error_t *
3000 svn_fs_fs__file_checksum(unsigned char digest[],
3001 node_revision_t *noderev,
3002 apr_pool_t *pool)
3004 if (noderev->data_rep)
3005 memcpy(digest, noderev->data_rep->checksum, APR_MD5_DIGESTSIZE);
3006 else
3007 memset(digest, 0, APR_MD5_DIGESTSIZE);
3009 return SVN_NO_ERROR;
3012 representation_t *
3013 svn_fs_fs__rep_copy(representation_t *rep,
3014 apr_pool_t *pool)
3016 representation_t *rep_new;
3018 if (rep == NULL)
3019 return NULL;
3021 rep_new = apr_pcalloc(pool, sizeof(*rep_new));
3023 memcpy(rep_new, rep, sizeof(*rep_new));
3025 return rep_new;
3028 /* Merge the internal-use-only CHANGE into a hash of public-FS
3029 svn_fs_path_change_t CHANGES, collapsing multiple changes into a
3030 single summarical (is that real word?) change per path. Also keep
3031 the COPYFROM_HASH up to date with new adds and replaces. */
3032 static svn_error_t *
3033 fold_change(apr_hash_t *changes,
3034 const change_t *change,
3035 apr_hash_t *copyfrom_hash)
3037 apr_pool_t *pool = apr_hash_pool_get(changes);
3038 apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_hash);
3039 svn_fs_path_change_t *old_change, *new_change;
3040 const char *path, *copyfrom_string, *copyfrom_path = NULL;
3042 if ((old_change = apr_hash_get(changes, change->path, APR_HASH_KEY_STRING)))
3044 /* This path already exists in the hash, so we have to merge
3045 this change into the already existing one. */
3047 /* Get the existing copyfrom entry for this path. */
3048 copyfrom_string = apr_hash_get(copyfrom_hash, change->path,
3049 APR_HASH_KEY_STRING);
3051 /* If this entry existed in the copyfrom hash, we don't need to
3052 copy it. */
3053 if (copyfrom_string)
3054 copyfrom_path = change->path;
3056 /* Since the path already exists in the hash, we don't have to
3057 dup the allocation for the path itself. */
3058 path = change->path;
3059 /* Sanity check: only allow NULL node revision ID in the
3060 `reset' case. */
3061 if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
3062 return svn_error_create
3063 (SVN_ERR_FS_CORRUPT, NULL,
3064 _("Missing required node revision ID"));
3066 /* Sanity check: we should be talking about the same node
3067 revision ID as our last change except where the last change
3068 was a deletion. */
3069 if (change->noderev_id
3070 && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
3071 && (old_change->change_kind != svn_fs_path_change_delete))
3072 return svn_error_create
3073 (SVN_ERR_FS_CORRUPT, NULL,
3074 _("Invalid change ordering: new node revision ID "
3075 "without delete"));
3077 /* Sanity check: an add, replacement, or reset must be the first
3078 thing to follow a deletion. */
3079 if ((old_change->change_kind == svn_fs_path_change_delete)
3080 && (! ((change->kind == svn_fs_path_change_replace)
3081 || (change->kind == svn_fs_path_change_reset)
3082 || (change->kind == svn_fs_path_change_add))))
3083 return svn_error_create
3084 (SVN_ERR_FS_CORRUPT, NULL,
3085 _("Invalid change ordering: non-add change on deleted path"));
3087 /* Now, merge that change in. */
3088 switch (change->kind)
3090 case svn_fs_path_change_reset:
3091 /* A reset here will simply remove the path change from the
3092 hash. */
3093 old_change = NULL;
3094 copyfrom_string = NULL;
3095 break;
3097 case svn_fs_path_change_delete:
3098 if (old_change->change_kind == svn_fs_path_change_add)
3100 /* If the path was introduced in this transaction via an
3101 add, and we are deleting it, just remove the path
3102 altogether. */
3103 old_change = NULL;
3105 else
3107 /* A deletion overrules all previous changes. */
3108 old_change->change_kind = svn_fs_path_change_delete;
3109 old_change->text_mod = change->text_mod;
3110 old_change->prop_mod = change->prop_mod;
3112 copyfrom_string = NULL;
3113 break;
3115 case svn_fs_path_change_add:
3116 case svn_fs_path_change_replace:
3117 /* An add at this point must be following a previous delete,
3118 so treat it just like a replace. */
3119 old_change->change_kind = svn_fs_path_change_replace;
3120 old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
3121 pool);
3122 old_change->text_mod = change->text_mod;
3123 old_change->prop_mod = change->prop_mod;
3124 if (change->copyfrom_rev == SVN_INVALID_REVNUM)
3125 copyfrom_string = apr_pstrdup(copyfrom_pool, "");
3126 else
3128 copyfrom_string = apr_psprintf(copyfrom_pool,
3129 "%ld %s",
3130 change->copyfrom_rev,
3131 change->copyfrom_path);
3133 break;
3135 case svn_fs_path_change_modify:
3136 default:
3137 if (change->text_mod)
3138 old_change->text_mod = TRUE;
3139 if (change->prop_mod)
3140 old_change->prop_mod = TRUE;
3141 break;
3144 /* Point our new_change to our (possibly modified) old_change. */
3145 new_change = old_change;
3147 else
3149 /* This change is new to the hash, so make a new public change
3150 structure from the internal one (in the hash's pool), and dup
3151 the path into the hash's pool, too. */
3152 new_change = apr_pcalloc(pool, sizeof(*new_change));
3153 new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
3154 new_change->change_kind = change->kind;
3155 new_change->text_mod = change->text_mod;
3156 new_change->prop_mod = change->prop_mod;
3157 if (change->copyfrom_rev != SVN_INVALID_REVNUM)
3159 copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
3160 change->copyfrom_rev,
3161 change->copyfrom_path);
3163 else
3164 copyfrom_string = apr_pstrdup(copyfrom_pool, "");
3165 path = apr_pstrdup(pool, change->path);
3168 /* Add (or update) this path. */
3169 apr_hash_set(changes, path, APR_HASH_KEY_STRING, new_change);
3171 /* If copyfrom_path is non-NULL, the key is already present in the
3172 hash, so we don't need to duplicate it in the copyfrom pool. */
3173 if (! copyfrom_path)
3175 /* If copyfrom_string is NULL, the hash entry will be deleted,
3176 so we don't need to duplicate the key in the copyfrom
3177 pool. */
3178 copyfrom_path = copyfrom_string ? apr_pstrdup(copyfrom_pool, path)
3179 : path;
3182 apr_hash_set(copyfrom_hash, copyfrom_path, APR_HASH_KEY_STRING,
3183 copyfrom_string);
3185 return SVN_NO_ERROR;
3188 /* The 256 is an arbitrary size large enough to hold the node id and the
3189 * various flags. */
3190 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
3192 /* Read the next entry in the changes record from file FILE and store
3193 the resulting change in *CHANGE_P. If there is no next record,
3194 store NULL there. Perform all allocations from POOL. */
3195 static svn_error_t *
3196 read_change(change_t **change_p,
3197 apr_file_t *file,
3198 apr_pool_t *pool)
3200 char buf[MAX_CHANGE_LINE_LEN];
3201 apr_size_t len = sizeof(buf);
3202 change_t *change;
3203 char *str, *last_str;
3204 svn_error_t *err;
3206 /* Default return value. */
3207 *change_p = NULL;
3209 err = svn_io_read_length_line(file, buf, &len, pool);
3211 /* Check for a blank line. */
3212 if (err || (len == 0))
3214 if (err && APR_STATUS_IS_EOF(err->apr_err))
3216 svn_error_clear(err);
3217 return SVN_NO_ERROR;
3219 if ((len == 0) && (! err))
3220 return SVN_NO_ERROR;
3221 return err;
3224 change = apr_pcalloc(pool, sizeof(*change));
3226 /* Get the node-id of the change. */
3227 str = apr_strtok(buf, " ", &last_str);
3228 if (str == NULL)
3229 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3230 _("Invalid changes line in rev-file"));
3232 change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
3234 /* Get the change type. */
3235 str = apr_strtok(NULL, " ", &last_str);
3236 if (str == NULL)
3237 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3238 _("Invalid changes line in rev-file"));
3240 if (strcmp(str, ACTION_MODIFY) == 0)
3242 change->kind = svn_fs_path_change_modify;
3244 else if (strcmp(str, ACTION_ADD) == 0)
3246 change->kind = svn_fs_path_change_add;
3248 else if (strcmp(str, ACTION_DELETE) == 0)
3250 change->kind = svn_fs_path_change_delete;
3252 else if (strcmp(str, ACTION_REPLACE) == 0)
3254 change->kind = svn_fs_path_change_replace;
3256 else if (strcmp(str, ACTION_RESET) == 0)
3258 change->kind = svn_fs_path_change_reset;
3260 else
3262 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3263 _("Invalid change kind in rev file"));
3266 /* Get the text-mod flag. */
3267 str = apr_strtok(NULL, " ", &last_str);
3268 if (str == NULL)
3269 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3270 _("Invalid changes line in rev-file"));
3272 if (strcmp(str, FLAG_TRUE) == 0)
3274 change->text_mod = TRUE;
3276 else if (strcmp(str, FLAG_FALSE) == 0)
3278 change->text_mod = FALSE;
3280 else
3282 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3283 _("Invalid text-mod flag in rev-file"));
3286 /* Get the prop-mod flag. */
3287 str = apr_strtok(NULL, " ", &last_str);
3288 if (str == NULL)
3289 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3290 _("Invalid changes line in rev-file"));
3292 if (strcmp(str, FLAG_TRUE) == 0)
3294 change->prop_mod = TRUE;
3296 else if (strcmp(str, FLAG_FALSE) == 0)
3298 change->prop_mod = FALSE;
3300 else
3302 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3303 _("Invalid prop-mod flag in rev-file"));
3306 /* Get the changed path. */
3307 change->path = apr_pstrdup(pool, last_str);
3310 /* Read the next line, the copyfrom line. */
3311 len = sizeof(buf);
3312 SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3314 if (len == 0)
3316 change->copyfrom_rev = SVN_INVALID_REVNUM;
3317 change->copyfrom_path = NULL;
3319 else
3321 str = apr_strtok(buf, " ", &last_str);
3322 if (! str)
3323 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3324 _("Invalid changes line in rev-file"));
3325 change->copyfrom_rev = atol(str);
3327 if (! last_str)
3328 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3329 _("Invalid changes line in rev-file"));
3331 change->copyfrom_path = apr_pstrdup(pool, last_str);
3334 *change_p = change;
3336 return SVN_NO_ERROR;
3339 /* Fetch all the changed path entries from FILE and store then in
3340 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
3341 *data. Store a hash of paths to copyfrom revisions/paths in
3342 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
3343 the changed-path entries have already been folded (by
3344 write_final_changed_path_info) and may be out of order, so we shouldn't
3345 remove children of replaced or deleted directories. Do all
3346 allocations in POOL. */
3347 static svn_error_t *
3348 fetch_all_changes(apr_hash_t *changed_paths,
3349 apr_hash_t *copyfrom_hash,
3350 apr_file_t *file,
3351 svn_boolean_t prefolded,
3352 apr_pool_t *pool)
3354 change_t *change;
3355 apr_pool_t *iterpool = svn_pool_create(pool);
3356 apr_hash_t *my_hash;
3358 /* If we are passed a NULL copyfrom hash, manufacture one for the
3359 duration of this call. */
3360 my_hash = copyfrom_hash ? copyfrom_hash : apr_hash_make(pool);
3362 /* Read in the changes one by one, folding them into our local hash
3363 as necessary. */
3365 SVN_ERR(read_change(&change, file, iterpool));
3367 while (change)
3369 SVN_ERR(fold_change(changed_paths, change, my_hash));
3371 /* Now, if our change was a deletion or replacement, we have to
3372 blow away any changes thus far on paths that are (or, were)
3373 children of this path.
3374 ### i won't bother with another iteration pool here -- at
3375 most we talking about a few extra dups of paths into what
3376 is already a temporary subpool.
3379 if (((change->kind == svn_fs_path_change_delete)
3380 || (change->kind == svn_fs_path_change_replace))
3381 && ! prefolded)
3383 apr_hash_index_t *hi;
3385 for (hi = apr_hash_first(iterpool, changed_paths);
3387 hi = apr_hash_next(hi))
3389 /* KEY is the path. */
3390 const void *hashkey;
3391 apr_ssize_t klen;
3392 apr_hash_this(hi, &hashkey, &klen, NULL);
3394 /* If we come across our own path, ignore it. */
3395 if (strcmp(change->path, hashkey) == 0)
3396 continue;
3398 /* If we come across a child of our path, remove it. */
3399 if (svn_path_is_child(change->path, hashkey, iterpool))
3400 apr_hash_set(changed_paths, hashkey, klen, NULL);
3404 /* Clear the per-iteration subpool. */
3405 svn_pool_clear(iterpool);
3407 SVN_ERR(read_change(&change, file, iterpool));
3410 /* Destroy the per-iteration subpool. */
3411 svn_pool_destroy(iterpool);
3413 return SVN_NO_ERROR;
3416 svn_error_t *
3417 svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
3418 svn_fs_t *fs,
3419 const char *txn_id,
3420 apr_hash_t *copyfrom_cache,
3421 apr_pool_t *pool)
3423 apr_file_t *file;
3424 apr_hash_t *changed_paths = apr_hash_make(pool);
3426 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
3427 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3429 SVN_ERR(fetch_all_changes(changed_paths, copyfrom_cache, file, FALSE,
3430 pool));
3432 SVN_ERR(svn_io_file_close(file, pool));
3434 *changed_paths_p = changed_paths;
3436 return SVN_NO_ERROR;
3439 svn_error_t *
3440 svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
3441 svn_fs_t *fs,
3442 svn_revnum_t rev,
3443 apr_hash_t *copyfrom_cache,
3444 apr_pool_t *pool)
3446 apr_off_t changes_offset;
3447 apr_hash_t *changed_paths;
3448 apr_file_t *revision_file;
3450 SVN_ERR(ensure_revision_exists(fs, rev, pool));
3452 SVN_ERR(svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool),
3453 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3455 SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file,
3456 pool));
3458 SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
3460 changed_paths = apr_hash_make(pool);
3462 SVN_ERR(fetch_all_changes(changed_paths, copyfrom_cache, revision_file,
3463 TRUE, pool));
3465 /* Close the revision file. */
3466 SVN_ERR(svn_io_file_close(revision_file, pool));
3468 *changed_paths_p = changed_paths;
3470 return SVN_NO_ERROR;
3473 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
3474 the filesystem FS. This is only used to create the root of a transaction.
3475 Allocations are from POOL. */
3476 static svn_error_t *
3477 create_new_txn_noderev_from_rev(svn_fs_t *fs,
3478 const char *txn_id,
3479 svn_fs_id_t *src,
3480 apr_pool_t *pool)
3482 node_revision_t *noderev;
3483 const char *node_id, *copy_id;
3485 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
3487 if (svn_fs_fs__id_txn_id(noderev->id))
3488 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3489 _("Copying from transactions not allowed"));
3491 noderev->predecessor_id = noderev->id;
3492 noderev->predecessor_count++;
3493 noderev->copyfrom_path = NULL;
3494 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
3496 /* For the transaction root, the copyroot never changes. */
3498 node_id = svn_fs_fs__id_node_id(noderev->id);
3499 copy_id = svn_fs_fs__id_copy_id(noderev->id);
3500 noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
3502 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool));
3504 return SVN_NO_ERROR;
3507 /* A structure used by get_and_increment_txn_key_body(). */
3508 struct get_and_increment_txn_key_baton {
3509 svn_fs_t *fs;
3510 char *txn_id;
3511 apr_pool_t *pool;
3514 /* Callback used in the implementation of create_txn_dir(). This gets
3515 the current base 36 value in PATH_TXN_CURRENT and increments it.
3516 It returns the original value by the baton. */
3517 static svn_error_t *
3518 get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
3520 struct get_and_increment_txn_key_baton *cb = baton;
3521 const char *txn_current_filename = path_txn_current(cb->fs, pool);
3522 apr_file_t *txn_current_file;
3523 const char *tmp_filename;
3524 char next_txn_id[MAX_KEY_SIZE+3];
3525 svn_error_t *err = SVN_NO_ERROR;
3526 apr_pool_t *iterpool;
3527 apr_size_t len;
3528 int i;
3530 cb->txn_id = apr_palloc(cb->pool, MAX_KEY_SIZE);
3532 iterpool = svn_pool_create(pool);
3533 for (i = 0; i < SVN_ESTALE_RETRY_COUNT; ++i)
3535 svn_pool_clear(iterpool);
3537 SVN_RETRY_ESTALE(err, svn_io_file_open(&txn_current_file,
3538 txn_current_filename,
3539 APR_READ | APR_BUFFERED,
3540 APR_OS_DEFAULT, iterpool));
3541 len = MAX_KEY_SIZE;
3542 SVN_RETRY_ESTALE(err, svn_io_read_length_line(txn_current_file,
3543 cb->txn_id,
3544 &len,
3545 iterpool));
3546 SVN_IGNORE_ESTALE(err, svn_io_file_close(txn_current_file, iterpool));
3548 break;
3550 if (err)
3551 return err;
3553 svn_pool_destroy(iterpool);
3555 /* Increment the key and add a trailing \n to the string so the
3556 txn-current file has a newline in it. */
3557 svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
3558 next_txn_id[len] = '\n';
3559 ++len;
3560 next_txn_id[len] = '\0';
3562 SVN_ERR(svn_io_open_unique_file2(&txn_current_file, &tmp_filename,
3563 txn_current_filename, ".tmp",
3564 svn_io_file_del_none, pool));
3566 SVN_ERR(svn_io_file_write_full(txn_current_file,
3567 next_txn_id,
3568 len,
3569 NULL,
3570 pool));
3572 SVN_ERR(svn_io_file_flush_to_disk(txn_current_file, pool));
3574 SVN_ERR(svn_io_file_close(txn_current_file, pool));
3576 SVN_ERR(svn_fs_fs__move_into_place(tmp_filename, txn_current_filename,
3577 txn_current_filename, pool));
3579 return err;
3582 /* Create a unique directory for a transaction in FS based on revision
3583 REV. Return the ID for this transaction in *ID_P. Use a sequence
3584 value in the transaction ID to prevent reuse of transaction IDs. */
3585 static svn_error_t *
3586 create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
3587 apr_pool_t *pool)
3589 struct get_and_increment_txn_key_baton cb;
3590 const char *txn_dir;
3592 /* Get the current transaction sequence value, which is a base-36
3593 number, from the txn-current file, and write an
3594 incremented value back out to the file. Place the revision
3595 number the transaction is based off into the transaction id. */
3596 cb.pool = pool;
3597 cb.fs = fs;
3598 SVN_ERR(with_txn_current_lock(fs,
3599 get_and_increment_txn_key_body,
3600 &cb,
3601 pool));
3602 *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
3604 txn_dir = svn_path_join_many(pool,
3605 fs->path,
3606 PATH_TXNS_DIR,
3607 apr_pstrcat(pool, *id_p, PATH_EXT_TXN, NULL),
3608 NULL);
3610 SVN_ERR(svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool));
3612 return SVN_NO_ERROR;
3615 /* Create a unique directory for a transaction in FS based on revision
3616 REV. Return the ID for this transaction in *ID_P. This
3617 implementation is used in svn 1.4 and earlier repositories and is
3618 kept in 1.5 and greater to support the --pre-1.4-compatible and
3619 --pre-1.5-compatible repository creation options. Reused
3620 transaction IDs are possible with this implementation. */
3621 static svn_error_t *
3622 create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
3623 apr_pool_t *pool)
3625 unsigned int i;
3626 apr_pool_t *subpool;
3627 const char *unique_path, *prefix;
3629 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
3630 prefix = svn_path_join_many(pool, fs->path, PATH_TXNS_DIR,
3631 apr_psprintf(pool, "%ld", rev), NULL);
3633 subpool = svn_pool_create(pool);
3634 for (i = 1; i <= 99999; i++)
3636 svn_error_t *err;
3638 svn_pool_clear(subpool);
3639 unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
3640 err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
3641 if (! err)
3643 /* We succeeded. Return the basename minus the ".txn" extension. */
3644 const char *name = svn_path_basename(unique_path, subpool);
3645 *id_p = apr_pstrndup(pool, name,
3646 strlen(name) - strlen(PATH_EXT_TXN));
3647 svn_pool_destroy(subpool);
3648 return SVN_NO_ERROR;
3650 if (! APR_STATUS_IS_EEXIST(err->apr_err))
3651 return err;
3652 svn_error_clear(err);
3655 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
3656 NULL,
3657 _("Unable to create transaction directory "
3658 "in '%s' for revision %ld"),
3659 fs->path, rev);
3662 svn_error_t *
3663 svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
3664 svn_fs_t *fs,
3665 svn_revnum_t rev,
3666 apr_pool_t *pool)
3668 fs_fs_data_t *ffd = fs->fsap_data;
3669 svn_fs_txn_t *txn;
3670 svn_fs_id_t *root_id;
3672 txn = apr_pcalloc(pool, sizeof(*txn));
3674 /* Get the txn_id. */
3675 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
3676 SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
3677 else
3678 SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
3680 txn->fs = fs;
3681 txn->base_rev = rev;
3683 txn->vtable = &txn_vtable;
3684 *txn_p = txn;
3686 /* Create a new root node for this transaction. */
3687 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
3688 SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
3690 /* Create an empty rev file. */
3691 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
3692 pool));
3694 /* Create an empty rev-lock file. */
3695 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
3696 pool));
3698 /* Create an empty changes file. */
3699 SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
3700 pool));
3702 /* Create the next-ids file. */
3703 SVN_ERR(svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
3704 pool));
3706 return SVN_NO_ERROR;
3709 /* Store the property list for transaction TXN_ID in PROPLIST.
3710 Perform temporary allocations in POOL. */
3711 static svn_error_t *
3712 get_txn_proplist(apr_hash_t *proplist,
3713 svn_fs_t *fs,
3714 const char *txn_id,
3715 apr_pool_t *pool)
3717 apr_file_t *txn_prop_file;
3719 /* Open the transaction properties file. */
3720 SVN_ERR(svn_io_file_open(&txn_prop_file, path_txn_props(fs, txn_id, pool),
3721 APR_READ | APR_BUFFERED,
3722 APR_OS_DEFAULT, pool));
3724 /* Read in the property list. */
3725 SVN_ERR(svn_hash_read2(proplist,
3726 svn_stream_from_aprfile(txn_prop_file, pool),
3727 SVN_HASH_TERMINATOR, pool));
3729 SVN_ERR(svn_io_file_close(txn_prop_file, pool));
3731 return SVN_NO_ERROR;
3734 svn_error_t *
3735 svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
3736 const char *name,
3737 const svn_string_t *value,
3738 apr_pool_t *pool)
3740 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
3741 svn_prop_t prop;
3743 prop.name = name;
3744 prop.value = value;
3745 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
3747 return svn_fs_fs__change_txn_props(txn, props, pool);
3750 svn_error_t *
3751 svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
3752 apr_array_header_t *props,
3753 apr_pool_t *pool)
3755 apr_file_t *txn_prop_file;
3756 apr_hash_t *txn_prop = apr_hash_make(pool);
3757 int i;
3758 svn_error_t *err;
3760 err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
3761 /* Here - and here only - we need to deal with the possibility that the
3762 transaction property file doesn't yet exist. The rest of the
3763 implementation assumes that the file exists, but we're called to set the
3764 initial transaction properties as the transaction is being created. */
3765 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
3766 svn_error_clear(err);
3767 else if (err)
3768 return err;
3770 for (i = 0; i < props->nelts; i++)
3772 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
3774 apr_hash_set(txn_prop, prop->name, APR_HASH_KEY_STRING, prop->value);
3777 /* Create a new version of the file and write out the new props. */
3778 /* Open the transaction properties file. */
3779 SVN_ERR(svn_io_file_open(&txn_prop_file,
3780 path_txn_props(txn->fs, txn->id, pool),
3781 APR_WRITE | APR_CREATE | APR_TRUNCATE
3782 | APR_BUFFERED, APR_OS_DEFAULT, pool));
3784 SVN_ERR(svn_hash_write(txn_prop, txn_prop_file, pool));
3786 SVN_ERR(svn_io_file_close(txn_prop_file, pool));
3788 return SVN_NO_ERROR;
3791 svn_error_t *
3792 svn_fs_fs__get_txn(transaction_t **txn_p,
3793 svn_fs_t *fs,
3794 const char *txn_id,
3795 apr_pool_t *pool)
3797 transaction_t *txn;
3798 node_revision_t *noderev;
3799 svn_fs_id_t *root_id;
3801 txn = apr_pcalloc(pool, sizeof(*txn));
3802 txn->proplist = apr_hash_make(pool);
3804 SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
3805 root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
3807 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
3809 txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
3810 txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
3811 txn->copies = NULL;
3813 *txn_p = txn;
3815 return SVN_NO_ERROR;
3818 /* Write out the currently available next node_id NODE_ID and copy_id
3819 COPY_ID for transaction TXN_ID in filesystem FS. Perform temporary
3820 allocations in POOL. */
3821 static svn_error_t *
3822 write_next_ids(svn_fs_t *fs,
3823 const char *txn_id,
3824 const char *node_id,
3825 const char *copy_id,
3826 apr_pool_t *pool)
3828 apr_file_t *file;
3829 svn_stream_t *out_stream;
3831 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
3832 APR_WRITE | APR_TRUNCATE,
3833 APR_OS_DEFAULT, pool));
3835 out_stream = svn_stream_from_aprfile(file, pool);
3837 SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
3839 SVN_ERR(svn_stream_close(out_stream));
3840 SVN_ERR(svn_io_file_close(file, pool));
3842 return SVN_NO_ERROR;
3845 /* Find out what the next unique node-id and copy-id are for
3846 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
3847 and *COPY_ID. Perform all allocations in POOL. */
3848 static svn_error_t *
3849 read_next_ids(const char **node_id,
3850 const char **copy_id,
3851 svn_fs_t *fs,
3852 const char *txn_id,
3853 apr_pool_t *pool)
3855 apr_file_t *file;
3856 char buf[MAX_KEY_SIZE*2+3];
3857 apr_size_t limit;
3858 char *str, *last_str;
3860 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
3861 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3863 limit = sizeof(buf);
3864 SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
3866 SVN_ERR(svn_io_file_close(file, pool));
3868 /* Parse this into two separate strings. */
3870 str = apr_strtok(buf, " ", &last_str);
3871 if (! str)
3872 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3873 _("next-id file corrupt"));
3875 *node_id = apr_pstrdup(pool, str);
3877 str = apr_strtok(NULL, " ", &last_str);
3878 if (! str)
3879 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3880 _("next-id file corrupt"));
3882 *copy_id = apr_pstrdup(pool, str);
3884 return SVN_NO_ERROR;
3887 /* Get a new and unique to this transaction node-id for transaction
3888 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
3889 Perform all allocations in POOL. */
3890 static svn_error_t *
3891 get_new_txn_node_id(const char **node_id_p,
3892 svn_fs_t *fs,
3893 const char *txn_id,
3894 apr_pool_t *pool)
3896 const char *cur_node_id, *cur_copy_id;
3897 char *node_id;
3898 apr_size_t len;
3900 /* First read in the current next-ids file. */
3901 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
3903 node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
3905 len = strlen(cur_node_id);
3906 svn_fs_fs__next_key(cur_node_id, &len, node_id);
3908 SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
3910 *node_id_p = apr_pstrcat(pool, "_", cur_node_id, NULL);
3912 return SVN_NO_ERROR;
3915 svn_error_t *
3916 svn_fs_fs__create_node(const svn_fs_id_t **id_p,
3917 svn_fs_t *fs,
3918 node_revision_t *noderev,
3919 const char *copy_id,
3920 const char *txn_id,
3921 apr_pool_t *pool)
3923 const char *node_id;
3924 const svn_fs_id_t *id;
3926 /* Get a new node-id for this node. */
3927 SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
3929 id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
3931 noderev->id = id;
3933 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
3935 *id_p = id;
3937 return SVN_NO_ERROR;
3940 svn_error_t *
3941 svn_fs_fs__purge_txn(svn_fs_t *fs,
3942 const char *txn_id,
3943 apr_pool_t *pool)
3945 fs_fs_data_t *ffd = fs->fsap_data;
3947 /* Remove the shared transaction object associated with this transaction. */
3948 SVN_ERR(purge_shared_txn(fs, txn_id, pool));
3949 /* Remove the directory associated with this transaction. */
3950 SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
3951 NULL, NULL, pool));
3952 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
3954 /* Delete protorev and its lock, which aren't in the txn
3955 directory. It's OK if they don't exist (for example, if this
3956 is post-commit and the proto-rev has been moved into
3957 place). */
3958 svn_error_t *err = svn_io_remove_file(path_txn_proto_rev(fs, txn_id,
3959 pool), pool);
3960 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3962 svn_error_clear(err);
3963 err = NULL;
3965 if (err)
3966 return err;
3968 err = svn_io_remove_file(path_txn_proto_rev_lock(fs, txn_id, pool),
3969 pool);
3970 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3972 svn_error_clear(err);
3973 err = NULL;
3975 if (err)
3976 return err;
3978 return SVN_NO_ERROR;
3982 svn_error_t *
3983 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
3984 apr_pool_t *pool)
3986 fs_fs_data_t *ffd;
3988 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
3990 /* Clean out the directory cache. */
3991 ffd = txn->fs->fsap_data;
3992 memset(&ffd->dir_cache_id, 0,
3993 sizeof(svn_fs_id_t *) * NUM_DIR_CACHE_ENTRIES);
3995 /* Now, purge the transaction. */
3996 SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
3997 _("Transaction cleanup failed"));
3999 return SVN_NO_ERROR;
4003 static const char *
4004 unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
4005 apr_pool_t *pool)
4007 return apr_psprintf(pool, "%s %s",
4008 (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
4009 svn_fs_fs__id_unparse(id, pool)->data);
4012 /* Given a hash ENTRIES of dirent structions, return a hash in
4013 *STR_ENTRIES_P, that has svn_string_t as the values in the format
4014 specified by the fs_fs directory contents file. Perform
4015 allocations in POOL. */
4016 static svn_error_t *
4017 unparse_dir_entries(apr_hash_t **str_entries_p,
4018 apr_hash_t *entries,
4019 apr_pool_t *pool)
4021 apr_hash_index_t *hi;
4023 *str_entries_p = apr_hash_make(pool);
4025 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
4027 const void *key;
4028 apr_ssize_t klen;
4029 void *val;
4030 svn_fs_dirent_t *dirent;
4031 const char *new_val;
4033 apr_hash_this(hi, &key, &klen, &val);
4034 dirent = val;
4035 new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
4036 apr_hash_set(*str_entries_p, key, klen,
4037 svn_string_create(new_val, pool));
4040 return SVN_NO_ERROR;
4044 svn_error_t *
4045 svn_fs_fs__set_entry(svn_fs_t *fs,
4046 const char *txn_id,
4047 node_revision_t *parent_noderev,
4048 const char *name,
4049 const svn_fs_id_t *id,
4050 svn_node_kind_t kind,
4051 apr_pool_t *pool)
4053 representation_t *rep = parent_noderev->data_rep;
4054 const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
4055 apr_file_t *file;
4056 svn_stream_t *out;
4058 if (!rep || !rep->txn_id)
4061 apr_hash_t *entries;
4062 apr_pool_t *subpool = svn_pool_create(pool);
4064 /* Before we can modify the directory, we need to dump its old
4065 contents into a mutable representation file. */
4066 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
4067 subpool));
4068 SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
4069 SVN_ERR(svn_io_file_open(&file, filename,
4070 APR_WRITE | APR_CREATE | APR_BUFFERED,
4071 APR_OS_DEFAULT, pool));
4072 out = svn_stream_from_aprfile(file, pool);
4073 SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
4075 svn_pool_destroy(subpool);
4078 /* Mark the node-rev's data rep as mutable. */
4079 rep = apr_pcalloc(pool, sizeof(*rep));
4080 rep->revision = SVN_INVALID_REVNUM;
4081 rep->txn_id = txn_id;
4082 parent_noderev->data_rep = rep;
4083 SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
4084 parent_noderev, FALSE, pool));
4086 else
4088 /* The directory rep is already mutable, so just open it for append. */
4089 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
4090 APR_OS_DEFAULT, pool));
4091 out = svn_stream_from_aprfile(file, pool);
4094 /* Append an incremental hash entry for the entry change. */
4095 if (id)
4097 const char *val = unparse_dir_entry(kind, id, pool);
4099 SVN_ERR(svn_stream_printf(out, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
4100 "V %" APR_SIZE_T_FMT "\n%s\n",
4101 strlen(name), name,
4102 strlen(val), val));
4104 else
4106 SVN_ERR(svn_stream_printf(out, pool, "D %" APR_SIZE_T_FMT "\n%s\n",
4107 strlen(name), name));
4110 SVN_ERR(svn_io_file_close(file, pool));
4111 return SVN_NO_ERROR;
4114 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
4115 string COPYFROM, into the file specified by FILE. All temporary
4116 allocations are in POOL. */
4117 static svn_error_t *
4118 write_change_entry(apr_file_t *file,
4119 const char *path,
4120 svn_fs_path_change_t *change,
4121 const char *copyfrom,
4122 apr_pool_t *pool)
4124 const char *idstr, *buf;
4125 const char *change_string = NULL;
4127 switch (change->change_kind)
4129 case svn_fs_path_change_modify:
4130 change_string = ACTION_MODIFY;
4131 break;
4132 case svn_fs_path_change_add:
4133 change_string = ACTION_ADD;
4134 break;
4135 case svn_fs_path_change_delete:
4136 change_string = ACTION_DELETE;
4137 break;
4138 case svn_fs_path_change_replace:
4139 change_string = ACTION_REPLACE;
4140 break;
4141 case svn_fs_path_change_reset:
4142 change_string = ACTION_RESET;
4143 break;
4144 default:
4145 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4146 _("Invalid change type"));
4149 if (change->node_rev_id)
4150 idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
4151 else
4152 idstr = ACTION_RESET;
4154 buf = apr_psprintf(pool, "%s %s %s %s %s\n",
4155 idstr, change_string,
4156 change->text_mod ? FLAG_TRUE : FLAG_FALSE,
4157 change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
4158 path);
4160 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
4162 if (copyfrom)
4164 SVN_ERR(svn_io_file_write_full(file, copyfrom, strlen(copyfrom),
4165 NULL, pool));
4168 SVN_ERR(svn_io_file_write_full(file, "\n", 1, NULL, pool));
4170 return SVN_NO_ERROR;
4173 svn_error_t *
4174 svn_fs_fs__add_change(svn_fs_t *fs,
4175 const char *txn_id,
4176 const char *path,
4177 const svn_fs_id_t *id,
4178 svn_fs_path_change_kind_t change_kind,
4179 svn_boolean_t text_mod,
4180 svn_boolean_t prop_mod,
4181 svn_revnum_t copyfrom_rev,
4182 const char *copyfrom_path,
4183 apr_pool_t *pool)
4185 apr_file_t *file;
4186 const char *copyfrom;
4187 svn_fs_path_change_t *change = apr_pcalloc(pool, sizeof(*change));
4189 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
4190 APR_APPEND | APR_WRITE | APR_CREATE
4191 | APR_BUFFERED, APR_OS_DEFAULT, pool));
4193 if (copyfrom_rev != SVN_INVALID_REVNUM)
4194 copyfrom = apr_psprintf(pool, "%ld %s", copyfrom_rev, copyfrom_path);
4195 else
4196 copyfrom = "";
4198 change->node_rev_id = id;
4199 change->change_kind = change_kind;
4200 change->text_mod = text_mod;
4201 change->prop_mod = prop_mod;
4203 SVN_ERR(write_change_entry(file, path, change, copyfrom, pool));
4205 SVN_ERR(svn_io_file_close(file, pool));
4207 return SVN_NO_ERROR;
4210 /* This baton is used by the representation writing streams. It keeps
4211 track of the checksum information as well as the total size of the
4212 representation so far. */
4213 struct rep_write_baton
4215 /* The FS we are writing to. */
4216 svn_fs_t *fs;
4218 /* Actual file to which we are writing. */
4219 svn_stream_t *rep_stream;
4221 /* A stream from the delta combiner. Data written here gets
4222 deltified, then eventually written to rep_stream. */
4223 svn_stream_t *delta_stream;
4225 /* Where is this representation header stored. */
4226 apr_off_t rep_offset;
4228 /* Start of the actual data. */
4229 apr_off_t delta_start;
4231 /* How many bytes have been written to this rep already. */
4232 svn_filesize_t rep_size;
4234 /* The node revision for which we're writing out info. */
4235 node_revision_t *noderev;
4237 /* Actual output file. */
4238 apr_file_t *file;
4239 /* Lock 'cookie' used to unlock the output file once we've finished
4240 writing to it. */
4241 void *lockcookie;
4243 struct apr_md5_ctx_t md5_context;
4245 apr_pool_t *pool;
4247 apr_pool_t *parent_pool;
4250 /* Handler for the write method of the representation writable stream.
4251 BATON is a rep_write_baton, DATA is the data to write, and *LEN is
4252 the length of this data. */
4253 static svn_error_t *
4254 rep_write_contents(void *baton,
4255 const char *data,
4256 apr_size_t *len)
4258 struct rep_write_baton *b = baton;
4260 apr_md5_update(&b->md5_context, data, *len);
4261 b->rep_size += *len;
4263 /* If we are writing a delta, use that stream. */
4264 if (b->delta_stream)
4266 SVN_ERR(svn_stream_write(b->delta_stream, data, len));
4268 else
4270 SVN_ERR(svn_stream_write(b->rep_stream, data, len));
4273 return SVN_NO_ERROR;
4276 /* Given a node-revision NODEREV in filesystem FS, return the
4277 representation in *REP to use as the base for a text representation
4278 delta. Perform temporary allocations in *POOL. */
4279 static svn_error_t *
4280 choose_delta_base(representation_t **rep,
4281 svn_fs_t *fs,
4282 node_revision_t *noderev,
4283 apr_pool_t *pool)
4285 int count;
4286 node_revision_t *base;
4288 /* If we have no predecessors, then use the empty stream as a
4289 base. */
4290 if (! noderev->predecessor_count)
4292 *rep = NULL;
4293 return SVN_NO_ERROR;
4296 /* Flip the rightmost '1' bit of the predecessor count to determine
4297 which file rev (counting from 0) we want to use. (To see why
4298 count & (count - 1) unsets the rightmost set bit, think about how
4299 you decrement a binary number.) */
4300 count = noderev->predecessor_count;
4301 count = count & (count - 1);
4303 /* Walk back a number of predecessors equal to the difference
4304 between count and the original predecessor count. (For example,
4305 if noderev has ten predecessors and we want the eighth file rev,
4306 walk back two predecessors.) */
4307 base = noderev;
4308 while ((count++) < noderev->predecessor_count)
4309 SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
4310 base->predecessor_id, pool));
4312 *rep = base->data_rep;
4314 return SVN_NO_ERROR;
4317 /* Get a rep_write_baton and store it in *WB_P for the representation
4318 indicated by NODEREV in filesystem FS. Perform allocations in
4319 POOL. Only appropriate for file contents, not for props or
4320 directory contents. */
4321 static svn_error_t *
4322 rep_write_get_baton(struct rep_write_baton **wb_p,
4323 svn_fs_t *fs,
4324 node_revision_t *noderev,
4325 apr_pool_t *pool)
4327 struct rep_write_baton *b;
4328 apr_file_t *file;
4329 representation_t *base_rep;
4330 svn_stream_t *source;
4331 const char *header;
4332 svn_txdelta_window_handler_t wh;
4333 void *whb;
4334 fs_fs_data_t *ffd = fs->fsap_data;
4336 b = apr_pcalloc(pool, sizeof(*b));
4338 apr_md5_init(&(b->md5_context));
4340 b->fs = fs;
4341 b->parent_pool = pool;
4342 b->pool = svn_pool_create(pool);
4343 b->rep_size = 0;
4344 b->noderev = noderev;
4346 /* Open the prototype rev file and seek to its end. */
4347 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
4348 fs, svn_fs_fs__id_txn_id(noderev->id),
4349 b->pool));
4351 b->file = file;
4352 b->rep_stream = svn_stream_from_aprfile(file, b->pool);
4354 SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
4356 /* Get the base for this delta. */
4357 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, b->pool));
4358 SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
4360 /* Write out the rep header. */
4361 if (base_rep)
4363 header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
4364 SVN_FILESIZE_T_FMT "\n",
4365 base_rep->revision, base_rep->offset,
4366 base_rep->size);
4368 else
4370 header = REP_DELTA "\n";
4372 SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
4373 b->pool));
4375 /* Now determine the offset of the actual svndiff data. */
4376 SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
4378 /* Prepare to write the svndiff data. */
4379 if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT)
4380 svn_txdelta_to_svndiff2(&wh, &whb, b->rep_stream, 1, pool);
4381 else
4382 svn_txdelta_to_svndiff2(&wh, &whb, b->rep_stream, 0, pool);
4384 b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
4386 *wb_p = b;
4388 return SVN_NO_ERROR;
4391 /* Close handler for the representation write stream. BATON is a
4392 rep_write_baton. Writes out a new node-rev that correctly
4393 references the representation we just finished writing. */
4394 static svn_error_t *
4395 rep_write_contents_close(void *baton)
4397 struct rep_write_baton *b = baton;
4398 representation_t *rep;
4399 apr_off_t offset;
4401 rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
4402 rep->offset = b->rep_offset;
4404 /* Close our delta stream so the last bits of svndiff are written
4405 out. */
4406 if (b->delta_stream)
4407 SVN_ERR(svn_stream_close(b->delta_stream));
4409 /* Determine the length of the svndiff data. */
4410 SVN_ERR(get_file_offset(&offset, b->file, b->pool));
4411 rep->size = offset - b->delta_start;
4413 /* Fill in the rest of the representation field. */
4414 rep->expanded_size = b->rep_size;
4415 rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
4416 rep->revision = SVN_INVALID_REVNUM;
4418 /* Finalize the MD5 checksum. */
4419 apr_md5_final(rep->checksum, &b->md5_context);
4421 /* Write out our cosmetic end marker. */
4422 SVN_ERR(svn_stream_printf(b->rep_stream, b->pool, "ENDREP\n"));
4424 b->noderev->data_rep = rep;
4426 /* Write out the new node-rev information. */
4427 SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
4428 b->pool));
4430 SVN_ERR(svn_io_file_close(b->file, b->pool));
4431 SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
4432 svn_pool_destroy(b->pool);
4434 return SVN_NO_ERROR;
4437 /* Store a writable stream in *CONTENTS_P that will receive all data
4438 written and store it as the file data representation referenced by
4439 NODEREV in filesystem FS. Perform temporary allocations in
4440 POOL. Only appropriate for file data, not props or directory
4441 contents. */
4442 static svn_error_t *
4443 set_representation(svn_stream_t **contents_p,
4444 svn_fs_t *fs,
4445 node_revision_t *noderev,
4446 apr_pool_t *pool)
4448 struct rep_write_baton *wb;
4450 if (! svn_fs_fs__id_txn_id(noderev->id))
4451 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4452 _("Attempted to write to non-transaction"));
4454 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
4456 *contents_p = svn_stream_create(wb, pool);
4457 svn_stream_set_write(*contents_p, rep_write_contents);
4458 svn_stream_set_close(*contents_p, rep_write_contents_close);
4460 return SVN_NO_ERROR;
4463 svn_error_t *
4464 svn_fs_fs__set_contents(svn_stream_t **stream,
4465 svn_fs_t *fs,
4466 node_revision_t *noderev,
4467 apr_pool_t *pool)
4469 if (noderev->kind != svn_node_file)
4470 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
4471 _("Can't set text contents of a directory"));
4473 return set_representation(stream, fs, noderev, pool);
4476 svn_error_t *
4477 svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
4478 svn_fs_t *fs,
4479 const svn_fs_id_t *old_idp,
4480 node_revision_t *new_noderev,
4481 const char *copy_id,
4482 const char *txn_id,
4483 apr_pool_t *pool)
4485 const svn_fs_id_t *id;
4487 if (! copy_id)
4488 copy_id = svn_fs_fs__id_copy_id(old_idp);
4489 id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
4490 txn_id, pool);
4492 new_noderev->id = id;
4494 if (! new_noderev->copyroot_path)
4496 new_noderev->copyroot_path = apr_pstrdup(pool,
4497 new_noderev->created_path);
4498 new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
4501 SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
4502 pool));
4504 *new_id_p = id;
4506 return SVN_NO_ERROR;
4509 svn_error_t *
4510 svn_fs_fs__set_proplist(svn_fs_t *fs,
4511 node_revision_t *noderev,
4512 apr_hash_t *proplist,
4513 apr_pool_t *pool)
4515 const char *filename = path_txn_node_props(fs, noderev->id, pool);
4516 apr_file_t *file;
4517 svn_stream_t *out;
4519 /* Dump the property list to the mutable property file. */
4520 SVN_ERR(svn_io_file_open(&file, filename,
4521 APR_WRITE | APR_CREATE | APR_TRUNCATE
4522 | APR_BUFFERED, APR_OS_DEFAULT, pool));
4523 out = svn_stream_from_aprfile(file, pool);
4524 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
4525 SVN_ERR(svn_io_file_close(file, pool));
4527 /* Mark the node-rev's prop rep as mutable, if not already done. */
4528 if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
4530 noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
4531 noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
4532 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
4535 return SVN_NO_ERROR;
4538 /* Read the 'current' file for filesystem FS and store the next
4539 available node id in *NODE_ID, and the next available copy id in
4540 *COPY_ID. Allocations are performed from POOL. */
4541 static svn_error_t *
4542 get_next_revision_ids(const char **node_id,
4543 const char **copy_id,
4544 svn_fs_t *fs,
4545 apr_pool_t *pool)
4547 char *buf;
4548 char *str, *last_str;
4550 SVN_ERR(read_current(svn_fs_fs__path_current(fs, pool), &buf, pool));
4552 str = apr_strtok(buf, " ", &last_str);
4553 if (! str)
4554 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4555 _("Corrupt current file"));
4557 str = apr_strtok(NULL, " ", &last_str);
4558 if (! str)
4559 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4560 _("Corrupt current file"));
4562 *node_id = apr_pstrdup(pool, str);
4564 str = apr_strtok(NULL, " ", &last_str);
4565 if (! str)
4566 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4567 _("Corrupt current file"));
4569 *copy_id = apr_pstrdup(pool, str);
4571 return SVN_NO_ERROR;
4574 /* This baton is used by the stream created for write_hash_rep. */
4575 struct write_hash_baton
4577 svn_stream_t *stream;
4579 apr_size_t size;
4581 struct apr_md5_ctx_t md5_context;
4584 /* The handler for the write_hash_rep stream. BATON is a
4585 write_hash_baton, DATA has the data to write and *LEN is the number
4586 of bytes to write. */
4587 static svn_error_t *
4588 write_hash_handler(void *baton,
4589 const char *data,
4590 apr_size_t *len)
4592 struct write_hash_baton *whb = baton;
4594 apr_md5_update(&whb->md5_context, data, *len);
4596 SVN_ERR(svn_stream_write(whb->stream, data, len));
4597 whb->size += *len;
4599 return SVN_NO_ERROR;
4602 /* Write out the hash HASH as a text representation to file FILE. In
4603 the process, record the total size of the dump in *SIZE, and the
4604 md5 digest in CHECKSUM. Perform temporary allocations in POOL. */
4605 static svn_error_t *
4606 write_hash_rep(svn_filesize_t *size,
4607 unsigned char checksum[APR_MD5_DIGESTSIZE],
4608 apr_file_t *file,
4609 apr_hash_t *hash,
4610 apr_pool_t *pool)
4612 svn_stream_t *stream;
4613 struct write_hash_baton *whb;
4615 whb = apr_pcalloc(pool, sizeof(*whb));
4617 whb->stream = svn_stream_from_aprfile(file, pool);
4618 whb->size = 0;
4619 apr_md5_init(&(whb->md5_context));
4621 stream = svn_stream_create(whb, pool);
4622 svn_stream_set_write(stream, write_hash_handler);
4624 SVN_ERR(svn_stream_printf(whb->stream, pool, "PLAIN\n"));
4626 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
4628 /* Store the results. */
4629 apr_md5_final(checksum, &whb->md5_context);
4630 *size = whb->size;
4632 SVN_ERR(svn_stream_printf(whb->stream, pool, "ENDREP\n"));
4634 return SVN_NO_ERROR;
4637 /* Copy a node-revision specified by id ID in fileystem FS from a
4638 transaction into the permanent rev-file FILE. Return the offset of
4639 the new node-revision in *OFFSET. If this is a directory, all
4640 children are copied as well. START_NODE_ID and START_COPY_ID are
4641 the first available node and copy ids for this filesystem, for older
4642 FS formats. Temporary allocations are from POOL. */
4643 static svn_error_t *
4644 write_final_rev(const svn_fs_id_t **new_id_p,
4645 apr_file_t *file,
4646 svn_revnum_t rev,
4647 svn_fs_t *fs,
4648 const svn_fs_id_t *id,
4649 const char *start_node_id,
4650 const char *start_copy_id,
4651 apr_pool_t *pool)
4653 node_revision_t *noderev;
4654 apr_off_t my_offset;
4655 char my_node_id_buf[MAX_KEY_SIZE + 2];
4656 char my_copy_id_buf[MAX_KEY_SIZE + 2];
4657 const svn_fs_id_t *new_id;
4658 const char *node_id, *copy_id, *my_node_id, *my_copy_id;
4659 fs_fs_data_t *ffd = fs->fsap_data;
4661 *new_id_p = NULL;
4663 /* Check to see if this is a transaction node. */
4664 if (! svn_fs_fs__id_txn_id(id))
4665 return SVN_NO_ERROR;
4667 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
4669 if (noderev->kind == svn_node_dir)
4671 apr_pool_t *subpool;
4672 apr_hash_t *entries, *str_entries;
4673 svn_fs_dirent_t *dirent;
4674 void *val;
4675 apr_hash_index_t *hi;
4677 /* This is a directory. Write out all the children first. */
4678 subpool = svn_pool_create(pool);
4680 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
4682 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
4684 svn_pool_clear(subpool);
4685 apr_hash_this(hi, NULL, NULL, &val);
4686 dirent = val;
4687 SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
4688 start_node_id, start_copy_id,
4689 subpool));
4690 if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
4691 dirent->id = svn_fs_fs__id_copy(new_id, pool);
4693 svn_pool_destroy(subpool);
4695 if (noderev->data_rep && noderev->data_rep->txn_id)
4697 /* Write out the contents of this directory as a text rep. */
4698 SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
4700 noderev->data_rep->txn_id = NULL;
4701 noderev->data_rep->revision = rev;
4702 SVN_ERR(get_file_offset(&noderev->data_rep->offset, file, pool));
4703 SVN_ERR(write_hash_rep(&noderev->data_rep->size,
4704 noderev->data_rep->checksum, file,
4705 str_entries, pool));
4706 noderev->data_rep->expanded_size = noderev->data_rep->size;
4709 else
4711 /* This is a file. We should make sure the data rep, if it
4712 exists in a "this" state, gets rewritten to our new revision
4713 num. */
4715 if (noderev->data_rep && noderev->data_rep->txn_id)
4717 noderev->data_rep->txn_id = NULL;
4718 noderev->data_rep->revision = rev;
4722 /* Fix up the property reps. */
4723 if (noderev->prop_rep && noderev->prop_rep->txn_id)
4725 apr_hash_t *proplist;
4727 SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
4728 SVN_ERR(get_file_offset(&noderev->prop_rep->offset, file, pool));
4729 SVN_ERR(write_hash_rep(&noderev->prop_rep->size,
4730 noderev->prop_rep->checksum, file,
4731 proplist, pool));
4733 noderev->prop_rep->txn_id = NULL;
4734 noderev->prop_rep->revision = rev;
4738 /* Convert our temporary ID into a permanent revision one. */
4739 SVN_ERR(get_file_offset(&my_offset, file, pool));
4741 node_id = svn_fs_fs__id_node_id(noderev->id);
4742 if (*node_id == '_')
4744 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
4745 my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
4746 else
4748 svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
4749 my_node_id = my_node_id_buf;
4752 else
4753 my_node_id = node_id;
4755 copy_id = svn_fs_fs__id_copy_id(noderev->id);
4756 if (*copy_id == '_')
4758 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
4759 my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
4760 else
4762 svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
4763 my_copy_id = my_copy_id_buf;
4766 else
4767 my_copy_id = copy_id;
4769 if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
4770 noderev->copyroot_rev = rev;
4772 new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
4773 pool);
4775 noderev->id = new_id;
4777 /* Write out our new node-revision. */
4778 SVN_ERR(write_noderev_txn(file, noderev,
4779 svn_fs_fs__fs_supports_mergeinfo(fs),
4780 pool));
4782 /* Return our ID that references the revision file. */
4783 *new_id_p = noderev->id;
4785 return SVN_NO_ERROR;
4788 /* Write the changed path info from transaction TXN_ID in filesystem
4789 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
4790 in the file of the beginning of this information. Perform
4791 temporary allocations in POOL. */
4792 static svn_error_t *
4793 write_final_changed_path_info(apr_off_t *offset_p,
4794 apr_file_t *file,
4795 svn_fs_t *fs,
4796 const char *txn_id,
4797 apr_pool_t *pool)
4799 const char *copyfrom;
4800 apr_hash_t *changed_paths, *copyfrom_cache = apr_hash_make(pool);
4801 apr_off_t offset;
4802 apr_hash_index_t *hi;
4803 apr_pool_t *iterpool = svn_pool_create(pool);
4805 SVN_ERR(get_file_offset(&offset, file, pool));
4807 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id,
4808 copyfrom_cache, pool));
4810 /* Iterate through the changed paths one at a time, and convert the
4811 temporary node-id into a permanent one for each change entry. */
4812 for (hi = apr_hash_first(pool, changed_paths); hi; hi = apr_hash_next(hi))
4814 node_revision_t *noderev;
4815 const svn_fs_id_t *id;
4816 svn_fs_path_change_t *change;
4817 const void *key;
4818 void *val;
4820 svn_pool_clear(iterpool);
4822 apr_hash_this(hi, &key, NULL, &val);
4823 change = val;
4825 id = change->node_rev_id;
4827 /* If this was a delete of a mutable node, then it is OK to
4828 leave the change entry pointing to the non-existent temporary
4829 node, since it will never be used. */
4830 if ((change->change_kind != svn_fs_path_change_delete) &&
4831 (! svn_fs_fs__id_txn_id(id)))
4833 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
4835 /* noderev has the permanent node-id at this point, so we just
4836 substitute it for the temporary one. */
4837 change->node_rev_id = noderev->id;
4840 /* Find the cached copyfrom information. */
4841 copyfrom = apr_hash_get(copyfrom_cache, key, APR_HASH_KEY_STRING);
4843 /* Write out the new entry into the final rev-file. */
4844 SVN_ERR(write_change_entry(file, key, change, copyfrom, iterpool));
4847 svn_pool_destroy(iterpool);
4849 *offset_p = offset;
4851 return SVN_NO_ERROR;
4855 svn_error_t *
4856 svn_fs_fs__dup_perms(const char *filename,
4857 const char *perms_reference,
4858 apr_pool_t *pool)
4860 #ifndef WIN32
4861 apr_status_t status;
4862 apr_finfo_t finfo;
4863 const char *filename_apr, *perms_reference_apr;
4865 SVN_ERR(svn_path_cstring_from_utf8(&filename_apr, filename, pool));
4866 SVN_ERR(svn_path_cstring_from_utf8(&perms_reference_apr, perms_reference,
4867 pool));
4869 status = apr_stat(&finfo, perms_reference_apr, APR_FINFO_PROT, pool);
4870 if (status)
4871 return svn_error_wrap_apr(status, _("Can't stat '%s'"),
4872 svn_path_local_style(perms_reference, pool));
4873 status = apr_file_perms_set(filename_apr, finfo.protection);
4874 if (status)
4875 return svn_error_wrap_apr(status, _("Can't chmod '%s'"),
4876 svn_path_local_style(filename, pool));
4877 #endif
4878 return SVN_NO_ERROR;
4882 svn_error_t *
4883 svn_fs_fs__move_into_place(const char *old_filename,
4884 const char *new_filename,
4885 const char *perms_reference,
4886 apr_pool_t *pool)
4888 svn_error_t *err;
4890 SVN_ERR(svn_fs_fs__dup_perms(old_filename, perms_reference, pool));
4892 /* Move the file into place. */
4893 err = svn_io_file_rename(old_filename, new_filename, pool);
4894 if (err && APR_STATUS_IS_EXDEV(err->apr_err))
4896 apr_file_t *file;
4898 /* Can't rename across devices; fall back to copying. */
4899 svn_error_clear(err);
4900 err = SVN_NO_ERROR;
4901 SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
4903 /* Flush the target of the copy to disk. */
4904 SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
4905 APR_OS_DEFAULT, pool));
4906 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4907 SVN_ERR(svn_io_file_close(file, pool));
4909 if (err)
4910 return err;
4912 #ifdef __linux__
4914 /* Linux has the unusual feature that fsync() on a file is not
4915 enough to ensure that a file's directory entries have been
4916 flushed to disk; you have to fsync the directory as well.
4917 On other operating systems, we'd only be asking for trouble
4918 by trying to open and fsync a directory. */
4919 const char *dirname;
4920 apr_file_t *file;
4922 dirname = svn_path_dirname(new_filename, pool);
4923 SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
4924 pool));
4925 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4926 SVN_ERR(svn_io_file_close(file, pool));
4928 #endif
4930 return SVN_NO_ERROR;
4933 /* Atomically update the current file to hold the specifed REV,
4934 NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are
4935 ignored and may be NULL if the FS format does not use them.)
4936 Perform temporary allocations in POOL. */
4937 static svn_error_t *
4938 write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
4939 const char *next_copy_id, apr_pool_t *pool)
4941 char *buf;
4942 const char *tmp_name, *name;
4943 apr_file_t *file;
4944 fs_fs_data_t *ffd = fs->fsap_data;
4946 /* Now we can just write out this line. */
4947 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
4948 buf = apr_psprintf(pool, "%ld\n", rev);
4949 else
4950 buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
4952 name = svn_fs_fs__path_current(fs, pool);
4953 SVN_ERR(svn_io_open_unique_file2(&file, &tmp_name, name, ".tmp",
4954 svn_io_file_del_none, pool));
4956 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
4958 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4960 SVN_ERR(svn_io_file_close(file, pool));
4962 SVN_ERR(svn_fs_fs__move_into_place(tmp_name, name, name, pool));
4964 return SVN_NO_ERROR;
4967 /* Update the current file to hold the correct next node and copy_ids
4968 from transaction TXN_ID in filesystem FS. The current revision is
4969 set to REV. Perform temporary allocations in POOL. */
4970 static svn_error_t *
4971 write_final_current(svn_fs_t *fs,
4972 const char *txn_id,
4973 svn_revnum_t rev,
4974 const char *start_node_id,
4975 const char *start_copy_id,
4976 apr_pool_t *pool)
4978 const char *txn_node_id, *txn_copy_id;
4979 char new_node_id[MAX_KEY_SIZE + 2];
4980 char new_copy_id[MAX_KEY_SIZE + 2];
4981 fs_fs_data_t *ffd = fs->fsap_data;
4983 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
4984 return write_current(fs, rev, NULL, NULL, pool);
4986 /* To find the next available ids, we add the id that used to be in
4987 the current file, to the next ids from the transaction file. */
4988 SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
4990 svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
4991 svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
4993 return write_current(fs, rev, new_node_id, new_copy_id, pool);
4996 /* Verify that the user registed with FS has all the locks necessary to
4997 permit all the changes associate with TXN_NAME.
4998 The FS write lock is assumed to be held by the caller. */
4999 static svn_error_t *
5000 verify_locks(svn_fs_t *fs,
5001 const char *txn_name,
5002 apr_pool_t *pool)
5004 apr_pool_t *subpool = svn_pool_create(pool);
5005 apr_hash_t *changes;
5006 apr_hash_index_t *hi;
5007 apr_array_header_t *changed_paths;
5008 svn_stringbuf_t *last_recursed = NULL;
5009 int i;
5011 /* Fetch the changes for this transaction. */
5012 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, NULL, pool));
5014 /* Make an array of the changed paths, and sort them depth-first-ily. */
5015 changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
5016 sizeof(const char *));
5017 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
5019 const void *key;
5020 apr_hash_this(hi, &key, NULL, NULL);
5021 APR_ARRAY_PUSH(changed_paths, const char *) = key;
5023 qsort(changed_paths->elts, changed_paths->nelts,
5024 changed_paths->elt_size, svn_sort_compare_paths);
5026 /* Now, traverse the array of changed paths, verify locks. Note
5027 that if we need to do a recursive verification a path, we'll skip
5028 over children of that path when we get to them. */
5029 for (i = 0; i < changed_paths->nelts; i++)
5031 const char *path;
5032 svn_fs_path_change_t *change;
5033 svn_boolean_t recurse = TRUE;
5035 svn_pool_clear(subpool);
5036 path = APR_ARRAY_IDX(changed_paths, i, const char *);
5038 /* If this path has already been verified as part of a recursive
5039 check of one of its parents, no need to do it again. */
5040 if (last_recursed
5041 && svn_path_is_child(last_recursed->data, path, subpool))
5042 continue;
5044 /* Fetch the change associated with our path. */
5045 change = apr_hash_get(changes, path, APR_HASH_KEY_STRING);
5047 /* What does it mean to succeed at lock verification for a given
5048 path? For an existing file or directory getting modified
5049 (text, props), it means we hold the lock on the file or
5050 directory. For paths being added or removed, we need to hold
5051 the locks for that path and any children of that path.
5053 WHEW! We have no reliable way to determine the node kind
5054 of deleted items, but fortunately we are going to do a
5055 recursive check on deleted paths regardless of their kind. */
5056 if (change->change_kind == svn_fs_path_change_modify)
5057 recurse = FALSE;
5058 SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
5059 subpool));
5061 /* If we just did a recursive check, remember the path we
5062 checked (so children can be skipped). */
5063 if (recurse)
5065 if (! last_recursed)
5066 last_recursed = svn_stringbuf_create(path, pool);
5067 else
5068 svn_stringbuf_set(last_recursed, path);
5071 svn_pool_destroy(subpool);
5072 return SVN_NO_ERROR;
5075 /* Baton used for commit_body below. */
5076 struct commit_baton {
5077 svn_revnum_t *new_rev_p;
5078 svn_fs_t *fs;
5079 svn_fs_txn_t *txn;
5082 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
5083 This implements the svn_fs_fs__with_write_lock() 'body' callback
5084 type. BATON is a 'struct commit_baton *'. */
5085 static svn_error_t *
5086 commit_body(void *baton, apr_pool_t *pool)
5088 struct commit_baton *cb = baton;
5089 fs_fs_data_t *ffd = cb->fs->fsap_data;
5090 const char *old_rev_filename, *rev_filename, *proto_filename;
5091 const char *revprop_filename, *final_revprop;
5092 const svn_fs_id_t *root_id, *new_root_id;
5093 const char *start_node_id = NULL, *start_copy_id = NULL;
5094 svn_revnum_t old_rev, new_rev;
5095 apr_file_t *proto_file;
5096 void *proto_file_lockcookie;
5097 apr_off_t changed_path_offset;
5098 char *buf;
5099 apr_hash_t *txnprops;
5100 svn_string_t date;
5102 /* Get the current youngest revision. */
5103 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
5105 /* Check to make sure this transaction is based off the most recent
5106 revision. */
5107 if (cb->txn->base_rev != old_rev)
5108 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
5109 _("Transaction out of date"));
5111 /* Locks may have been added (or stolen) between the calling of
5112 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
5113 to re-examine every changed-path in the txn and re-verify all
5114 discovered locks. */
5115 SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
5117 /* Get the next node_id and copy_id to use. */
5118 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
5119 SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
5120 pool));
5122 /* We are going to be one better than this puny old revision. */
5123 new_rev = old_rev + 1;
5125 /* Get a write handle on the proto revision file. */
5126 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
5127 cb->fs, cb->txn->id, pool));
5129 /* Write out all the node-revisions and directory contents. */
5130 root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
5131 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
5132 start_node_id, start_copy_id,
5133 pool));
5135 /* Write the changed-path information. */
5136 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
5137 cb->fs, cb->txn->id, pool));
5139 /* Write the final line. */
5140 buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
5141 svn_fs_fs__id_offset(new_root_id),
5142 changed_path_offset);
5143 SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
5144 pool));
5145 SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
5146 SVN_ERR(svn_io_file_close(proto_file, pool));
5148 /* We don't unlock the prototype revision file immediately to avoid a
5149 race with another caller writing to the prototype revision file
5150 before we commit it. */
5152 /* Remove any temporary txn props representing 'flags'. */
5153 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
5154 if (txnprops)
5156 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
5157 svn_prop_t prop;
5158 prop.value = NULL;
5160 if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_OOD,
5161 APR_HASH_KEY_STRING))
5163 prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
5164 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
5167 if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS,
5168 APR_HASH_KEY_STRING))
5170 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
5171 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
5174 if (! apr_is_empty_array(props))
5175 SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, props, pool));
5178 /* Create the shard for the rev and revprop file, if we're sharding and
5179 this is the first revision of a new shard. We don't care if this
5180 fails because the shard already existed for some reason. */
5181 if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
5183 svn_error_t *err;
5184 err = svn_io_dir_make(path_rev_shard(cb->fs, new_rev, pool),
5185 APR_OS_DEFAULT, pool);
5186 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5187 svn_error_clear(err);
5188 else
5189 SVN_ERR(err);
5190 err = svn_io_dir_make(path_revprops_shard(cb->fs, new_rev, pool),
5191 APR_OS_DEFAULT, pool);
5192 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5193 svn_error_clear(err);
5194 else
5195 SVN_ERR(err);
5198 /* Move the finished rev file into place. */
5199 old_rev_filename = svn_fs_fs__path_rev(cb->fs, old_rev, pool);
5200 rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
5201 proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
5202 SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
5203 old_rev_filename, pool));
5205 /* Now that we've moved the prototype revision file out of the way,
5206 we can unlock it (since further attempts to write to the file
5207 will fail as it no longer exists). We must do this so that we can
5208 remove the transaction directory later. */
5209 SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
5211 /* Update commit time to ensure that svn:date revprops remain ordered. */
5212 date.data = svn_time_to_cstring(apr_time_now(), pool);
5213 date.len = strlen(date.data);
5215 SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
5216 &date, pool));
5218 /* Move the revprops file into place. */
5219 revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
5220 final_revprop = path_revprops(cb->fs, new_rev, pool);
5221 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
5222 old_rev_filename, pool));
5224 /* Update the 'current' file. */
5225 SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
5226 start_copy_id, pool));
5227 ffd->youngest_rev_cache = new_rev;
5229 /* Remove this transaction directory. */
5230 SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
5232 *cb->new_rev_p = new_rev;
5234 return SVN_NO_ERROR;
5237 svn_error_t *
5238 svn_fs_fs__commit(svn_revnum_t *new_rev_p,
5239 svn_fs_t *fs,
5240 svn_fs_txn_t *txn,
5241 apr_pool_t *pool)
5243 struct commit_baton cb;
5245 cb.new_rev_p = new_rev_p;
5246 cb.fs = fs;
5247 cb.txn = txn;
5248 return svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool);
5251 svn_error_t *
5252 svn_fs_fs__reserve_copy_id(const char **copy_id_p,
5253 svn_fs_t *fs,
5254 const char *txn_id,
5255 apr_pool_t *pool)
5257 const char *cur_node_id, *cur_copy_id;
5258 char *copy_id;
5259 apr_size_t len;
5261 /* First read in the current next-ids file. */
5262 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
5264 copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
5266 len = strlen(cur_copy_id);
5267 svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
5269 SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
5271 *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, NULL);
5273 return SVN_NO_ERROR;
5276 /* Write out the zeroth revision for filesystem FS. */
5277 static svn_error_t *
5278 write_revision_zero(svn_fs_t *fs)
5280 apr_hash_t *proplist;
5281 svn_string_t date;
5283 /* Write out a rev file for revision 0. */
5284 SVN_ERR(svn_io_file_create(svn_fs_fs__path_rev(fs, 0, fs->pool),
5285 "PLAIN\nEND\nENDREP\n"
5286 "id: 0.0.r0/17\n"
5287 "type: dir\n"
5288 "count: 0\n"
5289 "text: 0 0 4 4 "
5290 "2d2977d1c96f487abe4a1e202dd03b4e\n"
5291 "cpath: /\n"
5292 "\n\n17 107\n", fs->pool));
5294 /* Set a date on revision 0. */
5295 date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
5296 date.len = strlen(date.data);
5297 proplist = apr_hash_make(fs->pool);
5298 apr_hash_set(proplist, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING, &date);
5299 return svn_fs_fs__set_revision_proplist(fs, 0, proplist, fs->pool);
5302 svn_error_t *
5303 svn_fs_fs__create(svn_fs_t *fs,
5304 const char *path,
5305 apr_pool_t *pool)
5307 int format = SVN_FS_FS__FORMAT_NUMBER;
5308 fs_fs_data_t *ffd = fs->fsap_data;
5310 fs->path = apr_pstrdup(pool, path);
5311 /* See if we had an explicitly requested pre-1.4- or pre-1.5-compatible. */
5312 if (fs->config)
5314 if (apr_hash_get(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE,
5315 APR_HASH_KEY_STRING))
5316 format = 1;
5317 else if (apr_hash_get(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE,
5318 APR_HASH_KEY_STRING))
5319 format = 2;
5321 ffd->format = format;
5323 /* Override the default linear layout if this is a new-enough format. */
5324 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
5325 ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
5327 if (ffd->max_files_per_dir)
5329 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool),
5330 pool));
5331 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
5332 pool));
5334 else
5336 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_REVS_DIR,
5337 pool),
5338 pool));
5339 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path,
5340 PATH_REVPROPS_DIR,
5341 pool),
5342 pool));
5344 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_TXNS_DIR,
5345 pool),
5346 pool));
5348 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
5349 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_TXN_PROTOS_DIR,
5350 pool),
5351 pool));
5353 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
5354 (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
5355 ? "0\n" : "0 1 1\n"),
5356 pool));
5357 SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
5358 SVN_ERR(svn_fs_fs__set_uuid(fs, svn_uuid_generate(pool), pool));
5360 SVN_ERR(write_revision_zero(fs));
5362 /* Create the txn-current file if the repository supports
5363 the transaction sequence file. */
5364 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
5366 SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
5367 "0\n", pool));
5368 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
5369 "", pool));
5372 /* This filesystem is ready. Stamp it with a format number. */
5373 SVN_ERR(write_format(path_format(fs, pool),
5374 ffd->format, ffd->max_files_per_dir, FALSE, pool));
5376 ffd->youngest_rev_cache = 0;
5377 return SVN_NO_ERROR;
5380 /* Part of the recovery procedure. Return the largest revision *REV in
5381 filesystem FS. Use POOL for temporary allocation. */
5382 static svn_error_t *
5383 recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
5385 /* Discovering the largest revision in the filesystem would be an
5386 expensive operation if we did a readdir() or searched linearly,
5387 so we'll do a form of binary search. left is a revision that we
5388 know exists, right a revision that we know does not exist. */
5389 apr_pool_t *iterpool;
5390 svn_revnum_t left, right = 1;
5392 iterpool = svn_pool_create(pool);
5393 /* Keep doubling right, until we find a revision that doesn't exist. */
5394 while (1)
5396 svn_node_kind_t kind;
5397 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs, right, iterpool),
5398 &kind, iterpool));
5399 svn_pool_clear(iterpool);
5401 if (kind == svn_node_none)
5402 break;
5404 right <<= 1;
5407 left = right >> 1;
5409 /* We know that left exists and right doesn't. Do a normal bsearch to find
5410 the last revision. */
5411 while (left + 1 < right)
5413 svn_revnum_t probe = left + ((right - left) / 2);
5414 svn_node_kind_t kind;
5416 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs, probe, iterpool),
5417 &kind, iterpool));
5418 svn_pool_clear(iterpool);
5420 if (kind == svn_node_none)
5421 right = probe;
5422 else
5423 left = probe;
5426 svn_pool_destroy(iterpool);
5428 /* left is now the largest revision that exists. */
5429 *rev = left;
5430 return SVN_NO_ERROR;
5433 /* A baton for reading a fixed amount from an open file. For
5434 recover_find_max_ids() below. */
5435 struct recover_read_from_file_baton
5437 apr_file_t *file;
5438 apr_pool_t *pool;
5439 apr_size_t remaining;
5442 /* A stream read handler used by recover_find_max_ids() below.
5443 Read and return at most BATON->REMAINING bytes from the stream,
5444 returning nothing after that to indicate EOF. */
5445 static svn_error_t *
5446 read_handler_recover(void *baton, char *buffer, apr_size_t *len)
5448 struct recover_read_from_file_baton *b = baton;
5449 apr_size_t bytes_to_read = *len;
5451 if (b->remaining == 0)
5453 /* Return a successful read of zero bytes to signal EOF. */
5454 *len = 0;
5455 return SVN_NO_ERROR;
5458 if (bytes_to_read > b->remaining)
5459 bytes_to_read = b->remaining;
5460 b->remaining -= bytes_to_read;
5462 return svn_io_file_read_full(b->file, buffer, bytes_to_read, len, b->pool);
5465 /* Part of the recovery procedure. Read the directory noderev at offset
5466 OFFSET of file REV_FILE (the revision file of revision REV of
5467 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
5468 and copy-id of that node, if greater than the current value stored
5469 in either. Recurse into any child directories that were modified in
5470 this revision.
5472 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
5474 Perform temporary allocation in POOL. */
5475 static svn_error_t *
5476 recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
5477 apr_file_t *rev_file, apr_off_t offset,
5478 char *max_node_id, char *max_copy_id,
5479 apr_pool_t *pool)
5481 apr_hash_t *headers;
5482 char *value;
5483 node_revision_t noderev;
5484 struct rep_args *ra;
5485 struct recover_read_from_file_baton baton;
5486 svn_stream_t *stream;
5487 apr_hash_t *entries;
5488 apr_hash_index_t *hi;
5489 apr_pool_t *iterpool;
5491 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
5492 SVN_ERR(read_header_block(&headers, rev_file, pool));
5494 /* We're going to populate a skeletal noderev - just the id and data_rep. */
5495 value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
5496 noderev.id = svn_fs_fs__id_parse(value, strlen(value), pool);
5498 /* Check that this is a directory. It should be. */
5499 value = apr_hash_get(headers, HEADER_TYPE, APR_HASH_KEY_STRING);
5500 if (value == NULL || strcmp(value, KIND_DIR) != 0)
5501 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5502 _("Recovery encountered a non-directory node"));
5504 /* Get the data location. No data location indicates an empty directory. */
5505 value = apr_hash_get(headers, HEADER_TEXT, APR_HASH_KEY_STRING);
5506 if (!value)
5507 return SVN_NO_ERROR;
5508 SVN_ERR(read_rep_offsets(&noderev.data_rep, value, NULL, FALSE, pool));
5510 /* If the directory's data representation wasn't changed in this revision,
5511 we've already scanned the directory's contents for noderevs, so we don't
5512 need to again. This will occur if a property is changed on a directory
5513 without changing the directory's contents. */
5514 if (noderev.data_rep->revision != rev)
5515 return SVN_NO_ERROR;
5517 /* We could use get_dir_contents(), but this is much cheaper. It does
5518 rely on directory entries being stored as PLAIN reps, though. */
5519 offset = noderev.data_rep->offset;
5520 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
5521 SVN_ERR(read_rep_line(&ra, rev_file, pool));
5522 if (ra->is_delta)
5523 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5524 _("Recovery encountered a deltified directory "
5525 "representation"));
5527 /* Now create a stream that's allowed to read only as much data as is
5528 stored in the representation. */
5529 baton.file = rev_file;
5530 baton.pool = pool;
5531 baton.remaining = noderev.data_rep->expanded_size;
5532 stream = svn_stream_create(&baton, pool);
5533 svn_stream_set_read(stream, read_handler_recover);
5535 /* Now read the entries from that stream. */
5536 entries = apr_hash_make(pool);
5537 SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
5538 SVN_ERR(svn_stream_close(stream));
5540 /* Now check each of the entries in our directory to find new node and
5541 copy ids, and recurse into new subdirectories. */
5542 iterpool = svn_pool_create(pool);
5543 for (hi = apr_hash_first(NULL, entries); hi; hi = apr_hash_next(hi))
5545 void *val;
5546 char *str_val;
5547 char *str, *last_str;
5548 svn_node_kind_t kind;
5549 svn_fs_id_t *id;
5550 const char *node_id, *copy_id;
5551 apr_off_t child_dir_offset;
5553 svn_pool_clear(iterpool);
5555 apr_hash_this(hi, NULL, NULL, &val);
5556 str_val = apr_pstrdup(iterpool, *((char **)val));
5558 str = apr_strtok(str_val, " ", &last_str);
5559 if (str == NULL)
5560 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5561 _("Directory entry corrupt"));
5563 if (strcmp(str, KIND_FILE) == 0)
5564 kind = svn_node_file;
5565 else if (strcmp(str, KIND_DIR) == 0)
5566 kind = svn_node_dir;
5567 else
5569 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5570 _("Directory entry corrupt"));
5573 str = apr_strtok(NULL, " ", &last_str);
5574 if (str == NULL)
5575 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5576 _("Directory entry corrupt"));
5578 id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
5580 if (svn_fs_fs__id_rev(id) != rev)
5582 /* If the node wasn't modified in this revision, we've already
5583 checked the node and copy id. */
5584 continue;
5587 node_id = svn_fs_fs__id_node_id(id);
5588 copy_id = svn_fs_fs__id_copy_id(id);
5590 if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
5591 strcpy(max_node_id, node_id);
5592 if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
5593 strcpy(max_copy_id, copy_id);
5595 if (kind == svn_node_file)
5596 continue;
5598 child_dir_offset = svn_fs_fs__id_offset(id);
5599 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
5600 max_node_id, max_copy_id, iterpool));
5602 svn_pool_destroy(iterpool);
5604 return SVN_NO_ERROR;
5607 /* Baton used for recover_body below. */
5608 struct recover_baton {
5609 svn_fs_t *fs;
5610 svn_cancel_func_t cancel_func;
5611 void *cancel_baton;
5614 /* The work-horse for svn_fs_fs__recover, called with the FS
5615 write lock. This implements the svn_fs_fs__with_write_lock()
5616 'body' callback type. BATON is a 'struct recover_baton *'. */
5617 static svn_error_t *
5618 recover_body(void *baton, apr_pool_t *pool)
5620 struct recover_baton *b = baton;
5621 svn_fs_t *fs = b->fs;
5622 fs_fs_data_t *ffd = fs->fsap_data;
5623 svn_revnum_t max_rev;
5624 char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
5625 char *next_node_id = NULL, *next_copy_id = NULL;
5626 svn_revnum_t youngest_rev;
5627 svn_node_kind_t youngest_revprops_kind;
5629 /* First, we need to know the largest revision in the filesystem. */
5630 SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
5632 /* Get the expected youngest revision */
5633 SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
5635 /* Policy note:
5637 Since the revprops file is written after the revs file, the true
5638 maximum available revision is the youngest one for which both are
5639 present. That's probably the same as the max_rev we just found,
5640 but if it's not, we could, in theory, repeatedly decrement
5641 max_rev until we find a revision that has both a revs and
5642 revprops file, then write db/current with that.
5644 But we choose not to. If a repository is so corrupt that it's
5645 missing at least one revprops file, we shouldn't assume that the
5646 youngest revision for which both the revs and revprops files are
5647 present is healthy. In other words, we're willing to recover
5648 from a missing or out-of-date db/current file, because db/current
5649 is truly redundant -- it's basically a cache so we don't have to
5650 find max_rev each time, albeit a cache with unusual semantics,
5651 since it also officially defines when a revision goes live. But
5652 if we're missing more than the cache, it's time to back out and
5653 let the admin reconstruct things by hand: correctness at that
5654 point may depend on external things like checking a commit email
5655 list, looking in particular working copies, etc.
5657 This policy matches well with a typical naive backup scenario.
5658 Say you're rsyncing your FSFS repository nightly to the same
5659 location. Once revs and revprops are written, you've got the
5660 maximum rev; if the backup should bomb before db/current is
5661 written, then db/current could stay arbitrarily out-of-date, but
5662 we can still recover. It's a small window, but we might as well
5663 do what we can. */
5665 /* Even if db/current were missing, it would be created with 0 by
5666 get_youngest(), so this conditional remains valid. */
5667 if (youngest_rev > max_rev)
5668 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5669 _("Expected current rev to be <= %ld "
5670 "but found %ld"), max_rev, youngest_rev);
5672 /* We only need to search for maximum IDs for old FS formats which
5673 se global ID counters. */
5674 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
5676 /* Next we need to find the maximum node id and copy id in use across the
5677 filesystem. Unfortunately, the only way we can get this information
5678 is to scan all the noderevs of all the revisions and keep track as
5679 we go along. */
5680 svn_revnum_t rev;
5681 apr_pool_t *iterpool = svn_pool_create(pool);
5682 char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
5683 apr_size_t len;
5685 for (rev = 0; rev <= max_rev; rev++)
5687 apr_file_t *rev_file;
5688 apr_off_t root_offset;
5690 svn_pool_clear(iterpool);
5692 if (b->cancel_func)
5693 SVN_ERR(b->cancel_func(b->cancel_baton));
5695 SVN_ERR(svn_io_file_open(&rev_file,
5696 svn_fs_fs__path_rev(fs, rev, iterpool),
5697 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
5698 iterpool));
5699 SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
5700 iterpool));
5701 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
5702 max_node_id, max_copy_id, iterpool));
5704 svn_pool_destroy(iterpool);
5706 /* Now that we finally have the maximum revision, node-id and copy-id, we
5707 can bump the two ids to get the next of each. */
5708 len = strlen(max_node_id);
5709 svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
5710 next_node_id = next_node_id_buf;
5711 len = strlen(max_copy_id);
5712 svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
5713 next_copy_id = next_copy_id_buf;
5716 /* Before setting current, verify that there is a revprops file
5717 for the youngest revision. (Issue #2992) */
5718 SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
5719 &youngest_revprops_kind, pool));
5720 if (youngest_revprops_kind == svn_node_none)
5721 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5722 _("Revision %ld has a revs file but no "
5723 "revprops file"),
5724 max_rev);
5725 else if (youngest_revprops_kind != svn_node_file)
5726 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5727 _("Revision %ld has a non-file where its "
5728 "revprops file should be"),
5729 max_rev);
5731 /* Now store the discovered youngest revision, and the next IDs if
5732 relevant, in a new current file. */
5733 SVN_ERR(write_current(fs, max_rev, next_node_id, next_copy_id, pool));
5735 return SVN_NO_ERROR;
5738 /* This implements the fs_library_vtable_t.recover() API. */
5739 svn_error_t *
5740 svn_fs_fs__recover(svn_fs_t *fs,
5741 svn_cancel_func_t cancel_func, void *cancel_baton,
5742 apr_pool_t *pool)
5744 struct recover_baton b;
5746 /* We have no way to take out an exclusive lock in FSFS, so we're
5747 restricted as to the types of recovery we can do. Luckily,
5748 we just want to recreate the current file, and we can do that just
5749 by blocking other writers. */
5750 b.fs = fs;
5751 b.cancel_func = cancel_func;
5752 b.cancel_baton = cancel_baton;
5753 return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
5756 svn_error_t *
5757 svn_fs_fs__get_uuid(svn_fs_t *fs,
5758 const char **uuid_p,
5759 apr_pool_t *pool)
5761 fs_fs_data_t *ffd = fs->fsap_data;
5763 *uuid_p = apr_pstrdup(pool, ffd->uuid);
5764 return SVN_NO_ERROR;
5767 svn_error_t *
5768 svn_fs_fs__set_uuid(svn_fs_t *fs,
5769 const char *uuid,
5770 apr_pool_t *pool)
5772 apr_file_t *uuid_file;
5773 const char *tmp_path;
5774 const char *uuid_path = path_uuid(fs, pool);
5775 fs_fs_data_t *ffd = fs->fsap_data;
5777 SVN_ERR(svn_io_open_unique_file2(&uuid_file, &tmp_path, uuid_path,
5778 ".tmp", svn_io_file_del_none, pool));
5780 if (! uuid)
5781 uuid = svn_uuid_generate(pool);
5783 SVN_ERR(svn_io_file_write_full(uuid_file, uuid, strlen(uuid), NULL,
5784 pool));
5785 SVN_ERR(svn_io_file_write_full(uuid_file, "\n", 1, NULL, pool));
5787 SVN_ERR(svn_io_file_close(uuid_file, pool));
5789 /* We use the permissions of the 'current' file, because the 'uuid'
5790 file does not exist during repository creation. */
5791 SVN_ERR(svn_fs_fs__move_into_place(tmp_path, uuid_path,
5792 svn_fs_fs__path_current(fs, pool), pool));
5794 ffd->uuid = apr_pstrdup(fs->pool, uuid);
5796 return SVN_NO_ERROR;
5799 /** Node origin lazy cache. */
5801 /* If directory PATH does not exist, create it and give it the same
5802 permissions as FS->path.*/
5803 svn_error_t *
5804 svn_fs_fs__ensure_dir_exists(const char *path,
5805 svn_fs_t *fs,
5806 apr_pool_t *pool)
5808 svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
5809 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5811 svn_error_clear(err);
5812 return SVN_NO_ERROR;
5814 SVN_ERR(err);
5816 /* We successfully created a new directory. Dup the permissions
5817 from FS->path. */
5818 SVN_ERR(svn_fs_fs__dup_perms(path, fs->path, pool));
5820 return SVN_NO_ERROR;
5823 /* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
5824 'svn_string_t *' node revision IDs. Use POOL for allocations. */
5825 static svn_error_t *
5826 get_node_origins_from_file(svn_fs_t *fs,
5827 apr_hash_t **node_origins,
5828 const char *node_origins_file,
5829 apr_pool_t *pool)
5831 apr_file_t *fd;
5832 svn_error_t *err;
5833 svn_stream_t *stream;
5835 *node_origins = NULL;
5836 err = svn_io_file_open(&fd, node_origins_file,
5837 APR_READ, APR_OS_DEFAULT, pool);
5838 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
5840 svn_error_clear(err);
5841 return SVN_NO_ERROR;
5843 SVN_ERR(err);
5845 stream = svn_stream_from_aprfile2(fd, FALSE, pool);
5846 *node_origins = apr_hash_make(pool);
5847 SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
5848 return svn_stream_close(stream);
5851 svn_error_t *
5852 svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
5853 svn_fs_t *fs,
5854 const char *node_id,
5855 apr_pool_t *pool)
5857 apr_hash_t *node_origins;
5859 *origin_id = NULL;
5860 SVN_ERR(get_node_origins_from_file(fs, &node_origins,
5861 path_node_origin(fs, node_id, pool),
5862 pool));
5863 if (node_origins)
5865 svn_string_t *origin_id_str =
5866 apr_hash_get(node_origins, node_id, APR_HASH_KEY_STRING);
5867 if (origin_id_str)
5868 *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
5869 origin_id_str->len, pool);
5871 return SVN_NO_ERROR;
5875 /* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID
5876 pair and adds it to the NODE_ORIGINS_PATH file. */
5877 static svn_error_t *
5878 set_node_origins_for_file(svn_fs_t *fs,
5879 const char *node_origins_path,
5880 const char *node_id,
5881 svn_string_t *node_rev_id,
5882 apr_pool_t *pool)
5884 apr_file_t *fd;
5885 const char *path_tmp;
5886 svn_stream_t *stream;
5887 apr_hash_t *origins_hash;
5888 svn_string_t *old_node_rev_id;
5890 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_join(fs->path,
5891 PATH_NODE_ORIGINS_DIR,
5892 pool),
5893 fs, pool));
5895 /* Read the previously existing origins (if any), and merge our
5896 update with it. */
5897 SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
5898 node_origins_path, pool));
5899 if (! origins_hash)
5900 origins_hash = apr_hash_make(pool);
5902 old_node_rev_id = apr_hash_get(origins_hash, node_id, APR_HASH_KEY_STRING);
5904 if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
5905 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5906 _("Node origin for '%s' exists with a different "
5907 "value (%s) than what we were about to store "
5908 "(%s)"),
5909 node_id, old_node_rev_id->data, node_rev_id->data);
5911 apr_hash_set(origins_hash, node_id, APR_HASH_KEY_STRING, node_rev_id);
5913 /* Sure, there's a race condition here. Two processes could be
5914 trying to add different cache elements to the same file at the
5915 same time, and the entries added by the first one to write will
5916 be lost. But this is just a cache of reconstructible data, so
5917 we'll accept this problem in return for not having to deal with
5918 locking overhead. */
5920 /* Create a temporary file, write out our hash, and close the file. */
5921 SVN_ERR(svn_io_open_unique_file2(&fd, &path_tmp, node_origins_path, ".tmp",
5922 svn_io_file_del_none, pool));
5923 stream = svn_stream_from_aprfile2(fd, FALSE, pool);
5924 SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
5925 SVN_ERR(svn_stream_close(stream));
5927 /* Rename the temp file as the real destination */
5928 SVN_ERR(svn_io_file_rename(path_tmp, node_origins_path, pool));
5930 return SVN_NO_ERROR;
5934 svn_error_t *
5935 svn_fs_fs__set_node_origin(svn_fs_t *fs,
5936 const char *node_id,
5937 const svn_fs_id_t *node_rev_id,
5938 apr_pool_t *pool)
5940 svn_error_t *err;
5941 const char *filename = path_node_origin(fs, node_id, pool);
5943 err = set_node_origins_for_file(fs, filename,
5944 node_id,
5945 svn_fs_fs__id_unparse(node_rev_id, pool),
5946 pool);
5947 if (err && APR_STATUS_IS_EACCES(err->apr_err))
5949 /* It's just a cache; stop trying if I can't write. */
5950 svn_error_clear(err);
5951 err = NULL;
5953 return err;
5957 svn_error_t *
5958 svn_fs_fs__list_transactions(apr_array_header_t **names_p,
5959 svn_fs_t *fs,
5960 apr_pool_t *pool)
5962 const char *txn_dir;
5963 apr_hash_t *dirents;
5964 apr_hash_index_t *hi;
5965 apr_array_header_t *names;
5966 apr_size_t ext_len = strlen(PATH_EXT_TXN);
5968 names = apr_array_make(pool, 1, sizeof(const char *));
5970 /* Get the transactions directory. */
5971 txn_dir = svn_path_join(fs->path, PATH_TXNS_DIR, pool);
5973 /* Now find a listing of this directory. */
5974 SVN_ERR(svn_io_get_dirents2(&dirents, txn_dir, pool));
5976 /* Loop through all the entries and return anything that ends with '.txn'. */
5977 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
5979 const void *key;
5980 const char *name, *id;
5981 apr_ssize_t klen;
5983 apr_hash_this(hi, &key, &klen, NULL);
5984 name = key;
5986 /* The name must end with ".txn" to be considered a transaction. */
5987 if ((apr_size_t) klen <= ext_len
5988 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
5989 continue;
5991 /* Truncate the ".txn" extension and store the ID. */
5992 id = apr_pstrndup(pool, name, strlen(name) - ext_len);
5993 APR_ARRAY_PUSH(names, const char *) = id;
5996 *names_p = names;
5998 return SVN_NO_ERROR;
6001 svn_error_t *
6002 svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
6003 svn_fs_t *fs,
6004 const char *name,
6005 apr_pool_t *pool)
6007 svn_fs_txn_t *txn;
6008 svn_node_kind_t kind;
6009 transaction_t *local_txn;
6011 /* First check to see if the directory exists. */
6012 SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
6014 /* Did we find it? */
6015 if (kind != svn_node_dir)
6016 return svn_error_create(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
6017 _("No such transaction"));
6019 txn = apr_pcalloc(pool, sizeof(*txn));
6021 /* Read in the root node of this transaction. */
6022 txn->id = apr_pstrdup(pool, name);
6023 txn->fs = fs;
6025 SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
6027 txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
6029 txn->vtable = &txn_vtable;
6030 *txn_p = txn;
6032 return SVN_NO_ERROR;
6035 svn_error_t *
6036 svn_fs_fs__txn_proplist(apr_hash_t **table_p,
6037 svn_fs_txn_t *txn,
6038 apr_pool_t *pool)
6040 apr_hash_t *proplist = apr_hash_make(pool);
6041 SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
6042 *table_p = proplist;
6044 return SVN_NO_ERROR;
6047 svn_error_t *
6048 svn_fs_fs__delete_node_revision(svn_fs_t *fs,
6049 const svn_fs_id_t *id,
6050 apr_pool_t *pool)
6052 node_revision_t *noderev;
6054 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
6056 /* Delete any mutable property representation. */
6057 if (noderev->prop_rep && noderev->prop_rep->txn_id)
6058 SVN_ERR(svn_io_remove_file(path_txn_node_props(fs, id, pool), pool));
6060 /* Delete any mutable data representation. */
6061 if (noderev->data_rep && noderev->data_rep->txn_id
6062 && noderev->kind == svn_node_dir)
6063 SVN_ERR(svn_io_remove_file(path_txn_node_children(fs, id, pool), pool));
6065 return svn_io_remove_file(path_txn_node_rev(fs, id, pool), pool);
6070 /*** Revisions ***/
6072 svn_error_t *
6073 svn_fs_fs__revision_prop(svn_string_t **value_p,
6074 svn_fs_t *fs,
6075 svn_revnum_t rev,
6076 const char *propname,
6077 apr_pool_t *pool)
6079 apr_hash_t *table;
6081 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6082 SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
6084 *value_p = apr_hash_get(table, propname, APR_HASH_KEY_STRING);
6086 return SVN_NO_ERROR;
6090 /* Baton used for change_rev_prop_body below. */
6091 struct change_rev_prop_baton {
6092 svn_fs_t *fs;
6093 svn_revnum_t rev;
6094 const char *name;
6095 const svn_string_t *value;
6098 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
6099 write lock. This implements the svn_fs_fs__with_write_lock()
6100 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
6102 static svn_error_t *
6103 change_rev_prop_body(void *baton, apr_pool_t *pool)
6105 struct change_rev_prop_baton *cb = baton;
6106 apr_hash_t *table;
6108 SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
6110 apr_hash_set(table, cb->name, APR_HASH_KEY_STRING, cb->value);
6112 SVN_ERR(svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool));
6114 return SVN_NO_ERROR;
6117 svn_error_t *
6118 svn_fs_fs__change_rev_prop(svn_fs_t *fs,
6119 svn_revnum_t rev,
6120 const char *name,
6121 const svn_string_t *value,
6122 apr_pool_t *pool)
6124 struct change_rev_prop_baton cb;
6126 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6128 cb.fs = fs;
6129 cb.rev = rev;
6130 cb.name = name;
6131 cb.value = value;
6133 return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
6138 /*** Transactions ***/
6140 svn_error_t *
6141 svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
6142 const svn_fs_id_t **base_root_id_p,
6143 svn_fs_t *fs,
6144 const char *txn_name,
6145 apr_pool_t *pool)
6147 transaction_t *txn;
6148 SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
6149 *root_id_p = txn->root_id;
6150 *base_root_id_p = txn->base_id;
6151 return SVN_NO_ERROR;
6155 /* Generic transaction operations. */
6157 svn_error_t *
6158 svn_fs_fs__txn_prop(svn_string_t **value_p,
6159 svn_fs_txn_t *txn,
6160 const char *propname,
6161 apr_pool_t *pool)
6163 apr_hash_t *table;
6164 svn_fs_t *fs = txn->fs;
6166 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6167 SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
6169 *value_p = apr_hash_get(table, propname, APR_HASH_KEY_STRING);
6171 return SVN_NO_ERROR;
6174 svn_error_t *
6175 svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
6176 svn_fs_t *fs,
6177 svn_revnum_t rev,
6178 apr_uint32_t flags,
6179 apr_pool_t *pool)
6181 svn_string_t date;
6182 svn_prop_t prop;
6183 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
6185 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6187 SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
6189 /* Put a datestamp on the newly created txn, so we always know
6190 exactly how old it is. (This will help sysadmins identify
6191 long-abandoned txns that may need to be manually removed.) When
6192 a txn is promoted to a revision, this property will be
6193 automatically overwritten with a revision datestamp. */
6194 date.data = svn_time_to_cstring(apr_time_now(), pool);
6195 date.len = strlen(date.data);
6197 prop.name = SVN_PROP_REVISION_DATE;
6198 prop.value = &date;
6199 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6201 /* Set temporary txn props that represent the requested 'flags'
6202 behaviors. */
6203 if (flags & SVN_FS_TXN_CHECK_OOD)
6205 prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
6206 prop.value = svn_string_create("true", pool);
6207 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6210 if (flags & SVN_FS_TXN_CHECK_LOCKS)
6212 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
6213 prop.value = svn_string_create("true", pool);
6214 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6217 return svn_fs_fs__change_txn_props(*txn_p, props, pool);